Skip to content
This repository has been archived by the owner on Oct 4, 2019. It is now read-only.

Commit

Permalink
Merge pull request #225 from whilei/dump-account
Browse files Browse the repository at this point in the history
Upgrade 'dump' command to be able to dump for specific addresses.
  • Loading branch information
whilei authored May 24, 2017
2 parents c5a701e + d96fc23 commit 5d69faf
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 21 deletions.
2 changes: 1 addition & 1 deletion cmd/evm/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
63 changes: 53 additions & 10 deletions cmd/geth/chaincmd.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,15 @@ import (
"strconv"
"time"

"encoding/json"
"github.com/ethereumproject/go-ethereum/common"
"github.com/ethereumproject/go-ethereum/console"
"github.com/ethereumproject/go-ethereum/core"
"github.com/ethereumproject/go-ethereum/core/state"
"github.com/ethereumproject/go-ethereum/core/types"
"github.com/ethereumproject/go-ethereum/logger/glog"
"gopkg.in/urfave/cli.v1"
"strings"
)

var (
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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 {
Expand All @@ -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
}

Expand Down
87 changes: 87 additions & 0 deletions cmd/geth/cli.bats
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -199,3 +285,4 @@ teardown() {
[ -d $DATA_DIR/morden ]
}


2 changes: 1 addition & 1 deletion core/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
11 changes: 8 additions & 3 deletions core/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down
30 changes: 26 additions & 4 deletions core/state/dump.go
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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,
Expand All @@ -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)
}
Expand Down
2 changes: 1 addition & 1 deletion core/state/state_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down
3 changes: 2 additions & 1 deletion eth/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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.
Expand Down

0 comments on commit 5d69faf

Please sign in to comment.