diff options
author | Felix Lange <fjl@users.noreply.github.com> | 2017-06-27 21:57:06 +0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2017-06-27 21:57:06 +0800 |
commit | 9e5f03b6c487175cc5aa1224e5e12fd573f483a7 (patch) | |
tree | 475e573ff6c7e77cd069a2f6238afdb27d4bce43 /light | |
parent | bb366271fe33cf87b462dc5a25ac6c448ac6d2e1 (diff) | |
download | go-tangerine-9e5f03b6c487175cc5aa1224e5e12fd573f483a7.tar go-tangerine-9e5f03b6c487175cc5aa1224e5e12fd573f483a7.tar.gz go-tangerine-9e5f03b6c487175cc5aa1224e5e12fd573f483a7.tar.bz2 go-tangerine-9e5f03b6c487175cc5aa1224e5e12fd573f483a7.tar.lz go-tangerine-9e5f03b6c487175cc5aa1224e5e12fd573f483a7.tar.xz go-tangerine-9e5f03b6c487175cc5aa1224e5e12fd573f483a7.tar.zst go-tangerine-9e5f03b6c487175cc5aa1224e5e12fd573f483a7.zip |
core/state: access trie through Database interface, track errors (#14589)
With this commit, core/state's access to the underlying key/value database is
mediated through an interface. Database errors are tracked in StateDB and
returned by CommitTo or the new Error method.
Motivation for this change: We can remove the light client's duplicated copy of
core/state. The light client now supports node iteration, so tracing and storage
enumeration can work with the light client (not implemented in this commit).
Diffstat (limited to 'light')
-rw-r--r-- | light/lightchain.go | 5 | ||||
-rw-r--r-- | light/odr.go | 8 | ||||
-rw-r--r-- | light/odr_test.go | 164 | ||||
-rw-r--r-- | light/odr_util.go | 19 | ||||
-rw-r--r-- | light/state.go | 316 | ||||
-rw-r--r-- | light/state_object.go | 275 | ||||
-rw-r--r-- | light/state_test.go | 248 | ||||
-rw-r--r-- | light/trie.go | 251 | ||||
-rw-r--r-- | light/trie_test.go | 83 | ||||
-rw-r--r-- | light/txpool.go | 32 | ||||
-rw-r--r-- | light/vm_env.go | 194 |
11 files changed, 365 insertions, 1230 deletions
diff --git a/light/lightchain.go b/light/lightchain.go index 5b7e57041..87436f4a5 100644 --- a/light/lightchain.go +++ b/light/lightchain.go @@ -180,11 +180,6 @@ func (self *LightChain) Status() (td *big.Int, currentBlock common.Hash, genesis return self.GetTd(hash, header.Number.Uint64()), hash, self.genesisBlock.Hash() } -// State returns a new mutable state based on the current HEAD block. -func (self *LightChain) State() *LightState { - return NewLightState(StateTrieID(self.hc.CurrentHeader()), self.odr) -} - // Reset purges the entire blockchain, restoring it to its genesis state. func (bc *LightChain) Reset() { bc.ResetWithGenesisBlock(bc.genesisBlock) diff --git a/light/odr.go b/light/odr.go index ca6364f28..d19a488f6 100644 --- a/light/odr.go +++ b/light/odr.go @@ -34,7 +34,7 @@ import ( // service is not required. var NoOdr = context.Background() -// OdrBackend is an interface to a backend service that handles ODR retrievals +// OdrBackend is an interface to a backend service that handles ODR retrievals type type OdrBackend interface { Database() ethdb.Database Retrieve(ctx context.Context, req OdrRequest) error @@ -66,11 +66,11 @@ func StateTrieID(header *types.Header) *TrieID { // StorageTrieID returns a TrieID for a contract storage trie at a given account // of a given state trie. It also requires the root hash of the trie for // checking Merkle proofs. -func StorageTrieID(state *TrieID, addr common.Address, root common.Hash) *TrieID { +func StorageTrieID(state *TrieID, addrHash, root common.Hash) *TrieID { return &TrieID{ BlockHash: state.BlockHash, BlockNumber: state.BlockNumber, - AccKey: crypto.Keccak256(addr[:]), + AccKey: addrHash[:], Root: root, } } @@ -102,7 +102,7 @@ func storeProof(db ethdb.Database, proof []rlp.RawValue) { // CodeRequest is the ODR request type for retrieving contract code type CodeRequest struct { OdrRequest - Id *TrieID + Id *TrieID // references storage trie of the account Hash common.Hash Data []byte } diff --git a/light/odr_test.go b/light/odr_test.go index 576e3abc9..544b64eff 100644 --- a/light/odr_test.go +++ b/light/odr_test.go @@ -86,11 +86,11 @@ func (odr *testOdr) Retrieve(ctx context.Context, req OdrRequest) error { return nil } -type odrTestFn func(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) []byte +type odrTestFn func(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) -func TestOdrGetBlockLes1(t *testing.T) { testChainOdr(t, 1, 1, odrGetBlock) } +func TestOdrGetBlockLes1(t *testing.T) { testChainOdr(t, 1, odrGetBlock) } -func odrGetBlock(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) []byte { +func odrGetBlock(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) { var block *types.Block if bc != nil { block = bc.GetBlockByHash(bhash) @@ -98,15 +98,15 @@ func odrGetBlock(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc block, _ = lc.GetBlockByHash(ctx, bhash) } if block == nil { - return nil + return nil, nil } rlp, _ := rlp.EncodeToBytes(block) - return rlp + return rlp, nil } -func TestOdrGetReceiptsLes1(t *testing.T) { testChainOdr(t, 1, 1, odrGetReceipts) } +func TestOdrGetReceiptsLes1(t *testing.T) { testChainOdr(t, 1, odrGetReceipts) } -func odrGetReceipts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) []byte { +func odrGetReceipts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) { var receipts types.Receipts if bc != nil { receipts = core.GetBlockReceipts(db, bhash, core.GetBlockNumber(db, bhash)) @@ -114,43 +114,37 @@ func odrGetReceipts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, receipts, _ = GetBlockReceipts(ctx, lc.Odr(), bhash, core.GetBlockNumber(db, bhash)) } if receipts == nil { - return nil + return nil, nil } rlp, _ := rlp.EncodeToBytes(receipts) - return rlp + return rlp, nil } -func TestOdrAccountsLes1(t *testing.T) { testChainOdr(t, 1, 1, odrAccounts) } +func TestOdrAccountsLes1(t *testing.T) { testChainOdr(t, 1, odrAccounts) } -func odrAccounts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) []byte { +func odrAccounts(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) { dummyAddr := common.HexToAddress("1234567812345678123456781234567812345678") acc := []common.Address{testBankAddress, acc1Addr, acc2Addr, dummyAddr} + var st *state.StateDB + if bc == nil { + header := lc.GetHeaderByHash(bhash) + st = NewState(ctx, header, lc.Odr()) + } else { + header := bc.GetHeaderByHash(bhash) + st, _ = state.New(header.Root, state.NewDatabase(db)) + } + var res []byte for _, addr := range acc { - if bc != nil { - header := bc.GetHeaderByHash(bhash) - st, err := state.New(header.Root, db) - if err == nil { - bal := st.GetBalance(addr) - rlp, _ := rlp.EncodeToBytes(bal) - res = append(res, rlp...) - } - } else { - header := lc.GetHeaderByHash(bhash) - st := NewLightState(StateTrieID(header), lc.Odr()) - bal, err := st.GetBalance(ctx, addr) - if err == nil { - rlp, _ := rlp.EncodeToBytes(bal) - res = append(res, rlp...) - } - } + bal := st.GetBalance(addr) + rlp, _ := rlp.EncodeToBytes(bal) + res = append(res, rlp...) } - - return res + return res, st.Error() } -func TestOdrContractCallLes1(t *testing.T) { testChainOdr(t, 1, 2, odrContractCall) } +func TestOdrContractCallLes1(t *testing.T) { testChainOdr(t, 1, odrContractCall) } type callmsg struct { types.Message @@ -158,50 +152,42 @@ type callmsg struct { func (callmsg) CheckNonce() bool { return false } -func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) []byte { +func odrContractCall(ctx context.Context, db ethdb.Database, bc *core.BlockChain, lc *LightChain, bhash common.Hash) ([]byte, error) { data := common.Hex2Bytes("60CD26850000000000000000000000000000000000000000000000000000000000000000") - config := params.TestChainConfig var res []byte for i := 0; i < 3; i++ { data[35] = byte(i) - if bc != nil { - header := bc.GetHeaderByHash(bhash) - statedb, err := state.New(header.Root, db) - if err == nil { - from := statedb.GetOrNewStateObject(testBankAddress) - from.SetBalance(math.MaxBig256) - - msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), big.NewInt(1000000), new(big.Int), data, false)} - context := core.NewEVMContext(msg, header, bc, nil) - vmenv := vm.NewEVM(context, statedb, config, vm.Config{}) - - gp := new(core.GasPool).AddGas(math.MaxBig256) - ret, _, _ := core.ApplyMessage(vmenv, msg, gp) - res = append(res, ret...) - } + var ( + st *state.StateDB + header *types.Header + chain core.ChainContext + ) + if bc == nil { + chain = lc + header = lc.GetHeaderByHash(bhash) + st = NewState(ctx, header, lc.Odr()) } else { - header := lc.GetHeaderByHash(bhash) - state := NewLightState(StateTrieID(header), lc.Odr()) - vmstate := NewVMState(ctx, state) - from, err := state.GetOrNewStateObject(ctx, testBankAddress) - if err == nil { - from.SetBalance(math.MaxBig256) - - msg := callmsg{types.NewMessage(from.Address(), &testContractAddr, 0, new(big.Int), big.NewInt(1000000), new(big.Int), data, false)} - context := core.NewEVMContext(msg, header, lc, nil) - vmenv := vm.NewEVM(context, vmstate, config, vm.Config{}) - gp := new(core.GasPool).AddGas(math.MaxBig256) - ret, _, _ := core.ApplyMessage(vmenv, msg, gp) - if vmstate.Error() == nil { - res = append(res, ret...) - } - } + chain = bc + header = bc.GetHeaderByHash(bhash) + st, _ = state.New(header.Root, state.NewDatabase(db)) + } + + // Perform read-only call. + st.SetBalance(testBankAddress, math.MaxBig256) + msg := callmsg{types.NewMessage(testBankAddress, &testContractAddr, 0, new(big.Int), big.NewInt(1000000), new(big.Int), data, false)} + context := core.NewEVMContext(msg, header, chain, nil) + vmenv := vm.NewEVM(context, st, config, vm.Config{}) + gp := new(core.GasPool).AddGas(math.MaxBig256) + ret, _, _ := core.ApplyMessage(vmenv, msg, gp) + res = append(res, ret...) + if st.Error() != nil { + return res, st.Error() } } - return res + return res, nil } func testChainGen(i int, block *core.BlockGen) { @@ -245,7 +231,7 @@ func testChainGen(i int, block *core.BlockGen) { } } -func testChainOdr(t *testing.T, protocol int, expFail uint64, fn odrTestFn) { +func testChainOdr(t *testing.T, protocol int, fn odrTestFn) { var ( evmux = new(event.TypeMux) sdb, _ = ethdb.NewMemDatabase() @@ -258,46 +244,58 @@ func testChainOdr(t *testing.T, protocol int, expFail uint64, fn odrTestFn) { blockchain, _ := core.NewBlockChain(sdb, params.TestChainConfig, ethash.NewFullFaker(), evmux, vm.Config{}) gchain, _ := core.GenerateChain(params.TestChainConfig, genesis, sdb, 4, testChainGen) if _, err := blockchain.InsertChain(gchain); err != nil { - panic(err) + t.Fatal(err) } odr := &testOdr{sdb: sdb, ldb: ldb} - lightchain, _ := NewLightChain(odr, params.TestChainConfig, ethash.NewFullFaker(), evmux) + lightchain, err := NewLightChain(odr, params.TestChainConfig, ethash.NewFullFaker(), evmux) + if err != nil { + t.Fatal(err) + } headers := make([]*types.Header, len(gchain)) for i, block := range gchain { headers[i] = block.Header() } if _, err := lightchain.InsertHeaderChain(headers, 1); err != nil { - panic(err) + t.Fatal(err) } - test := func(expFail uint64) { + test := func(expFail int) { for i := uint64(0); i <= blockchain.CurrentHeader().Number.Uint64(); i++ { bhash := core.GetCanonicalHash(sdb, i) - b1 := fn(NoOdr, sdb, blockchain, nil, bhash) + b1, err := fn(NoOdr, sdb, blockchain, nil, bhash) + if err != nil { + t.Fatalf("error in full-node test for block %d: %v", i, err) + } ctx, cancel := context.WithTimeout(context.Background(), 200*time.Millisecond) defer cancel() - b2 := fn(ctx, ldb, nil, lightchain, bhash) + + exp := i < uint64(expFail) + b2, err := fn(ctx, ldb, nil, lightchain, bhash) + if err != nil && exp { + t.Errorf("error in ODR test for block %d: %v", i, err) + } eq := bytes.Equal(b1, b2) - exp := i < expFail if exp && !eq { - t.Errorf("odr mismatch") - } - if !exp && eq { - t.Errorf("unexpected odr match") + t.Errorf("ODR test output for block %d doesn't match full node", i) } } } - odr.disable = true // expect retrievals to fail (except genesis block) without a les peer - test(expFail) - odr.disable = false - // expect all retrievals to pass - test(5) + t.Log("checking without ODR") odr.disable = true + test(1) + + // expect all retrievals to pass with ODR enabled + t.Log("checking with ODR") + odr.disable = false + test(len(gchain)) + // still expect all retrievals to pass, now data should be cached locally - test(5) + t.Log("checking without ODR, should be cached") + odr.disable = true + test(len(gchain)) } diff --git a/light/odr_util.go b/light/odr_util.go index d7f8458f1..fcdfdb82c 100644 --- a/light/odr_util.go +++ b/light/odr_util.go @@ -106,25 +106,6 @@ func GetCanonicalHash(ctx context.Context, odr OdrBackend, number uint64) (commo return common.Hash{}, err } -// retrieveContractCode tries to retrieve the contract code of the given account -// with the given hash from the network (id points to the storage trie belonging -// to the same account) -func retrieveContractCode(ctx context.Context, odr OdrBackend, id *TrieID, hash common.Hash) ([]byte, error) { - if hash == sha3_nil { - return nil, nil - } - res, _ := odr.Database().Get(hash[:]) - if res != nil { - return res, nil - } - r := &CodeRequest{Id: id, Hash: hash} - if err := odr.Retrieve(ctx, r); err != nil { - return nil, err - } else { - return r.Data, nil - } -} - // GetBodyRLP retrieves the block body (transactions and uncles) in RLP encoding. func GetBodyRLP(ctx context.Context, odr OdrBackend, hash common.Hash, number uint64) (rlp.RawValue, error) { if data := core.GetBodyRLP(odr.Database(), hash, number); data != nil { diff --git a/light/state.go b/light/state.go deleted file mode 100644 index b184dc3a5..000000000 --- a/light/state.go +++ /dev/null @@ -1,316 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package light - -import ( - "context" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" -) - -// LightState is a memory representation of a state. -// This version is ODR capable, caching only the already accessed part of the -// state, retrieving unknown parts on-demand from the ODR backend. Changes are -// never stored in the local database, only in the memory objects. -type LightState struct { - odr OdrBackend - trie *LightTrie - id *TrieID - stateObjects map[string]*StateObject - refund *big.Int -} - -// NewLightState creates a new LightState with the specified root. -// Note that the creation of a light state is always successful, even if the -// root is non-existent. In that case, ODR retrieval will always be unsuccessful -// and every operation will return with an error or wait for the context to be -// cancelled. -func NewLightState(id *TrieID, odr OdrBackend) *LightState { - var tr *LightTrie - if id != nil { - tr = NewLightTrie(id, odr, true) - } - return &LightState{ - odr: odr, - trie: tr, - id: id, - stateObjects: make(map[string]*StateObject), - refund: new(big.Int), - } -} - -// AddRefund adds an amount to the refund value collected during a vm execution -func (self *LightState) AddRefund(gas *big.Int) { - self.refund.Add(self.refund, gas) -} - -// HasAccount returns true if an account exists at the given address -func (self *LightState) HasAccount(ctx context.Context, addr common.Address) (bool, error) { - so, err := self.GetStateObject(ctx, addr) - return so != nil, err -} - -// GetBalance retrieves the balance from the given address or 0 if the account does -// not exist -func (self *LightState) GetBalance(ctx context.Context, addr common.Address) (*big.Int, error) { - stateObject, err := self.GetStateObject(ctx, addr) - if err != nil { - return common.Big0, err - } - if stateObject != nil { - return stateObject.balance, nil - } - - return common.Big0, nil -} - -// GetNonce returns the nonce at the given address or 0 if the account does -// not exist -func (self *LightState) GetNonce(ctx context.Context, addr common.Address) (uint64, error) { - stateObject, err := self.GetStateObject(ctx, addr) - if err != nil { - return 0, err - } - if stateObject != nil { - return stateObject.nonce, nil - } - return 0, nil -} - -// GetCode returns the contract code at the given address or nil if the account -// does not exist -func (self *LightState) GetCode(ctx context.Context, addr common.Address) ([]byte, error) { - stateObject, err := self.GetStateObject(ctx, addr) - if err != nil { - return nil, err - } - if stateObject != nil { - return stateObject.code, nil - } - return nil, nil -} - -// GetState returns the contract storage value at storage address b from the -// contract address a or common.Hash{} if the account does not exist -func (self *LightState) GetState(ctx context.Context, a common.Address, b common.Hash) (common.Hash, error) { - stateObject, err := self.GetStateObject(ctx, a) - if err == nil && stateObject != nil { - return stateObject.GetState(ctx, b) - } - return common.Hash{}, err -} - -// HasSuicided returns true if the given account has been marked for deletion -// or false if the account does not exist -func (self *LightState) HasSuicided(ctx context.Context, addr common.Address) (bool, error) { - stateObject, err := self.GetStateObject(ctx, addr) - if err == nil && stateObject != nil { - return stateObject.remove, nil - } - return false, err -} - -/* - * SETTERS - */ - -// AddBalance adds the given amount to the balance of the specified account -func (self *LightState) AddBalance(ctx context.Context, addr common.Address, amount *big.Int) error { - stateObject, err := self.GetOrNewStateObject(ctx, addr) - if err == nil && stateObject != nil { - stateObject.AddBalance(amount) - } - return err -} - -// SubBalance adds the given amount to the balance of the specified account -func (self *LightState) SubBalance(ctx context.Context, addr common.Address, amount *big.Int) error { - stateObject, err := self.GetOrNewStateObject(ctx, addr) - if err == nil && stateObject != nil { - stateObject.SubBalance(amount) - } - return err -} - -// SetNonce sets the nonce of the specified account -func (self *LightState) SetNonce(ctx context.Context, addr common.Address, nonce uint64) error { - stateObject, err := self.GetOrNewStateObject(ctx, addr) - if err == nil && stateObject != nil { - stateObject.SetNonce(nonce) - } - return err -} - -// SetCode sets the contract code at the specified account -func (self *LightState) SetCode(ctx context.Context, addr common.Address, code []byte) error { - stateObject, err := self.GetOrNewStateObject(ctx, addr) - if err == nil && stateObject != nil { - stateObject.SetCode(crypto.Keccak256Hash(code), code) - } - return err -} - -// SetState sets the storage value at storage address key of the account addr -func (self *LightState) SetState(ctx context.Context, addr common.Address, key common.Hash, value common.Hash) error { - stateObject, err := self.GetOrNewStateObject(ctx, addr) - if err == nil && stateObject != nil { - stateObject.SetState(key, value) - } - return err -} - -// Delete marks an account to be removed and clears its balance -func (self *LightState) Suicide(ctx context.Context, addr common.Address) (bool, error) { - stateObject, err := self.GetOrNewStateObject(ctx, addr) - if err == nil && stateObject != nil { - stateObject.MarkForDeletion() - stateObject.balance = new(big.Int) - - return true, nil - } - - return false, err -} - -// -// Get, set, new state object methods -// - -// GetStateObject returns the state object of the given account or nil if the -// account does not exist -func (self *LightState) GetStateObject(ctx context.Context, addr common.Address) (stateObject *StateObject, err error) { - stateObject = self.stateObjects[addr.Str()] - if stateObject != nil { - if stateObject.deleted { - stateObject = nil - } - return stateObject, nil - } - data, err := self.trie.Get(ctx, addr[:]) - if err != nil { - return nil, err - } - if len(data) == 0 { - return nil, nil - } - - stateObject, err = DecodeObject(ctx, self.id, addr, self.odr, []byte(data)) - if err != nil { - return nil, err - } - - self.SetStateObject(stateObject) - - return stateObject, nil -} - -// SetStateObject sets the state object of the given account -func (self *LightState) SetStateObject(object *StateObject) { - self.stateObjects[object.Address().Str()] = object -} - -// GetOrNewStateObject returns the state object of the given account or creates a -// new one if the account does not exist -func (self *LightState) GetOrNewStateObject(ctx context.Context, addr common.Address) (*StateObject, error) { - stateObject, err := self.GetStateObject(ctx, addr) - if err == nil && (stateObject == nil || stateObject.deleted) { - stateObject, err = self.CreateStateObject(ctx, addr) - } - return stateObject, err -} - -// newStateObject creates a state object whether it exists in the state or not -func (self *LightState) newStateObject(addr common.Address) *StateObject { - stateObject := NewStateObject(addr, self.odr) - self.stateObjects[addr.Str()] = stateObject - - return stateObject -} - -// CreateStateObject creates creates a new state object and takes ownership. -// This is different from "NewStateObject" -func (self *LightState) CreateStateObject(ctx context.Context, addr common.Address) (*StateObject, error) { - // Get previous (if any) - so, err := self.GetStateObject(ctx, addr) - if err != nil { - return nil, err - } - // Create a new one - newSo := self.newStateObject(addr) - - // If it existed set the balance to the new account - if so != nil { - newSo.balance = so.balance - } - - return newSo, nil -} - -// ForEachStorage calls a callback function for every key/value pair found -// in the local storage cache. Note that unlike core/state.StateObject, -// light.StateObject only returns cached values and doesn't download the -// entire storage tree. -func (self *LightState) ForEachStorage(ctx context.Context, addr common.Address, cb func(key, value common.Hash) bool) error { - so, err := self.GetStateObject(ctx, addr) - if err != nil { - return err - } - - if so == nil { - return nil - } - - for h, v := range so.storage { - cb(h, v) - } - return nil -} - -// -// Setting, copying of the state methods -// - -// Copy creates a copy of the state -func (self *LightState) Copy() *LightState { - // ignore error - we assume state-to-be-copied always exists - state := NewLightState(nil, self.odr) - state.trie = self.trie - state.id = self.id - for k, stateObject := range self.stateObjects { - if stateObject.dirty { - state.stateObjects[k] = stateObject.Copy() - } - } - - state.refund.Set(self.refund) - return state -} - -// Set copies the contents of the given state onto this state, overwriting -// its contents -func (self *LightState) Set(state *LightState) { - self.trie = state.trie - self.stateObjects = state.stateObjects - self.refund = state.refund -} - -// GetRefund returns the refund value collected during a vm execution -func (self *LightState) GetRefund() *big.Int { - return self.refund -} diff --git a/light/state_object.go b/light/state_object.go deleted file mode 100644 index a54ea1d9f..000000000 --- a/light/state_object.go +++ /dev/null @@ -1,275 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package light - -import ( - "bytes" - "context" - "fmt" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/rlp" -) - -var emptyCodeHash = crypto.Keccak256(nil) - -// Code represents a contract code in binary form -type Code []byte - -// String returns a string representation of the code -func (self Code) String() string { - return string(self) //strings.Join(Disassemble(self), " ") -} - -// Storage is a memory map cache of a contract storage -type Storage map[common.Hash]common.Hash - -// String returns a string representation of the storage cache -func (self Storage) String() (str string) { - for key, value := range self { - str += fmt.Sprintf("%X : %X\n", key, value) - } - - return -} - -// Copy copies the contents of a storage cache -func (self Storage) Copy() Storage { - cpy := make(Storage) - for key, value := range self { - cpy[key] = value - } - - return cpy -} - -// StateObject is a memory representation of an account or contract and its storage. -// This version is ODR capable, caching only the already accessed part of the -// storage, retrieving unknown parts on-demand from the ODR backend. Changes are -// never stored in the local database, only in the memory objects. -type StateObject struct { - odr OdrBackend - trie *LightTrie - - // Address belonging to this account - address common.Address - // The balance of the account - balance *big.Int - // The nonce of the account - nonce uint64 - // The code hash if code is present (i.e. a contract) - codeHash []byte - // The code for this account - code Code - // Cached storage (flushed when updated) - storage Storage - - // Mark for deletion - // When an object is marked for deletion it will be delete from the trie - // during the "update" phase of the state transition - remove bool - deleted bool - dirty bool -} - -// NewStateObject creates a new StateObject of the specified account address -func NewStateObject(address common.Address, odr OdrBackend) *StateObject { - object := &StateObject{ - odr: odr, - address: address, - balance: new(big.Int), - dirty: true, - codeHash: emptyCodeHash, - storage: make(Storage), - } - object.trie = NewLightTrie(&TrieID{}, odr, true) - return object -} - -// MarkForDeletion marks an account to be removed -func (self *StateObject) MarkForDeletion() { - self.remove = true - self.dirty = true -} - -// getAddr gets the storage value at the given address from the trie -func (c *StateObject) getAddr(ctx context.Context, addr common.Hash) (common.Hash, error) { - var ret []byte - val, err := c.trie.Get(ctx, addr[:]) - if err != nil { - return common.Hash{}, err - } - rlp.DecodeBytes(val, &ret) - return common.BytesToHash(ret), nil -} - -// Storage returns the storage cache object of the account -func (self *StateObject) Storage() Storage { - return self.storage -} - -// GetState returns the storage value at the given address from either the cache -// or the trie -func (self *StateObject) GetState(ctx context.Context, key common.Hash) (common.Hash, error) { - value, exists := self.storage[key] - if !exists { - var err error - value, err = self.getAddr(ctx, key) - if err != nil { - return common.Hash{}, err - } - if (value != common.Hash{}) { - self.storage[key] = value - } - } - - return value, nil -} - -// SetState sets the storage value at the given address -func (self *StateObject) SetState(k, value common.Hash) { - self.storage[k] = value - self.dirty = true -} - -// AddBalance adds the given amount to the account balance -func (c *StateObject) AddBalance(amount *big.Int) { - c.SetBalance(new(big.Int).Add(c.balance, amount)) -} - -// SubBalance subtracts the given amount from the account balance -func (c *StateObject) SubBalance(amount *big.Int) { - c.SetBalance(new(big.Int).Sub(c.balance, amount)) -} - -// SetBalance sets the account balance to the given amount -func (c *StateObject) SetBalance(amount *big.Int) { - c.balance = amount - c.dirty = true -} - -// ReturnGas returns the gas back to the origin. Used by the Virtual machine or Closures -func (c *StateObject) ReturnGas(gas *big.Int) {} - -// Copy creates a copy of the state object -func (self *StateObject) Copy() *StateObject { - stateObject := NewStateObject(self.Address(), self.odr) - stateObject.balance.Set(self.balance) - stateObject.codeHash = common.CopyBytes(self.codeHash) - stateObject.nonce = self.nonce - stateObject.trie = self.trie - stateObject.code = self.code - stateObject.storage = self.storage.Copy() - stateObject.remove = self.remove - stateObject.dirty = self.dirty - stateObject.deleted = self.deleted - - return stateObject -} - -// -// Attribute accessors -// - -// empty returns whether the account is considered empty. -func (self *StateObject) empty() bool { - return self.nonce == 0 && self.balance.Sign() == 0 && bytes.Equal(self.codeHash, emptyCodeHash) -} - -// Balance returns the account balance -func (self *StateObject) Balance() *big.Int { - return self.balance -} - -// Address returns the address of the contract/account -func (self *StateObject) Address() common.Address { - return self.address -} - -// Code returns the contract code -func (self *StateObject) Code() []byte { - return self.code -} - -// SetCode sets the contract code -func (self *StateObject) SetCode(hash common.Hash, code []byte) { - self.code = code - self.codeHash = hash[:] - self.dirty = true -} - -// SetNonce sets the account nonce -func (self *StateObject) SetNonce(nonce uint64) { - self.nonce = nonce - self.dirty = true -} - -// Nonce returns the account nonce -func (self *StateObject) Nonce() uint64 { - return self.nonce -} - -// ForEachStorage calls a callback function for every key/value pair found -// in the local storage cache. Note that unlike core/state.StateObject, -// light.StateObject only returns cached values and doesn't download the -// entire storage tree. -func (self *StateObject) ForEachStorage(cb func(key, value common.Hash) bool) { - for h, v := range self.storage { - cb(h, v) - } -} - -// Never called, but must be present to allow StateObject to be used -// as a vm.Account interface that also satisfies the vm.ContractRef -// interface. Interfaces are awesome. -func (self *StateObject) Value() *big.Int { - panic("Value on StateObject should never be called") -} - -// Encoding - -type extStateObject struct { - Nonce uint64 - Balance *big.Int - Root common.Hash - CodeHash []byte -} - -// DecodeObject decodes an RLP-encoded state object. -func DecodeObject(ctx context.Context, stateID *TrieID, address common.Address, odr OdrBackend, data []byte) (*StateObject, error) { - var ( - obj = &StateObject{address: address, odr: odr, storage: make(Storage)} - ext extStateObject - err error - ) - if err = rlp.DecodeBytes(data, &ext); err != nil { - return nil, err - } - trieID := StorageTrieID(stateID, address, ext.Root) - obj.trie = NewLightTrie(trieID, odr, true) - if !bytes.Equal(ext.CodeHash, emptyCodeHash) { - if obj.code, err = retrieveContractCode(ctx, obj.odr, trieID, common.BytesToHash(ext.CodeHash)); err != nil { - return nil, fmt.Errorf("can't find code for hash %x: %v", ext.CodeHash, err) - } - } - obj.nonce = ext.Nonce - obj.balance = ext.Balance - obj.codeHash = ext.CodeHash - return obj, nil -} diff --git a/light/state_test.go b/light/state_test.go deleted file mode 100644 index e776efec8..000000000 --- a/light/state_test.go +++ /dev/null @@ -1,248 +0,0 @@ -// Copyright 2015 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package light - -import ( - "bytes" - "context" - "math/big" - "testing" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core" - "github.com/ethereum/go-ethereum/core/state" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/ethdb" -) - -func makeTestState() (common.Hash, ethdb.Database) { - sdb, _ := ethdb.NewMemDatabase() - st, _ := state.New(common.Hash{}, sdb) - for i := byte(0); i < 100; i++ { - addr := common.Address{i} - for j := byte(0); j < 100; j++ { - st.SetState(addr, common.Hash{j}, common.Hash{i, j}) - } - st.SetNonce(addr, 100) - st.AddBalance(addr, big.NewInt(int64(i))) - st.SetCode(addr, []byte{i, i, i}) - } - root, _ := st.Commit(false) - return root, sdb -} - -func TestLightStateOdr(t *testing.T) { - root, sdb := makeTestState() - header := &types.Header{Root: root, Number: big.NewInt(0)} - core.WriteHeader(sdb, header) - ldb, _ := ethdb.NewMemDatabase() - odr := &testOdr{sdb: sdb, ldb: ldb} - ls := NewLightState(StateTrieID(header), odr) - ctx := context.Background() - - for i := byte(0); i < 100; i++ { - addr := common.Address{i} - err := ls.AddBalance(ctx, addr, big.NewInt(1000)) - if err != nil { - t.Fatalf("Error adding balance to acc[%d]: %v", i, err) - } - err = ls.SetState(ctx, addr, common.Hash{100}, common.Hash{i, 100}) - if err != nil { - t.Fatalf("Error setting storage of acc[%d]: %v", i, err) - } - } - - addr := common.Address{100} - _, err := ls.CreateStateObject(ctx, addr) - if err != nil { - t.Fatalf("Error creating state object: %v", err) - } - err = ls.SetCode(ctx, addr, []byte{100, 100, 100}) - if err != nil { - t.Fatalf("Error setting code: %v", err) - } - err = ls.AddBalance(ctx, addr, big.NewInt(1100)) - if err != nil { - t.Fatalf("Error adding balance to acc[100]: %v", err) - } - for j := byte(0); j < 101; j++ { - err = ls.SetState(ctx, addr, common.Hash{j}, common.Hash{100, j}) - if err != nil { - t.Fatalf("Error setting storage of acc[100]: %v", err) - } - } - err = ls.SetNonce(ctx, addr, 100) - if err != nil { - t.Fatalf("Error setting nonce for acc[100]: %v", err) - } - - for i := byte(0); i < 101; i++ { - addr := common.Address{i} - - bal, err := ls.GetBalance(ctx, addr) - if err != nil { - t.Fatalf("Error getting balance of acc[%d]: %v", i, err) - } - if bal.Int64() != int64(i)+1000 { - t.Fatalf("Incorrect balance at acc[%d]: expected %v, got %v", i, int64(i)+1000, bal.Int64()) - } - - nonce, err := ls.GetNonce(ctx, addr) - if err != nil { - t.Fatalf("Error getting nonce of acc[%d]: %v", i, err) - } - if nonce != 100 { - t.Fatalf("Incorrect nonce at acc[%d]: expected %v, got %v", i, 100, nonce) - } - - code, err := ls.GetCode(ctx, addr) - exp := []byte{i, i, i} - if err != nil { - t.Fatalf("Error getting code of acc[%d]: %v", i, err) - } - if !bytes.Equal(code, exp) { - t.Fatalf("Incorrect code at acc[%d]: expected %v, got %v", i, exp, code) - } - - for j := byte(0); j < 101; j++ { - exp := common.Hash{i, j} - val, err := ls.GetState(ctx, addr, common.Hash{j}) - if err != nil { - t.Fatalf("Error retrieving acc[%d].storage[%d]: %v", i, j, err) - } - if val != exp { - t.Fatalf("Retrieved wrong value from acc[%d].storage[%d]: expected %04x, got %04x", i, j, exp, val) - } - } - } -} - -func TestLightStateSetCopy(t *testing.T) { - root, sdb := makeTestState() - header := &types.Header{Root: root, Number: big.NewInt(0)} - core.WriteHeader(sdb, header) - ldb, _ := ethdb.NewMemDatabase() - odr := &testOdr{sdb: sdb, ldb: ldb} - ls := NewLightState(StateTrieID(header), odr) - ctx := context.Background() - - for i := byte(0); i < 100; i++ { - addr := common.Address{i} - err := ls.AddBalance(ctx, addr, big.NewInt(1000)) - if err != nil { - t.Fatalf("Error adding balance to acc[%d]: %v", i, err) - } - err = ls.SetState(ctx, addr, common.Hash{100}, common.Hash{i, 100}) - if err != nil { - t.Fatalf("Error setting storage of acc[%d]: %v", i, err) - } - } - - ls2 := ls.Copy() - - for i := byte(0); i < 100; i++ { - addr := common.Address{i} - err := ls2.AddBalance(ctx, addr, big.NewInt(1000)) - if err != nil { - t.Fatalf("Error adding balance to acc[%d]: %v", i, err) - } - err = ls2.SetState(ctx, addr, common.Hash{100}, common.Hash{i, 200}) - if err != nil { - t.Fatalf("Error setting storage of acc[%d]: %v", i, err) - } - } - - lsx := ls.Copy() - ls.Set(ls2) - ls2.Set(lsx) - - for i := byte(0); i < 100; i++ { - addr := common.Address{i} - // check balance in ls - bal, err := ls.GetBalance(ctx, addr) - if err != nil { - t.Fatalf("Error getting balance to acc[%d]: %v", i, err) - } - if bal.Int64() != int64(i)+2000 { - t.Fatalf("Incorrect balance at ls.acc[%d]: expected %v, got %v", i, int64(i)+1000, bal.Int64()) - } - // check balance in ls2 - bal, err = ls2.GetBalance(ctx, addr) - if err != nil { - t.Fatalf("Error getting balance to acc[%d]: %v", i, err) - } - if bal.Int64() != int64(i)+1000 { - t.Fatalf("Incorrect balance at ls.acc[%d]: expected %v, got %v", i, int64(i)+1000, bal.Int64()) - } - // check storage in ls - exp := common.Hash{i, 200} - val, err := ls.GetState(ctx, addr, common.Hash{100}) - if err != nil { - t.Fatalf("Error retrieving acc[%d].storage[100]: %v", i, err) - } - if val != exp { - t.Fatalf("Retrieved wrong value from acc[%d].storage[100]: expected %04x, got %04x", i, exp, val) - } - // check storage in ls2 - exp = common.Hash{i, 100} - val, err = ls2.GetState(ctx, addr, common.Hash{100}) - if err != nil { - t.Fatalf("Error retrieving acc[%d].storage[100]: %v", i, err) - } - if val != exp { - t.Fatalf("Retrieved wrong value from acc[%d].storage[100]: expected %04x, got %04x", i, exp, val) - } - } -} - -func TestLightStateDelete(t *testing.T) { - root, sdb := makeTestState() - header := &types.Header{Root: root, Number: big.NewInt(0)} - core.WriteHeader(sdb, header) - ldb, _ := ethdb.NewMemDatabase() - odr := &testOdr{sdb: sdb, ldb: ldb} - ls := NewLightState(StateTrieID(header), odr) - ctx := context.Background() - - addr := common.Address{42} - - b, err := ls.HasAccount(ctx, addr) - if err != nil { - t.Fatalf("HasAccount error: %v", err) - } - if !b { - t.Fatalf("HasAccount returned false, expected true") - } - - b, err = ls.HasSuicided(ctx, addr) - if err != nil { - t.Fatalf("HasSuicided error: %v", err) - } - if b { - t.Fatalf("HasSuicided returned true, expected false") - } - - ls.Suicide(ctx, addr) - - b, err = ls.HasSuicided(ctx, addr) - if err != nil { - t.Fatalf("HasSuicided error: %v", err) - } - if !b { - t.Fatalf("HasSuicided returned false, expected true") - } -} diff --git a/light/trie.go b/light/trie.go index 2988a16cf..7502b6e5d 100644 --- a/light/trie.go +++ b/light/trie.go @@ -18,99 +18,216 @@ package light import ( "context" + "fmt" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" - "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/trie" ) -// LightTrie is an ODR-capable wrapper around trie.SecureTrie -type LightTrie struct { - trie *trie.SecureTrie +func NewState(ctx context.Context, head *types.Header, odr OdrBackend) *state.StateDB { + state, _ := state.New(head.Root, NewStateDatabase(ctx, head, odr)) + return state +} + +func NewStateDatabase(ctx context.Context, head *types.Header, odr OdrBackend) state.Database { + return &odrDatabase{ctx, StateTrieID(head), odr} +} + +type odrDatabase struct { + ctx context.Context + id *TrieID + backend OdrBackend +} + +func (db *odrDatabase) OpenTrie(root common.Hash) (state.Trie, error) { + return &odrTrie{db: db, id: db.id}, nil +} + +func (db *odrDatabase) OpenStorageTrie(addrHash, root common.Hash) (state.Trie, error) { + return &odrTrie{db: db, id: StorageTrieID(db.id, addrHash, root)}, nil +} + +func (db *odrDatabase) CopyTrie(t state.Trie) state.Trie { + switch t := t.(type) { + case *odrTrie: + cpy := &odrTrie{db: t.db, id: t.id} + if t.trie != nil { + cpytrie := *t.trie + cpy.trie = &cpytrie + } + return cpy + default: + panic(fmt.Errorf("unknown trie type %T", t)) + } +} + +func (db *odrDatabase) ContractCode(addrHash, codeHash common.Hash) ([]byte, error) { + if codeHash == sha3_nil { + return nil, nil + } + if code, err := db.backend.Database().Get(codeHash[:]); err == nil { + return code, nil + } + id := *db.id + id.AccKey = addrHash[:] + req := &CodeRequest{Id: &id, Hash: codeHash} + err := db.backend.Retrieve(db.ctx, req) + return req.Data, err +} + +func (db *odrDatabase) ContractCodeSize(addrHash, codeHash common.Hash) (int, error) { + code, err := db.ContractCode(addrHash, codeHash) + return len(code), err +} + +type odrTrie struct { + db *odrDatabase id *TrieID - odr OdrBackend - db ethdb.Database -} - -// NewLightTrie creates a new LightTrie instance. It doesn't instantly try to -// access the db or network and retrieve the root node, it only initializes its -// encapsulated SecureTrie at the first actual operation. -func NewLightTrie(id *TrieID, odr OdrBackend, useFakeMap bool) *LightTrie { - return &LightTrie{ - // SecureTrie is initialized before first request - id: id, - odr: odr, - db: odr.Database(), + trie *trie.Trie +} + +func (t *odrTrie) TryGet(key []byte) ([]byte, error) { + key = crypto.Keccak256(key) + var res []byte + err := t.do(key, func() (err error) { + res, err = t.trie.TryGet(key) + return err + }) + return res, err +} + +func (t *odrTrie) TryUpdate(key, value []byte) error { + key = crypto.Keccak256(key) + return t.do(key, func() error { + return t.trie.TryDelete(key) + }) +} + +func (t *odrTrie) TryDelete(key []byte) error { + key = crypto.Keccak256(key) + return t.do(key, func() error { + return t.trie.TryDelete(key) + }) +} + +func (t *odrTrie) CommitTo(db trie.DatabaseWriter) (common.Hash, error) { + if t.trie == nil { + return t.id.Root, nil + } + return t.trie.CommitTo(db) +} + +func (t *odrTrie) Hash() common.Hash { + if t.trie == nil { + return t.id.Root } + return t.trie.Hash() +} + +func (t *odrTrie) NodeIterator(startkey []byte) trie.NodeIterator { + return newNodeIterator(t, startkey) } -// retrieveKey retrieves a single key, returns true and stores nodes in local -// database if successful -func (t *LightTrie) retrieveKey(ctx context.Context, key []byte) bool { - r := &TrieRequest{Id: t.id, Key: crypto.Keccak256(key)} - return t.odr.Retrieve(ctx, r) == nil +func (t *odrTrie) GetKey(sha []byte) []byte { + return nil } // do tries and retries to execute a function until it returns with no error or // an error type other than MissingNodeError -func (t *LightTrie) do(ctx context.Context, key []byte, fn func() error) error { - err := fn() - for err != nil { +func (t *odrTrie) do(key []byte, fn func() error) error { + for { + var err error + if t.trie == nil { + t.trie, err = trie.New(t.id.Root, t.db.backend.Database()) + } + if err == nil { + err = fn() + } if _, ok := err.(*trie.MissingNodeError); !ok { return err } - if !t.retrieveKey(ctx, key) { - break + r := &TrieRequest{Id: t.id, Key: key} + if err := t.db.backend.Retrieve(t.db.ctx, r); err != nil { + return fmt.Errorf("can't fetch trie key %x: %v", key, err) } - err = fn() } - return err } -// Get returns the value for key stored in the trie. -// The value bytes must not be modified by the caller. -func (t *LightTrie) Get(ctx context.Context, key []byte) (res []byte, err error) { - err = t.do(ctx, key, func() (err error) { - if t.trie == nil { - t.trie, err = trie.NewSecure(t.id.Root, t.db, 0) - } - if err == nil { - res, err = t.trie.TryGet(key) - } - return +type nodeIterator struct { + trie.NodeIterator + t *odrTrie + err error +} + +func newNodeIterator(t *odrTrie, startkey []byte) trie.NodeIterator { + it := &nodeIterator{t: t} + // Open the actual non-ODR trie if that hasn't happened yet. + if t.trie == nil { + it.do(func() error { + t, err := trie.New(t.id.Root, t.db.backend.Database()) + if err == nil { + it.t.trie = t + } + return err + }) + } + it.do(func() error { + it.NodeIterator = it.t.trie.NodeIterator(startkey) + return it.NodeIterator.Error() }) - return + return it } -// Update associates key with value in the trie. Subsequent calls to -// Get will return value. If value has length zero, any existing value -// is deleted from the trie and calls to Get will return nil. -// -// The value bytes must not be modified by the caller while they are -// stored in the trie. -func (t *LightTrie) Update(ctx context.Context, key, value []byte) (err error) { - err = t.do(ctx, key, func() (err error) { - if t.trie == nil { - t.trie, err = trie.NewSecure(t.id.Root, t.db, 0) - } - if err == nil { - err = t.trie.TryUpdate(key, value) - } - return +func (it *nodeIterator) Next(descend bool) bool { + var ok bool + it.do(func() error { + ok = it.NodeIterator.Next(descend) + return it.NodeIterator.Error() }) - return + return ok } -// Delete removes any existing value for key from the trie. -func (t *LightTrie) Delete(ctx context.Context, key []byte) (err error) { - err = t.do(ctx, key, func() (err error) { - if t.trie == nil { - t.trie, err = trie.NewSecure(t.id.Root, t.db, 0) +// do runs fn and attempts to fill in missing nodes by retrieving. +func (it *nodeIterator) do(fn func() error) { + var lasthash common.Hash + for { + it.err = fn() + missing, ok := it.err.(*trie.MissingNodeError) + if !ok { + return } - if err == nil { - err = t.trie.TryDelete(key) + if missing.NodeHash == lasthash { + it.err = fmt.Errorf("retrieve loop for trie node %x", missing.NodeHash) + return } - return - }) - return + lasthash = missing.NodeHash + r := &TrieRequest{Id: it.t.id, Key: nibblesToKey(missing.Path)} + if it.err = it.t.db.backend.Retrieve(it.t.db.ctx, r); it.err != nil { + return + } + } +} + +func (it *nodeIterator) Error() error { + if it.err != nil { + return it.err + } + return it.NodeIterator.Error() +} + +func nibblesToKey(nib []byte) []byte { + if len(nib) > 0 && nib[len(nib)-1] == 0x10 { + nib = nib[:len(nib)-1] // drop terminator + } + if len(nib)&1 == 1 { + nib = append(nib, 0) // make even + } + key := make([]byte, len(nib)/2) + for bi, ni := 0, 0; ni < len(nib); bi, ni = bi+1, ni+2 { + key[bi] = nib[ni]<<4 | nib[ni+1] + } + return key } diff --git a/light/trie_test.go b/light/trie_test.go new file mode 100644 index 000000000..9b2cf7c2b --- /dev/null +++ b/light/trie_test.go @@ -0,0 +1,83 @@ +// Copyright 2017 The go-ethereum Authors +// This file is part of the go-ethereum library. +// +// The go-ethereum library is free software: you can redistribute it and/or modify +// it under the terms of the GNU Lesser General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// The go-ethereum library is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Lesser General Public License for more details. +// +// You should have received a copy of the GNU Lesser General Public License +// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. + +package light + +import ( + "bytes" + "context" + "fmt" + "testing" + + "github.com/davecgh/go-spew/spew" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/ethdb" + "github.com/ethereum/go-ethereum/event" + "github.com/ethereum/go-ethereum/params" + "github.com/ethereum/go-ethereum/trie" +) + +func TestNodeIterator(t *testing.T) { + var ( + fulldb, _ = ethdb.NewMemDatabase() + lightdb, _ = ethdb.NewMemDatabase() + gspec = core.Genesis{Alloc: core.GenesisAlloc{testBankAddress: {Balance: testBankFunds}}} + genesis = gspec.MustCommit(fulldb) + ) + gspec.MustCommit(lightdb) + blockchain, _ := core.NewBlockChain(fulldb, params.TestChainConfig, ethash.NewFullFaker(), new(event.TypeMux), vm.Config{}) + gchain, _ := core.GenerateChain(params.TestChainConfig, genesis, fulldb, 4, testChainGen) + if _, err := blockchain.InsertChain(gchain); err != nil { + panic(err) + } + + ctx := context.Background() + odr := &testOdr{sdb: fulldb, ldb: lightdb} + head := blockchain.CurrentHeader() + lightTrie, _ := NewStateDatabase(ctx, head, odr).OpenTrie(head.Root) + fullTrie, _ := state.NewDatabase(fulldb).OpenTrie(head.Root) + if err := diffTries(fullTrie, lightTrie); err != nil { + t.Fatal(err) + } +} + +func diffTries(t1, t2 state.Trie) error { + i1 := trie.NewIterator(t1.NodeIterator(nil)) + i2 := trie.NewIterator(t2.NodeIterator(nil)) + for i1.Next() && i2.Next() { + if !bytes.Equal(i1.Key, i2.Key) { + spew.Dump(i2) + return fmt.Errorf("tries have different keys %x, %x", i1.Key, i2.Key) + } + if !bytes.Equal(i2.Value, i2.Value) { + return fmt.Errorf("tries differ at key %x", i1.Key) + } + } + switch { + case i1.Err != nil: + return fmt.Errorf("full trie iterator error: %v", i1.Err) + case i2.Err != nil: + return fmt.Errorf("light trie iterator error: %v", i1.Err) + case i1.Next(): + return fmt.Errorf("full trie iterator has more k/v pairs") + case i2.Next(): + return fmt.Errorf("light trie iterator has more k/v pairs") + } + return nil +} diff --git a/light/txpool.go b/light/txpool.go index 7276874b8..0430b280f 100644 --- a/light/txpool.go +++ b/light/txpool.go @@ -24,6 +24,7 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core" + "github.com/ethereum/go-ethereum/core/state" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/event" @@ -100,17 +101,18 @@ func NewTxPool(config *params.ChainConfig, eventMux *event.TypeMux, chain *Light } // currentState returns the light state of the current head header -func (pool *TxPool) currentState() *LightState { - return NewLightState(StateTrieID(pool.chain.CurrentHeader()), pool.odr) +func (pool *TxPool) currentState(ctx context.Context) *state.StateDB { + return NewState(ctx, pool.chain.CurrentHeader(), pool.odr) } // GetNonce returns the "pending" nonce of a given address. It always queries // the nonce belonging to the latest header too in order to detect if another // client using the same key sent a transaction. func (pool *TxPool) GetNonce(ctx context.Context, addr common.Address) (uint64, error) { - nonce, err := pool.currentState().GetNonce(ctx, addr) - if err != nil { - return 0, err + state := pool.currentState(ctx) + nonce := state.GetNonce(addr) + if state.Error() != nil { + return 0, state.Error() } sn, ok := pool.nonce[addr] if ok && sn > nonce { @@ -357,13 +359,9 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error return core.ErrInvalidSender } // Last but not least check for nonce errors - currentState := pool.currentState() - if n, err := currentState.GetNonce(ctx, from); err == nil { - if n > tx.Nonce() { - return core.ErrNonceTooLow - } - } else { - return err + currentState := pool.currentState(ctx) + if n := currentState.GetNonce(from); n > tx.Nonce() { + return core.ErrNonceTooLow } // Check the transaction doesn't exceed the current @@ -382,12 +380,8 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error // Transactor should have enough funds to cover the costs // cost == V + GP * GL - if b, err := currentState.GetBalance(ctx, from); err == nil { - if b.Cmp(tx.Cost()) < 0 { - return core.ErrInsufficientFunds - } - } else { - return err + if b := currentState.GetBalance(from); b.Cmp(tx.Cost()) < 0 { + return core.ErrInsufficientFunds } // Should supply enough intrinsic gas @@ -395,7 +389,7 @@ func (pool *TxPool) validateTx(ctx context.Context, tx *types.Transaction) error return core.ErrIntrinsicGas } - return nil + return currentState.Error() } // add validates a new transaction and sets its state pending if processable. diff --git a/light/vm_env.go b/light/vm_env.go deleted file mode 100644 index 54aa12875..000000000 --- a/light/vm_env.go +++ /dev/null @@ -1,194 +0,0 @@ -// Copyright 2016 The go-ethereum Authors -// This file is part of the go-ethereum library. -// -// The go-ethereum library is free software: you can redistribute it and/or modify -// it under the terms of the GNU Lesser General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// The go-ethereum library is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Lesser General Public License for more details. -// -// You should have received a copy of the GNU Lesser General Public License -// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. - -package light - -import ( - "context" - "math/big" - - "github.com/ethereum/go-ethereum/common" - "github.com/ethereum/go-ethereum/core/types" - "github.com/ethereum/go-ethereum/crypto" -) - -// VMState is a wrapper for the light state that holds the actual context and -// passes it to any state operation that requires it. -type VMState struct { - ctx context.Context - state *LightState - snapshots []*LightState - err error -} - -func NewVMState(ctx context.Context, state *LightState) *VMState { - return &VMState{ctx: ctx, state: state} -} - -func (s *VMState) Error() error { - return s.err -} - -func (s *VMState) AddLog(log *types.Log) {} - -func (s *VMState) AddPreimage(hash common.Hash, preimage []byte) {} - -// errHandler handles and stores any state error that happens during execution. -func (s *VMState) errHandler(err error) { - if err != nil && s.err == nil { - s.err = err - } -} - -func (self *VMState) Snapshot() int { - self.snapshots = append(self.snapshots, self.state.Copy()) - return len(self.snapshots) - 1 -} - -func (self *VMState) RevertToSnapshot(idx int) { - self.state.Set(self.snapshots[idx]) - self.snapshots = self.snapshots[:idx] -} - -// CreateAccount creates creates a new account object and takes ownership. -func (s *VMState) CreateAccount(addr common.Address) { - _, err := s.state.CreateStateObject(s.ctx, addr) - s.errHandler(err) -} - -// AddBalance adds the given amount to the balance of the specified account -func (s *VMState) AddBalance(addr common.Address, amount *big.Int) { - err := s.state.AddBalance(s.ctx, addr, amount) - s.errHandler(err) -} - -// SubBalance adds the given amount to the balance of the specified account -func (s *VMState) SubBalance(addr common.Address, amount *big.Int) { - err := s.state.SubBalance(s.ctx, addr, amount) - s.errHandler(err) -} - -// ForEachStorage calls a callback function for every key/value pair found -// in the local storage cache. Note that unlike core/state.StateObject, -// light.StateObject only returns cached values and doesn't download the -// entire storage tree. -func (s *VMState) ForEachStorage(addr common.Address, cb func(key, value common.Hash) bool) { - err := s.state.ForEachStorage(s.ctx, addr, cb) - s.errHandler(err) -} - -// GetBalance retrieves the balance from the given address or 0 if the account does -// not exist -func (s *VMState) GetBalance(addr common.Address) *big.Int { - res, err := s.state.GetBalance(s.ctx, addr) - s.errHandler(err) - return res -} - -// GetNonce returns the nonce at the given address or 0 if the account does -// not exist -func (s *VMState) GetNonce(addr common.Address) uint64 { - res, err := s.state.GetNonce(s.ctx, addr) - s.errHandler(err) - return res -} - -// SetNonce sets the nonce of the specified account -func (s *VMState) SetNonce(addr common.Address, nonce uint64) { - err := s.state.SetNonce(s.ctx, addr, nonce) - s.errHandler(err) -} - -// GetCode returns the contract code at the given address or nil if the account -// does not exist -func (s *VMState) GetCode(addr common.Address) []byte { - res, err := s.state.GetCode(s.ctx, addr) - s.errHandler(err) - return res -} - -// GetCodeHash returns the contract code hash at the given address -func (s *VMState) GetCodeHash(addr common.Address) common.Hash { - res, err := s.state.GetCode(s.ctx, addr) - s.errHandler(err) - return crypto.Keccak256Hash(res) -} - -// GetCodeSize returns the contract code size at the given address -func (s *VMState) GetCodeSize(addr common.Address) int { - res, err := s.state.GetCode(s.ctx, addr) - s.errHandler(err) - return len(res) -} - -// SetCode sets the contract code at the specified account -func (s *VMState) SetCode(addr common.Address, code []byte) { - err := s.state.SetCode(s.ctx, addr, code) - s.errHandler(err) -} - -// AddRefund adds an amount to the refund value collected during a vm execution -func (s *VMState) AddRefund(gas *big.Int) { - s.state.AddRefund(gas) -} - -// GetRefund returns the refund value collected during a vm execution -func (s *VMState) GetRefund() *big.Int { - return s.state.GetRefund() -} - -// GetState returns the contract storage value at storage address b from the -// contract address a or common.Hash{} if the account does not exist -func (s *VMState) GetState(a common.Address, b common.Hash) common.Hash { - res, err := s.state.GetState(s.ctx, a, b) - s.errHandler(err) - return res -} - -// SetState sets the storage value at storage address key of the account addr -func (s *VMState) SetState(addr common.Address, key common.Hash, value common.Hash) { - err := s.state.SetState(s.ctx, addr, key, value) - s.errHandler(err) -} - -// Suicide marks an account to be removed and clears its balance -func (s *VMState) Suicide(addr common.Address) bool { - res, err := s.state.Suicide(s.ctx, addr) - s.errHandler(err) - return res -} - -// Exist returns true if an account exists at the given address -func (s *VMState) Exist(addr common.Address) bool { - res, err := s.state.HasAccount(s.ctx, addr) - s.errHandler(err) - return res -} - -// Empty returns true if the account at the given address is considered empty -func (s *VMState) Empty(addr common.Address) bool { - so, err := s.state.GetStateObject(s.ctx, addr) - s.errHandler(err) - return so == nil || so.empty() -} - -// HasSuicided returns true if the given account has been marked for deletion -// or false if the account does not exist -func (s *VMState) HasSuicided(addr common.Address) bool { - res, err := s.state.HasSuicided(s.ctx, addr) - s.errHandler(err) - return res -} |