diff --git a/cmd/evm/main.go b/cmd/evm/main.go index 877bc277c..aa462f594 100644 --- a/cmd/evm/main.go +++ b/cmd/evm/main.go @@ -164,7 +164,7 @@ func run(ctx *cli.Context) error { if ctx.GlobalBool(DumpFlag.Name) { statedb.Commit() - fmt.Println(string(statedb.Dump())) + fmt.Println(string(statedb.Dump([]common.Address{}))) } if ctx.GlobalBool(SysStatFlag.Name) { diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 210cc4f7f..7cec089d8 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -24,6 +24,7 @@ import ( "strconv" "time" + "encoding/json" "github.com/ethereumproject/go-ethereum/common" "github.com/ethereumproject/go-ethereum/console" "github.com/ethereumproject/go-ethereum/core" @@ -31,6 +32,7 @@ import ( "github.com/ethereumproject/go-ethereum/core/types" "github.com/ethereumproject/go-ethereum/logger/glog" "gopkg.in/urfave/cli.v1" + "strings" ) var ( @@ -82,10 +84,10 @@ Use "ethereum dump 0" to dump the genesis block. `, } rollbackCommand = cli.Command{ - Action: rollback, - Name: "rollback", + Action: rollback, + Name: "rollback", Aliases: []string{"roll-back", "set-head", "sethead"}, - Usage: "rollback [block index number] - set current head for blockchain", + Usage: "rollback [block index number] - set current head for blockchain", Description: ` Rollback set the current head block for block chain already in the database. This is a destructive action, purging any block more recent than the index specified. @@ -191,14 +193,44 @@ func upgradeDB(ctx *cli.Context) error { return nil } +// Original use allows n hashes|ints as space-separated arguments, dumping entire state for each block n[x]. +// $ geth dump [hash|num] [hash|num] ... [hash|num] +// $ geth dump 0x234234234234233 42 43 0xlksdf234r23r234223 +// +// Revised use allows n hashes|ints as comma-separated first argument and n addresses as comma-separated second argument, +// dumping only state information for given addresses if they're present. +// revised use: $ geth dump [hash|num],[hash|num],...,[hash|num] [address],[address],...,[address] func dump(ctx *cli.Context) error { + + if ctx.NArg() == 0 { + return fmt.Errorf("%v: use: $ geth dump [blockHash|blockNum],[blockHash|blockNum] [[addressHex|addressPrefixedHex],[addressHex|addressPrefixedHex]]", ErrInvalidFlag) + } + + blocks := strings.Split(ctx.Args()[0], ",") + addresses := []common.Address{} + argaddress := "" + if ctx.NArg() > 1 { + argaddress = ctx.Args()[1] + } + + if argaddress != "" { + argaddresses := strings.Split(argaddress, ",") + for _, a := range argaddresses { + addresses = append(addresses, common.HexToAddress(strings.TrimSpace(a))) + } + } + chain, chainDb := MakeChain(ctx) - for _, arg := range ctx.Args() { + defer chainDb.Close() + + dumps := state.Dumps{} + for _, b := range blocks { + b = strings.TrimSpace(b) var block *types.Block - if hashish(arg) { - block = chain.GetBlock(common.HexToHash(arg)) + if hashish(b) { + block = chain.GetBlock(common.HexToHash(b)) } else { - num, _ := strconv.Atoi(arg) + num, _ := strconv.Atoi(b) block = chain.GetBlockByNumber(uint64(num)) } if block == nil { @@ -207,12 +239,23 @@ func dump(ctx *cli.Context) error { } else { state, err := state.New(block.Root(), chainDb) if err != nil { - log.Fatal("could not create new state: ", err) + return fmt.Errorf("could not create new state: %v", err) + } + + if len(blocks) > 1 { + dumps = append(dumps, state.RawDump(addresses)) + } else { + fmt.Printf("%s\n", state.Dump(addresses)) + return nil } - fmt.Printf("%s\n", state.Dump()) } } - chainDb.Close() + json, err := json.MarshalIndent(dumps, "", " ") + if err != nil { + return fmt.Errorf("dump err: %v", err) + } + fmt.Printf("%s\n", json) + return nil } diff --git a/cmd/geth/cli.bats b/cmd/geth/cli.bats index 673a40f82..201bb326b 100644 --- a/cmd/geth/cli.bats +++ b/cmd/geth/cli.bats @@ -165,6 +165,92 @@ teardown() { [[ "$output" == *"Alloted 17MB cache"* ]] } +# Test `dump` command. +# All tests copy testdata/testdatadir to $DATA_DIR to ensure we're consistently testing against a not-only-init'ed chaindb. +@test "dump [noargs] | exit >0" { + cp -a $BATS_TEST_DIRNAME/../../cmd/geth/testdata/testdatadir/. $DATA_DIR/ + run $GETH_CMD --data-dir $DATA_DIR dump + echo "$output" + [ "$status" -gt 0 ] + [[ "$output" == *"invalid"* ]] # invalid use +} +@test "dump 0 [noaddress] | exit 0" { + cp -a $BATS_TEST_DIRNAME/../../cmd/geth/testdata/testdatadir/. $DATA_DIR/ + run $GETH_CMD --data-dir $DATA_DIR dump 0 + echo "$output" + [ "$status" -eq 0 ] + [[ "$output" == *"root"* ]] # block state root + [[ "$output" == *"balance"* ]] + [[ "$output" == *"accounts"* ]] + [[ "$output" == *"d7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544"* ]] # block state root + [[ "$output" == *"ffec0913c635baca2f5e57a37aa9fb7b6c9b6e26"* ]] # random hex address existing in genesis + [[ "$output" == *"253319000000000000000"* ]] # random address balance existing in genesis +} +@test "dump 0 fff7ac99c8e4feb60c9750054bdc14ce1857f181 | exit 0" { + cp -a $BATS_TEST_DIRNAME/../../cmd/geth/testdata/testdatadir/. $DATA_DIR/ + run $GETH_CMD --data-dir $DATA_DIR dump 0 fff7ac99c8e4feb60c9750054bdc14ce1857f181 + echo "$output" + [ "$status" -eq 0 ] + [[ "$output" == *"root"* ]] # block state root + [[ "$output" == *"balance"* ]] + [[ "$output" == *"accounts"* ]] + [[ "$output" == *"d7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544"* ]] # block state root + [[ "$output" == *"fff7ac99c8e4feb60c9750054bdc14ce1857f181"* ]] # hex address + [[ "$output" == *"1000000000000000000000"* ]] # address balance +} +@test "dump 0 fff7ac99c8e4feb60c9750054bdc14ce1857f181,0xffe8cbc1681e5e9db74a0f93f8ed25897519120f | exit 0" { + cp -a $BATS_TEST_DIRNAME/../../cmd/geth/testdata/testdatadir/. $DATA_DIR/ + run $GETH_CMD --data-dir $DATA_DIR dump 0 fff7ac99c8e4feb60c9750054bdc14ce1857f181,0xffe8cbc1681e5e9db74a0f93f8ed25897519120f + echo "$output" + [ "$status" -eq 0 ] + [[ "$output" == *"root"* ]] # block state root + [[ "$output" == *"balance"* ]] + [[ "$output" == *"accounts"* ]] + [[ "$output" == *"d7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544"* ]] # block 0 state root + + [[ "$output" == *"fff7ac99c8e4feb60c9750054bdc14ce1857f181"* ]] # hex address + [[ "$output" == *"1000000000000000000000"* ]] # address balance + + [[ "$output" == *"ffe8cbc1681e5e9db74a0f93f8ed25897519120f"* ]] # hex address + [[ "$output" == *"1507000000000000000000"* ]] # address balance +} + +@test "dump 0,1 fff7ac99c8e4feb60c9750054bdc14ce1857f181,0xffe8cbc1681e5e9db74a0f93f8ed25897519120f | exit 0" { + cp -a $BATS_TEST_DIRNAME/../../cmd/geth/testdata/testdatadir/. $DATA_DIR/ + run $GETH_CMD --data-dir $DATA_DIR dump 0,1 fff7ac99c8e4feb60c9750054bdc14ce1857f181,0xffe8cbc1681e5e9db74a0f93f8ed25897519120f + echo "$output" + [ "$status" -eq 0 ] + [[ "$output" == *"root"* ]] # block state root + [[ "$output" == *"balance"* ]] + [[ "$output" == *"accounts"* ]] + + [[ "$output" == *"d7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544"* ]] # block 0 state root + [[ "$output" == *"d67e4d450343046425ae4271474353857ab860dbc0a1dde64b41b5cd3a532bf3"* ]] # block 1 state root + + [[ "$output" == *"fff7ac99c8e4feb60c9750054bdc14ce1857f181"* ]] # hex address + [[ "$output" == *"1000000000000000000000"* ]] # address balance + + [[ "$output" == *"ffe8cbc1681e5e9db74a0f93f8ed25897519120f"* ]] # hex address + [[ "$output" == *"1507000000000000000000"* ]] # address balance +} +@test "dump 0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3 fff7ac99c8e4feb60c9750054bdc14ce1857f181,0xffe8cbc1681e5e9db74a0f93f8ed25897519120f | exit 0" { + cp -a $BATS_TEST_DIRNAME/../../cmd/geth/testdata/testdatadir/. $DATA_DIR/ + run $GETH_CMD --data-dir $DATA_DIR dump 0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3 fff7ac99c8e4feb60c9750054bdc14ce1857f181,0xffe8cbc1681e5e9db74a0f93f8ed25897519120f + echo "$output" + [ "$status" -eq 0 ] + [[ "$output" == *"root"* ]] # block state root + [[ "$output" == *"balance"* ]] + [[ "$output" == *"accounts"* ]] + + [[ "$output" == *"d7f8974fb5ac78d9ac099b9ad5018bedc2ce0a72dad1827a1709da30580f0544"* ]] # block 0 state root + + [[ "$output" == *"fff7ac99c8e4feb60c9750054bdc14ce1857f181"* ]] # hex address + [[ "$output" == *"1000000000000000000000"* ]] # address balance + + [[ "$output" == *"ffe8cbc1681e5e9db74a0f93f8ed25897519120f"* ]] # hex address + [[ "$output" == *"1507000000000000000000"* ]] # address balance +} + # Ensure --testnet and --chain=morden/testnet set up respective subdirs with default 'morden' @test "--chain=testnet creates /morden subdir, activating testnet genesis" { # This is kind of weird, but it is expected. run $GETH_CMD --data-dir $DATA_DIR --chain=testnet --exec 'eth.getBlock(0).hash' console @@ -199,3 +285,4 @@ teardown() { [ -d $DATA_DIR/morden ] } + diff --git a/core/config.go b/core/config.go index df64ef8fb..2e8bf96ce 100644 --- a/core/config.go +++ b/core/config.go @@ -668,7 +668,7 @@ func MakeGenesisDump(chaindb ethdb.Database) (*GenesisDump, error) { if err != nil { return nil, err } - stateDump := genState.RawDump() + stateDump := genState.RawDump([]common.Address{}) stateAccounts := stateDump.Accounts dump.Alloc = make(map[hex]*GenesisDumpAlloc, len(stateAccounts)) diff --git a/core/config_test.go b/core/config_test.go index b6b5cf518..bf4e538dd 100644 --- a/core/config_test.go +++ b/core/config_test.go @@ -111,11 +111,16 @@ func TestChainConfig_IsExplosion(t *testing.T) { func sameGenesisDumpAllocationsBalances(gd1, gd2 *GenesisDump) bool { for address, alloc := range gd2.Alloc { - bal1, _ := new(big.Int).SetString(gd1.Alloc[address].Balance, 0) - bal2, _ := new(big.Int).SetString(alloc.Balance, 0) - if bal1.Cmp(bal2) != 0 { + if gd1.Alloc[address] != nil { + bal1, _ := new(big.Int).SetString(gd1.Alloc[address].Balance, 0) + bal2, _ := new(big.Int).SetString(alloc.Balance, 0) + if bal1.Cmp(bal2) != 0 { + return false + } + } else if alloc.Balance != "" { return false } + } return true } diff --git a/core/state/dump.go b/core/state/dump.go index 78ecee147..687287381 100644 --- a/core/state/dump.go +++ b/core/state/dump.go @@ -33,12 +33,24 @@ type DumpAccount struct { Storage map[string]string `json:"storage"` } +type Dumps []Dump + type Dump struct { Root string `json:"root"` Accounts map[string]DumpAccount `json:"accounts"` } -func (self *StateDB) RawDump() Dump { +func lookupAddress(addr common.Address, addresses []common.Address) bool { + for _, add := range addresses { + if add.Hex() == addr.Hex() { + return true + } + } + return false +} + +func (self *StateDB) RawDump(addresses []common.Address) Dump { + dump := Dump{ Root: common.Bytes2Hex(self.trie.Root()), Accounts: make(map[string]DumpAccount), @@ -47,12 +59,22 @@ func (self *StateDB) RawDump() Dump { it := self.trie.Iterator() for it.Next() { addr := self.trie.GetKey(it.Key) + addrA := common.BytesToAddress(addr) + + if addresses != nil && len(addresses) > 0 { + // check if address existing in argued addresses (lookup) + // if it's not one we're looking for, continue + if !lookupAddress(addrA, addresses) { + continue + } + } + var data Account if err := rlp.DecodeBytes(it.Value, &data); err != nil { panic(err) } - obj := newObject(nil, common.BytesToAddress(addr), data, nil) + obj := newObject(nil, addrA, data, nil) account := DumpAccount{ Balance: data.Balance.String(), Nonce: data.Nonce, @@ -70,8 +92,8 @@ func (self *StateDB) RawDump() Dump { return dump } -func (self *StateDB) Dump() []byte { - json, err := json.MarshalIndent(self.RawDump(), "", " ") +func (self *StateDB) Dump(addresses []common.Address) []byte { + json, err := json.MarshalIndent(self.RawDump(addresses), "", " ") if err != nil { fmt.Println("dump err", err) } diff --git a/core/state/state_test.go b/core/state/state_test.go index cc886bd38..73f47a83e 100644 --- a/core/state/state_test.go +++ b/core/state/state_test.go @@ -51,7 +51,7 @@ func (s *StateSuite) TestDump(c *checker.C) { s.state.Commit() // check that dump contains the state objects that are in trie - got := string(s.state.Dump()) + got := string(s.state.Dump([]common.Address{})) want := `{ "root": "71edff0130dd2385947095001c73d9e28d862fc286fca2b922ca6f6f3cddfdd2", "accounts": { diff --git a/eth/api.go b/eth/api.go index dd7a75811..dc2b43203 100644 --- a/eth/api.go +++ b/eth/api.go @@ -1592,6 +1592,7 @@ func NewPublicDebugAPI(eth *Ethereum) *PublicDebugAPI { } // DumpBlock retrieves the entire state of the database at a given block. +// TODO: update to be able to dump for specific addresses? func (api *PublicDebugAPI) DumpBlock(number uint64) (state.Dump, error) { block := api.eth.BlockChain().GetBlockByNumber(number) if block == nil { @@ -1601,7 +1602,7 @@ func (api *PublicDebugAPI) DumpBlock(number uint64) (state.Dump, error) { if err != nil { return state.Dump{}, err } - return stateDb.RawDump(), nil + return stateDb.RawDump([]common.Address{}), nil } // GetBlockRlp retrieves the RLP encoded for of a single block.