From 2521c930d2eed650e0ba87b691621405430e811b Mon Sep 17 00:00:00 2001 From: Silas Davis <silas@monax.io> Date: Mon, 8 Jan 2018 13:16:06 +0000 Subject: [PATCH] Execution/EVM Execution: remove manager package/notion to simplify and improve structure (we only have one manager), remove pipe (busywork and not really a pipe), use account, blockchain, and state types and interfaces throughout. TxCache, BlockCache, and State. Encapsulate Tx execution in Executor interfaces. Implement Tendermint 0.12 ABCI app. Refactor tests to support new interfaces and make minor improvements to them. EVM: Use single definitions of state from account packages and use immutable Account instead of separate vm accounts. Emit vm debug in standard logging. Reorganise package. Use strongly typed Address. Signed-off-by: Silas Davis <silas@monax.io> --- definitions/pipe.go | 85 -- definitions/tendermint_pipe.go | 77 - execution/accounts.go | 246 +++ execution/block_cache.go | 386 +++++ execution/events/events.go | 51 + .../evm/abi/types.go | 0 execution/evm/accounts.go | 25 + execution/evm/asm/bc/helpers.go | 68 + .../opcodes => execution/evm/asm}/opcodes.go | 58 +- .../burrow-mint => execution}/evm/common.go | 2 +- execution/evm/events/events.go | 54 + execution/evm/fake_app_state.go | 87 ++ {manager/burrow-mint => execution}/evm/gas.go | 26 +- .../evm/log_event_test.go | 48 +- .../burrow-mint => execution}/evm/memory.go | 12 +- .../evm/memory_test.go | 2 +- .../burrow-mint => execution}/evm/native.go | 27 +- execution/evm/sha3/LICENSE | 27 + execution/evm/sha3/PATENTS | 22 + .../evm/sha3/keccakf.go | 0 .../evm/sha3/sha3.go | 0 .../burrow-mint => execution}/evm/snative.go | 274 ++-- .../evm/snative_test.go | 55 +- .../burrow-mint => execution}/evm/stack.go | 30 +- {manager/burrow-mint => execution}/evm/vm.go | 407 ++--- .../burrow-mint => execution}/evm/vm_test.go | 254 ++-- .../state => execution}/execution.go | 1130 +++++++------- execution/execution_test.go | 1329 +++++++++++++++++ {manager/burrow-mint => execution}/namereg.go | 100 +- .../burrow-mint/state => execution}/state.go | 543 ++++--- .../state => execution}/state_test.go | 623 ++++---- execution/transactor.go | 457 ++++++ execution/tx_cache.go | 128 ++ manager/burrow-mint/accounts.go | 228 --- manager/burrow-mint/burrow-mint.go | 216 --- manager/burrow-mint/evm/fake_app_state.go | 94 -- manager/burrow-mint/evm/types.go | 65 - manager/burrow-mint/pipe.go | 677 --------- manager/burrow-mint/state/block_cache.go | 304 ---- manager/burrow-mint/state/common.go | 32 - manager/burrow-mint/state/genesis_test.go | 172 --- manager/burrow-mint/state/permissions_test.go | 1314 ---------------- manager/burrow-mint/state/tx_cache.go | 218 --- manager/burrow-mint/state/tx_cache_test.go | 36 - manager/burrow-mint/transactor.go | 438 ------ manager/burrow-mint/version.go | 53 - manager/manager.go | 44 - manager/types/application.go | 104 -- rpc/v0/rest_server_pipe_test.go | 313 ---- util/snatives/cmd/main.go | 4 +- util/snatives/templates/solidity_templates.go | 12 +- .../templates/solidity_templates_test.go | 6 +- 52 files changed, 4820 insertions(+), 6143 deletions(-) delete mode 100644 definitions/pipe.go delete mode 100644 definitions/tendermint_pipe.go create mode 100644 execution/accounts.go create mode 100644 execution/block_cache.go create mode 100644 execution/events/events.go rename {manager/burrow-mint => execution}/evm/abi/types.go (100%) create mode 100644 execution/evm/accounts.go create mode 100644 execution/evm/asm/bc/helpers.go rename {manager/burrow-mint/evm/opcodes => execution/evm/asm}/opcodes.go (81%) rename {manager/burrow-mint => execution}/evm/common.go (98%) create mode 100644 execution/evm/events/events.go create mode 100644 execution/evm/fake_app_state.go rename {manager/burrow-mint => execution}/evm/gas.go (62%) rename {manager/burrow-mint => execution}/evm/log_event_test.go (65%) rename {manager/burrow-mint => execution}/evm/memory.go (91%) rename {manager/burrow-mint => execution}/evm/memory_test.go (99%) rename {manager/burrow-mint => execution}/evm/native.go (69%) create mode 100644 execution/evm/sha3/LICENSE create mode 100644 execution/evm/sha3/PATENTS rename {manager/burrow-mint => execution}/evm/sha3/keccakf.go (100%) rename {manager/burrow-mint => execution}/evm/sha3/sha3.go (100%) rename {manager/burrow-mint => execution}/evm/snative.go (62%) rename {manager/burrow-mint => execution}/evm/snative_test.go (76%) rename {manager/burrow-mint => execution}/evm/stack.go (84%) rename {manager/burrow-mint => execution}/evm/vm.go (67%) rename {manager/burrow-mint => execution}/evm/vm_test.go (65%) rename {manager/burrow-mint/state => execution}/execution.go (54%) create mode 100644 execution/execution_test.go rename {manager/burrow-mint => execution}/namereg.go (63%) rename {manager/burrow-mint/state => execution}/state.go (54%) rename {manager/burrow-mint/state => execution}/state_test.go (51%) create mode 100644 execution/transactor.go create mode 100644 execution/tx_cache.go delete mode 100644 manager/burrow-mint/accounts.go delete mode 100644 manager/burrow-mint/burrow-mint.go delete mode 100644 manager/burrow-mint/evm/fake_app_state.go delete mode 100644 manager/burrow-mint/evm/types.go delete mode 100644 manager/burrow-mint/pipe.go delete mode 100644 manager/burrow-mint/state/block_cache.go delete mode 100644 manager/burrow-mint/state/common.go delete mode 100644 manager/burrow-mint/state/genesis_test.go delete mode 100644 manager/burrow-mint/state/permissions_test.go delete mode 100644 manager/burrow-mint/state/tx_cache.go delete mode 100644 manager/burrow-mint/state/tx_cache_test.go delete mode 100644 manager/burrow-mint/transactor.go delete mode 100644 manager/burrow-mint/version.go delete mode 100644 manager/manager.go delete mode 100644 manager/types/application.go delete mode 100644 rpc/v0/rest_server_pipe_test.go diff --git a/definitions/pipe.go b/definitions/pipe.go deleted file mode 100644 index 286a0623..00000000 --- a/definitions/pipe.go +++ /dev/null @@ -1,85 +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 definitions - -// TODO: [ben] This respects the old Pipe interface from burrow. -// This made sense as a wrapper around the old Tendermint, but now -// it strongly reflects the internal details of old Tendermint outwards -// and provides little value as an abstraction. -// The refactor needed here for burrow-0.12.1 is to expose a language -// of transactions, block verification and accounts, grouping -// these interfaces into an Engine, Communicator, NameReg, Permissions (suggestion) - -import ( - account "github.com/hyperledger/burrow/account" - blockchain_types "github.com/hyperledger/burrow/blockchain/types" - consensus_types "github.com/hyperledger/burrow/consensus/types" - core_types "github.com/hyperledger/burrow/core/types" - types "github.com/hyperledger/burrow/core/types" - event "github.com/hyperledger/burrow/event" - logging_types "github.com/hyperledger/burrow/logging/types" - manager_types "github.com/hyperledger/burrow/manager/types" - "github.com/hyperledger/burrow/txs" -) - -type Pipe interface { - Accounts() Accounts - Blockchain() blockchain_types.Blockchain - Events() event.EventEmitter - NameReg() NameReg - Transactor() Transactor - // Hash of Genesis state - GenesisHash() []byte - Logger() logging_types.InfoTraceLogger - // NOTE: [ben] added to Pipe interface on 0.12 refactor - GetApplication() manager_types.Application - SetConsensusEngine(consensusEngine consensus_types.ConsensusEngine) error - GetConsensusEngine() consensus_types.ConsensusEngine - SetBlockchain(blockchain blockchain_types.Blockchain) error - GetBlockchain() blockchain_types.Blockchain - // Support for Tendermint RPC - GetTendermintPipe() (TendermintPipe, error) -} - -type Accounts interface { - GenPrivAccount() (*account.PrivAccount, error) - GenPrivAccountFromKey(privKey []byte) (*account.PrivAccount, error) - Accounts([]*event.FilterData) (*types.AccountList, error) - Account(address []byte) (*account.Account, error) - Storage(address []byte) (*types.Storage, error) - StorageAt(address, key []byte) (*types.StorageItem, error) -} - -type NameReg interface { - Entry(key string) (*core_types.NameRegEntry, error) - Entries([]*event.FilterData) (*types.ResultListNames, error) -} - -type Transactor interface { - Call(fromAddress, toAddress, data []byte) (*types.Call, error) - CallCode(fromAddress, code, data []byte) (*types.Call, error) - // Send(privKey, toAddress []byte, amount int64) (*types.Receipt, error) - // SendAndHold(privKey, toAddress []byte, amount int64) (*types.Receipt, error) - BroadcastTx(tx txs.Tx) (*txs.Receipt, error) - Transact(privKey, address, data []byte, gasLimit, - fee int64) (*txs.Receipt, error) - TransactAndHold(privKey, address, data []byte, gasLimit, - fee int64) (*txs.EventDataCall, error) - Send(privKey, toAddress []byte, amount int64) (*txs.Receipt, error) - SendAndHold(privKey, toAddress []byte, amount int64) (*txs.Receipt, error) - TransactNameReg(privKey []byte, name, data string, amount, - fee int64) (*txs.Receipt, error) - SignTx(tx txs.Tx, privAccounts []*account.PrivAccount) (txs.Tx, error) -} diff --git a/definitions/tendermint_pipe.go b/definitions/tendermint_pipe.go deleted file mode 100644 index faf78788..00000000 --- a/definitions/tendermint_pipe.go +++ /dev/null @@ -1,77 +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 definitions - -import ( - "github.com/hyperledger/burrow/account" - rpc_tm_types "github.com/hyperledger/burrow/rpc/tendermint/core/types" - "github.com/hyperledger/burrow/txs" -) - -// NOTE: [ben] TendermintPipe is the additional pipe to carry over -// the RPC exposed by old Tendermint on port `46657` (burrow-0.11.4 and before) -// This TendermintPipe interface should be deprecated and work towards a generic -// collection of RPC routes for burrow-1.0.0 - -type TendermintPipe interface { - Pipe - // Events - // Subscribe attempts to subscribe the listener identified by listenerId to - // the event named event. The Event result is written to rpcResponseWriter - // which must be non-blocking - Subscribe(eventId string, - rpcResponseWriter func(result rpc_tm_types.BurrowResult)) (*rpc_tm_types.ResultSubscribe, error) - Unsubscribe(subscriptionId string) (*rpc_tm_types.ResultUnsubscribe, error) - - // Net - Status() (*rpc_tm_types.ResultStatus, error) - NetInfo() (*rpc_tm_types.ResultNetInfo, error) - Genesis() (*rpc_tm_types.ResultGenesis, error) - ChainId() (*rpc_tm_types.ResultChainId, error) - - // Accounts - GetAccount(address []byte) (*rpc_tm_types.ResultGetAccount, error) - ListAccounts() (*rpc_tm_types.ResultListAccounts, error) - GetStorage(address, key []byte) (*rpc_tm_types.ResultGetStorage, error) - DumpStorage(address []byte) (*rpc_tm_types.ResultDumpStorage, error) - - // Call - Call(fromAddress, toAddress, data []byte) (*rpc_tm_types.ResultCall, error) - CallCode(fromAddress, code, data []byte) (*rpc_tm_types.ResultCall, error) - - // TODO: [ben] deprecate as we should not allow unsafe behaviour - // where a user is allowed to send a private key over the wire, - // especially unencrypted. - SignTransaction(tx txs.Tx, - privAccounts []*account.PrivAccount) (*rpc_tm_types.ResultSignTx, - error) - - // Name registry - GetName(name string) (*rpc_tm_types.ResultGetName, error) - ListNames() (*rpc_tm_types.ResultListNames, error) - - // Memory pool - BroadcastTxAsync(transaction txs.Tx) (*rpc_tm_types.ResultBroadcastTx, error) - BroadcastTxSync(transaction txs.Tx) (*rpc_tm_types.ResultBroadcastTx, error) - - // Blockchain - BlockchainInfo(minHeight, maxHeight, maxBlockLookback int) (*rpc_tm_types.ResultBlockchainInfo, error) - ListUnconfirmedTxs(maxTxs int) (*rpc_tm_types.ResultListUnconfirmedTxs, error) - GetBlock(height int) (*rpc_tm_types.ResultGetBlock, error) - - // Consensus - ListValidators() (*rpc_tm_types.ResultListValidators, error) - DumpConsensusState() (*rpc_tm_types.ResultDumpConsensusState, error) -} diff --git a/execution/accounts.go b/execution/accounts.go new file mode 100644 index 00000000..0e9d1bd1 --- /dev/null +++ b/execution/accounts.go @@ -0,0 +1,246 @@ +// 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 + +// Accounts is part of the pipe for BurrowMint and provides the implementation +// for the pipe to call into the BurrowMint application + +import ( + "bytes" + "encoding/hex" + "fmt" + "sync" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/event" +) + +// The accounts struct has methods for working with accounts. +type accounts struct { + state acm.StateIterable + filterFactory *event.FilterFactory +} + +// Accounts +type AccountList struct { + Accounts []acm.Account `json:"accounts"` +} + +// A contract account storage item. +type StorageItem struct { + Key []byte `json:"key"` + Value []byte `json:"value"` +} + +// Account storage +type Storage struct { + StorageRoot []byte `json:"storage_root"` + StorageItems []StorageItem `json:"storage_items"` +} + +// TODO: [Silas] there are various notes about using mempool (which I guess translates to CheckTx cache). We need +// to understand if this is the right thing to do, since we cannot guarantee stability of the check cache it doesn't +// seem like the right thing to do.... +func newAccounts(state acm.StateIterable) *accounts { + filterFactory := event.NewFilterFactory() + + filterFactory.RegisterFilterPool("code", &sync.Pool{ + New: func() interface{} { + return &AccountCodeFilter{} + }, + }) + + filterFactory.RegisterFilterPool("balance", &sync.Pool{ + New: func() interface{} { + return &AccountBalanceFilter{} + }, + }) + + return &accounts{ + state: state, + filterFactory: filterFactory, + } +} + +// Generate a new Private Key Account. +func (accs *accounts) GenPrivAccount() (*acm.ConcretePrivateAccount, error) { + pa := acm.GeneratePrivateAccount().ConcretePrivateAccount + return pa, nil +} + +// Generate a new Private Key Account. +func (accs *accounts) GenPrivAccountFromKey(privKey []byte) ( + *acm.ConcretePrivateAccount, error) { + if len(privKey) != 64 { + return nil, fmt.Errorf("Private key is not 64 bytes long.") + } + fmt.Printf("PK BYTES FROM ACCOUNTS: %x\n", privKey) + pa := acm.GeneratePrivateAccountFromPrivateKeyBytes(privKey).ConcretePrivateAccount + return pa, nil +} + +// Get all accounts. +func (accs *accounts) Accounts(fda []*event.FilterData) ( + *AccountList, error) { + accounts := make([]acm.Account, 0) + filter, err := accs.filterFactory.NewFilter(fda) + if err != nil { + return nil, fmt.Errorf("Error in query: " + err.Error()) + } + accs.state.IterateAccounts(func(account acm.Account) bool { + if filter.Match(account) { + accounts = append(accounts, account) + } + return false + }) + return &AccountList{accounts}, nil +} + +// Get an account. +func (accs *accounts) Account(address acm.Address) (acm.Account, error) { + acc, err := accs.state.GetAccount(address) // NOTE: we want to read from mempool! + if err != nil { + return nil, err + } + if acc == nil { + acc = accs.newAcc(address) + } + return acc, nil +} + +// Get the value stored at 'key' in the account with address 'address' +// Both the key and value is returned. +func (accs *accounts) StorageAt(address acm.Address, key []byte) (*StorageItem, + error) { + acc, err := accs.state.GetAccount(address) + if err != nil { + return nil, err + } + if acc == nil { + return &StorageItem{key, []byte{}}, nil + } + value, err := accs.state.GetStorage(address, binary.LeftPadWord256(key)) + if err != nil { + return nil, err + } + if value == binary.Zero256 { + return &StorageItem{key, []byte{}}, nil + } + return &StorageItem{key, value.UnpadLeft()}, nil +} + +// Get the storage of the account with address 'address'. +func (accs *accounts) Storage(address acm.Address) (*Storage, error) { + state := accs.state + acc, err := state.GetAccount(address) + if err != nil { + return nil, err + } + storageItems := make([]StorageItem, 0) + if acc == nil { + return &Storage{nil, storageItems}, nil + } + accs.state.IterateStorage(address, func(key, value binary.Word256) bool { + storageItems = append(storageItems, StorageItem{ + Key: key.UnpadLeft(), + Value: value.UnpadLeft(), + }) + return false + }) + return &Storage{acc.StorageRoot(), storageItems}, nil +} + +// Create a new account. +func (accs *accounts) newAcc(address acm.Address) acm.Account { + return (&acm.ConcreteAccount{ + Address: address, + Sequence: 0, + Balance: 0, + Code: nil, + StorageRoot: nil, + }).Account() +} + +// Filter for account code. +// Ops: == or != +// Could be used to match against nil, to see if an account is a contract account. +type AccountCodeFilter struct { + op string + value []byte + match func([]byte, []byte) bool +} + +func (this *AccountCodeFilter) Configure(fd *event.FilterData) error { + op := fd.Op + val, err := hex.DecodeString(fd.Value) + + if err != nil { + return fmt.Errorf("Wrong value type.") + } + if op == "==" { + this.match = func(a, b []byte) bool { + return bytes.Equal(a, b) + } + } else if op == "!=" { + this.match = func(a, b []byte) bool { + return !bytes.Equal(a, b) + } + } else { + return fmt.Errorf("Op: " + this.op + " is not supported for 'code' filtering") + } + this.op = op + this.value = val + return nil +} + +func (this *AccountCodeFilter) Match(v interface{}) bool { + acc, ok := v.(acm.Account) + if !ok { + return false + } + return this.match(acc.Code(), this.value) +} + +// Filter for account balance. +// Ops: All +type AccountBalanceFilter struct { + op string + value uint64 + match func(uint64, uint64) bool +} + +func (this *AccountBalanceFilter) Configure(fd *event.FilterData) error { + val, err := event.ParseNumberValue(fd.Value) + if err != nil { + return err + } + match, err2 := event.GetRangeFilter(fd.Op, "balance") + if err2 != nil { + return err2 + } + this.match = match + this.op = fd.Op + this.value = uint64(val) + return nil +} + +func (this *AccountBalanceFilter) Match(v interface{}) bool { + acc, ok := v.(acm.Account) + if !ok { + return false + } + return this.match(acc.Balance(), this.value) +} diff --git a/execution/block_cache.go b/execution/block_cache.go new file mode 100644 index 00000000..3c83e29e --- /dev/null +++ b/execution/block_cache.go @@ -0,0 +1,386 @@ +// 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 ( + "bytes" + "fmt" + "sort" + + acm "github.com/hyperledger/burrow/account" + . "github.com/hyperledger/burrow/binary" + + "sync" + + "github.com/tendermint/merkleeyes/iavl" + dbm "github.com/tendermint/tmlibs/db" + "github.com/tendermint/tmlibs/merkle" +) + +func makeStorage(db dbm.DB, root []byte) merkle.Tree { + storage := iavl.NewIAVLTree(1024, db) + storage.Load(root) + return storage +} + +var _ acm.StateWriter = &BlockCache{} + +var _ acm.StateIterable = &BlockCache{} + +// TODO: BlockCache badly needs a rewrite to remove database sharing with State and make it communicate using the +// Account interfaces like a proper person. As well as other oddities of decoupled storage and account state + +// The blockcache helps prevent unnecessary IAVLTree updates and garbage generation. +type BlockCache struct { + // We currently provide the RPC layer with access to read-only access to BlockCache via the StateIterable interface + // on BatchExecutor. However since read-only operations generate writes to the BlockCache in the current design + // we need a mutex here. Otherwise BlockCache ought to be used within a component that is responsible for serialising + // the operations on the BlockCache. + sync.RWMutex + db dbm.DB + backend *State + accounts map[acm.Address]accountInfo + storages map[acm.Address]map[Word256]storageInfo + names map[string]nameInfo +} + +func NewBlockCache(backend *State) *BlockCache { + return &BlockCache{ + // TODO: This is bad and probably the cause of various panics. Accounts themselves are written + // to the State 'backend' but updates to storage just skip that and write directly to the database + db: backend.db, + backend: backend, + accounts: make(map[acm.Address]accountInfo), + storages: make(map[acm.Address]map[Word256]storageInfo), + names: make(map[string]nameInfo), + } +} + +func (cache *BlockCache) State() *State { + return cache.backend +} + +//------------------------------------- +// BlockCache.account + +func (cache *BlockCache) GetAccount(addr acm.Address) (acm.Account, error) { + acc, _, removed, _ := cache.accounts[addr].unpack() + if removed { + return nil, nil + } else if acc != nil { + return acc, nil + } else { + acc, err := cache.backend.GetAccount(addr) + if err != nil { + return nil, err + } + cache.Lock() + defer cache.Unlock() + cache.accounts[addr] = accountInfo{acc, nil, false, false} + return acc, nil + } +} + +func (cache *BlockCache) UpdateAccount(acc acm.Account) error { + cache.Lock() + defer cache.Unlock() + addr := acc.Address() + _, storage, removed, _ := cache.accounts[addr].unpack() + if removed { + return fmt.Errorf("UpdateAccount on a removed account %s", addr) + } + cache.accounts[addr] = accountInfo{acc, storage, false, true} + return nil +} + +func (cache *BlockCache) RemoveAccount(addr acm.Address) error { + cache.Lock() + defer cache.Unlock() + _, _, removed, _ := cache.accounts[addr].unpack() + if removed { + return fmt.Errorf("RemoveAccount on a removed account %s", addr) + } + cache.accounts[addr] = accountInfo{nil, nil, true, false} + return nil +} + +func (cache *BlockCache) IterateAccounts(consumer func(acm.Account) (stop bool)) (bool, error) { + cache.RLock() + defer cache.RUnlock() + for _, info := range cache.accounts { + if consumer(info.account) { + return true, nil + } + } + return cache.backend.IterateAccounts(consumer) +} + +// BlockCache.account +//------------------------------------- +// BlockCache.storage + +func (cache *BlockCache) GetStorage(addr acm.Address, key Word256) (Word256, error) { + // Check cache + cache.RLock() + info, ok := cache.lookupStorage(addr, key) + cache.RUnlock() + if ok { + return info.value, nil + } + // Get or load storage + cache.RLock() + acc, storage, removed, dirty := cache.accounts[addr].unpack() + cache.RUnlock() + if removed { + return Zero256, fmt.Errorf("GetStorage on a removed account %s", addr) + } + cache.Lock() + defer cache.Unlock() + + if acc != nil && storage == nil { + storage = makeStorage(cache.db, acc.StorageRoot()) + cache.accounts[addr] = accountInfo{acc, storage, false, dirty} + } else if acc == nil { + return Zero256, nil + } + // Load and set cache + _, val, _ := storage.Get(key.Bytes()) + value := LeftPadWord256(val) + cache.setStorage(addr, key, storageInfo{value, false}) + return value, nil +} + +// NOTE: Set value to zero to removed from the trie. +func (cache *BlockCache) SetStorage(addr acm.Address, key Word256, value Word256) error { + cache.Lock() + defer cache.Unlock() + _, _, removed, _ := cache.accounts[addr].unpack() + if removed { + return fmt.Errorf("SetStorage on a removed account %s", addr) + } + cache.setStorage(addr, key, storageInfo{value, true}) + return nil +} + +func (cache *BlockCache) IterateStorage(address acm.Address, consumer func(key, value Word256) (stop bool)) (bool, error) { + cache.RLock() + defer cache.RUnlock() + // Try cache first for early exit + for key, info := range cache.storages[address] { + if consumer(key, info.value) { + return true, nil + } + } + + return cache.backend.IterateStorage(address, consumer) +} + +// BlockCache.storage +//------------------------------------- +// BlockCache.names + +func (cache *BlockCache) GetNameRegEntry(name string) *NameRegEntry { + cache.RLock() + entry, removed, _ := cache.names[name].unpack() + cache.RUnlock() + if removed { + return nil + } else if entry != nil { + return entry + } else { + entry = cache.backend.GetNameRegEntry(name) + cache.Lock() + cache.names[name] = nameInfo{entry, false, false} + cache.Unlock() + return entry + } +} + +func (cache *BlockCache) UpdateNameRegEntry(entry *NameRegEntry) { + cache.Lock() + defer cache.Unlock() + cache.names[entry.Name] = nameInfo{entry, false, true} +} + +func (cache *BlockCache) RemoveNameRegEntry(name string) { + cache.Lock() + defer cache.Unlock() + _, removed, _ := cache.names[name].unpack() + if removed { + panic("RemoveNameRegEntry on a removed entry") + } + cache.names[name] = nameInfo{nil, true, false} +} + +// BlockCache.names +//------------------------------------- + +// CONTRACT the updates are in deterministic order. +func (cache *BlockCache) Sync() { + cache.Lock() + defer cache.Unlock() + // Determine order for storage updates + // The address comes first so it'll be grouped. + storageKeys := make([]Tuple256, 0, len(cache.storages)) + for address, keyInfoMap := range cache.storages { + for key, _ := range keyInfoMap { + storageKeys = append(storageKeys, Tuple256{First: address.Word256(), Second: key}) + } + } + Tuple256Slice(storageKeys).Sort() + + // Update storage for all account/key. + // Later we'll iterate over all the users and save storage + update storage root. + var ( + curAddr acm.Address + curAcc acm.Account + curAccRemoved bool + curStorage merkle.Tree + ) + for _, storageKey := range storageKeys { + addrWord256, key := Tuple256Split(storageKey) + addr := acm.AddressFromWord256(addrWord256) + if addr != curAddr || curAcc == nil { + acc, storage, removed, _ := cache.accounts[addr].unpack() + if !removed && storage == nil { + storage = makeStorage(cache.db, acc.StorageRoot()) + } + curAddr = addr + curAcc = acc + curAccRemoved = removed + curStorage = storage + } + if curAccRemoved { + continue + } + value, dirty := cache.storages[acm.AddressFromWord256(storageKey.First)][storageKey.Second].unpack() + if !dirty { + continue + } + if value.IsZero() { + curStorage.Remove(key.Bytes()) + } else { + curStorage.Set(key.Bytes(), value.Bytes()) + cache.accounts[addr] = accountInfo{curAcc, curStorage, false, true} + } + } + + // Determine order for accounts + addrs := []acm.Address{} + for addr := range cache.accounts { + addrs = append(addrs, addr) + } + sort.Slice(addrs, func(i, j int) bool { + return addrs[i].String() < addrs[j].String() + }) + + // Update or delete accounts. + for _, addr := range addrs { + acc, storage, removed, dirty := cache.accounts[addr].unpack() + if removed { + cache.backend.RemoveAccount(addr) + } else { + if acc == nil { + continue + } + if storage != nil { + newStorageRoot := storage.Save() + if !bytes.Equal(newStorageRoot, acc.StorageRoot()) { + acc = acm.AsMutableAccount(acc).SetStorageRoot(newStorageRoot) + dirty = true + } + } + if dirty { + cache.backend.UpdateAccount(acc) + } + } + } + + // Determine order for names + // note names may be of any length less than some limit + nameStrs := []string{} + for nameStr := range cache.names { + nameStrs = append(nameStrs, nameStr) + } + sort.Strings(nameStrs) + + // Update or delete names. + for _, nameStr := range nameStrs { + entry, removed, dirty := cache.names[nameStr].unpack() + if removed { + removed := cache.backend.RemoveNameRegEntry(nameStr) + if !removed { + panic(fmt.Sprintf("Could not remove namereg entry to be removed: %s", nameStr)) + } + } else { + if entry == nil { + continue + } + if dirty { + cache.backend.UpdateNameRegEntry(entry) + } + } + } +} + +func (cache *BlockCache) lookupStorage(address acm.Address, key Word256) (storageInfo, bool) { + keyInfoMap, ok := cache.storages[address] + if !ok { + return storageInfo{}, false + } + info, ok := keyInfoMap[key] + return info, ok +} + +func (cache *BlockCache) setStorage(address acm.Address, key Word256, info storageInfo) { + keyInfoMap, ok := cache.storages[address] + if !ok { + keyInfoMap = make(map[Word256]storageInfo) + cache.storages[address] = keyInfoMap + } + keyInfoMap[key] = info +} + +//----------------------------------------------------------------------------- + +type accountInfo struct { + account acm.Account + storage merkle.Tree + removed bool + dirty bool +} + +func (accInfo accountInfo) unpack() (acm.Account, merkle.Tree, bool, bool) { + return accInfo.account, accInfo.storage, accInfo.removed, accInfo.dirty +} + +type storageInfo struct { + value Word256 + dirty bool +} + +func (stjInfo storageInfo) unpack() (Word256, bool) { + return stjInfo.value, stjInfo.dirty +} + +type nameInfo struct { + name *NameRegEntry + removed bool + dirty bool +} + +func (nInfo nameInfo) unpack() (*NameRegEntry, bool, bool) { + return nInfo.name, nInfo.removed, nInfo.dirty +} diff --git a/execution/events/events.go b/execution/events/events.go new file mode 100644 index 00000000..266a1860 --- /dev/null +++ b/execution/events/events.go @@ -0,0 +1,51 @@ +package events + +import ( + "encoding/json" + "fmt" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/txs" +) + +func EventStringAccInput(addr acm.Address) string { return fmt.Sprintf("Acc/%s/Input", addr) } +func EventStringAccOutput(addr acm.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 EventStringBond() string { return "Bond" } +func EventStringUnbond() string { return "Unbond" } +func EventStringRebond() string { return "Rebond" } + +// All txs fire EventDataTx, but only CallTx might have Return or Exception +type EventDataTx struct { + Tx txs.Tx `json:"tx"` + Return []byte `json:"return"` + Exception string `json:"exception"` +} + +type eventDataTx struct { + Tx txs.Wrapper `json:"tx"` + Return []byte `json:"return"` + Exception string `json:"exception"` +} + +func (edTx EventDataTx) MarshalJSON() ([]byte, error) { + model := eventDataTx{ + Tx: txs.Wrap(edTx.Tx), + Exception: edTx.Exception, + Return: edTx.Return, + } + return json.Marshal(model) +} + +func (edTx *EventDataTx) UnmarshalJSON(data []byte) error { + model := new(eventDataTx) + err := json.Unmarshal(data, model) + if err != nil { + return err + } + edTx.Tx = model.Tx.Unwrap() + edTx.Return = model.Return + edTx.Exception = model.Exception + return nil +} diff --git a/manager/burrow-mint/evm/abi/types.go b/execution/evm/abi/types.go similarity index 100% rename from manager/burrow-mint/evm/abi/types.go rename to execution/evm/abi/types.go diff --git a/execution/evm/accounts.go b/execution/evm/accounts.go new file mode 100644 index 00000000..19735838 --- /dev/null +++ b/execution/evm/accounts.go @@ -0,0 +1,25 @@ +package evm + +import ( + acm "github.com/hyperledger/burrow/account" + ptypes "github.com/hyperledger/burrow/permission/types" +) + +// Create a new account from a parent 'creator' account. The creator account will have its +// sequence number incremented +func DeriveNewAccount(creator acm.MutableAccount, permissions ptypes.AccountPermissions) acm.MutableAccount { + // Generate an address + sequence := creator.Sequence() + creator.IncSequence() + + addr := acm.NewContractAddress(creator.Address(), sequence) + + // Create account from address. + return (&acm.ConcreteAccount{ + Address: addr, + Balance: 0, + Code: nil, + Sequence: 0, + Permissions: permissions, + }).MutableAccount() +} diff --git a/execution/evm/asm/bc/helpers.go b/execution/evm/asm/bc/helpers.go new file mode 100644 index 00000000..4d8058d1 --- /dev/null +++ b/execution/evm/asm/bc/helpers.go @@ -0,0 +1,68 @@ +package bc + +import ( + "fmt" + + "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/execution/evm/asm" +) + +// Convenience function to allow us to mix bytes, ints, and OpCodes that +// represent bytes in an EVM assembly code to make assembly more readable. +// Also allows us to splice together assembly +// fragments because any []byte arguments are flattened in the result. +func Splice(bytelikes ...interface{}) []byte { + bytes := make([]byte, len(bytelikes)) + for i, bytelike := range bytelikes { + switch b := bytelike.(type) { + case byte: + bytes[i] = b + case asm.OpCode: + bytes[i] = byte(b) + case int: + bytes[i] = byte(b) + if int(bytes[i]) != b { + panic(fmt.Sprintf("The int %v does not fit inside a byte", b)) + } + case int64: + bytes[i] = byte(b) + if int64(bytes[i]) != b { + panic(fmt.Sprintf("The int64 %v does not fit inside a byte", b)) + } + case uint64: + bytes[i] = byte(b) + if uint64(bytes[i]) != b { + panic(fmt.Sprintf("The uint64 %v does not fit inside a byte", b)) + } + case binary.Word256: + return Concat(bytes[:i], b[:], Splice(bytelikes[i+1:]...)) + case binary.Word160: + return Concat(bytes[:i], b[:], Splice(bytelikes[i+1:]...)) + case account.Address: + return Concat(bytes[:i], b[:], Splice(bytelikes[i+1:]...)) + case []byte: + // splice + return Concat(bytes[:i], b, Splice(bytelikes[i+1:]...)) + default: + panic(fmt.Errorf("could not convert %s to a byte or sequence of bytes", bytelike)) + } + } + return bytes +} + +func Concat(bss ...[]byte) []byte { + offset := 0 + for _, bs := range bss { + offset += len(bs) + } + bytes := make([]byte, offset) + offset = 0 + for _, bs := range bss { + for i, b := range bs { + bytes[offset+i] = b + } + offset += len(bs) + } + return bytes +} diff --git a/manager/burrow-mint/evm/opcodes/opcodes.go b/execution/evm/asm/opcodes.go similarity index 81% rename from manager/burrow-mint/evm/opcodes/opcodes.go rename to execution/evm/asm/opcodes.go index a8139d82..bbe5dec9 100644 --- a/manager/burrow-mint/evm/opcodes/opcodes.go +++ b/execution/evm/asm/opcodes.go @@ -12,12 +12,11 @@ // See the License for the specific language governing permissions and // limitations under the License. -package opcodes +package asm import ( "fmt" - "github.com/hyperledger/burrow/word256" "gopkg.in/fatih/set.v0" ) @@ -341,6 +340,11 @@ var opCodeToString = map[OpCode]string{ SELFDESTRUCT: "SELFDESTRUCT", } +func OpCodeName(op OpCode) (name string, isOpcode bool) { + name, isOpcode = opCodeToString[op] + return name, isOpcode +} + func (o OpCode) String() string { str := opCodeToString[o] if len(str) == 0 { @@ -368,53 +372,3 @@ func AnalyzeJumpDests(code []byte) (dests *set.Set) { } return } - -// Convenience function to allow us to mix bytes, ints, and OpCodes that -// represent bytes in an EVM assembly code to make assembly more readable. -// Also allows us to splice together assembly -// fragments because any []byte arguments are flattened in the result. -func Bytecode(bytelikes ...interface{}) []byte { - bytes := make([]byte, len(bytelikes)) - for i, bytelike := range bytelikes { - switch b := bytelike.(type) { - case byte: - bytes[i] = b - case OpCode: - bytes[i] = byte(b) - case int: - bytes[i] = byte(b) - if int(bytes[i]) != b { - panic(fmt.Sprintf("The int %v does not fit inside a byte", b)) - } - case int64: - bytes[i] = byte(b) - if int64(bytes[i]) != b { - panic(fmt.Sprintf("The int64 %v does not fit inside a byte", b)) - } - case word256.Word256: - return Concat(bytes[:i], b[:], Bytecode(bytelikes[i+1:]...)) - case []byte: - // splice - return Concat(bytes[:i], b, Bytecode(bytelikes[i+1:]...)) - default: - panic("Only byte-like codes (and []byte sequences) can be used to form bytecode") - } - } - return bytes -} - -func Concat(bss ...[]byte) []byte { - offset := 0 - for _, bs := range bss { - offset += len(bs) - } - bytes := make([]byte, offset) - offset = 0 - for _, bs := range bss { - for i, b := range bs { - bytes[offset+i] = b - } - offset += len(bs) - } - return bytes -} diff --git a/manager/burrow-mint/evm/common.go b/execution/evm/common.go similarity index 98% rename from manager/burrow-mint/evm/common.go rename to execution/evm/common.go index fbbd4c51..e8fb7444 100644 --- a/manager/burrow-mint/evm/common.go +++ b/execution/evm/common.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vm +package evm import ( "math/big" diff --git a/execution/evm/events/events.go b/execution/evm/events/events.go new file mode 100644 index 00000000..4bc7a323 --- /dev/null +++ b/execution/evm/events/events.go @@ -0,0 +1,54 @@ +// 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 events + +import ( + "fmt" + + acm "github.com/hyperledger/burrow/account" + . "github.com/hyperledger/burrow/binary" +) + +// Functions to generate eventId strings + +func EventStringAccCall(addr acm.Address) string { return fmt.Sprintf("Acc/%s/Call", addr) } +func EventStringLogEvent(addr acm.Address) string { return fmt.Sprintf("Log/%s", addr) } + +//---------------------------------------- + +// EventDataCall fires when we call a contract, and when a contract calls another contract +type EventDataCall struct { + CallData *CallData `json:"call_data"` + Origin acm.Address `json:"origin"` + TxID []byte `json:"tx_id"` + Return []byte `json:"return"` + Exception string `json:"exception"` +} + +type CallData struct { + Caller acm.Address `json:"caller"` + Callee acm.Address `json:"callee"` + Data []byte `json:"data"` + Value uint64 `json:"value"` + Gas uint64 `json:"gas"` +} + +// EventDataLog fires when a contract executes the LOG opcode +type EventDataLog struct { + Address acm.Address `json:"address"` + Topics []Word256 `json:"topics"` + Data []byte `json:"data"` + Height uint64 `json:"height"` +} diff --git a/execution/evm/fake_app_state.go b/execution/evm/fake_app_state.go new file mode 100644 index 00000000..09a985ca --- /dev/null +++ b/execution/evm/fake_app_state.go @@ -0,0 +1,87 @@ +// 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 evm + +import ( + "fmt" + + "bytes" + + acm "github.com/hyperledger/burrow/account" + . "github.com/hyperledger/burrow/binary" +) + +type FakeAppState struct { + accounts map[acm.Address]acm.Account + storage map[string]Word256 +} + +var _ acm.StateWriter = &FakeAppState{} + +func (fas *FakeAppState) GetAccount(addr acm.Address) (acm.Account, error) { + account := fas.accounts[addr] + return account, nil +} + +func (fas *FakeAppState) UpdateAccount(account acm.Account) error { + fas.accounts[account.Address()] = account + return nil +} + +func (fas *FakeAppState) RemoveAccount(address acm.Address) error { + _, ok := fas.accounts[address] + if !ok { + panic(fmt.Sprintf("Invalid account addr: %s", address)) + } else { + // Remove account + delete(fas.accounts, address) + } + return nil +} + +func (fas *FakeAppState) GetStorage(addr acm.Address, key Word256) (Word256, error) { + _, ok := fas.accounts[addr] + if !ok { + panic(fmt.Sprintf("Invalid account addr: %s", addr)) + } + + value, ok := fas.storage[addr.String()+key.String()] + if ok { + return value, nil + } else { + return Zero256, nil + } +} + +func (fas *FakeAppState) SetStorage(addr acm.Address, key Word256, value Word256) error { + _, ok := fas.accounts[addr] + if !ok { + + fmt.Println("\n\n", fas.accountsDump()) + panic(fmt.Sprintf("Invalid account addr: %s", addr)) + } + + fas.storage[addr.String()+key.String()] = value + return nil +} + +func (fas *FakeAppState) accountsDump() string { + buf := new(bytes.Buffer) + fmt.Fprint(buf, "Dumping accounts...", "\n") + for _, acc := range fas.accounts { + fmt.Fprint(buf, acc.Address().String(), "\n") + } + return buf.String() +} diff --git a/manager/burrow-mint/evm/gas.go b/execution/evm/gas.go similarity index 62% rename from manager/burrow-mint/evm/gas.go rename to execution/evm/gas.go index 9ffc5e56..a764a5a5 100644 --- a/manager/burrow-mint/evm/gas.go +++ b/execution/evm/gas.go @@ -12,21 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vm +package evm const ( - GasSha3 int64 = 1 - GasGetAccount int64 = 1 - GasStorageUpdate int64 = 1 + GasSha3 uint64 = 1 + GasGetAccount uint64 = 1 + GasStorageUpdate uint64 = 1 - GasBaseOp int64 = 0 // TODO: make this 1 - GasStackOp int64 = 1 + GasBaseOp uint64 = 0 // TODO: make this 1 + GasStackOp uint64 = 1 - GasEcRecover int64 = 1 - GasSha256Word int64 = 1 - GasSha256Base int64 = 1 - GasRipemd160Word int64 = 1 - GasRipemd160Base int64 = 1 - GasIdentityWord int64 = 1 - GasIdentityBase int64 = 1 + GasEcRecover uint64 = 1 + GasSha256Word uint64 = 1 + GasSha256Base uint64 = 1 + GasRipemd160Word uint64 = 1 + GasRipemd160Base uint64 = 1 + GasIdentityWord uint64 = 1 + GasIdentityBase uint64 = 1 ) diff --git a/manager/burrow-mint/evm/log_event_test.go b/execution/evm/log_event_test.go similarity index 65% rename from manager/burrow-mint/evm/log_event_test.go rename to execution/evm/log_event_test.go index 7dd569a6..76b7dc75 100644 --- a/manager/burrow-mint/evm/log_event_test.go +++ b/execution/evm/log_event_test.go @@ -12,21 +12,23 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vm +package evm import ( "bytes" "reflect" "testing" - . "github.com/hyperledger/burrow/manager/burrow-mint/evm/opcodes" - "github.com/hyperledger/burrow/txs" - . "github.com/hyperledger/burrow/word256" - "github.com/tendermint/go-events" + acm "github.com/hyperledger/burrow/account" + . "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/event" + . "github.com/hyperledger/burrow/execution/evm/asm" + "github.com/hyperledger/burrow/execution/evm/events" + "github.com/hyperledger/burrow/logging/loggers" ) var expectedData = []byte{0x10} -var expectedHeight int64 = 0 +var expectedHeight uint64 = 0 var expectedTopics = []Word256{ Int64ToWord256(1), Int64ToWord256(2), @@ -38,28 +40,24 @@ func TestLog4(t *testing.T) { st := newAppState() // Create accounts - account1 := &Account{ - Address: LeftPadWord256(makeBytes(20)), - } - account2 := &Account{ - Address: LeftPadWord256(makeBytes(20)), - } - st.accounts[account1.Address.String()] = account1 - st.accounts[account2.Address.String()] = account2 + account1 := acm.ConcreteAccount{ + Address: acm.Address{1, 3, 5, 7, 9}, + }.MutableAccount() + account2 := acm.ConcreteAccount{ + Address: acm.Address{2, 4, 6, 8, 10}, + }.MutableAccount() + st.accounts[account1.Address()] = account1 + st.accounts[account2.Address()] = account2 - ourVm := NewVM(st, DefaultDynamicMemoryProvider, newParams(), Zero256, nil) + ourVm := NewVM(st, DefaultDynamicMemoryProvider, newParams(), acm.ZeroAddress, nil, logger) - eventSwitch := events.NewEventSwitch() - _, err := eventSwitch.Start() - if err != nil { - t.Errorf("Failed to start eventSwitch: %v", err) - } - eventID := txs.EventStringLogEvent(account2.Address.Postfix(20)) + eventSwitch := event.NewEmitter(loggers.NewNoopInfoTraceLogger()) + eventID := events.EventStringLogEvent(account2.Address()) doneChan := make(chan struct{}, 1) - eventSwitch.AddListenerForEvent("test", eventID, func(event events.EventData) { - logEvent := event.(txs.EventDataLog) + eventSwitch.Subscribe("test", eventID, func(eventData event.AnyEventData) { + logEvent := eventData.EventDataLog() // No need to test address as this event would not happen if it wasn't correct if !reflect.DeepEqual(logEvent.Topics, expectedTopics) { t.Errorf("Event topics are wrong. Got: %v. Expected: %v", logEvent.Topics, expectedTopics) @@ -75,7 +73,7 @@ func TestLog4(t *testing.T) { ourVm.SetFireable(eventSwitch) - var gas int64 = 100000 + var gas uint64 = 100000 mstore8 := byte(MSTORE8) push1 := byte(PUSH1) @@ -96,7 +94,7 @@ func TestLog4(t *testing.T) { stop, } - _, err = ourVm.Call(account1, account2, code, []byte{}, 0, &gas) + _, err := ourVm.Call(account1, account2, code, []byte{}, 0, &gas) <-doneChan if err != nil { t.Fatal(err) diff --git a/manager/burrow-mint/evm/memory.go b/execution/evm/memory.go similarity index 91% rename from manager/burrow-mint/evm/memory.go rename to execution/evm/memory.go index fc24dbd7..6eb8456c 100644 --- a/manager/burrow-mint/evm/memory.go +++ b/execution/evm/memory.go @@ -1,4 +1,4 @@ -package vm +package evm import ( "fmt" @@ -12,7 +12,7 @@ const ( // Change the length of this zero array to tweak the size of the block of zeros // written to the backing slice at a time when it is grown. A larger number may -// lead to less calls to append to achieve the desired capacity although it is +// lead to fewer calls to append to achieve the desired capacity although it is // unlikely to make a lot of difference. var zeroBlock []byte = make([]byte, 32) @@ -86,9 +86,9 @@ func (mem *dynamicMemory) Capacity() int64 { // memory (will not shrink). func (mem *dynamicMemory) ensureCapacity(newCapacity int64) error { if newCapacity > math.MaxInt32 { - // If we ever did want to then we would need to maintain multiple pages - // of memory - return fmt.Errorf("Cannot address memory beyond a maximum index "+ + // If we ever did want more than an int32 of space then we would need to + // maintain multiple pages of memory + return fmt.Errorf("cannot address memory beyond a maximum index "+ "of Int32 type (%v bytes)", math.MaxInt32) } newCapacityInt := int(newCapacity) @@ -97,7 +97,7 @@ func (mem *dynamicMemory) ensureCapacity(newCapacity int64) error { return nil } if newCapacity > mem.maximumCapacity { - return fmt.Errorf("Cannot grow memory because it would exceed the "+ + return fmt.Errorf("cannot grow memory because it would exceed the "+ "current maximum limit of %v bytes", mem.maximumCapacity) } // Ensure the backing array of slice is big enough diff --git a/manager/burrow-mint/evm/memory_test.go b/execution/evm/memory_test.go similarity index 99% rename from manager/burrow-mint/evm/memory_test.go rename to execution/evm/memory_test.go index 2477b32d..923cdda4 100644 --- a/manager/burrow-mint/evm/memory_test.go +++ b/execution/evm/memory_test.go @@ -1,4 +1,4 @@ -package vm +package evm import ( "testing" diff --git a/manager/burrow-mint/evm/native.go b/execution/evm/native.go similarity index 69% rename from manager/burrow-mint/evm/native.go rename to execution/evm/native.go index 17478055..198977c4 100644 --- a/manager/burrow-mint/evm/native.go +++ b/execution/evm/native.go @@ -12,13 +12,14 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vm +package evm import ( "crypto/sha256" - . "github.com/hyperledger/burrow/word256" - + acm "github.com/hyperledger/burrow/account" + . "github.com/hyperledger/burrow/binary" + logging_types "github.com/hyperledger/burrow/logging/types" "golang.org/x/crypto/ripemd160" ) @@ -52,10 +53,11 @@ func registerNativeContracts() { //----------------------------------------------------------------------------- -type NativeContract func(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) +type NativeContract func(state acm.StateWriter, caller acm.Account, input []byte, gas *uint64, + logger logging_types.InfoTraceLogger) (output []byte, err error) /* Removed due to C dependency -func ecrecoverFunc(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) { +func ecrecoverFunc(state State, caller *acm.Account, input []byte, gas *int64) (output []byte, err error) { // Deduct gas gasRequired := GasEcRecover if *gas < gasRequired { @@ -77,9 +79,10 @@ func ecrecoverFunc(appState AppState, caller *Account, input []byte, gas *int64) } */ -func sha256Func(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) { +func sha256Func(state acm.StateWriter, caller acm.Account, input []byte, gas *uint64, + logger logging_types.InfoTraceLogger) (output []byte, err error) { // Deduct gas - gasRequired := int64((len(input)+31)/32)*GasSha256Word + GasSha256Base + gasRequired := uint64((len(input)+31)/32)*GasSha256Word + GasSha256Base if *gas < gasRequired { return nil, ErrInsufficientGas } else { @@ -92,9 +95,10 @@ func sha256Func(appState AppState, caller *Account, input []byte, gas *int64) (o return hasher.Sum(nil), nil } -func ripemd160Func(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) { +func ripemd160Func(state acm.StateWriter, caller acm.Account, input []byte, gas *uint64, + logger logging_types.InfoTraceLogger) (output []byte, err error) { // Deduct gas - gasRequired := int64((len(input)+31)/32)*GasRipemd160Word + GasRipemd160Base + gasRequired := uint64((len(input)+31)/32)*GasRipemd160Word + GasRipemd160Base if *gas < gasRequired { return nil, ErrInsufficientGas } else { @@ -107,9 +111,10 @@ func ripemd160Func(appState AppState, caller *Account, input []byte, gas *int64) return LeftPadBytes(hasher.Sum(nil), 32), nil } -func identityFunc(appState AppState, caller *Account, input []byte, gas *int64) (output []byte, err error) { +func identityFunc(state acm.StateWriter, caller acm.Account, input []byte, gas *uint64, + logger logging_types.InfoTraceLogger) (output []byte, err error) { // Deduct gas - gasRequired := int64((len(input)+31)/32)*GasIdentityWord + GasIdentityBase + gasRequired := uint64((len(input)+31)/32)*GasIdentityWord + GasIdentityBase if *gas < gasRequired { return nil, ErrInsufficientGas } else { diff --git a/execution/evm/sha3/LICENSE b/execution/evm/sha3/LICENSE new file mode 100644 index 00000000..6a66aea5 --- /dev/null +++ b/execution/evm/sha3/LICENSE @@ -0,0 +1,27 @@ +Copyright (c) 2009 The Go Authors. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright +notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above +copyright notice, this list of conditions and the following disclaimer +in the documentation and/or other materials provided with the +distribution. + * Neither the name of Google Inc. nor the names of its +contributors may be used to endorse or promote products derived from +this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/execution/evm/sha3/PATENTS b/execution/evm/sha3/PATENTS new file mode 100644 index 00000000..73309904 --- /dev/null +++ b/execution/evm/sha3/PATENTS @@ -0,0 +1,22 @@ +Additional IP Rights Grant (Patents) + +"This implementation" means the copyrightable works distributed by +Google as part of the Go project. + +Google hereby grants to You a perpetual, worldwide, non-exclusive, +no-charge, royalty-free, irrevocable (except as stated in this section) +patent license to make, have made, use, offer to sell, sell, import, +transfer and otherwise run, modify and propagate the contents of this +implementation of Go, where such license applies only to those patent +claims, both currently owned or controlled by Google and acquired in +the future, licensable by Google that are necessarily infringed by this +implementation of Go. This grant does not include claims that would be +infringed only as a consequence of further modification of this +implementation. If you or your agent or exclusive licensee institute or +order or agree to the institution of patent litigation against any +entity (including a cross-claim or counterclaim in a lawsuit) alleging +that this implementation of Go or any code incorporated within this +implementation of Go constitutes direct or contributory patent +infringement, or inducement of patent infringement, then any patent +rights granted to you under this License for this implementation of Go +shall terminate as of the date such litigation is filed. diff --git a/manager/burrow-mint/evm/sha3/keccakf.go b/execution/evm/sha3/keccakf.go similarity index 100% rename from manager/burrow-mint/evm/sha3/keccakf.go rename to execution/evm/sha3/keccakf.go diff --git a/manager/burrow-mint/evm/sha3/sha3.go b/execution/evm/sha3/sha3.go similarity index 100% rename from manager/burrow-mint/evm/sha3/sha3.go rename to execution/evm/sha3/sha3.go diff --git a/manager/burrow-mint/evm/snative.go b/execution/evm/snative.go similarity index 62% rename from manager/burrow-mint/evm/snative.go rename to execution/evm/snative.go index 3f628e99..e98937ad 100644 --- a/manager/burrow-mint/evm/snative.go +++ b/execution/evm/snative.go @@ -12,19 +12,22 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vm +package evm import ( "fmt" - "github.com/hyperledger/burrow/common/sanity" - "github.com/hyperledger/burrow/manager/burrow-mint/evm/sha3" - ptypes "github.com/hyperledger/burrow/permission/types" - . "github.com/hyperledger/burrow/word256" - "strings" - "github.com/hyperledger/burrow/manager/burrow-mint/evm/abi" + acm "github.com/hyperledger/burrow/account" + . "github.com/hyperledger/burrow/binary" + "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" + logging_types "github.com/hyperledger/burrow/logging/types" + "github.com/hyperledger/burrow/permission" + ptypes "github.com/hyperledger/burrow/permission/types" ) // @@ -65,7 +68,7 @@ type SNativeFunctionDescription struct { func registerSNativeContracts() { for _, contract := range SNativeContracts() { - registeredNativeContracts[contract.AddressWord256()] = contract.Dispatch + registeredNativeContracts[contract.Address().Word256()] = contract.Dispatch } } @@ -91,7 +94,7 @@ func SNativeContracts() map[string]*SNativeContractDescription { abiArg("_role", roleTypeName), }, abiReturn("result", abi.BoolTypeName), - ptypes.AddRole, + permission.AddRole, addRole}, &SNativeFunctionDescription{` @@ -106,7 +109,7 @@ func SNativeContracts() map[string]*SNativeContractDescription { abiArg("_role", roleTypeName), }, abiReturn("result", abi.BoolTypeName), - ptypes.RmRole, + permission.RemoveRole, removeRole}, &SNativeFunctionDescription{` @@ -121,7 +124,7 @@ func SNativeContracts() map[string]*SNativeContractDescription { abiArg("_role", roleTypeName), }, abiReturn("result", abi.BoolTypeName), - ptypes.HasRole, + permission.HasRole, hasRole}, &SNativeFunctionDescription{` @@ -138,13 +141,13 @@ func SNativeContracts() map[string]*SNativeContractDescription { abiArg("_set", abi.BoolTypeName), }, abiReturn("result", permFlagTypeName), - ptypes.SetBase, + permission.SetBase, setBase}, &SNativeFunctionDescription{` * @notice Unsets the permissions flags for an account. Causes permissions being unset to fall through to global permissions. - * @param _account account address - * @param _permission the permissions flags to unset for the account + * @param _account account address + * @param _permission the permissions flags to unset for the account * @return result the effective permissions flags on the account after the call `, "unsetBase", @@ -152,7 +155,7 @@ func SNativeContracts() map[string]*SNativeContractDescription { abiArg("_account", abi.AddressTypeName), abiArg("_permission", permFlagTypeName)}, abiReturn("result", permFlagTypeName), - ptypes.UnsetBase, + permission.UnsetBase, unsetBase}, &SNativeFunctionDescription{` @@ -166,7 +169,7 @@ func SNativeContracts() map[string]*SNativeContractDescription { abiArg("_account", abi.AddressTypeName), abiArg("_permission", permFlagTypeName)}, abiReturn("result", abi.BoolTypeName), - ptypes.HasBase, + permission.HasBase, hasBase}, &SNativeFunctionDescription{` @@ -180,7 +183,7 @@ func SNativeContracts() map[string]*SNativeContractDescription { abiArg("_permission", permFlagTypeName), abiArg("_set", abi.BoolTypeName)}, abiReturn("result", permFlagTypeName), - ptypes.SetGlobal, + permission.SetGlobal, setGlobal}, ), } @@ -190,8 +193,8 @@ func SNativeContracts() map[string]*SNativeContractDescription { if _, ok := contractMap[contract.Name]; ok { // If this happens we have a pseudo compile time error that will be caught // on native.go init() - panic(fmt.Errorf("Duplicate contract with name %s defined. "+ - "Contract names must be unique.", contract.Name)) + panic(fmt.Errorf("duplicate contract with name %s defined. "+ + "Contract names must be unique", contract.Name)) } contractMap[contract.Name] = contract } @@ -208,7 +211,7 @@ func NewSNativeContract(comment, name string, fid := f.ID() otherF, ok := functionsByID[fid] if ok { - panic(fmt.Errorf("Function with ID %x already defined: %s", fid, + panic(fmt.Errorf("function with ID %x already defined: %s", fid, otherF)) } functionsByID[fid] = f @@ -224,8 +227,12 @@ func NewSNativeContract(comment, name string, // 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 -func (contract *SNativeContractDescription) Dispatch(appState AppState, - caller *Account, args []byte, gas *int64) (output []byte, err error) { +func (contract *SNativeContractDescription) Dispatch(state acm.StateWriter, caller acm.Account, + args []byte, gas *uint64, logger logging_types.InfoTraceLogger) (output []byte, err error) { + + logger = logger.WithPrefix(structure.ComponentKey, "SNatives"). + 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 %s bytes long", len(args)) @@ -236,41 +243,31 @@ func (contract *SNativeContractDescription) Dispatch(appState AppState, return nil, err } + logging.TraceMsg(logger, "Dispatching to function", "function_name", function.Name) + remainingArgs := args[abi.FunctionSelectorLength:] // check if we have permission to call this function - if !HasPermission(appState, caller, function.PermFlag) { - return nil, ErrInvalidPermission{caller.Address, function.Name} + if !HasPermission(state, caller, function.PermFlag) { + return nil, ErrLacksSNativePermission{caller.Address(), function.Name} } // ensure there are enough arguments if len(remainingArgs) != function.NArgs()*Word256Length { - return nil, fmt.Errorf("%s() takes %d arguments", function.Name, - function.NArgs()) + return nil, fmt.Errorf("%s() takes %d arguments but got %d (with %d bytes unconsumed - should be 0)", + function.Name, function.NArgs(), len(remainingArgs)/Word256Length, len(remainingArgs)%Word256Length) } // call the function - return function.F(appState, caller, remainingArgs, gas) + return function.F(state, caller, remainingArgs, gas, logger) } // We define the address of an SNative contact as the last 20 bytes of the sha3 // hash of its name -func (contract *SNativeContractDescription) Address() abi.Address { - var address abi.Address +func (contract *SNativeContractDescription) Address() (address acm.Address) { hash := sha3.Sha3([]byte(contract.Name)) copy(address[:], hash[len(hash)-abi.AddressLength:]) - return address -} - -// Get address as a byte slice -func (contract *SNativeContractDescription) AddressBytes() []byte { - address := contract.Address() - return address[:] -} - -// Get address as a left-padded Word256 -func (contract *SNativeContractDescription) AddressWord256() Word256 { - return LeftPadWord256(contract.AddressBytes()) + return } // Get function by calling identifier FunctionSelector @@ -278,7 +275,7 @@ func (contract *SNativeContractDescription) FunctionByID(id abi.FunctionSelector f, ok := contract.functionsByID[id] if !ok { return nil, - fmt.Errorf("Unknown SNative function with ID %x", id) + fmt.Errorf("unknown SNative function with ID %x", id) } return f, nil } @@ -290,7 +287,7 @@ func (contract *SNativeContractDescription) FunctionByName(name string) (*SNativ return f, nil } } - return nil, fmt.Errorf("Unknown SNative function with name %s", name) + return nil, fmt.Errorf("unknown SNative function with name %s", name) } // Get functions in order of declaration @@ -341,142 +338,197 @@ func abiReturn(name string, abiTypeName abi.TypeName) abi.Return { // Permission function defintions // TODO: catch errors, log em, return 0s to the vm (should some errors cause exceptions though?) -func hasBase(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { - addr, permNum := returnTwoArgs(args) - vmAcc := appState.GetAccount(addr) - if vmAcc == nil { - return nil, fmt.Errorf("Unknown account %X", addr) +func hasBase(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64, + logger logging_types.InfoTraceLogger) (output []byte, err error) { + + addrWord256, permNum := returnTwoArgs(args) + address := acm.AddressFromWord256(addrWord256) + acc, err := state.GetAccount(address) + if err != nil { + return nil, err + } + if acc == nil { + return nil, fmt.Errorf("unknown account %s", address) } permN := ptypes.PermFlag(Uint64FromWord256(permNum)) // already shifted if !ValidPermN(permN) { return nil, ptypes.ErrInvalidPermission(permN) } - permInt := byteFromBool(HasPermission(appState, vmAcc, permN)) - dbg.Printf("snative.hasBasePerm(0x%X, %b) = %v\n", addr.Postfix(20), permN, permInt) + hasPermission := HasPermission(state, acc, permN) + permInt := byteFromBool(hasPermission) + logger.Trace("function", "hasBase", "address", address.String(), + "perm_flag", fmt.Sprintf("%b", permN), "has_permission", hasPermission) return LeftPadWord256([]byte{permInt}).Bytes(), nil } -func setBase(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { - addr, permNum, permVal := returnThreeArgs(args) - vmAcc := appState.GetAccount(addr) - if vmAcc == nil { - return nil, fmt.Errorf("Unknown account %X", addr) +func setBase(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64, + logger logging_types.InfoTraceLogger) (output []byte, err error) { + + addrWord256, permNum, permVal := returnThreeArgs(args) + address := acm.AddressFromWord256(addrWord256) + acc, err := acm.GetMutableAccount(state, address) + if err != nil { + return nil, err + } + if acc == nil { + return nil, fmt.Errorf("unknown account %s", address) } permN := ptypes.PermFlag(Uint64FromWord256(permNum)) if !ValidPermN(permN) { return nil, ptypes.ErrInvalidPermission(permN) } permV := !permVal.IsZero() - if err = vmAcc.Permissions.Base.Set(permN, permV); err != nil { + if err = acc.MutablePermissions().Base.Set(permN, permV); err != nil { return nil, err } - appState.UpdateAccount(vmAcc) - dbg.Printf("snative.setBasePerm(0x%X, %b, %v)\n", addr.Postfix(20), permN, permV) - return effectivePermBytes(vmAcc.Permissions.Base, globalPerms(appState)), nil + state.UpdateAccount(acc) + logger.Trace("function", "setBase", "address", address.String(), + "permission_flag", fmt.Sprintf("%b", permN), + "permission_value", permV) + return effectivePermBytes(acc.Permissions().Base, globalPerms(state)), nil } -func unsetBase(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { - addr, permNum := returnTwoArgs(args) - vmAcc := appState.GetAccount(addr) - if vmAcc == nil { - return nil, fmt.Errorf("Unknown account %X", addr) +func unsetBase(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64, + logger logging_types.InfoTraceLogger) (output []byte, err error) { + + addrWord256, permNum := returnTwoArgs(args) + address := acm.AddressFromWord256(addrWord256) + acc, err := acm.GetMutableAccount(state, address) + if err != nil { + return nil, err + } + if acc == nil { + return nil, fmt.Errorf("unknown account %s", address) } permN := ptypes.PermFlag(Uint64FromWord256(permNum)) if !ValidPermN(permN) { return nil, ptypes.ErrInvalidPermission(permN) } - if err = vmAcc.Permissions.Base.Unset(permN); err != nil { + if err = acc.MutablePermissions().Base.Unset(permN); err != nil { return nil, err } - appState.UpdateAccount(vmAcc) - dbg.Printf("snative.unsetBasePerm(0x%X, %b)\n", addr.Postfix(20), permN) - return effectivePermBytes(vmAcc.Permissions.Base, globalPerms(appState)), nil + state.UpdateAccount(acc) + logger.Trace("function", "unsetBase", "address", address.String(), + "perm_flag", fmt.Sprintf("%b", permN), + "permission_flag", fmt.Sprintf("%b", permN)) + + return effectivePermBytes(acc.Permissions().Base, globalPerms(state)), nil } -func setGlobal(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { +func setGlobal(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64, + logger logging_types.InfoTraceLogger) (output []byte, err error) { + permNum, permVal := returnTwoArgs(args) - vmAcc := appState.GetAccount(ptypes.GlobalPermissionsAddress256) - if vmAcc == nil { - sanity.PanicSanity("cant find the global permissions account") + acc, err := acm.GetMutableAccount(state, permission.GlobalPermissionsAddress) + if err != nil { + return nil, err + } + if acc == nil { + panic("cant find the global permissions account") } permN := ptypes.PermFlag(Uint64FromWord256(permNum)) if !ValidPermN(permN) { return nil, ptypes.ErrInvalidPermission(permN) } permV := !permVal.IsZero() - if err = vmAcc.Permissions.Base.Set(permN, permV); err != nil { + if err = acc.MutablePermissions().Base.Set(permN, permV); err != nil { return nil, err } - appState.UpdateAccount(vmAcc) - dbg.Printf("snative.setGlobalPerm(%b, %v)\n", permN, permV) - return permBytes(vmAcc.Permissions.Base.ResultantPerms()), nil + state.UpdateAccount(acc) + logger.Trace("function", "setGlobal", + "permission_flag", fmt.Sprintf("%b", permN), + "permission_value", permV) + return permBytes(acc.Permissions().Base.ResultantPerms()), nil } -func hasRole(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { - addr, role := returnTwoArgs(args) - vmAcc := appState.GetAccount(addr) - if vmAcc == nil { - return nil, fmt.Errorf("Unknown account %X", addr) +func hasRole(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64, + logger logging_types.InfoTraceLogger) (output []byte, err error) { + + addrWord256, role := returnTwoArgs(args) + address := acm.AddressFromWord256(addrWord256) + acc, err := state.GetAccount(address) + if err != nil { + return nil, err + } + if acc == nil { + return nil, fmt.Errorf("unknown account %s", address) } roleS := string(role.Bytes()) - permInt := byteFromBool(vmAcc.Permissions.HasRole(roleS)) - dbg.Printf("snative.hasRole(0x%X, %s) = %v\n", addr.Postfix(20), roleS, permInt > 0) + hasRole := acc.Permissions().HasRole(roleS) + permInt := byteFromBool(hasRole) + logger.Trace("function", "hasRole", "address", address.String(), + "role", roleS, + "has_role", hasRole) return LeftPadWord256([]byte{permInt}).Bytes(), nil } -func addRole(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { - addr, role := returnTwoArgs(args) - vmAcc := appState.GetAccount(addr) - if vmAcc == nil { - return nil, fmt.Errorf("Unknown account %X", addr) +func addRole(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64, + logger logging_types.InfoTraceLogger) (output []byte, err error) { + + addrWord256, role := returnTwoArgs(args) + address := acm.AddressFromWord256(addrWord256) + acc, err := acm.GetMutableAccount(state, address) + if err != nil { + return nil, err + } + if acc == nil { + return nil, fmt.Errorf("unknown account %s", address) } roleS := string(role.Bytes()) - permInt := byteFromBool(vmAcc.Permissions.AddRole(roleS)) - appState.UpdateAccount(vmAcc) - dbg.Printf("snative.addRole(0x%X, %s) = %v\n", addr.Postfix(20), roleS, permInt > 0) + roleAdded := acc.MutablePermissions().AddRole(roleS) + permInt := byteFromBool(roleAdded) + state.UpdateAccount(acc) + logger.Trace("function", "addRole", "address", address.String(), + "role", roleS, + "role_added", roleAdded) return LeftPadWord256([]byte{permInt}).Bytes(), nil } -func removeRole(appState AppState, caller *Account, args []byte, gas *int64) (output []byte, err error) { - addr, role := returnTwoArgs(args) - vmAcc := appState.GetAccount(addr) - if vmAcc == nil { - return nil, fmt.Errorf("Unknown account %X", addr) +func removeRole(state acm.StateWriter, caller acm.Account, args []byte, gas *uint64, + logger logging_types.InfoTraceLogger) (output []byte, err error) { + + addrWord256, role := returnTwoArgs(args) + address := acm.AddressFromWord256(addrWord256) + acc, err := acm.GetMutableAccount(state, address) + if err != nil { + return nil, err + } + if acc == nil { + return nil, fmt.Errorf("unknown account %s", address) } roleS := string(role.Bytes()) - permInt := byteFromBool(vmAcc.Permissions.RmRole(roleS)) - appState.UpdateAccount(vmAcc) - dbg.Printf("snative.rmRole(0x%X, %s) = %v\n", addr.Postfix(20), roleS, permInt > 0) + roleRemoved := acc.MutablePermissions().RmRole(roleS) + permInt := byteFromBool(roleRemoved) + state.UpdateAccount(acc) + logger.Trace("function", "removeRole", "address", address.String(), + "role", roleS, + "role_removed", roleRemoved) return LeftPadWord256([]byte{permInt}).Bytes(), nil } //------------------------------------------------------------------------------------------------ // Errors and utility funcs -type ErrInvalidPermission struct { - Address Word256 +type ErrLacksSNativePermission struct { + Address acm.Address SNative string } -func (e ErrInvalidPermission) Error() string { - return fmt.Sprintf("Account %X does not have permission snative.%s", e.Address.Postfix(20), e.SNative) +func (e ErrLacksSNativePermission) Error() string { + return fmt.Sprintf("account %s does not have SNative function call permission: %s", e.Address, e.SNative) } // Checks if a permission flag is valid (a known base chain or snative permission) func ValidPermN(n ptypes.PermFlag) bool { - return n <= ptypes.TopPermFlag + return n <= permission.TopPermFlag } // Get the global BasePermissions -func globalPerms(appState AppState) ptypes.BasePermissions { - vmAcc := appState.GetAccount(ptypes.GlobalPermissionsAddress256) - if vmAcc == nil { - sanity.PanicSanity("cant find the global permissions account") - } - return vmAcc.Permissions.Base +func globalPerms(state acm.StateWriter) ptypes.BasePermissions { + return permission.GlobalAccountPermissions(state).Base } -// Compute the effective permissions from an Account's BasePermissions by +// Compute the effective permissions from an acm.Account's BasePermissions by // taking the bitwise or with the global BasePermissions resultant permissions func effectivePermBytes(basePerms ptypes.BasePermissions, globalPerms ptypes.BasePermissions) []byte { diff --git a/manager/burrow-mint/evm/snative_test.go b/execution/evm/snative_test.go similarity index 76% rename from manager/burrow-mint/evm/snative_test.go rename to execution/evm/snative_test.go index 18006ad6..ab173b97 100644 --- a/manager/burrow-mint/evm/snative_test.go +++ b/execution/evm/snative_test.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vm +package evm import ( "encoding/hex" @@ -20,11 +20,13 @@ import ( "strings" - "github.com/hyperledger/burrow/manager/burrow-mint/evm/abi" - . "github.com/hyperledger/burrow/manager/burrow-mint/evm/opcodes" - "github.com/hyperledger/burrow/manager/burrow-mint/evm/sha3" + acm "github.com/hyperledger/burrow/account" + . "github.com/hyperledger/burrow/binary" + "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" ptypes "github.com/hyperledger/burrow/permission/types" - . "github.com/hyperledger/burrow/word256" "github.com/stretchr/testify/assert" ) @@ -63,12 +65,12 @@ func TestPermissionsContractSignatures(t *testing.T) { func TestSNativeContractDescription_Dispatch(t *testing.T) { contract := SNativeContracts()["Permissions"] state := newAppState() - caller := &Account{ - Address: addr(1, 1, 1), - } - grantee := &Account{ - Address: addr(2, 2, 2), - } + caller := acm.ConcreteAccount{ + Address: acm.Address{1, 1, 1}, + }.MutableAccount() + grantee := acm.ConcreteAccount{ + Address: acm.Address{2, 2, 2}, + }.MutableAccount() state.UpdateAccount(grantee) function, err := contract.FunctionByName("addRole") @@ -76,20 +78,20 @@ func TestSNativeContractDescription_Dispatch(t *testing.T) { t.Fatalf("Could not get function: %s", err) } funcID := function.ID() - gas := int64(1000) + gas := uint64(1000) // Should fail since we have no permissions - retValue, err := contract.Dispatch(state, caller, Bytecode(funcID[:], - grantee.Address, permFlagToWord256(ptypes.CreateAccount)), &gas) - assert.Error(t, err) - if err != nil { - assert.Contains(t, err.Error(), "does not have permission") + retValue, err := contract.Dispatch(state, caller, bc.Splice(funcID[:], + grantee.Address(), permFlagToWord256(permission.CreateAccount)), &gas, logger) + if !assert.Error(t, err, "Should fail due to lack of permissions") { + return } + assert.IsType(t, err, ErrLacksSNativePermission{}) // Grant all permissions and dispatch should success - caller.Permissions = allAccountPermissions() - retValue, err = contract.Dispatch(state, caller, Bytecode(funcID[:], - grantee.Address, permFlagToWord256(ptypes.CreateAccount)), &gas) + caller.SetPermissions(allAccountPermissions()) + retValue, err = contract.Dispatch(state, caller, bc.Splice(funcID[:], + grantee.Address().Word256(), permFlagToWord256(permission.CreateAccount)), &gas, logger) assert.NoError(t, err) assert.Equal(t, retValue, LeftPadBytes([]byte{1}, 32)) } @@ -97,7 +99,7 @@ func TestSNativeContractDescription_Dispatch(t *testing.T) { func TestSNativeContractDescription_Address(t *testing.T) { contract := NewSNativeContract("A comment", "CoolButVeryLongNamedContractOfDoom") - assert.Equal(t, sha3.Sha3(([]byte)(contract.Name))[12:], contract.AddressBytes()) + assert.Equal(t, sha3.Sha3(([]byte)(contract.Name))[12:], contract.Address().Bytes()) } // @@ -105,7 +107,8 @@ func TestSNativeContractDescription_Address(t *testing.T) { // func assertFunctionIDSignature(t *testing.T, contract *SNativeContractDescription, funcIDHex string, expectedSignature string) { - function, err := contract.FunctionByID(funcIDFromHex(t, funcIDHex)) + fromHex := funcIDFromHex(t, funcIDHex) + function, err := contract.FunctionByID(fromHex) assert.NoError(t, err, "Error retrieving SNativeFunctionDescription with ID %s", funcIDHex) if err == nil { @@ -127,15 +130,11 @@ func permFlagToWord256(permFlag ptypes.PermFlag) Word256 { return Uint64ToWord256(uint64(permFlag)) } -func addr(rightBytes ...uint8) Word256 { - return LeftPadWord256(rightBytes) -} - func allAccountPermissions() ptypes.AccountPermissions { return ptypes.AccountPermissions{ Base: ptypes.BasePermissions{ - Perms: ptypes.AllPermFlags, - SetBit: ptypes.AllPermFlags, + Perms: permission.AllPermFlags, + SetBit: permission.AllPermFlags, }, Roles: []string{}, } diff --git a/manager/burrow-mint/evm/stack.go b/execution/evm/stack.go similarity index 84% rename from manager/burrow-mint/evm/stack.go rename to execution/evm/stack.go index f9d48986..0d8a8f3f 100644 --- a/manager/burrow-mint/evm/stack.go +++ b/execution/evm/stack.go @@ -12,14 +12,12 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vm +package evm import ( "fmt" - "github.com/hyperledger/burrow/common/math/integral" - "github.com/hyperledger/burrow/common/sanity" - . "github.com/hyperledger/burrow/word256" + . "github.com/hyperledger/burrow/binary" ) // Not goroutine safe @@ -27,11 +25,11 @@ type Stack struct { data []Word256 ptr int - gas *int64 + gas *uint64 err *error } -func NewStack(capacity int, gas *int64, err *error) *Stack { +func NewStack(capacity int, gas *uint64, err *error) *Stack { return &Stack{ data: make([]Word256, capacity), ptr: 0, @@ -40,7 +38,7 @@ func NewStack(capacity int, gas *int64, err *error) *Stack { } } -func (st *Stack) useGas(gasToUse int64) { +func (st *Stack) useGas(gasToUse uint64) { if *st.gas > gasToUse { *st.gas -= gasToUse } else { @@ -64,10 +62,10 @@ func (st *Stack) Push(d Word256) { st.ptr++ } -// currently only called after Sha3 +// currently only called after sha3.Sha3 func (st *Stack) PushBytes(bz []byte) { if len(bz) != 32 { - sanity.PanicSanity("Invalid bytes size: expected 32") + panic("Invalid bytes size: expected 32") } st.Push(LeftPadWord256(bz)) } @@ -76,6 +74,10 @@ func (st *Stack) Push64(i int64) { st.Push(Int64ToWord256(i)) } +func (st *Stack) PushU64(i uint64) { + st.Push(Uint64ToWord256(i)) +} + func (st *Stack) Pop() Word256 { st.useGas(GasStackOp) if st.ptr == 0 { @@ -95,6 +97,11 @@ func (st *Stack) Pop64() int64 { return Int64FromWord256(d) } +func (st *Stack) PopU64() uint64 { + d := st.Pop() + return Uint64FromWord256(d) +} + func (st *Stack) Len() int { return st.ptr } @@ -129,7 +136,10 @@ func (st *Stack) Peek() Word256 { func (st *Stack) Print(n int) { fmt.Println("### stack ###") if st.ptr > 0 { - nn := integral.MinInt(n, st.ptr) + nn := n + if st.ptr < n { + nn = st.ptr + } for j, i := 0, st.ptr-1; i > st.ptr-1-nn; i-- { fmt.Printf("%-3d %X\n", j, st.data[i]) j += 1 diff --git a/manager/burrow-mint/evm/vm.go b/execution/evm/vm.go similarity index 67% rename from manager/burrow-mint/evm/vm.go rename to execution/evm/vm.go index 60ae0f64..2813a5e5 100644 --- a/manager/burrow-mint/evm/vm.go +++ b/execution/evm/vm.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vm +package evm import ( "bytes" @@ -20,14 +20,17 @@ import ( "fmt" "math/big" - "github.com/hyperledger/burrow/common/sanity" - . "github.com/hyperledger/burrow/manager/burrow-mint/evm/opcodes" - "github.com/hyperledger/burrow/manager/burrow-mint/evm/sha3" + acm "github.com/hyperledger/burrow/account" + . "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/event" + . "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/logging/structure" + logging_types "github.com/hyperledger/burrow/logging/types" + "github.com/hyperledger/burrow/permission" ptypes "github.com/hyperledger/burrow/permission/types" - "github.com/hyperledger/burrow/txs" - . "github.com/hyperledger/burrow/word256" - - "github.com/tendermint/go-events" ) var ( @@ -59,46 +62,43 @@ const ( callStackCapacity = 100 // TODO ensure usage. ) -type Debug bool - -var dbg Debug - -func SetDebug(d bool) { - dbg = Debug(d) -} - -func (d Debug) Printf(s string, a ...interface{}) { - if d { - fmt.Printf(s, a...) - } +type Params struct { + BlockHeight uint64 + BlockHash Word256 + BlockTime int64 + GasLimit uint64 } type VM struct { - appState AppState + state acm.StateWriter memoryProvider func() Memory params Params - origin Word256 + origin acm.Address txid []byte - - callDepth int - - evc events.Fireable + callDepth int + evc event.Fireable + logger logging_types.InfoTraceLogger } -func NewVM(appState AppState, memoryProvider func() Memory, params Params, - origin Word256, txid []byte) *VM { +func NewVM(state acm.StateWriter, memoryProvider func() Memory, params Params, origin acm.Address, txid []byte, + logger logging_types.InfoTraceLogger) *VM { return &VM{ - appState: appState, + state: state, memoryProvider: memoryProvider, params: params, origin: origin, callDepth: 0, txid: txid, + logger: logger.WithPrefix(structure.ComponentKey, "EVM"), } } -// satisfies events.Eventable -func (vm *VM) SetFireable(evc events.Fireable) { +func (vm *VM) Debugf(format string, a ...interface{}) { + logging.TraceMsg(vm.logger, fmt.Sprintf(format, a...)) +} + +// satisfies go_events.Eventable +func (vm *VM) SetFireable(evc event.Fireable) { vm.evc = evc } @@ -108,24 +108,25 @@ func (vm *VM) SetFireable(evc events.Fireable) { // on known permissions and panics else) // If the perm is not defined in the acc nor set by default in GlobalPermissions, // this function returns false. -func HasPermission(appState AppState, acc *Account, perm ptypes.PermFlag) bool { - v, err := acc.Permissions.Base.Get(perm) +func HasPermission(state acm.StateWriter, acc acm.Account, perm ptypes.PermFlag) bool { + v, err := acc.Permissions().Base.Get(perm) if _, ok := err.(ptypes.ErrValueNotSet); ok { - if appState == nil { + if state == nil { // In this case the permission is unknown return false } - return HasPermission(nil, appState.GetAccount(ptypes.GlobalPermissionsAddress256), perm) + return HasPermission(nil, permission.GlobalPermissionsAccount(state), perm) } return v } -func (vm *VM) fireCallEvent(exception *string, output *[]byte, caller, callee *Account, input []byte, value int64, gas *int64) { +func (vm *VM) fireCallEvent(exception *string, output *[]byte, callerAddress, calleeAddress acm.Address, input []byte, value uint64, gas *uint64) { // fire the post call event (including exception if applicable) if vm.evc != nil { - vm.evc.FireEvent(txs.EventStringAccCall(callee.Address.Postfix(20)), txs.EventDataCall{ - &txs.CallData{caller.Address.Postfix(20), callee.Address.Postfix(20), input, value, *gas}, - vm.origin.Postfix(20), + stringAccCall := events.EventStringAccCall(calleeAddress) + vm.evc.Fire(stringAccCall, events.EventDataCall{ + &events.CallData{Caller: callerAddress, Callee: calleeAddress, Data: input, Value: value, Gas: *gas}, + vm.origin, vm.txid, *output, *exception, @@ -133,17 +134,17 @@ func (vm *VM) fireCallEvent(exception *string, output *[]byte, caller, callee *A } } -// CONTRACT appState is aware of caller and callee, so we can just mutate them. +// 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. // 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(caller, callee *Account, code, input []byte, value int64, gas *int64) (output []byte, err error) { +func (vm *VM) Call(caller, callee acm.MutableAccount, code, input []byte, value uint64, gas *uint64) (output []byte, err error) { exception := new(string) // fire the post call event (including exception if applicable) - defer vm.fireCallEvent(exception, &output, caller, callee, input, value, gas) + defer vm.fireCallEvent(exception, &output, caller.Address(), callee.Address(), input, value, gas) if err = transfer(caller, callee, value); err != nil { *exception = err.Error() @@ -159,7 +160,7 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value int64, gas err := transfer(callee, caller, value) if err != nil { // data has been corrupted in ram - sanity.PanicCrisis("Could not return value to caller") + panic("Could not return value to caller") } } } @@ -171,12 +172,12 @@ func (vm *VM) Call(caller, callee *Account, code, input []byte, value int64, gas // 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(caller, callee *Account, code, input []byte, value int64, gas *int64) (output []byte, err error) { +func (vm *VM) DelegateCall(caller, callee acm.MutableAccount, code, input []byte, value uint64, gas *uint64) (output []byte, err error) { exception := new(string) // fire the post call event (including exception if applicable) // NOTE: [ben] hotfix for issue 371; - // introduce event EventStringAccDelegateCall Acc/%X/DelegateCall + // introduce event EventStringAccDelegateCall Acc/%s/DelegateCall // defer vm.fireCallEvent(exception, &output, caller, callee, input, value, gas) // DelegateCall does not transfer the value to the callee. @@ -195,7 +196,7 @@ func (vm *VM) DelegateCall(caller, callee *Account, code, input []byte, value in // Try to deduct gasToUse from gasLeft. If ok return false, otherwise // set err and return true. -func useGasNegative(gasLeft *int64, gasToUse int64, err *error) bool { +func useGasNegative(gasLeft *uint64, gasToUse uint64, err *error) bool { if *gasLeft >= gasToUse { *gasLeft -= gasToUse return false @@ -206,8 +207,9 @@ func useGasNegative(gasLeft *int64, gasToUse int64, err *error) bool { } // Just like Call() but does not transfer 'value' or modify the callDepth. -func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas *int64) (output []byte, err error) { - dbg.Printf("(%d) (%X) %X (code=%d) gas: %v (d) %X\n", vm.callDepth, caller.Address[:4], callee.Address, len(callee.Code), *gas, input) +func (vm *VM) call(caller, callee acm.MutableAccount, code, input []byte, value uint64, gas *uint64) (output []byte, err error) { + vm.Debugf("(%d) (%X) %X (code=%d) gas: %v (d) %X\n", vm.callDepth, caller.Address().Bytes()[:4], callee.Address(), + len(callee.Code()), *gas, input) var ( pc int64 = 0 @@ -222,7 +224,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } var op = codeGetOp(code, pc) - dbg.Printf("(pc) %-3d (op) %-14s (st) %-4d ", pc, op.String(), stack.Len()) + vm.Debugf("(pc) %-3d (op) %-14s (st) %-4d ", pc, op.String(), stack.Len()) switch op { @@ -233,7 +235,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas sum := new(big.Int).Add(xb, yb) res := LeftPadWord256(U256(sum).Bytes()) stack.Push(res) - dbg.Printf(" %v + %v = %v (%X)\n", xb, yb, sum, res) + vm.Debugf(" %v + %v = %v (%X)\n", xb, yb, sum, res) case MUL: // 0x02 x, y := stack.Pop(), stack.Pop() @@ -242,7 +244,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas prod := new(big.Int).Mul(xb, yb) res := LeftPadWord256(U256(prod).Bytes()) stack.Push(res) - dbg.Printf(" %v * %v = %v (%X)\n", xb, yb, prod, res) + vm.Debugf(" %v * %v = %v (%X)\n", xb, yb, prod, res) case SUB: // 0x03 x, y := stack.Pop(), stack.Pop() @@ -251,69 +253,69 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas diff := new(big.Int).Sub(xb, yb) res := LeftPadWord256(U256(diff).Bytes()) stack.Push(res) - dbg.Printf(" %v - %v = %v (%X)\n", xb, yb, diff, res) + vm.Debugf(" %v - %v = %v (%X)\n", xb, yb, diff, res) case DIV: // 0x04 x, y := stack.Pop(), stack.Pop() if y.IsZero() { stack.Push(Zero256) - dbg.Printf(" %x / %x = %v\n", x, y, 0) + vm.Debugf(" %x / %x = %v\n", x, y, 0) } else { xb := new(big.Int).SetBytes(x[:]) yb := new(big.Int).SetBytes(y[:]) div := new(big.Int).Div(xb, yb) res := LeftPadWord256(U256(div).Bytes()) stack.Push(res) - dbg.Printf(" %v / %v = %v (%X)\n", xb, yb, div, res) + vm.Debugf(" %v / %v = %v (%X)\n", xb, yb, div, res) } case SDIV: // 0x05 x, y := stack.Pop(), stack.Pop() if y.IsZero() { stack.Push(Zero256) - dbg.Printf(" %x / %x = %v\n", x, y, 0) + vm.Debugf(" %x / %x = %v\n", x, y, 0) } else { xb := S256(new(big.Int).SetBytes(x[:])) yb := S256(new(big.Int).SetBytes(y[:])) div := new(big.Int).Div(xb, yb) res := LeftPadWord256(U256(div).Bytes()) stack.Push(res) - dbg.Printf(" %v / %v = %v (%X)\n", xb, yb, div, res) + vm.Debugf(" %v / %v = %v (%X)\n", xb, yb, div, res) } case MOD: // 0x06 x, y := stack.Pop(), stack.Pop() if y.IsZero() { stack.Push(Zero256) - dbg.Printf(" %v %% %v = %v\n", x, y, 0) + vm.Debugf(" %v %% %v = %v\n", x, y, 0) } else { xb := new(big.Int).SetBytes(x[:]) yb := new(big.Int).SetBytes(y[:]) mod := new(big.Int).Mod(xb, yb) res := LeftPadWord256(U256(mod).Bytes()) stack.Push(res) - dbg.Printf(" %v %% %v = %v (%X)\n", xb, yb, mod, res) + vm.Debugf(" %v %% %v = %v (%X)\n", xb, yb, mod, res) } case SMOD: // 0x07 x, y := stack.Pop(), stack.Pop() if y.IsZero() { stack.Push(Zero256) - dbg.Printf(" %v %% %v = %v\n", x, y, 0) + vm.Debugf(" %v %% %v = %v\n", x, y, 0) } else { xb := S256(new(big.Int).SetBytes(x[:])) yb := S256(new(big.Int).SetBytes(y[:])) mod := new(big.Int).Mod(xb, yb) res := LeftPadWord256(U256(mod).Bytes()) stack.Push(res) - dbg.Printf(" %v %% %v = %v (%X)\n", xb, yb, mod, res) + vm.Debugf(" %v %% %v = %v (%X)\n", xb, yb, mod, res) } case ADDMOD: // 0x08 x, y, z := stack.Pop(), stack.Pop(), stack.Pop() if z.IsZero() { stack.Push(Zero256) - dbg.Printf(" %v %% %v = %v\n", x, y, 0) + vm.Debugf(" %v %% %v = %v\n", x, y, 0) } else { xb := new(big.Int).SetBytes(x[:]) yb := new(big.Int).SetBytes(y[:]) @@ -322,7 +324,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas mod := new(big.Int).Mod(add, zb) res := LeftPadWord256(U256(mod).Bytes()) stack.Push(res) - dbg.Printf(" %v + %v %% %v = %v (%X)\n", + vm.Debugf(" %v + %v %% %v = %v (%X)\n", xb, yb, zb, mod, res) } @@ -330,7 +332,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas x, y, z := stack.Pop(), stack.Pop(), stack.Pop() if z.IsZero() { stack.Push(Zero256) - dbg.Printf(" %v %% %v = %v\n", x, y, 0) + vm.Debugf(" %v %% %v = %v\n", x, y, 0) } else { xb := new(big.Int).SetBytes(x[:]) yb := new(big.Int).SetBytes(y[:]) @@ -339,7 +341,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas mod := new(big.Int).Mod(mul, zb) res := LeftPadWord256(U256(mod).Bytes()) stack.Push(res) - dbg.Printf(" %v * %v %% %v = %v (%X)\n", + vm.Debugf(" %v * %v %% %v = %v (%X)\n", xb, yb, zb, mod, res) } @@ -350,7 +352,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas pow := new(big.Int).Exp(xb, yb, big.NewInt(0)) res := LeftPadWord256(U256(pow).Bytes()) stack.Push(res) - dbg.Printf(" %v ** %v = %v (%X)\n", xb, yb, pow, res) + vm.Debugf(" %v ** %v = %v (%X)\n", xb, yb, pow, res) case SIGNEXTEND: // 0x0B back := stack.Pop() @@ -367,7 +369,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas numb.Add(numb, mask) } res := LeftPadWord256(U256(numb).Bytes()) - dbg.Printf(" = %v (%X)", numb, res) + vm.Debugf(" = %v (%X)", numb, res) stack.Push(res) } @@ -377,10 +379,10 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas yb := new(big.Int).SetBytes(y[:]) if xb.Cmp(yb) < 0 { stack.Push64(1) - dbg.Printf(" %v < %v = %v\n", xb, yb, 1) + vm.Debugf(" %v < %v = %v\n", xb, yb, 1) } else { stack.Push(Zero256) - dbg.Printf(" %v < %v = %v\n", xb, yb, 0) + vm.Debugf(" %v < %v = %v\n", xb, yb, 0) } case GT: // 0x11 @@ -389,10 +391,10 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas yb := new(big.Int).SetBytes(y[:]) if xb.Cmp(yb) > 0 { stack.Push64(1) - dbg.Printf(" %v > %v = %v\n", xb, yb, 1) + vm.Debugf(" %v > %v = %v\n", xb, yb, 1) } else { stack.Push(Zero256) - dbg.Printf(" %v > %v = %v\n", xb, yb, 0) + vm.Debugf(" %v > %v = %v\n", xb, yb, 0) } case SLT: // 0x12 @@ -401,10 +403,10 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas yb := S256(new(big.Int).SetBytes(y[:])) if xb.Cmp(yb) < 0 { stack.Push64(1) - dbg.Printf(" %v < %v = %v\n", xb, yb, 1) + vm.Debugf(" %v < %v = %v\n", xb, yb, 1) } else { stack.Push(Zero256) - dbg.Printf(" %v < %v = %v\n", xb, yb, 0) + vm.Debugf(" %v < %v = %v\n", xb, yb, 0) } case SGT: // 0x13 @@ -413,30 +415,30 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas yb := S256(new(big.Int).SetBytes(y[:])) if xb.Cmp(yb) > 0 { stack.Push64(1) - dbg.Printf(" %v > %v = %v\n", xb, yb, 1) + vm.Debugf(" %v > %v = %v\n", xb, yb, 1) } else { stack.Push(Zero256) - dbg.Printf(" %v > %v = %v\n", xb, yb, 0) + vm.Debugf(" %v > %v = %v\n", xb, yb, 0) } case EQ: // 0x14 x, y := stack.Pop(), stack.Pop() if bytes.Equal(x[:], y[:]) { stack.Push64(1) - dbg.Printf(" %X == %X = %v\n", x, y, 1) + vm.Debugf(" %X == %X = %v\n", x, y, 1) } else { stack.Push(Zero256) - dbg.Printf(" %X == %X = %v\n", x, y, 0) + vm.Debugf(" %X == %X = %v\n", x, y, 0) } case ISZERO: // 0x15 x := stack.Pop() if x.IsZero() { stack.Push64(1) - dbg.Printf(" %v == 0 = %v\n", x, 1) + vm.Debugf(" %v == 0 = %v\n", x, 1) } else { stack.Push(Zero256) - dbg.Printf(" %v == 0 = %v\n", x, 0) + vm.Debugf(" %v == 0 = %v\n", x, 0) } case AND: // 0x16 @@ -446,7 +448,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas z[i] = x[i] & y[i] } stack.Push(z) - dbg.Printf(" %X & %X = %X\n", x, y, z) + vm.Debugf(" %X & %X = %X\n", x, y, z) case OR: // 0x17 x, y := stack.Pop(), stack.Pop() @@ -455,7 +457,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas z[i] = x[i] | y[i] } stack.Push(z) - dbg.Printf(" %X | %X = %X\n", x, y, z) + vm.Debugf(" %X | %X = %X\n", x, y, z) case XOR: // 0x18 x, y := stack.Pop(), stack.Pop() @@ -464,7 +466,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas z[i] = x[i] ^ y[i] } stack.Push(z) - dbg.Printf(" %X ^ %X = %X\n", x, y, z) + vm.Debugf(" %X ^ %X = %X\n", x, y, z) case NOT: // 0x19 x := stack.Pop() @@ -473,7 +475,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas z[i] = ^x[i] } stack.Push(z) - dbg.Printf(" !%X = %X\n", x, z) + vm.Debugf(" !%X = %X\n", x, z) case BYTE: // 0x1A idx, val := stack.Pop64(), stack.Pop() @@ -482,7 +484,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas res = val[idx] } stack.Push64(int64(res)) - dbg.Printf(" => 0x%X\n", res) + vm.Debugf(" => 0x%X\n", res) case SHA3: // 0x20 if useGasNegative(gas, GasSha3, &err) { @@ -491,41 +493,44 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas offset, size := stack.Pop64(), stack.Pop64() data, memErr := memory.Read(offset, size) if memErr != nil { - dbg.Printf(" => Memory err: %s", memErr) + vm.Debugf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } data = sha3.Sha3(data) stack.PushBytes(data) - dbg.Printf(" => (%v) %X\n", size, data) + vm.Debugf(" => (%v) %X\n", size, data) case ADDRESS: // 0x30 - stack.Push(callee.Address) - dbg.Printf(" => %X\n", callee.Address) + stack.Push(callee.Address().Word256()) + vm.Debugf(" => %X\n", callee.Address()) case BALANCE: // 0x31 addr := stack.Pop() if useGasNegative(gas, GasGetAccount, &err) { return nil, err } - acc := vm.appState.GetAccount(addr) + acc, errAcc := vm.state.GetAccount(acm.AddressFromWord256(addr)) + if errAcc != nil { + return nil, firstErr(err, errAcc) + } if acc == nil { return nil, firstErr(err, ErrUnknownAddress) } - balance := acc.Balance - stack.Push64(balance) - dbg.Printf(" => %v (%X)\n", balance, addr) + balance := acc.Balance() + stack.PushU64(balance) + vm.Debugf(" => %v (%X)\n", balance, addr) case ORIGIN: // 0x32 - stack.Push(vm.origin) - dbg.Printf(" => %X\n", vm.origin) + stack.Push(vm.origin.Word256()) + vm.Debugf(" => %X\n", vm.origin) case CALLER: // 0x33 - stack.Push(caller.Address) - dbg.Printf(" => %X\n", caller.Address) + stack.Push(caller.Address().Word256()) + vm.Debugf(" => %X\n", caller.Address()) case CALLVALUE: // 0x34 - stack.Push64(value) - dbg.Printf(" => %v\n", value) + stack.PushU64(value) + vm.Debugf(" => %v\n", value) case CALLDATALOAD: // 0x35 offset := stack.Pop64() @@ -535,11 +540,11 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } res := LeftPadWord256(data) stack.Push(res) - dbg.Printf(" => 0x%X\n", res) + vm.Debugf(" => 0x%X\n", res) case CALLDATASIZE: // 0x36 stack.Push64(int64(len(input))) - dbg.Printf(" => %d\n", len(input)) + vm.Debugf(" => %d\n", len(input)) case CALLDATACOPY: // 0x37 memOff := stack.Pop64() @@ -551,15 +556,15 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } memErr := memory.Write(memOff, data) if memErr != nil { - dbg.Printf(" => Memory err: %s", memErr) + vm.Debugf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - dbg.Printf(" => [%v, %v, %v] %X\n", memOff, inputOff, length, data) + vm.Debugf(" => [%v, %v, %v] %X\n", memOff, inputOff, length, data) case CODESIZE: // 0x38 l := int64(len(code)) stack.Push64(l) - dbg.Printf(" => %d\n", l) + vm.Debugf(" => %d\n", l) case CODECOPY: // 0x39 memOff := stack.Pop64() @@ -571,47 +576,53 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } memErr := memory.Write(memOff, data) if memErr != nil { - dbg.Printf(" => Memory err: %s", memErr) + vm.Debugf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - dbg.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) + vm.Debugf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) case GASPRICE_DEPRECATED: // 0x3A stack.Push(Zero256) - dbg.Printf(" => %X (GASPRICE IS DEPRECATED)\n") + vm.Debugf(" => %X (GASPRICE IS DEPRECATED)\n") case EXTCODESIZE: // 0x3B addr := stack.Pop() if useGasNegative(gas, GasGetAccount, &err) { return nil, err } - acc := vm.appState.GetAccount(addr) + acc, errAcc := vm.state.GetAccount(acm.AddressFromWord256(addr)) + if errAcc != nil { + return nil, firstErr(err, errAcc) + } if acc == nil { if _, ok := registeredNativeContracts[addr]; !ok { return nil, firstErr(err, ErrUnknownAddress) } - dbg.Printf(" => returning code size of 1 to indicated existence of native contract at %X\n", addr) + vm.Debugf(" => returning code size of 1 to indicated existence of native contract at %X\n", addr) stack.Push(One256) } else { - code := acc.Code + code := acc.Code() l := int64(len(code)) stack.Push64(l) - dbg.Printf(" => %d\n", l) + vm.Debugf(" => %d\n", l) } case EXTCODECOPY: // 0x3C addr := stack.Pop() if useGasNegative(gas, GasGetAccount, &err) { return nil, err } - acc := vm.appState.GetAccount(addr) + acc, errAcc := vm.state.GetAccount(acm.AddressFromWord256(addr)) + if errAcc != nil { + return nil, firstErr(err, errAcc) + } if acc == nil { if _, ok := registeredNativeContracts[addr]; ok { - dbg.Printf(" => attempted to copy native contract at %X but this is not supported\n", addr) + 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, ErrUnknownAddress) } - code := acc.Code + code := acc.Code() memOff := stack.Pop64() codeOff := stack.Pop64() length := stack.Pop64() @@ -621,81 +632,84 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } memErr := memory.Write(memOff, data) if memErr != nil { - dbg.Printf(" => Memory err: %s", memErr) + vm.Debugf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - dbg.Printf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) + vm.Debugf(" => [%v, %v, %v] %X\n", memOff, codeOff, length, data) case BLOCKHASH: // 0x40 stack.Push(Zero256) - dbg.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes()) + vm.Debugf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes()) case COINBASE: // 0x41 stack.Push(Zero256) - dbg.Printf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes()) + vm.Debugf(" => 0x%X (NOT SUPPORTED)\n", stack.Peek().Bytes()) case TIMESTAMP: // 0x42 time := vm.params.BlockTime stack.Push64(int64(time)) - dbg.Printf(" => 0x%X\n", time) + vm.Debugf(" => 0x%X\n", time) case BLOCKHEIGHT: // 0x43 - number := int64(vm.params.BlockHeight) - stack.Push64(number) - dbg.Printf(" => 0x%X\n", number) + number := vm.params.BlockHeight + stack.PushU64(number) + vm.Debugf(" => 0x%X\n", number) case GASLIMIT: // 0x45 - stack.Push64(vm.params.GasLimit) - dbg.Printf(" => %v\n", vm.params.GasLimit) + stack.PushU64(vm.params.GasLimit) + vm.Debugf(" => %v\n", vm.params.GasLimit) case POP: // 0x50 popped := stack.Pop() - dbg.Printf(" => 0x%X\n", popped) + vm.Debugf(" => 0x%X\n", popped) case MLOAD: // 0x51 offset := stack.Pop64() data, memErr := memory.Read(offset, 32) if memErr != nil { - dbg.Printf(" => Memory err: %s", memErr) + vm.Debugf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } stack.Push(LeftPadWord256(data)) - dbg.Printf(" => 0x%X @ 0x%X\n", data, offset) + vm.Debugf(" => 0x%X @ 0x%X\n", data, offset) case MSTORE: // 0x52 offset, data := stack.Pop64(), stack.Pop() memErr := memory.Write(offset, data.Bytes()) if memErr != nil { - dbg.Printf(" => Memory err: %s", memErr) + vm.Debugf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - dbg.Printf(" => 0x%X @ 0x%X\n", data, offset) + vm.Debugf(" => 0x%X @ 0x%X\n", data, offset) case MSTORE8: // 0x53 offset, val := stack.Pop64(), byte(stack.Pop64()&0xFF) memErr := memory.Write(offset, []byte{val}) if memErr != nil { - dbg.Printf(" => Memory err: %s", memErr) + vm.Debugf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - dbg.Printf(" => [%v] 0x%X\n", offset, val) + vm.Debugf(" => [%v] 0x%X\n", offset, val) case SLOAD: // 0x54 loc := stack.Pop() - data := vm.appState.GetStorage(callee.Address, loc) + data, errSto := vm.state.GetStorage(callee.Address(), loc) + if errSto != nil { + return nil, firstErr(err, errSto) + } stack.Push(data) - dbg.Printf(" {0x%X : 0x%X}\n", loc, data) + vm.Debugf(" {0x%X : 0x%X}\n", loc, data) case SSTORE: // 0x55 loc, data := stack.Pop(), stack.Pop() if useGasNegative(gas, GasStorageUpdate, &err) { return nil, err } - vm.appState.SetStorage(callee.Address, loc, data) - dbg.Printf(" {0x%X : 0x%X}\n", loc, data) + vm.state.SetStorage(callee.Address(), loc, data) + vm.Debugf(" {0x%X : 0x%X}\n", loc, data) case JUMP: // 0x56 - if err = jump(code, stack.Pop64(), &pc); err != nil { + if err = vm.jump(code, stack.Pop64(), &pc); err != nil { return nil, err } continue @@ -703,12 +717,12 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas case JUMPI: // 0x57 pos, cond := stack.Pop64(), stack.Pop() if !cond.IsZero() { - if err = jump(code, pos, &pc); err != nil { + if err = vm.jump(code, pos, &pc); err != nil { return nil, err } continue } - dbg.Printf(" ~> false\n") + vm.Debugf(" ~> false\n") case PC: // 0x58 stack.Push64(pc) @@ -719,14 +733,14 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas // this offset. capacity := memory.Capacity() stack.Push64(capacity) - dbg.Printf(" => 0x%X\n", capacity) + vm.Debugf(" => 0x%X\n", capacity) case GAS: // 0x5A - stack.Push64(*gas) - dbg.Printf(" => %X\n", *gas) + stack.PushU64(*gas) + vm.Debugf(" => %X\n", *gas) case JUMPDEST: // 0x5B - dbg.Printf("\n") + vm.Debugf("\n") // Do nothing case PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, PUSH12, PUSH13, PUSH14, PUSH15, PUSH16, PUSH17, PUSH18, PUSH19, PUSH20, PUSH21, PUSH22, PUSH23, PUSH24, PUSH25, PUSH26, PUSH27, PUSH28, PUSH29, PUSH30, PUSH31, PUSH32: @@ -738,18 +752,18 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas res := LeftPadWord256(codeSegment) stack.Push(res) pc += a - dbg.Printf(" => 0x%X\n", res) + vm.Debugf(" => 0x%X\n", res) //stack.Print(10) case DUP1, DUP2, DUP3, DUP4, DUP5, DUP6, DUP7, DUP8, DUP9, DUP10, DUP11, DUP12, DUP13, DUP14, DUP15, DUP16: n := int(op - DUP1 + 1) stack.Dup(n) - dbg.Printf(" => [%d] 0x%X\n", n, stack.Peek().Bytes()) + vm.Debugf(" => [%d] 0x%X\n", n, stack.Peek().Bytes()) case SWAP1, SWAP2, SWAP3, SWAP4, SWAP5, SWAP6, SWAP7, SWAP8, SWAP9, SWAP10, SWAP11, SWAP12, SWAP13, SWAP14, SWAP15, SWAP16: n := int(op - SWAP1 + 2) stack.Swap(n) - dbg.Printf(" => [%d] %X\n", n, stack.Peek()) + vm.Debugf(" => [%d] %X\n", n, stack.Peek()) //stack.Print(10) case LOG0, LOG1, LOG2, LOG3, LOG4: @@ -761,41 +775,42 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } data, memErr := memory.Read(offset, size) if memErr != nil { - dbg.Printf(" => Memory err: %s", memErr) + vm.Debugf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } if vm.evc != nil { - eventID := txs.EventStringLogEvent(callee.Address.Postfix(20)) + eventID := events.EventStringLogEvent(callee.Address()) fmt.Printf("eventID: %s\n", eventID) - log := txs.EventDataLog{ - callee.Address, - topics, - data, - vm.params.BlockHeight, + log := events.EventDataLog{ + Address: callee.Address(), + Topics: topics, + Data: data, + Height: vm.params.BlockHeight, } - vm.evc.FireEvent(eventID, log) + vm.evc.Fire(eventID, log) } - dbg.Printf(" => T:%X D:%X\n", topics, data) + vm.Debugf(" => T:%X D:%X\n", topics, data) case CREATE: // 0xF0 - if !HasPermission(vm.appState, callee, ptypes.CreateContract) { + if !HasPermission(vm.state, callee, permission.CreateContract) { return nil, ErrPermission{"create_contract"} } - contractValue := stack.Pop64() + contractValue := stack.PopU64() offset, size := stack.Pop64(), stack.Pop64() input, memErr := memory.Read(offset, size) if memErr != nil { - dbg.Printf(" => Memory err: %s", memErr) + vm.Debugf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } // Check balance - if callee.Balance < contractValue { + if callee.Balance() < uint64(contractValue) { return nil, firstErr(err, ErrInsufficientBalance) } // TODO charge for gas to create account _ the code length * GasCreateByte - newAccount := vm.appState.CreateAccount(callee) + newAccount := DeriveNewAccount(callee, permission.GlobalAccountPermissions(vm.state)) + vm.state.UpdateAccount(newAccount) // Run the input to get the contract code. // NOTE: no need to copy 'input' as per Call contract. @@ -803,15 +818,15 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas if err_ != nil { stack.Push(Zero256) } else { - newAccount.Code = ret // Set the code (ret need not be copied as per Call contract) - stack.Push(newAccount.Address) + newAccount.SetCode(ret) // Set the code (ret need not be copied as per Call contract) + stack.Push(newAccount.Address().Word256()) } case CALL, CALLCODE, DELEGATECALL: // 0xF1, 0xF2, 0xF4 - if !HasPermission(vm.appState, callee, ptypes.Call) { + if !HasPermission(vm.state, callee, permission.Call) { return nil, ErrPermission{"call"} } - gasLimit := stack.Pop64() + gasLimit := stack.PopU64() addr := stack.Pop() // NOTE: for DELEGATECALL value is preserved from the original // caller, as such it is not stored on stack as an argument @@ -819,16 +834,16 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas // caller value is used. for CALL and CALLCODE value is stored // on stack and needs to be overwritten from the given value. if op != DELEGATECALL { - value = stack.Pop64() + value = stack.PopU64() } inOffset, inSize := stack.Pop64(), stack.Pop64() // inputs retOffset, retSize := stack.Pop64(), stack.Pop64() // outputs - dbg.Printf(" => %X\n", addr) + vm.Debugf(" => %X\n", addr) // Get the arguments from the memory args, memErr := memory.Read(inOffset, inSize) if memErr != nil { - dbg.Printf(" => Memory err: %s", memErr) + vm.Debugf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } @@ -845,21 +860,24 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas var err error if nativeContract := registeredNativeContracts[addr]; nativeContract != nil { // Native contract - ret, err = nativeContract(vm.appState, callee, args, &gasLimit) + ret, err = nativeContract(vm.state, callee, args, &gasLimit, vm.logger) // for now we fire the Call event. maybe later we'll fire more particulars var exception string if err != nil { exception = err.Error() } - // NOTE: these fire call events and not particular events for eg name reg or permissions - vm.fireCallEvent(&exception, &ret, callee, &Account{Address: addr}, args, value, &gasLimit) + // NOTE: these fire call go_events and not particular go_events for eg name reg or permissions + vm.fireCallEvent(&exception, &ret, callee.Address(), acm.AddressFromWord256(addr), args, value, &gasLimit) } else { // EVM contract if useGasNegative(gas, GasGetAccount, &err) { return nil, err } - acc := vm.appState.GetAccount(addr) + acc, errAcc := acm.GetMutableAccount(vm.state, acm.AddressFromWord256(addr)) + if errAcc != nil { + return nil, firstErr(err, errAcc) + } // since CALL is used also for sending funds, // acc may not exist yet. This is an error for // CALLCODE, but not for CALL, though I don't think @@ -868,29 +886,29 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas if acc == nil { return nil, firstErr(err, ErrUnknownAddress) } - ret, err = vm.Call(callee, callee, acc.Code, args, value, &gasLimit) + ret, err = vm.Call(callee, callee, acc.Code(), args, value, &gasLimit) } else if op == DELEGATECALL { if acc == nil { return nil, firstErr(err, ErrUnknownAddress) } - ret, err = vm.DelegateCall(caller, callee, acc.Code, args, value, &gasLimit) + ret, err = vm.DelegateCall(caller, callee, acc.Code(), args, value, &gasLimit) } else { // nil account means we're sending funds to a new account if acc == nil { - if !HasPermission(vm.appState, caller, ptypes.CreateAccount) { + if !HasPermission(vm.state, caller, permission.CreateAccount) { return nil, ErrPermission{"create_account"} } - acc = &Account{Address: addr} + acc = (&acm.ConcreteAccount{Address: acm.AddressFromWord256(addr)}).MutableAccount() } // add account to the tx cache - vm.appState.UpdateAccount(acc) - ret, err = vm.Call(callee, acc, acc.Code, args, value, &gasLimit) + vm.state.UpdateAccount(acc) + ret, err = vm.Call(callee, acc, acc.Code(), args, value, &gasLimit) } } // Push result if err != nil { - dbg.Printf("error on call: %s\n", err.Error()) + vm.Debugf("error on call: %s\n", err.Error()) stack.Push(Zero256) } else { stack.Push(One256) @@ -900,7 +918,7 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas // defensively pad or truncate the portion of ret to be returned. memErr := memory.Write(retOffset, RightPadBytes(ret, int(retSize))) if memErr != nil { - dbg.Printf(" => Memory err: %s", memErr) + vm.Debugf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } } @@ -908,16 +926,16 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas // Handle remaining gas. *gas += gasLimit - dbg.Printf("resume %X (%v)\n", callee.Address, gas) + vm.Debugf("resume %s (%v)\n", callee.Address(), gas) case RETURN: // 0xF3 offset, size := stack.Pop64(), stack.Pop64() output, memErr := memory.Read(offset, size) if memErr != nil { - dbg.Printf(" => Memory err: %s", memErr) + vm.Debugf(" => Memory err: %s", memErr) return nil, firstErr(err, ErrMemoryOutOfBounds) } - dbg.Printf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(output), output) + vm.Debugf(" => [%v, %v] (%d) 0x%X\n", offset, size, len(output), output) return output, nil case SELFDESTRUCT: // 0xFF @@ -927,22 +945,25 @@ func (vm *VM) call(caller, callee *Account, code, input []byte, value int64, gas } // TODO if the receiver is , then make it the fee. (?) // TODO: create account if doesn't exist (no reason not to) - receiver := vm.appState.GetAccount(addr) + receiver, errAcc := acm.GetMutableAccount(vm.state, acm.AddressFromWord256(addr)) + if errAcc != nil { + return nil, firstErr(err, errAcc) + } if receiver == nil { return nil, firstErr(err, ErrUnknownAddress) } - balance := callee.Balance - receiver.Balance += balance - vm.appState.UpdateAccount(receiver) - vm.appState.RemoveAccount(callee) - dbg.Printf(" => (%X) %v\n", addr[:4], balance) + + receiver.AddToBalance(callee.Balance()) + vm.state.UpdateAccount(receiver) + vm.state.RemoveAccount(callee.Address()) + vm.Debugf(" => (%X) %v\n", addr[:4], callee.Balance()) fallthrough case STOP: // 0x00 return nil, nil default: - dbg.Printf("(pc) %-3v Invalid opcode %X\n", pc, op) + vm.Debugf("(pc) %-3v Invalid opcode %X\n", pc, op) return nil, fmt.Errorf("Invalid opcode %X", op) } @@ -981,13 +1002,13 @@ func codeGetOp(code []byte, n int64) OpCode { } } -func jump(code []byte, to int64, pc *int64) (err error) { +func (vm *VM) jump(code []byte, to int64, pc *int64) (err error) { dest := codeGetOp(code, to) if dest != JUMPDEST { - dbg.Printf(" ~> %v invalid jump dest %v\n", to, dest) + vm.Debugf(" ~> %v invalid jump dest %v\n", to, dest) return ErrInvalidJumpDest } - dbg.Printf(" ~> %v\n", to) + vm.Debugf(" ~> %v\n", to) *pc = to return nil } @@ -1000,12 +1021,12 @@ func firstErr(errA, errB error) error { } } -func transfer(from, to *Account, amount int64) error { - if from.Balance < amount { +func transfer(from, to acm.MutableAccount, amount uint64) error { + if from.Balance() < amount { return ErrInsufficientBalance } else { - from.Balance -= amount - to.Balance += amount + from.SubtractFromBalance(amount) + to.AddToBalance(amount) return nil } } diff --git a/manager/burrow-mint/evm/vm_test.go b/execution/evm/vm_test.go similarity index 65% rename from manager/burrow-mint/evm/vm_test.go rename to execution/evm/vm_test.go index 0dd35682..523ed41d 100644 --- a/manager/burrow-mint/evm/vm_test.go +++ b/execution/evm/vm_test.go @@ -12,39 +12,42 @@ // See the License for the specific language governing permissions and // limitations under the License. -package vm +package evm import ( - "crypto/rand" "encoding/hex" "fmt" "strings" "testing" "time" + acm "github.com/hyperledger/burrow/account" + "errors" - . "github.com/hyperledger/burrow/manager/burrow-mint/evm/opcodes" - ptypes "github.com/hyperledger/burrow/permission/types" - "github.com/hyperledger/burrow/txs" - . "github.com/hyperledger/burrow/word256" + . "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/event" + exe_events "github.com/hyperledger/burrow/execution/events" + . "github.com/hyperledger/burrow/execution/evm/asm" + . "github.com/hyperledger/burrow/execution/evm/asm/bc" + evm_events "github.com/hyperledger/burrow/execution/evm/events" + "github.com/hyperledger/burrow/logging/lifecycle" + "github.com/hyperledger/burrow/logging/loggers" + "github.com/hyperledger/burrow/permission" "github.com/stretchr/testify/assert" - "github.com/tendermint/go-events" ) -func init() { - SetDebug(true) -} +var logger, _ = lifecycle.NewStdErrLogger() func newAppState() *FakeAppState { fas := &FakeAppState{ - accounts: make(map[string]*Account), + accounts: make(map[acm.Address]acm.Account), storage: make(map[string]Word256), } // For default permissions - fas.accounts[ptypes.GlobalPermissionsAddress256.String()] = &Account{ - Permissions: ptypes.DefaultAccountPermissions, - } + fas.accounts[permission.GlobalPermissionsAddress] = acm.ConcreteAccount{ + Permissions: permission.DefaultAccountPermissions, + }.Account() return fas } @@ -57,25 +60,21 @@ func newParams() Params { } } -func makeBytes(n int) []byte { - b := make([]byte, n) - rand.Read(b) - return b +func newAccount(address ...byte) acm.MutableAccount { + return acm.ConcreteAccount{ + Address: acm.AddressFromWord256(RightPadWord256(address)), + }.MutableAccount() } // Runs a basic loop func TestVM(t *testing.T) { - ourVm := NewVM(newAppState(), DefaultDynamicMemoryProvider, newParams(), Zero256, nil) + ourVm := NewVM(newAppState(), DefaultDynamicMemoryProvider, newParams(), acm.ZeroAddress, nil, logger) // Create accounts - account1 := &Account{ - Address: Int64ToWord256(100), - } - account2 := &Account{ - Address: Int64ToWord256(101), - } + account1 := newAccount(1) + account2 := newAccount(1, 0, 1) - var gas int64 = 100000 + var gas uint64 = 100000 N := []byte{0x0f, 0x0f} // Loop N times code := []byte{0x60, 0x00, 0x60, 0x20, 0x52, 0x5B, byte(0x60 + len(N) - 1)} @@ -91,17 +90,13 @@ func TestVM(t *testing.T) { } func TestJumpErr(t *testing.T) { - ourVm := NewVM(newAppState(), DefaultDynamicMemoryProvider, newParams(), Zero256, nil) + ourVm := NewVM(newAppState(), DefaultDynamicMemoryProvider, newParams(), acm.ZeroAddress, nil, logger) // Create accounts - account1 := &Account{ - Address: Int64ToWord256(100), - } - account2 := &Account{ - Address: Int64ToWord256(101), - } + account1 := newAccount(1) + account2 := newAccount(2) - var gas int64 = 100000 + var gas uint64 = 100000 code := []byte{0x60, 0x10, 0x56} // jump to position 16, a clear failure var err error ch := make(chan struct{}) @@ -124,18 +119,14 @@ func TestJumpErr(t *testing.T) { func TestSubcurrency(t *testing.T) { st := newAppState() // Create accounts - account1 := &Account{ - Address: LeftPadWord256(makeBytes(20)), - } - account2 := &Account{ - Address: LeftPadWord256(makeBytes(20)), - } - st.accounts[account1.Address.String()] = account1 - st.accounts[account2.Address.String()] = account2 + account1 := newAccount(1, 2, 3) + account2 := newAccount(3, 2, 1) + st.accounts[account1.Address()] = account1 + st.accounts[account2.Address()] = account2 - ourVm := NewVM(st, DefaultDynamicMemoryProvider, newParams(), Zero256, nil) + ourVm := NewVM(st, DefaultDynamicMemoryProvider, newParams(), acm.ZeroAddress, nil, logger) - var gas int64 = 1000 + var gas uint64 = 1000 code_parts := []string{"620f42403355", "7c0100000000000000000000000000000000000000000000000000000000", "600035046315cf268481141561004657", @@ -143,7 +134,7 @@ func TestSubcurrency(t *testing.T) { "60043560805260243560a052335460c0523360e05260a05160c05112151561008657", "60a05160c0510360e0515560a0516080515401608051555b5b505b6000f3"} code, _ := hex.DecodeString(strings.Join(code_parts, "")) - fmt.Printf("Code: %x\n", code) + fmt.Printf("Code: %s\n", code) data, _ := hex.DecodeString("693200CE0000000000000000000000004B4363CDE27C2EB05E66357DB05BC5C88F850C1A0000000000000000000000000000000000000000000000000000000000000005") output, err := ourVm.Call(account1, account2, code, data, 0, &gas) fmt.Printf("Output: %v Error: %v\n", output, err) @@ -155,21 +146,15 @@ func TestSubcurrency(t *testing.T) { // Test sending tokens from a contract to another account func TestSendCall(t *testing.T) { fakeAppState := newAppState() - ourVm := NewVM(fakeAppState, DefaultDynamicMemoryProvider, newParams(), Zero256, nil) + ourVm := NewVM(fakeAppState, DefaultDynamicMemoryProvider, newParams(), acm.ZeroAddress, nil, logger) // Create accounts - account1 := &Account{ - Address: Int64ToWord256(100), - } - account2 := &Account{ - Address: Int64ToWord256(101), - } - account3 := &Account{ - Address: Int64ToWord256(102), - } + account1 := newAccount(1) + account2 := newAccount(2) + account3 := newAccount(3) // account1 will call account2 which will trigger CALL opcode to account3 - addr := account3.Address.Postfix(20) + addr := account3.Address() contractCode := callContractCode(addr) //---------------------------------------------- @@ -179,14 +164,13 @@ func TestSendCall(t *testing.T) { //---------------------------------------------- // give account2 sufficient balance, should pass - account2.Balance = 100000 + account2 = newAccount(2).AddToBalance(100000) _, err = runVMWaitError(ourVm, account1, account2, addr, contractCode, 1000) assert.NoError(t, err, "Should have sufficient balance") //---------------------------------------------- // insufficient gas, should fail - - account2.Balance = 100000 + account2 = newAccount(2).AddToBalance(100000) _, err = runVMWaitError(ourVm, account1, account2, addr, contractCode, 100) assert.Error(t, err, "Expected insufficient gas error") } @@ -197,8 +181,8 @@ func TestSendCall(t *testing.T) { // We first run the DELEGATECALL with _just_ enough gas expecting a simple return, // and then run it with 1 gas unit less, expecting a failure func TestDelegateCallGas(t *testing.T) { - appState := newAppState() - ourVm := NewVM(appState, DefaultDynamicMemoryProvider, newParams(), Zero256, nil) + state := newAppState() + ourVm := NewVM(state, DefaultDynamicMemoryProvider, newParams(), acm.ZeroAddress, nil, logger) inOff := 0 inSize := 0 // no call data @@ -218,55 +202,55 @@ func TestDelegateCallGas(t *testing.T) { costBetweenGasAndDelegateCall := gasCost + subCost + delegateCallCost + pushCost // Do a simple operation using 1 gas unit - calleeAccount, calleeAddress := makeAccountWithCode(appState, "callee", - Bytecode(PUSH1, calleeReturnValue, return1())) + calleeAccount, calleeAddress := makeAccountWithCode(state, "callee", + Splice(PUSH1, calleeReturnValue, return1())) // Here we split up the caller code so we can make a DELEGATE call with // different amounts of gas. The value we sandwich in the middle is the amount // we subtract from the available gas (that the caller has available), so: - // code := Bytecode(callerCodePrefix, <amount to subtract from GAS> , callerCodeSuffix) + // code := Splice(callerCodePrefix, <amount to subtract from GAS> , callerCodeSuffix) // gives us the code to make the call - callerCodePrefix := Bytecode(PUSH1, retSize, PUSH1, retOff, PUSH1, inSize, + callerCodePrefix := Splice(PUSH1, retSize, PUSH1, retOff, PUSH1, inSize, PUSH1, inOff, PUSH20, calleeAddress, PUSH1) - callerCodeSuffix := Bytecode(GAS, SUB, DELEGATECALL, returnWord()) + callerCodeSuffix := Splice(GAS, SUB, DELEGATECALL, returnWord()) // Perform a delegate call - callerAccount, _ := makeAccountWithCode(appState, "caller", - Bytecode(callerCodePrefix, + callerAccount, _ := makeAccountWithCode(state, "caller", + Splice(callerCodePrefix, // Give just enough gas to make the DELEGATECALL costBetweenGasAndDelegateCall, callerCodeSuffix)) // Should pass output, err := runVMWaitError(ourVm, callerAccount, calleeAccount, calleeAddress, - callerAccount.Code, 100) + callerAccount.Code(), 100) assert.NoError(t, err, "Should have sufficient funds for call") assert.Equal(t, Int64ToWord256(calleeReturnValue).Bytes(), output) - callerAccount.Code = Bytecode(callerCodePrefix, + callerAccount.SetCode(Splice(callerCodePrefix, // Shouldn't be enough gas to make call costBetweenGasAndDelegateCall-1, - callerCodeSuffix) + callerCodeSuffix)) // Should fail _, err = runVMWaitError(ourVm, callerAccount, calleeAccount, calleeAddress, - callerAccount.Code, 100) + callerAccount.Code(), 100) assert.Error(t, err, "Should have insufficient funds for call") } func TestMemoryBounds(t *testing.T) { - appState := newAppState() + state := newAppState() memoryProvider := func() Memory { return NewDynamicMemory(1024, 2048) } - ourVm := NewVM(appState, memoryProvider, newParams(), Zero256, nil) - caller, _ := makeAccountWithCode(appState, "caller", nil) - callee, _ := makeAccountWithCode(appState, "callee", nil) - gas := int64(100000) + ourVm := NewVM(state, memoryProvider, newParams(), acm.ZeroAddress, nil, logger) + caller, _ := makeAccountWithCode(state, "caller", nil) + callee, _ := makeAccountWithCode(state, "callee", nil) + gas := uint64(100000) // This attempts to store a value at the memory boundary and return it word := One256 output, err := ourVm.call(caller, callee, - Bytecode(pushWord(word), storeAtEnd(), MLOAD, storeAtEnd(), returnAfterStore()), + Splice(pushWord(word), storeAtEnd(), MLOAD, storeAtEnd(), returnAfterStore()), nil, 0, &gas) assert.NoError(t, err) assert.Equal(t, word.Bytes(), output) @@ -274,7 +258,7 @@ func TestMemoryBounds(t *testing.T) { // Same with number word = Int64ToWord256(232234234432) output, err = ourVm.call(caller, callee, - Bytecode(pushWord(word), storeAtEnd(), MLOAD, storeAtEnd(), returnAfterStore()), + Splice(pushWord(word), storeAtEnd(), MLOAD, storeAtEnd(), returnAfterStore()), nil, 0, &gas) assert.NoError(t, err) assert.Equal(t, word.Bytes(), output) @@ -282,9 +266,9 @@ func TestMemoryBounds(t *testing.T) { // Now test a series of boundary stores code := pushWord(word) for i := 0; i < 10; i++ { - code = Bytecode(code, storeAtEnd(), MLOAD) + code = Splice(code, storeAtEnd(), MLOAD) } - output, err = ourVm.call(caller, callee, Bytecode(code, storeAtEnd(), returnAfterStore()), + output, err = ourVm.call(caller, callee, Splice(code, storeAtEnd(), returnAfterStore()), nil, 0, &gas) assert.NoError(t, err) assert.Equal(t, word.Bytes(), output) @@ -292,9 +276,9 @@ func TestMemoryBounds(t *testing.T) { // Same as above but we should breach the upper memory limit set in memoryProvider code = pushWord(word) for i := 0; i < 100; i++ { - code = Bytecode(code, storeAtEnd(), MLOAD) + code = Splice(code, storeAtEnd(), MLOAD) } - output, err = ourVm.call(caller, callee, Bytecode(code, storeAtEnd(), returnAfterStore()), + output, err = ourVm.call(caller, callee, Splice(code, storeAtEnd(), returnAfterStore()), nil, 0, &gas) assert.Error(t, err, "Should hit memory out of bounds") } @@ -307,49 +291,44 @@ func TestMemoryBounds(t *testing.T) { // stores that value at the current memory boundary func storeAtEnd() []byte { // Pull in MSIZE (to carry forward to MLOAD), swap in value to store, store it at MSIZE - return Bytecode(MSIZE, SWAP1, DUP2, MSTORE) + return Splice(MSIZE, SWAP1, DUP2, MSTORE) } func returnAfterStore() []byte { - return Bytecode(PUSH1, 32, DUP2, RETURN) + return Splice(PUSH1, 32, DUP2, RETURN) } // Store the top element of the stack (which is a 32-byte word) in memory // and return it. Useful for a simple return value. func return1() []byte { - return Bytecode(PUSH1, 0, MSTORE, returnWord()) + return Splice(PUSH1, 0, MSTORE, returnWord()) } func returnWord() []byte { // PUSH1 => return size, PUSH1 => return offset, RETURN - return Bytecode(PUSH1, 32, PUSH1, 0, RETURN) + return Splice(PUSH1, 32, PUSH1, 0, RETURN) } -func makeAccountWithCode(appState AppState, name string, - code []byte) (*Account, []byte) { - account := &Account{ - Address: LeftPadWord256([]byte(name)), - Balance: 9999999, - Code: code, - Nonce: 0, - } - account.Code = code - appState.UpdateAccount(account) - // Sanity check - address := new([20]byte) - for i, b := range account.Address.Postfix(20) { - address[i] = b - } - return account, address[:] +func makeAccountWithCode(state acm.Updater, name string, + code []byte) (acm.MutableAccount, acm.Address) { + address, _ := acm.AddressFromBytes([]byte(name)) + account := acm.ConcreteAccount{ + Address: address, + Balance: 9999999, + Code: code, + Sequence: 0, + }.MutableAccount() + state.UpdateAccount(account) + return account, account.Address() } // Subscribes to an AccCall, runs the vm, returns the output any direct exception -// and then waits for any exceptions transmitted by EventData in the AccCall +// and then waits for any exceptions transmitted by Data in the AccCall // event (in the case of no direct error from call we will block waiting for // at least 1 AccCall event) -func runVMWaitError(ourVm *VM, caller, callee *Account, subscribeAddr, - contractCode []byte, gas int64) (output []byte, err error) { - eventCh := make(chan txs.EventData) +func runVMWaitError(ourVm *VM, caller, callee acm.MutableAccount, subscribeAddr acm.Address, + contractCode []byte, gas uint64) (output []byte, err error) { + eventCh := make(chan event.EventData) output, err = runVM(eventCh, ourVm, caller, callee, subscribeAddr, contractCode, gas) if err != nil { @@ -357,10 +336,10 @@ func runVMWaitError(ourVm *VM, caller, callee *Account, subscribeAddr, } msg := <-eventCh var errString string - switch ev := msg.(type) { - case txs.EventDataTx: + switch ev := msg.Unwrap().(type) { + case exe_events.EventDataTx: errString = ev.Exception - case txs.EventDataCall: + case evm_events.EventDataCall: errString = ev.Exception } @@ -372,18 +351,17 @@ func runVMWaitError(ourVm *VM, caller, callee *Account, subscribeAddr, // Subscribes to an AccCall, runs the vm, returns the output and any direct // exception -func runVM(eventCh chan txs.EventData, ourVm *VM, caller, callee *Account, - subscribeAddr, contractCode []byte, gas int64) ([]byte, error) { +func runVM(eventCh chan event.EventData, ourVm *VM, caller, callee acm.MutableAccount, + subscribeAddr acm.Address, contractCode []byte, gas uint64) ([]byte, error) { // we need to catch the event from the CALL to check for exceptions - evsw := events.NewEventSwitch() - evsw.Start() - fmt.Printf("subscribe to %x\n", subscribeAddr) - evsw.AddListenerForEvent("test", txs.EventStringAccCall(subscribeAddr), - func(msg events.EventData) { - eventCh <- msg.(txs.EventData) + evsw := event.NewEmitter(loggers.NewNoopInfoTraceLogger()) + fmt.Printf("subscribe to %s\n", subscribeAddr) + evsw.Subscribe("test", evm_events.EventStringAccCall(subscribeAddr), + func(msg event.AnyEventData) { + eventCh <- *msg.BurrowEventData }) - evc := events.NewEventCache(evsw) + evc := event.NewEventCache(evsw) ourVm.SetFireable(evc) start := time.Now() output, err := ourVm.Call(caller, callee, contractCode, []byte{}, 0, &gas) @@ -394,13 +372,13 @@ func runVM(eventCh chan txs.EventData, ourVm *VM, caller, callee *Account, } // this is code to call another contract (hardcoded as addr) -func callContractCode(addr []byte) []byte { +func callContractCode(addr acm.Address) []byte { gas1, gas2 := byte(0x1), byte(0x1) value := byte(0x69) inOff, inSize := byte(0x0), byte(0x0) // no call data retOff, retSize := byte(0x0), byte(0x20) // this is the code we want to run (send funds to an account and return) - return Bytecode(PUSH1, retSize, PUSH1, retOff, PUSH1, inSize, PUSH1, + return Splice(PUSH1, retSize, PUSH1, retOff, PUSH1, inSize, PUSH1, inOff, PUSH1, value, PUSH20, addr, PUSH2, gas1, gas2, CALL, PUSH1, retSize, PUSH1, retOff, RETURN) } @@ -417,50 +395,50 @@ func pushWord(word Word256) []byte { if word[leadingZeros] == 0 { leadingZeros++ } else { - return Bytecode(byte(PUSH32)-leadingZeros, word[leadingZeros:]) + return Splice(byte(PUSH32)-leadingZeros, word[leadingZeros:]) } } - return Bytecode(PUSH1, 0) + return Splice(PUSH1, 0) } func TestPushWord(t *testing.T) { word := Int64ToWord256(int64(2133213213)) - assert.Equal(t, Bytecode(PUSH4, 0x7F, 0x26, 0x40, 0x1D), pushWord(word)) + assert.Equal(t, Splice(PUSH4, 0x7F, 0x26, 0x40, 0x1D), pushWord(word)) word[0] = 1 - assert.Equal(t, Bytecode(PUSH32, + assert.Equal(t, Splice(PUSH32, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x7F, 0x26, 0x40, 0x1D), pushWord(word)) - assert.Equal(t, Bytecode(PUSH1, 0), pushWord(Word256{})) - assert.Equal(t, Bytecode(PUSH1, 1), pushWord(Int64ToWord256(1))) + assert.Equal(t, Splice(PUSH1, 0), pushWord(Word256{})) + assert.Equal(t, Splice(PUSH1, 1), pushWord(Int64ToWord256(1))) } func TestBytecode(t *testing.T) { assert.Equal(t, - Bytecode(1, 2, 3, 4, 5, 6), - Bytecode(1, 2, 3, Bytecode(4, 5, 6))) + Splice(1, 2, 3, 4, 5, 6), + Splice(1, 2, 3, Splice(4, 5, 6))) assert.Equal(t, - Bytecode(1, 2, 3, 4, 5, 6, 7, 8), - Bytecode(1, 2, 3, Bytecode(4, Bytecode(5), 6), 7, 8)) + Splice(1, 2, 3, 4, 5, 6, 7, 8), + Splice(1, 2, 3, Splice(4, Splice(5), 6), 7, 8)) assert.Equal(t, - Bytecode(PUSH1, 2), - Bytecode(byte(PUSH1), 0x02)) + Splice(PUSH1, 2), + Splice(byte(PUSH1), 0x02)) assert.Equal(t, []byte{}, - Bytecode(Bytecode(Bytecode()))) + Splice(Splice(Splice()))) - contractAccount := &Account{Address: Int64ToWord256(102)} - addr := contractAccount.Address.Postfix(20) + contractAccount := &acm.ConcreteAccount{Address: acm.AddressFromWord256(Int64ToWord256(102))} + addr := contractAccount.Address gas1, gas2 := byte(0x1), byte(0x1) value := byte(0x69) inOff, inSize := byte(0x0), byte(0x0) // no call data retOff, retSize := byte(0x0), byte(0x20) - contractCodeBytecode := Bytecode(PUSH1, retSize, PUSH1, retOff, PUSH1, inSize, PUSH1, + contractCodeBytecode := Splice(PUSH1, retSize, PUSH1, retOff, PUSH1, inSize, PUSH1, inOff, PUSH1, value, PUSH20, addr, PUSH2, gas1, gas2, CALL, PUSH1, retSize, PUSH1, retOff, RETURN) contractCode := []byte{0x60, retSize, 0x60, retOff, 0x60, inSize, 0x60, inOff, 0x60, value, 0x73} - contractCode = append(contractCode, addr...) + contractCode = append(contractCode, addr[:]...) contractCode = append(contractCode, []byte{0x61, gas1, gas2, 0xf1, 0x60, 0x20, 0x60, 0x0, 0xf3}...) assert.Equal(t, contractCode, contractCodeBytecode) } diff --git a/manager/burrow-mint/state/execution.go b/execution/execution.go similarity index 54% rename from manager/burrow-mint/state/execution.go rename to execution/execution.go index 4c765ec7..cf634233 100644 --- a/manager/burrow-mint/state/execution.go +++ b/execution/execution.go @@ -12,341 +12,170 @@ // See the License for the specific language governing permissions and // limitations under the License. -package state +package execution import ( - "bytes" "fmt" + "sync" acm "github.com/hyperledger/burrow/account" - "github.com/hyperledger/burrow/common/sanity" - core_types "github.com/hyperledger/burrow/core/types" + "github.com/hyperledger/burrow/binary" + bcm "github.com/hyperledger/burrow/blockchain" + "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/execution/events" + "github.com/hyperledger/burrow/execution/evm" + "github.com/hyperledger/burrow/logging" + "github.com/hyperledger/burrow/logging/structure" logging_types "github.com/hyperledger/burrow/logging/types" - "github.com/hyperledger/burrow/manager/burrow-mint/evm" - ptypes "github.com/hyperledger/burrow/permission/types" // for GlobalPermissionAddress ... + "github.com/hyperledger/burrow/permission" + ptypes "github.com/hyperledger/burrow/permission/types" "github.com/hyperledger/burrow/txs" - . "github.com/hyperledger/burrow/word256" - - "github.com/hyperledger/burrow/logging" - "github.com/tendermint/go-events" ) -// 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 +type BatchExecutor interface { + acm.StateIterable + acm.Updater + acm.StorageSetter + // Execute transaction against block cache (i.e. block buffer) + Execute(tx txs.Tx) error + // Reset executor to underlying State + Reset() error } -// 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.LastBlockHash, 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.LastBlockHash, 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.evc) - if err != nil { - return InvalidTxError{tx, err} - } - } +// Executes transactions +type BatchCommitter interface { + BatchExecutor + // Commit execution results to underlying State and provide opportunity + // to mutate state before it is saved + Commit() (stateHash []byte, err error) +} - // Now sync the BlockCache to the backend. - blockCache.Sync() +type executor struct { + mtx sync.Mutex + chainID string + tip bcm.Tip + runCall bool + state *State + blockCache *BlockCache + fireable event.Fireable + eventCache *event.Cache + logger logging_types.InfoTraceLogger +} - // 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) - } +var _ BatchExecutor = (*executor)(nil) - // 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) - } +// Wraps a cache of what is variously known as the 'check cache' and 'mempool' +func NewBatchChecker(state *State, + chainID string, + tip bcm.Tip, + logger logging_types.InfoTraceLogger) BatchExecutor { + return newExecutor(false, state, chainID, tip, event.NewNoOpFireable(), + logging.WithScope(logger, "NewBatchExecutor")) +} - // Increment validator AccumPowers - s.BondedValidators.IncrementAccum(1) - s.LastBlockHeight = block.Height - s.LastBlockHash = block.Hash() - s.LastBlockParts = blockPartsHeader - s.LastBlockTime = block.Time - return nil +func NewBatchCommitter(state *State, + chainID string, + tip bcm.Tip, + fireable event.Fireable, + logger logging_types.InfoTraceLogger) BatchCommitter { + return newExecutor(true, state, chainID, tip, fireable, + logging.WithScope(logger, "NewBatchCommitter")) } -*/ -// The accounts from the TxInputs must either already have -// acm.PubKey.(type) != nil, (it must be known), -// or it must be specified in the TxInput. If redeclared, -// the TxInput is modified and input.PubKey set to nil. -func getInputs(state AccountGetter, ins []*txs.TxInput) (map[string]*acm.Account, error) { - accounts := map[string]*acm.Account{} - for _, in := range ins { - // Account shouldn't be duplicated - if _, ok := accounts[string(in.Address)]; ok { - return nil, txs.ErrTxDuplicateAddress - } - acc := state.GetAccount(in.Address) - if acc == nil { - return nil, txs.ErrTxInvalidAddress - } - // PubKey should be present in either "account" or "in" - if err := checkInputPubKey(acc, in); err != nil { - return nil, err - } - accounts[string(in.Address)] = acc +func newExecutor(runCall bool, + state *State, + chainID string, + tip bcm.Tip, + eventFireable event.Fireable, + logger logging_types.InfoTraceLogger) *executor { + return &executor{ + chainID: chainID, + tip: tip, + runCall: runCall, + state: state, + blockCache: NewBlockCache(state), + fireable: eventFireable, + eventCache: event.NewEventCache(eventFireable), + logger: logger.With(structure.ComponentKey, "Execution"), } - return accounts, nil } -func getOrMakeOutputs(state AccountGetter, accounts map[string]*acm.Account, - outs []*txs.TxOutput, logger logging_types.InfoTraceLogger) (map[string]*acm.Account, error) { - if accounts == nil { - accounts = make(map[string]*acm.Account) - } +// Accounts +func (exe *executor) GetAccount(address acm.Address) (acm.Account, error) { + return exe.blockCache.GetAccount(address) +} - // 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 := accounts[string(out.Address)]; ok { - return nil, txs.ErrTxDuplicateAddress - } - acc := state.GetAccount(out.Address) - // output account may be nil (new) - if acc == nil { - if !checkedCreatePerms { - if !hasCreateAccountPermission(state, accounts, logger) { - return nil, fmt.Errorf("At least one input does not have permission to create accounts") - } - checkedCreatePerms = true - } - acc = &acm.Account{ - Address: out.Address, - PubKey: nil, - Sequence: 0, - Balance: 0, - Permissions: ptypes.ZeroAccountPermissions, - } - } - accounts[string(out.Address)] = acc - } - return accounts, nil +func (exe *executor) UpdateAccount(account acm.Account) error { + return exe.blockCache.UpdateAccount(account) } -// Since all ethereum accounts implicitly exist we sometimes lazily create an Account object to represent them -// only when needed. Sometimes we need to create an unknown Account knowing only its address (which is expected to -// be a deterministic hash of its associated public key) and not its public key. When we eventually receive a -// transaction acting on behalf of that account we will be given a public key that we can check matches the address. -// If it does then we will associate the public key with the stub account already registered in the system once and -// for all time. -func checkInputPubKey(acc *acm.Account, in *txs.TxInput) error { - if acc.PubKey == nil { - if in.PubKey == nil { - return txs.ErrTxUnknownPubKey - } - if !bytes.Equal(in.PubKey.Address(), acc.Address) { - return txs.ErrTxInvalidPubKey - } - acc.PubKey = in.PubKey - } else { - in.PubKey = nil - } - return nil +func (exe *executor) RemoveAccount(address acm.Address) error { + return exe.blockCache.RemoveAccount(address) } -func validateInputs(accounts map[string]*acm.Account, signBytes []byte, ins []*txs.TxInput) (total int64, err error) { - for _, in := range ins { - acc := accounts[string(in.Address)] - if acc == nil { - sanity.PanicSanity("validateInputs() expects account in accounts") - } - err = validateInput(acc, signBytes, in) - if err != nil { - return - } - // Good. Add amount to total - total += in.Amount - } - return total, nil +func (exe *executor) IterateAccounts(consumer func(acm.Account) bool) (bool, error) { + return exe.blockCache.IterateAccounts(consumer) } -func validateInput(acc *acm.Account, signBytes []byte, in *txs.TxInput) (err error) { - // Check TxInput basic - if err := in.ValidateBasic(); err != nil { - return err - } - // Check signatures - if !acc.PubKey.VerifyBytes(signBytes, in.Signature) { - return txs.ErrTxInvalidSignature - } - // Check sequences - if acc.Sequence+1 != in.Sequence { - return txs.ErrTxInvalidSequence{ - Got: in.Sequence, - Expected: acc.Sequence + 1, - } - } - // Check amount - if acc.Balance < in.Amount { - return txs.ErrTxInsufficientFunds - } - return nil +// Storage +func (exe *executor) GetStorage(address acm.Address, key binary.Word256) (binary.Word256, error) { + return exe.blockCache.GetStorage(address, key) } -func validateOutputs(outs []*txs.TxOutput) (total int64, err error) { - 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 (exe *executor) SetStorage(address acm.Address, key binary.Word256, value binary.Word256) error { + return exe.blockCache.SetStorage(address, key, value) } -func adjustByInputs(accounts map[string]*acm.Account, ins []*txs.TxInput) { - for _, in := range ins { - acc := accounts[string(in.Address)] - if acc == nil { - sanity.PanicSanity("adjustByInputs() expects account in accounts") - } - if acc.Balance < in.Amount { - sanity.PanicSanity("adjustByInputs() expects sufficient funds") - } - acc.Balance -= in.Amount +func (exe *executor) IterateStorage(address acm.Address, consumer func(key, value binary.Word256) bool) (bool, error) { + return exe.blockCache.IterateStorage(address, consumer) +} - acc.Sequence += 1 - } +func (exe *executor) Commit() ([]byte, error) { + exe.mtx.Lock() + defer exe.mtx.Unlock() + // sync the cache + exe.blockCache.Sync() + // save state to disk + exe.state.Save() + // flush events to listeners (XXX: note issue with blocking) + exe.eventCache.Flush() + return exe.state.Hash(), nil } -func adjustByOutputs(accounts map[string]*acm.Account, outs []*txs.TxOutput) { - for _, out := range outs { - acc := accounts[string(out.Address)] - if acc == nil { - sanity.PanicSanity("adjustByOutputs() expects account in accounts") - } - acc.Balance += out.Amount - } +func (exe *executor) Reset() error { + exe.blockCache = NewBlockCache(exe.state) + exe.eventCache = event.NewEventCache(exe.fireable) + return nil } // If the tx is invalid, an error will be returned. // Unlike ExecBlock(), state will not be altered. -func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable, - logger logging_types.InfoTraceLogger) (err error) { - - logger = logging.WithScope(logger, "ExecTx") +func (exe *executor) Execute(tx txs.Tx) error { + logger := logging.WithScope(exe.logger, "executor.Execute(tx txs.Tx)") // TODO: do something with fees - fees := int64(0) - _s := blockCache.State() // hack to access validators and block height + fees := uint64(0) // Exec tx switch tx := tx.(type) { case *txs.SendTx: - accounts, err := getInputs(blockCache, tx.Inputs) + accounts, err := getInputs(exe.blockCache, tx.Inputs) if err != nil { return err } // ensure all inputs have send permissions - if !hasSendPermission(blockCache, accounts, logger) { - return fmt.Errorf("At least one input lacks permission for SendTx") + if !hasSendPermission(exe.blockCache, 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(blockCache, accounts, tx.Outputs, logger) + accounts, err = getOrMakeOutputs(exe.blockCache, accounts, tx.Outputs, logger) if err != nil { return err } - signBytes := acm.SignBytes(_s.ChainID, tx) + signBytes := acm.SignBytes(exe.chainID, tx) inTotal, err := validateInputs(accounts, signBytes, tx.Inputs) if err != nil { return err @@ -365,40 +194,44 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable adjustByInputs(accounts, tx.Inputs) adjustByOutputs(accounts, tx.Outputs) for _, acc := range accounts { - blockCache.UpdateAccount(acc) + exe.blockCache.UpdateAccount(acc) } - // if the evc is nil, nothing will happen - if evc != nil { + // if the exe.eventCache is nil, nothing will happen + if exe.eventCache != nil { for _, i := range tx.Inputs { - evc.FireEvent(txs.EventStringAccInput(i.Address), txs.EventDataTx{tx, nil, ""}) + exe.eventCache.Fire(events.EventStringAccInput(i.Address), events.EventDataTx{tx, nil, ""}) } for _, o := range tx.Outputs { - evc.FireEvent(txs.EventStringAccOutput(o.Address), txs.EventDataTx{tx, nil, ""}) + exe.eventCache.Fire(events.EventStringAccOutput(o.Address), events.EventDataTx{tx, nil, ""}) } } return nil case *txs.CallTx: - var inAcc, outAcc *acm.Account + var inAcc acm.MutableAccount + var outAcc acm.Account // Validate input - inAcc = blockCache.GetAccount(tx.Input.Address) + inAcc, err := acm.GetMutableAccount(exe.blockCache, tx.Input.Address) + if err != nil { + return err + } if inAcc == nil { logging.InfoMsg(logger, "Cannot find input account", "tx_input", tx.Input) return txs.ErrTxInvalidAddress } - createContract := len(tx.Address) == 0 + createContract := tx.Address == nil if createContract { - if !hasCreateContractPermission(blockCache, inAcc, logger) { - return fmt.Errorf("Account %X does not have CreateContract permission", tx.Input.Address) + if !hasCreateContractPermission(exe.blockCache, inAcc, logger) { + return fmt.Errorf("account %s does not have CreateContract permission", tx.Input.Address) } } else { - if !hasCallPermission(blockCache, inAcc, logger) { - return fmt.Errorf("Account %X does not have Call permission", tx.Input.Address) + if !hasCallPermission(exe.blockCache, inAcc, logger) { + return fmt.Errorf("account %s does not have Call permission", tx.Input.Address) } } @@ -408,8 +241,8 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable "tx_input", tx.Input) return err } - signBytes := acm.SignBytes(_s.ChainID, tx) - err := validateInput(inAcc, signBytes, tx.Input) + signBytes := acm.SignBytes(exe.chainID, tx) + err = validateInput(inAcc, signBytes, tx.Input) if err != nil { logging.InfoMsg(logger, "validateInput failed", "tx_input", tx.Input, "error", err) @@ -422,24 +255,22 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable } if !createContract { - // Validate output - if len(tx.Address) != 20 { - logging.InfoMsg(logger, "Destination address is not 20 bytes", - "address", tx.Address) - return txs.ErrTxInvalidAddress - } // check if its a native contract - if vm.RegisteredNativeContract(LeftPadWord256(tx.Address)) { - return fmt.Errorf("Attempt to call a native contract at %X, "+ + 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) + "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 - outAcc = blockCache.GetAccount(tx.Address) + // Note: tx.Address == nil iff createContract so dereference is okay + outAcc, err = exe.blockCache.GetAccount(*tx.Address) + if err != nil { + return err + } } logger.Trace("output_account", outAcc) @@ -447,31 +278,35 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // Good! value := tx.Input.Amount - tx.Fee - inAcc.Sequence += 1 - inAcc.Balance -= tx.Fee - blockCache.UpdateAccount(inAcc) + logging.TraceMsg(logger, "Incrementing sequence number", + "account", inAcc.Address(), + "old_sequence", inAcc.Sequence(), + "new_sequence", inAcc.Sequence()+1) + inAcc.IncSequence().SubtractFromBalance(tx.Fee) + + exe.blockCache.UpdateAccount(inAcc) // The logic in runCall MUST NOT return. - if runCall { + if exe.runCall { // VM call variables var ( - gas int64 = tx.GasLimit - err error = nil - caller *vm.Account = toVMAccount(inAcc) - callee *vm.Account = nil // initialized below - code []byte = nil - ret []byte = nil - txCache = NewTxCache(blockCache) - params = vm.Params{ - BlockHeight: int64(_s.LastBlockHeight), - BlockHash: LeftPadWord256(_s.LastBlockHash), - BlockTime: _s.LastBlockTime.Unix(), - GasLimit: _s.GetGasLimit(), + 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 = NewTxCache(exe.blockCache) + 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 !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 @@ -495,28 +330,28 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // get or create callee if createContract { // We already checked for permission - callee = txCache.CreateAccount(caller) + callee = evm.DeriveNewAccount(caller, permission.GlobalAccountPermissions(exe.state)) logging.TraceMsg(logger, "Created new contract", "contract_address", callee.Address, "contract_code", callee.Code) code = tx.Data } else { - callee = toVMAccount(outAcc) + callee = acm.AsMutableAccount(outAcc) logging.TraceMsg(logger, "Calling existing contract", "contract_address", callee.Address, "contract_code", callee.Code) - code = callee.Code + code = callee.Code() } - logger.Trace("callee_") + logger.Trace("callee", callee.Address().String()) - // Run VM call and sync txCache to blockCache. + // 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 := vm.NewVM(txCache, vm.DefaultDynamicMemoryProvider, params, - caller.Address, txs.TxHash(_s.ChainID, tx)) - vmach.SetFireable(evc) + vmach := evm.NewVM(txCache, evm.DefaultDynamicMemoryProvider, params, caller.Address(), + txs.TxHash(exe.chainID, tx), logger) + vmach.SetFireable(exe.eventCache) // NOTE: Call() transfers the value from caller to callee iff call succeeds. ret, err = vmach.Call(caller, callee, code, tx.Data, value, &gas) if err != nil { @@ -528,9 +363,9 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable logging.TraceMsg(logger, "Successful execution") if createContract { - callee.Code = ret + callee.SetCode(ret) } - txCache.Sync() + txCache.Sync(exe.blockCache) } CALL_COMPLETE: // err may or may not be nil. @@ -544,41 +379,52 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // Fire Events for sender and receiver // a separate event will be fired from vm for each additional call - if evc != nil { + if exe.eventCache != nil { exception := "" if err != nil { exception = err.Error() } - evc.FireEvent(txs.EventStringAccInput(tx.Input.Address), txs.EventDataTx{tx, ret, exception}) - evc.FireEvent(txs.EventStringAccOutput(tx.Address), txs.EventDataTx{tx, ret, exception}) + exe.eventCache.Fire(events.EventStringAccInput(tx.Input.Address), + events.EventDataTx{tx, ret, exception}) + if tx.Address != nil { + exe.eventCache.Fire(events.EventStringAccOutput(*tx.Address), + events.EventDataTx{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.Balance -= value + inAcc.SubtractFromBalance(value) if createContract { - inAcc.Sequence += 1 // XXX ?! + // This is done by DeriveNewAccount when runCall == true + + logging.TraceMsg(logger, "Incrementing sequence number since creates contract", + "account", inAcc.Address(), + "old_sequence", inAcc.Sequence(), + "new_sequence", inAcc.Sequence()+1) + inAcc.IncSequence() } - blockCache.UpdateAccount(inAcc) + exe.blockCache.UpdateAccount(inAcc) } return nil case *txs.NameTx: - var inAcc *acm.Account - // Validate input - inAcc = blockCache.GetAccount(tx.Input.Address) + inAcc, err := acm.GetMutableAccount(exe.blockCache, tx.Input.Address) + if err != nil { + return err + } if inAcc == nil { logging.InfoMsg(logger, "Cannot find input account", "tx_input", tx.Input) return txs.ErrTxInvalidAddress } // check permission - if !hasNamePermission(blockCache, inAcc, logger) { - return fmt.Errorf("Account %X does not have Name permission", tx.Input.Address) + if !hasNamePermission(exe.blockCache, inAcc, logger) { + return fmt.Errorf("account %s does not have Name permission", tx.Input.Address) } // pubKey should be present in either "inAcc" or "tx.Input" if err := checkInputPubKey(inAcc, tx.Input); err != nil { @@ -586,8 +432,8 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable "tx_input", tx.Input) return err } - signBytes := acm.SignBytes(_s.ChainID, tx) - err := validateInput(inAcc, signBytes, tx.Input) + signBytes := acm.SignBytes(exe.chainID, tx) + err = validateInput(inAcc, signBytes, tx.Input) if err != nil { logging.InfoMsg(logger, "validateInput failed", "tx_input", tx.Input, "error", err) @@ -608,8 +454,8 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // let's say cost of a name for one block is len(data) + 32 costPerBlock := txs.NameCostPerBlock(txs.NameBaseCost(tx.Name, tx.Data)) - expiresIn := int(value / costPerBlock) - lastBlockHeight := _s.LastBlockHeight + expiresIn := value / uint64(costPerBlock) + lastBlockHeight := exe.tip.LastBlockHeight() logging.TraceMsg(logger, "New NameTx", "value", value, @@ -618,7 +464,7 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable "last_block_height", lastBlockHeight) // check if the name exists - entry := blockCache.GetNameRegEntry(tx.Name) + entry := exe.blockCache.GetNameRegEntry(tx.Name) if entry != nil { var expired bool @@ -626,11 +472,9 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // if the entry already exists, and hasn't expired, we must be owner if entry.Expires > lastBlockHeight { // ensure we are owner - if !bytes.Equal(entry.Owner, tx.Input.Address) { - logging.InfoMsg(logger, "Sender is trying to update a name for which they are not an owner", - "sender_address", tx.Input.Address, - "name", tx.Name) - return txs.ErrTxPermissionDenied + 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 @@ -642,7 +486,7 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // (owners if not expired, anyone if expired) logging.TraceMsg(logger, "Removing NameReg entry (no value and empty data in tx requests this)", "name", entry.Name) - blockCache.RemoveNameRegEntry(entry.Name) + exe.blockCache.RemoveNameRegEntry(entry.Name) } else { // update the entry by bumping the expiry // and changing the data @@ -659,11 +503,11 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable } else { // since the size of the data may have changed // we use the total amount of "credit" - oldCredit := int64(entry.Expires-lastBlockHeight) * txs.NameBaseCost(entry.Name, entry.Data) + oldCredit := (entry.Expires - lastBlockHeight) * txs.NameBaseCost(entry.Name, entry.Data) credit := oldCredit + value - expiresIn = int(credit / costPerBlock) + expiresIn = uint64(credit / costPerBlock) if expiresIn < txs.MinNameRegistrationPeriod { - return fmt.Errorf("Names must be registered for at least %d blocks", txs.MinNameRegistrationPeriod) + return fmt.Errorf("names must be registered for at least %d blocks", txs.MinNameRegistrationPeriod) } entry.Expires = lastBlockHeight + expiresIn logging.TraceMsg(logger, "Updated NameReg entry", @@ -674,14 +518,14 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable "credit", credit) } entry.Data = tx.Data - blockCache.UpdateNameRegEntry(entry) + exe.blockCache.UpdateNameRegEntry(entry) } } else { if expiresIn < txs.MinNameRegistrationPeriod { return fmt.Errorf("Names must be registered for at least %d blocks", txs.MinNameRegistrationPeriod) } // entry does not exist, so create it - entry = &core_types.NameRegEntry{ + entry = &NameRegEntry{ Name: tx.Name, Owner: tx.Input.Address, Data: tx.Data, @@ -690,21 +534,21 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable logging.TraceMsg(logger, "Creating NameReg entry", "name", entry.Name, "expires_in", expiresIn) - blockCache.UpdateNameRegEntry(entry) + exe.blockCache.UpdateNameRegEntry(entry) } // TODO: something with the value sent? // Good! - inAcc.Sequence += 1 - inAcc.Balance -= value - blockCache.UpdateAccount(inAcc) + inAcc.IncSequence() + inAcc.SubtractFromBalance(value) + exe.blockCache.UpdateAccount(inAcc) // TODO: maybe we want to take funds on error and allow txs in that don't do anythingi? - if evc != nil { - evc.FireEvent(txs.EventStringAccInput(tx.Input.Address), txs.EventDataTx{tx, nil, ""}) - evc.FireEvent(txs.EventStringNameReg(tx.Name), txs.EventDataTx{tx, nil, ""}) + if exe.eventCache != nil { + exe.eventCache.Fire(events.EventStringAccInput(tx.Input.Address), events.EventDataTx{tx, nil, ""}) + exe.eventCache.Fire(events.EventStringNameReg(tx.Name), events.EventDataTx{tx, nil, ""}) } return nil @@ -713,14 +557,14 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // TODO! /* case *txs.BondTx: - valInfo := blockCache.State().GetValidatorInfo(tx.PubKey.Address()) + 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(blockCache, tx.Inputs) + accounts, err := getInputs(exe.blockCache, tx.Inputs) if err != nil { return err } @@ -728,29 +572,29 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // add outputs to accounts map // if any outputs don't exist, all inputs must have CreateAccount perm // though outputs aren't created until unbonding/release time - canCreate := hasCreateAccountPermission(blockCache, accounts) + canCreate := hasCreateAccountPermission(exe.blockCache, accounts) for _, out := range tx.UnbondTo { - acc := blockCache.GetAccount(out.Address) + 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 := blockCache.GetAccount(tx.PubKey.Address()) - if !hasBondPermission(blockCache, bondAcc) { + 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(blockCache, accounts) { + if !hasBondOrSendPermission(exe.blockCache, accounts) { return fmt.Errorf("At least one input lacks permission to bond") } - signBytes := acm.SignBytes(_s.ChainID, tx) + signBytes := acm.SignBytes(exe.chainID, tx) inTotal, err := validateInputs(accounts, signBytes, tx.Inputs) if err != nil { return err } - if !tx.PubKey.VerifyBytes(signBytes, tx.Signature) { + if !tx.PublicKey().VerifyBytes(signBytes, tx.Signature) { return txs.ErrTxInvalidSignature } outTotal, err := validateOutputs(tx.UnbondTo) @@ -766,30 +610,30 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // Good! Adjust accounts adjustByInputs(accounts, tx.Inputs) for _, acc := range accounts { - blockCache.UpdateAccount(acc) + exe.blockCache.UpdateAccount(acc) } // Add ValidatorInfo _s.SetValidatorInfo(&txs.ValidatorInfo{ - Address: tx.PubKey.Address(), - PubKey: tx.PubKey, + Address: tx.PublicKey().Address(), + PublicKey: tx.PublicKey(), UnbondTo: tx.UnbondTo, - FirstBondHeight: _s.LastBlockHeight + 1, + FirstBondHeight: _s.lastBlockHeight + 1, FirstBondAmount: outTotal, }) // Add Validator added := _s.BondedValidators.Add(&txs.Validator{ - Address: tx.PubKey.Address(), - PubKey: tx.PubKey, - BondHeight: _s.LastBlockHeight + 1, + Address: tx.PublicKey().Address(), + PublicKey: tx.PublicKey(), + BondHeight: _s.lastBlockHeight + 1, VotingPower: outTotal, Accum: 0, }) if !added { PanicCrisis("Failed to add validator") } - if evc != nil { + if exe.eventCache != nil { // TODO: fire for all inputs - evc.FireEvent(txs.EventStringBond(), txs.EventDataTx{tx, nil, ""}) + exe.eventCache.Fire(txs.EventStringBond(), txs.EventDataTx{tx, nil, ""}) } return nil @@ -801,8 +645,8 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable } // Verify the signature - signBytes := acm.SignBytes(_s.ChainID, tx) - if !val.PubKey.VerifyBytes(signBytes, tx.Signature) { + signBytes := acm.SignBytes(exe.chainID, tx) + if !val.PublicKey().VerifyBytes(signBytes, tx.Signature) { return txs.ErrTxInvalidSignature } @@ -813,8 +657,8 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // Good! _s.unbondValidator(val) - if evc != nil { - evc.FireEvent(txs.EventStringUnbond(), txs.EventDataTx{tx, nil, ""}) + if exe.eventCache != nil { + exe.eventCache.Fire(txs.EventStringUnbond(), txs.EventDataTx{tx, nil, ""}) } return nil @@ -826,14 +670,14 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable } // Verify the signature - signBytes := acm.SignBytes(_s.ChainID, tx) - if !val.PubKey.VerifyBytes(signBytes, tx.Signature) { + signBytes := acm.SignBytes(exe.chainID, tx) + if !val.PublicKey().VerifyBytes(signBytes, tx.Signature) { return txs.ErrTxInvalidSignature } // tx.Height must be in a suitable range - minRebondHeight := _s.LastBlockHeight - (validatorTimeoutBlocks / 2) - maxRebondHeight := _s.LastBlockHeight + 2 + 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)) @@ -841,66 +685,30 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable // Good! _s.rebondValidator(val) - if evc != nil { - evc.FireEvent(txs.EventStringRebond(), txs.EventDataTx{tx, nil, ""}) + if exe.eventCache != nil { + exe.eventCache.Fire(txs.EventStringRebond(), txs.EventDataTx{tx, nil, ""}) } return nil - case *txs.DupeoutTx: - // Verify the signatures - _, accused := _s.BondedValidators.GetByAddress(tx.Address) - if accused == nil { - _, accused = _s.UnbondingValidators.GetByAddress(tx.Address) - if accused == nil { - return txs.ErrTxInvalidAddress - } - } - voteASignBytes := acm.SignBytes(_s.ChainID, &tx.VoteA) - voteBSignBytes := acm.SignBytes(_s.ChainID, &tx.VoteB) - if !accused.PubKey.VerifyBytes(voteASignBytes, tx.VoteA.Signature) || - !accused.PubKey.VerifyBytes(voteBSignBytes, tx.VoteB.Signature) { - return txs.ErrTxInvalidSignature - } - - // Verify equivocation - // TODO: in the future, just require one vote from a previous height that - // doesn't exist on this chain. - if tx.VoteA.Height != tx.VoteB.Height { - return errors.New("DupeoutTx heights don't match") - } - if tx.VoteA.Round != tx.VoteB.Round { - return errors.New("DupeoutTx rounds don't match") - } - if tx.VoteA.Type != tx.VoteB.Type { - return errors.New("DupeoutTx types don't match") - } - if bytes.Equal(tx.VoteA.BlockHash, tx.VoteB.BlockHash) { - return errors.New("DupeoutTx blockhashes shouldn't match") - } - - // Good! (Bad validator!) - _s.destroyValidator(accused) - if evc != nil { - evc.FireEvent(txs.EventStringDupeout(), txs.EventDataTx{tx, nil, ""}) - } - return nil */ case *txs.PermissionsTx: - var inAcc *acm.Account - // Validate input - inAcc = blockCache.GetAccount(tx.Input.Address) + inAcc, err := acm.GetMutableAccount(exe.blockCache, tx.Input.Address) + if err != nil { + return err + } if inAcc == nil { logging.InfoMsg(logger, "Cannot find input account", "tx_input", tx.Input) return txs.ErrTxInvalidAddress } - permFlag := tx.PermArgs.PermFlag() + permFlag := tx.PermArgs.PermFlag // check permission - if !HasPermission(blockCache, inAcc, permFlag, logger) { - return fmt.Errorf("Account %X does not have moderator permission %s (%b)", tx.Input.Address, ptypes.PermFlagToString(permFlag), permFlag) + if !HasPermission(exe.blockCache, inAcc, permFlag, logger) { + return fmt.Errorf("account %s does not have moderator permission %s (%b)", tx.Input.Address, + permission.PermFlagToString(permFlag), permFlag) } // pubKey should be present in either "inAcc" or "tx.Input" @@ -909,8 +717,8 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable "tx_input", tx.Input) return err } - signBytes := acm.SignBytes(_s.ChainID, tx) - err := validateInput(inAcc, signBytes, tx.Input) + signBytes := acm.SignBytes(exe.chainID, tx) + err = validateInput(inAcc, signBytes, tx.Input) if err != nil { logging.InfoMsg(logger, "validateInput failed", "tx_input", tx.Input, @@ -921,47 +729,49 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable value := tx.Input.Amount logging.TraceMsg(logger, "New PermissionsTx", - "perm_flag", ptypes.PermFlagToString(permFlag), + "perm_flag", permission.PermFlagToString(permFlag), "perm_args", tx.PermArgs) - var permAcc *acm.Account - switch args := tx.PermArgs.(type) { - case *ptypes.HasBaseArgs: + 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 *ptypes.SetBaseArgs: - if permAcc = blockCache.GetAccount(args.Address); permAcc == nil { - return fmt.Errorf("Trying to update permissions for unknown account %X", args.Address) - } - err = permAcc.Permissions.Base.Set(args.Permission, args.Value) - case *ptypes.UnsetBaseArgs: - if permAcc = blockCache.GetAccount(args.Address); permAcc == nil { - return fmt.Errorf("Trying to update permissions for unknown account %X", args.Address) - } - err = permAcc.Permissions.Base.Unset(args.Permission) - case *ptypes.SetGlobalArgs: - if permAcc = blockCache.GetAccount(ptypes.GlobalPermissionsAddress); permAcc == nil { - sanity.PanicSanity("can't find global permissions account") - } - err = permAcc.Permissions.Base.Set(args.Permission, args.Value) - case *ptypes.HasRoleArgs: + case permission.SetBase: + permAcc, err = mutatePermissions(exe.blockCache, 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.blockCache, tx.PermArgs.Address, + func(perms *ptypes.AccountPermissions) error { + return perms.Base.Unset(tx.PermArgs.Permission) + }) + case permission.SetGlobal: + permAcc, err = mutatePermissions(exe.blockCache, permission.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 *ptypes.AddRoleArgs: - if permAcc = blockCache.GetAccount(args.Address); permAcc == nil { - return fmt.Errorf("Trying to update roles for unknown account %X", args.Address) - } - if !permAcc.Permissions.AddRole(args.Role) { - return fmt.Errorf("Role (%s) already exists for account %X", args.Role, args.Address) - } - case *ptypes.RmRoleArgs: - if permAcc = blockCache.GetAccount(args.Address); permAcc == nil { - return fmt.Errorf("Trying to update roles for unknown account %X", args.Address) - } - if !permAcc.Permissions.RmRole(args.Role) { - return fmt.Errorf("Role (%s) does not exist for account %X", args.Role, args.Address) - } + case permission.AddRole: + permAcc, err = mutatePermissions(exe.blockCache, 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.blockCache, 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: - sanity.PanicSanity(fmt.Sprintf("invalid permission function: %s", ptypes.PermFlagToString(permFlag))) + panic(fmt.Sprintf("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? @@ -970,34 +780,345 @@ func ExecTx(blockCache *BlockCache, tx txs.Tx, runCall bool, evc events.Fireable } // Good! - inAcc.Sequence += 1 - inAcc.Balance -= value - blockCache.UpdateAccount(inAcc) + inAcc.IncSequence() + inAcc.SubtractFromBalance(value) + exe.blockCache.UpdateAccount(inAcc) if permAcc != nil { - blockCache.UpdateAccount(permAcc) + exe.blockCache.UpdateAccount(permAcc) } - if evc != nil { - evc.FireEvent(txs.EventStringAccInput(tx.Input.Address), txs.EventDataTx{tx, nil, ""}) - evc.FireEvent(txs.EventStringPermissions(ptypes.PermFlagToString(permFlag)), txs.EventDataTx{tx, nil, ""}) + if exe.eventCache != nil { + exe.eventCache.Fire(events.EventStringAccInput(tx.Input.Address), + events.EventDataTx{tx, nil, ""}) + exe.eventCache.Fire(events.EventStringPermissions(permission.PermFlagToString(permFlag)), + events.EventDataTx{tx, nil, ""}) } return nil default: // binary decoding should not let this happen - sanity.PanicSanity("Unknown Tx type") + panic("Unknown Tx type") return nil } } +func mutatePermissions(stateReader acm.StateReader, address acm.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 acm.Getter, + ins []*txs.TxInput) (map[acm.Address]acm.MutableAccount, error) { + + accounts := map[acm.Address]acm.MutableAccount{} + for _, in := range ins { + // Account shouldn't be duplicated + if _, ok := accounts[in.Address]; ok { + return nil, txs.ErrTxDuplicateAddress + } + acc, err := acm.GetMutableAccount(accountGetter, in.Address) + if err != nil { + return nil, err + } + if acc == nil { + return nil, txs.ErrTxInvalidAddress + } + // PublicKey should be present in either "account" or "in" + if err := checkInputPubKey(acc, in); err != nil { + return nil, err + } + accounts[in.Address] = acc + } + return accounts, nil +} + +func getOrMakeOutputs(accountGetter acm.Getter, accs map[acm.Address]acm.MutableAccount, + outs []*txs.TxOutput, logger logging_types.InfoTraceLogger) (map[acm.Address]acm.MutableAccount, error) { + if accs == nil { + accs = make(map[acm.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, txs.ErrTxDuplicateAddress + } + acc, err := acm.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 +} + +// Since all ethereum accounts implicitly exist we sometimes lazily create an Account object to represent them +// only when needed. Sometimes we need to create an unknown Account knowing only its address (which is expected to +// be a deterministic hash of its associated public key) and not its public key. When we eventually receive a +// transaction acting on behalf of that account we will be given a public key that we can check matches the address. +// If it does then we will associate the public key with the stub account already registered in the system once and +// for all time. +func checkInputPubKey(acc acm.MutableAccount, in *txs.TxInput) error { + if acc.PublicKey().Unwrap() == nil { + if in.PubKey.Unwrap() == nil { + return txs.ErrTxUnknownPubKey + } + addressFromPubKey := in.PubKey.Address() + addressFromAccount := acc.Address() + if addressFromPubKey != addressFromAccount { + return txs.ErrTxInvalidPubKey + } + acc.SetPublicKey(in.PubKey) + } else { + in.PubKey = acm.PublicKey{} + } + return nil +} + +func validateInputs(accs map[acm.Address]acm.MutableAccount, signBytes []byte, ins []*txs.TxInput) (total uint64, err error) { + for _, in := range ins { + acc := accs[in.Address] + if acc == nil { + panic("validateInputs() expects account in accounts") + } + err = validateInput(acc, signBytes, in) + if err != nil { + return + } + // Good. Add amount to total + total += in.Amount + } + return total, nil +} + +func validateInput(acc acm.MutableAccount, signBytes []byte, in *txs.TxInput) (err error) { + // Check TxInput basic + if err := in.ValidateBasic(); err != nil { + return err + } + // Check signatures + if !acc.PublicKey().VerifyBytes(signBytes, in.Signature) { + return txs.ErrTxInvalidSignature + } + // Check sequences + if acc.Sequence()+1 != uint64(in.Sequence) { + return txs.ErrTxInvalidSequence{ + Got: in.Sequence, + Expected: acc.Sequence() + uint64(1), + } + } + // Check amount + if acc.Balance() < uint64(in.Amount) { + return txs.ErrTxInsufficientFunds + } + return nil +} + +func validateOutputs(outs []*txs.TxOutput) (total uint64, err error) { + 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[acm.Address]acm.MutableAccount, ins []*txs.TxInput) { + for _, in := range ins { + acc := accs[in.Address] + if acc == nil { + panic("adjustByInputs() expects account in accounts") + } + if acc.Balance() < in.Amount { + panic("adjustByInputs() expects sufficient funds") + } + acc.SubtractFromBalance(in.Amount) + + acc.IncSequence() + } +} + +func adjustByOutputs(accs map[acm.Address]acm.MutableAccount, outs []*txs.TxOutput) { + for _, out := range outs { + acc := accs[out.Address] + if acc == nil { + panic("adjustByOutputs() expects account in accounts") + } + acc.AddToBalance(out.Amount) + } +} + //--------------------------------------------------------------- // Get permission on an account or fall back to global value -func HasPermission(state AccountGetter, acc *acm.Account, perm ptypes.PermFlag, +func HasPermission(accountGetter acm.Getter, acc acm.Account, perm ptypes.PermFlag, logger logging_types.InfoTraceLogger) bool { - if perm > ptypes.AllPermFlags { - sanity.PanicSanity("Checking an unknown permission in state should never happen") + if perm > permission.AllPermFlags { + panic("Checking an unknown permission in state should never happen") } //if acc == nil { @@ -1005,16 +1126,17 @@ func HasPermission(state AccountGetter, acc *acm.Account, perm ptypes.PermFlag, // this needs to fall back to global or do some other specific things // eg. a bondAcc may be nil and so can only bond if global bonding is true //} - permString := ptypes.PermFlagToString(perm) + permString := permission.PermFlagToString(perm) - v, err := acc.Permissions.Base.Get(perm) + v, err := acc.Permissions().Base.Get(perm) if _, ok := err.(ptypes.ErrValueNotSet); ok { - if state == nil { - sanity.PanicSanity("All known global permissions should be set!") + if accountGetter == nil { + panic("All known global permissions should be set!") } logging.TraceMsg(logger, "Permission for account is not set. Querying GlobalPermissionsAddres.", "perm_flag", permString) - return HasPermission(nil, state.GetAccount(ptypes.GlobalPermissionsAddress), perm, logger) + + return HasPermission(nil, permission.GlobalPermissionsAccount(accountGetter), perm, logger) } else if v { logging.TraceMsg(logger, "Account has permission", "account_address", acc.Address, @@ -1028,51 +1150,51 @@ func HasPermission(state AccountGetter, acc *acm.Account, perm ptypes.PermFlag, } // TODO: for debug log the failed accounts -func hasSendPermission(state AccountGetter, accs map[string]*acm.Account, +func hasSendPermission(accountGetter acm.Getter, accs map[acm.Address]acm.MutableAccount, logger logging_types.InfoTraceLogger) bool { for _, acc := range accs { - if !HasPermission(state, acc, ptypes.Send, logger) { + if !HasPermission(accountGetter, acc, permission.Send, logger) { return false } } return true } -func hasNamePermission(state AccountGetter, acc *acm.Account, +func hasNamePermission(accountGetter acm.Getter, acc acm.Account, logger logging_types.InfoTraceLogger) bool { - return HasPermission(state, acc, ptypes.Name, logger) + return HasPermission(accountGetter, acc, permission.Name, logger) } -func hasCallPermission(state AccountGetter, acc *acm.Account, +func hasCallPermission(accountGetter acm.Getter, acc acm.Account, logger logging_types.InfoTraceLogger) bool { - return HasPermission(state, acc, ptypes.Call, logger) + return HasPermission(accountGetter, acc, permission.Call, logger) } -func hasCreateContractPermission(state AccountGetter, acc *acm.Account, +func hasCreateContractPermission(accountGetter acm.Getter, acc acm.Account, logger logging_types.InfoTraceLogger) bool { - return HasPermission(state, acc, ptypes.CreateContract, logger) + return HasPermission(accountGetter, acc, permission.CreateContract, logger) } -func hasCreateAccountPermission(state AccountGetter, accs map[string]*acm.Account, +func hasCreateAccountPermission(accountGetter acm.Getter, accs map[acm.Address]acm.MutableAccount, logger logging_types.InfoTraceLogger) bool { for _, acc := range accs { - if !HasPermission(state, acc, ptypes.CreateAccount, logger) { + if !HasPermission(accountGetter, acc, permission.CreateAccount, logger) { return false } } return true } -func hasBondPermission(state AccountGetter, acc *acm.Account, +func hasBondPermission(accountGetter acm.Getter, acc acm.Account, logger logging_types.InfoTraceLogger) bool { - return HasPermission(state, acc, ptypes.Bond, logger) + return HasPermission(accountGetter, acc, permission.Bond, logger) } -func hasBondOrSendPermission(state AccountGetter, accs map[string]*acm.Account, +func hasBondOrSendPermission(accountGetter acm.Getter, accs map[acm.Address]acm.Account, logger logging_types.InfoTraceLogger) bool { for _, acc := range accs { - if !HasPermission(state, acc, ptypes.Bond, logger) { - if !HasPermission(state, acc, ptypes.Send, logger) { + if !HasPermission(accountGetter, acc, permission.Bond, logger) { + if !HasPermission(accountGetter, acc, permission.Send, logger) { return false } } diff --git a/execution/execution_test.go b/execution/execution_test.go new file mode 100644 index 00000000..68e4d96b --- /dev/null +++ b/execution/execution_test.go @@ -0,0 +1,1329 @@ +// 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 ( + "bytes" + "fmt" + "strconv" + "testing" + "time" + + acm "github.com/hyperledger/burrow/account" + . "github.com/hyperledger/burrow/binary" + bcm "github.com/hyperledger/burrow/blockchain" + "github.com/hyperledger/burrow/event" + exe_events "github.com/hyperledger/burrow/execution/events" + "github.com/hyperledger/burrow/execution/evm" + . "github.com/hyperledger/burrow/execution/evm/asm" + "github.com/hyperledger/burrow/execution/evm/asm/bc" + evm_events "github.com/hyperledger/burrow/execution/evm/events" + "github.com/hyperledger/burrow/genesis" + "github.com/hyperledger/burrow/logging/loggers" + "github.com/hyperledger/burrow/permission" + ptypes "github.com/hyperledger/burrow/permission/types" + "github.com/hyperledger/burrow/txs" + dbm "github.com/tendermint/tmlibs/db" +) + +var ( + dbBackend = "memdb" + dbDir = "" + permissionsContract = evm.SNativeContracts()["Permissions"] +) + +/* +Permission Tests: + +- SendTx: +x - 1 input, no perm, call perm, create perm +x - 1 input, perm +x - 2 inputs, one with perm one without + +- CallTx, CALL +x - 1 input, no perm, send perm, create perm +x - 1 input, perm +x - contract runs call but doesn't have call perm +x - contract runs call and has call perm +x - contract runs call (with perm), runs contract that runs call (without perm) +x - contract runs call (with perm), runs contract that runs call (with perm) + +- CallTx for Create, CREATE +x - 1 input, no perm, send perm, call perm +x - 1 input, perm +x - contract runs create but doesn't have create perm +x - contract runs create but has perm +x - contract runs call with empty address (has call and create perm) + +- NameTx + - no perm, send perm, call perm + - with perm + +- BondTx +x - 1 input, no perm +x - 1 input, perm +x - 1 bonder with perm, input without send or bond +x - 1 bonder with perm, input with send +x - 1 bonder with perm, input with bond +x - 2 inputs, one with perm one without + +- SendTx for new account +x - 1 input, 1 unknown ouput, input with send, not create (fail) +x - 1 input, 1 unknown ouput, input with send and create (pass) +x - 2 inputs, 1 unknown ouput, both inputs with send, one with create, one without (fail) +x - 2 inputs, 1 known output, 1 unknown ouput, one input with create, one without (fail) +x - 2 inputs, 1 unknown ouput, both inputs with send, both inputs with create (pass ) +x - 2 inputs, 1 known output, 1 unknown ouput, both inputs with create, (pass) + + +- CALL for new account +x - unknown output, without create (fail) +x - unknown output, with create (pass) + + +- SNative (CallTx, CALL): + - for each of CallTx, Call +x - call each snative without permission, fails +x - call each snative with permission, pass + - list: +x - base: has,set,unset +x - globals: set +x - roles: has, add, rm + + +*/ + +// keys +var users = makeUsers(10) +var logger = loggers.NewNoopInfoTraceLogger() + +func makeUsers(n int) []acm.PrivateAccount { + users := make([]acm.PrivateAccount, n) + for i := 0; i < n; i++ { + secret := "mysecret" + strconv.Itoa(i) + users[i] = acm.GeneratePrivateAccountFromSecret(secret) + } + return users +} + +func makeExecutor(state *State) *executor { + return newExecutor(true, state, testChainID, bcm.NewBlockchain(testGenesisDoc), event.NewEmitter(logger), + logger) +} + +func newBaseGenDoc(globalPerm, accountPerm ptypes.AccountPermissions) genesis.GenesisDoc { + genAccounts := []genesis.Account{} + for _, user := range users[:5] { + accountPermCopy := accountPerm // Create new instance for custom overridability. + genAccounts = append(genAccounts, genesis.Account{ + BasicAccount: genesis.BasicAccount{ + Address: user.Address(), + Amount: 1000000, + }, + Permissions: accountPermCopy, + }) + } + + return genesis.GenesisDoc{ + GenesisTime: time.Now(), + ChainName: testGenesisDoc.ChainName, + GlobalPermissions: globalPerm, + Accounts: genAccounts, + Validators: []genesis.Validator{ + { + BasicAccount: genesis.BasicAccount{ + PublicKey: users[0].PublicKey(), + Amount: 10, + }, + UnbondTo: []genesis.BasicAccount{ + { + Address: users[0].Address(), + }, + }, + }, + }, + } +} + +//func getAccount(state acm.Getter, address acm.Address) acm.MutableAccount { +// acc, _ := acm.GetMutableAccount(state, address) +// return acc +//} + +func TestSendFails(t *testing.T) { + stateDB := dbm.NewDB("state", dbBackend, dbDir) + 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) + st := MakeGenesisState(stateDB, &genDoc) + batchCommitter := makeExecutor(st) + + //------------------- + // send txs + + // simple send tx should fail + tx := txs.NewSendTx() + if err := tx.AddInput(batchCommitter.blockCache, users[0].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[1].Address(), 5) + tx.SignInput(testChainID, 0, users[0]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // simple send tx with call perm should fail + tx = txs.NewSendTx() + if err := tx.AddInput(batchCommitter.blockCache, users[2].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[4].Address(), 5) + tx.SignInput(testChainID, 0, users[2]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // simple send tx with create perm should fail + tx = txs.NewSendTx() + if err := tx.AddInput(batchCommitter.blockCache, users[3].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[4].Address(), 5) + tx.SignInput(testChainID, 0, users[3]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // simple send tx to unknown account without create_account perm should fail + acc := getAccount(batchCommitter.blockCache, users[3].Address()) + acc.MutablePermissions().Base.Set(permission.Send, true) + batchCommitter.blockCache.UpdateAccount(acc) + tx = txs.NewSendTx() + if err := tx.AddInput(batchCommitter.blockCache, users[3].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[6].Address(), 5) + tx.SignInput(testChainID, 0, users[3]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } +} + +func TestName(t *testing.T) { + stateDB := dbm.NewDB("state", dbBackend, dbDir) + genDoc := newBaseGenDoc(permission.ZeroAccountPermissions, permission.ZeroAccountPermissions) + genDoc.Accounts[0].Permissions.Base.Set(permission.Send, true) + genDoc.Accounts[1].Permissions.Base.Set(permission.Name, true) + st := MakeGenesisState(stateDB, &genDoc) + batchCommitter := makeExecutor(st) + + //------------------- + // name txs + + // simple name tx without perm should fail + tx, err := txs.NewNameTx(st, users[0].PublicKey(), "somename", "somedata", 10000, 100) + if err != nil { + t.Fatal(err) + } + tx.Sign(testChainID, users[0]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // simple name tx with perm should pass + tx, err = txs.NewNameTx(st, users[1].PublicKey(), "somename", "somedata", 10000, 100) + if err != nil { + t.Fatal(err) + } + tx.Sign(testChainID, users[1]) + if err := batchCommitter.Execute(tx); err != nil { + t.Fatal(err) + } +} + +func TestCallFails(t *testing.T) { + stateDB := dbm.NewDB("state", dbBackend, dbDir) + 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) + st := MakeGenesisState(stateDB, &genDoc) + batchCommitter := makeExecutor(st) + + //------------------- + // call txs + + address4 := users[4].Address() + // simple call tx should fail + tx, _ := txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), &address4, nil, 100, 100, 100) + tx.Sign(testChainID, users[0]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // simple call tx with send permission should fail + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[1].PublicKey(), &address4, nil, 100, 100, 100) + tx.Sign(testChainID, users[1]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // simple call tx with create permission should fail + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[3].PublicKey(), &address4, nil, 100, 100, 100) + tx.Sign(testChainID, users[3]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + //------------------- + // create txs + + // simple call create tx should fail + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), nil, nil, 100, 100, 100) + tx.Sign(testChainID, users[0]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // simple call create tx with send perm should fail + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[1].PublicKey(), nil, nil, 100, 100, 100) + tx.Sign(testChainID, users[1]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // simple call create tx with call perm should fail + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[2].PublicKey(), nil, nil, 100, 100, 100) + tx.Sign(testChainID, users[2]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } +} + +func TestSendPermission(t *testing.T) { + stateDB := dbm.NewDB("state", dbBackend, dbDir) + genDoc := newBaseGenDoc(permission.ZeroAccountPermissions, permission.ZeroAccountPermissions) + genDoc.Accounts[0].Permissions.Base.Set(permission.Send, true) // give the 0 account permission + st := MakeGenesisState(stateDB, &genDoc) + batchCommitter := makeExecutor(st) + + // A single input, having the permission, should succeed + tx := txs.NewSendTx() + if err := tx.AddInput(batchCommitter.blockCache, users[0].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[1].Address(), 5) + tx.SignInput(testChainID, 0, users[0]) + if err := batchCommitter.Execute(tx); err != nil { + t.Fatal("Transaction failed", err) + } + + // Two inputs, one with permission, one without, should fail + tx = txs.NewSendTx() + if err := tx.AddInput(batchCommitter.blockCache, users[0].PublicKey(), 5); err != nil { + t.Fatal(err) + } + if err := tx.AddInput(batchCommitter.blockCache, users[1].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[2].Address(), 10) + tx.SignInput(testChainID, 0, users[0]) + tx.SignInput(testChainID, 1, users[1]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } +} + +func TestCallPermission(t *testing.T) { + stateDB := dbm.NewDB("state", dbBackend, dbDir) + genDoc := newBaseGenDoc(permission.ZeroAccountPermissions, permission.ZeroAccountPermissions) + genDoc.Accounts[0].Permissions.Base.Set(permission.Call, true) // give the 0 account permission + st := MakeGenesisState(stateDB, &genDoc) + batchCommitter := makeExecutor(st) + + //------------------------------ + // call to simple contract + fmt.Println("\n##### SIMPLE CONTRACT") + + // create simple contract + simpleContractAddr := acm.NewContractAddress(users[0].Address(), 100) + simpleAcc := acm.ConcreteAccount{ + Address: simpleContractAddr, + Balance: 0, + Code: []byte{0x60}, + Sequence: 0, + StorageRoot: Zero256.Bytes(), + Permissions: permission.ZeroAccountPermissions, + }.MutableAccount() + st.UpdateAccount(simpleAcc) + + // A single input, having the permission, should succeed + tx, _ := txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), &simpleContractAddr, nil, 100, 100, 100) + tx.Sign(testChainID, users[0]) + if err := batchCommitter.Execute(tx); err != nil { + t.Fatal("Transaction failed", err) + } + + //---------------------------------------------------------- + // call to contract that calls simple contract - without perm + fmt.Println("\n##### CALL TO SIMPLE CONTRACT (FAIL)") + + // create contract that calls the simple contract + contractCode := callContractCode(simpleContractAddr) + caller1ContractAddr := acm.NewContractAddress(users[0].Address(), 101) + caller1Acc := acm.ConcreteAccount{ + Address: caller1ContractAddr, + Balance: 10000, + Code: contractCode, + Sequence: 0, + StorageRoot: Zero256.Bytes(), + Permissions: permission.ZeroAccountPermissions, + }.MutableAccount() + batchCommitter.blockCache.UpdateAccount(caller1Acc) + + // A single input, having the permission, but the contract doesn't have permission + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), &caller1ContractAddr, nil, 100, 10000, 100) + tx.Sign(testChainID, users[0]) + + // we need to subscribe to the Call event to detect the exception + _, exception := execTxWaitEvent(t, batchCommitter, tx, evm_events.EventStringAccCall(caller1ContractAddr)) // + if exception == "" { + t.Fatal("Expected exception") + } + + //---------------------------------------------------------- + // 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) + batchCommitter.blockCache.UpdateAccount(caller1Acc) + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), &caller1ContractAddr, nil, 100, 10000, 100) + tx.Sign(testChainID, users[0]) + + // we need to subscribe to the Call event to detect the exception + _, exception = execTxWaitEvent(t, batchCommitter, tx, evm_events.EventStringAccCall(caller1ContractAddr)) // + if exception != "" { + t.Fatal("Unexpected exception:", exception) + } + + //---------------------------------------------------------- + // call to contract that calls contract that calls simple contract - without perm + // caller1Contract calls simpleContract. caller2Contract calls caller1Contract. + // caller1Contract does not have call perms, but caller2Contract does. + fmt.Println("\n##### CALL TO CONTRACT CALLING SIMPLE CONTRACT (FAIL)") + + contractCode2 := callContractCode(caller1ContractAddr) + caller2ContractAddr := acm.NewContractAddress(users[0].Address(), 102) + caller2Acc := acm.ConcreteAccount{ + Address: caller2ContractAddr, + Balance: 1000, + Code: contractCode2, + Sequence: 0, + StorageRoot: Zero256.Bytes(), + Permissions: permission.ZeroAccountPermissions, + }.MutableAccount() + caller1Acc.MutablePermissions().Base.Set(permission.Call, false) + caller2Acc.MutablePermissions().Base.Set(permission.Call, true) + batchCommitter.blockCache.UpdateAccount(caller1Acc) + batchCommitter.blockCache.UpdateAccount(caller2Acc) + + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), &caller2ContractAddr, nil, 100, 10000, 100) + tx.Sign(testChainID, users[0]) + + // we need to subscribe to the Call event to detect the exception + _, exception = execTxWaitEvent(t, batchCommitter, tx, evm_events.EventStringAccCall(caller1ContractAddr)) // + if exception == "" { + t.Fatal("Expected exception") + } + + //---------------------------------------------------------- + // call to contract that calls contract that calls simple contract - without perm + // caller1Contract calls simpleContract. caller2Contract calls caller1Contract. + // both caller1 and caller2 have permission + fmt.Println("\n##### CALL TO CONTRACT CALLING SIMPLE CONTRACT (PASS)") + + caller1Acc.MutablePermissions().Base.Set(permission.Call, true) + batchCommitter.blockCache.UpdateAccount(caller1Acc) + + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), &caller2ContractAddr, nil, 100, 10000, 100) + tx.Sign(testChainID, users[0]) + + // we need to subscribe to the Call event to detect the exception + _, exception = execTxWaitEvent(t, batchCommitter, tx, evm_events.EventStringAccCall(caller1ContractAddr)) // + if exception != "" { + t.Fatal("Unexpected exception", exception) + } +} + +func TestCreatePermission(t *testing.T) { + stateDB := dbm.NewDB("state", dbBackend, dbDir) + 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 + st := MakeGenesisState(stateDB, &genDoc) + batchCommitter := makeExecutor(st) + + //------------------------------ + // create a simple contract + fmt.Println("\n##### CREATE SIMPLE CONTRACT") + + contractCode := []byte{0x60} + createCode := wrapContractForCreate(contractCode) + + // A single input, having the permission, should succeed + tx, _ := txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), nil, createCode, 100, 100, 100) + tx.Sign(testChainID, users[0]) + if err := batchCommitter.Execute(tx); err != nil { + t.Fatal("Transaction failed", err) + } + // ensure the contract is there + contractAddr := acm.NewContractAddress(tx.Input.Address, tx.Input.Sequence) + contractAcc := getAccount(batchCommitter.blockCache, contractAddr) + if contractAcc == nil { + t.Fatalf("failed to create contract %s", contractAddr) + } + if !bytes.Equal(contractAcc.Code(), contractCode) { + t.Fatalf("contract does not have correct code. Got %X, expected %X", contractAcc.Code(), contractCode) + } + + //------------------------------ + // create contract that uses the CREATE op + fmt.Println("\n##### CREATE FACTORY") + + contractCode = []byte{0x60} + createCode = wrapContractForCreate(contractCode) + factoryCode := createContractCode() + createFactoryCode := wrapContractForCreate(factoryCode) + + // A single input, having the permission, should succeed + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), nil, createFactoryCode, 100, 100, 100) + tx.Sign(testChainID, users[0]) + if err := batchCommitter.Execute(tx); err != nil { + t.Fatal("Transaction failed", err) + } + // ensure the contract is there + contractAddr = acm.NewContractAddress(tx.Input.Address, tx.Input.Sequence) + contractAcc = getAccount(batchCommitter.blockCache, contractAddr) + if contractAcc == nil { + t.Fatalf("failed to create contract %s", contractAddr) + } + if !bytes.Equal(contractAcc.Code(), factoryCode) { + t.Fatalf("contract does not have correct code. Got %X, expected %X", contractAcc.Code(), factoryCode) + } + + //------------------------------ + // call the contract (should FAIL) + fmt.Println("\n###### CALL THE FACTORY (FAIL)") + + // A single input, having the permission, should succeed + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), &contractAddr, createCode, 100, 100, 100) + tx.Sign(testChainID, users[0]) + // we need to subscribe to the Call event to detect the exception + _, exception := execTxWaitEvent(t, batchCommitter, tx, evm_events.EventStringAccCall(contractAddr)) // + if exception == "" { + t.Fatal("expected exception") + } + + //------------------------------ + // call the contract (should PASS) + fmt.Println("\n###### CALL THE FACTORY (PASS)") + + contractAcc.MutablePermissions().Base.Set(permission.CreateContract, true) + batchCommitter.blockCache.UpdateAccount(contractAcc) + + // A single input, having the permission, should succeed + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), &contractAddr, createCode, 100, 100, 100) + tx.Sign(testChainID, users[0]) + // we need to subscribe to the Call event to detect the exception + _, exception = execTxWaitEvent(t, batchCommitter, tx, evm_events.EventStringAccCall(contractAddr)) // + if exception != "" { + t.Fatal("unexpected exception", exception) + } + + //-------------------------------- + fmt.Println("\n##### CALL to empty address") + code := callContractCode(acm.Address{}) + + contractAddr = acm.NewContractAddress(users[0].Address(), 110) + contractAcc = acm.ConcreteAccount{ + Address: contractAddr, + Balance: 1000, + Code: code, + Sequence: 0, + StorageRoot: Zero256.Bytes(), + Permissions: permission.ZeroAccountPermissions, + }.MutableAccount() + contractAcc.MutablePermissions().Base.Set(permission.Call, true) + contractAcc.MutablePermissions().Base.Set(permission.CreateContract, true) + batchCommitter.blockCache.UpdateAccount(contractAcc) + + // this should call the 0 address but not create ... + tx, _ = txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), &contractAddr, createCode, 100, 10000, 100) + tx.Sign(testChainID, users[0]) + // we need to subscribe to the Call event to detect the exception + _, exception = execTxWaitEvent(t, batchCommitter, tx, evm_events.EventStringAccCall(acm.Address{})) // + if exception != "" { + t.Fatal("unexpected exception", exception) + } + zeroAcc := getAccount(batchCommitter.blockCache, acm.Address{}) + if len(zeroAcc.Code()) != 0 { + t.Fatal("the zero account was given code from a CALL!") + } +} + +/* TODO +func TestBondPermission(t *testing.T) { + stateDB := dbm.NewDB("state",dbBackend,dbDir) + genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) + st := MakeGenesisState(stateDB, &genDoc) + batchCommitter := makeExecutor(st) + var bondAcc *acm.Account + + //------------------------------ + // one bonder without permission should fail + tx, _ := txs.NewBondTx(users[1].PublicKey()) + if err := tx.AddInput(batchCommitter.blockCache, users[1].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[1].Address(), 5) + tx.SignInput(testChainID, 0, users[1]) + tx.SignBond(testChainID, users[1]) + if err := ExecTx(batchCommitter.blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + //------------------------------ + // one bonder with permission should pass + bondAcc = batchCommitter.blockCache.GetAccount(users[1].Address()) + bondAcc.Permissions.Base.Set(permission.Bond, true) + batchCommitter.blockCache.UpdateAccount(bondAcc) + if err := ExecTx(batchCommitter.blockCache, tx, true, nil); err != nil { + t.Fatal("Unexpected error", err) + } + + // reset state (we can only bond with an account once ..) + genDoc = newBaseGenDoc(PermsAllFalse, PermsAllFalse) + st = MakeGenesisState(stateDB, &genDoc) + batchCommitter.blockCache = NewBlockCache(st) + bondAcc = batchCommitter.blockCache.GetAccount(users[1].Address()) + bondAcc.Permissions.Base.Set(permission.Bond, true) + batchCommitter.blockCache.UpdateAccount(bondAcc) + //------------------------------ + // one bonder with permission and an input without send should fail + tx, _ = txs.NewBondTx(users[1].PublicKey()) + if err := tx.AddInput(batchCommitter.blockCache, users[2].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[1].Address(), 5) + tx.SignInput(testChainID, 0, users[2]) + tx.SignBond(testChainID, users[1]) + if err := ExecTx(batchCommitter.blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // reset state (we can only bond with an account once ..) + genDoc = newBaseGenDoc(PermsAllFalse, PermsAllFalse) + st = MakeGenesisState(stateDB, &genDoc) + batchCommitter.blockCache = NewBlockCache(st) + bondAcc = batchCommitter.blockCache.GetAccount(users[1].Address()) + bondAcc.Permissions.Base.Set(permission.Bond, true) + batchCommitter.blockCache.UpdateAccount(bondAcc) + //------------------------------ + // one bonder with permission and an input with send should pass + sendAcc := batchCommitter.blockCache.GetAccount(users[2].Address()) + sendAcc.Permissions.Base.Set(permission.Send, true) + batchCommitter.blockCache.UpdateAccount(sendAcc) + tx, _ = txs.NewBondTx(users[1].PublicKey()) + if err := tx.AddInput(batchCommitter.blockCache, users[2].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[1].Address(), 5) + tx.SignInput(testChainID, 0, users[2]) + tx.SignBond(testChainID, users[1]) + if err := ExecTx(batchCommitter.blockCache, tx, true, nil); err != nil { + t.Fatal("Unexpected error", err) + } + + // reset state (we can only bond with an account once ..) + genDoc = newBaseGenDoc(PermsAllFalse, PermsAllFalse) + st = MakeGenesisState(stateDB, &genDoc) + batchCommitter.blockCache = NewBlockCache(st) + bondAcc = batchCommitter.blockCache.GetAccount(users[1].Address()) + bondAcc.Permissions.Base.Set(permission.Bond, true) + batchCommitter.blockCache.UpdateAccount(bondAcc) + //------------------------------ + // one bonder with permission and an input with bond should pass + sendAcc.Permissions.Base.Set(permission.Bond, true) + batchCommitter.blockCache.UpdateAccount(sendAcc) + tx, _ = txs.NewBondTx(users[1].PublicKey()) + if err := tx.AddInput(batchCommitter.blockCache, users[2].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[1].Address(), 5) + tx.SignInput(testChainID, 0, users[2]) + tx.SignBond(testChainID, users[1]) + if err := ExecTx(batchCommitter.blockCache, tx, true, nil); err != nil { + t.Fatal("Unexpected error", err) + } + + // reset state (we can only bond with an account once ..) + genDoc = newBaseGenDoc(PermsAllFalse, PermsAllFalse) + st = MakeGenesisState(stateDB, &genDoc) + batchCommitter.blockCache = NewBlockCache(st) + bondAcc = batchCommitter.blockCache.GetAccount(users[1].Address()) + bondAcc.Permissions.Base.Set(permission.Bond, true) + batchCommitter.blockCache.UpdateAccount(bondAcc) + //------------------------------ + // one bonder with permission and an input from that bonder and an input without send or bond should fail + tx, _ = txs.NewBondTx(users[1].PublicKey()) + if err := tx.AddInput(batchCommitter.blockCache, users[1].PublicKey(), 5); err != nil { + t.Fatal(err) + } + if err := tx.AddInput(batchCommitter.blockCache, users[2].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[1].Address(), 5) + tx.SignInput(testChainID, 0, users[1]) + tx.SignInput(testChainID, 1, users[2]) + tx.SignBond(testChainID, users[1]) + if err := ExecTx(batchCommitter.blockCache, tx, true, nil); err == nil { + t.Fatal("Expected error") + } +} +*/ + +func TestCreateAccountPermission(t *testing.T) { + stateDB := dbm.NewDB("state", dbBackend, dbDir) + 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 + st := MakeGenesisState(stateDB, &genDoc) + batchCommitter := makeExecutor(st) + + //---------------------------------------------------------- + // SendTx to unknown account + + // A single input, having the permission, should succeed + tx := txs.NewSendTx() + if err := tx.AddInput(batchCommitter.blockCache, users[0].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[6].Address(), 5) + tx.SignInput(testChainID, 0, users[0]) + if err := batchCommitter.Execute(tx); err != nil { + t.Fatal("Transaction failed", err) + } + + // Two inputs, both with send, one with create, one without, should fail + tx = txs.NewSendTx() + if err := tx.AddInput(batchCommitter.blockCache, users[0].PublicKey(), 5); err != nil { + t.Fatal(err) + } + if err := tx.AddInput(batchCommitter.blockCache, users[1].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[7].Address(), 10) + tx.SignInput(testChainID, 0, users[0]) + tx.SignInput(testChainID, 1, users[1]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // Two inputs, both with send, one with create, one without, two ouputs (one known, one unknown) should fail + tx = txs.NewSendTx() + if err := tx.AddInput(batchCommitter.blockCache, users[0].PublicKey(), 5); err != nil { + t.Fatal(err) + } + if err := tx.AddInput(batchCommitter.blockCache, users[1].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[7].Address(), 4) + tx.AddOutput(users[4].Address(), 6) + tx.SignInput(testChainID, 0, users[0]) + tx.SignInput(testChainID, 1, users[1]) + if err := batchCommitter.Execute(tx); err == nil { + t.Fatal("Expected error") + } else { + fmt.Println(err) + } + + // Two inputs, both with send, both with create, should pass + acc := getAccount(batchCommitter.blockCache, users[1].Address()) + acc.MutablePermissions().Base.Set(permission.CreateAccount, true) + batchCommitter.blockCache.UpdateAccount(acc) + tx = txs.NewSendTx() + if err := tx.AddInput(batchCommitter.blockCache, users[0].PublicKey(), 5); err != nil { + t.Fatal(err) + } + if err := tx.AddInput(batchCommitter.blockCache, users[1].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[7].Address(), 10) + tx.SignInput(testChainID, 0, users[0]) + tx.SignInput(testChainID, 1, users[1]) + if err := batchCommitter.Execute(tx); err != nil { + t.Fatal("Unexpected error", err) + } + + // Two inputs, both with send, both with create, two outputs (one known, one unknown) should pass + tx = txs.NewSendTx() + if err := tx.AddInput(batchCommitter.blockCache, users[0].PublicKey(), 5); err != nil { + t.Fatal(err) + } + if err := tx.AddInput(batchCommitter.blockCache, users[1].PublicKey(), 5); err != nil { + t.Fatal(err) + } + tx.AddOutput(users[7].Address(), 7) + tx.AddOutput(users[4].Address(), 3) + tx.SignInput(testChainID, 0, users[0]) + tx.SignInput(testChainID, 1, users[1]) + if err := batchCommitter.Execute(tx); err != nil { + t.Fatal("Unexpected error", err) + } + + //---------------------------------------------------------- + // CALL to unknown account + + acc = getAccount(batchCommitter.blockCache, users[0].Address()) + acc.MutablePermissions().Base.Set(permission.Call, true) + batchCommitter.blockCache.UpdateAccount(acc) + + // call to contract that calls unknown account - without create_account perm + // create contract that calls the simple contract + contractCode := callContractCode(users[9].Address()) + caller1ContractAddr := acm.NewContractAddress(users[4].Address(), 101) + caller1Acc := acm.ConcreteAccount{ + Address: caller1ContractAddr, + Balance: 0, + Code: contractCode, + Sequence: 0, + StorageRoot: Zero256.Bytes(), + Permissions: permission.ZeroAccountPermissions, + }.MutableAccount() + batchCommitter.blockCache.UpdateAccount(caller1Acc) + + // A single input, having the permission, but the contract doesn't have permission + txCall, _ := txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), &caller1ContractAddr, nil, 100, 10000, 100) + txCall.Sign(testChainID, users[0]) + + // we need to subscribe to the Call event to detect the exception + _, exception := execTxWaitEvent(t, batchCommitter, txCall, evm_events.EventStringAccCall(caller1ContractAddr)) // + if exception == "" { + t.Fatal("Expected exception") + } + + // NOTE: for a contract to be able to CreateAccount, it must be able to call + // NOTE: for a 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) + batchCommitter.blockCache.UpdateAccount(caller1Acc) + // A single input, having the permission, but the contract doesn't have permission + txCall, _ = txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), &caller1ContractAddr, nil, 100, 10000, 100) + txCall.Sign(testChainID, users[0]) + + // we need to subscribe to the Call event to detect the exception + _, exception = execTxWaitEvent(t, batchCommitter, txCall, evm_events.EventStringAccCall(caller1ContractAddr)) // + if exception != "" { + t.Fatal("Unexpected exception", exception) + } + +} + +// holla at my boy +var DougAddress acm.Address + +func init() { + copy(DougAddress[:], ([]byte)("THISISDOUG")) +} + +func TestSNativeCALL(t *testing.T) { + stateDB := dbm.NewDB("state", dbBackend, dbDir) + 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[3].Permissions.AddRole("bumble") + genDoc.Accounts[3].Permissions.AddRole("bee") + st := MakeGenesisState(stateDB, &genDoc) + batchCommitter := makeExecutor(st) + + //---------------------------------------------------------- + // Test CALL to SNative contracts + + // make the main contract once + doug := acm.ConcreteAccount{ + Address: DougAddress, + Balance: 0, + Code: nil, + Sequence: 0, + StorageRoot: Zero256.Bytes(), + Permissions: permission.ZeroAccountPermissions, + }.MutableAccount() + + doug.MutablePermissions().Base.Set(permission.Call, true) + //doug.Permissions.Base.Set(permission.HasBase, true) + batchCommitter.blockCache.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 { + // return value should be true or false as a 32 byte array... + if !IsZeros(ret[:31]) || ret[31] != byte(1) { + return fmt.Errorf("Expected 1. Got %X", ret) + } + return nil + }) + + fmt.Println("\n#### SetBase") + // SetBase + 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 { + // 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 { + // return value should be true or false as a 32 byte array... + if !IsZeros(ret[:31]) || ret[31] != byte(1) { + return fmt.Errorf("Expected 1. Got %X", ret) + } + return nil + }) + + fmt.Println("\n#### UnsetBase") + // UnsetBase + 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 { + if !IsZeros(ret) { + return fmt.Errorf("Expected 0. Got %X", ret) + } + return nil + }) + + 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 { + // return value should be true or false as a 32 byte array... + if !IsZeros(ret[:31]) || ret[31] != byte(1) { + return fmt.Errorf("Expected 1. Got %X", ret) + } + return nil + }) + + fmt.Println("\n#### 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 { + if !IsZeros(ret[:31]) || ret[31] != byte(1) { + return fmt.Errorf("Expected 1. Got %X", ret) + } + return nil + }) + + fmt.Println("\n#### AddRole") + // AddRole + snativeAddress, pF, data = snativeRoleTestInputCALL("hasRole", users[3], "chuck") + testSNativeCALLExpectPass(t, batchCommitter, 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 }) + snativeAddress, pF, data = snativeRoleTestInputCALL("hasRole", users[3], "chuck") + testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { + if !IsZeros(ret[:31]) || ret[31] != byte(1) { + return fmt.Errorf("Expected 1. Got %X", ret) + } + return nil + }) + + fmt.Println("\n#### 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 }) + snativeAddress, pF, data = snativeRoleTestInputCALL("hasRole", users[3], "chuck") + testSNativeCALLExpectPass(t, batchCommitter, doug, pF, snativeAddress, data, func(ret []byte) error { + if !IsZeros(ret) { + return fmt.Errorf("Expected 0. Got %X", ret) + } + return nil + }) +} + +func TestSNativeTx(t *testing.T) { + stateDB := dbm.NewDB("state", dbBackend, dbDir) + 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[3].Permissions.AddRole("bumble") + genDoc.Accounts[3].Permissions.AddRole("bee") + st := MakeGenesisState(stateDB, &genDoc) + batchCommitter := makeExecutor(st) + + //---------------------------------------------------------- + // Test SNativeTx + + fmt.Println("\n#### SetBase") + // SetBase + snativeArgs := snativePermTestInputTx("setBase", users[3], permission.Bond, false) + testSNativeTxExpectFail(t, batchCommitter, snativeArgs) + testSNativeTxExpectPass(t, batchCommitter, permission.SetBase, snativeArgs) + acc := getAccount(batchCommitter.blockCache, users[3].Address()) + if v, _ := acc.MutablePermissions().Base.Get(permission.Bond); v { + t.Fatal("expected permission to be set false") + } + snativeArgs = snativePermTestInputTx("setBase", users[3], permission.CreateContract, true) + testSNativeTxExpectPass(t, batchCommitter, permission.SetBase, snativeArgs) + acc = getAccount(batchCommitter.blockCache, users[3].Address()) + if v, _ := acc.MutablePermissions().Base.Get(permission.CreateContract); !v { + t.Fatal("expected permission to be set true") + } + + fmt.Println("\n#### UnsetBase") + // UnsetBase + snativeArgs = snativePermTestInputTx("unsetBase", users[3], permission.CreateContract, false) + testSNativeTxExpectFail(t, batchCommitter, snativeArgs) + testSNativeTxExpectPass(t, batchCommitter, permission.UnsetBase, snativeArgs) + acc = getAccount(batchCommitter.blockCache, users[3].Address()) + if v, _ := acc.MutablePermissions().Base.Get(permission.CreateContract); v { + t.Fatal("expected permission to be set false") + } + + fmt.Println("\n#### SetGlobal") + // SetGlobalPerm + snativeArgs = snativePermTestInputTx("setGlobal", users[3], permission.CreateContract, true) + testSNativeTxExpectFail(t, batchCommitter, snativeArgs) + testSNativeTxExpectPass(t, batchCommitter, permission.SetGlobal, snativeArgs) + acc = getAccount(batchCommitter.blockCache, permission.GlobalPermissionsAddress) + if v, _ := acc.MutablePermissions().Base.Get(permission.CreateContract); !v { + t.Fatal("expected permission to be set true") + } + + fmt.Println("\n#### AddRole") + // AddRole + snativeArgs = snativeRoleTestInputTx("addRole", users[3], "chuck") + testSNativeTxExpectFail(t, batchCommitter, snativeArgs) + testSNativeTxExpectPass(t, batchCommitter, permission.AddRole, snativeArgs) + acc = getAccount(batchCommitter.blockCache, users[3].Address()) + if v := acc.Permissions().HasRole("chuck"); !v { + t.Fatal("expected role to be added") + } + + fmt.Println("\n#### RemoveRole") + // RemoveRole + snativeArgs = snativeRoleTestInputTx("removeRole", users[3], "chuck") + testSNativeTxExpectFail(t, batchCommitter, snativeArgs) + testSNativeTxExpectPass(t, batchCommitter, permission.RemoveRole, snativeArgs) + acc = getAccount(batchCommitter.blockCache, users[3].Address()) + if v := acc.Permissions().HasRole("chuck"); v { + t.Fatal("expected role to be removed") + } +} + +//------------------------------------------------------------------------------------- +// helpers + +var ExceptionTimeOut = "timed out waiting for event" + +// run ExecTx and wait for the Call event on given addr +// returns the msg data and an error/exception +func execTxWaitEvent(t *testing.T, batchCommitter *executor, tx txs.Tx, eventid string) (interface{}, string) { + evsw := event.NewEmitter(logger) + ch := make(chan event.AnyEventData) + evsw.Subscribe("test", eventid, func(msg event.AnyEventData) { + ch <- msg + }) + evc := event.NewEventCache(evsw) + batchCommitter.eventCache = evc + go func() { + if err := batchCommitter.Execute(tx); err != nil { + errStr := err.Error() + ch <- event.AnyEventData{Err: &errStr} + } + evc.Flush() + }() + ticker := time.NewTicker(5 * time.Second) + var msg event.AnyEventData + select { + case msg = <-ch: + case <-ticker.C: + return nil, ExceptionTimeOut + } + + switch ev := msg.Get().(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, "" + } +} + +// 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, + snativeAddress acm.Address, data []byte) { + testSNativeCALL(t, false, batchCommitter, 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, + snativeAddress acm.Address, data []byte, f func([]byte) error) { + testSNativeCALL(t, true, batchCommitter, doug, snativePerm, snativeAddress, data, f) +} + +func testSNativeCALL(t *testing.T, expectPass bool, batchCommitter *executor, doug acm.MutableAccount, + snativePerm ptypes.PermFlag, snativeAddress acm.Address, data []byte, f func([]byte) error) { + if expectPass { + doug.MutablePermissions().Base.Set(snativePerm, true) + } + + doug.SetCode(callContractCode(snativeAddress)) + dougAddress := doug.Address() + + batchCommitter.blockCache.UpdateAccount(doug) + tx, _ := txs.NewCallTx(batchCommitter.blockCache, users[0].PublicKey(), &dougAddress, data, 100, 10000, 100) + tx.Sign(testChainID, users[0]) + fmt.Println("subscribing to", evm_events.EventStringAccCall(snativeAddress)) + ev, exception := execTxWaitEvent(t, batchCommitter, tx, evm_events.EventStringAccCall(snativeAddress)) + if exception == 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 + if err := f(ret); err != nil { + t.Fatal(err) + } + } else { + if exception == "" { + t.Fatal("Expected exception") + } + } +} + +func testSNativeTxExpectFail(t *testing.T, batchCommitter *executor, snativeArgs permission.PermArgs) { + testSNativeTx(t, false, batchCommitter, 0, snativeArgs) +} + +func testSNativeTxExpectPass(t *testing.T, batchCommitter *executor, perm ptypes.PermFlag, snativeArgs permission.PermArgs) { + testSNativeTx(t, true, batchCommitter, perm, snativeArgs) +} + +func testSNativeTx(t *testing.T, expectPass bool, batchCommitter *executor, perm ptypes.PermFlag, snativeArgs permission.PermArgs) { + if expectPass { + acc := getAccount(batchCommitter.blockCache, users[0].Address()) + acc.MutablePermissions().Base.Set(perm, true) + batchCommitter.blockCache.UpdateAccount(acc) + } + tx, _ := txs.NewPermissionsTx(batchCommitter.blockCache, users[0].PublicKey(), &snativeArgs) + tx.Sign(testChainID, users[0]) + err := batchCommitter.Execute(tx) + if expectPass { + if err != nil { + t.Fatal("Unexpected exception", err) + } + } else { + if err == nil { + t.Fatal("Expected exception") + } + } +} + +func boolToWord256(v bool) Word256 { + var vint byte + if v { + vint = 0x1 + } else { + vint = 0x0 + } + return LeftPadWord256([]byte{vint}) +} + +func permNameToFuncID(name string) []byte { + function, err := permissionsContract.FunctionByName(name) + if err != nil { + panic("didn't find snative function signature!") + } + id := function.ID() + return id[:] +} + +func snativePermTestInputCALL(name string, user acm.PrivateAccount, perm ptypes.PermFlag, + val bool) (addr acm.Address, pF ptypes.PermFlag, data []byte) { + addr = permissionsContract.Address() + switch name { + case "hasBase", "unsetBase": + data = user.Address().Word256().Bytes() + data = append(data, Uint64ToWord256(uint64(perm)).Bytes()...) + case "setBase": + data = user.Address().Word256().Bytes() + data = append(data, Uint64ToWord256(uint64(perm)).Bytes()...) + data = append(data, boolToWord256(val).Bytes()...) + case "setGlobal": + data = Uint64ToWord256(uint64(perm)).Bytes() + data = append(data, boolToWord256(val).Bytes()...) + } + data = append(permNameToFuncID(name), data...) + var err error + if pF, err = permission.PermStringToFlag(name); err != nil { + panic(fmt.Sprintf("failed to convert perm string (%s) to flag", name)) + } + return +} + +func snativePermTestInputTx(name string, user acm.PrivateAccount, perm ptypes.PermFlag, val bool) (snativeArgs permission.PermArgs) { + switch name { + case "hasBase": + snativeArgs = *permission.HasBaseArgs(user.Address(), perm) + case "unsetBase": + snativeArgs = *permission.UnsetBaseArgs(user.Address(), perm) + case "setBase": + snativeArgs = *permission.SetBaseArgs(user.Address(), perm, val) + case "setGlobal": + snativeArgs = *permission.SetGlobalArgs(perm, val) + } + return +} + +func snativeRoleTestInputCALL(name string, user acm.PrivateAccount, + role string) (addr acm.Address, pF ptypes.PermFlag, data []byte) { + addr = permissionsContract.Address() + data = user.Address().Word256().Bytes() + data = append(data, RightPadBytes([]byte(role), 32)...) + data = append(permNameToFuncID(name), data...) + + var err error + if pF, err = permission.PermStringToFlag(name); err != nil { + panic(fmt.Sprintf("failed to convert perm string (%s) to flag", name)) + } + return +} + +func snativeRoleTestInputTx(name string, user acm.PrivateAccount, role string) (snativeArgs permission.PermArgs) { + switch name { + case "hasRole": + snativeArgs = *permission.HasRoleArgs(user.Address(), role) + case "addRole": + snativeArgs = *permission.AddRoleArgs(user.Address(), role) + case "removeRole": + snativeArgs = *permission.RemoveRoleArgs(user.Address(), role) + } + return +} + +// convenience function for contract that calls a given address +func callContractCode(contractAddr acm.Address) []byte { + // calldatacopy into mem and use as input to call + memOff, inputOff := byte(0x0), byte(0x0) + value := byte(0x1) + inOff := byte(0x0) + retOff, retSize := byte(0x0), byte(0x20) + + // this is the code we want to run (call a contract and return) + return bc.Splice(CALLDATASIZE, PUSH1, inputOff, PUSH1, memOff, + CALLDATACOPY, PUSH1, retSize, PUSH1, retOff, CALLDATASIZE, PUSH1, inOff, + PUSH1, value, PUSH20, contractAddr, + // Zeno loves us - call with half of the available gas each time we CALL + PUSH1, 2, GAS, DIV, CALL, + PUSH1, 32, PUSH1, 0, RETURN) +} + +// convenience function for contract that is a factory for the code that comes as call data +func createContractCode() []byte { + // TODO: gas ... + + // calldatacopy the calldatasize + memOff, inputOff := byte(0x0), byte(0x0) + contractCode := []byte{0x60, memOff, 0x60, inputOff, 0x36, 0x37} + + // create + value := byte(0x1) + contractCode = append(contractCode, []byte{0x60, value, 0x36, 0x60, memOff, 0xf0}...) + return contractCode +} + +// wrap a contract in create code +func wrapContractForCreate(contractCode []byte) []byte { + // the is the code we need to return the contractCode when the contract is initialized + lenCode := len(contractCode) + // push code to the stack + code := append([]byte{0x7f}, RightPadWord256(contractCode).Bytes()...) + // store it in memory + code = append(code, []byte{0x60, 0x0, 0x52}...) + // return whats in memory + code = append(code, []byte{0x60, byte(lenCode), 0x60, 0x0, 0xf3}...) + // return init code, contract code, expected return + return code +} diff --git a/manager/burrow-mint/namereg.go b/execution/namereg.go similarity index 63% rename from manager/burrow-mint/namereg.go rename to execution/namereg.go index 3deedd44..5e71f0fc 100644 --- a/manager/burrow-mint/namereg.go +++ b/execution/namereg.go @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -package burrowmint +package execution import ( "bytes" @@ -20,82 +20,108 @@ import ( "fmt" "sync" - sm "github.com/hyperledger/burrow/manager/burrow-mint/state" - - core_types "github.com/hyperledger/burrow/core/types" - event "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/blockchain" + "github.com/hyperledger/burrow/event" ) // NameReg is part of the pipe for BurrowMint and provides the implementation // for the pipe to call into the BurrowMint application +type NameRegGetter interface { + GetNameRegEntry(name string) *NameRegEntry +} + +type NameRegIterable interface { + NameRegGetter + IterateNameRegEntries(consumer func(*NameRegEntry) (stop bool)) (stopped bool) +} + type namereg struct { - burrowMint *BurrowMint + state *State + blockchain blockchain.Blockchain filterFactory *event.FilterFactory } -func newNameReg(burrowMint *BurrowMint) *namereg { +var _ NameRegIterable = &namereg{} + +type NameRegEntry struct { + Name string `json:"name"` // registered name for the entry + Owner account.Address `json:"owner"` // address that created the entry + Data string `json:"data"` // data to store under this name + Expires uint64 `json:"expires"` // block at which this entry expires +} - ff := event.NewFilterFactory() +func NewNameReg(state *State, blockchain blockchain.Blockchain) *namereg { + filterFactory := event.NewFilterFactory() - ff.RegisterFilterPool("name", &sync.Pool{ + filterFactory.RegisterFilterPool("name", &sync.Pool{ New: func() interface{} { return &NameRegNameFilter{} }, }) - ff.RegisterFilterPool("owner", &sync.Pool{ + filterFactory.RegisterFilterPool("owner", &sync.Pool{ New: func() interface{} { return &NameRegOwnerFilter{} }, }) - ff.RegisterFilterPool("data", &sync.Pool{ + filterFactory.RegisterFilterPool("data", &sync.Pool{ New: func() interface{} { return &NameRegDataFilter{} }, }) - ff.RegisterFilterPool("expires", &sync.Pool{ + filterFactory.RegisterFilterPool("expires", &sync.Pool{ New: func() interface{} { return &NameRegExpiresFilter{} }, }) - return &namereg{burrowMint, ff} + return &namereg{ + state: state, + blockchain: blockchain, + filterFactory: filterFactory, + } +} + +func (nr *namereg) GetNameRegEntry(name string) *NameRegEntry { + return nr.state.GetNameRegEntry(name) +} + +func (nr *namereg) IterateNameRegEntries(consumer func(*NameRegEntry) (stop bool)) bool { + return nr.state.IterateNameRegEntries(consumer) } -func (this *namereg) Entry(key string) (*core_types.NameRegEntry, error) { - st := this.burrowMint.GetState() // performs a copy - entry := st.GetNameRegEntry(key) +func (nr *namereg) Entry(key string) (*NameRegEntry, error) { + entry := nr.state.GetNameRegEntry(key) if entry == nil { - return nil, fmt.Errorf("Entry %s not found", key) + return nil, fmt.Errorf("entry %s not found", key) } return entry, nil } -func (this *namereg) Entries(filters []*event.FilterData) (*core_types.ResultListNames, error) { - var blockHeight int - var names []*core_types.NameRegEntry - state := this.burrowMint.GetState() - blockHeight = state.LastBlockHeight - filter, err := this.filterFactory.NewFilter(filters) +func (nr *namereg) Entries(filters []*event.FilterData) (*ResultListNames, error) { + var names []*NameRegEntry + blockHeight := nr.blockchain.Tip().LastBlockHeight() + filter, err := nr.filterFactory.NewFilter(filters) if err != nil { return nil, fmt.Errorf("Error in query: " + err.Error()) } - state.GetNames().Iterate(func(key, value []byte) bool { - nre := sm.DecodeNameRegEntry(value) + nr.state.GetNames().Iterate(func(key, value []byte) bool { + nre := DecodeNameRegEntry(value) if filter.Match(nre) { names = append(names, nre) } return false }) - return &core_types.ResultListNames{blockHeight, names}, nil + return &ResultListNames{blockHeight, names}, nil } type ResultListNames struct { - BlockHeight int `json:"block_height"` - Names []*core_types.NameRegEntry `json:"names"` + BlockHeight uint64 `json:"block_height"` + Names []*NameRegEntry `json:"names"` } // Filter for namereg name. This should not be used to get individual entries by name. @@ -127,7 +153,7 @@ func (this *NameRegNameFilter) Configure(fd *event.FilterData) error { } func (this *NameRegNameFilter) Match(v interface{}) bool { - nre, ok := v.(*core_types.NameRegEntry) + nre, ok := v.(*NameRegEntry) if !ok { return false } @@ -147,7 +173,7 @@ func (this *NameRegOwnerFilter) Configure(fd *event.FilterData) error { val, err := hex.DecodeString(fd.Value) if err != nil { - return fmt.Errorf("Wrong value type.") + return fmt.Errorf("wrong value type.") } if op == "==" { this.match = func(a, b []byte) bool { @@ -166,11 +192,11 @@ func (this *NameRegOwnerFilter) Configure(fd *event.FilterData) error { } func (this *NameRegOwnerFilter) Match(v interface{}) bool { - nre, ok := v.(*core_types.NameRegEntry) + nre, ok := v.(*NameRegEntry) if !ok { return false } - return this.match(nre.Owner, this.value) + return this.match(nre.Owner.Bytes(), this.value) } // Filter for namereg data. Useful for example if you store an ipfs hash and know the hash but need the key. @@ -202,7 +228,7 @@ func (this *NameRegDataFilter) Configure(fd *event.FilterData) error { } func (this *NameRegDataFilter) Match(v interface{}) bool { - nre, ok := v.(*core_types.NameRegEntry) + nre, ok := v.(*NameRegEntry) if !ok { return false } @@ -213,8 +239,8 @@ func (this *NameRegDataFilter) Match(v interface{}) bool { // Ops: All type NameRegExpiresFilter struct { op string - value int64 - match func(int64, int64) bool + value uint64 + match func(uint64, uint64) bool } func (this *NameRegExpiresFilter) Configure(fd *event.FilterData) error { @@ -233,9 +259,9 @@ func (this *NameRegExpiresFilter) Configure(fd *event.FilterData) error { } func (this *NameRegExpiresFilter) Match(v interface{}) bool { - nre, ok := v.(*core_types.NameRegEntry) + nre, ok := v.(*NameRegEntry) if !ok { return false } - return this.match(int64(nre.Expires), this.value) + return this.match(nre.Expires, this.value) } diff --git a/manager/burrow-mint/state/state.go b/execution/state.go similarity index 54% rename from manager/burrow-mint/state/state.go rename to execution/state.go index ccf68bf3..74a1b1eb 100644 --- a/manager/burrow-mint/state/state.go +++ b/execution/state.go @@ -12,28 +12,26 @@ // See the License for the specific language governing permissions and // limitations under the License. -package state +package execution import ( "bytes" "fmt" "io" - "io/ioutil" + "sync" "time" acm "github.com/hyperledger/burrow/account" - genesis "github.com/hyperledger/burrow/genesis" - ptypes "github.com/hyperledger/burrow/permission/types" + "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/genesis" + "github.com/hyperledger/burrow/permission" + ptypes "github.com/hyperledger/burrow/permission" "github.com/hyperledger/burrow/txs" - - dbm "github.com/tendermint/go-db" - "github.com/tendermint/go-events" - "github.com/tendermint/go-merkle" - "github.com/tendermint/go-wire" - - core_types "github.com/hyperledger/burrow/core/types" "github.com/hyperledger/burrow/util" - "github.com/tendermint/tendermint/types" + "github.com/tendermint/go-wire" + "github.com/tendermint/merkleeyes/iavl" + dbm "github.com/tendermint/tmlibs/db" + "github.com/tendermint/tmlibs/merkle" ) var ( @@ -45,189 +43,240 @@ var ( maxLoadStateElementSize = 0 // no max ) +// TODO +const GasLimit = uint64(1000000) + //----------------------------------------------------------------------------- // NOTE: not goroutine-safe. type State struct { - DB dbm.DB - ChainID string - LastBlockHeight int - LastBlockHash []byte - LastBlockParts types.PartSetHeader - LastBlockTime time.Time + sync.RWMutex + db dbm.DB // BondedValidators *types.ValidatorSet // LastBondedValidators *types.ValidatorSet // UnbondingValidators *types.ValidatorSet accounts merkle.Tree // Shouldn't be accessed directly. validatorInfos merkle.Tree // Shouldn't be accessed directly. nameReg merkle.Tree // Shouldn't be accessed directly. +} + +// Implements account and blockchain state +var _ acm.Updater = &State{} + +var _ acm.StateIterable = &State{} + +var _ acm.StateWriter = &State{} + +func MakeGenesisState(db dbm.DB, genDoc *genesis.GenesisDoc) *State { + if len(genDoc.Validators) == 0 { + util.Fatalf("The genesis file has no validators") + } + + if genDoc.GenesisTime.IsZero() { + // NOTE: [ben] change GenesisTime to requirement on v0.17 + // GenesisTime needs to be deterministic across the chain + // and should be required in the genesis file; + // the requirement is not yet enforced when lacking set + // time to 11/18/2016 @ 4:09am (UTC) + genDoc.GenesisTime = time.Unix(1479442162, 0) + } + + // Make accounts state tree + accounts := iavl.NewIAVLTree(defaultAccountsCacheCapacity, db) + for _, genAcc := range genDoc.Accounts { + perm := genAcc.Permissions + acc := &acm.ConcreteAccount{ + Address: genAcc.Address, + Balance: genAcc.Amount, + Permissions: perm, + } + accounts.Set(acc.Address.Bytes(), acc.Encode()) + } + + // global permissions are saved as the 0 address + // so they are included in the accounts tree + globalPerms := ptypes.DefaultAccountPermissions + globalPerms = genDoc.GlobalPermissions + // XXX: make sure the set bits are all true + // Without it the HasPermission() functions will fail + globalPerms.Base.SetBit = ptypes.AllPermFlags + + permsAcc := &acm.ConcreteAccount{ + Address: permission.GlobalPermissionsAddress, + Balance: 1337, + Permissions: globalPerms, + } + accounts.Set(permsAcc.Address.Bytes(), permsAcc.Encode()) + + // Make validatorInfos state tree && validators slice + /* + validatorInfos := merkle.NewIAVLTree(wire.BasicCodec, types.ValidatorInfoCodec, 0, db) + validators := make([]*types.Validator, len(genDoc.Validators)) + for i, val := range genDoc.Validators { + pubKey := val.PublicKey + address := pubKey.Address() - evc events.Fireable // typically an events.EventCache + // Make ValidatorInfo + valInfo := &types.ValidatorInfo{ + Address: address, + PublicKey: pubKey, + UnbondTo: make([]*types.TxOutput, len(val.UnbondTo)), + FirstBondHeight: 0, + FirstBondAmount: val.Amount, + } + for i, unbondTo := range val.UnbondTo { + valInfo.UnbondTo[i] = &types.TxOutput{ + Address: unbondTo.Address, + Amount: unbondTo.Amount, + } + } + validatorInfos.Set(address, valInfo) + + // Make validator + validators[i] = &types.Validator{ + Address: address, + PublicKey: pubKey, + VotingPower: val.Amount, + } + } + */ + + // Make namereg tree + nameReg := iavl.NewIAVLTree(0, db) + // TODO: add names, contracts to genesis.json + + // IAVLTrees must be persisted before copy operations. + accounts.Save() + //validatorInfos.Save() + nameReg.Save() + + return &State{ + db: db, + //BondedValidators: types.NewValidatorSet(validators), + //LastBondedValidators: types.NewValidatorSet(nil), + //UnbondingValidators: types.NewValidatorSet(nil), + accounts: accounts, + //validatorInfos: validatorInfos, + nameReg: nameReg, + } } -func LoadState(db dbm.DB) *State { - s := &State{DB: db} +func LoadState(db dbm.DB) (*State, error) { + s := &State{db: db} buf := db.Get(stateKey) if len(buf) == 0 { - return nil + return nil, nil } else { r, n, err := bytes.NewReader(buf), new(int), new(error) - s.ChainID = wire.ReadString(r, maxLoadStateElementSize, n, err) - s.LastBlockHeight = wire.ReadVarint(r, n, err) - s.LastBlockHash = wire.ReadByteSlice(r, maxLoadStateElementSize, n, err) - s.LastBlockParts = wire.ReadBinary(types.PartSetHeader{}, r, maxLoadStateElementSize, n, err).(types.PartSetHeader) - s.LastBlockTime = wire.ReadTime(r, n, err) - // s.BondedValidators = wire.ReadBinary(&types.ValidatorSet{}, r, maxLoadStateElementSize, n, err).(*types.ValidatorSet) - // s.LastBondedValidators = wire.ReadBinary(&types.ValidatorSet{}, r, maxLoadStateElementSize, n, err).(*types.ValidatorSet) - // s.UnbondingValidators = wire.ReadBinary(&types.ValidatorSet{}, r, maxLoadStateElementSize, n, err).(*types.ValidatorSet) - accountsHash := wire.ReadByteSlice(r, maxLoadStateElementSize, n, err) - s.accounts = merkle.NewIAVLTree(defaultAccountsCacheCapacity, db) - s.accounts.Load(accountsHash) - //validatorInfosHash := wire.ReadByteSlice(r, maxLoadStateElementSize, n, err) - //s.validatorInfos = merkle.NewIAVLTree(wire.BasicCodec, types.ValidatorInfoCodec, 0, db) - //s.validatorInfos.Load(validatorInfosHash) - nameRegHash := wire.ReadByteSlice(r, maxLoadStateElementSize, n, err) - s.nameReg = merkle.NewIAVLTree(0, db) - s.nameReg.Load(nameRegHash) + wire.ReadBinaryPtr(&s, r, 0, n, err) if *err != nil { // DATA HAS BEEN CORRUPTED OR THE SPEC HAS CHANGED - util.Fatalf("Data has been corrupted or its spec has changed: %v\n", *err) + return nil, fmt.Errorf("data has been corrupted or its spec has changed: %v", *err) } - // TODO: ensure that buf is completely read. } - return s + return s, nil } func (s *State) Save() { + s.Lock() + defer s.Unlock() s.accounts.Save() //s.validatorInfos.Save() s.nameReg.Save() - buf, n, err := new(bytes.Buffer), new(int), new(error) - wire.WriteString(s.ChainID, buf, n, err) - wire.WriteVarint(s.LastBlockHeight, buf, n, err) - wire.WriteByteSlice(s.LastBlockHash, buf, n, err) - wire.WriteBinary(s.LastBlockParts, buf, n, err) - wire.WriteTime(s.LastBlockTime, buf, n, err) - // wire.WriteBinary(s.BondedValidators, buf, n, err) - // wire.WriteBinary(s.LastBondedValidators, buf, n, err) - // wire.WriteBinary(s.UnbondingValidators, buf, n, err) - wire.WriteByteSlice(s.accounts.Hash(), buf, n, err) - //wire.WriteByteSlice(s.validatorInfos.Hash(), buf, n, err) - wire.WriteByteSlice(s.nameReg.Hash(), buf, n, err) - if *err != nil { - // TODO: [Silas] Do something better than this, really serialising ought to - // be error-free - util.Fatalf("Could not serialise state in order to save the state, "+ - "cannot continue, error: %s", *err) - } - s.DB.Set(stateKey, buf.Bytes()) + s.db.SetSync(stateKey, wire.BinaryBytes(s)) } // CONTRACT: // Copy() is a cheap way to take a snapshot, // as if State were copied by value. +// TODO [Silas]: Kill this with fire it is totally broken - there is no safe way to copy IAVLTree while sharing database func (s *State) Copy() *State { return &State{ - DB: s.DB, - ChainID: s.ChainID, - LastBlockHeight: s.LastBlockHeight, - LastBlockHash: s.LastBlockHash, - LastBlockParts: s.LastBlockParts, - LastBlockTime: s.LastBlockTime, + db: s.db, // BondedValidators: s.BondedValidators.Copy(), // TODO remove need for Copy() here. // LastBondedValidators: s.LastBondedValidators.Copy(), // That is, make updates to the validator set // UnbondingValidators: s.UnbondingValidators.Copy(), // copy the valSet lazily. accounts: s.accounts.Copy(), //validatorInfos: s.validatorInfos.Copy(), nameReg: s.nameReg.Copy(), - evc: nil, } } +//func (s *State) Copy() *State { +// stateCopy := &State{ +// db: dbm.NewMemDB(), +// chainID: s.chainID, +// lastBlockHeight: s.lastBlockHeight, +// lastBlockAppHash: s.lastBlockAppHash, +// lastBlockTime: s.lastBlockTime, +// // BondedValidators: s.BondedValidators.Copy(), // TODO remove need for Copy() here. +// // LastBondedValidators: s.LastBondedValidators.Copy(), // That is, make updates to the validator set +// // UnbondingValidators: s.UnbondingValidators.Copy(), // copy the valSet lazily. +// accounts: copyTree(s.accounts), +// //validatorInfos: s.validatorInfos.Copy(), +// nameReg: copyTree(s.nameReg), +// evc: nil, +// } +// stateCopy.Save() +// return stateCopy +//} + // Returns a hash that represents the state data, excluding Last* func (s *State) Hash() []byte { + s.RLock() + defer s.RUnlock() return merkle.SimpleHashFromMap(map[string]interface{}{ //"BondedValidators": s.BondedValidators, //"UnbondingValidators": s.UnbondingValidators, - "Accounts": s.accounts, + "Accounts": s.accounts.Hash(), //"ValidatorInfos": s.validatorInfos, "NameRegistry": s.nameReg, }) } -/* //XXX Done by tendermint core -// Mutates the block in place and updates it with new state hash. -func (s *State) ComputeBlockStateHash(block *types.Block) error { - sCopy := s.Copy() - // sCopy has no event cache in it, so this won't fire events - err := execBlock(sCopy, block, types.PartSetHeader{}) - if err != nil { - return err - } - // Set block.StateHash - block.StateHash = sCopy.Hash() - return nil -} -*/ - -func (s *State) GetGenesisDoc() (*genesis.GenesisDoc, error) { - var genesisDoc *genesis.GenesisDoc - loadedGenesisDocBytes := s.DB.Get(genesis.GenDocKey) - err := new(error) - wire.ReadJSONPtr(&genesisDoc, loadedGenesisDocBytes, err) - if *err != nil { - return nil, fmt.Errorf("Unable to read genesisDoc from db on Get: %v", err) - } - return genesisDoc, nil -} - -func (s *State) SetDB(db dbm.DB) { - s.DB = db -} - -//------------------------------------- -// State.params - -func (s *State) GetGasLimit() int64 { - return 1000000 // TODO -} - -// State.params -//------------------------------------- -// State.accounts - // Returns nil if account does not exist with given address. -// Implements Statelike -func (s *State) GetAccount(address []byte) *acm.Account { - _, accBytes, _ := s.accounts.Get(address) +func (s *State) GetAccount(address acm.Address) (acm.Account, error) { + s.RLock() + defer s.RUnlock() + _, accBytes, _ := s.accounts.Get(address.Bytes()) if accBytes == nil { - return nil + return nil, nil } - return acm.DecodeAccount(accBytes) + return acm.Decode(accBytes) } -// The account is copied before setting, so mutating it -// afterwards has no side effects. -// Implements Statelike -func (s *State) UpdateAccount(account *acm.Account) bool { - return s.accounts.Set(account.Address, acm.EncodeAccount(account)) +func (s *State) UpdateAccount(account acm.Account) error { + s.Lock() + defer s.Unlock() + s.accounts.Set(account.Address().Bytes(), account.Encode()) + return nil } -// Implements Statelike -func (s *State) RemoveAccount(address []byte) bool { - _, removed := s.accounts.Remove(address) - return removed +func (s *State) RemoveAccount(address acm.Address) error { + s.Lock() + defer s.Unlock() + s.accounts.Remove(address.Bytes()) + return nil } -// The returned Account is a copy, so mutating it -// has no side effects. +// This does not give a true independent copy since the underlying database is shared and any save calls all copies +// to become invalid and using them may cause panics func (s *State) GetAccounts() merkle.Tree { return s.accounts.Copy() } -// Set the accounts tree -func (s *State) SetAccounts(accounts merkle.Tree) { - s.accounts = accounts +func (s *State) IterateAccounts(consumer func(acm.Account) (stop bool)) (stopped bool, err error) { + s.RLock() + defer s.RUnlock() + stopped = s.accounts.Iterate(func(key, value []byte) bool { + var account acm.Account + account, err = acm.Decode(value) + if err != nil { + return true + } + return consumer(account) + }) + return } // State.accounts @@ -265,7 +314,7 @@ func (s *State) unbondValidator(val *types.Validator) { if !removed { PanicCrisis("Couldn't remove validator for unbonding") } - val.UnbondHeight = s.LastBlockHeight + 1 + val.UnbondHeight = s.lastBlockHeight + 1 added := s.UnbondingValidators.Add(val) if !added { PanicCrisis("Couldn't add validator for unbonding") @@ -278,7 +327,7 @@ func (s *State) rebondValidator(val *types.Validator) { if !removed { PanicCrisis("Couldn't remove validator for rebonding") } - val.BondHeight = s.LastBlockHeight + 1 + val.BondHeight = s.lastBlockHeight + 1 added := s.BondedValidators.Add(val) if !added { PanicCrisis("Couldn't add validator for rebonding") @@ -291,7 +340,7 @@ func (s *State) releaseValidator(val *types.Validator) { if valInfo == nil { PanicSanity("Couldn't find validatorInfo for release") } - valInfo.ReleasedHeight = s.LastBlockHeight + 1 + valInfo.ReleasedHeight = s.lastBlockHeight + 1 s.SetValidatorInfo(valInfo) // Send coins back to UnbondTo outputs @@ -317,7 +366,7 @@ func (s *State) destroyValidator(val *types.Validator) { if valInfo == nil { PanicSanity("Couldn't find validatorInfo for release") } - valInfo.DestroyedHeight = s.LastBlockHeight + 1 + valInfo.DestroyedHeight = s.lastBlockHeight + 1 valInfo.DestroyedAmount = val.VotingPower s.SetValidatorInfo(valInfo) @@ -343,17 +392,81 @@ func (s *State) SetValidatorInfos(validatorInfos merkle.Tree) { //------------------------------------- // State.storage -func (s *State) LoadStorage(hash []byte) (storage merkle.Tree) { - storage = merkle.NewIAVLTree(1024, s.DB) +func (s *State) accountStorage(address acm.Address) (merkle.Tree, error) { + account, err := s.GetAccount(address) + if err != nil { + return nil, err + } + if account == nil { + return nil, fmt.Errorf("could not find account %s to access its storage", address) + } + return s.LoadStorage(account.StorageRoot()), nil +} + +func (s *State) LoadStorage(hash []byte) merkle.Tree { + s.RLock() + defer s.RUnlock() + storage := iavl.NewIAVLTree(1024, s.db) storage.Load(hash) return storage } +func (s *State) GetStorage(address acm.Address, key binary.Word256) (binary.Word256, error) { + s.RLock() + defer s.RUnlock() + storageTree, err := s.accountStorage(address) + if err != nil { + return binary.Zero256, err + } + _, value, _ := storageTree.Get(key.Bytes()) + return binary.LeftPadWord256(value), nil +} + +func (s *State) SetStorage(address acm.Address, key, value binary.Word256) error { + s.Lock() + defer s.Unlock() + storageTree, err := s.accountStorage(address) + if err != nil { + return err + } + if storageTree != nil { + storageTree.Set(key.Bytes(), value.Bytes()) + } + return nil +} + +func (s *State) IterateStorage(address acm.Address, + consumer func(key, value binary.Word256) (stop bool)) (stopped bool, err error) { + + var storageTree merkle.Tree + storageTree, err = s.accountStorage(address) + if err != nil { + return + } + stopped = storageTree.Iterate(func(key []byte, value []byte) (stop bool) { + // Note: no left padding should occur unless there is a bug and non-words have been writte to this storage tree + if len(key) != binary.Word256Length { + err = fmt.Errorf("key '%X' stored for account %s is not a %v-byte word", + key, address, binary.Word256Length) + return true + } + if len(value) != binary.Word256Length { + err = fmt.Errorf("value '%X' stored for account %s is not a %v-byte word", + key, address, binary.Word256Length) + return true + } + return consumer(binary.LeftPadWord256(key), binary.LeftPadWord256(value)) + }) + return +} + // State.storage //------------------------------------- // State.nameReg -func (s *State) GetNameRegEntry(name string) *core_types.NameRegEntry { +var _ NameRegIterable = &State{} + +func (s *State) GetNameRegEntry(name string) *NameRegEntry { _, valueBytes, _ := s.nameReg.Get([]byte(name)) if valueBytes == nil { return nil @@ -362,18 +475,24 @@ func (s *State) GetNameRegEntry(name string) *core_types.NameRegEntry { return DecodeNameRegEntry(valueBytes) } -func DecodeNameRegEntry(entryBytes []byte) *core_types.NameRegEntry { +func (s *State) IterateNameRegEntries(consumer func(*NameRegEntry) (stop bool)) (stopped bool) { + return s.nameReg.Iterate(func(key []byte, value []byte) (stop bool) { + return consumer(DecodeNameRegEntry(value)) + }) +} + +func DecodeNameRegEntry(entryBytes []byte) *NameRegEntry { var n int var err error - value := NameRegCodec.Decode(bytes.NewBuffer(entryBytes), &n, &err) - return value.(*core_types.NameRegEntry) + value := NameRegDecode(bytes.NewBuffer(entryBytes), &n, &err) + return value.(*NameRegEntry) } -func (s *State) UpdateNameRegEntry(entry *core_types.NameRegEntry) bool { +func (s *State) UpdateNameRegEntry(entry *NameRegEntry) bool { w := new(bytes.Buffer) var n int var err error - NameRegCodec.Encode(entry, w, &n, &err) + NameRegEncode(entry, w, &n, &err) return s.nameReg.Set([]byte(entry.Name), w.Bytes()) } @@ -390,144 +509,10 @@ func (s *State) GetNames() merkle.Tree { func (s *State) SetNameReg(nameReg merkle.Tree) { s.nameReg = nameReg } - -func NameRegEncoder(o interface{}, w io.Writer, n *int, err *error) { - wire.WriteBinary(o.(*core_types.NameRegEntry), w, n, err) -} - -func NameRegDecoder(r io.Reader, n *int, err *error) interface{} { - return wire.ReadBinary(&core_types.NameRegEntry{}, r, txs.MaxDataLength, n, err) +func NameRegEncode(o interface{}, w io.Writer, n *int, err *error) { + wire.WriteBinary(o.(*NameRegEntry), w, n, err) } -var NameRegCodec = wire.Codec{ - Encode: NameRegEncoder, - Decode: NameRegDecoder, -} - -// State.nameReg -//------------------------------------- - -// Implements events.Eventable. Typically uses events.EventCache -func (s *State) SetFireable(evc events.Fireable) { - s.evc = evc -} - -//----------------------------------------------------------------------------- -// Genesis - -func MakeGenesisStateFromFile(db dbm.DB, genDocFile string) (*genesis.GenesisDoc, *State) { - jsonBlob, err := ioutil.ReadFile(genDocFile) - if err != nil { - util.Fatalf("Couldn't read GenesisDoc file: %v", err) - } - genDoc := genesis.GenesisDocFromJSON(jsonBlob) - return genDoc, MakeGenesisState(db, genDoc) -} - -func MakeGenesisState(db dbm.DB, genDoc *genesis.GenesisDoc) *State { - if len(genDoc.Validators) == 0 { - util.Fatalf("The genesis file has no validators") - } - - if genDoc.GenesisTime.IsZero() { - // NOTE: [ben] change GenesisTime to requirement on v0.17 - // GenesisTime needs to be deterministic across the chain - // and should be required in the genesis file; - // the requirement is not yet enforced when lacking set - // time to 11/18/2016 @ 4:09am (UTC) - genDoc.GenesisTime = time.Unix(1479442162, 0) - } - - // Make accounts state tree - accounts := merkle.NewIAVLTree(defaultAccountsCacheCapacity, db) - for _, genAcc := range genDoc.Accounts { - perm := ptypes.ZeroAccountPermissions - if genAcc.Permissions != nil { - perm = *genAcc.Permissions - } - acc := &acm.Account{ - Address: genAcc.Address, - PubKey: nil, - Sequence: 0, - Balance: genAcc.Amount, - Permissions: perm, - } - accounts.Set(acc.Address, acm.EncodeAccount(acc)) - } - - // global permissions are saved as the 0 address - // so they are included in the accounts tree - globalPerms := ptypes.DefaultAccountPermissions - if genDoc.Params != nil && genDoc.Params.GlobalPermissions != nil { - globalPerms = *genDoc.Params.GlobalPermissions - // XXX: make sure the set bits are all true - // Without it the HasPermission() functions will fail - globalPerms.Base.SetBit = ptypes.AllPermFlags - } - - permsAcc := &acm.Account{ - Address: ptypes.GlobalPermissionsAddress, - PubKey: nil, - Sequence: 0, - Balance: 1337, - Permissions: globalPerms, - } - accounts.Set(permsAcc.Address, acm.EncodeAccount(permsAcc)) - - // Make validatorInfos state tree && validators slice - /* - validatorInfos := merkle.NewIAVLTree(wire.BasicCodec, types.ValidatorInfoCodec, 0, db) - validators := make([]*types.Validator, len(genDoc.Validators)) - for i, val := range genDoc.Validators { - pubKey := val.PubKey - address := pubKey.Address() - - // Make ValidatorInfo - valInfo := &types.ValidatorInfo{ - Address: address, - PubKey: pubKey, - UnbondTo: make([]*types.TxOutput, len(val.UnbondTo)), - FirstBondHeight: 0, - FirstBondAmount: val.Amount, - } - for i, unbondTo := range val.UnbondTo { - valInfo.UnbondTo[i] = &types.TxOutput{ - Address: unbondTo.Address, - Amount: unbondTo.Amount, - } - } - validatorInfos.Set(address, valInfo) - - // Make validator - validators[i] = &types.Validator{ - Address: address, - PubKey: pubKey, - VotingPower: val.Amount, - } - } - */ - - // Make namereg tree - nameReg := merkle.NewIAVLTree(0, db) - // TODO: add names, contracts to genesis.json - - // IAVLTrees must be persisted before copy operations. - accounts.Save() - //validatorInfos.Save() - nameReg.Save() - - return &State{ - DB: db, - ChainID: genDoc.ChainID, - LastBlockHeight: 0, - LastBlockHash: nil, - LastBlockParts: types.PartSetHeader{}, - LastBlockTime: genDoc.GenesisTime, - //BondedValidators: types.NewValidatorSet(validators), - //LastBondedValidators: types.NewValidatorSet(nil), - //UnbondingValidators: types.NewValidatorSet(nil), - accounts: accounts, - //validatorInfos: validatorInfos, - nameReg: nameReg, - } +func NameRegDecode(r io.Reader, n *int, err *error) interface{} { + return wire.ReadBinary(&NameRegEntry{}, r, txs.MaxDataLength, n, err) } diff --git a/manager/burrow-mint/state/state_test.go b/execution/state_test.go similarity index 51% rename from manager/burrow-mint/state/state_test.go rename to execution/state_test.go index f9f91d9b..909b77d0 100644 --- a/manager/burrow-mint/state/state_test.go +++ b/execution/state_test.go @@ -12,48 +12,88 @@ // See the License for the specific language governing permissions and // limitations under the License. -package state +package execution import ( "bytes" "encoding/hex" "testing" - core_types "github.com/hyperledger/burrow/core/types" - evm "github.com/hyperledger/burrow/manager/burrow-mint/evm" - "github.com/hyperledger/burrow/txs" - "github.com/hyperledger/burrow/word256" + "fmt" + + "github.com/hyperledger/burrow/execution/evm/sha3" - "github.com/tendermint/tendermint/config/tendermint_test" + "time" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/binary" + bcm "github.com/hyperledger/burrow/blockchain" + "github.com/hyperledger/burrow/event" + "github.com/hyperledger/burrow/genesis" + "github.com/hyperledger/burrow/txs" + "github.com/stretchr/testify/assert" + dbm "github.com/tendermint/tmlibs/db" ) -func init() { - tendermint_test.ResetConfig("state_test") - evm.SetDebug(true) -} +var deterministicGenesis = genesis.NewDeterministicGenesis(34059836243380576) +var testGenesisDoc, testPrivAccounts = deterministicGenesis. + GenesisDoc(3, true, 1000, 1, true, 1000) +var testChainID = testGenesisDoc.ChainID() -func execTxWithState(state *State, tx txs.Tx, runCall bool) error { - cache := NewBlockCache(state) - if err := ExecTx(cache, tx, runCall, nil, logger); err != nil { +func execTxWithStateAndBlockchain(state *State, tip bcm.Tip, tx txs.Tx) error { + exe := newExecutor(true, state, testChainID, tip, event.NewNoOpFireable(), logger) + if err := exe.Execute(tx); err != nil { return err } else { - cache.Sync() + exe.blockCache.Sync() return nil } } -func execTxWithStateNewBlock(state *State, tx txs.Tx, runCall bool) error { - if err := execTxWithState(state, tx, runCall); err != nil { +func execTxWithState(state *State, tx txs.Tx) error { + return execTxWithStateAndBlockchain(state, bcm.NewBlockchain(testGenesisDoc), tx) +} + +func commitNewBlock(state *State, blockchain bcm.MutableBlockchain) { + blockchain.CommitBlock(blockchain.LastBlockTime().Add(time.Second), sha3.Sha3(blockchain.LastBlockHash()), + state.Hash()) +} + +func execTxWithStateNewBlock(state *State, blockchain bcm.MutableBlockchain, tx txs.Tx) error { + if err := execTxWithStateAndBlockchain(state, blockchain, tx); err != nil { return err } - - state.LastBlockHeight += 1 + commitNewBlock(state, blockchain) return nil } +func makeGenesisState(numAccounts int, randBalance bool, minBalance uint64, numValidators int, randBonded bool, + minBonded int64) (*State, []acm.PrivateAccount) { + testGenesisDoc, privAccounts := deterministicGenesis.GenesisDoc(numAccounts, randBalance, minBalance, + numValidators, randBonded, minBonded) + s0 := MakeGenesisState(dbm.NewMemDB(), testGenesisDoc) + s0.Save() + return s0, privAccounts +} + +func getAccount(state acm.Getter, address acm.Address) acm.MutableAccount { + acc, _ := acm.GetMutableAccount(state, address) + return acc +} + +func addressPtr(account acm.Account) *acm.Address { + if account == nil { + return nil + } + accountAddresss := account.Address() + return &accountAddresss +} + +// Tests + func TestCopyState(t *testing.T) { // Generate a random state - s0, privAccounts, _ := RandGenesisState(10, true, 1000, 5, true, 1000) + s0, privAccounts := makeGenesisState(10, true, 1000, 5, true, 1000) s0Hash := s0.Hash() if len(s0Hash) == 0 { t.Error("Expected state hash") @@ -61,23 +101,22 @@ func TestCopyState(t *testing.T) { // Check hash of copy s0Copy := s0.Copy() - if !bytes.Equal(s0Hash, s0Copy.Hash()) { - t.Error("Expected state copy hash to be the same") - } + assert.Equal(t, s0Hash, s0Copy.Hash(), "Expected state copy hash to be the same") + assert.Equal(t, s0Copy.Copy().Hash(), s0Copy.Hash(), "Expected COPY COPY COPY the same") // Mutate the original; hash should change. - acc0Address := privAccounts[0].PubKey.Address() - acc := s0.GetAccount(acc0Address) - acc.Balance += 1 + acc0Address := privAccounts[0].Address() + acc := getAccount(s0, acc0Address) + acc.AddToBalance(1) // The account balance shouldn't have changed yet. - if s0.GetAccount(acc0Address).Balance == acc.Balance { + if getAccount(s0, acc0Address).Balance() == acc.Balance() { t.Error("Account balance changed unexpectedly") } // Setting, however, should change the balance. s0.UpdateAccount(acc) - if s0.GetAccount(acc0Address).Balance != acc.Balance { + if getAccount(s0, acc0Address).Balance() != acc.Balance() { t.Error("Account balance wasn't set") } @@ -99,11 +138,11 @@ func makeBlock(t *testing.T, state *State, validation *tmtypes.Commit, txs []txs } block := &tmtypes.Block{ Header: &tmtypes.Header{ - ChainID: state.ChainID, - Height: state.LastBlockHeight + 1, - Time: state.LastBlockTime.Add(time.Minute), + testChainID: testChainID, + Height: blockchain.LastBlockHeight() + 1, + Time: state.lastBlockTime.Add(time.Minute), NumTxs: len(txs), - LastBlockHash: state.LastBlockHash, + lastBlockAppHash: state.lastBlockAppHash, LastBlockParts: state.LastBlockParts, AppHash: nil, }, @@ -129,7 +168,7 @@ func makeBlock(t *testing.T, state *State, validation *tmtypes.Commit, txs []txs func TestGenesisSaveLoad(t *testing.T) { // Generate a state, save & load it. - s0, _, _ := RandGenesisState(10, true, 1000, 5, true, 1000) + s0, _, _ := makeGenesisState(10, true, 1000, 5, true, 1000) // Make complete block and blockParts block := makeBlock(t, s0, nil, nil) @@ -145,23 +184,23 @@ func TestGenesisSaveLoad(t *testing.T) { s0.Save() // Sanity check s0 - //s0.DB.(*dbm.MemDB).Print() + //s0.db.(*dbm.MemDB).Print() if s0.BondedValidators.TotalVotingPower() == 0 { t.Error("s0 BondedValidators TotalVotingPower should not be 0") } - if s0.LastBlockHeight != 1 { - t.Error("s0 LastBlockHeight should be 1, got", s0.LastBlockHeight) + if s0.lastBlockHeight != 1 { + t.Error("s0 lastBlockHeight should be 1, got", s0.lastBlockHeight) } // Load s1 - s1 := LoadState(s0.DB) + s1 := LoadState(s0.db) // Compare height & blockHash - if s0.LastBlockHeight != s1.LastBlockHeight { - t.Error("LastBlockHeight mismatch") + if s0.lastBlockHeight != s1.lastBlockHeight { + t.Error("lastBlockHeight mismatch") } - if !bytes.Equal(s0.LastBlockHash, s1.LastBlockHash) { - t.Error("LastBlockHash mismatch") + if !bytes.Equal(s0.lastBlockAppHash, s1.lastBlockAppHash) { + t.Error("lastBlockAppHash mismatch") } // Compare state merkle trees @@ -194,64 +233,68 @@ func TestGenesisSaveLoad(t *testing.T) { func TestTxSequence(t *testing.T) { - state, privAccounts, _ := RandGenesisState(3, true, 1000, 1, true, 1000) - acc0 := state.GetAccount(privAccounts[0].PubKey.Address()) - acc0PubKey := privAccounts[0].PubKey - acc1 := state.GetAccount(privAccounts[1].PubKey.Address()) + state, privAccounts := makeGenesisState(3, true, 1000, 1, true, 1000) + acc0 := getAccount(state, privAccounts[0].Address()) + acc0PubKey := privAccounts[0].PublicKey() + acc1 := getAccount(state, privAccounts[1].Address()) // Test a variety of sequence numbers for the tx. // The tx should only pass when i == 1. - for i := -1; i < 3; i++ { - sequence := acc0.Sequence + i + for i := uint64(0); i < 3; i++ { + sequence := acc0.Sequence() + i tx := txs.NewSendTx() tx.AddInputWithNonce(acc0PubKey, 1, sequence) - tx.AddOutput(acc1.Address, 1) - tx.Inputs[0].Signature = privAccounts[0].Sign(state.ChainID, tx) + tx.AddOutput(acc1.Address(), 1) + tx.Inputs[0].Signature = acm.ChainSign(privAccounts[0], testChainID, tx) stateCopy := state.Copy() - err := execTxWithState(stateCopy, tx, true) + err := execTxWithState(stateCopy, tx) if i == 1 { // Sequence is good. if err != nil { t.Errorf("Expected good sequence to pass: %v", err) } - // Check acc.Sequence. - newAcc0 := stateCopy.GetAccount(acc0.Address) - if newAcc0.Sequence != sequence { + // Check acc.Sequence(). + newAcc0 := getAccount(stateCopy, acc0.Address()) + if newAcc0.Sequence() != sequence { t.Errorf("Expected account sequence to change to %v, got %v", - sequence, newAcc0.Sequence) + sequence, newAcc0.Sequence()) } } else { // Sequence is bad. if err == nil { t.Errorf("Expected bad sequence to fail") } - // Check acc.Sequence. (shouldn't have changed) - newAcc0 := stateCopy.GetAccount(acc0.Address) - if newAcc0.Sequence != acc0.Sequence { + // Check acc.Sequence(). (shouldn't have changed) + newAcc0 := getAccount(stateCopy, acc0.Address()) + if newAcc0.Sequence() != acc0.Sequence() { t.Errorf("Expected account sequence to not change from %v, got %v", - acc0.Sequence, newAcc0.Sequence) + acc0.Sequence(), newAcc0.Sequence()) } } } } func TestNameTxs(t *testing.T) { - state, privAccounts, _ := RandGenesisState(3, true, 1000, 1, true, 1000) + state := MakeGenesisState(dbm.NewMemDB(), testGenesisDoc) + state.Save() txs.MinNameRegistrationPeriod = 5 - startingBlock := state.LastBlockHeight + blockchain := bcm.NewBlockchain(testGenesisDoc) + startingBlock := blockchain.LastBlockHeight() // try some bad names. these should all fail - names := []string{"", "\n", "123#$%", "\x00", string([]byte{20, 40, 60, 80}), "baffledbythespectacleinallofthisyouseeehesaidwithouteyessurprised", "no spaces please"} + names := []string{"", "\n", "123#$%", "\x00", string([]byte{20, 40, 60, 80}), + "baffledbythespectacleinallofthisyouseeehesaidwithouteyessurprised", "no spaces please"} data := "something about all this just doesn't feel right." - fee := int64(1000) - numDesiredBlocks := 5 + fee := uint64(1000) + numDesiredBlocks := uint64(5) for _, name := range names { - amt := fee + int64(numDesiredBlocks)*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) - tx, _ := txs.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) - tx.Sign(state.ChainID, privAccounts[0]) + amt := fee + numDesiredBlocks*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier* + txs.NameBaseCost(name, data) + tx, _ := txs.NewNameTx(state, testPrivAccounts[0].PublicKey(), name, data, amt, fee) + tx.Sign(testChainID, testPrivAccounts[0]) - if err := execTxWithState(state, tx, true); err == nil { + if err := execTxWithState(state, tx); err == nil { t.Fatalf("Expected invalid name error from %s", name) } } @@ -260,23 +303,23 @@ func TestNameTxs(t *testing.T) { name := "hold_it_chum" datas := []string{"cold&warm", "!@#$%^&*()", "<<<>>>>", "because why would you ever need a ~ or a & or even a % in a json file? make your case and we'll talk"} for _, data := range datas { - amt := fee + int64(numDesiredBlocks)*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) - tx, _ := txs.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) - tx.Sign(state.ChainID, privAccounts[0]) + amt := fee + numDesiredBlocks*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier* + txs.NameBaseCost(name, data) + tx, _ := txs.NewNameTx(state, testPrivAccounts[0].PublicKey(), name, data, amt, fee) + tx.Sign(testChainID, testPrivAccounts[0]) - if err := execTxWithState(state, tx, true); err == nil { + if err := execTxWithState(state, tx); err == nil { t.Fatalf("Expected invalid data error from %s", data) } } - validateEntry := func(t *testing.T, entry *core_types.NameRegEntry, name, - data string, addr []byte, expires int) { + validateEntry := func(t *testing.T, entry *NameRegEntry, name, data string, addr acm.Address, expires uint64) { if entry == nil { t.Fatalf("Could not find name %s", name) } - if !bytes.Equal(entry.Owner, addr) { - t.Fatalf("Wrong owner. Got %X expected %X", entry.Owner, addr) + if entry.Owner != addr { + t.Fatalf("Wrong owner. Got %s expected %s", entry.Owner, addr) } if data != entry.Data { t.Fatalf("Wrong data. Got %s expected %s", entry.Data, data) @@ -292,78 +335,81 @@ func TestNameTxs(t *testing.T) { // try a good one, check data, owner, expiry name = "@looking_good/karaoke_bar.broadband" data = "on this side of neptune there are 1234567890 people: first is OMNIVORE+-3. Or is it. Ok this is pretty restrictive. No exclamations :(. Faces tho :')" - amt := fee + int64(numDesiredBlocks)*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) - tx, _ := txs.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) - tx.Sign(state.ChainID, privAccounts[0]) - if err := execTxWithState(state, tx, true); err != nil { + amt := fee + numDesiredBlocks*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) + tx, _ := txs.NewNameTx(state, testPrivAccounts[0].PublicKey(), name, data, amt, fee) + tx.Sign(testChainID, testPrivAccounts[0]) + if err := execTxWithState(state, tx); err != nil { t.Fatal(err) } entry := state.GetNameRegEntry(name) - validateEntry(t, entry, name, data, privAccounts[0].Address, startingBlock+numDesiredBlocks) + validateEntry(t, entry, name, data, testPrivAccounts[0].Address(), startingBlock+numDesiredBlocks) // fail to update it as non-owner, in same block - tx, _ = txs.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) - tx.Sign(state.ChainID, privAccounts[1]) - if err := execTxWithState(state, tx, true); err == nil { + tx, _ = txs.NewNameTx(state, testPrivAccounts[1].PublicKey(), name, data, amt, fee) + tx.Sign(testChainID, testPrivAccounts[1]) + if err := execTxWithState(state, tx); err == nil { t.Fatal("Expected error") } // update it as owner, just to increase expiry, in same block // NOTE: we have to resend the data or it will clear it (is this what we want?) - tx, _ = txs.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) - tx.Sign(state.ChainID, privAccounts[0]) - if err := execTxWithStateNewBlock(state, tx, true); err != nil { + tx, _ = txs.NewNameTx(state, testPrivAccounts[0].PublicKey(), name, data, amt, fee) + tx.Sign(testChainID, testPrivAccounts[0]) + if err := execTxWithStateNewBlock(state, blockchain, tx); err != nil { t.Fatal(err) } entry = state.GetNameRegEntry(name) - validateEntry(t, entry, name, data, privAccounts[0].Address, startingBlock+numDesiredBlocks*2) + validateEntry(t, entry, name, data, testPrivAccounts[0].Address(), startingBlock+numDesiredBlocks*2) // update it as owner, just to increase expiry, in next block - tx, _ = txs.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) - tx.Sign(state.ChainID, privAccounts[0]) - if err := execTxWithStateNewBlock(state, tx, true); err != nil { + tx, _ = txs.NewNameTx(state, testPrivAccounts[0].PublicKey(), name, data, amt, fee) + tx.Sign(testChainID, testPrivAccounts[0]) + if err := execTxWithStateNewBlock(state, blockchain, tx); err != nil { t.Fatal(err) } entry = state.GetNameRegEntry(name) - validateEntry(t, entry, name, data, privAccounts[0].Address, startingBlock+numDesiredBlocks*3) + validateEntry(t, entry, name, data, testPrivAccounts[0].Address(), startingBlock+numDesiredBlocks*3) // fail to update it as non-owner - state.LastBlockHeight = entry.Expires - 1 - tx, _ = txs.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) - tx.Sign(state.ChainID, privAccounts[1]) - if err := execTxWithState(state, tx, true); err == nil { + // Fast forward + for blockchain.Tip().LastBlockHeight() < entry.Expires-1 { + commitNewBlock(state, blockchain) + } + tx, _ = txs.NewNameTx(state, testPrivAccounts[1].PublicKey(), name, data, amt, fee) + tx.Sign(testChainID, testPrivAccounts[1]) + if err := execTxWithStateAndBlockchain(state, blockchain, tx); err == nil { t.Fatal("Expected error") } + commitNewBlock(state, blockchain) // once expires, non-owner succeeds - state.LastBlockHeight = entry.Expires - tx, _ = txs.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) - tx.Sign(state.ChainID, privAccounts[1]) - if err := execTxWithState(state, tx, true); err != nil { + tx, _ = txs.NewNameTx(state, testPrivAccounts[1].PublicKey(), name, data, amt, fee) + tx.Sign(testChainID, testPrivAccounts[1]) + if err := execTxWithStateAndBlockchain(state, blockchain, tx); err != nil { t.Fatal(err) } entry = state.GetNameRegEntry(name) - validateEntry(t, entry, name, data, privAccounts[1].Address, state.LastBlockHeight+numDesiredBlocks) + validateEntry(t, entry, name, data, testPrivAccounts[1].Address(), blockchain.LastBlockHeight()+numDesiredBlocks) // update it as new owner, with new data (longer), but keep the expiry! data = "In the beginning there was no thing, not even the beginning. It hadn't been here, no there, nor for that matter anywhere, not especially because it had not to even exist, let alone to not. Nothing especially odd about that." oldCredit := amt - fee numDesiredBlocks = 10 - amt = fee + (int64(numDesiredBlocks)*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) - oldCredit) - tx, _ = txs.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) - tx.Sign(state.ChainID, privAccounts[1]) - if err := execTxWithState(state, tx, true); err != nil { + amt = fee + numDesiredBlocks*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) - oldCredit + tx, _ = txs.NewNameTx(state, testPrivAccounts[1].PublicKey(), name, data, amt, fee) + tx.Sign(testChainID, testPrivAccounts[1]) + if err := execTxWithStateAndBlockchain(state, blockchain, tx); err != nil { t.Fatal(err) } entry = state.GetNameRegEntry(name) - validateEntry(t, entry, name, data, privAccounts[1].Address, state.LastBlockHeight+numDesiredBlocks) + validateEntry(t, entry, name, data, testPrivAccounts[1].Address(), blockchain.LastBlockHeight()+numDesiredBlocks) // test removal amt = fee data = "" - tx, _ = txs.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) - tx.Sign(state.ChainID, privAccounts[1]) - if err := execTxWithStateNewBlock(state, tx, true); err != nil { + tx, _ = txs.NewNameTx(state, testPrivAccounts[1].PublicKey(), name, data, amt, fee) + tx.Sign(testChainID, testPrivAccounts[1]) + if err := execTxWithStateNewBlock(state, blockchain, tx); err != nil { t.Fatal(err) } entry = state.GetNameRegEntry(name) @@ -375,21 +421,24 @@ func TestNameTxs(t *testing.T) { // test removal by key1 after expiry name = "looking_good/karaoke_bar" data = "some data" - amt = fee + int64(numDesiredBlocks)*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) - tx, _ = txs.NewNameTx(state, privAccounts[0].PubKey, name, data, amt, fee) - tx.Sign(state.ChainID, privAccounts[0]) - if err := execTxWithState(state, tx, true); err != nil { + amt = fee + numDesiredBlocks*txs.NameByteCostMultiplier*txs.NameBlockCostMultiplier*txs.NameBaseCost(name, data) + tx, _ = txs.NewNameTx(state, testPrivAccounts[0].PublicKey(), name, data, amt, fee) + tx.Sign(testChainID, testPrivAccounts[0]) + if err := execTxWithStateAndBlockchain(state, blockchain, tx); err != nil { t.Fatal(err) } entry = state.GetNameRegEntry(name) - validateEntry(t, entry, name, data, privAccounts[0].Address, state.LastBlockHeight+numDesiredBlocks) - state.LastBlockHeight = entry.Expires + validateEntry(t, entry, name, data, testPrivAccounts[0].Address(), blockchain.LastBlockHeight()+numDesiredBlocks) + // Fast forward + for blockchain.Tip().LastBlockHeight() < entry.Expires { + commitNewBlock(state, blockchain) + } amt = fee data = "" - tx, _ = txs.NewNameTx(state, privAccounts[1].PubKey, name, data, amt, fee) - tx.Sign(state.ChainID, privAccounts[1]) - if err := execTxWithStateNewBlock(state, tx, true); err != nil { + tx, _ = txs.NewNameTx(state, testPrivAccounts[1].PublicKey(), name, data, amt, fee) + tx.Sign(testChainID, testPrivAccounts[1]) + if err := execTxWithStateNewBlock(state, blockchain, tx); err != nil { t.Fatal(err) } entry = state.GetNameRegEntry(name) @@ -424,71 +473,71 @@ var createData, _ = hex.DecodeString("9ed93318") func TestCreates(t *testing.T) { //evm.SetDebug(true) - state, privAccounts, _ := RandGenesisState(3, true, 1000, 1, true, 1000) + state, privAccounts := makeGenesisState(3, true, 1000, 1, true, 1000) - //val0 := state.GetValidatorInfo(privValidators[0].Address) - acc0 := state.GetAccount(privAccounts[0].PubKey.Address()) - acc0PubKey := privAccounts[0].PubKey - acc1 := state.GetAccount(privAccounts[1].PubKey.Address()) - acc2 := state.GetAccount(privAccounts[2].PubKey.Address()) + //val0 := state.GetValidatorInfo(privValidators[0].Address()) + acc0 := getAccount(state, privAccounts[0].Address()) + acc0PubKey := privAccounts[0].PublicKey() + acc1 := getAccount(state, privAccounts[1].Address()) + acc2 := getAccount(state, privAccounts[2].Address()) state = state.Copy() - newAcc1 := state.GetAccount(acc1.Address) - newAcc1.Code = preFactoryCode - newAcc2 := state.GetAccount(acc2.Address) - newAcc2.Code = factoryCode + newAcc1 := getAccount(state, acc1.Address()) + newAcc1.SetCode(preFactoryCode) + newAcc2 := getAccount(state, acc2.Address()) + newAcc2.SetCode(factoryCode) state.UpdateAccount(newAcc1) state.UpdateAccount(newAcc2) - createData = append(createData, word256.LeftPadBytes(acc2.Address, 32)...) + createData = append(createData, acc2.Address().Word256().Bytes()...) // call the pre-factory, triggering the factory to run a create tx := &txs.CallTx{ Input: &txs.TxInput{ - Address: acc0.Address, + Address: acc0.Address(), Amount: 1, - Sequence: acc0.Sequence + 1, + Sequence: acc0.Sequence() + 1, PubKey: acc0PubKey, }, - Address: acc1.Address, + Address: addressPtr(acc1), GasLimit: 10000, Data: createData, } - tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx) - err := execTxWithState(state, tx, true) + tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + err := execTxWithState(state, tx) if err != nil { t.Errorf("Got error in executing call transaction, %v", err) } - acc1 = state.GetAccount(acc1.Address) - storage := state.LoadStorage(acc1.StorageRoot) - _, firstCreatedAddress, _ := storage.Get(word256.LeftPadBytes([]byte{0}, 32)) + acc1 = getAccount(state, acc1.Address()) + storage := state.LoadStorage(acc1.StorageRoot()) + _, firstCreatedAddress, _ := storage.Get(binary.LeftPadBytes([]byte{0}, 32)) - acc0 = state.GetAccount(acc0.Address) + acc0 = getAccount(state, acc0.Address()) // call the pre-factory, triggering the factory to run a create tx = &txs.CallTx{ Input: &txs.TxInput{ - Address: acc0.Address, + Address: acc0.Address(), Amount: 1, - Sequence: acc0.Sequence + 1, + Sequence: acc0.Sequence() + 1, PubKey: acc0PubKey, }, - Address: acc1.Address, + Address: addressPtr(acc1), GasLimit: 100000, Data: createData, } - tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx) - err = execTxWithState(state, tx, true) + tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + err = execTxWithState(state, tx) if err != nil { t.Errorf("Got error in executing call transaction, %v", err) } - acc1 = state.GetAccount(acc1.Address) - storage = state.LoadStorage(acc1.StorageRoot) - _, secondCreatedAddress, _ := storage.Get(word256.LeftPadBytes([]byte{0}, 32)) + acc1 = getAccount(state, acc1.Address()) + storage = state.LoadStorage(acc1.StorageRoot()) + _, secondCreatedAddress, _ := storage.Get(binary.LeftPadBytes([]byte{0}, 32)) if bytes.Equal(firstCreatedAddress, secondCreatedAddress) { t.Errorf("Multiple contracts created with the same address!") @@ -506,129 +555,196 @@ var callerCode, _ = hex.DecodeString("60606040526000357c010000000000000000000000 var sendData, _ = hex.DecodeString("3e58c58c") func TestContractSend(t *testing.T) { - state, privAccounts, _ := RandGenesisState(3, true, 1000, 1, true, 1000) + state, privAccounts := makeGenesisState(3, true, 1000, 1, true, 1000) - //val0 := state.GetValidatorInfo(privValidators[0].Address) - acc0 := state.GetAccount(privAccounts[0].PubKey.Address()) - acc0PubKey := privAccounts[0].PubKey - acc1 := state.GetAccount(privAccounts[1].PubKey.Address()) - acc2 := state.GetAccount(privAccounts[2].PubKey.Address()) + //val0 := state.GetValidatorInfo(privValidators[0].Address()) + acc0 := getAccount(state, privAccounts[0].Address()) + acc0PubKey := privAccounts[0].PublicKey() + acc1 := getAccount(state, privAccounts[1].Address()) + acc2 := getAccount(state, privAccounts[2].Address()) state = state.Copy() - newAcc1 := state.GetAccount(acc1.Address) - newAcc1.Code = callerCode + newAcc1 := getAccount(state, acc1.Address()) + newAcc1.SetCode(callerCode) state.UpdateAccount(newAcc1) - sendData = append(sendData, word256.LeftPadBytes(acc2.Address, 32)...) - sendAmt := int64(10) - acc2Balance := acc2.Balance + sendData = append(sendData, acc2.Address().Word256().Bytes()...) + sendAmt := uint64(10) + acc2Balance := acc2.Balance() // call the contract, triggering the send tx := &txs.CallTx{ Input: &txs.TxInput{ - Address: acc0.Address, + Address: acc0.Address(), Amount: sendAmt, - Sequence: acc0.Sequence + 1, + Sequence: acc0.Sequence() + 1, PubKey: acc0PubKey, }, - Address: acc1.Address, + Address: addressPtr(acc1), GasLimit: 1000, Data: sendData, } - tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx) - err := execTxWithState(state, tx, true) + tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + err := execTxWithState(state, tx) if err != nil { t.Errorf("Got error in executing call transaction, %v", err) } - acc2 = state.GetAccount(acc2.Address) - if acc2.Balance != sendAmt+acc2Balance { - t.Errorf("Value transfer from contract failed! Got %d, expected %d", acc2.Balance, sendAmt+acc2Balance) + acc2 = getAccount(state, acc2.Address()) + if acc2.Balance() != sendAmt+acc2Balance { + t.Errorf("Value transfer from contract failed! Got %d, expected %d", acc2.Balance(), sendAmt+acc2Balance) + } +} +func TestMerklePanic(t *testing.T) { + state, privAccounts := makeGenesisState(3, true, 1000, 1, true, + 1000) + + //val0 := state.GetValidatorInfo(privValidators[0].Address()) + acc0 := getAccount(state, privAccounts[0].Address()) + acc0PubKey := privAccounts[0].PublicKey() + acc1 := getAccount(state, privAccounts[1].Address()) + + state.Save() + // SendTx. + { + stateSendTx := state.Copy() + tx := &txs.SendTx{ + Inputs: []*txs.TxInput{ + { + Address: acc0.Address(), + Amount: 1, + Sequence: acc0.Sequence() + 1, + PubKey: acc0PubKey, + }, + }, + Outputs: []*txs.TxOutput{ + { + Address: acc1.Address(), + Amount: 1, + }, + }, + } + + tx.Inputs[0].Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + err := execTxWithState(stateSendTx, tx) + if err != nil { + t.Errorf("Got error in executing send transaction, %v", err) + } + // uncomment for panic fun! + //stateSendTx.Save() + } + + // CallTx. Just runs through it and checks the transfer. See vm, rpc tests for more + { + stateCallTx := state.Copy() + newAcc1 := getAccount(stateCallTx, acc1.Address()) + newAcc1.SetCode([]byte{0x60}) + stateCallTx.UpdateAccount(newAcc1) + tx := &txs.CallTx{ + Input: &txs.TxInput{ + Address: acc0.Address(), + Amount: 1, + Sequence: acc0.Sequence() + 1, + PubKey: acc0PubKey, + }, + Address: addressPtr(acc1), + GasLimit: 10, + } + + tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + err := execTxWithState(stateCallTx, tx) + if err != nil { + t.Errorf("Got error in executing call transaction, %v", err) + } } + state.Save() + trygetacc0 := getAccount(state, privAccounts[0].Address()) + fmt.Println(trygetacc0.Address()) } // TODO: test overflows. // TODO: test for unbonding validators. func TestTxs(t *testing.T) { + state, privAccounts := makeGenesisState(3, true, 1000, 1, true, 1000) - state, privAccounts, _ := RandGenesisState(3, true, 1000, 1, true, 1000) - - //val0 := state.GetValidatorInfo(privValidators[0].Address) - acc0 := state.GetAccount(privAccounts[0].PubKey.Address()) - acc0PubKey := privAccounts[0].PubKey - acc1 := state.GetAccount(privAccounts[1].PubKey.Address()) + //val0 := state.GetValidatorInfo(privValidators[0].Address()) + acc0 := getAccount(state, privAccounts[0].Address()) + acc0PubKey := privAccounts[0].PublicKey() + acc1 := getAccount(state, privAccounts[1].Address()) // SendTx. { - state := state.Copy() + stateSendTx := state.Copy() tx := &txs.SendTx{ Inputs: []*txs.TxInput{ - &txs.TxInput{ - Address: acc0.Address, + { + Address: acc0.Address(), Amount: 1, - Sequence: acc0.Sequence + 1, + Sequence: acc0.Sequence() + 1, PubKey: acc0PubKey, }, }, Outputs: []*txs.TxOutput{ - &txs.TxOutput{ - Address: acc1.Address, + { + Address: acc1.Address(), Amount: 1, }, }, } - tx.Inputs[0].Signature = privAccounts[0].Sign(state.ChainID, tx) - err := execTxWithState(state, tx, true) + tx.Inputs[0].Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + err := execTxWithState(stateSendTx, tx) if err != nil { t.Errorf("Got error in executing send transaction, %v", err) } - newAcc0 := state.GetAccount(acc0.Address) - if acc0.Balance-1 != newAcc0.Balance { + newAcc0 := getAccount(stateSendTx, acc0.Address()) + if acc0.Balance()-1 != newAcc0.Balance() { t.Errorf("Unexpected newAcc0 balance. Expected %v, got %v", - acc0.Balance-1, newAcc0.Balance) + acc0.Balance()-1, newAcc0.Balance()) } - newAcc1 := state.GetAccount(acc1.Address) - if acc1.Balance+1 != newAcc1.Balance { + newAcc1 := getAccount(stateSendTx, acc1.Address()) + if acc1.Balance()+1 != newAcc1.Balance() { t.Errorf("Unexpected newAcc1 balance. Expected %v, got %v", - acc1.Balance+1, newAcc1.Balance) + acc1.Balance()+1, newAcc1.Balance()) } } // CallTx. Just runs through it and checks the transfer. See vm, rpc tests for more { - state := state.Copy() - newAcc1 := state.GetAccount(acc1.Address) - newAcc1.Code = []byte{0x60} - state.UpdateAccount(newAcc1) + stateCallTx := state.Copy() + newAcc1 := getAccount(stateCallTx, acc1.Address()) + newAcc1.SetCode([]byte{0x60}) + stateCallTx.UpdateAccount(newAcc1) tx := &txs.CallTx{ Input: &txs.TxInput{ - Address: acc0.Address, + Address: acc0.Address(), Amount: 1, - Sequence: acc0.Sequence + 1, + Sequence: acc0.Sequence() + 1, PubKey: acc0PubKey, }, - Address: acc1.Address, + Address: addressPtr(acc1), GasLimit: 10, } - tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx) - err := execTxWithState(state, tx, true) + tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + err := execTxWithState(stateCallTx, tx) if err != nil { t.Errorf("Got error in executing call transaction, %v", err) } - newAcc0 := state.GetAccount(acc0.Address) - if acc0.Balance-1 != newAcc0.Balance { + newAcc0 := getAccount(stateCallTx, acc0.Address()) + if acc0.Balance()-1 != newAcc0.Balance() { t.Errorf("Unexpected newAcc0 balance. Expected %v, got %v", - acc0.Balance-1, newAcc0.Balance) + acc0.Balance()-1, newAcc0.Balance()) } - newAcc1 = state.GetAccount(acc1.Address) - if acc1.Balance+1 != newAcc1.Balance { + newAcc1 = getAccount(stateCallTx, acc1.Address()) + if acc1.Balance()+1 != newAcc1.Balance() { t.Errorf("Unexpected newAcc1 balance. Expected %v, got %v", - acc1.Balance+1, newAcc1.Balance) + acc1.Balance()+1, newAcc1.Balance()) } } + trygetacc0 := getAccount(state, privAccounts[0].Address()) + fmt.Println(trygetacc0.Address()) // NameTx. { @@ -648,31 +764,32 @@ attack the network, they'll generate the longest chain and outpace attackers. network itself requires minimal structure. Messages are broadcast on a best effort basis, and nodes can leave and rejoin the network at will, accepting the longest proof-of-work chain as proof of what happened while they were gone ` - entryAmount := int64(10000) + entryAmount := uint64(10000) - state := state.Copy() + stateNameTx := state.Copy() tx := &txs.NameTx{ Input: &txs.TxInput{ - Address: acc0.Address, + Address: acc0.Address(), Amount: entryAmount, - Sequence: acc0.Sequence + 1, + Sequence: acc0.Sequence() + 1, PubKey: acc0PubKey, }, Name: entryName, Data: entryData, } - tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx) - err := execTxWithState(state, tx, true) + tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + + err := execTxWithState(stateNameTx, tx) if err != nil { t.Errorf("Got error in executing call transaction, %v", err) } - newAcc0 := state.GetAccount(acc0.Address) - if acc0.Balance-entryAmount != newAcc0.Balance { + newAcc0 := getAccount(stateNameTx, acc0.Address()) + if acc0.Balance()-entryAmount != newAcc0.Balance() { t.Errorf("Unexpected newAcc0 balance. Expected %v, got %v", - acc0.Balance-entryAmount, newAcc0.Balance) + acc0.Balance()-entryAmount, newAcc0.Balance()) } - entry := state.GetNameRegEntry(entryName) + entry := stateNameTx.GetNameRegEntry(entryName) if entry == nil { t.Errorf("Expected an entry but got nil") } @@ -683,8 +800,8 @@ proof-of-work chain as proof of what happened while they were gone ` // test a bad string tx.Data = string([]byte{0, 1, 2, 3, 127, 128, 129, 200, 251}) tx.Input.Sequence += 1 - tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx) - err = execTxWithState(state, tx, true) + tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + err = execTxWithState(stateNameTx, tx) if _, ok := err.(txs.ErrTxInvalidString); !ok { t.Errorf("Expected invalid string error. Got: %s", err.Error()) } @@ -695,44 +812,44 @@ proof-of-work chain as proof of what happened while they were gone ` { state := state.Copy() tx := &txs.BondTx{ - PubKey: acc0PubKey.(crypto.PubKeyEd25519), + PublicKey: acc0PubKey.(acm.PublicKeyEd25519), Inputs: []*txs.TxInput{ &txs.TxInput{ - Address: acc0.Address, + Address: acc0.Address(), Amount: 1, - Sequence: acc0.Sequence + 1, - PubKey: acc0PubKey, + Sequence: acc0.Sequence() + 1, + PublicKey: acc0PubKey, }, }, UnbondTo: []*txs.TxOutput{ &txs.TxOutput{ - Address: acc0.Address, + Address: acc0.Address(), Amount: 1, }, }, } - tx.Signature = privAccounts[0].Sign(state.ChainID, tx).(crypto.SignatureEd25519) - tx.Inputs[0].Signature = privAccounts[0].Sign(state.ChainID, tx) - err := execTxWithState(state, tx, true) + tx.Signature = privAccounts[0] acm.ChainSign(testChainID, tx).(crypto.SignatureEd25519) + tx.Inputs[0].Signature = privAccounts[0] acm.ChainSign(testChainID, tx) + err := execTxWithState(state, tx) if err != nil { t.Errorf("Got error in executing bond transaction, %v", err) } - newAcc0 := state.GetAccount(acc0.Address) - if newAcc0.Balance != acc0.Balance-1 { + newAcc0 := getAccount(state, acc0.Address()) + if newAcc0.Balance() != acc0.Balance()-1 { t.Errorf("Unexpected newAcc0 balance. Expected %v, got %v", - acc0.Balance-1, newAcc0.Balance) + acc0.Balance()-1, newAcc0.Balance()) } - _, acc0Val := state.BondedValidators.GetByAddress(acc0.Address) + _, acc0Val := state.BondedValidators.GetByAddress(acc0.Address()) if acc0Val == nil { t.Errorf("acc0Val not present") } - if acc0Val.BondHeight != state.LastBlockHeight+1 { + if acc0Val.BondHeight != blockchain.LastBlockHeight()+1 { t.Errorf("Unexpected bond height. Expected %v, got %v", - state.LastBlockHeight, acc0Val.BondHeight) + blockchain.LastBlockHeight(), acc0Val.BondHeight) } if acc0Val.VotingPower != 1 { t.Errorf("Unexpected voting power. Expected %v, got %v", - acc0Val.VotingPower, acc0.Balance) + acc0Val.VotingPower, acc0.Balance()) } if acc0Val.Accum != 0 { t.Errorf("Unexpected accum. Expected 0, got %v", @@ -746,52 +863,52 @@ proof-of-work chain as proof of what happened while they were gone ` func TestSelfDestruct(t *testing.T) { - state, privAccounts, _ := RandGenesisState(3, true, 1000, 1, true, 1000) + state, privAccounts := makeGenesisState(3, true, 1000, 1, true, 1000) - acc0 := state.GetAccount(privAccounts[0].PubKey.Address()) - acc0PubKey := privAccounts[0].PubKey - acc1 := state.GetAccount(privAccounts[1].PubKey.Address()) - acc2 := state.GetAccount(privAccounts[2].Address) - sendingAmount, refundedBalance, oldBalance := int64(1), acc1.Balance, acc2.Balance + acc0 := getAccount(state, privAccounts[0].Address()) + acc0PubKey := privAccounts[0].PublicKey() + acc1 := getAccount(state, privAccounts[1].Address()) + acc2 := getAccount(state, privAccounts[2].Address()) + sendingAmount, refundedBalance, oldBalance := uint64(1), acc1.Balance(), acc2.Balance() - newAcc1 := state.GetAccount(acc1.Address) + newAcc1 := getAccount(state, acc1.Address()) // store 0x1 at 0x1, push an address, then self-destruct:) contractCode := []byte{0x60, 0x01, 0x60, 0x01, 0x55, 0x73} - contractCode = append(contractCode, acc2.Address...) + contractCode = append(contractCode, acc2.Address().Bytes()...) contractCode = append(contractCode, 0xff) - newAcc1.Code = contractCode + newAcc1.SetCode(contractCode) state.UpdateAccount(newAcc1) // send call tx with no data, cause self-destruct - tx := txs.NewCallTxWithNonce(acc0PubKey, acc1.Address, nil, sendingAmount, 1000, 0, acc0.Sequence+1) - tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx) + tx := txs.NewCallTxWithNonce(acc0PubKey, addressPtr(acc1), nil, sendingAmount, 1000, 0, acc0.Sequence()+1) + tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) // we use cache instead of execTxWithState so we can run the tx twice - cache := NewBlockCache(state) - if err := ExecTx(cache, tx, true, nil, logger); err != nil { + exe := NewBatchCommitter(state, testChainID, bcm.NewBlockchain(testGenesisDoc), event.NewNoOpFireable(), logger) + if err := exe.Execute(tx); err != nil { t.Errorf("Got error in executing call transaction, %v", err) } // if we do it again, we won't get an error, but the self-destruct // shouldn't happen twice and the caller should lose fee tx.Input.Sequence += 1 - tx.Input.Signature = privAccounts[0].Sign(state.ChainID, tx) - if err := ExecTx(cache, tx, true, nil, logger); err != nil { + tx.Input.Signature = acm.ChainSign(privAccounts[0], testChainID, tx) + if err := exe.Execute(tx); err != nil { t.Errorf("Got error in executing call transaction, %v", err) } // commit the block - cache.Sync() + exe.Commit() // acc2 should receive the sent funds and the contracts balance - newAcc2 := state.GetAccount(acc2.Address) + newAcc2 := getAccount(state, acc2.Address()) newBalance := sendingAmount + refundedBalance + oldBalance - if newAcc2.Balance != newBalance { + if newAcc2.Balance() != newBalance { t.Errorf("Unexpected newAcc2 balance. Expected %v, got %v", - newAcc2.Balance, newBalance) + newAcc2.Balance(), newBalance) } - newAcc1 = state.GetAccount(acc1.Address) + newAcc1 = getAccount(state, acc1.Address()) if newAcc1 != nil { t.Errorf("Expected account to be removed") } @@ -802,29 +919,29 @@ func TestSelfDestruct(t *testing.T) { func TestAddValidator(t *testing.T) { // Generate a state, save & load it. - s0, privAccounts, privValidators := RandGenesisState(10, false, 1000, 1, false, 1000) + s0, privAccounts, privValidators := makeGenesisState(10, false, 1000, 1, false, 1000) // The first privAccount will become a validator acc0 := privAccounts[0] bondTx := &txs.BondTx{ - PubKey: acc0.PubKey.(account.PubKeyEd25519), + PublicKey: acc0.PublicKey.(account.PubKeyEd25519), Inputs: []*txs.TxInput{ &txs.TxInput{ - Address: acc0.Address, + Address: acc0.Address(), Amount: 1000, Sequence: 1, - PubKey: acc0.PubKey, + PublicKey: acc0.PublicKey, }, }, UnbondTo: []*txs.TxOutput{ &txs.TxOutput{ - Address: acc0.Address, + Address: acc0.Address(), Amount: 1000, }, }, } - bondTx.Signature = acc0.Sign(s0.ChainID, bondTx).(account.SignatureEd25519) - bondTx.Inputs[0].Signature = acc0.Sign(s0.ChainID, bondTx) + bondTx.Signature = acc0 acm.ChainSign(testChainID, bondTx).(account.SignatureEd25519) + bondTx.Inputs[0].Signature = acc0 acm.ChainSign(testChainID, bondTx) // Make complete block and blockParts block0 := makeBlock(t, s0, nil, []txs.Tx{bondTx}) @@ -858,7 +975,7 @@ func TestAddValidator(t *testing.T) { BlockHash: block0.Hash(), BlockPartsHeader: block0Parts.Header(), } - privValidators[0].SignVote(s0.ChainID, precommit0) + privValidators[0].SignVote(testChainID, precommit0) block1 := makeBlock(t, s0, &txs.Validation{ diff --git a/execution/transactor.go b/execution/transactor.go new file mode 100644 index 00000000..e9957310 --- /dev/null +++ b/execution/transactor.go @@ -0,0 +1,457 @@ +// 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 ( + "bytes" + "fmt" + "sync" + "time" + + "reflect" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/binary" + "github.com/hyperledger/burrow/blockchain" + "github.com/hyperledger/burrow/event" + 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/logging" + "github.com/hyperledger/burrow/logging/structure" + logging_types "github.com/hyperledger/burrow/logging/types" + "github.com/hyperledger/burrow/txs" + abci_types "github.com/tendermint/abci/types" + "github.com/tendermint/go-wire" +) + +type Call struct { + Return []byte `json:"return"` + GasUsed uint64 `json:"gas_used"` +} + +type Transactor interface { + Call(fromAddress, toAddress acm.Address, data []byte) (*Call, error) + CallCode(fromAddress acm.Address, code, data []byte) (*Call, error) + BroadcastTx(tx txs.Tx) (*txs.Receipt, error) + BroadcastTxAsync(tx txs.Tx, callback func(res *abci_types.Response)) error + Transact(privKey []byte, address acm.Address, data []byte, gasLimit, fee uint64) (*txs.Receipt, error) + TransactAndHold(privKey []byte, address acm.Address, data []byte, gasLimit, fee uint64) (*evm_events.EventDataCall, error) + Send(privKey []byte, toAddress acm.Address, amount uint64) (*txs.Receipt, error) + SendAndHold(privKey []byte, toAddress acm.Address, amount uint64) (*txs.Receipt, error) + TransactNameReg(privKey []byte, name, data string, amount, fee uint64) (*txs.Receipt, error) + SignTx(tx txs.Tx, privAccounts []acm.PrivateAccount) (txs.Tx, error) +} + +// Transactor is the controller/middleware for the v0 RPC +type transactor struct { + txMtx *sync.Mutex + blockchain blockchain.Blockchain + state acm.StateReader + eventEmitter event.Emitter + broadcastTxAsync func(tx txs.Tx, callback func(res *abci_types.Response)) error + logger logging_types.InfoTraceLogger +} + +var _ Transactor = &transactor{} + +func NewTransactor(blockchain blockchain.Blockchain, state acm.StateReader, eventEmitter event.Emitter, + broadcastTxAsync func(tx txs.Tx, callback func(res *abci_types.Response)) error, + logger logging_types.InfoTraceLogger) *transactor { + + return &transactor{ + blockchain: blockchain, + state: state, + eventEmitter: eventEmitter, + broadcastTxAsync: broadcastTxAsync, + logger: logger.With(structure.ComponentKey, "Transactor"), + } +} + +// Run a contract's code on an isolated and unpersisted state +// Cannot be used to create new contracts +func (trans *transactor) Call(fromAddress, toAddress acm.Address, data []byte) (*Call, error) { + if evm.RegisteredNativeContract(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) + } + // This was being run against CheckTx cache, need to understand the reasoning + callee, err := acm.GetMutableAccount(trans.state, toAddress) + if err != nil { + return nil, err + } + if callee == nil { + return nil, fmt.Errorf("account %s does not exist", toAddress) + } + caller := acm.ConcreteAccount{Address: fromAddress}.MutableAccount() + txCache := NewTxCache(trans.state) + params := vmParams(trans.blockchain) + + vmach := evm.NewVM(txCache, evm.DefaultDynamicMemoryProvider, params, caller.Address(), nil, + logging.WithScope(trans.logger, "Call")) + vmach.SetFireable(trans.eventEmitter) + + gas := params.GasLimit + ret, err := vmach.Call(caller, callee, callee.Code(), data, 0, &gas) + if err != nil { + return nil, err + } + gasUsed := params.GasLimit - gas + return &Call{Return: ret, GasUsed: gasUsed}, nil +} + +// Run the given code on an isolated and unpersisted state +// Cannot be used to create new contracts. +func (trans *transactor) CallCode(fromAddress acm.Address, code, data []byte) (*Call, error) { + // This was being run against CheckTx cache, need to understand the reasoning + callee := acm.ConcreteAccount{Address: fromAddress}.MutableAccount() + caller := acm.ConcreteAccount{Address: fromAddress}.MutableAccount() + txCache := NewTxCache(trans.state) + params := vmParams(trans.blockchain) + + vmach := evm.NewVM(txCache, evm.DefaultDynamicMemoryProvider, params, caller.Address(), nil, + logging.WithScope(trans.logger, "CallCode")) + gas := params.GasLimit + ret, err := vmach.Call(caller, callee, code, data, 0, &gas) + if err != nil { + return nil, err + } + gasUsed := params.GasLimit - gas + return &Call{Return: ret, GasUsed: gasUsed}, nil +} + +func (trans *transactor) BroadcastTxAsync(tx txs.Tx, callback func(res *abci_types.Response)) error { + return trans.broadcastTxAsync(tx, callback) +} + +// Broadcast a transaction. +func (trans *transactor) BroadcastTx(tx txs.Tx) (*txs.Receipt, error) { + responseCh := make(chan *abci_types.Response, 1) + err := trans.BroadcastTxAsync(tx, func(res *abci_types.Response) { + responseCh <- res + }) + + if err != nil { + return nil, err + } + response := <-responseCh + checkTxResponse := response.GetCheckTx() + if checkTxResponse == nil { + return nil, fmt.Errorf("application did not return CheckTx response") + } + + switch checkTxResponse.Code { + case abci_types.CodeType_OK: + receipt := new(txs.Receipt) + err := wire.ReadBinaryBytes(checkTxResponse.Data, receipt) + if err != nil { + return nil, fmt.Errorf("could not deserialise transaction receipt: %s", err) + } + return receipt, nil + default: + return nil, fmt.Errorf("error returned by Tendermint in BroadcastTxSync "+ + "ABCI code: %v, ABCI log: %v", checkTxResponse.Code, checkTxResponse.Log) + } +} + +// Orders calls to BroadcastTx using lock (waits for response from core before releasing) +func (trans *transactor) Transact(privKey []byte, address acm.Address, data []byte, gasLimit, + fee uint64) (*txs.Receipt, error) { + if len(privKey) != 64 { + return nil, fmt.Errorf("Private key is not of the right length: %d\n", len(privKey)) + } + trans.txMtx.Lock() + defer trans.txMtx.Unlock() + pa := acm.GeneratePrivateAccountFromPrivateKeyBytes(privKey) + // [Silas] This is puzzling, if the account doesn't exist the CallTx will fail, so what's the point in this? + acc, err := trans.state.GetAccount(pa.Address()) + if err != nil { + return nil, err + } + sequence := uint64(1) + if acc != nil { + sequence = acc.Sequence() + uint64(1) + } + // TODO: [Silas] we should consider revising this method and removing fee, or + // possibly adding an amount parameter. It is non-sensical to just be able to + // set the fee. Our support of fees in general is questionable since at the + // moment all we do is deduct the fee effectively leaking token. It is possible + // someone may be using the sending of native token to payable functions but + // they can be served by broadcasting a token. + + // We hard-code the amount to be equal to the fee which means the CallTx we + // generate transfers 0 value, which is the most sensible default since in + // recent solidity compilers the EVM generated will throw an error if value + // is transferred to a non-payable function. + txInput := &txs.TxInput{ + Address: pa.Address(), + Amount: fee, + Sequence: sequence, + PubKey: pa.PublicKey(), + } + tx := &txs.CallTx{ + Input: txInput, + Address: &address, + GasLimit: gasLimit, + Fee: fee, + Data: data, + } + + // Got ourselves a tx. + txS, errS := trans.SignTx(tx, []acm.PrivateAccount{pa}) + if errS != nil { + return nil, errS + } + return trans.BroadcastTx(txS) +} + +func (trans *transactor) TransactAndHold(privKey []byte, address acm.Address, data []byte, gasLimit, + fee uint64) (*evm_events.EventDataCall, error) { + rec, tErr := trans.Transact(privKey, address, data, gasLimit, fee) + if tErr != nil { + return nil, tErr + } + var addr acm.Address + if rec.CreatesContract { + addr = rec.ContractAddr + } else { + addr = address + } + // We want non-blocking on the first event received (but buffer the value), + // after which we want to block (and then discard the value - see below) + wc := make(chan *evm_events.EventDataCall, 1) + subId := fmt.Sprintf("%X", rec.TxHash) + trans.eventEmitter.Subscribe(subId, evm_events.EventStringAccCall(addr), + func(eventData event.AnyEventData) { + eventDataCall := eventData.EventDataCall() + if eventDataCall == nil { + trans.logger.Info("error", "cold not be convert event data to EventDataCall", + structure.ScopeKey, "TransactAndHold", + "sub_id", subId, + "event_data_type", reflect.TypeOf(eventData.Get()).String()) + return + } + if bytes.Equal(eventDataCall.TxID, rec.TxHash) { + // Beware the contract of go-events subscribe is that we must not be + // blocking in an event callback when we try to unsubscribe! + // We work around this by using a non-blocking send. + select { + // This is a non-blocking send, but since we are using a buffered + // channel of size 1 we will always grab our first event even if we + // haven't read from the channel at the time we receive the first event. + case wc <- eventDataCall: + default: + } + } + }) + + timer := time.NewTimer(300 * time.Second) + toChan := timer.C + + var ret *evm_events.EventDataCall + var rErr error + + select { + case <-toChan: + rErr = fmt.Errorf("Transaction timed out. Hash: " + subId) + case e := <-wc: + timer.Stop() + if e.Exception != "" { + rErr = fmt.Errorf("error when transacting: " + e.Exception) + } else { + ret = e + } + } + trans.eventEmitter.Unsubscribe(subId) + return ret, rErr +} + +func (trans *transactor) Send(privKey []byte, toAddress acm.Address, amount uint64) (*txs.Receipt, error) { + if len(privKey) != 64 { + return nil, fmt.Errorf("Private key is not of the right length: %d\n", + len(privKey)) + } + + pk := &[64]byte{} + copy(pk[:], privKey) + trans.txMtx.Lock() + defer trans.txMtx.Unlock() + pa := acm.GeneratePrivateAccountFromPrivateKeyBytes(privKey) + cache := trans.state + acc, err := cache.GetAccount(pa.Address()) + if err != nil { + return nil, err + } + sequence := uint64(1) + if acc != nil { + sequence = acc.Sequence() + uint64(1) + } + + tx := txs.NewSendTx() + + txInput := &txs.TxInput{ + Address: pa.Address(), + Amount: amount, + Sequence: sequence, + PubKey: pa.PublicKey(), + } + + tx.Inputs = append(tx.Inputs, txInput) + + txOutput := &txs.TxOutput{Address: toAddress, Amount: amount} + + tx.Outputs = append(tx.Outputs, txOutput) + + // Got ourselves a tx. + txS, errS := trans.SignTx(tx, []acm.PrivateAccount{pa}) + if errS != nil { + return nil, errS + } + return trans.BroadcastTx(txS) +} + +func (trans *transactor) SendAndHold(privKey []byte, toAddress acm.Address, amount uint64) (*txs.Receipt, error) { + rec, tErr := trans.Send(privKey, toAddress, amount) + if tErr != nil { + return nil, tErr + } + + wc := make(chan *txs.SendTx) + subId := fmt.Sprintf("%X", rec.TxHash) + + trans.eventEmitter.Subscribe(subId, exe_events.EventStringAccOutput(toAddress), + func(eventData event.AnyEventData) { + eventDataTx, ok := eventData.Get().(exe_events.EventDataTx) + if !ok { + trans.logger.Info("error", "cold not be convert event data to EventDataCall", + structure.ScopeKey, "SendAndHold", + "tx_hash", subId, + "event_data_type", reflect.TypeOf(eventData.Get()).String()) + return + } + tx, ok := eventDataTx.Tx.(*txs.SendTx) + if !ok { + trans.logger.Info("error", "EventDataTx was expected to contain SendTx", + structure.ScopeKey, "SendAndHold", + "sub_id", subId, + "tx_type", reflect.TypeOf(eventDataTx.Tx).String()) + return + } + + wc <- tx + }) + + timer := time.NewTimer(300 * time.Second) + toChan := timer.C + + var rErr error + + pa := acm.GeneratePrivateAccountFromPrivateKeyBytes(privKey) + + select { + case <-toChan: + rErr = fmt.Errorf("Transaction timed out. Hash: " + subId) + case e := <-wc: + if e.Inputs[0].Address == pa.Address() && e.Inputs[0].Amount == amount { + timer.Stop() + trans.eventEmitter.Unsubscribe(subId) + return rec, rErr + } + } + return nil, rErr +} + +func (trans *transactor) TransactNameReg(privKey []byte, name, data string, amount, fee uint64) (*txs.Receipt, error) { + + if len(privKey) != 64 { + return nil, fmt.Errorf("Private key is not of the right length: %d\n", len(privKey)) + } + trans.txMtx.Lock() + defer trans.txMtx.Unlock() + pa := acm.GeneratePrivateAccountFromPrivateKeyBytes(privKey) + cache := trans.state // XXX: DON'T MUTATE THIS CACHE (used internally for CheckTx) + acc, err := cache.GetAccount(pa.Address()) + if err != nil { + return nil, err + } + sequence := uint64(1) + if acc == nil { + sequence = acc.Sequence() + uint64(1) + } + tx := txs.NewNameTxWithNonce(pa.PublicKey(), name, data, amount, fee, sequence) + // Got ourselves a tx. + txS, errS := trans.SignTx(tx, []acm.PrivateAccount{pa}) + if errS != nil { + return nil, errS + } + return trans.BroadcastTx(txS) +} + +// Sign a transaction +func (trans *transactor) SignTx(tx txs.Tx, privAccounts []acm.PrivateAccount) (txs.Tx, error) { + // more checks? + + for i, privAccount := range privAccounts { + if privAccount == nil || privAccount.PrivateKey().Unwrap() == nil { + return nil, fmt.Errorf("invalid (empty) privAccount @%v", i) + } + } + chainID := trans.blockchain.ChainID() + switch tx.(type) { + case *txs.NameTx: + nameTx := tx.(*txs.NameTx) + nameTx.Input.PubKey = privAccounts[0].PublicKey() + nameTx.Input.Signature = acm.ChainSign(privAccounts[0], chainID, nameTx) + case *txs.SendTx: + sendTx := tx.(*txs.SendTx) + for i, input := range sendTx.Inputs { + input.PubKey = privAccounts[i].PublicKey() + input.Signature = acm.ChainSign(privAccounts[i], chainID, sendTx) + } + case *txs.CallTx: + callTx := tx.(*txs.CallTx) + callTx.Input.PubKey = privAccounts[0].PublicKey() + callTx.Input.Signature = acm.ChainSign(privAccounts[0], chainID, callTx) + case *txs.BondTx: + bondTx := tx.(*txs.BondTx) + // the first privaccount corresponds to the BondTx pub key. + // the rest to the inputs + bondTx.Signature = acm.ChainSign(privAccounts[0], chainID, bondTx) + for i, input := range bondTx.Inputs { + input.PubKey = privAccounts[i+1].PublicKey() + input.Signature = acm.ChainSign(privAccounts[i+1], chainID, bondTx) + } + case *txs.UnbondTx: + unbondTx := tx.(*txs.UnbondTx) + unbondTx.Signature = acm.ChainSign(privAccounts[0], chainID, unbondTx) + case *txs.RebondTx: + rebondTx := tx.(*txs.RebondTx) + rebondTx.Signature = acm.ChainSign(privAccounts[0], chainID, rebondTx) + default: + return nil, fmt.Errorf("Object is not a proper transaction: %v\n", tx) + } + return tx, nil +} + +func vmParams(blockchain blockchain.Blockchain) evm.Params { + tip := blockchain.Tip() + return evm.Params{ + BlockHeight: tip.LastBlockHeight(), + BlockHash: binary.LeftPadWord256(tip.LastBlockHash()), + BlockTime: tip.LastBlockTime().Unix(), + GasLimit: GasLimit, + } +} diff --git a/execution/tx_cache.go b/execution/tx_cache.go new file mode 100644 index 00000000..4dc56485 --- /dev/null +++ b/execution/tx_cache.go @@ -0,0 +1,128 @@ +// 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 ( + "fmt" + + acm "github.com/hyperledger/burrow/account" + "github.com/hyperledger/burrow/binary" +) + +type TxCache struct { + backend acm.StateReader + accounts map[acm.Address]vmAccountInfo + storages map[binary.Tuple256]binary.Word256 +} + +var _ acm.StateWriter = &TxCache{} + +func NewTxCache(backend acm.StateReader) *TxCache { + return &TxCache{ + backend: backend, + accounts: make(map[acm.Address]vmAccountInfo), + storages: make(map[binary.Tuple256]binary.Word256), + } +} + +//------------------------------------- +// TxCache.account + +func (cache *TxCache) GetAccount(addr acm.Address) (acm.Account, error) { + acc, removed := cache.accounts[addr].unpack() + if removed { + return nil, nil + } else if acc == nil { + return cache.backend.GetAccount(addr) + } + return acc, nil +} + +func (cache *TxCache) UpdateAccount(acc acm.Account) error { + _, removed := cache.accounts[acc.Address()].unpack() + if removed { + return fmt.Errorf("UpdateAccount on a removed account %s", acc.Address()) + } + cache.accounts[acc.Address()] = vmAccountInfo{acc, false} + return nil +} + +func (cache *TxCache) RemoveAccount(addr acm.Address) error { + acc, removed := cache.accounts[addr].unpack() + if removed { + fmt.Errorf("RemoveAccount on a removed account %s", addr) + } + cache.accounts[addr] = vmAccountInfo{acc, true} + return nil +} + +// TxCache.account +//------------------------------------- +// TxCache.storage + +func (cache *TxCache) GetStorage(addr acm.Address, key binary.Word256) (binary.Word256, error) { + // Check cache + value, ok := cache.storages[binary.Tuple256{First: addr.Word256(), Second: key}] + if ok { + return value, nil + } + + // Load from backend + return cache.backend.GetStorage(addr, key) +} + +// NOTE: Set value to zero to removed from the trie. +func (cache *TxCache) SetStorage(addr acm.Address, key binary.Word256, value binary.Word256) error { + _, removed := cache.accounts[addr].unpack() + if removed { + fmt.Errorf("SetStorage on a removed account %s", addr) + } + cache.storages[binary.Tuple256{First: addr.Word256(), Second: key}] = value + return nil +} + +// TxCache.storage +//------------------------------------- + +// These updates do not have to be in deterministic order, +// the backend is responsible for ordering updates. +func (cache *TxCache) Sync(backend acm.StateWriter) { + // Remove or update storage + for addrKey, value := range cache.storages { + addrWord256, key := binary.Tuple256Split(addrKey) + backend.SetStorage(acm.AddressFromWord256(addrWord256), key, value) + } + + // Remove or update accounts + for addr, accInfo := range cache.accounts { + acc, removed := accInfo.unpack() + if removed { + backend.RemoveAccount(addr) + } else { + backend.UpdateAccount(acc) + } + } +} + +//----------------------------------------------------------------------------- + +type vmAccountInfo struct { + account acm.Account + removed bool +} + +func (accInfo vmAccountInfo) unpack() (acm.Account, bool) { + return accInfo.account, accInfo.removed +} diff --git a/manager/burrow-mint/accounts.go b/manager/burrow-mint/accounts.go deleted file mode 100644 index b0e58c35..00000000 --- a/manager/burrow-mint/accounts.go +++ /dev/null @@ -1,228 +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 burrowmint - -// Accounts is part of the pipe for BurrowMint and provides the implementation -// for the pipe to call into the BurrowMint application - -import ( - "bytes" - "encoding/hex" - "fmt" - "sync" - - account "github.com/hyperledger/burrow/account" - core_types "github.com/hyperledger/burrow/core/types" - definitions "github.com/hyperledger/burrow/definitions" - event "github.com/hyperledger/burrow/event" - word256 "github.com/hyperledger/burrow/word256" -) - -// NOTE [ben] Compiler check to ensure Accounts successfully implements -// burrow/definitions.Accounts -var _ definitions.Accounts = (*accounts)(nil) - -// The accounts struct has methods for working with accounts. -type accounts struct { - burrowMint *BurrowMint - filterFactory *event.FilterFactory -} - -func newAccounts(burrowMint *BurrowMint) *accounts { - ff := event.NewFilterFactory() - - ff.RegisterFilterPool("code", &sync.Pool{ - New: func() interface{} { - return &AccountCodeFilter{} - }, - }) - - ff.RegisterFilterPool("balance", &sync.Pool{ - New: func() interface{} { - return &AccountBalanceFilter{} - }, - }) - - return &accounts{burrowMint, ff} - -} - -// Generate a new Private Key Account. -func (this *accounts) GenPrivAccount() (*account.PrivAccount, error) { - pa := account.GenPrivAccount() - return pa, nil -} - -// Generate a new Private Key Account. -func (this *accounts) GenPrivAccountFromKey(privKey []byte) ( - *account.PrivAccount, error) { - if len(privKey) != 64 { - return nil, fmt.Errorf("Private key is not 64 bytes long.") - } - fmt.Printf("PK BYTES FROM ACCOUNTS: %x\n", privKey) - pa := account.GenPrivAccountFromPrivKeyBytes(privKey) - return pa, nil -} - -// Get all accounts. -func (this *accounts) Accounts(fda []*event.FilterData) ( - *core_types.AccountList, error) { - accounts := make([]*account.Account, 0) - state := this.burrowMint.GetState() - filter, err := this.filterFactory.NewFilter(fda) - if err != nil { - return nil, fmt.Errorf("Error in query: " + err.Error()) - } - state.GetAccounts().Iterate(func(key, value []byte) bool { - acc := account.DecodeAccount(value) - if filter.Match(acc) { - accounts = append(accounts, acc) - } - return false - }) - return &core_types.AccountList{accounts}, nil -} - -// Get an account. -func (this *accounts) Account(address []byte) (*account.Account, error) { - cache := this.burrowMint.GetState() // NOTE: we want to read from mempool! - acc := cache.GetAccount(address) - if acc == nil { - acc = this.newAcc(address) - } - return acc, nil -} - -// Get the value stored at 'key' in the account with address 'address' -// Both the key and value is returned. -func (this *accounts) StorageAt(address, key []byte) (*core_types.StorageItem, - error) { - state := this.burrowMint.GetState() - account := state.GetAccount(address) - if account == nil { - return &core_types.StorageItem{key, []byte{}}, nil - } - storageRoot := account.StorageRoot - storageTree := state.LoadStorage(storageRoot) - - _, value, _ := storageTree.Get(word256.LeftPadWord256(key).Bytes()) - if value == nil { - return &core_types.StorageItem{key, []byte{}}, nil - } - return &core_types.StorageItem{key, value}, nil -} - -// Get the storage of the account with address 'address'. -func (this *accounts) Storage(address []byte) (*core_types.Storage, error) { - - state := this.burrowMint.GetState() - account := state.GetAccount(address) - storageItems := make([]core_types.StorageItem, 0) - if account == nil { - return &core_types.Storage{nil, storageItems}, nil - } - storageRoot := account.StorageRoot - storageTree := state.LoadStorage(storageRoot) - - storageTree.Iterate(func(key, value []byte) bool { - storageItems = append(storageItems, core_types.StorageItem{ - key, value}) - return false - }) - return &core_types.Storage{storageRoot, storageItems}, nil -} - -// Create a new account. -func (this *accounts) newAcc(address []byte) *account.Account { - return &account.Account{ - Address: address, - PubKey: nil, - Sequence: 0, - Balance: 0, - Code: nil, - StorageRoot: nil, - } -} - -// Filter for account code. -// Ops: == or != -// Could be used to match against nil, to see if an account is a contract account. -type AccountCodeFilter struct { - op string - value []byte - match func([]byte, []byte) bool -} - -func (this *AccountCodeFilter) Configure(fd *event.FilterData) error { - op := fd.Op - val, err := hex.DecodeString(fd.Value) - - if err != nil { - return fmt.Errorf("Wrong value type.") - } - if op == "==" { - this.match = func(a, b []byte) bool { - return bytes.Equal(a, b) - } - } else if op == "!=" { - this.match = func(a, b []byte) bool { - return !bytes.Equal(a, b) - } - } else { - return fmt.Errorf("Op: " + this.op + " is not supported for 'code' filtering") - } - this.op = op - this.value = val - return nil -} - -func (this *AccountCodeFilter) Match(v interface{}) bool { - acc, ok := v.(*account.Account) - if !ok { - return false - } - return this.match(acc.Code, this.value) -} - -// Filter for account balance. -// Ops: All -type AccountBalanceFilter struct { - op string - value int64 - match func(int64, int64) bool -} - -func (this *AccountBalanceFilter) Configure(fd *event.FilterData) error { - val, err := event.ParseNumberValue(fd.Value) - if err != nil { - return err - } - match, err2 := event.GetRangeFilter(fd.Op, "balance") - if err2 != nil { - return err2 - } - this.match = match - this.op = fd.Op - this.value = val - return nil -} - -func (this *AccountBalanceFilter) Match(v interface{}) bool { - acc, ok := v.(*account.Account) - if !ok { - return false - } - return this.match(int64(acc.Balance), this.value) -} diff --git a/manager/burrow-mint/burrow-mint.go b/manager/burrow-mint/burrow-mint.go deleted file mode 100644 index d3255452..00000000 --- a/manager/burrow-mint/burrow-mint.go +++ /dev/null @@ -1,216 +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 burrowmint - -import ( - "bytes" - "fmt" - "sync" - "time" - - abci "github.com/tendermint/abci/types" - tendermint_events "github.com/tendermint/go-events" - wire "github.com/tendermint/go-wire" - - consensus_types "github.com/hyperledger/burrow/consensus/types" - "github.com/hyperledger/burrow/logging" - logging_types "github.com/hyperledger/burrow/logging/types" - sm "github.com/hyperledger/burrow/manager/burrow-mint/state" - manager_types "github.com/hyperledger/burrow/manager/types" - "github.com/hyperledger/burrow/txs" -) - -//-------------------------------------------------------------------------------- -// BurrowMint holds the current state, runs transactions, computes hashes. -// Typically two connections are opened by the tendermint core: -// one for mempool, one for consensus. - -type BurrowMint struct { - mtx sync.Mutex - - state *sm.State - cache *sm.BlockCache - checkCache *sm.BlockCache // for CheckTx (eg. so we get nonces right) - - evc *tendermint_events.EventCache - evsw tendermint_events.EventSwitch - - nTxs int // count txs in a block - logger logging_types.InfoTraceLogger -} - -// Currently we just wrap ConsensusEngine but this interface can give us -// arbitrary control over the type of ConsensusEngine at such a point that we -// support others. For example it can demand a 'marker' function: -// func IsBurrowMint_0.XX.XX_CompatibleConsensusEngine() -type BurrowMintCompatibleConsensusEngine interface { - consensus_types.ConsensusEngine -} - -// NOTE [ben] Compiler check to ensure BurrowMint successfully implements -// burrow/manager/types.Application -var _ manager_types.Application = (*BurrowMint)(nil) - -func (app *BurrowMint) CompatibleConsensus(consensusEngine consensus_types.ConsensusEngine) bool { - _, ok := consensusEngine.(BurrowMintCompatibleConsensusEngine) - return ok -} - -func (app *BurrowMint) GetState() *sm.State { - app.mtx.Lock() - defer app.mtx.Unlock() - return app.state.Copy() -} - -// TODO: this is used for call/callcode and to get nonces during mempool. -// the former should work on last committed state only and the later should -// be handled by the client, or a separate wallet-like nonce tracker thats not part of the app -func (app *BurrowMint) GetCheckCache() *sm.BlockCache { - return app.checkCache -} - -func NewBurrowMint(s *sm.State, evsw tendermint_events.EventSwitch, - logger logging_types.InfoTraceLogger) *BurrowMint { - return &BurrowMint{ - state: s, - cache: sm.NewBlockCache(s), - checkCache: sm.NewBlockCache(s), - evc: tendermint_events.NewEventCache(evsw), - evsw: evsw, - logger: logging.WithScope(logger, "BurrowMint"), - } -} - -// Implements manager/types.Application -func (app *BurrowMint) Info() (info abci.ResponseInfo) { - return abci.ResponseInfo{} -} - -// Implements manager/types.Application -func (app *BurrowMint) SetOption(key string, value string) (log string) { - return "" -} - -// Implements manager/types.Application -func (app *BurrowMint) DeliverTx(txBytes []byte) abci.Result { - app.nTxs += 1 - - // XXX: if we had tx ids we could cache the decoded txs on CheckTx - var n int - var err error - tx := new(txs.Tx) - buf := bytes.NewBuffer(txBytes) - wire.ReadBinaryPtr(tx, buf, len(txBytes), &n, &err) - if err != nil { - return abci.NewError(abci.CodeType_EncodingError, fmt.Sprintf("Encoding error: %v", err)) - } - - err = sm.ExecTx(app.cache, *tx, true, app.evc, app.logger) - if err != nil { - return abci.NewError(abci.CodeType_InternalError, fmt.Sprintf("Internal error: %v", err)) - } - - receipt := txs.GenerateReceipt(app.state.ChainID, *tx) - receiptBytes := wire.BinaryBytes(receipt) - return abci.NewResultOK(receiptBytes, "Success") -} - -// Implements manager/types.Application -func (app *BurrowMint) CheckTx(txBytes []byte) abci.Result { - var n int - var err error - tx := new(txs.Tx) - buf := bytes.NewBuffer(txBytes) - wire.ReadBinaryPtr(tx, buf, len(txBytes), &n, &err) - if err != nil { - return abci.NewError(abci.CodeType_EncodingError, fmt.Sprintf("Encoding error: %v", err)) - } - - // TODO: map ExecTx errors to sensible abci error codes - err = sm.ExecTx(app.checkCache, *tx, false, nil, app.logger) - if err != nil { - return abci.NewError(abci.CodeType_InternalError, fmt.Sprintf("Internal error: %v", err)) - } - receipt := txs.GenerateReceipt(app.state.ChainID, *tx) - receiptBytes := wire.BinaryBytes(receipt) - return abci.NewResultOK(receiptBytes, "Success") -} - -// Implements manager/types.Application -// Commit the state (called at end of block) -// NOTE: CheckTx/AppendTx must not run concurrently with Commit - -// the mempool should run during AppendTxs, but lock for Commit and Update -func (app *BurrowMint) Commit() (res abci.Result) { - app.mtx.Lock() // the lock protects app.state - defer app.mtx.Unlock() - - app.state.LastBlockHeight += 1 - logging.InfoMsg(app.logger, "Committing block", - "last_block_height", app.state.LastBlockHeight) - - // sync the AppendTx cache - app.cache.Sync() - - // Refresh the checkCache with the latest commited state - logging.InfoMsg(app.logger, "Resetting checkCache", - "txs", app.nTxs) - app.checkCache = sm.NewBlockCache(app.state) - - app.nTxs = 0 - - // save state to disk - app.state.Save() - - // flush events to listeners (XXX: note issue with blocking) - app.evc.Flush() - - // TODO: [ben] over the tendermint 0.6 TMSP interface we have - // no access to the block header implemented; - // On Tendermint v0.8 load the blockheader into the application - // state and remove the fixed 2-"seconds" per block internal clock. - // NOTE: set internal time as two seconds per block - app.state.LastBlockTime = app.state.LastBlockTime.Add(time.Duration(2) * time.Second) - appHash := app.state.Hash() - return abci.NewResultOK(appHash, "Success") -} - -func (app *BurrowMint) Query(query abci.RequestQuery) (res abci.ResponseQuery) { - return abci.ResponseQuery{ - Code: abci.CodeType_OK, - Log: "success", - } -} - -// BlockchainAware interface - -// Initialise the blockchain -// validators: genesis validators from tendermint core -func (app *BurrowMint) InitChain(validators []*abci.Validator) { - // Could verify agreement on initial validator set here -} - -// Signals the beginning of a block -func (app *BurrowMint) BeginBlock(hash []byte, header *abci.Header) { - -} - -// Signals the end of a blockchain, return value can be used to modify validator -// set and voting power distribution see our BlockchainAware interface -func (app *BurrowMint) EndBlock(height uint64) (respEndblock abci.ResponseEndBlock) { - // TODO: [Silas] Bondage - // TODO: [Silas] this might be a better place for us to dispatch new block - // events particularly if we want to separate ourselves from go-events - return -} diff --git a/manager/burrow-mint/evm/fake_app_state.go b/manager/burrow-mint/evm/fake_app_state.go deleted file mode 100644 index c9b1e747..00000000 --- a/manager/burrow-mint/evm/fake_app_state.go +++ /dev/null @@ -1,94 +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 vm - -import ( - "fmt" - - "github.com/hyperledger/burrow/manager/burrow-mint/evm/sha3" - . "github.com/hyperledger/burrow/word256" -) - -type FakeAppState struct { - accounts map[string]*Account - storage map[string]Word256 -} - -func (fas *FakeAppState) GetAccount(addr Word256) *Account { - account := fas.accounts[addr.String()] - return account -} - -func (fas *FakeAppState) UpdateAccount(account *Account) { - fas.accounts[account.Address.String()] = account -} - -func (fas *FakeAppState) RemoveAccount(account *Account) { - _, ok := fas.accounts[account.Address.String()] - if !ok { - panic(fmt.Sprintf("Invalid account addr: %X", account.Address)) - } else { - // Remove account - delete(fas.accounts, account.Address.String()) - } -} - -func (fas *FakeAppState) CreateAccount(creator *Account) *Account { - addr := createAddress(creator) - account := fas.accounts[addr.String()] - if account == nil { - return &Account{ - Address: addr, - Balance: 0, - Code: nil, - Nonce: 0, - } - } else { - panic(fmt.Sprintf("Invalid account addr: %X", addr)) - } -} - -func (fas *FakeAppState) GetStorage(addr Word256, key Word256) Word256 { - _, ok := fas.accounts[addr.String()] - if !ok { - panic(fmt.Sprintf("Invalid account addr: %X", addr)) - } - - value, ok := fas.storage[addr.String()+key.String()] - if ok { - return value - } else { - return Zero256 - } -} - -func (fas *FakeAppState) SetStorage(addr Word256, key Word256, value Word256) { - _, ok := fas.accounts[addr.String()] - if !ok { - panic(fmt.Sprintf("Invalid account addr: %X", addr)) - } - - fas.storage[addr.String()+key.String()] = value -} - -// Creates a 20 byte address and bumps the nonce. -func createAddress(creator *Account) Word256 { - nonce := creator.Nonce - creator.Nonce += 1 - temp := make([]byte, 32+8) - copy(temp, creator.Address[:]) - PutInt64BE(temp[32:], nonce) - return LeftPadWord256(sha3.Sha3(temp)[:20]) -} diff --git a/manager/burrow-mint/evm/types.go b/manager/burrow-mint/evm/types.go deleted file mode 100644 index f083812e..00000000 --- a/manager/burrow-mint/evm/types.go +++ /dev/null @@ -1,65 +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 vm - -import ( - "fmt" - - ptypes "github.com/hyperledger/burrow/permission/types" - . "github.com/hyperledger/burrow/word256" -) - -const ( - defaultDataStackCapacity = 10 -) - -type Account struct { - Address Word256 - Balance int64 - Code []byte - Nonce int64 - Other interface{} // For holding all other data. - - Permissions ptypes.AccountPermissions -} - -func (acc *Account) String() string { - if acc == nil { - return "nil-VMAccount" - } - return fmt.Sprintf("VMAccount{%X B:%v C:%X N:%v}", - acc.Address, acc.Balance, acc.Code, acc.Nonce) -} - -type AppState interface { - - // Accounts - GetAccount(addr Word256) *Account - UpdateAccount(*Account) - RemoveAccount(*Account) - CreateAccount(*Account) *Account - - // Storage - GetStorage(Word256, Word256) Word256 - SetStorage(Word256, Word256, Word256) // Setting to Zero is deleting. - -} - -type Params struct { - BlockHeight int64 - BlockHash Word256 - BlockTime int64 - GasLimit int64 -} diff --git a/manager/burrow-mint/pipe.go b/manager/burrow-mint/pipe.go deleted file mode 100644 index cb91fb2a..00000000 --- a/manager/burrow-mint/pipe.go +++ /dev/null @@ -1,677 +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 burrowmint - -import ( - "bytes" - "fmt" - - abci_types "github.com/tendermint/abci/types" - crypto "github.com/tendermint/go-crypto" - db "github.com/tendermint/go-db" - go_events "github.com/tendermint/go-events" - wire "github.com/tendermint/go-wire" - tm_types "github.com/tendermint/tendermint/types" - - "github.com/hyperledger/burrow/account" - blockchain_types "github.com/hyperledger/burrow/blockchain/types" - imath "github.com/hyperledger/burrow/common/math/integral" - "github.com/hyperledger/burrow/config" - consensus_types "github.com/hyperledger/burrow/consensus/types" - core_types "github.com/hyperledger/burrow/core/types" - "github.com/hyperledger/burrow/definitions" - edb_event "github.com/hyperledger/burrow/event" - genesis "github.com/hyperledger/burrow/genesis" - "github.com/hyperledger/burrow/logging" - logging_types "github.com/hyperledger/burrow/logging/types" - vm "github.com/hyperledger/burrow/manager/burrow-mint/evm" - "github.com/hyperledger/burrow/manager/burrow-mint/state" - manager_types "github.com/hyperledger/burrow/manager/types" - rpc_tm_types "github.com/hyperledger/burrow/rpc/tendermint/core/types" - "github.com/hyperledger/burrow/txs" - "github.com/hyperledger/burrow/word256" -) - -type burrowMintPipe struct { - burrowMintState *state.State - burrowMint *BurrowMint - // Pipe implementations - accounts *accounts - blockchain blockchain_types.Blockchain - consensusEngine consensus_types.ConsensusEngine - events edb_event.EventEmitter - namereg *namereg - transactor *transactor - // Genesis cache - genesisDoc *genesis.GenesisDoc - genesisState *state.State - logger logging_types.InfoTraceLogger -} - -// Interface type assertions -var _ definitions.Pipe = (*burrowMintPipe)(nil) - -var _ definitions.TendermintPipe = (*burrowMintPipe)(nil) - -func NewBurrowMintPipe(moduleConfig *config.ModuleConfig, - eventSwitch go_events.EventSwitch, - logger logging_types.InfoTraceLogger) (*burrowMintPipe, error) { - - startedState, genesisDoc, err := startState(moduleConfig.DataDir, - moduleConfig.Config.GetString("db_backend"), moduleConfig.GenesisFile, - moduleConfig.ChainId) - if err != nil { - return nil, fmt.Errorf("Failed to start state: %v", err) - } - logger = logging.WithScope(logger, "BurrowMintPipe") - // assert ChainId matches genesis ChainId - logging.InfoMsg(logger, "Loaded state", - "chainId", startedState.ChainID, - "lastBlockHeight", startedState.LastBlockHeight, - "lastBlockHash", startedState.LastBlockHash) - // start the application - burrowMint := NewBurrowMint(startedState, eventSwitch, logger) - - // initialise the components of the pipe - events := edb_event.NewEvents(eventSwitch, logger) - accounts := newAccounts(burrowMint) - namereg := newNameReg(burrowMint) - - pipe := &burrowMintPipe{ - burrowMintState: startedState, - burrowMint: burrowMint, - accounts: accounts, - events: events, - namereg: namereg, - // We need to set transactor later since we are introducing a mutual dependency - // NOTE: this will be cleaned up when the RPC is unified - transactor: nil, - // genesis cache - genesisDoc: genesisDoc, - genesisState: nil, - // consensus and blockchain should both be loaded into the pipe by a higher - // authority - this is a sort of dependency injection pattern - consensusEngine: nil, - blockchain: nil, - logger: logger, - } - - // NOTE: [Silas] - // This is something of a loopback, but seems like a nicer option than - // transactor calling the Tendermint native RPC (as it was before), - // or indeed calling this RPC over the wire given that we have direct access. - // - // We could just hand transactor a copy of Pipe, but doing it this way seems - // like a reasonably minimal and flexible way of providing transactor with the - // broadcast function it needs, without making it explicitly - // aware of/depend on Pipe. - transactor := newTransactor(moduleConfig.ChainId, eventSwitch, burrowMint, - events, - func(tx txs.Tx) error { - _, err := pipe.BroadcastTxSync(tx) - return err - }) - - pipe.transactor = transactor - return pipe, nil -} - -//------------------------------------------------------------------------------ -// Start state - -// Start state tries to load the existing state in the data directory; -// if an existing database can be loaded, it will validate that the -// chainId in the genesis of that loaded state matches the asserted chainId. -// If no state can be loaded, the JSON genesis file will be loaded into the -// state database as the zero state. -func startState(dataDir, backend, genesisFile, chainId string) (*state.State, - *genesis.GenesisDoc, error) { - // avoid Tendermints PanicSanity and return a clean error - if backend != db.MemDBBackendStr && - backend != db.LevelDBBackendStr { - return nil, nil, fmt.Errorf("Database backend %s is not supported "+ - "by burrowmint", backend) - } - - stateDB := db.NewDB("burrowmint", backend, dataDir) - newState := state.LoadState(stateDB) - var genesisDoc *genesis.GenesisDoc - if newState == nil { - genesisDoc, newState = state.MakeGenesisStateFromFile(stateDB, genesisFile) - newState.Save() - buf, n, err := new(bytes.Buffer), new(int), new(error) - wire.WriteJSON(genesisDoc, buf, n, err) - stateDB.Set(genesis.GenDocKey, buf.Bytes()) - if *err != nil { - return nil, nil, fmt.Errorf("Unable to write genesisDoc to db: %v", err) - } - } else { - loadedGenesisDocBytes := stateDB.Get(genesis.GenDocKey) - err := new(error) - wire.ReadJSONPtr(&genesisDoc, loadedGenesisDocBytes, err) - if *err != nil { - return nil, nil, fmt.Errorf("Unable to read genesisDoc from db on startState: %v", err) - } - // assert loaded genesis doc has the same chainId as the provided chainId - if genesisDoc.ChainID != chainId { - return nil, nil, fmt.Errorf("ChainId (%s) loaded from genesis document in existing database does not match"+ - " configuration chainId (%s).", genesisDoc.ChainID, chainId) - } - } - - return newState, genesisDoc, nil -} - -//------------------------------------------------------------------------------ -// Implement definitions.Pipe for burrowMintPipe - -func (pipe *burrowMintPipe) Logger() logging_types.InfoTraceLogger { - return pipe.logger -} - -func (pipe *burrowMintPipe) Accounts() definitions.Accounts { - return pipe.accounts -} - -func (pipe *burrowMintPipe) Blockchain() blockchain_types.Blockchain { - return pipe.blockchain -} - -func (pipe *burrowMintPipe) Events() edb_event.EventEmitter { - return pipe.events -} - -func (pipe *burrowMintPipe) NameReg() definitions.NameReg { - return pipe.namereg -} - -func (pipe *burrowMintPipe) Transactor() definitions.Transactor { - return pipe.transactor -} - -func (pipe *burrowMintPipe) GetApplication() manager_types.Application { - return pipe.burrowMint -} - -func (pipe *burrowMintPipe) SetBlockchain( - blockchain blockchain_types.Blockchain) error { - if pipe.blockchain == nil { - pipe.blockchain = blockchain - } else { - return fmt.Errorf("Failed to set Blockchain for pipe; already set") - } - return nil -} - -func (pipe *burrowMintPipe) GetBlockchain() blockchain_types.Blockchain { - return pipe.blockchain -} - -func (pipe *burrowMintPipe) SetConsensusEngine( - consensusEngine consensus_types.ConsensusEngine) error { - if pipe.consensusEngine == nil { - pipe.consensusEngine = consensusEngine - } else { - return fmt.Errorf("Failed to set consensus engine for pipe; already set") - } - return nil -} - -func (pipe *burrowMintPipe) GetConsensusEngine() consensus_types.ConsensusEngine { - return pipe.consensusEngine -} - -func (pipe *burrowMintPipe) GetTendermintPipe() (definitions.TendermintPipe, - error) { - return definitions.TendermintPipe(pipe), nil -} - -func (pipe *burrowMintPipe) consensusAndManagerEvents() edb_event.EventEmitter { - // NOTE: [Silas] We could initialise this lazily and use the cached instance, - // but for the time being that feels like a premature optimisation - return edb_event.Multiplex(pipe.events, pipe.consensusEngine.Events()) -} - -//------------------------------------------------------------------------------ -// Implement definitions.TendermintPipe for burrowMintPipe -func (pipe *burrowMintPipe) Subscribe(eventId string, - rpcResponseWriter func(result rpc_tm_types.BurrowResult)) (*rpc_tm_types.ResultSubscribe, error) { - subscriptionId, err := edb_event.GenerateSubId() - if err != nil { - return nil, err - logging.InfoMsg(pipe.logger, "Subscribing to event", - "eventId", eventId, "subscriptionId", subscriptionId) - } - pipe.consensusAndManagerEvents().Subscribe(subscriptionId, eventId, - func(eventData txs.EventData) { - result := rpc_tm_types.BurrowResult( - &rpc_tm_types.ResultEvent{ - Event: eventId, - Data: txs.EventData(eventData)}) - // NOTE: EventSwitch callbacks must be nonblocking - rpcResponseWriter(result) - }) - return &rpc_tm_types.ResultSubscribe{ - SubscriptionId: subscriptionId, - Event: eventId, - }, nil -} - -func (pipe *burrowMintPipe) Unsubscribe(subscriptionId string) (*rpc_tm_types.ResultUnsubscribe, error) { - logging.InfoMsg(pipe.logger, "Unsubscribing from event", - "subscriptionId", subscriptionId) - pipe.consensusAndManagerEvents().Unsubscribe(subscriptionId) - return &rpc_tm_types.ResultUnsubscribe{SubscriptionId: subscriptionId}, nil -} -func (pipe *burrowMintPipe) GenesisState() *state.State { - if pipe.genesisState == nil { - memoryDatabase := db.NewMemDB() - pipe.genesisState = state.MakeGenesisState(memoryDatabase, pipe.genesisDoc) - } - return pipe.genesisState -} - -func (pipe *burrowMintPipe) GenesisHash() []byte { - return pipe.GenesisState().Hash() -} - -func (pipe *burrowMintPipe) Status() (*rpc_tm_types.ResultStatus, error) { - if pipe.consensusEngine == nil { - return nil, fmt.Errorf("Consensus Engine not initialised in burrowmint pipe.") - } - latestHeight := pipe.blockchain.Height() - var ( - latestBlockMeta *tm_types.BlockMeta - latestBlockHash []byte - latestBlockTime int64 - ) - if latestHeight != 0 { - latestBlockMeta = pipe.blockchain.BlockMeta(latestHeight) - latestBlockHash = latestBlockMeta.Header.Hash() - latestBlockTime = latestBlockMeta.Header.Time.UnixNano() - } - return &rpc_tm_types.ResultStatus{ - NodeInfo: pipe.consensusEngine.NodeInfo(), - GenesisHash: pipe.GenesisHash(), - PubKey: pipe.consensusEngine.PublicValidatorKey(), - LatestBlockHash: latestBlockHash, - LatestBlockHeight: latestHeight, - LatestBlockTime: latestBlockTime}, nil -} - -func (pipe *burrowMintPipe) ChainId() (*rpc_tm_types.ResultChainId, error) { - if pipe.blockchain == nil { - return nil, fmt.Errorf("Blockchain not initialised in burrowmint pipe.") - } - chainId := pipe.blockchain.ChainId() - - return &rpc_tm_types.ResultChainId{ - ChainName: chainId, // MARMOT: copy ChainId for ChainName as a placehodlder - ChainId: chainId, - GenesisHash: pipe.GenesisHash(), - }, nil -} - -func (pipe *burrowMintPipe) NetInfo() (*rpc_tm_types.ResultNetInfo, error) { - listening := pipe.consensusEngine.IsListening() - listeners := []string{} - for _, listener := range pipe.consensusEngine.Listeners() { - listeners = append(listeners, listener.String()) - } - peers := pipe.consensusEngine.Peers() - return &rpc_tm_types.ResultNetInfo{ - Listening: listening, - Listeners: listeners, - Peers: peers, - }, nil -} - -func (pipe *burrowMintPipe) Genesis() (*rpc_tm_types.ResultGenesis, error) { - return &rpc_tm_types.ResultGenesis{ - // TODO: [ben] sharing pointer to unmutated GenesisDoc, but is not immutable - Genesis: pipe.genesisDoc, - }, nil -} - -// Accounts -func (pipe *burrowMintPipe) GetAccount(address []byte) (*rpc_tm_types.ResultGetAccount, - error) { - cache := pipe.burrowMint.GetCheckCache() - account := cache.GetAccount(address) - return &rpc_tm_types.ResultGetAccount{Account: account}, nil -} - -func (pipe *burrowMintPipe) ListAccounts() (*rpc_tm_types.ResultListAccounts, error) { - var blockHeight int - var accounts []*account.Account - state := pipe.burrowMint.GetState() - blockHeight = state.LastBlockHeight - state.GetAccounts().Iterate(func(key []byte, value []byte) bool { - accounts = append(accounts, account.DecodeAccount(value)) - return false - }) - return &rpc_tm_types.ResultListAccounts{blockHeight, accounts}, nil -} - -func (pipe *burrowMintPipe) GetStorage(address, key []byte) (*rpc_tm_types.ResultGetStorage, - error) { - state := pipe.burrowMint.GetState() - // state := consensusState.GetState() - account := state.GetAccount(address) - if account == nil { - return nil, fmt.Errorf("UnknownAddress: %X", address) - } - storageRoot := account.StorageRoot - storageTree := state.LoadStorage(storageRoot) - - _, value, exists := storageTree.Get( - word256.LeftPadWord256(key).Bytes()) - if !exists { - // value == nil { - return &rpc_tm_types.ResultGetStorage{key, nil}, nil - } - return &rpc_tm_types.ResultGetStorage{key, value}, nil -} - -func (pipe *burrowMintPipe) DumpStorage(address []byte) (*rpc_tm_types.ResultDumpStorage, - error) { - state := pipe.burrowMint.GetState() - account := state.GetAccount(address) - if account == nil { - return nil, fmt.Errorf("UnknownAddress: %X", address) - } - storageRoot := account.StorageRoot - storageTree := state.LoadStorage(storageRoot) - storageItems := []rpc_tm_types.StorageItem{} - storageTree.Iterate(func(key []byte, value []byte) bool { - storageItems = append(storageItems, rpc_tm_types.StorageItem{key, - value}) - return false - }) - return &rpc_tm_types.ResultDumpStorage{storageRoot, storageItems}, nil -} - -// Call -// NOTE: this function is used from 46657 and has sibling on 1337 -// in transactor.go -// TODO: [ben] resolve incompatibilities in byte representation for 0.12.0 release -func (pipe *burrowMintPipe) Call(fromAddress, toAddress, data []byte) (*rpc_tm_types.ResultCall, - error) { - if vm.RegisteredNativeContract(word256.LeftPadWord256(toAddress)) { - 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) - } - st := pipe.burrowMint.GetState() - cache := state.NewBlockCache(st) - outAcc := cache.GetAccount(toAddress) - if outAcc == nil { - return nil, fmt.Errorf("Account %x does not exist", toAddress) - } - if fromAddress == nil { - fromAddress = []byte{} - } - callee := toVMAccount(outAcc) - caller := &vm.Account{Address: word256.LeftPadWord256(fromAddress)} - txCache := state.NewTxCache(cache) - gasLimit := st.GetGasLimit() - params := vm.Params{ - BlockHeight: int64(st.LastBlockHeight), - BlockHash: word256.LeftPadWord256(st.LastBlockHash), - BlockTime: st.LastBlockTime.Unix(), - GasLimit: gasLimit, - } - - vmach := vm.NewVM(txCache, vm.DefaultDynamicMemoryProvider, params, - caller.Address, nil) - gas := gasLimit - ret, err := vmach.Call(caller, callee, callee.Code, data, 0, &gas) - if err != nil { - return nil, err - } - gasUsed := gasLimit - gas - // here return bytes are not hex encoded; on the sibling function - // they are - return &rpc_tm_types.ResultCall{Return: ret, GasUsed: gasUsed}, nil -} - -func (pipe *burrowMintPipe) CallCode(fromAddress, code, data []byte) (*rpc_tm_types.ResultCall, - error) { - st := pipe.burrowMint.GetState() - cache := pipe.burrowMint.GetCheckCache() - callee := &vm.Account{Address: word256.LeftPadWord256(fromAddress)} - caller := &vm.Account{Address: word256.LeftPadWord256(fromAddress)} - txCache := state.NewTxCache(cache) - gasLimit := st.GetGasLimit() - params := vm.Params{ - BlockHeight: int64(st.LastBlockHeight), - BlockHash: word256.LeftPadWord256(st.LastBlockHash), - BlockTime: st.LastBlockTime.Unix(), - GasLimit: gasLimit, - } - - vmach := vm.NewVM(txCache, vm.DefaultDynamicMemoryProvider, params, - caller.Address, nil) - gas := gasLimit - ret, err := vmach.Call(caller, callee, code, data, 0, &gas) - if err != nil { - return nil, err - } - gasUsed := gasLimit - gas - return &rpc_tm_types.ResultCall{Return: ret, GasUsed: gasUsed}, nil -} - -// TODO: [ben] deprecate as we should not allow unsafe behaviour -// where a user is allowed to send a private key over the wire, -// especially unencrypted. -func (pipe *burrowMintPipe) SignTransaction(tx txs.Tx, - privAccounts []*account.PrivAccount) (*rpc_tm_types.ResultSignTx, - error) { - - for i, privAccount := range privAccounts { - if privAccount == nil || privAccount.PrivKey == nil { - return nil, fmt.Errorf("Invalid (empty) privAccount @%v", i) - } - } - switch tx.(type) { - case *txs.SendTx: - sendTx := tx.(*txs.SendTx) - for i, input := range sendTx.Inputs { - input.PubKey = privAccounts[i].PubKey - input.Signature = privAccounts[i].Sign(pipe.transactor.chainID, sendTx) - } - case *txs.CallTx: - callTx := tx.(*txs.CallTx) - callTx.Input.PubKey = privAccounts[0].PubKey - callTx.Input.Signature = privAccounts[0].Sign(pipe.transactor.chainID, callTx) - case *txs.BondTx: - bondTx := tx.(*txs.BondTx) - // the first privaccount corresponds to the BondTx pub key. - // the rest to the inputs - bondTx.Signature = privAccounts[0].Sign(pipe.transactor.chainID, bondTx).(crypto.SignatureEd25519) - for i, input := range bondTx.Inputs { - input.PubKey = privAccounts[i+1].PubKey - input.Signature = privAccounts[i+1].Sign(pipe.transactor.chainID, bondTx) - } - case *txs.UnbondTx: - unbondTx := tx.(*txs.UnbondTx) - unbondTx.Signature = privAccounts[0].Sign(pipe.transactor.chainID, unbondTx).(crypto.SignatureEd25519) - case *txs.RebondTx: - rebondTx := tx.(*txs.RebondTx) - rebondTx.Signature = privAccounts[0].Sign(pipe.transactor.chainID, rebondTx).(crypto.SignatureEd25519) - } - return &rpc_tm_types.ResultSignTx{tx}, nil -} - -// Name registry -func (pipe *burrowMintPipe) GetName(name string) (*rpc_tm_types.ResultGetName, error) { - currentState := pipe.burrowMint.GetState() - entry := currentState.GetNameRegEntry(name) - if entry == nil { - return nil, fmt.Errorf("Name %s not found", name) - } - return &rpc_tm_types.ResultGetName{entry}, nil -} - -func (pipe *burrowMintPipe) ListNames() (*rpc_tm_types.ResultListNames, error) { - var blockHeight int - var names []*core_types.NameRegEntry - currentState := pipe.burrowMint.GetState() - blockHeight = currentState.LastBlockHeight - currentState.GetNames().Iterate(func(key []byte, value []byte) bool { - names = append(names, state.DecodeNameRegEntry(value)) - return false - }) - return &rpc_tm_types.ResultListNames{blockHeight, names}, nil -} - -func (pipe *burrowMintPipe) broadcastTx(tx txs.Tx, - callback func(res *abci_types.Response)) (*rpc_tm_types.ResultBroadcastTx, error) { - - txBytes, err := txs.EncodeTx(tx) - if err != nil { - return nil, fmt.Errorf("Error encoding transaction: %v", err) - } - err = pipe.consensusEngine.BroadcastTransaction(txBytes, callback) - if err != nil { - return nil, fmt.Errorf("Error broadcasting transaction: %v", err) - } - return &rpc_tm_types.ResultBroadcastTx{}, nil -} - -// Memory pool -// NOTE: txs must be signed -func (pipe *burrowMintPipe) BroadcastTxAsync(tx txs.Tx) (*rpc_tm_types.ResultBroadcastTx, error) { - return pipe.broadcastTx(tx, nil) -} - -func (pipe *burrowMintPipe) BroadcastTxSync(tx txs.Tx) (*rpc_tm_types.ResultBroadcastTx, error) { - responseChannel := make(chan *abci_types.Response, 1) - _, err := pipe.broadcastTx(tx, - func(res *abci_types.Response) { - responseChannel <- res - }) - if err != nil { - return nil, err - } - // NOTE: [ben] This Response is set in /consensus/tendermint/local_client.go - // a call to Application, here implemented by BurrowMint, over local callback, - // or abci RPC call. Hence the result is determined by BurrowMint/burrowmint.go - // CheckTx() Result (Result converted to ReqRes into Response returned here) - // NOTE: [ben] BroadcastTx just calls CheckTx in Tendermint (oddly... [Silas]) - response := <-responseChannel - responseCheckTx := response.GetCheckTx() - if responseCheckTx == nil { - return nil, fmt.Errorf("Error, application did not return CheckTx response.") - } - resultBroadCastTx := &rpc_tm_types.ResultBroadcastTx{ - Code: responseCheckTx.Code, - Data: responseCheckTx.Data, - Log: responseCheckTx.Log, - } - switch responseCheckTx.Code { - case abci_types.CodeType_OK: - return resultBroadCastTx, nil - case abci_types.CodeType_EncodingError: - return resultBroadCastTx, fmt.Errorf(resultBroadCastTx.Log) - case abci_types.CodeType_InternalError: - return resultBroadCastTx, fmt.Errorf(resultBroadCastTx.Log) - default: - logging.InfoMsg(pipe.logger, "Unknown error returned from Tendermint CheckTx on BroadcastTxSync", - "application", "burrowmint", - "abci_code_type", responseCheckTx.Code, - "abci_log", responseCheckTx.Log, - ) - return resultBroadCastTx, fmt.Errorf("Unknown error returned: " + responseCheckTx.Log) - } -} - -func (pipe *burrowMintPipe) ListUnconfirmedTxs(maxTxs int) (*rpc_tm_types.ResultListUnconfirmedTxs, error) { - // Get all transactions for now - transactions, err := pipe.consensusEngine.ListUnconfirmedTxs(maxTxs) - if err != nil { - return nil, err - } - return &rpc_tm_types.ResultListUnconfirmedTxs{ - N: len(transactions), - Txs: transactions, - }, nil -} - -// Returns the current blockchain height and metadata for a range of blocks -// between minHeight and maxHeight. Only returns maxBlockLookback block metadata -// from the top of the range of blocks. -// Passing 0 for maxHeight sets the upper height of the range to the current -// blockchain height. -func (pipe *burrowMintPipe) BlockchainInfo(minHeight, maxHeight, - maxBlockLookback int) (*rpc_tm_types.ResultBlockchainInfo, error) { - - latestHeight := pipe.blockchain.Height() - - if maxHeight < 1 { - maxHeight = latestHeight - } else { - maxHeight = imath.MinInt(latestHeight, maxHeight) - } - if minHeight < 1 { - minHeight = imath.MaxInt(1, maxHeight-maxBlockLookback) - } - - blockMetas := []*tm_types.BlockMeta{} - for height := maxHeight; height >= minHeight; height-- { - blockMeta := pipe.blockchain.BlockMeta(height) - blockMetas = append(blockMetas, blockMeta) - } - - return &rpc_tm_types.ResultBlockchainInfo{ - LastHeight: latestHeight, - BlockMetas: blockMetas, - }, nil -} - -func (pipe *burrowMintPipe) GetBlock(height int) (*rpc_tm_types.ResultGetBlock, error) { - return &rpc_tm_types.ResultGetBlock{ - Block: pipe.blockchain.Block(height), - BlockMeta: pipe.blockchain.BlockMeta(height), - }, nil -} - -func (pipe *burrowMintPipe) ListValidators() (*rpc_tm_types.ResultListValidators, error) { - validators := pipe.consensusEngine.ListValidators() - consensusState := pipe.consensusEngine.ConsensusState() - // TODO: when we reintroduce support for bonding and unbonding update this - // to reflect the mutable bonding state - return &rpc_tm_types.ResultListValidators{ - BlockHeight: consensusState.Height, - BondedValidators: validators, - UnbondingValidators: nil, - }, nil -} - -func (pipe *burrowMintPipe) DumpConsensusState() (*rpc_tm_types.ResultDumpConsensusState, error) { - statesMap := pipe.consensusEngine.PeerConsensusStates() - peerStates := make([]*rpc_tm_types.ResultPeerConsensusState, len(statesMap)) - for key, peerState := range statesMap { - peerStates = append(peerStates, &rpc_tm_types.ResultPeerConsensusState{ - PeerKey: key, - PeerConsensusState: peerState, - }) - } - dump := rpc_tm_types.ResultDumpConsensusState{ - ConsensusState: pipe.consensusEngine.ConsensusState(), - PeerConsensusStates: peerStates, - } - return &dump, nil -} diff --git a/manager/burrow-mint/state/block_cache.go b/manager/burrow-mint/state/block_cache.go deleted file mode 100644 index 3ba8a456..00000000 --- a/manager/burrow-mint/state/block_cache.go +++ /dev/null @@ -1,304 +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 state - -import ( - "bytes" - "fmt" - "sort" - - acm "github.com/hyperledger/burrow/account" - "github.com/hyperledger/burrow/common/sanity" - core_types "github.com/hyperledger/burrow/core/types" - . "github.com/hyperledger/burrow/word256" - - dbm "github.com/tendermint/go-db" - "github.com/tendermint/go-merkle" -) - -func makeStorage(db dbm.DB, root []byte) merkle.Tree { - storage := merkle.NewIAVLTree(1024, db) - storage.Load(root) - return storage -} - -// The blockcache helps prevent unnecessary IAVLTree updates and garbage generation. -type BlockCache struct { - db dbm.DB - backend *State - accounts map[string]accountInfo - storages map[Tuple256]storageInfo - names map[string]nameInfo -} - -func NewBlockCache(backend *State) *BlockCache { - return &BlockCache{ - db: backend.DB, - backend: backend, - accounts: make(map[string]accountInfo), - storages: make(map[Tuple256]storageInfo), - names: make(map[string]nameInfo), - } -} - -func (cache *BlockCache) State() *State { - return cache.backend -} - -//------------------------------------- -// BlockCache.account - -func (cache *BlockCache) GetAccount(addr []byte) *acm.Account { - acc, _, removed, _ := cache.accounts[string(addr)].unpack() - if removed { - return nil - } else if acc != nil { - return acc - } else { - acc = cache.backend.GetAccount(addr) - cache.accounts[string(addr)] = accountInfo{acc, nil, false, false} - return acc - } -} - -func (cache *BlockCache) UpdateAccount(acc *acm.Account) { - addr := acc.Address - _, storage, removed, _ := cache.accounts[string(addr)].unpack() - if removed { - sanity.PanicSanity("UpdateAccount on a removed account") - } - cache.accounts[string(addr)] = accountInfo{acc, storage, false, true} -} - -func (cache *BlockCache) RemoveAccount(addr []byte) { - _, _, removed, _ := cache.accounts[string(addr)].unpack() - if removed { - sanity.PanicSanity("RemoveAccount on a removed account") - } - cache.accounts[string(addr)] = accountInfo{nil, nil, true, false} -} - -// BlockCache.account -//------------------------------------- -// BlockCache.storage - -func (cache *BlockCache) GetStorage(addr Word256, key Word256) (value Word256) { - // Check cache - info, ok := cache.storages[Tuple256{addr, key}] - if ok { - return info.value - } - - // Get or load storage - acc, storage, removed, dirty := cache.accounts[string(addr.Postfix(20))].unpack() - if removed { - sanity.PanicSanity("GetStorage() on removed account") - } - if acc != nil && storage == nil { - storage = makeStorage(cache.db, acc.StorageRoot) - cache.accounts[string(addr.Postfix(20))] = accountInfo{acc, storage, false, dirty} - } else if acc == nil { - return Zero256 - } - - // Load and set cache - _, val_, _ := storage.Get(key.Bytes()) - value = Zero256 - if val_ != nil { - value = LeftPadWord256(val_) - } - cache.storages[Tuple256{addr, key}] = storageInfo{value, false} - return value -} - -// NOTE: Set value to zero to removed from the trie. -func (cache *BlockCache) SetStorage(addr Word256, key Word256, value Word256) { - _, _, removed, _ := cache.accounts[string(addr.Postfix(20))].unpack() - if removed { - sanity.PanicSanity("SetStorage() on a removed account") - } - cache.storages[Tuple256{addr, key}] = storageInfo{value, true} -} - -// BlockCache.storage -//------------------------------------- -// BlockCache.names - -func (cache *BlockCache) GetNameRegEntry(name string) *core_types.NameRegEntry { - entry, removed, _ := cache.names[name].unpack() - if removed { - return nil - } else if entry != nil { - return entry - } else { - entry = cache.backend.GetNameRegEntry(name) - cache.names[name] = nameInfo{entry, false, false} - return entry - } -} - -func (cache *BlockCache) UpdateNameRegEntry(entry *core_types.NameRegEntry) { - name := entry.Name - cache.names[name] = nameInfo{entry, false, true} -} - -func (cache *BlockCache) RemoveNameRegEntry(name string) { - _, removed, _ := cache.names[name].unpack() - if removed { - sanity.PanicSanity("RemoveNameRegEntry on a removed entry") - } - cache.names[name] = nameInfo{nil, true, false} -} - -// BlockCache.names -//------------------------------------- - -// CONTRACT the updates are in deterministic order. -func (cache *BlockCache) Sync() { - - // Determine order for storage updates - // The address comes first so it'll be grouped. - storageKeys := make([]Tuple256, 0, len(cache.storages)) - for keyTuple := range cache.storages { - storageKeys = append(storageKeys, keyTuple) - } - Tuple256Slice(storageKeys).Sort() - - // Update storage for all account/key. - // Later we'll iterate over all the users and save storage + update storage root. - var ( - curAddr Word256 - curAcc *acm.Account - curAccRemoved bool - curStorage merkle.Tree - ) - for _, storageKey := range storageKeys { - addr, key := Tuple256Split(storageKey) - if addr != curAddr || curAcc == nil { - acc, storage, removed, _ := cache.accounts[string(addr.Postfix(20))].unpack() - if !removed && storage == nil { - storage = makeStorage(cache.db, acc.StorageRoot) - } - curAddr = addr - curAcc = acc - curAccRemoved = removed - curStorage = storage - } - if curAccRemoved { - continue - } - value, dirty := cache.storages[storageKey].unpack() - if !dirty { - continue - } - if value.IsZero() { - curStorage.Remove(key.Bytes()) - } else { - curStorage.Set(key.Bytes(), value.Bytes()) - cache.accounts[string(addr.Postfix(20))] = accountInfo{curAcc, curStorage, false, true} - } - } - - // Determine order for accounts - addrStrs := []string{} - for addrStr := range cache.accounts { - addrStrs = append(addrStrs, addrStr) - } - sort.Strings(addrStrs) - - // Update or delete accounts. - for _, addrStr := range addrStrs { - acc, storage, removed, dirty := cache.accounts[addrStr].unpack() - if removed { - removed := cache.backend.RemoveAccount([]byte(addrStr)) - if !removed { - sanity.PanicCrisis(fmt.Sprintf("Could not remove account to be removed: %X", acc.Address)) - } - } else { - if acc == nil { - continue - } - if storage != nil { - newStorageRoot := storage.Save() - if !bytes.Equal(newStorageRoot, acc.StorageRoot) { - acc.StorageRoot = newStorageRoot - dirty = true - } - } - if dirty { - cache.backend.UpdateAccount(acc) - } - } - } - - // Determine order for names - // note names may be of any length less than some limit - nameStrs := []string{} - for nameStr := range cache.names { - nameStrs = append(nameStrs, nameStr) - } - sort.Strings(nameStrs) - - // Update or delete names. - for _, nameStr := range nameStrs { - entry, removed, dirty := cache.names[nameStr].unpack() - if removed { - removed := cache.backend.RemoveNameRegEntry(nameStr) - if !removed { - sanity.PanicCrisis(fmt.Sprintf("Could not remove namereg entry to be removed: %s", nameStr)) - } - } else { - if entry == nil { - continue - } - if dirty { - cache.backend.UpdateNameRegEntry(entry) - } - } - } - -} - -//----------------------------------------------------------------------------- - -type accountInfo struct { - account *acm.Account - storage merkle.Tree - removed bool - dirty bool -} - -func (accInfo accountInfo) unpack() (*acm.Account, merkle.Tree, bool, bool) { - return accInfo.account, accInfo.storage, accInfo.removed, accInfo.dirty -} - -type storageInfo struct { - value Word256 - dirty bool -} - -func (stjInfo storageInfo) unpack() (Word256, bool) { - return stjInfo.value, stjInfo.dirty -} - -type nameInfo struct { - name *core_types.NameRegEntry - removed bool - dirty bool -} - -func (nInfo nameInfo) unpack() (*core_types.NameRegEntry, bool, bool) { - return nInfo.name, nInfo.removed, nInfo.dirty -} diff --git a/manager/burrow-mint/state/common.go b/manager/burrow-mint/state/common.go deleted file mode 100644 index 37cf6ca1..00000000 --- a/manager/burrow-mint/state/common.go +++ /dev/null @@ -1,32 +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 state - -import ( - acm "github.com/hyperledger/burrow/account" - "github.com/hyperledger/burrow/manager/burrow-mint/evm" - . "github.com/hyperledger/burrow/word256" -) - -type AccountGetter interface { - GetAccount(addr []byte) *acm.Account -} - -type VMAccountState interface { - GetAccount(addr Word256) *vm.Account - UpdateAccount(acc *vm.Account) - RemoveAccount(acc *vm.Account) - CreateAccount(creator *vm.Account) *vm.Account -} diff --git a/manager/burrow-mint/state/genesis_test.go b/manager/burrow-mint/state/genesis_test.go deleted file mode 100644 index 983b5cb5..00000000 --- a/manager/burrow-mint/state/genesis_test.go +++ /dev/null @@ -1,172 +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 state - -import ( - "bytes" - "encoding/hex" - "fmt" - "sort" - "testing" - "time" - - acm "github.com/hyperledger/burrow/account" - "github.com/hyperledger/burrow/common/random" - genesis "github.com/hyperledger/burrow/genesis" - ptypes "github.com/hyperledger/burrow/permission/types" - - tdb "github.com/tendermint/go-db" - "github.com/tendermint/tendermint/types" -) - -var chain_id = "lone_ranger" -var addr1, _ = hex.DecodeString("964B1493BBE3312278B7DEB94C39149F7899A345") -var send1 = 1 -var perms, setbit = 66, 70 -var accName = "me" -var roles1 = []string{"master", "universal-ruler"} -var amt1 int64 = 1000000 -var g1 = fmt.Sprintf(` -{ - "chain_id":"%s", - "accounts": [ - { - "address": "%X", - "amount": %d, - "name": "%s", - "permissions": { - "base": { - "perms": %d, - "set": %d - }, - "roles": [ - "%s", - "%s" - ] - } - } - ], - "validators": [ - { - "amount": 100000000, - "pub_key": [1,"F6C79CF0CB9D66B677988BCB9B8EADD9A091CD465A60542A8AB85476256DBA92"], - "unbond_to": [ - { - "address": "964B1493BBE3312278B7DEB94C39149F7899A345", - "amount": 10000000 - } - ] - } - ] -} -`, chain_id, addr1, amt1, accName, perms, setbit, roles1[0], roles1[1]) - -func TestGenesisReadable(t *testing.T) { - genDoc := genesis.GenesisDocFromJSON([]byte(g1)) - if genDoc.ChainID != chain_id { - t.Fatalf("Incorrect chain id. Got %d, expected %d\n", genDoc.ChainID, chain_id) - } - acc := genDoc.Accounts[0] - if !bytes.Equal(acc.Address, addr1) { - t.Fatalf("Incorrect address for account. Got %X, expected %X\n", acc.Address, addr1) - } - if acc.Amount != amt1 { - t.Fatalf("Incorrect amount for account. Got %d, expected %d\n", acc.Amount, amt1) - } - if acc.Name != accName { - t.Fatalf("Incorrect name for account. Got %s, expected %s\n", acc.Name, accName) - } - - perm, _ := acc.Permissions.Base.Get(ptypes.Send) - if perm != (send1 > 0) { - t.Fatalf("Incorrect permission for send. Got %v, expected %v\n", perm, send1 > 0) - } -} - -func TestGenesisMakeState(t *testing.T) { - genDoc := genesis.GenesisDocFromJSON([]byte(g1)) - db := tdb.NewMemDB() - st := MakeGenesisState(db, genDoc) - acc := st.GetAccount(addr1) - v, _ := acc.Permissions.Base.Get(ptypes.Send) - if v != (send1 > 0) { - t.Fatalf("Incorrect permission for send. Got %v, expected %v\n", v, send1 > 0) - } -} - -//------------------------------------------------------- - -func RandGenesisState(numAccounts int, randBalance bool, minBalance int64, numValidators int, randBonded bool, minBonded int64) (*State, []*acm.PrivAccount, []*types.PrivValidator) { - db := tdb.NewMemDB() - genDoc, privAccounts, privValidators := RandGenesisDoc(numAccounts, randBalance, minBalance, numValidators, randBonded, minBonded) - s0 := MakeGenesisState(db, genDoc) - s0.Save() - return s0, privAccounts, privValidators -} - -func RandAccount(randBalance bool, minBalance int64) (*acm.Account, *acm.PrivAccount) { - privAccount := acm.GenPrivAccount() - perms := ptypes.DefaultAccountPermissions - acc := &acm.Account{ - Address: privAccount.PubKey.Address(), - PubKey: privAccount.PubKey, - Sequence: random.RandInt(), - Balance: minBalance, - Permissions: perms, - } - if randBalance { - acc.Balance += int64(random.RandUint32()) - } - return acc, privAccount -} - -func RandGenesisDoc(numAccounts int, randBalance bool, minBalance int64, numValidators int, randBonded bool, minBonded int64) (*genesis.GenesisDoc, []*acm.PrivAccount, []*types.PrivValidator) { - accounts := make([]genesis.GenesisAccount, numAccounts) - privAccounts := make([]*acm.PrivAccount, numAccounts) - defaultPerms := ptypes.DefaultAccountPermissions - for i := 0; i < numAccounts; i++ { - account, privAccount := RandAccount(randBalance, minBalance) - accounts[i] = genesis.GenesisAccount{ - Address: account.Address, - Amount: account.Balance, - Permissions: &defaultPerms, // This will get copied into each state.Account. - } - privAccounts[i] = privAccount - } - validators := make([]genesis.GenesisValidator, numValidators) - privValidators := make([]*types.PrivValidator, numValidators) - for i := 0; i < numValidators; i++ { - valInfo, privVal := types.RandValidator(randBonded, minBonded) - validators[i] = genesis.GenesisValidator{ - PubKey: valInfo.PubKey, - Amount: valInfo.VotingPower, - UnbondTo: []genesis.BasicAccount{ - { - Address: valInfo.PubKey.Address(), - Amount: valInfo.VotingPower, - }, - }, - } - privValidators[i] = privVal - } - sort.Sort(types.PrivValidatorsByAddress(privValidators)) - return &genesis.GenesisDoc{ - GenesisTime: time.Now(), - ChainID: "tendermint_test", - Accounts: accounts, - Validators: validators, - }, privAccounts, privValidators - -} diff --git a/manager/burrow-mint/state/permissions_test.go b/manager/burrow-mint/state/permissions_test.go deleted file mode 100644 index 4f550efd..00000000 --- a/manager/burrow-mint/state/permissions_test.go +++ /dev/null @@ -1,1314 +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 state - -import ( - "bytes" - "fmt" - "strconv" - "testing" - "time" - - acm "github.com/hyperledger/burrow/account" - genesis "github.com/hyperledger/burrow/genesis" - "github.com/hyperledger/burrow/manager/burrow-mint/evm" - . "github.com/hyperledger/burrow/manager/burrow-mint/evm/opcodes" - ptypes "github.com/hyperledger/burrow/permission/types" - "github.com/hyperledger/burrow/txs" - . "github.com/hyperledger/burrow/word256" - - "github.com/hyperledger/burrow/logging/lifecycle" - "github.com/tendermint/go-crypto" - dbm "github.com/tendermint/go-db" - "github.com/tendermint/go-events" - "github.com/tendermint/tendermint/config/tendermint_test" -) - -func init() { - tendermint_test.ResetConfig("permissions_test") -} - -var ( - dbBackend = "memdb" - dbDir = "" - permissionsContract = vm.SNativeContracts()["Permissions"] -) - -/* -Permission Tests: - -- SendTx: -x - 1 input, no perm, call perm, create perm -x - 1 input, perm -x - 2 inputs, one with perm one without - -- CallTx, CALL -x - 1 input, no perm, send perm, create perm -x - 1 input, perm -x - contract runs call but doesn't have call perm -x - contract runs call and has call perm -x - contract runs call (with perm), runs contract that runs call (without perm) -x - contract runs call (with perm), runs contract that runs call (with perm) - -- CallTx for Create, CREATE -x - 1 input, no perm, send perm, call perm -x - 1 input, perm -x - contract runs create but doesn't have create perm -x - contract runs create but has perm -x - contract runs call with empty address (has call and create perm) - -- NameTx - - no perm, send perm, call perm - - with perm - -- BondTx -x - 1 input, no perm -x - 1 input, perm -x - 1 bonder with perm, input without send or bond -x - 1 bonder with perm, input with send -x - 1 bonder with perm, input with bond -x - 2 inputs, one with perm one without - -- SendTx for new account -x - 1 input, 1 unknown ouput, input with send, not create (fail) -x - 1 input, 1 unknown ouput, input with send and create (pass) -x - 2 inputs, 1 unknown ouput, both inputs with send, one with create, one without (fail) -x - 2 inputs, 1 known output, 1 unknown ouput, one input with create, one without (fail) -x - 2 inputs, 1 unknown ouput, both inputs with send, both inputs with create (pass ) -x - 2 inputs, 1 known output, 1 unknown ouput, both inputs with create, (pass) - - -- CALL for new account -x - unknown output, without create (fail) -x - unknown output, with create (pass) - - -- SNative (CallTx, CALL): - - for each of CallTx, Call -x - call each snative without permission, fails -x - call each snative with permission, pass - - list: -x - base: has,set,unset -x - globals: set -x - roles: has, add, rm - - -*/ - -// keys -var user = makeUsers(10) -var chainID = "testchain" -var logger, _ = lifecycle.NewStdErrLogger() - -func makeUsers(n int) []*acm.PrivAccount { - accounts := []*acm.PrivAccount{} - for i := 0; i < n; i++ { - secret := ("mysecret" + strconv.Itoa(i)) - user := acm.GenPrivAccountFromSecret(secret) - accounts = append(accounts, user) - } - return accounts -} - -var ( - PermsAllFalse = ptypes.ZeroAccountPermissions -) - -func newBaseGenDoc(globalPerm, accountPerm ptypes.AccountPermissions) genesis.GenesisDoc { - genAccounts := []genesis.GenesisAccount{} - for _, u := range user[:5] { - accountPermCopy := accountPerm // Create new instance for custom overridability. - genAccounts = append(genAccounts, genesis.GenesisAccount{ - Address: u.Address, - Amount: 1000000, - Permissions: &accountPermCopy, - }) - } - - return genesis.GenesisDoc{ - GenesisTime: time.Now(), - ChainID: chainID, - Params: &genesis.GenesisParams{ - GlobalPermissions: &globalPerm, - }, - Accounts: genAccounts, - Validators: []genesis.GenesisValidator{ - genesis.GenesisValidator{ - PubKey: user[0].PubKey.(crypto.PubKeyEd25519), - Amount: 10, - UnbondTo: []genesis.BasicAccount{ - genesis.BasicAccount{ - Address: user[0].Address, - }, - }, - }, - }, - } -} - -func TestSendFails(t *testing.T) { - stateDB := dbm.NewDB("state", dbBackend, dbDir) - genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) - 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 := MakeGenesisState(stateDB, &genDoc) - blockCache := NewBlockCache(st) - - //------------------- - // send txs - - // simple send tx should fail - tx := txs.NewSendTx() - if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[1].Address, 5) - tx.SignInput(chainID, 0, user[0]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - // simple send tx with call perm should fail - tx = txs.NewSendTx() - if err := tx.AddInput(blockCache, user[2].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[4].Address, 5) - tx.SignInput(chainID, 0, user[2]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - // simple send tx with create perm should fail - tx = txs.NewSendTx() - if err := tx.AddInput(blockCache, user[3].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[4].Address, 5) - tx.SignInput(chainID, 0, user[3]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - // simple send tx to unknown account without create_account perm should fail - acc := blockCache.GetAccount(user[3].Address) - acc.Permissions.Base.Set(ptypes.Send, true) - blockCache.UpdateAccount(acc) - tx = txs.NewSendTx() - if err := tx.AddInput(blockCache, user[3].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[6].Address, 5) - tx.SignInput(chainID, 0, user[3]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } -} - -func TestName(t *testing.T) { - stateDB := dbm.NewDB("state", dbBackend, dbDir) - genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) - genDoc.Accounts[0].Permissions.Base.Set(ptypes.Send, true) - genDoc.Accounts[1].Permissions.Base.Set(ptypes.Name, true) - st := MakeGenesisState(stateDB, &genDoc) - blockCache := NewBlockCache(st) - - //------------------- - // name txs - - // simple name tx without perm should fail - tx, err := txs.NewNameTx(st, user[0].PubKey, "somename", "somedata", 10000, 100) - if err != nil { - t.Fatal(err) - } - tx.Sign(chainID, user[0]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - // simple name tx with perm should pass - tx, err = txs.NewNameTx(st, user[1].PubKey, "somename", "somedata", 10000, 100) - if err != nil { - t.Fatal(err) - } - tx.Sign(chainID, user[1]) - if err := ExecTx(blockCache, tx, true, nil, logger); err != nil { - t.Fatal(err) - } -} - -func TestCallFails(t *testing.T) { - stateDB := dbm.NewDB("state", dbBackend, dbDir) - genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) - 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 := MakeGenesisState(stateDB, &genDoc) - blockCache := NewBlockCache(st) - - //------------------- - // call txs - - // simple call tx should fail - tx, _ := txs.NewCallTx(blockCache, user[0].PubKey, user[4].Address, nil, 100, 100, 100) - tx.Sign(chainID, user[0]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - // simple call tx with send permission should fail - tx, _ = txs.NewCallTx(blockCache, user[1].PubKey, user[4].Address, nil, 100, 100, 100) - tx.Sign(chainID, user[1]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - // simple call tx with create permission should fail - tx, _ = txs.NewCallTx(blockCache, user[3].PubKey, user[4].Address, nil, 100, 100, 100) - tx.Sign(chainID, user[3]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - //------------------- - // create txs - - // simple call create tx should fail - tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, nil, nil, 100, 100, 100) - tx.Sign(chainID, user[0]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - // simple call create tx with send perm should fail - tx, _ = txs.NewCallTx(blockCache, user[1].PubKey, nil, nil, 100, 100, 100) - tx.Sign(chainID, user[1]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - // simple call create tx with call perm should fail - tx, _ = txs.NewCallTx(blockCache, user[2].PubKey, nil, nil, 100, 100, 100) - tx.Sign(chainID, user[2]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } -} - -func TestSendPermission(t *testing.T) { - stateDB := dbm.NewDB("state", dbBackend, dbDir) - genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) - genDoc.Accounts[0].Permissions.Base.Set(ptypes.Send, true) // give the 0 account permission - st := MakeGenesisState(stateDB, &genDoc) - blockCache := NewBlockCache(st) - - // A single input, having the permission, should succeed - tx := txs.NewSendTx() - if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[1].Address, 5) - tx.SignInput(chainID, 0, user[0]) - if err := ExecTx(blockCache, tx, true, nil, logger); err != nil { - t.Fatal("Transaction failed", err) - } - - // Two inputs, one with permission, one without, should fail - tx = txs.NewSendTx() - if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { - t.Fatal(err) - } - if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[2].Address, 10) - tx.SignInput(chainID, 0, user[0]) - tx.SignInput(chainID, 1, user[1]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } -} - -func TestCallPermission(t *testing.T) { - stateDB := dbm.NewDB("state", dbBackend, dbDir) - genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) - genDoc.Accounts[0].Permissions.Base.Set(ptypes.Call, true) // give the 0 account permission - st := MakeGenesisState(stateDB, &genDoc) - blockCache := NewBlockCache(st) - - //------------------------------ - // call to simple contract - fmt.Println("\n##### SIMPLE CONTRACT") - - // create simple contract - simpleContractAddr := NewContractAddress(user[0].Address, 100) - simpleAcc := &acm.Account{ - Address: simpleContractAddr, - Balance: 0, - Code: []byte{0x60}, - Sequence: 0, - StorageRoot: Zero256.Bytes(), - Permissions: ptypes.ZeroAccountPermissions, - } - st.UpdateAccount(simpleAcc) - - // A single input, having the permission, should succeed - tx, _ := txs.NewCallTx(blockCache, user[0].PubKey, simpleContractAddr, nil, 100, 100, 100) - tx.Sign(chainID, user[0]) - if err := ExecTx(blockCache, tx, true, nil, logger); err != nil { - t.Fatal("Transaction failed", err) - } - - //---------------------------------------------------------- - // call to contract that calls simple contract - without perm - fmt.Println("\n##### CALL TO SIMPLE CONTRACT (FAIL)") - - // create contract that calls the simple contract - contractCode := callContractCode(simpleContractAddr) - caller1ContractAddr := NewContractAddress(user[0].Address, 101) - caller1Acc := &acm.Account{ - Address: caller1ContractAddr, - Balance: 10000, - Code: contractCode, - Sequence: 0, - StorageRoot: Zero256.Bytes(), - Permissions: ptypes.ZeroAccountPermissions, - } - blockCache.UpdateAccount(caller1Acc) - - // A single input, having the permission, but the contract doesn't have permission - tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, caller1ContractAddr, nil, 100, 10000, 100) - tx.Sign(chainID, user[0]) - - // we need to subscribe to the Call event to detect the exception - _, exception := execTxWaitEvent(t, blockCache, tx, txs.EventStringAccCall(caller1ContractAddr)) // - if exception == "" { - t.Fatal("Expected exception") - } - - //---------------------------------------------------------- - // 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.Permissions.Base.Set(ptypes.Call, true) - blockCache.UpdateAccount(caller1Acc) - tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, caller1ContractAddr, nil, 100, 10000, 100) - tx.Sign(chainID, user[0]) - - // we need to subscribe to the Call event to detect the exception - _, exception = execTxWaitEvent(t, blockCache, tx, txs.EventStringAccCall(caller1ContractAddr)) // - if exception != "" { - t.Fatal("Unexpected exception:", exception) - } - - //---------------------------------------------------------- - // call to contract that calls contract that calls simple contract - without perm - // caller1Contract calls simpleContract. caller2Contract calls caller1Contract. - // caller1Contract does not have call perms, but caller2Contract does. - fmt.Println("\n##### CALL TO CONTRACT CALLING SIMPLE CONTRACT (FAIL)") - - contractCode2 := callContractCode(caller1ContractAddr) - caller2ContractAddr := NewContractAddress(user[0].Address, 102) - caller2Acc := &acm.Account{ - Address: caller2ContractAddr, - Balance: 1000, - Code: contractCode2, - Sequence: 0, - StorageRoot: Zero256.Bytes(), - Permissions: ptypes.ZeroAccountPermissions, - } - caller1Acc.Permissions.Base.Set(ptypes.Call, false) - caller2Acc.Permissions.Base.Set(ptypes.Call, true) - blockCache.UpdateAccount(caller1Acc) - blockCache.UpdateAccount(caller2Acc) - - tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, caller2ContractAddr, nil, 100, 10000, 100) - tx.Sign(chainID, user[0]) - - // we need to subscribe to the Call event to detect the exception - _, exception = execTxWaitEvent(t, blockCache, tx, txs.EventStringAccCall(caller1ContractAddr)) // - if exception == "" { - t.Fatal("Expected exception") - } - - //---------------------------------------------------------- - // call to contract that calls contract that calls simple contract - without perm - // caller1Contract calls simpleContract. caller2Contract calls caller1Contract. - // both caller1 and caller2 have permission - fmt.Println("\n##### CALL TO CONTRACT CALLING SIMPLE CONTRACT (PASS)") - - caller1Acc.Permissions.Base.Set(ptypes.Call, true) - blockCache.UpdateAccount(caller1Acc) - - tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, caller2ContractAddr, nil, 100, 10000, 100) - tx.Sign(chainID, user[0]) - - // we need to subscribe to the Call event to detect the exception - _, exception = execTxWaitEvent(t, blockCache, tx, txs.EventStringAccCall(caller1ContractAddr)) // - if exception != "" { - t.Fatal("Unexpected exception", exception) - } -} - -func TestCreatePermission(t *testing.T) { - stateDB := dbm.NewDB("state", dbBackend, dbDir) - genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) - 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 := MakeGenesisState(stateDB, &genDoc) - blockCache := NewBlockCache(st) - - //------------------------------ - // create a simple contract - fmt.Println("\n##### CREATE SIMPLE CONTRACT") - - contractCode := []byte{0x60} - createCode := wrapContractForCreate(contractCode) - - // A single input, having the permission, should succeed - tx, _ := txs.NewCallTx(blockCache, user[0].PubKey, nil, createCode, 100, 100, 100) - tx.Sign(chainID, user[0]) - if err := ExecTx(blockCache, tx, true, nil, logger); err != nil { - t.Fatal("Transaction failed", err) - } - // ensure the contract is there - contractAddr := NewContractAddress(tx.Input.Address, tx.Input.Sequence) - contractAcc := blockCache.GetAccount(contractAddr) - if contractAcc == nil { - t.Fatalf("failed to create contract %X", contractAddr) - } - if !bytes.Equal(contractAcc.Code, contractCode) { - t.Fatalf("contract does not have correct code. Got %X, expected %X", contractAcc.Code, contractCode) - } - - //------------------------------ - // create contract that uses the CREATE op - fmt.Println("\n##### CREATE FACTORY") - - contractCode = []byte{0x60} - createCode = wrapContractForCreate(contractCode) - factoryCode := createContractCode() - createFactoryCode := wrapContractForCreate(factoryCode) - - // A single input, having the permission, should succeed - tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, nil, createFactoryCode, 100, 100, 100) - tx.Sign(chainID, user[0]) - if err := ExecTx(blockCache, tx, true, nil, logger); err != nil { - t.Fatal("Transaction failed", err) - } - // ensure the contract is there - contractAddr = NewContractAddress(tx.Input.Address, tx.Input.Sequence) - contractAcc = blockCache.GetAccount(contractAddr) - if contractAcc == nil { - t.Fatalf("failed to create contract %X", contractAddr) - } - if !bytes.Equal(contractAcc.Code, factoryCode) { - t.Fatalf("contract does not have correct code. Got %X, expected %X", contractAcc.Code, factoryCode) - } - - //------------------------------ - // call the contract (should FAIL) - fmt.Println("\n###### CALL THE FACTORY (FAIL)") - - // A single input, having the permission, should succeed - tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, contractAddr, createCode, 100, 100, 100) - tx.Sign(chainID, user[0]) - // we need to subscribe to the Call event to detect the exception - _, exception := execTxWaitEvent(t, blockCache, tx, txs.EventStringAccCall(contractAddr)) // - if exception == "" { - t.Fatal("expected exception") - } - - //------------------------------ - // call the contract (should PASS) - fmt.Println("\n###### CALL THE FACTORY (PASS)") - - contractAcc.Permissions.Base.Set(ptypes.CreateContract, true) - blockCache.UpdateAccount(contractAcc) - - // A single input, having the permission, should succeed - tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, contractAddr, createCode, 100, 100, 100) - tx.Sign(chainID, user[0]) - // we need to subscribe to the Call event to detect the exception - _, exception = execTxWaitEvent(t, blockCache, tx, txs.EventStringAccCall(contractAddr)) // - if exception != "" { - t.Fatal("unexpected exception", exception) - } - - //-------------------------------- - fmt.Println("\n##### CALL to empty address") - zeroAddr := LeftPadBytes([]byte{}, 20) - code := callContractCode(zeroAddr) - - contractAddr = NewContractAddress(user[0].Address, 110) - contractAcc = &acm.Account{ - Address: contractAddr, - Balance: 1000, - Code: code, - Sequence: 0, - StorageRoot: Zero256.Bytes(), - Permissions: ptypes.ZeroAccountPermissions, - } - contractAcc.Permissions.Base.Set(ptypes.Call, true) - contractAcc.Permissions.Base.Set(ptypes.CreateContract, true) - blockCache.UpdateAccount(contractAcc) - - // this should call the 0 address but not create ... - tx, _ = txs.NewCallTx(blockCache, user[0].PubKey, contractAddr, createCode, 100, 10000, 100) - tx.Sign(chainID, user[0]) - // we need to subscribe to the Call event to detect the exception - _, exception = execTxWaitEvent(t, blockCache, tx, txs.EventStringAccCall(zeroAddr)) // - if exception != "" { - t.Fatal("unexpected exception", exception) - } - zeroAcc := blockCache.GetAccount(zeroAddr) - if len(zeroAcc.Code) != 0 { - t.Fatal("the zero account was given code from a CALL!") - } -} - -/* TODO -func TestBondPermission(t *testing.T) { - stateDB := dbm.NewDB("state",dbBackend,dbDir) - genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) - st := MakeGenesisState(stateDB, &genDoc) - blockCache := NewBlockCache(st) - var bondAcc *acm.Account - - //------------------------------ - // one bonder without permission should fail - tx, _ := txs.NewBondTx(user[1].PubKey) - if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[1].Address, 5) - tx.SignInput(chainID, 0, user[1]) - tx.SignBond(chainID, user[1]) - if err := ExecTx(blockCache, tx, true, nil); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - //------------------------------ - // one bonder with permission should pass - bondAcc = blockCache.GetAccount(user[1].Address) - bondAcc.Permissions.Base.Set(ptypes.Bond, true) - blockCache.UpdateAccount(bondAcc) - if err := ExecTx(blockCache, tx, true, nil); err != nil { - t.Fatal("Unexpected error", err) - } - - // reset state (we can only bond with an account once ..) - genDoc = newBaseGenDoc(PermsAllFalse, PermsAllFalse) - st = MakeGenesisState(stateDB, &genDoc) - blockCache = NewBlockCache(st) - bondAcc = blockCache.GetAccount(user[1].Address) - bondAcc.Permissions.Base.Set(ptypes.Bond, true) - blockCache.UpdateAccount(bondAcc) - //------------------------------ - // one bonder with permission and an input without send should fail - tx, _ = txs.NewBondTx(user[1].PubKey) - if err := tx.AddInput(blockCache, user[2].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[1].Address, 5) - tx.SignInput(chainID, 0, user[2]) - tx.SignBond(chainID, user[1]) - if err := ExecTx(blockCache, tx, true, nil); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - // reset state (we can only bond with an account once ..) - genDoc = newBaseGenDoc(PermsAllFalse, PermsAllFalse) - st = MakeGenesisState(stateDB, &genDoc) - blockCache = NewBlockCache(st) - bondAcc = blockCache.GetAccount(user[1].Address) - bondAcc.Permissions.Base.Set(ptypes.Bond, true) - blockCache.UpdateAccount(bondAcc) - //------------------------------ - // one bonder with permission and an input with send should pass - sendAcc := blockCache.GetAccount(user[2].Address) - sendAcc.Permissions.Base.Set(ptypes.Send, true) - blockCache.UpdateAccount(sendAcc) - tx, _ = txs.NewBondTx(user[1].PubKey) - if err := tx.AddInput(blockCache, user[2].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[1].Address, 5) - tx.SignInput(chainID, 0, user[2]) - tx.SignBond(chainID, user[1]) - if err := ExecTx(blockCache, tx, true, nil); err != nil { - t.Fatal("Unexpected error", err) - } - - // reset state (we can only bond with an account once ..) - genDoc = newBaseGenDoc(PermsAllFalse, PermsAllFalse) - st = MakeGenesisState(stateDB, &genDoc) - blockCache = NewBlockCache(st) - bondAcc = blockCache.GetAccount(user[1].Address) - bondAcc.Permissions.Base.Set(ptypes.Bond, true) - blockCache.UpdateAccount(bondAcc) - //------------------------------ - // one bonder with permission and an input with bond should pass - sendAcc.Permissions.Base.Set(ptypes.Bond, true) - blockCache.UpdateAccount(sendAcc) - tx, _ = txs.NewBondTx(user[1].PubKey) - if err := tx.AddInput(blockCache, user[2].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[1].Address, 5) - tx.SignInput(chainID, 0, user[2]) - tx.SignBond(chainID, user[1]) - if err := ExecTx(blockCache, tx, true, nil); err != nil { - t.Fatal("Unexpected error", err) - } - - // reset state (we can only bond with an account once ..) - genDoc = newBaseGenDoc(PermsAllFalse, PermsAllFalse) - st = MakeGenesisState(stateDB, &genDoc) - blockCache = NewBlockCache(st) - bondAcc = blockCache.GetAccount(user[1].Address) - bondAcc.Permissions.Base.Set(ptypes.Bond, true) - blockCache.UpdateAccount(bondAcc) - //------------------------------ - // one bonder with permission and an input from that bonder and an input without send or bond should fail - tx, _ = txs.NewBondTx(user[1].PubKey) - if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { - t.Fatal(err) - } - if err := tx.AddInput(blockCache, user[2].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[1].Address, 5) - tx.SignInput(chainID, 0, user[1]) - tx.SignInput(chainID, 1, user[2]) - tx.SignBond(chainID, user[1]) - if err := ExecTx(blockCache, tx, true, nil); err == nil { - t.Fatal("Expected error") - } -} -*/ - -func TestCreateAccountPermission(t *testing.T) { - stateDB := dbm.NewDB("state", dbBackend, dbDir) - genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) - genDoc.Accounts[0].Permissions.Base.Set(ptypes.Send, true) // give the 0 account permission - genDoc.Accounts[1].Permissions.Base.Set(ptypes.Send, true) // give the 0 account permission - genDoc.Accounts[0].Permissions.Base.Set(ptypes.CreateAccount, true) // give the 0 account permission - st := MakeGenesisState(stateDB, &genDoc) - blockCache := NewBlockCache(st) - - //---------------------------------------------------------- - // SendTx to unknown account - - // A single input, having the permission, should succeed - tx := txs.NewSendTx() - if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[6].Address, 5) - tx.SignInput(chainID, 0, user[0]) - if err := ExecTx(blockCache, tx, true, nil, logger); err != nil { - t.Fatal("Transaction failed", err) - } - - // Two inputs, both with send, one with create, one without, should fail - tx = txs.NewSendTx() - if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { - t.Fatal(err) - } - if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[7].Address, 10) - tx.SignInput(chainID, 0, user[0]) - tx.SignInput(chainID, 1, user[1]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - // Two inputs, both with send, one with create, one without, two ouputs (one known, one unknown) should fail - tx = txs.NewSendTx() - if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { - t.Fatal(err) - } - if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[7].Address, 4) - tx.AddOutput(user[4].Address, 6) - tx.SignInput(chainID, 0, user[0]) - tx.SignInput(chainID, 1, user[1]) - if err := ExecTx(blockCache, tx, true, nil, logger); err == nil { - t.Fatal("Expected error") - } else { - fmt.Println(err) - } - - // Two inputs, both with send, both with create, should pass - acc := blockCache.GetAccount(user[1].Address) - acc.Permissions.Base.Set(ptypes.CreateAccount, true) - blockCache.UpdateAccount(acc) - tx = txs.NewSendTx() - if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { - t.Fatal(err) - } - if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[7].Address, 10) - tx.SignInput(chainID, 0, user[0]) - tx.SignInput(chainID, 1, user[1]) - if err := ExecTx(blockCache, tx, true, nil, logger); err != nil { - t.Fatal("Unexpected error", err) - } - - // Two inputs, both with send, both with create, two outputs (one known, one unknown) should pass - tx = txs.NewSendTx() - if err := tx.AddInput(blockCache, user[0].PubKey, 5); err != nil { - t.Fatal(err) - } - if err := tx.AddInput(blockCache, user[1].PubKey, 5); err != nil { - t.Fatal(err) - } - tx.AddOutput(user[7].Address, 7) - tx.AddOutput(user[4].Address, 3) - tx.SignInput(chainID, 0, user[0]) - tx.SignInput(chainID, 1, user[1]) - if err := ExecTx(blockCache, tx, true, nil, logger); err != nil { - t.Fatal("Unexpected error", err) - } - - //---------------------------------------------------------- - // CALL to unknown account - - acc = blockCache.GetAccount(user[0].Address) - acc.Permissions.Base.Set(ptypes.Call, true) - blockCache.UpdateAccount(acc) - - // call to contract that calls unknown account - without create_account perm - // create contract that calls the simple contract - contractCode := callContractCode(user[9].Address) - caller1ContractAddr := NewContractAddress(user[4].Address, 101) - caller1Acc := &acm.Account{ - Address: caller1ContractAddr, - Balance: 0, - Code: contractCode, - Sequence: 0, - StorageRoot: Zero256.Bytes(), - Permissions: ptypes.ZeroAccountPermissions, - } - blockCache.UpdateAccount(caller1Acc) - - // A single input, having the permission, but the contract doesn't have permission - txCall, _ := txs.NewCallTx(blockCache, user[0].PubKey, caller1ContractAddr, nil, 100, 10000, 100) - txCall.Sign(chainID, user[0]) - - // we need to subscribe to the Call event to detect the exception - _, exception := execTxWaitEvent(t, blockCache, txCall, txs.EventStringAccCall(caller1ContractAddr)) // - if exception == "" { - t.Fatal("Expected exception") - } - - // NOTE: for a contract to be able to CreateAccount, it must be able to call - // NOTE: for a user to be able to CreateAccount, it must be able to send! - caller1Acc.Permissions.Base.Set(ptypes.CreateAccount, true) - caller1Acc.Permissions.Base.Set(ptypes.Call, true) - blockCache.UpdateAccount(caller1Acc) - // A single input, having the permission, but the contract doesn't have permission - txCall, _ = txs.NewCallTx(blockCache, user[0].PubKey, caller1ContractAddr, nil, 100, 10000, 100) - txCall.Sign(chainID, user[0]) - - // we need to subscribe to the Call event to detect the exception - _, exception = execTxWaitEvent(t, blockCache, txCall, txs.EventStringAccCall(caller1ContractAddr)) // - if exception != "" { - t.Fatal("Unexpected exception", exception) - } - -} - -// holla at my boy -var DougAddress = append([]byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, []byte("THISISDOUG")...) - -func TestSNativeCALL(t *testing.T) { - stateDB := dbm.NewDB("state", dbBackend, dbDir) - genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) - 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 := MakeGenesisState(stateDB, &genDoc) - blockCache := NewBlockCache(st) - - //---------------------------------------------------------- - // Test CALL to SNative contracts - - // make the main contract once - doug := &acm.Account{ - Address: DougAddress, - Balance: 0, - Code: nil, - Sequence: 0, - StorageRoot: Zero256.Bytes(), - Permissions: ptypes.ZeroAccountPermissions, - } - doug.Permissions.Base.Set(ptypes.Call, true) - //doug.Permissions.Base.Set(ptypes.HasBase, true) - blockCache.UpdateAccount(doug) - - fmt.Println("\n#### HasBase") - // HasBase - snativeAddress, pF, data := snativePermTestInputCALL("hasBase", user[3], ptypes.Bond, false) - testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) - testSNativeCALLExpectPass(t, blockCache, 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) - } - return nil - }) - - fmt.Println("\n#### SetBase") - // SetBase - snativeAddress, pF, data = snativePermTestInputCALL("setBase", user[3], ptypes.Bond, false) - testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) - testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) - snativeAddress, pF, data = snativePermTestInputCALL("hasBase", user[3], ptypes.Bond, false) - testSNativeCALLExpectPass(t, blockCache, 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", user[3], ptypes.CreateContract, true) - testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) - snativeAddress, pF, data = snativePermTestInputCALL("hasBase", user[3], ptypes.CreateContract, false) - testSNativeCALLExpectPass(t, blockCache, 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) - } - return nil - }) - - fmt.Println("\n#### UnsetBase") - // UnsetBase - snativeAddress, pF, data = snativePermTestInputCALL("unsetBase", user[3], ptypes.CreateContract, false) - testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) - testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) - snativeAddress, pF, data = snativePermTestInputCALL("hasBase", user[3], ptypes.CreateContract, false) - testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { - if !IsZeros(ret) { - return fmt.Errorf("Expected 0. Got %X", ret) - } - return nil - }) - - fmt.Println("\n#### SetGlobal") - // SetGlobalPerm - snativeAddress, pF, data = snativePermTestInputCALL("setGlobal", user[3], ptypes.CreateContract, true) - testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) - testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) - snativeAddress, pF, data = snativePermTestInputCALL("hasBase", user[3], ptypes.CreateContract, false) - testSNativeCALLExpectPass(t, blockCache, 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) - } - return nil - }) - - fmt.Println("\n#### HasRole") - // HasRole - snativeAddress, pF, data = snativeRoleTestInputCALL("hasRole", user[3], "bumble") - testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) - testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { - if !IsZeros(ret[:31]) || ret[31] != byte(1) { - return fmt.Errorf("Expected 1. Got %X", ret) - } - return nil - }) - - fmt.Println("\n#### AddRole") - // AddRole - snativeAddress, pF, data = snativeRoleTestInputCALL("hasRole", user[3], "chuck") - testSNativeCALLExpectPass(t, blockCache, 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", user[3], "chuck") - testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) - testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) - snativeAddress, pF, data = snativeRoleTestInputCALL("hasRole", user[3], "chuck") - testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { - if !IsZeros(ret[:31]) || ret[31] != byte(1) { - return fmt.Errorf("Expected 1. Got %X", ret) - } - return nil - }) - - fmt.Println("\n#### RmRole") - // RmRole - snativeAddress, pF, data = snativeRoleTestInputCALL("removeRole", user[3], "chuck") - testSNativeCALLExpectFail(t, blockCache, doug, snativeAddress, data) - testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { return nil }) - snativeAddress, pF, data = snativeRoleTestInputCALL("hasRole", user[3], "chuck") - testSNativeCALLExpectPass(t, blockCache, doug, pF, snativeAddress, data, func(ret []byte) error { - if !IsZeros(ret) { - return fmt.Errorf("Expected 0. Got %X", ret) - } - return nil - }) -} - -func TestSNativeTx(t *testing.T) { - stateDB := dbm.NewDB("state", dbBackend, dbDir) - genDoc := newBaseGenDoc(PermsAllFalse, PermsAllFalse) - 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 := MakeGenesisState(stateDB, &genDoc) - blockCache := NewBlockCache(st) - - //---------------------------------------------------------- - // Test SNativeTx - - fmt.Println("\n#### SetBase") - // SetBase - snativeArgs := snativePermTestInputTx("setBase", user[3], ptypes.Bond, false) - testSNativeTxExpectFail(t, blockCache, snativeArgs) - testSNativeTxExpectPass(t, blockCache, ptypes.SetBase, snativeArgs) - acc := blockCache.GetAccount(user[3].Address) - if v, _ := acc.Permissions.Base.Get(ptypes.Bond); v { - t.Fatal("expected permission to be set false") - } - snativeArgs = snativePermTestInputTx("setBase", user[3], ptypes.CreateContract, true) - testSNativeTxExpectPass(t, blockCache, ptypes.SetBase, snativeArgs) - acc = blockCache.GetAccount(user[3].Address) - if v, _ := acc.Permissions.Base.Get(ptypes.CreateContract); !v { - t.Fatal("expected permission to be set true") - } - - fmt.Println("\n#### UnsetBase") - // UnsetBase - snativeArgs = snativePermTestInputTx("unsetBase", user[3], ptypes.CreateContract, false) - testSNativeTxExpectFail(t, blockCache, snativeArgs) - testSNativeTxExpectPass(t, blockCache, ptypes.UnsetBase, snativeArgs) - acc = blockCache.GetAccount(user[3].Address) - if v, _ := acc.Permissions.Base.Get(ptypes.CreateContract); v { - t.Fatal("expected permission to be set false") - } - - fmt.Println("\n#### SetGlobal") - // SetGlobalPerm - snativeArgs = snativePermTestInputTx("setGlobal", user[3], ptypes.CreateContract, true) - testSNativeTxExpectFail(t, blockCache, snativeArgs) - testSNativeTxExpectPass(t, blockCache, ptypes.SetGlobal, snativeArgs) - acc = blockCache.GetAccount(ptypes.GlobalPermissionsAddress) - if v, _ := acc.Permissions.Base.Get(ptypes.CreateContract); !v { - t.Fatal("expected permission to be set true") - } - - fmt.Println("\n#### AddRole") - // AddRole - snativeArgs = snativeRoleTestInputTx("addRole", user[3], "chuck") - testSNativeTxExpectFail(t, blockCache, snativeArgs) - testSNativeTxExpectPass(t, blockCache, ptypes.AddRole, snativeArgs) - acc = blockCache.GetAccount(user[3].Address) - if v := acc.Permissions.HasRole("chuck"); !v { - t.Fatal("expected role to be added") - } - - fmt.Println("\n#### RmRole") - // RmRole - snativeArgs = snativeRoleTestInputTx("removeRole", user[3], "chuck") - testSNativeTxExpectFail(t, blockCache, snativeArgs) - testSNativeTxExpectPass(t, blockCache, ptypes.RmRole, snativeArgs) - acc = blockCache.GetAccount(user[3].Address) - if v := acc.Permissions.HasRole("chuck"); v { - t.Fatal("expected role to be removed") - } -} - -//------------------------------------------------------------------------------------- -// helpers - -var ExceptionTimeOut = "timed out waiting for event" - -// run ExecTx and wait for the Call event on given addr -// returns the msg data and an error/exception -func execTxWaitEvent(t *testing.T, blockCache *BlockCache, tx txs.Tx, eventid string) (interface{}, string) { - evsw := events.NewEventSwitch() - evsw.Start() - ch := make(chan interface{}) - evsw.AddListenerForEvent("test", eventid, func(msg events.EventData) { - ch <- msg - }) - evc := events.NewEventCache(evsw) - go func() { - if err := ExecTx(blockCache, tx, true, evc, logger); err != nil { - ch <- err.Error() - } - evc.Flush() - }() - ticker := time.NewTicker(5 * time.Second) - var msg interface{} - select { - case msg = <-ch: - case <-ticker.C: - return nil, ExceptionTimeOut - } - - switch ev := msg.(type) { - case txs.EventDataTx: - return ev, ev.Exception - case txs.EventDataCall: - return ev, ev.Exception - case string: - return nil, ev - default: - return ev, "" - } -} - -// give a contract perms for an snative, call it, it calls the snative, but shouldn't have permission -func testSNativeCALLExpectFail(t *testing.T, blockCache *BlockCache, doug *acm.Account, snativeAddress, data []byte) { - testSNativeCALL(t, false, blockCache, doug, 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, blockCache *BlockCache, doug *acm.Account, snativePerm ptypes.PermFlag, snativeAddress, data []byte, f func([]byte) error) { - testSNativeCALL(t, true, blockCache, doug, snativePerm, snativeAddress, data, f) -} - -func testSNativeCALL(t *testing.T, expectPass bool, blockCache *BlockCache, doug *acm.Account, snativePerm ptypes.PermFlag, snativeAddress, data []byte, f func([]byte) error) { - if expectPass { - doug.Permissions.Base.Set(snativePerm, true) - } - var addr []byte - contractCode := callContractCode(snativeAddress) - doug.Code = contractCode - blockCache.UpdateAccount(doug) - addr = doug.Address - tx, _ := txs.NewCallTx(blockCache, user[0].PubKey, addr, data, 100, 10000, 100) - tx.Sign(chainID, user[0]) - fmt.Println("subscribing to", txs.EventStringAccCall(snativeAddress)) - ev, exception := execTxWaitEvent(t, blockCache, tx, txs.EventStringAccCall(snativeAddress)) - if exception == ExceptionTimeOut { - t.Fatal("Timed out waiting for event") - } - if expectPass { - if exception != "" { - t.Fatal("Unexpected exception", exception) - } - evv := ev.(txs.EventDataCall) - ret := evv.Return - if err := f(ret); err != nil { - t.Fatal(err) - } - } else { - if exception == "" { - t.Fatal("Expected exception") - } - } -} - -func testSNativeTxExpectFail(t *testing.T, blockCache *BlockCache, snativeArgs ptypes.PermArgs) { - testSNativeTx(t, false, blockCache, 0, snativeArgs) -} - -func testSNativeTxExpectPass(t *testing.T, blockCache *BlockCache, perm ptypes.PermFlag, snativeArgs ptypes.PermArgs) { - testSNativeTx(t, true, blockCache, perm, snativeArgs) -} - -func testSNativeTx(t *testing.T, expectPass bool, blockCache *BlockCache, perm ptypes.PermFlag, snativeArgs ptypes.PermArgs) { - if expectPass { - acc := blockCache.GetAccount(user[0].Address) - acc.Permissions.Base.Set(perm, true) - blockCache.UpdateAccount(acc) - } - tx, _ := txs.NewPermissionsTx(blockCache, user[0].PubKey, snativeArgs) - tx.Sign(chainID, user[0]) - err := ExecTx(blockCache, tx, true, nil, logger) - if expectPass { - if err != nil { - t.Fatal("Unexpected exception", err) - } - } else { - if err == nil { - t.Fatal("Expected exception") - } - } -} - -func boolToWord256(v bool) Word256 { - var vint byte - if v { - vint = 0x1 - } else { - vint = 0x0 - } - return LeftPadWord256([]byte{vint}) -} - -func permNameToFuncID(name string) []byte { - function, err := permissionsContract.FunctionByName(name) - if err != nil { - panic("didn't find snative function signature!") - } - id := function.ID() - return id[:] -} - -func snativePermTestInputCALL(name string, user *acm.PrivAccount, perm ptypes.PermFlag, val bool) (addr []byte, pF ptypes.PermFlag, data []byte) { - addr = permissionsContract.AddressBytes() - switch name { - case "hasBase", "unsetBase": - data = LeftPadBytes(user.Address, 32) - data = append(data, Uint64ToWord256(uint64(perm)).Bytes()...) - case "setBase": - data = LeftPadBytes(user.Address, 32) - data = append(data, Uint64ToWord256(uint64(perm)).Bytes()...) - data = append(data, boolToWord256(val).Bytes()...) - case "setGlobal": - data = Uint64ToWord256(uint64(perm)).Bytes() - data = append(data, boolToWord256(val).Bytes()...) - } - data = append(permNameToFuncID(name), data...) - var err error - if pF, err = ptypes.PermStringToFlag(name); err != nil { - panic(fmt.Sprintf("failed to convert perm string (%s) to flag", name)) - } - return -} - -func snativePermTestInputTx(name string, user *acm.PrivAccount, perm ptypes.PermFlag, val bool) (snativeArgs ptypes.PermArgs) { - switch name { - case "hasBase": - snativeArgs = &ptypes.HasBaseArgs{user.Address, perm} - case "unsetBase": - snativeArgs = &ptypes.UnsetBaseArgs{user.Address, perm} - case "setBase": - snativeArgs = &ptypes.SetBaseArgs{user.Address, perm, val} - case "setGlobal": - snativeArgs = &ptypes.SetGlobalArgs{perm, val} - } - return -} - -func snativeRoleTestInputCALL(name string, user *acm.PrivAccount, role string) (addr []byte, pF ptypes.PermFlag, data []byte) { - addr = permissionsContract.AddressBytes() - data = LeftPadBytes(user.Address, 32) - data = append(data, RightPadBytes([]byte(role), 32)...) - data = append(permNameToFuncID(name), data...) - - var err error - if pF, err = ptypes.PermStringToFlag(name); err != nil { - panic(fmt.Sprintf("failed to convert perm string (%s) to flag", name)) - } - return -} - -func snativeRoleTestInputTx(name string, user *acm.PrivAccount, role string) (snativeArgs ptypes.PermArgs) { - switch name { - case "hasRole": - snativeArgs = &ptypes.HasRoleArgs{user.Address, role} - case "addRole": - snativeArgs = &ptypes.AddRoleArgs{user.Address, role} - case "removeRole": - snativeArgs = &ptypes.RmRoleArgs{user.Address, role} - } - return -} - -// convenience function for contract that calls a given address -func callContractCode(contractAddr []byte) []byte { - // calldatacopy into mem and use as input to call - memOff, inputOff := byte(0x0), byte(0x0) - value := byte(0x1) - inOff := byte(0x0) - retOff, retSize := byte(0x0), byte(0x20) - - // this is the code we want to run (call a contract and return) - return Bytecode(CALLDATASIZE, PUSH1, inputOff, PUSH1, memOff, - CALLDATACOPY, PUSH1, retSize, PUSH1, retOff, CALLDATASIZE, PUSH1, inOff, - PUSH1, value, PUSH20, contractAddr, - // Zeno loves us - call with half of the available gas each time we CALL - PUSH1, 2, GAS, DIV, CALL, - PUSH1, 32, PUSH1, 0, RETURN) -} - -// convenience function for contract that is a factory for the code that comes as call data -func createContractCode() []byte { - // TODO: gas ... - - // calldatacopy the calldatasize - memOff, inputOff := byte(0x0), byte(0x0) - contractCode := []byte{0x60, memOff, 0x60, inputOff, 0x36, 0x37} - - // create - value := byte(0x1) - contractCode = append(contractCode, []byte{0x60, value, 0x36, 0x60, memOff, 0xf0}...) - return contractCode -} - -// wrap a contract in create code -func wrapContractForCreate(contractCode []byte) []byte { - // the is the code we need to return the contractCode when the contract is initialized - lenCode := len(contractCode) - // push code to the stack - code := append([]byte{0x7f}, RightPadWord256(contractCode).Bytes()...) - // store it in memory - code = append(code, []byte{0x60, 0x0, 0x52}...) - // return whats in memory - code = append(code, []byte{0x60, byte(lenCode), 0x60, 0x0, 0xf3}...) - // return init code, contract code, expected return - return code -} diff --git a/manager/burrow-mint/state/tx_cache.go b/manager/burrow-mint/state/tx_cache.go deleted file mode 100644 index d892f46e..00000000 --- a/manager/burrow-mint/state/tx_cache.go +++ /dev/null @@ -1,218 +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 state - -import ( - "fmt" - - acm "github.com/hyperledger/burrow/account" - "github.com/hyperledger/burrow/common/sanity" - "github.com/hyperledger/burrow/manager/burrow-mint/evm" - ptypes "github.com/hyperledger/burrow/permission/types" // for GlobalPermissionAddress ... - "github.com/hyperledger/burrow/txs" - . "github.com/hyperledger/burrow/word256" - - "github.com/tendermint/go-crypto" -) - -type TxCache struct { - backend *BlockCache - accounts map[Word256]vmAccountInfo - storages map[Tuple256]Word256 -} - -var _ vm.AppState = &TxCache{} - -func NewTxCache(backend *BlockCache) *TxCache { - return &TxCache{ - backend: backend, - accounts: make(map[Word256]vmAccountInfo), - storages: make(map[Tuple256]Word256), - } -} - -//------------------------------------- -// TxCache.account - -func (cache *TxCache) GetAccount(addr Word256) *vm.Account { - acc, removed := cache.accounts[addr].unpack() - if removed { - return nil - } else if acc == nil { - acc2 := cache.backend.GetAccount(addr.Postfix(20)) - if acc2 != nil { - return toVMAccount(acc2) - } - } - return acc -} - -func (cache *TxCache) UpdateAccount(acc *vm.Account) { - addr := acc.Address - _, removed := cache.accounts[addr].unpack() - if removed { - sanity.PanicSanity("UpdateAccount on a removed account") - } - cache.accounts[addr] = vmAccountInfo{acc, false} -} - -func (cache *TxCache) RemoveAccount(acc *vm.Account) { - addr := acc.Address - _, removed := cache.accounts[addr].unpack() - if removed { - sanity.PanicSanity("RemoveAccount on a removed account") - } - cache.accounts[addr] = vmAccountInfo{acc, true} -} - -// Creates a 20 byte address and bumps the creator's nonce. -func (cache *TxCache) CreateAccount(creator *vm.Account) *vm.Account { - - // Generate an address - nonce := creator.Nonce - creator.Nonce += 1 - - addr := LeftPadWord256(NewContractAddress(creator.Address.Postfix(20), int(nonce))) - - // Create account from address. - account, removed := cache.accounts[addr].unpack() - if removed || account == nil { - account = &vm.Account{ - Address: addr, - Balance: 0, - Code: nil, - Nonce: 0, - Permissions: cache.GetAccount(ptypes.GlobalPermissionsAddress256).Permissions, - Other: vmAccountOther{ - PubKey: nil, - StorageRoot: nil, - }, - } - cache.accounts[addr] = vmAccountInfo{account, false} - return account - } else { - // either we've messed up nonce handling, or sha3 is broken - sanity.PanicSanity(fmt.Sprintf("Could not create account, address already exists: %X", addr)) - return nil - } -} - -// TxCache.account -//------------------------------------- -// TxCache.storage - -func (cache *TxCache) GetStorage(addr Word256, key Word256) Word256 { - // Check cache - value, ok := cache.storages[Tuple256{addr, key}] - if ok { - return value - } - - // Load from backend - return cache.backend.GetStorage(addr, key) -} - -// NOTE: Set value to zero to removed from the trie. -func (cache *TxCache) SetStorage(addr Word256, key Word256, value Word256) { - _, removed := cache.accounts[addr].unpack() - if removed { - sanity.PanicSanity("SetStorage() on a removed account") - } - cache.storages[Tuple256{addr, key}] = value -} - -// TxCache.storage -//------------------------------------- - -// These updates do not have to be in deterministic order, -// the backend is responsible for ordering updates. -func (cache *TxCache) Sync() { - // Remove or update storage - for addrKey, value := range cache.storages { - addr, key := Tuple256Split(addrKey) - cache.backend.SetStorage(addr, key, value) - } - - // Remove or update accounts - for addr, accInfo := range cache.accounts { - acc, removed := accInfo.unpack() - if removed { - cache.backend.RemoveAccount(addr.Postfix(20)) - } else { - cache.backend.UpdateAccount(toStateAccount(acc)) - } - } -} - -//----------------------------------------------------------------------------- - -// Convenience function to return address of new contract -func NewContractAddress(caller []byte, nonce int) []byte { - return txs.NewContractAddress(caller, nonce) -} - -// Converts backend.Account to vm.Account struct. -func toVMAccount(acc *acm.Account) *vm.Account { - return &vm.Account{ - Address: LeftPadWord256(acc.Address), - Balance: acc.Balance, - Code: acc.Code, // This is crazy. - Nonce: int64(acc.Sequence), - Permissions: acc.Permissions, // Copy - Other: vmAccountOther{ - PubKey: acc.PubKey, - StorageRoot: acc.StorageRoot, - }, - } -} - -// Converts vm.Account to backend.Account struct. -func toStateAccount(acc *vm.Account) *acm.Account { - var pubKey crypto.PubKey - var storageRoot []byte - if acc.Other != nil { - pubKey, storageRoot = acc.Other.(vmAccountOther).unpack() - } - - return &acm.Account{ - Address: acc.Address.Postfix(20), - PubKey: pubKey, - Balance: acc.Balance, - Code: acc.Code, - Sequence: int(acc.Nonce), - StorageRoot: storageRoot, - Permissions: acc.Permissions, // Copy - } -} - -// Everything in acmAccount that doesn't belong in -// exported vmAccount fields. -type vmAccountOther struct { - PubKey crypto.PubKey - StorageRoot []byte -} - -func (accOther vmAccountOther) unpack() (crypto.PubKey, []byte) { - return accOther.PubKey, accOther.StorageRoot -} - -type vmAccountInfo struct { - account *vm.Account - removed bool -} - -func (accInfo vmAccountInfo) unpack() (*vm.Account, bool) { - return accInfo.account, accInfo.removed -} diff --git a/manager/burrow-mint/state/tx_cache_test.go b/manager/burrow-mint/state/tx_cache_test.go deleted file mode 100644 index 4b30fa4a..00000000 --- a/manager/burrow-mint/state/tx_cache_test.go +++ /dev/null @@ -1,36 +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 state - -import ( - "bytes" - "testing" - - "github.com/tendermint/go-wire" -) - -func TestStateToFromVMAccount(t *testing.T) { - acmAcc1, _ := RandAccount(true, 456) - vmAcc := toVMAccount(acmAcc1) - acmAcc2 := toStateAccount(vmAcc) - - acmAcc1Bytes := wire.BinaryBytes(acmAcc1) - acmAcc2Bytes := wire.BinaryBytes(acmAcc2) - if !bytes.Equal(acmAcc1Bytes, acmAcc2Bytes) { - t.Errorf("Unexpected account wire bytes\n%X vs\n%X", - acmAcc1Bytes, acmAcc2Bytes) - } - -} diff --git a/manager/burrow-mint/transactor.go b/manager/burrow-mint/transactor.go deleted file mode 100644 index b4f16d89..00000000 --- a/manager/burrow-mint/transactor.go +++ /dev/null @@ -1,438 +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 burrowmint - -import ( - "bytes" - "encoding/hex" - "fmt" - "sync" - "time" - - "github.com/hyperledger/burrow/account" - core_types "github.com/hyperledger/burrow/core/types" - "github.com/hyperledger/burrow/event" - "github.com/hyperledger/burrow/manager/burrow-mint/evm" - "github.com/hyperledger/burrow/manager/burrow-mint/state" - "github.com/hyperledger/burrow/txs" - "github.com/hyperledger/burrow/word256" - - "github.com/tendermint/go-crypto" - tEvents "github.com/tendermint/go-events" -) - -// Transactor is part of the pipe for BurrowMint and provides the implementation -// for the pipe to call into the BurrowMint application -type transactor struct { - chainID string - eventSwitch tEvents.Fireable - burrowMint *BurrowMint - eventEmitter event.EventEmitter - txMtx *sync.Mutex - txBroadcaster func(tx txs.Tx) error -} - -func newTransactor(chainID string, eventSwitch tEvents.Fireable, - burrowMint *BurrowMint, eventEmitter event.EventEmitter, - txBroadcaster func(tx txs.Tx) error) *transactor { - return &transactor{ - chainID, - eventSwitch, - burrowMint, - eventEmitter, - &sync.Mutex{}, - txBroadcaster, - } -} - -// Run a contract's code on an isolated and unpersisted state -// Cannot be used to create new contracts -// NOTE: this function is used from 1337 and has sibling on 46657 -// in pipe.go -// TODO: [ben] resolve incompatibilities in byte representation for 0.12.0 release -func (this *transactor) Call(fromAddress, toAddress, data []byte) ( - *core_types.Call, error) { - - st := this.burrowMint.GetState() - cache := state.NewBlockCache(st) // XXX: DON'T MUTATE THIS CACHE (used internally for CheckTx) - outAcc := cache.GetAccount(toAddress) - if outAcc == nil { - return nil, fmt.Errorf("Account %X does not exist", toAddress) - } - if fromAddress == nil { - fromAddress = []byte{} - } - callee := toVMAccount(outAcc) - caller := &vm.Account{Address: word256.LeftPadWord256(fromAddress)} - txCache := state.NewTxCache(cache) - gasLimit := st.GetGasLimit() - params := vm.Params{ - BlockHeight: int64(st.LastBlockHeight), - BlockHash: word256.LeftPadWord256(st.LastBlockHash), - BlockTime: st.LastBlockTime.Unix(), - GasLimit: gasLimit, - } - - vmach := vm.NewVM(txCache, vm.DefaultDynamicMemoryProvider, params, - caller.Address, nil) - vmach.SetFireable(this.eventSwitch) - gas := gasLimit - ret, err := vmach.Call(caller, callee, callee.Code, data, 0, &gas) - if err != nil { - return nil, err - } - gasUsed := gasLimit - gas - // here return bytes are hex encoded; on the sibling function - // they are not - return &core_types.Call{Return: hex.EncodeToString(ret), GasUsed: gasUsed}, nil -} - -// Run the given code on an isolated and unpersisted state -// Cannot be used to create new contracts. -func (this *transactor) CallCode(fromAddress, code, data []byte) ( - *core_types.Call, error) { - if fromAddress == nil { - fromAddress = []byte{} - } - cache := this.burrowMint.GetCheckCache() // XXX: DON'T MUTATE THIS CACHE (used internally for CheckTx) - callee := &vm.Account{Address: word256.LeftPadWord256(fromAddress)} - caller := &vm.Account{Address: word256.LeftPadWord256(fromAddress)} - txCache := state.NewTxCache(cache) - st := this.burrowMint.GetState() // for block height, time - gasLimit := st.GetGasLimit() - params := vm.Params{ - BlockHeight: int64(st.LastBlockHeight), - BlockHash: word256.LeftPadWord256(st.LastBlockHash), - BlockTime: st.LastBlockTime.Unix(), - GasLimit: gasLimit, - } - - vmach := vm.NewVM(txCache, vm.DefaultDynamicMemoryProvider, params, - caller.Address, nil) - gas := gasLimit - ret, err := vmach.Call(caller, callee, code, data, 0, &gas) - if err != nil { - return nil, err - } - gasUsed := gasLimit - gas - // here return bytes are hex encoded; on the sibling function - // they are not - return &core_types.Call{Return: hex.EncodeToString(ret), GasUsed: gasUsed}, nil -} - -// Broadcast a transaction. -func (this *transactor) BroadcastTx(tx txs.Tx) (*txs.Receipt, error) { - err := this.txBroadcaster(tx) - - if err != nil { - return nil, fmt.Errorf("Error broadcasting transaction: %v", err) - } - - txHash := txs.TxHash(this.chainID, tx) - var createsContract uint8 - var contractAddr []byte - // check if creates new contract - if callTx, ok := tx.(*txs.CallTx); ok { - if len(callTx.Address) == 0 { - createsContract = 1 - contractAddr = state.NewContractAddress(callTx.Input.Address, callTx.Input.Sequence) - } - } - return &txs.Receipt{txHash, createsContract, contractAddr}, nil -} - -// Orders calls to BroadcastTx using lock (waits for response from core before releasing) -func (this *transactor) Transact(privKey, address, data []byte, gasLimit, - fee int64) (*txs.Receipt, error) { - var addr []byte - if len(address) == 0 { - addr = nil - } else if len(address) != 20 { - return nil, fmt.Errorf("Address is not of the right length: %d\n", len(address)) - } else { - addr = address - } - if len(privKey) != 64 { - return nil, fmt.Errorf("Private key is not of the right length: %d\n", len(privKey)) - } - this.txMtx.Lock() - defer this.txMtx.Unlock() - pa := account.GenPrivAccountFromPrivKeyBytes(privKey) - cache := this.burrowMint.GetCheckCache() // XXX: DON'T MUTATE THIS CACHE (used internally for CheckTx) - acc := cache.GetAccount(pa.Address) - var sequence int - if acc == nil { - sequence = 1 - } else { - sequence = acc.Sequence + 1 - } - // TODO: [Silas] we should consider revising this method and removing fee, or - // possibly adding an amount parameter. It is non-sensical to just be able to - // set the fee. Our support of fees in general is questionable since at the - // moment all we do is deduct the fee effectively leaking token. It is possible - // someone may be using the sending of native token to payable functions but - // they can be served by broadcasting a token. - - // We hard-code the amount to be equal to the fee which means the CallTx we - // generate transfers 0 value, which is the most sensible default since in - // recent solidity compilers the EVM generated will throw an error if value - // is transferred to a non-payable function. - txInput := &txs.TxInput{ - Address: pa.Address, - Amount: fee, - Sequence: sequence, - PubKey: pa.PubKey, - } - tx := &txs.CallTx{ - Input: txInput, - Address: addr, - GasLimit: gasLimit, - Fee: fee, - Data: data, - } - - // Got ourselves a tx. - txS, errS := this.SignTx(tx, []*account.PrivAccount{pa}) - if errS != nil { - return nil, errS - } - return this.BroadcastTx(txS) -} - -func (this *transactor) TransactAndHold(privKey, address, data []byte, gasLimit, fee int64) (*txs.EventDataCall, error) { - rec, tErr := this.Transact(privKey, address, data, gasLimit, fee) - if tErr != nil { - return nil, tErr - } - var addr []byte - if rec.CreatesContract == 1 { - addr = rec.ContractAddr - } else { - addr = address - } - // We want non-blocking on the first event received (but buffer the value), - // after which we want to block (and then discard the value - see below) - wc := make(chan *txs.EventDataCall, 1) - subId := fmt.Sprintf("%X", rec.TxHash) - this.eventEmitter.Subscribe(subId, txs.EventStringAccCall(addr), - func(evt txs.EventData) { - eventDataCall := evt.(txs.EventDataCall) - if bytes.Equal(eventDataCall.TxID, rec.TxHash) { - // Beware the contract of go-events subscribe is that we must not be - // blocking in an event callback when we try to unsubscribe! - // We work around this by using a non-blocking send. - select { - // This is a non-blocking send, but since we are using a buffered - // channel of size 1 we will always grab our first event even if we - // haven't read from the channel at the time we receive the first event. - case wc <- &eventDataCall: - default: - } - } - }) - - timer := time.NewTimer(300 * time.Second) - toChan := timer.C - - var ret *txs.EventDataCall - var rErr error - - select { - case <-toChan: - rErr = fmt.Errorf("Transaction timed out. Hash: " + subId) - case e := <-wc: - timer.Stop() - if e.Exception != "" { - rErr = fmt.Errorf("Error when transacting: " + e.Exception) - } else { - ret = e - } - } - this.eventEmitter.Unsubscribe(subId) - return ret, rErr -} - -func (this *transactor) Send(privKey, toAddress []byte, - amount int64) (*txs.Receipt, error) { - var toAddr []byte - if len(toAddress) == 0 { - toAddr = nil - } else if len(toAddress) != 20 { - return nil, fmt.Errorf("To-address is not of the right length: %d\n", - len(toAddress)) - } else { - toAddr = toAddress - } - - if len(privKey) != 64 { - return nil, fmt.Errorf("Private key is not of the right length: %d\n", - len(privKey)) - } - - pk := &[64]byte{} - copy(pk[:], privKey) - this.txMtx.Lock() - defer this.txMtx.Unlock() - pa := account.GenPrivAccountFromPrivKeyBytes(privKey) - cache := this.burrowMint.GetState() - acc := cache.GetAccount(pa.Address) - var sequence int - if acc == nil { - sequence = 1 - } else { - sequence = acc.Sequence + 1 - } - - tx := txs.NewSendTx() - - txInput := &txs.TxInput{ - Address: pa.Address, - Amount: amount, - Sequence: sequence, - PubKey: pa.PubKey, - } - - tx.Inputs = append(tx.Inputs, txInput) - - txOutput := &txs.TxOutput{toAddr, amount} - - tx.Outputs = append(tx.Outputs, txOutput) - - // Got ourselves a tx. - txS, errS := this.SignTx(tx, []*account.PrivAccount{pa}) - if errS != nil { - return nil, errS - } - return this.BroadcastTx(txS) -} - -func (this *transactor) SendAndHold(privKey, toAddress []byte, - amount int64) (*txs.Receipt, error) { - rec, tErr := this.Send(privKey, toAddress, amount) - if tErr != nil { - return nil, tErr - } - - wc := make(chan *txs.SendTx) - subId := fmt.Sprintf("%X", rec.TxHash) - - this.eventEmitter.Subscribe(subId, txs.EventStringAccOutput(toAddress), - func(evt txs.EventData) { - event := evt.(txs.EventDataTx) - tx := event.Tx.(*txs.SendTx) - wc <- tx - }) - - timer := time.NewTimer(300 * time.Second) - toChan := timer.C - - var rErr error - - pa := account.GenPrivAccountFromPrivKeyBytes(privKey) - - select { - case <-toChan: - rErr = fmt.Errorf("Transaction timed out. Hash: " + subId) - case e := <-wc: - if bytes.Equal(e.Inputs[0].Address, pa.Address) && e.Inputs[0].Amount == amount { - timer.Stop() - this.eventEmitter.Unsubscribe(subId) - return rec, rErr - } - } - return nil, rErr -} - -func (this *transactor) TransactNameReg(privKey []byte, name, data string, - amount, fee int64) (*txs.Receipt, error) { - - if len(privKey) != 64 { - return nil, fmt.Errorf("Private key is not of the right length: %d\n", len(privKey)) - } - this.txMtx.Lock() - defer this.txMtx.Unlock() - pa := account.GenPrivAccountFromPrivKeyBytes(privKey) - cache := this.burrowMint.GetCheckCache() // XXX: DON'T MUTATE THIS CACHE (used internally for CheckTx) - acc := cache.GetAccount(pa.Address) - var sequence int - if acc == nil { - sequence = 1 - } else { - sequence = acc.Sequence + 1 - } - tx := txs.NewNameTxWithNonce(pa.PubKey, name, data, amount, fee, sequence) - // Got ourselves a tx. - txS, errS := this.SignTx(tx, []*account.PrivAccount{pa}) - if errS != nil { - return nil, errS - } - return this.BroadcastTx(txS) -} - -// Sign a transaction -func (this *transactor) SignTx(tx txs.Tx, privAccounts []*account.PrivAccount) (txs.Tx, error) { - // more checks? - - for i, privAccount := range privAccounts { - if privAccount == nil || privAccount.PrivKey == nil { - return nil, fmt.Errorf("Invalid (empty) privAccount @%v", i) - } - } - switch tx.(type) { - case *txs.NameTx: - nameTx := tx.(*txs.NameTx) - nameTx.Input.PubKey = privAccounts[0].PubKey - nameTx.Input.Signature = privAccounts[0].Sign(this.chainID, nameTx) - case *txs.SendTx: - sendTx := tx.(*txs.SendTx) - for i, input := range sendTx.Inputs { - input.PubKey = privAccounts[i].PubKey - input.Signature = privAccounts[i].Sign(this.chainID, sendTx) - } - case *txs.CallTx: - callTx := tx.(*txs.CallTx) - callTx.Input.PubKey = privAccounts[0].PubKey - callTx.Input.Signature = privAccounts[0].Sign(this.chainID, callTx) - case *txs.BondTx: - bondTx := tx.(*txs.BondTx) - // the first privaccount corresponds to the BondTx pub key. - // the rest to the inputs - bondTx.Signature = privAccounts[0].Sign(this.chainID, bondTx).(crypto.SignatureEd25519) - for i, input := range bondTx.Inputs { - input.PubKey = privAccounts[i+1].PubKey - input.Signature = privAccounts[i+1].Sign(this.chainID, bondTx) - } - case *txs.UnbondTx: - unbondTx := tx.(*txs.UnbondTx) - unbondTx.Signature = privAccounts[0].Sign(this.chainID, unbondTx).(crypto.SignatureEd25519) - case *txs.RebondTx: - rebondTx := tx.(*txs.RebondTx) - rebondTx.Signature = privAccounts[0].Sign(this.chainID, rebondTx).(crypto.SignatureEd25519) - default: - return nil, fmt.Errorf("Object is not a proper transaction: %v\n", tx) - } - return tx, nil -} - -// No idea what this does. -func toVMAccount(acc *account.Account) *vm.Account { - return &vm.Account{ - Address: word256.LeftPadWord256(acc.Address), - Balance: acc.Balance, - Code: acc.Code, - Nonce: int64(acc.Sequence), - Other: acc.PubKey, - } -} diff --git a/manager/burrow-mint/version.go b/manager/burrow-mint/version.go deleted file mode 100644 index 888de79c..00000000 --- a/manager/burrow-mint/version.go +++ /dev/null @@ -1,53 +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 burrowmint - -import ( - "fmt" - - version "github.com/hyperledger/burrow/version" -) - -const ( - // Client identifier to advertise over the network - burrowMintClientIdentifier = "burrowmint" - // Major version component of the current release - burrowMintVersionMajor = 0 - // Minor version component of the current release - burrowMintVersionMinor = 17 - // Patch version component of the current release - burrowMintVersionPatch = 0 -) - -// Define the compatible consensus engines this application manager -// is compatible and has been tested with. -var compatibleConsensus = [...]string{ - "tendermint-0.9", -} - -func GetBurrowMintVersion() *version.VersionIdentifier { - return version.New(burrowMintClientIdentifier, burrowMintVersionMajor, - burrowMintVersionMinor, burrowMintVersionPatch) -} - -func AssertCompatibleConsensus(consensusMinorVersion string) error { - for _, supported := range compatibleConsensus { - if consensusMinorVersion == supported { - return nil - } - } - return fmt.Errorf("BurrowMint (%s) is not compatible with consensus engine %s", - GetBurrowMintVersion(), consensusMinorVersion) -} diff --git a/manager/manager.go b/manager/manager.go deleted file mode 100644 index 2724b126..00000000 --- a/manager/manager.go +++ /dev/null @@ -1,44 +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 manager - -import ( - "fmt" - - events "github.com/tendermint/go-events" - - config "github.com/hyperledger/burrow/config" - definitions "github.com/hyperledger/burrow/definitions" - burrowmint "github.com/hyperledger/burrow/manager/burrow-mint" - - "github.com/hyperledger/burrow/logging" - logging_types "github.com/hyperledger/burrow/logging/types" -) - -// NewApplicationPipe returns an initialised Pipe interface -// based on the loaded module configuration file. -// NOTE: [ben] Currently we only have a single `generic` definition -// of an application. It is feasible this will be insufficient to support -// different types of applications later down the line. -func NewApplicationPipe(moduleConfig *config.ModuleConfig, - evsw events.EventSwitch, - logger logging_types.InfoTraceLogger) (definitions.Pipe, error) { - switch moduleConfig.Name { - case "burrowmint": - logging.InfoMsg(logger, "Loading BurrowMint") - return burrowmint.NewBurrowMintPipe(moduleConfig, evsw, logger) - } - return nil, fmt.Errorf("Failed to return Pipe for %s", moduleConfig.Name) -} diff --git a/manager/types/application.go b/manager/types/application.go deleted file mode 100644 index 2025742b..00000000 --- a/manager/types/application.go +++ /dev/null @@ -1,104 +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 types - -import ( - // TODO: [ben] this is currently only used for abci result type; but should - // be removed as abci dependencies shouldn't feature in the application - // manager - consensus_types "github.com/hyperledger/burrow/consensus/types" - abci "github.com/tendermint/abci/types" -) - -// NOTE: [ben] this interface is likely to be changed. Currently it is taken -// from the tendermint socket protocol application interface; - -// Application interface applies transactions to the state. -type Application interface { - - // Info returns application information as a string - // NOTE: [ben] likely to move - Info() (info abci.ResponseInfo) - - // Set application option (e.g. mode=mempool, mode=consensus) - // NOTE: [ben] taken from tendermint, but it is unclear what the use is, - // specifically, when will tendermint call this over abci ? - SetOption(key string, value string) (log string) - - // Append transaction applies a transaction to the state regardless of - // whether the transaction is valid or not. - // Currently AppendTx is taken from abci, and returns a result. - // This will be altered, as AppendTransaction needs to more strongly reflect - // the theoretical logic: - // Append(StateN, Transaction) = StateN+1 - // here invalid transactions are allowed, but should act as the identity on - // the state: - // Append(StateN, InvalidTransaction) = StateN - // TODO: implementation notes: - // 1. at this point the transaction should already be strongly typed - // 2. - DeliverTx(tx []byte) abci.Result - - // Check Transaction validates a transaction before being allowed into the - // consensus' engine memory pool. This is the original defintion and - // intention as taken from abci, but should be remapped to the more - // general concept of basic, cheap verification; - // Check Transaction does not alter the state, but does require an immutable - // copy of the state. In particular there is no consensus on ordering yet. - // TODO: implementation notes: - // 1. at this point the transaction should already be strongly typed - // 2. - CheckTx(tx []byte) abci.Result - - // Commit returns the root hash of the current application state - // NOTE: [ben] Because the concept of the block has been erased here - // the commit root hash is a fully implict stateful function; - // the opposit the principle of explicit stateless functions. - // This will be amended when we introduce the concept of (streaming) - // blocks in the pipe. - Commit() abci.Result - - // Query for state. This query request is not passed over the p2p network - // and is called from Tenderpmint rpc directly up to the application. - // NOTE: [ben] burrow will give preference to queries from the local client - // directly over the burrow rpc. - // We will support this for Tendermint compatibility. - Query(reqQuery abci.RequestQuery) abci.ResponseQuery - - // Tendermint acbi_types.Application extends our base definition of an - // Application with a parenthetical (begin/end) streaming block interface - - // Initialise the blockchain - // When Tendermint initialises the genesis validators from tendermint core - // are passed in as validators - InitChain(validators []*abci.Validator) - - // Signals the beginning of communicating a block (all transactions have been - // closed into the block already - BeginBlock(hash []byte, header *abci.Header) - - // Signals the end of a blockchain - // ResponseEndBlock wraps a slice of Validators with the Diff field. A Validator - // is a public key and a voting power. Returning a Validator within this slice - // asks Tendermint to set that validator's voting power to the Power provided. - // Note: although the field is named 'Diff' the intention is that it declares - // the what the new voting power should be (for validators specified, - // those omitted are left alone) it is not an relative increment to - // be added (or subtracted) from voting power. - EndBlock(height uint64) abci.ResponseEndBlock - - // Is this the passed ConsensusEngine compatible with this manager - CompatibleConsensus(consensusEngine consensus_types.ConsensusEngine) bool -} diff --git a/rpc/v0/rest_server_pipe_test.go b/rpc/v0/rest_server_pipe_test.go deleted file mode 100644 index 1cd82822..00000000 --- a/rpc/v0/rest_server_pipe_test.go +++ /dev/null @@ -1,313 +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 v0 - -import ( - "fmt" - - account "github.com/hyperledger/burrow/account" - core_types "github.com/hyperledger/burrow/core/types" - definitions "github.com/hyperledger/burrow/definitions" - event "github.com/hyperledger/burrow/event" - - blockchain_types "github.com/hyperledger/burrow/blockchain/types" - consensus_types "github.com/hyperledger/burrow/consensus/types" - logging_types "github.com/hyperledger/burrow/logging/types" - manager_types "github.com/hyperledger/burrow/manager/types" - "github.com/hyperledger/burrow/txs" - - "github.com/hyperledger/burrow/logging/loggers" - abci_types "github.com/tendermint/abci/types" - "github.com/tendermint/go-crypto" - "github.com/tendermint/go-p2p" - mintTypes "github.com/tendermint/tendermint/types" -) - -// Base struct. -type MockPipe struct { - testData TestData - accounts definitions.Accounts - blockchain blockchain_types.Blockchain - consensusEngine consensus_types.ConsensusEngine - events event.EventEmitter - namereg definitions.NameReg - transactor definitions.Transactor - logger logging_types.InfoTraceLogger -} - -// Create a new mock tendermint pipe. -func NewMockPipe(td *TestData) definitions.Pipe { - return &MockPipe{ - testData: *td, - accounts: &accounts{td}, - blockchain: &chain{td}, - consensusEngine: &consensusEngine{td}, - events: &eventer{td}, - namereg: &namereg{td}, - transactor: &transactor{td}, - logger: loggers.NewNoopInfoTraceLogger(), - } -} - -// Create a mock pipe with default mock data. -func NewDefaultMockPipe() definitions.Pipe { - return NewMockPipe(LoadTestData()) -} - -func (pipe *MockPipe) Accounts() definitions.Accounts { - return pipe.accounts -} - -func (pipe *MockPipe) Blockchain() blockchain_types.Blockchain { - return pipe.blockchain -} - -func (pipe *MockPipe) Events() event.EventEmitter { - return pipe.events -} - -func (pipe *MockPipe) NameReg() definitions.NameReg { - return pipe.namereg -} - -func (pipe *MockPipe) Transactor() definitions.Transactor { - return pipe.transactor -} - -func (pipe *MockPipe) Logger() logging_types.InfoTraceLogger { - return pipe.logger -} - -func (pipe *MockPipe) GetApplication() manager_types.Application { - // TODO: [ben] mock application - return nil -} - -func (pipe *MockPipe) SetConsensusEngine(_ consensus_types.ConsensusEngine) error { - // TODO: [ben] mock consensus engine - return nil -} - -func (pipe *MockPipe) GetConsensusEngine() consensus_types.ConsensusEngine { - return pipe.consensusEngine -} - -func (pipe *MockPipe) SetBlockchain(_ blockchain_types.Blockchain) error { - // TODO: [ben] mock consensus engine - return nil -} - -func (pipe *MockPipe) GetBlockchain() blockchain_types.Blockchain { - return nil -} - -func (pipe *MockPipe) GetTendermintPipe() (definitions.TendermintPipe, error) { - return nil, fmt.Errorf("Tendermint pipe is not supported by mocked pipe.") -} - -func (pipe *MockPipe) GenesisHash() []byte { - return pipe.testData.GetGenesisHash.Output.Hash -} - -// Components - -// Accounts -type accounts struct { - testData *TestData -} - -func (acc *accounts) GenPrivAccount() (*account.PrivAccount, error) { - return acc.testData.GenPrivAccount.Output, nil -} - -func (acc *accounts) GenPrivAccountFromKey(key []byte) (*account.PrivAccount, error) { - return acc.testData.GenPrivAccount.Output, nil -} - -func (acc *accounts) Accounts([]*event.FilterData) (*core_types.AccountList, error) { - return acc.testData.GetAccounts.Output, nil -} - -func (acc *accounts) Account(address []byte) (*account.Account, error) { - return acc.testData.GetAccount.Output, nil -} - -func (acc *accounts) Storage(address []byte) (*core_types.Storage, error) { - return acc.testData.GetStorage.Output, nil -} - -func (acc *accounts) StorageAt(address, key []byte) (*core_types.StorageItem, error) { - return acc.testData.GetStorageAt.Output, nil -} - -// Blockchain -type chain struct { - testData *TestData -} - -func (this *chain) ChainId() string { - return this.testData.GetChainId.Output.ChainId -} - -func (this *chain) Height() int { - return this.testData.GetLatestBlockHeight.Output.Height -} - -func (this *chain) Block(height int) *mintTypes.Block { - return this.testData.GetBlock.Output -} - -func (this *chain) BlockMeta(height int) *mintTypes.BlockMeta { - return &mintTypes.BlockMeta{} -} - -// Consensus -type consensusEngine struct { - testData *TestData -} - -func (cons *consensusEngine) BroadcastTransaction(transaction []byte, - callback func(*abci_types.Response)) error { - return nil -} - -func (cons *consensusEngine) IsListening() bool { - return cons.testData.IsListening.Output.Listening -} - -func (cons *consensusEngine) Listeners() []p2p.Listener { - p2pListeners := make([]p2p.Listener, 0) - - for _, name := range cons.testData.GetListeners.Output.Listeners { - p2pListeners = append(p2pListeners, p2p.NewDefaultListener("tcp", name, true)) - } - - return p2pListeners -} - -func (cons *consensusEngine) NodeInfo() *p2p.NodeInfo { - return &p2p.NodeInfo{ - Version: cons.testData.GetNetworkInfo.Output.ClientVersion, - Moniker: cons.testData.GetNetworkInfo.Output.Moniker, - } -} - -func (cons *consensusEngine) Peers() []*consensus_types.Peer { - return cons.testData.GetPeers.Output -} - -func (cons *consensusEngine) PublicValidatorKey() crypto.PubKey { - return crypto.PubKeyEd25519{ - 1, 2, 3, 4, 5, 6, 7, 8, - 1, 2, 3, 4, 5, 6, 7, 8, - 1, 2, 3, 4, 5, 6, 7, 8, - 1, 2, 3, 4, 5, 6, 7, 8, - } -} - -func (cons *consensusEngine) Events() event.EventEmitter { - return nil -} - -func (cons *consensusEngine) ListUnconfirmedTxs(maxTxs int) ([]txs.Tx, error) { - return cons.testData.GetUnconfirmedTxs.Output.Txs, nil -} - -func (cons *consensusEngine) ListValidators() []consensus_types.Validator { - return nil -} - -func (cons *consensusEngine) ConsensusState() *consensus_types.ConsensusState { - return &consensus_types.ConsensusState{} -} - -func (cons *consensusEngine) PeerConsensusStates() map[string]string { - return map[string]string{} -} - -func (cons *consensusEngine) Stop() bool { - return true -} - -// Events -type eventer struct { - testData *TestData -} - -func (evntr *eventer) Subscribe(subId, event string, callback func(txs.EventData)) error { - return nil -} - -func (evntr *eventer) Unsubscribe(subId string) error { - return nil -} - -// NameReg -type namereg struct { - testData *TestData -} - -func (nmreg *namereg) Entry(key string) (*core_types.NameRegEntry, error) { - return nmreg.testData.GetNameRegEntry.Output, nil -} - -func (nmreg *namereg) Entries(filters []*event.FilterData) (*core_types.ResultListNames, error) { - return nmreg.testData.GetNameRegEntries.Output, nil -} - -// Txs -type transactor struct { - testData *TestData -} - -func (trans *transactor) Call(fromAddress, toAddress, data []byte) (*core_types.Call, error) { - return trans.testData.Call.Output, nil -} - -func (trans *transactor) CallCode(from, code, data []byte) (*core_types.Call, error) { - return trans.testData.CallCode.Output, nil -} - -func (trans *transactor) BroadcastTx(tx txs.Tx) (*txs.Receipt, error) { - receipt := txs.GenerateReceipt(trans.testData.GetChainId.Output.ChainId, tx) - return &receipt, nil -} - -func (trans *transactor) Transact(privKey, address, data []byte, gasLimit, fee int64) (*txs.Receipt, error) { - if len(address) == 0 { - return trans.testData.TransactCreate.Output, nil - } - return trans.testData.Transact.Output, nil -} - -func (trans *transactor) TransactAndHold(privKey, address, data []byte, gasLimit, fee int64) (*txs.EventDataCall, error) { - return nil, nil -} - -func (trans *transactor) Send(privKey, toAddress []byte, amount int64) (*txs.Receipt, error) { - return nil, nil -} - -func (trans *transactor) SendAndHold(privKey, toAddress []byte, amount int64) (*txs.Receipt, error) { - return nil, nil -} - -func (trans *transactor) TransactNameReg(privKey []byte, name, data string, amount, fee int64) (*txs.Receipt, error) { - return trans.testData.TransactNameReg.Output, nil -} - -func (trans *transactor) SignTx(tx txs.Tx, privAccounts []*account.PrivAccount) (txs.Tx, error) { - return nil, nil -} diff --git a/util/snatives/cmd/main.go b/util/snatives/cmd/main.go index 16711bd0..a20bbe71 100644 --- a/util/snatives/cmd/main.go +++ b/util/snatives/cmd/main.go @@ -17,13 +17,13 @@ package main import ( "fmt" - "github.com/hyperledger/burrow/manager/burrow-mint/evm" + "github.com/hyperledger/burrow/execution/evm" "github.com/hyperledger/burrow/util/snatives/templates" ) // Dump SNative contracts func main() { - contracts := vm.SNativeContracts() + contracts := evm.SNativeContracts() // Index of next contract i := 1 fmt.Print("pragma solidity >=0.0.0;\n\n") diff --git a/util/snatives/templates/solidity_templates.go b/util/snatives/templates/solidity_templates.go index 165a2e6a..fec8861a 100644 --- a/util/snatives/templates/solidity_templates.go +++ b/util/snatives/templates/solidity_templates.go @@ -20,7 +20,7 @@ import ( "strings" "text/template" - "github.com/hyperledger/burrow/manager/burrow-mint/evm" + "github.com/hyperledger/burrow/execution/evm" ) const contractTemplateText = `/** @@ -63,11 +63,11 @@ func init() { } type solidityContract struct { - *vm.SNativeContractDescription + *evm.SNativeContractDescription } type solidityFunction struct { - *vm.SNativeFunctionDescription + *evm.SNativeFunctionDescription } // @@ -75,7 +75,7 @@ type solidityFunction struct { // // Create a templated solidityContract from an SNative contract description -func NewSolidityContract(contract *vm.SNativeContractDescription) *solidityContract { +func NewSolidityContract(contract *evm.SNativeContractDescription) *solidityContract { return &solidityContract{contract} } @@ -95,7 +95,7 @@ func (contract *solidityContract) InstanceName() string { } func (contract *solidityContract) Address() string { - return fmt.Sprintf("0x%x", + return fmt.Sprintf("0x%s", contract.SNativeContractDescription.Address()) } @@ -123,7 +123,7 @@ func (contract *solidityContract) Functions() []*solidityFunction { // // Create a templated solidityFunction from an SNative function description -func NewSolidityFunction(function *vm.SNativeFunctionDescription) *solidityFunction { +func NewSolidityFunction(function *evm.SNativeFunctionDescription) *solidityFunction { return &solidityFunction{function} } diff --git a/util/snatives/templates/solidity_templates_test.go b/util/snatives/templates/solidity_templates_test.go index 58fe37f7..998aa11f 100644 --- a/util/snatives/templates/solidity_templates_test.go +++ b/util/snatives/templates/solidity_templates_test.go @@ -18,12 +18,12 @@ import ( "fmt" "testing" - "github.com/hyperledger/burrow/manager/burrow-mint/evm" + "github.com/hyperledger/burrow/execution/evm" "github.com/stretchr/testify/assert" ) func TestSNativeFuncTemplate(t *testing.T) { - contract := vm.SNativeContracts()["Permissions"] + contract := evm.SNativeContracts()["Permissions"] function, err := contract.FunctionByName("removeRole") if err != nil { t.Fatal("Couldn't get function") @@ -37,7 +37,7 @@ func TestSNativeFuncTemplate(t *testing.T) { // This test checks that we can generate the SNative contract interface and // prints it to stdout func TestSNativeContractTemplate(t *testing.T) { - contract := vm.SNativeContracts()["Permissions"] + contract := evm.SNativeContracts()["Permissions"] solidityContract := NewSolidityContract(contract) solidity, err := solidityContract.Solidity() assert.NoError(t, err) -- GitLab