diff options
-rw-r--r-- | CONTRIBUTING.md | 9 | ||||
-rw-r--r-- | accounts/abi/abi.go | 32 | ||||
-rw-r--r-- | accounts/abi/abi_test.go | 82 | ||||
-rw-r--r-- | accounts/abi/argument.go | 5 | ||||
-rw-r--r-- | accounts/abi/event.go | 44 | ||||
-rw-r--r-- | accounts/abi/event_test.go | 40 | ||||
-rw-r--r-- | accounts/abi/type.go | 2 | ||||
-rw-r--r-- | cmd/gethrpctest/main.go | 4 | ||||
-rw-r--r-- | core/state/statedb.go | 10 | ||||
-rw-r--r-- | core/state/statedb_test.go | 120 | ||||
-rw-r--r-- | core/vm/common.go | 2 | ||||
-rw-r--r-- | core/vm/vm.go | 2 | ||||
-rw-r--r-- | eth/api.go | 337 | ||||
-rw-r--r-- | eth/filters/api.go | 4 | ||||
-rw-r--r-- | ethdb/memory_database.go | 2 | ||||
-rw-r--r-- | rpc/javascript.go | 5 | ||||
-rw-r--r-- | rpc/server.go | 2 | ||||
-rw-r--r-- | rpc/types.go | 13 | ||||
-rw-r--r-- | trie/secure_trie.go | 53 |
19 files changed, 612 insertions, 156 deletions
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 918a2c154..829bf5d43 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,3 +1,12 @@ +## Can I have feature X + +Before you do a feature request please check and make sure that it isn't possible +through some other means. The JavaScript enabled console is a powerful feature +in the right hands. Please check our [Bitchin' tricks](https://github.com/ethereum/go-ethereum/wiki/bitchin-tricks) wiki page for more info +and help. + +## Contributing + If you'd like to contribute to go-ethereum please fork, fix, commit and send a pull request. Commits who do not comply with the coding standards are ignored (use gofmt!). If you send pull requests make absolute sure that you diff --git a/accounts/abi/abi.go b/accounts/abi/abi.go index 635dc43fe..b84fd463a 100644 --- a/accounts/abi/abi.go +++ b/accounts/abi/abi.go @@ -37,6 +37,7 @@ type Executer func(datain []byte) []byte // packs data accordingly. type ABI struct { Methods map[string]Method + Events map[string]Event } // JSON returns a parsed ABI interface and error if it failed. @@ -149,14 +150,37 @@ func (abi ABI) Call(executer Executer, name string, args ...interface{}) interfa } func (abi *ABI) UnmarshalJSON(data []byte) error { - var methods []Method - if err := json.Unmarshal(data, &methods); err != nil { + var fields []struct { + Type string + Name string + Const bool + Indexed bool + Inputs []Argument + Outputs []Argument + } + + if err := json.Unmarshal(data, &fields); err != nil { return err } abi.Methods = make(map[string]Method) - for _, method := range methods { - abi.Methods[method.Name] = method + abi.Events = make(map[string]Event) + for _, field := range fields { + switch field.Type { + // empty defaults to function according to the abi spec + case "function", "": + abi.Methods[field.Name] = Method{ + Name: field.Name, + Const: field.Const, + Inputs: field.Inputs, + Outputs: field.Outputs, + } + case "event": + abi.Events[field.Name] = Event{ + Name: field.Name, + Inputs: field.Inputs, + } + } } return nil diff --git a/accounts/abi/abi_test.go b/accounts/abi/abi_test.go index d514fb8c7..000c118f8 100644 --- a/accounts/abi/abi_test.go +++ b/accounts/abi/abi_test.go @@ -31,25 +31,25 @@ import ( const jsondata = ` [ - { "name" : "balance", "const" : true }, - { "name" : "send", "const" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] } + { "type" : "function", "name" : "balance", "const" : true }, + { "type" : "function", "name" : "send", "const" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] } ]` const jsondata2 = ` [ - { "name" : "balance", "const" : true }, - { "name" : "send", "const" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] }, - { "name" : "test", "const" : false, "inputs" : [ { "name" : "number", "type" : "uint32" } ] }, - { "name" : "string", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "string" } ] }, - { "name" : "bool", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "bool" } ] }, - { "name" : "address", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "address" } ] }, - { "name" : "string32", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "string32" } ] }, - { "name" : "uint64[2]", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[2]" } ] }, - { "name" : "uint64[]", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[]" } ] }, - { "name" : "foo", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] }, - { "name" : "bar", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] }, - { "name" : "slice", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] }, - { "name" : "slice256", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] } + { "type" : "function", "name" : "balance", "const" : true }, + { "type" : "function", "name" : "send", "const" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] }, + { "type" : "function", "name" : "test", "const" : false, "inputs" : [ { "name" : "number", "type" : "uint32" } ] }, + { "type" : "function", "name" : "string", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "string" } ] }, + { "type" : "function", "name" : "bool", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "bool" } ] }, + { "type" : "function", "name" : "address", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "address" } ] }, + { "type" : "function", "name" : "string32", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "string32" } ] }, + { "type" : "function", "name" : "uint64[2]", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[2]" } ] }, + { "type" : "function", "name" : "uint64[]", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint64[]" } ] }, + { "type" : "function", "name" : "foo", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" } ] }, + { "type" : "function", "name" : "bar", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32" }, { "name" : "string", "type" : "uint16" } ] }, + { "type" : "function", "name" : "slice", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] }, + { "type" : "function", "name" : "slice256", "const" : false, "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] } ]` func TestType(t *testing.T) { @@ -96,7 +96,7 @@ func TestReader(t *testing.T) { }, "send": Method{ "send", false, []Argument{ - Argument{"amount", Uint256}, + Argument{"amount", Uint256, false}, }, nil, }, }, @@ -238,7 +238,7 @@ func TestTestAddress(t *testing.T) { func TestMethodSignature(t *testing.T) { String, _ := NewType("string") String32, _ := NewType("string32") - m := Method{"foo", false, []Argument{Argument{"bar", String32}, Argument{"baz", String}}, nil} + m := Method{"foo", false, []Argument{Argument{"bar", String32, false}, Argument{"baz", String, false}}, nil} exp := "foo(string32,string)" if m.Sig() != exp { t.Error("signature mismatch", exp, "!=", m.Sig()) @@ -250,7 +250,7 @@ func TestMethodSignature(t *testing.T) { } uintt, _ := NewType("uint") - m = Method{"foo", false, []Argument{Argument{"bar", uintt}}, nil} + m = Method{"foo", false, []Argument{Argument{"bar", uintt, false}}, nil} exp = "foo(uint256)" if m.Sig() != exp { t.Error("signature mismatch", exp, "!=", m.Sig()) @@ -367,8 +367,8 @@ func ExampleJSON() { func TestBytes(t *testing.T) { const definition = `[ - { "name" : "balance", "const" : true, "inputs" : [ { "name" : "address", "type" : "bytes20" } ] }, - { "name" : "send", "const" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] } + { "type" : "function", "name" : "balance", "const" : true, "inputs" : [ { "name" : "address", "type" : "bytes20" } ] }, + { "type" : "function", "name" : "send", "const" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] } ]` abi, err := JSON(strings.NewReader(definition)) @@ -396,10 +396,8 @@ func TestBytes(t *testing.T) { func TestReturn(t *testing.T) { const definition = `[ - { "name" : "balance", "const" : true, "inputs" : [], "outputs" : [ { "name": "", "type": "hash" } ] }, - { "name" : "name", "const" : true, "inputs" : [], "outputs" : [ { "name": "", "type": "address" } ] } - -]` + { "type" : "function", "name" : "balance", "const" : true, "inputs" : [], "outputs" : [ { "name": "", "type": "hash" } ] }, + { "type" : "function", "name" : "name", "const" : true, "inputs" : [], "outputs" : [ { "name": "", "type": "address" } ] }]` abi, err := JSON(strings.NewReader(definition)) if err != nil { @@ -424,3 +422,39 @@ func TestReturn(t *testing.T) { t.Errorf("expected type common.Address, got %T", r) } } + +func TestDefaultFunctionParsing(t *testing.T) { + const definition = `[{ "name" : "balance" }]` + + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + t.Fatal(err) + } + + if _, ok := abi.Methods["balance"]; !ok { + t.Error("expected 'balance' to be present") + } +} + +func TestBareEvents(t *testing.T) { + const definition = `[ + { "type" : "event", "name" : "balance" }, + { "type" : "event", "name" : "name" }]` + + abi, err := JSON(strings.NewReader(definition)) + if err != nil { + t.Fatal(err) + } + + if len(abi.Events) != 2 { + t.Error("expected 2 events") + } + + if _, ok := abi.Events["balance"]; !ok { + t.Error("expected 'balance' event to be present") + } + + if _, ok := abi.Events["name"]; !ok { + t.Error("expected 'name' event to be present") + } +} diff --git a/accounts/abi/argument.go b/accounts/abi/argument.go index 8907b2979..188203a5d 100644 --- a/accounts/abi/argument.go +++ b/accounts/abi/argument.go @@ -24,8 +24,9 @@ import ( // Argument holds the name of the argument and the corresponding type. // Types are used when packing and testing arguments. type Argument struct { - Name string - Type Type + Name string + Type Type + Indexed bool // indexed is only used by events } func (a *Argument) UnmarshalJSON(data []byte) error { diff --git a/accounts/abi/event.go b/accounts/abi/event.go new file mode 100644 index 000000000..7c4e092ea --- /dev/null +++ b/accounts/abi/event.go @@ -0,0 +1,44 @@ +// 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 abi + +import ( + "fmt" + "strings" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +// Event is an event potentially triggered by the EVM's LOG mechanism. The Event +// holds type information (inputs) about the yielded output +type Event struct { + Name string + Inputs []Argument +} + +// Id returns the canonical representation of the event's signature used by the +// abi definition to identify event names and types. +func (e Event) Id() common.Hash { + types := make([]string, len(e.Inputs)) + i := 0 + for _, input := range e.Inputs { + types[i] = input.Type.String() + i++ + } + return common.BytesToHash(crypto.Sha3([]byte(fmt.Sprintf("%v(%v)", e.Name, strings.Join(types, ","))))) +} diff --git a/accounts/abi/event_test.go b/accounts/abi/event_test.go new file mode 100644 index 000000000..34a7a1684 --- /dev/null +++ b/accounts/abi/event_test.go @@ -0,0 +1,40 @@ +package abi + +import ( + "strings" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +func TestEventId(t *testing.T) { + var table = []struct { + definition string + expectations map[string]common.Hash + }{ + { + definition: `[ + { "type" : "event", "name" : "balance", "inputs": [{ "name" : "in", "type": "uint" }] }, + { "type" : "event", "name" : "check", "inputs": [{ "name" : "t", "type": "address" }, { "name": "b", "type": "uint256" }] } + ]`, + expectations: map[string]common.Hash{ + "balance": crypto.Sha3Hash([]byte("balance(uint256)")), + "check": crypto.Sha3Hash([]byte("check(address,uint256)")), + }, + }, + } + + for _, test := range table { + abi, err := JSON(strings.NewReader(test.definition)) + if err != nil { + t.Fatal(err) + } + + for name, event := range abi.Events { + if event.Id() != test.expectations[name] { + t.Errorf("expected id to be %x, got %x", test.expectations[name], event.Id()) + } + } + } +} diff --git a/accounts/abi/type.go b/accounts/abi/type.go index 8f0238fc9..32f761ef0 100644 --- a/accounts/abi/type.go +++ b/accounts/abi/type.go @@ -218,5 +218,5 @@ func (t Type) pack(v interface{}) ([]byte, error) { } } - return nil, fmt.Errorf("ABI: bad input given %T", value.Kind()) + return nil, fmt.Errorf("ABI: bad input given %v", value.Kind()) } diff --git a/cmd/gethrpctest/main.go b/cmd/gethrpctest/main.go index b4530ca51..8522258a9 100644 --- a/cmd/gethrpctest/main.go +++ b/cmd/gethrpctest/main.go @@ -52,6 +52,10 @@ var ( func main() { flag.Parse() + // Enable logging errors, we really do want to see those + glog.SetV(2) + glog.SetToStderr(true) + // Load the test suite to run the RPC against tests, err := tests.LoadBlockTests(*testFile) if err != nil { diff --git a/core/state/statedb.go b/core/state/statedb.go index 8093472b5..22ffa36a0 100644 --- a/core/state/statedb.go +++ b/core/state/statedb.go @@ -206,9 +206,6 @@ func (self *StateDB) Delete(addr common.Address) bool { // Update the given state object and apply it to state trie func (self *StateDB) UpdateStateObject(stateObject *StateObject) { - if len(stateObject.code) > 0 { - self.db.Put(stateObject.codeHash, stateObject.code) - } addr := stateObject.Address() data, err := rlp.EncodeToBytes(stateObject) if err != nil { @@ -375,8 +372,15 @@ func (s *StateDB) commit(db trie.DatabaseWriter) (common.Hash, error) { // and just mark it for deletion in the trie. s.DeleteStateObject(stateObject) } else { + // Write any contract code associated with the state object + if len(stateObject.code) > 0 { + if err := db.Put(stateObject.codeHash, stateObject.code); err != nil { + return common.Hash{}, err + } + } // Write any storage changes in the state object to its trie. stateObject.Update() + // Commit the trie of the object to the batch. // This updates the trie root internally, so // getting the root hash of the storage trie diff --git a/core/state/statedb_test.go b/core/state/statedb_test.go new file mode 100644 index 000000000..8138f8d78 --- /dev/null +++ b/core/state/statedb_test.go @@ -0,0 +1,120 @@ +// 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 state + +import ( + "math/big" + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/ethdb" +) + +// Tests that updating a state trie does not leak any database writes prior to +// actually committing the state. +func TestUpdateLeaks(t *testing.T) { + // Create an empty state database + db, _ := ethdb.NewMemDatabase() + state, _ := New(common.Hash{}, db) + + // Update it with some accounts + for i := byte(0); i < 255; i++ { + obj := state.GetOrNewStateObject(common.BytesToAddress([]byte{i})) + obj.AddBalance(big.NewInt(int64(11 * i))) + obj.SetNonce(uint64(42 * i)) + if i%2 == 0 { + obj.SetState(common.BytesToHash([]byte{i, i, i}), common.BytesToHash([]byte{i, i, i, i})) + } + if i%3 == 0 { + obj.SetCode([]byte{i, i, i, i, i}) + } + state.UpdateStateObject(obj) + } + // Ensure that no data was leaked into the database + for _, key := range db.Keys() { + value, _ := db.Get(key) + t.Errorf("State leaked into database: %x -> %x", key, value) + } +} + +// Tests that no intermediate state of an object is stored into the database, +// only the one right before the commit. +func TestIntermediateLeaks(t *testing.T) { + // Create two state databases, one transitioning to the final state, the other final from the beginning + transDb, _ := ethdb.NewMemDatabase() + finalDb, _ := ethdb.NewMemDatabase() + transState, _ := New(common.Hash{}, transDb) + finalState, _ := New(common.Hash{}, finalDb) + + // Update the states with some objects + for i := byte(0); i < 255; i++ { + // Create a new state object with some data into the transition database + obj := transState.GetOrNewStateObject(common.BytesToAddress([]byte{i})) + obj.SetBalance(big.NewInt(int64(11 * i))) + obj.SetNonce(uint64(42 * i)) + if i%2 == 0 { + obj.SetState(common.BytesToHash([]byte{i, i, i, 0}), common.BytesToHash([]byte{i, i, i, i, 0})) + } + if i%3 == 0 { + obj.SetCode([]byte{i, i, i, i, i, 0}) + } + transState.UpdateStateObject(obj) + + // Overwrite all the data with new values in the transition database + obj.SetBalance(big.NewInt(int64(11*i + 1))) + obj.SetNonce(uint64(42*i + 1)) + if i%2 == 0 { + obj.SetState(common.BytesToHash([]byte{i, i, i, 0}), common.Hash{}) + obj.SetState(common.BytesToHash([]byte{i, i, i, 1}), common.BytesToHash([]byte{i, i, i, i, 1})) + } + if i%3 == 0 { + obj.SetCode([]byte{i, i, i, i, i, 1}) + } + transState.UpdateStateObject(obj) + + // Create the final state object directly in the final database + obj = finalState.GetOrNewStateObject(common.BytesToAddress([]byte{i})) + obj.SetBalance(big.NewInt(int64(11*i + 1))) + obj.SetNonce(uint64(42*i + 1)) + if i%2 == 0 { + obj.SetState(common.BytesToHash([]byte{i, i, i, 1}), common.BytesToHash([]byte{i, i, i, i, 1})) + } + if i%3 == 0 { + obj.SetCode([]byte{i, i, i, i, i, 1}) + } + finalState.UpdateStateObject(obj) + } + if _, err := transState.Commit(); err != nil { + t.Fatalf("failed to commit transition state: %v", err) + } + if _, err := finalState.Commit(); err != nil { + t.Fatalf("failed to commit final state: %v", err) + } + // Cross check the databases to ensure they are the same + for _, key := range finalDb.Keys() { + if _, err := transDb.Get(key); err != nil { + val, _ := finalDb.Get(key) + t.Errorf("entry missing from the transition database: %x -> %x", key, val) + } + } + for _, key := range transDb.Keys() { + if _, err := finalDb.Get(key); err != nil { + val, _ := transDb.Get(key) + t.Errorf("extra entry in the transition database: %x -> %x", key, val) + } + } +} diff --git a/core/vm/common.go b/core/vm/common.go index 2d1aa9332..395ed0471 100644 --- a/core/vm/common.go +++ b/core/vm/common.go @@ -28,6 +28,8 @@ import ( // Global Debug flag indicating Debug VM (full logging) var Debug bool +var GenerateStructLogs bool = false + // Type is the VM type accepted by **NewVm** type Type byte diff --git a/core/vm/vm.go b/core/vm/vm.go index 8e07aaa89..0c6bbcd42 100644 --- a/core/vm/vm.go +++ b/core/vm/vm.go @@ -367,7 +367,7 @@ func (self *Vm) RunPrecompiled(p *PrecompiledAccount, input []byte, contract *Co // log emits a log event to the environment for each opcode encountered. This is not to be confused with the // LOG* opcode. func (self *Vm) log(pc uint64, op OpCode, gas, cost *big.Int, memory *Memory, stack *stack, contract *Contract, err error) { - if Debug { + if Debug || GenerateStructLogs { mem := make([]byte, len(memory.Data())) copy(mem, memory.Data()) diff --git a/eth/api.go b/eth/api.go index 0f55c60a9..0fa855f15 100644 --- a/eth/api.go +++ b/eth/api.go @@ -53,19 +53,40 @@ const ( defaultGas = uint64(90000) ) -// blockByNumber is a commonly used helper function which retrieves and returns the block for the given block number. It -// returns nil when no block could be found. +// blockByNumber is a commonly used helper function which retrieves and returns +// the block for the given block number, capable of handling two special blocks: +// rpc.LatestBlockNumber adn rpc.PendingBlockNumber. It returns nil when no block +// could be found. func blockByNumber(m *miner.Miner, bc *core.BlockChain, blockNr rpc.BlockNumber) *types.Block { + // Pending block is only known by the miner if blockNr == rpc.PendingBlockNumber { return m.PendingBlock() } + // Otherwise resolve and return the block if blockNr == rpc.LatestBlockNumber { return bc.CurrentBlock() } - return bc.GetBlockByNumber(uint64(blockNr)) } +// stateAndBlockByNumber is a commonly used helper function which retrieves and +// returns the state and containing block for the given block number, capable of +// handling two special states: rpc.LatestBlockNumber adn rpc.PendingBlockNumber. +// It returns nil when no block or state could be found. +func stateAndBlockByNumber(m *miner.Miner, bc *core.BlockChain, blockNr rpc.BlockNumber, chainDb ethdb.Database) (*state.StateDB, *types.Block, error) { + // Pending state is only known by the miner + if blockNr == rpc.PendingBlockNumber { + return m.PendingState(), m.PendingBlock(), nil + } + // Otherwise resolve the block number and return its state + block := blockByNumber(m, bc, blockNr) + if block == nil { + return nil, nil, nil + } + stateDb, err := state.New(block.Root(), chainDb) + return stateDb, block, err +} + // PublicEthereumAPI provides an API to access Ethereum related information. // It offers only methods that operate on public data that is freely available to anyone. type PublicEthereumAPI struct { @@ -395,16 +416,12 @@ func (s *PublicBlockChainAPI) BlockNumber() *big.Int { return s.bc.CurrentHeader().Number } -// GetBalance returns the amount of wei for the given address in the state of the given block number. -// When block number equals rpc.LatestBlockNumber the current block is used. +// GetBalance returns the amount of wei for the given address in the state of the +// given block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta +// block numbers are also allowed. func (s *PublicBlockChainAPI) GetBalance(address common.Address, blockNr rpc.BlockNumber) (*big.Int, error) { - block := blockByNumber(s.miner, s.bc, blockNr) - if block == nil { - return nil, nil - } - - state, err := state.New(block.Root(), s.chainDb) - if err != nil { + state, _, err := stateAndBlockByNumber(s.miner, s.bc, blockNr, s.chainDb) + if state == nil || err != nil { return nil, err } return state.GetBalance(address), nil @@ -414,7 +431,14 @@ func (s *PublicBlockChainAPI) GetBalance(address common.Address, blockNr rpc.Blo // transactions in the block are returned in full detail, otherwise only the transaction hash is returned. func (s *PublicBlockChainAPI) GetBlockByNumber(blockNr rpc.BlockNumber, fullTx bool) (map[string]interface{}, error) { if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { - return s.rpcOutputBlock(block, true, fullTx) + response, err := s.rpcOutputBlock(block, true, fullTx) + if err == nil && blockNr == rpc.PendingBlockNumber { + // Pending blocks need to nil out a few fields + for _, field := range []string{"hash", "nonce", "logsBloom", "miner"} { + response[field] = nil + } + } + return response, err } return nil, nil } @@ -431,10 +455,6 @@ func (s *PublicBlockChainAPI) GetBlockByHash(blockHash common.Hash, fullTx bool) // GetUncleByBlockNumberAndIndex returns the uncle block for the given block hash and index. When fullTx is true // all transactions in the block are returned in full detail, otherwise only the transaction hash is returned. func (s *PublicBlockChainAPI) GetUncleByBlockNumberAndIndex(blockNr rpc.BlockNumber, index rpc.HexNumber) (map[string]interface{}, error) { - if blockNr == rpc.PendingBlockNumber { - return nil, nil - } - if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { uncles := block.Uncles() if index.Int() < 0 || index.Int() >= len(uncles) { @@ -464,10 +484,6 @@ func (s *PublicBlockChainAPI) GetUncleByBlockHashAndIndex(blockHash common.Hash, // GetUncleCountByBlockNumber returns number of uncles in the block for the given block number func (s *PublicBlockChainAPI) GetUncleCountByBlockNumber(blockNr rpc.BlockNumber) *rpc.HexNumber { - if blockNr == rpc.PendingBlockNumber { - return rpc.NewHexNumber(0) - } - if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { return rpc.NewHexNumber(len(block.Uncles())) } @@ -508,38 +524,26 @@ func (s *PublicBlockChainAPI) NewBlocks(args NewBlocksArgs) (rpc.Subscription, e // GetCode returns the code stored at the given address in the state for the given block number. func (s *PublicBlockChainAPI) GetCode(address common.Address, blockNr rpc.BlockNumber) (string, error) { - return s.GetData(address, blockNr) -} - -// GetData returns the data stored at the given address in the state for the given block number. -func (s *PublicBlockChainAPI) GetData(address common.Address, blockNr rpc.BlockNumber) (string, error) { - if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { - state, err := state.New(block.Root(), s.chainDb) - if err != nil { - return "", err - } - res := state.GetCode(address) - if len(res) == 0 { // backwards compatibility - return "0x", nil - } - return common.ToHex(res), nil + state, _, err := stateAndBlockByNumber(s.miner, s.bc, blockNr, s.chainDb) + if state == nil || err != nil { + return "", err } - - return "0x", nil + res := state.GetCode(address) + if len(res) == 0 { // backwards compatibility + return "0x", nil + } + return common.ToHex(res), nil } -// GetStorageAt returns the storage from the state at the given address, key and block number. +// GetStorageAt returns the storage from the state at the given address, key and +// block number. The rpc.LatestBlockNumber and rpc.PendingBlockNumber meta block +// numbers are also allowed. func (s *PublicBlockChainAPI) GetStorageAt(address common.Address, key string, blockNr rpc.BlockNumber) (string, error) { - if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { - state, err := state.New(block.Root(), s.chainDb) - if err != nil { - return "", err - } - - return state.GetState(address, common.HexToHash(key)).Hex(), nil + state, _, err := stateAndBlockByNumber(s.miner, s.bc, blockNr, s.chainDb) + if state == nil || err != nil { + return "0x", err } - - return "0x", nil + return state.GetState(address, common.HexToHash(key)).Hex(), nil } // callmsg is the message type used for call transations. @@ -570,55 +574,51 @@ type CallArgs struct { } func (s *PublicBlockChainAPI) doCall(args CallArgs, blockNr rpc.BlockNumber) (string, *big.Int, error) { - if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { - stateDb, err := state.New(block.Root(), s.chainDb) - if err != nil { - return "0x", nil, err - } - - stateDb = stateDb.Copy() - var from *state.StateObject - if args.From == (common.Address{}) { - accounts, err := s.am.Accounts() - if err != nil || len(accounts) == 0 { - from = stateDb.GetOrNewStateObject(common.Address{}) - } else { - from = stateDb.GetOrNewStateObject(accounts[0].Address) - } + // Fetch the state associated with the block number + stateDb, block, err := stateAndBlockByNumber(s.miner, s.bc, blockNr, s.chainDb) + if stateDb == nil || err != nil { + return "0x", nil, err + } + stateDb = stateDb.Copy() + + // Retrieve the account state object to interact with + var from *state.StateObject + if args.From == (common.Address{}) { + accounts, err := s.am.Accounts() + if err != nil || len(accounts) == 0 { + from = stateDb.GetOrNewStateObject(common.Address{}) } else { - from = stateDb.GetOrNewStateObject(args.From) - } - - from.SetBalance(common.MaxBig) - - msg := callmsg{ - from: from, - to: &args.To, - gas: args.Gas.BigInt(), - gasPrice: args.GasPrice.BigInt(), - value: args.Value.BigInt(), - data: common.FromHex(args.Data), - } - - if msg.gas.Cmp(common.Big0) == 0 { - msg.gas = big.NewInt(50000000) - } - - if msg.gasPrice.Cmp(common.Big0) == 0 { - msg.gasPrice = new(big.Int).Mul(big.NewInt(50), common.Shannon) + from = stateDb.GetOrNewStateObject(accounts[0].Address) } + } else { + from = stateDb.GetOrNewStateObject(args.From) + } + from.SetBalance(common.MaxBig) - header := s.bc.CurrentBlock().Header() - vmenv := core.NewEnv(stateDb, s.bc, msg, header) - gp := new(core.GasPool).AddGas(common.MaxBig) - res, gas, err := core.ApplyMessage(vmenv, msg, gp) - if len(res) == 0 { // backwards compatability - return "0x", gas, err - } - return common.ToHex(res), gas, err + // Assemble the CALL invocation + msg := callmsg{ + from: from, + to: &args.To, + gas: args.Gas.BigInt(), + gasPrice: args.GasPrice.BigInt(), + value: args.Value.BigInt(), + data: common.FromHex(args.Data), + } + if msg.gas.Cmp(common.Big0) == 0 { + msg.gas = big.NewInt(50000000) } + if msg.gasPrice.Cmp(common.Big0) == 0 { + msg.gasPrice = new(big.Int).Mul(big.NewInt(50), common.Shannon) + } + // Execute the call and return + vmenv := core.NewEnv(stateDb, s.bc, msg, block.Header()) + gp := new(core.GasPool).AddGas(common.MaxBig) - return "0x", common.Big0, nil + res, gas, err := core.ApplyMessage(vmenv, msg, gp) + if len(res) == 0 { // backwards compatibility + return "0x", gas, err + } + return common.ToHex(res), gas, err } // Call executes the given transaction on the state for the given block number. @@ -802,14 +802,9 @@ func getTransaction(chainDb ethdb.Database, txPool *core.TxPool, txHash common.H // GetBlockTransactionCountByNumber returns the number of transactions in the block with the given block number. func (s *PublicTransactionPoolAPI) GetBlockTransactionCountByNumber(blockNr rpc.BlockNumber) *rpc.HexNumber { - if blockNr == rpc.PendingBlockNumber { - return rpc.NewHexNumber(0) - } - if block := blockByNumber(s.miner, s.bc, blockNr); block != nil { return rpc.NewHexNumber(len(block.Transactions())) } - return nil } @@ -839,13 +834,8 @@ func (s *PublicTransactionPoolAPI) GetTransactionByBlockHashAndIndex(blockHash c // GetTransactionCount returns the number of transactions the given address has sent for the given block number func (s *PublicTransactionPoolAPI) GetTransactionCount(address common.Address, blockNr rpc.BlockNumber) (*rpc.HexNumber, error) { - block := blockByNumber(s.miner, s.bc, blockNr) - if block == nil { - return nil, nil - } - - state, err := state.New(block.Root(), s.chainDb) - if err != nil { + state, _, err := stateAndBlockByNumber(s.miner, s.bc, blockNr, s.chainDb) + if state == nil || err != nil { return nil, err } return rpc.NewHexNumber(state.GetNonce(address)), nil @@ -1501,6 +1491,145 @@ func (api *PrivateDebugAPI) SetHead(number uint64) { api.eth.BlockChain().SetHead(number) } +// StructLogRes stores a structured log emitted by the evm while replaying a +// transaction in debug mode +type structLogRes struct { + Pc uint64 `json:"pc"` + Op string `json:"op"` + Gas *big.Int `json:"gas"` + GasCost *big.Int `json:"gasCost"` + Error error `json:"error"` + Stack []string `json:"stack"` + Memory map[string]string `json:"memory"` + Storage map[string]string `json:"storage"` +} + +// TransactionExecutionRes groups all structured logs emitted by the evm +// while replaying a transaction in debug mode as well as the amount of +// gas used and the return value +type TransactionExecutionResult struct { + Gas *big.Int `json:"gas"` + ReturnValue string `json:"returnValue"` + StructLogs []structLogRes `json:"structLogs"` +} + +func (s *PrivateDebugAPI) doReplayTransaction(txHash common.Hash) ([]vm.StructLog, []byte, *big.Int, error) { + // Retrieve the tx from the chain + tx, _, blockIndex, _ := core.GetTransaction(s.eth.ChainDb(), txHash) + + if tx == nil { + return nil, nil, nil, fmt.Errorf("Transaction not found") + } + + block := s.eth.BlockChain().GetBlockByNumber(blockIndex - 1) + if block == nil { + return nil, nil, nil, fmt.Errorf("Unable to retrieve prior block") + } + + // Create the state database + stateDb, err := state.New(block.Root(), s.eth.ChainDb()) + if err != nil { + return nil, nil, nil, err + } + + txFrom, err := tx.From() + + if err != nil { + return nil, nil, nil, fmt.Errorf("Unable to create transaction sender") + } + from := stateDb.GetOrNewStateObject(txFrom) + msg := callmsg{ + from: from, + to: tx.To(), + gas: tx.Gas(), + gasPrice: tx.GasPrice(), + value: tx.Value(), + data: tx.Data(), + } + + vmenv := core.NewEnv(stateDb, s.eth.BlockChain(), msg, block.Header()) + gp := new(core.GasPool).AddGas(block.GasLimit()) + vm.GenerateStructLogs = true + defer func() { vm.GenerateStructLogs = false }() + + ret, gas, err := core.ApplyMessage(vmenv, msg, gp) + if err != nil { + return nil, nil, nil, fmt.Errorf("Error executing transaction %v", err) + } + + return vmenv.StructLogs(), ret, gas, nil +} + +// Executes a transaction and returns the structured logs of the evm +// gathered during the execution +func (s *PrivateDebugAPI) ReplayTransaction(txHash common.Hash, stackDepth int, memorySize int, storageSize int) (*TransactionExecutionResult, error) { + + structLogs, ret, gas, err := s.doReplayTransaction(txHash) + + if err != nil { + return nil, err + } + + res := TransactionExecutionResult{ + Gas: gas, + ReturnValue: fmt.Sprintf("%x", ret), + StructLogs: make([]structLogRes, len(structLogs)), + } + + for index, trace := range structLogs { + + stackLength := len(trace.Stack) + + // Return full stack by default + if stackDepth != -1 && stackDepth < stackLength { + stackLength = stackDepth + } + + res.StructLogs[index] = structLogRes{ + Pc: trace.Pc, + Op: trace.Op.String(), + Gas: trace.Gas, + GasCost: trace.GasCost, + Error: trace.Err, + Stack: make([]string, stackLength), + Memory: make(map[string]string), + Storage: make(map[string]string), + } + + for i := 0; i < stackLength; i++ { + res.StructLogs[index].Stack[i] = fmt.Sprintf("%x", common.LeftPadBytes(trace.Stack[i].Bytes(), 32)) + } + + addr := 0 + memorySizeLocal := memorySize + + // Return full memory by default + if memorySize == -1 { + memorySizeLocal = len(trace.Memory) + } + + for i := 0; i+16 <= len(trace.Memory) && addr < memorySizeLocal; i += 16 { + res.StructLogs[index].Memory[fmt.Sprintf("%04d", addr*16)] = fmt.Sprintf("%x", trace.Memory[i:i+16]) + addr++ + } + + storageLength := len(trace.Stack) + if storageSize != -1 && storageSize < storageLength { + storageLength = storageSize + } + + i := 0 + for storageIndex, storageValue := range trace.Storage { + if i >= storageLength { + break + } + res.StructLogs[index].Storage[fmt.Sprintf("%x", storageIndex)] = fmt.Sprintf("%x", storageValue) + i++ + } + } + return &res, nil +} + // PublicNetAPI offers network related RPC methods type PublicNetAPI struct { net *p2p.Server diff --git a/eth/filters/api.go b/eth/filters/api.go index f2b0ed32f..aa4c305a6 100644 --- a/eth/filters/api.go +++ b/eth/filters/api.go @@ -239,13 +239,13 @@ func (args *NewFilterArgs) UnmarshalJSON(data []byte) error { return err } - if raw.From == nil { + if raw.From == nil || raw.From.Int64() < 0 { args.FromBlock = rpc.LatestBlockNumber } else { args.FromBlock = *raw.From } - if raw.ToBlock == nil { + if raw.ToBlock == nil || raw.ToBlock.Int64() < 0 { args.ToBlock = rpc.LatestBlockNumber } else { args.ToBlock = *raw.ToBlock diff --git a/ethdb/memory_database.go b/ethdb/memory_database.go index 45423ed73..a729f5233 100644 --- a/ethdb/memory_database.go +++ b/ethdb/memory_database.go @@ -107,7 +107,7 @@ func (b *memBatch) Put(key, value []byte) error { b.lock.Lock() defer b.lock.Unlock() - b.writes = append(b.writes, kv{key, common.CopyBytes(value)}) + b.writes = append(b.writes, kv{common.CopyBytes(key), common.CopyBytes(value)}) return nil } diff --git a/rpc/javascript.go b/rpc/javascript.go index c145163e5..4c0ac5354 100644 --- a/rpc/javascript.go +++ b/rpc/javascript.go @@ -407,6 +407,11 @@ web3._extend({ call: 'debug_writeMemProfile', params: 1 }), + new web3._extend.Method({ + name: 'replayTransaction', + call: 'debug_replayTransaction', + params: 4 + }) ], properties: [ diff --git a/rpc/server.go b/rpc/server.go index 0b93a4e64..5b88d843a 100644 --- a/rpc/server.go +++ b/rpc/server.go @@ -332,7 +332,6 @@ func (s *Server) handle(ctx context.Context, codec ServerCodec, req *serverReque return res } } - return codec.CreateResponse(req.id, reply[0].Interface()) } @@ -344,7 +343,6 @@ func (s *Server) exec(ctx context.Context, codec ServerCodec, req *serverRequest } else { response = s.handle(ctx, codec, req) } - if err := codec.Write(response); err != nil { glog.V(logger.Error).Infof("%v\n", err) codec.Close() diff --git a/rpc/types.go b/rpc/types.go index 02295a022..f268d84db 100644 --- a/rpc/types.go +++ b/rpc/types.go @@ -174,12 +174,14 @@ type HexNumber big.Int // NewHexNumber creates a new hex number instance which will serialize the given val with `%#x` on marshal. func NewHexNumber(val interface{}) *HexNumber { if val == nil { - return nil + return nil // note, this doesn't catch nil pointers, only passing nil directly! } - if v, ok := val.(*big.Int); ok && v != nil { - hn := new(big.Int).Set(v) - return (*HexNumber)(hn) + if v, ok := val.(*big.Int); ok { + if v != nil { + return (*HexNumber)(new(big.Int).Set(v)) + } + return nil } rval := reflect.ValueOf(val) @@ -303,10 +305,9 @@ const ( ) // UnmarshalJSON parses the given JSON fragement into a BlockNumber. It supports: -// - "latest" or "earliest" as string arguments +// - "latest", "earliest" or "pending" as string arguments // - the block number // Returned errors: -// - an unsupported error when "pending" is specified (not yet implemented) // - an invalid block number error when the given argument isn't a known strings // - an out of range error when the given block number is either too little or too large func (bn *BlockNumber) UnmarshalJSON(data []byte) error { diff --git a/trie/secure_trie.go b/trie/secure_trie.go index caeef3c3a..be7defe83 100644 --- a/trie/secure_trie.go +++ b/trie/secure_trie.go @@ -40,9 +40,10 @@ var secureKeyPrefix = []byte("secure-key-") type SecureTrie struct { *Trie - hash hash.Hash - secKeyBuf []byte - hashKeyBuf []byte + hash hash.Hash + hashKeyBuf []byte + secKeyBuf []byte + secKeyCache map[string][]byte } // NewSecure creates a trie with an existing root node from db. @@ -59,7 +60,10 @@ func NewSecure(root common.Hash, db Database) (*SecureTrie, error) { if err != nil { return nil, err } - return &SecureTrie{Trie: trie}, nil + return &SecureTrie{ + Trie: trie, + secKeyCache: make(map[string][]byte), + }, nil } // Get returns the value for key stored in the trie. @@ -105,7 +109,7 @@ func (t *SecureTrie) TryUpdate(key, value []byte) error { if err != nil { return err } - t.Trie.db.Put(t.secKey(hk), key) + t.secKeyCache[string(hk)] = common.CopyBytes(key) return nil } @@ -119,16 +123,53 @@ func (t *SecureTrie) Delete(key []byte) { // TryDelete removes any existing value for key from the trie. // If a node was not found in the database, a MissingNodeError is returned. func (t *SecureTrie) TryDelete(key []byte) error { - return t.Trie.TryDelete(t.hashKey(key)) + hk := t.hashKey(key) + delete(t.secKeyCache, string(hk)) + return t.Trie.TryDelete(hk) } // GetKey returns the sha3 preimage of a hashed key that was // previously used to store a value. func (t *SecureTrie) GetKey(shaKey []byte) []byte { + if key, ok := t.secKeyCache[string(shaKey)]; ok { + return key + } key, _ := t.Trie.db.Get(t.secKey(shaKey)) return key } +// Commit writes all nodes and the secure hash pre-images to the trie's database. +// Nodes are stored with their sha3 hash as the key. +// +// Committing flushes nodes from memory. Subsequent Get calls will load nodes +// from the database. +func (t *SecureTrie) Commit() (root common.Hash, err error) { + return t.CommitTo(t.db) +} + +// CommitTo writes all nodes and the secure hash pre-images to the given database. +// Nodes are stored with their sha3 hash as the key. +// +// Committing flushes nodes from memory. Subsequent Get calls will load nodes from +// the trie's database. Calling code must ensure that the changes made to db are +// written back to the trie's attached database before using the trie. +func (t *SecureTrie) CommitTo(db DatabaseWriter) (root common.Hash, err error) { + if len(t.secKeyCache) > 0 { + for hk, key := range t.secKeyCache { + if err := db.Put(t.secKey([]byte(hk)), key); err != nil { + return common.Hash{}, err + } + } + t.secKeyCache = make(map[string][]byte) + } + n, err := t.hashRoot(db) + if err != nil { + return (common.Hash{}), err + } + t.root = n + return common.BytesToHash(n.(hashNode)), nil +} + func (t *SecureTrie) secKey(key []byte) []byte { t.secKeyBuf = append(t.secKeyBuf[:0], secureKeyPrefix...) t.secKeyBuf = append(t.secKeyBuf, key...) |