diff --git a/account/address.go b/account/address.go index 2340afd48a4fd4c4b52f8beceddc29dd5e1481b6..70353baccef9a398392e0a7be6d596e611523064 100644 --- a/account/address.go +++ b/account/address.go @@ -1,6 +1,7 @@ package account import ( + "bytes" "encoding/json" "fmt" @@ -11,6 +12,19 @@ import ( type Address binary.Word160 +type Addresses []Address + +func (as Addresses) Len() int { + return len(as) +} + +func (as Addresses) Less(i, j int) bool { + return bytes.Compare(as[i][:], as[j][:]) < 0 +} +func (as Addresses) Swap(i, j int) { + as[i], as[j] = as[j], as[i] +} + const AddressHexLength = 2 * binary.Word160Length var ZeroAddress = Address{} diff --git a/account/address_test.go b/account/address_test.go index 3ea47daa8672bfbd3528bc749732bc1f17c10cd7..5a1572b34d1d763435b2d3961d597ab9a56dd1e5 100644 --- a/account/address_test.go +++ b/account/address_test.go @@ -1,9 +1,9 @@ package account import ( - "testing" - "encoding/json" + "sort" + "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -70,3 +70,19 @@ func TestAddress_Length(t *testing.T) { err = addrOut.UnmarshalText(([]byte)("49EA30FCAE731BDE36742F85901549F515EA1A1020")) assert.Error(t, err, "address too long") } + +func TestAddress_Sort(t *testing.T) { + addresses := Addresses{ + {2, 3, 4}, + {3, 1, 2}, + {2, 1, 2}, + } + sorted := make(Addresses, len(addresses)) + copy(sorted, addresses) + sort.Stable(sorted) + assert.Equal(t, Addresses{ + {2, 1, 2}, + {2, 3, 4}, + {3, 1, 2}, + }, sorted) +} diff --git a/account/state_cache.go b/account/state_cache.go new file mode 100644 index 0000000000000000000000000000000000000000..723126a2a1e66b2e31513f8f64f1e2234174f751 --- /dev/null +++ b/account/state_cache.go @@ -0,0 +1,191 @@ +// 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 account + +import ( + "fmt" + "sort" + + "github.com/hyperledger/burrow/binary" +) + +type StateCache struct { + backend StateReader + accounts map[Address]*accountInfo +} + +type accountInfo struct { + account Account + storage map[binary.Word256]binary.Word256 + removed bool + updated bool +} + +var _ StateWriter = &StateCache{} + +func NewStateCache(backend StateReader) *StateCache { + return &StateCache{ + backend: backend, + accounts: make(map[Address]*accountInfo), + } +} + +func (cache *StateCache) GetAccount(address Address) (Account, error) { + accInfo, err := cache.get(address) + if err != nil { + return nil, err + } + if accInfo.removed { + return nil, nil + } + + if accInfo.account == nil { + // fill cache + account, err := cache.backend.GetAccount(address) + if err != nil { + return nil, err + } + accInfo.account = account + } + return accInfo.account, nil +} + +func (cache *StateCache) UpdateAccount(account Account) error { + accInfo, err := cache.get(account.Address()) + if err != nil { + return err + } + if accInfo.removed { + return fmt.Errorf("UpdateAccount on a removed account %s", account.Address()) + } + accInfo.account = account + accInfo.updated = true + return nil +} + +func (cache *StateCache) RemoveAccount(address Address) error { + accInfo, err := cache.get(address) + if err != nil { + return err + } + if accInfo.removed { + fmt.Errorf("RemoveAccount on a removed account %s", address) + } else { + accInfo.removed = true + } + return nil +} + +func (cache *StateCache) GetStorage(address Address, key binary.Word256) (binary.Word256, error) { + accInfo, err := cache.get(address) + if err != nil { + return binary.Zero256, err + } + // Check cache + value, ok := accInfo.storage[key] + if ok { + return value, nil + } else { + // Load from backend + value, err := cache.backend.GetStorage(address, key) + if err != nil { + return binary.Zero256, err + } + accInfo.storage[key] = value + return value, nil + } +} + +// NOTE: Set value to zero to remove. +func (cache *StateCache) SetStorage(address Address, key binary.Word256, value binary.Word256) error { + accInfo, err := cache.get(address) + if err != nil { + return err + } + if accInfo.removed { + return fmt.Errorf("SetStorage on a removed account %s", address) + } + accInfo.storage[key] = value + accInfo.updated = true + return nil +} + +// Syncs changes to the backend in deterministic order. Sends storage updates before updating +// the account they belong so that storage values can be taken account of in the update. +func (cache *StateCache) Sync(state StateWriter) error { + var addresses Addresses + for address := range cache.accounts { + addresses = append(addresses, address) + } + + sort.Stable(addresses) + for _, address := range addresses { + accInfo := cache.accounts[address] + if accInfo.removed { + err := state.RemoveAccount(address) + if err != nil { + return err + } + } else if accInfo.updated { + var keys binary.Words256 + for key := range accInfo.storage { + keys = append(keys, key) + } + // First update keys + sort.Stable(keys) + for _, key := range keys { + value := accInfo.storage[key] + err := state.SetStorage(address, key, value) + if err != nil { + return err + } + } + // Update account - this gives backend the opportunity to update StorageRoot after calculating the new + // value from any storage value updates + err := state.UpdateAccount(accInfo.account) + if err != nil { + return err + } + } + } + return nil +} + +// Resets the cache to empty initialising the backing map to the same size as the previous iteration. +func (cache *StateCache) Reset(backend StateReader) { + cache.backend = backend + cache.accounts = make(map[Address]*accountInfo, len(cache.accounts)) +} + +func (cache *StateCache) Backend() StateReader { + return cache.backend +} + +// Get the cache accountInfo item creating it if necessary +func (cache *StateCache) get(address Address) (*accountInfo, error) { + accInfo := cache.accounts[address] + if accInfo == nil { + account, err := cache.backend.GetAccount(address) + if err != nil { + return nil, err + } + accInfo = &accountInfo{ + account: account, + storage: make(map[binary.Word256]binary.Word256), + } + cache.accounts[address] = accInfo + } + return accInfo, nil +} diff --git a/account/state_cache_test.go b/account/state_cache_test.go new file mode 100644 index 0000000000000000000000000000000000000000..cc40d7e94834e28c401dec199f0a11974d9bcd9b --- /dev/null +++ b/account/state_cache_test.go @@ -0,0 +1,62 @@ +package account + +import ( + "testing" + + "fmt" + + "github.com/hyperledger/burrow/binary" +) + +type testStateReader struct { + Accounts map[Address]Account + Storage map[Address]map[binary.Word256]binary.Word256 +} + +func accountAndStorage(account Account, keyvals ...binary.Word256) *testStateReader { + return &testStateReader{} + +} + +func (tsr *testStateReader) GetAccount(address Address) (Account, error) { + account, ok := tsr.Accounts[address] + if !ok { + return nil, fmt.Errorf("could not find account %s", address) + } + return account, nil +} + +func (tsr *testStateReader) GetStorage(address Address, key binary.Word256) (binary.Word256, error) { + storage, ok := tsr.Storage[address] + if !ok { + return binary.Zero256, fmt.Errorf("could not find storage for account %s", address) + } + value, ok := storage[key] + if !ok { + return binary.Zero256, fmt.Errorf("could not find key %x for account %s", key, address) + } + return value, nil +} + +var _ StateReader = &testStateReader{} + +func TestStateCache_GetAccount(t *testing.T) { +} + +func TestStateCache_UpdateAccount(t *testing.T) { +} + +func TestStateCache_RemoveAccount(t *testing.T) { +} + +func TestStateCache_GetStorage(t *testing.T) { +} + +func TestStateCache_SetStorage(t *testing.T) { +} + +func TestStateCache_Sync(t *testing.T) { +} + +func TestStateCache_get(t *testing.T) { +} diff --git a/binary/word256.go b/binary/word256.go index 02a90581f9c297f49c9fe08369755efa82694b3f..7bdbb4ec0e63921e44ff8b3191b2485aa3a8cb6f 100644 --- a/binary/word256.go +++ b/binary/word256.go @@ -114,6 +114,20 @@ func Int64FromWord256(word Word256) int64 { //------------------------------------- +type Words256 []Word256 + +func (ws Words256) Len() int { + return len(ws) +} + +func (ws Words256) Less(i, j int) bool { + return ws[i].Compare(ws[j]) < 0 +} + +func (ws Words256) Swap(i, j int) { + ws[i], ws[j] = ws[j], ws[i] +} + type Tuple256 struct { First Word256 Second Word256 diff --git a/execution/execution.go b/execution/execution.go index 0689e6c9be77a40901f6344f58be6e5db10005de..f62560d7b1f122fd2bc908729c299e4a16e1e094 100644 --- a/execution/execution.go +++ b/execution/execution.go @@ -320,7 +320,7 @@ func (exe *executor) Execute(tx txs.Tx) (err error) { callee acm.MutableAccount = nil // initialized below code []byte = nil ret []byte = nil - txCache = NewTxCache(exe.blockCache) + txCache = acm.NewStateCache(exe.blockCache) params = evm.Params{ BlockHeight: exe.tip.LastBlockHeight(), BlockHash: binary.LeftPadWord256(exe.tip.LastBlockHash()), diff --git a/execution/transactor.go b/execution/transactor.go index e527fe5fd227342176a32a71e8ebc8a050130098..e4642112a3d5cc1cc8102200aee34477915f3d1b 100644 --- a/execution/transactor.go +++ b/execution/transactor.go @@ -98,7 +98,7 @@ func (trans *transactor) Call(fromAddress, toAddress acm.Address, data []byte) ( return nil, fmt.Errorf("account %s does not exist", toAddress) } caller := acm.ConcreteAccount{Address: fromAddress}.MutableAccount() - txCache := NewTxCache(trans.state) + txCache := acm.NewStateCache(trans.state) params := vmParams(trans.blockchain) vmach := evm.NewVM(txCache, evm.DefaultDynamicMemoryProvider, params, caller.Address(), nil, @@ -120,7 +120,7 @@ func (trans *transactor) CallCode(fromAddress acm.Address, code, data []byte) (* // 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) + txCache := acm.NewStateCache(trans.state) params := vmParams(trans.blockchain) vmach := evm.NewVM(txCache, evm.DefaultDynamicMemoryProvider, params, caller.Address(), nil, diff --git a/execution/tx_cache.go b/execution/tx_cache.go deleted file mode 100644 index 4dc56485f739954c5ab9f5c7e0f70c210c66a826..0000000000000000000000000000000000000000 --- a/execution/tx_cache.go +++ /dev/null @@ -1,128 +0,0 @@ -// Copyright 2017 Monax Industries Limited -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package execution - -import ( - "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 -}