aboutsummaryrefslogtreecommitdiffstats
path: root/light
diff options
context:
space:
mode:
authorFelix Lange <fjl@users.noreply.github.com>2017-06-27 21:57:06 +0800
committerGitHub <noreply@github.com>2017-06-27 21:57:06 +0800
commit9e5f03b6c487175cc5aa1224e5e12fd573f483a7 (patch)
tree475e573ff6c7e77cd069a2f6238afdb27d4bce43 /light
parentbb366271fe33cf87b462dc5a25ac6c448ac6d2e1 (diff)
downloaddexon-9e5f03b6c487175cc5aa1224e5e12fd573f483a7.tar
dexon-9e5f03b6c487175cc5aa1224e5e12fd573f483a7.tar.gz
dexon-9e5f03b6c487175cc5aa1224e5e12fd573f483a7.tar.bz2
dexon-9e5f03b6c487175cc5aa1224e5e12fd573f483a7.tar.lz
dexon-9e5f03b6c487175cc5aa1224e5e12fd573f483a7.tar.xz
dexon-9e5f03b6c487175cc5aa1224e5e12fd573f483a7.tar.zst
dexon-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.go5
-rw-r--r--light/odr.go8
-rw-r--r--light/odr_test.go164
-rw-r--r--light/odr_util.go19
-rw-r--r--light/state.go316
-rw-r--r--light/state_object.go275
-rw-r--r--light/state_test.go248
-rw-r--r--light/trie.go251
-rw-r--r--light/trie_test.go83
-rw-r--r--light/txpool.go32
-rw-r--r--light/vm_env.go194
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
-}