Merge pull request #446: Restructure Importing Test

This commit is contained in:
Aleksandr Bezobchuk 2018-07-18 14:14:14 -04:00
parent 5e41b54971
commit 2caeba151d
10 changed files with 239 additions and 178 deletions

View File

@ -36,9 +36,11 @@ $ make tools deps install
There is an included Ethereum Mainnet blockchain file in `data/blockchain` that provides an easy way to run the demo of parsing Mainnet Ethereum blocks. The dump in `data/` only includes up to block `97638`. To run this, type the following command: There is an included Ethereum Mainnet blockchain file in `data/blockchain` that provides an easy way to run the demo of parsing Mainnet Ethereum blocks. The dump in `data/` only includes up to block `97638`. To run this, type the following command:
```bash ```bash
$ go run main.go copied.go $ go run test/run.go
``` ```
By default, state will be dumped into `$HOME/.ethermint`. See `--help` for further usage.
### Community ### Community
The following chat channels and forums are a great spot to ask questions about Ethermint: The following chat channels and forums are a great spot to ask questions about Ethermint:

View File

@ -1,96 +0,0 @@
// Code copied from go-ethereum
package main
import (
"fmt"
"math/big"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/params"
)
// Some weird constants to avoid constant memory allocs for them.
var (
big8 = big.NewInt(8)
big32 = big.NewInt(32)
)
// AccumulateRewards credits the coinbase of the given block with the mining
// reward. The total reward consists of the static block reward and rewards for
// included uncles. The coinbase of each uncle block is also rewarded.
func accumulateRewards(config *params.ChainConfig, state *state.StateDB, header *types.Header, uncles []*types.Header) {
// Select the correct block reward based on chain progression
blockReward := ethash.FrontierBlockReward
if config.IsByzantium(header.Number) {
blockReward = ethash.ByzantiumBlockReward
}
// Accumulate the rewards for the miner and any included uncles
reward := new(big.Int).Set(blockReward)
r := new(big.Int)
for _, uncle := range uncles {
r.Add(uncle.Number, big8)
r.Sub(r, header.Number)
r.Mul(r, blockReward)
r.Div(r, big8)
state.AddBalance(uncle.Coinbase, r)
r.Div(blockReward, big32)
reward.Add(reward, r)
}
state.AddBalance(header.Coinbase, reward)
}
// 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 uint64 `json:"gas"`
GasCost uint64 `json:"gasCost"`
Depth int `json:"depth"`
Error error `json:"error,omitempty"`
Stack *[]string `json:"stack,omitempty"`
Memory *[]string `json:"memory,omitempty"`
Storage *map[string]string `json:"storage,omitempty"`
}
// formatLogs formats EVM returned structured logs for json output
func FormatLogs(logs []vm.StructLog) []StructLogRes {
formatted := make([]StructLogRes, len(logs))
for index, trace := range logs {
formatted[index] = StructLogRes{
Pc: trace.Pc,
Op: trace.Op.String(),
Gas: trace.Gas,
GasCost: trace.GasCost,
Depth: trace.Depth,
Error: trace.Err,
}
if trace.Stack != nil {
stack := make([]string, len(trace.Stack))
for i, stackValue := range trace.Stack {
stack[i] = fmt.Sprintf("%x", math.PaddedBigBytes(stackValue, 32))
}
formatted[index].Stack = &stack
}
if trace.Memory != nil {
memory := make([]string, 0, (len(trace.Memory)+31)/32)
for i := 0; i+32 <= len(trace.Memory); i += 32 {
memory = append(memory, fmt.Sprintf("%x", trace.Memory[i:i+32]))
}
formatted[index].Memory = &memory
}
if trace.Storage != nil {
storage := make(map[string]string)
for i, storageValue := range trace.Storage {
storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue)
}
formatted[index].Storage = &storage
}
}
return formatted
}

View File

@ -2,6 +2,7 @@ package core
import ( import (
ethdb "github.com/ethereum/go-ethereum/ethdb" ethdb "github.com/ethereum/go-ethereum/ethdb"
dbm "github.com/tendermint/tendermint/libs/db" dbm "github.com/tendermint/tendermint/libs/db"
) )

View File

@ -2,28 +2,31 @@ package state
import ( import (
"github.com/cosmos/cosmos-sdk/store" "github.com/cosmos/cosmos-sdk/store"
"github.com/cosmos/cosmos-sdk/types"
sdk "github.com/cosmos/cosmos-sdk/types" sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/cosmos/ethermint/core" "github.com/cosmos/ethermint/core"
ethcommon "github.com/ethereum/go-ethereum/common" ethcommon "github.com/ethereum/go-ethereum/common"
ethstate "github.com/ethereum/go-ethereum/core/state" ethstate "github.com/ethereum/go-ethereum/core/state"
ethtrie "github.com/ethereum/go-ethereum/trie" ethtrie "github.com/ethereum/go-ethereum/trie"
lru "github.com/hashicorp/golang-lru" lru "github.com/hashicorp/golang-lru"
dbm "github.com/tendermint/tendermint/libs/db" dbm "github.com/tendermint/tendermint/libs/db"
) )
var ( var (
// AccountsKey is the key used for storing Ethereum accounts in the Cosmos // AccountsKey is the key used for storing Ethereum accounts in the Cosmos
// SDK multi-store. // SDK multi-store.
AccountsKey = types.NewKVStoreKey("account") AccountsKey = sdk.NewKVStoreKey("account")
// StorageKey is the key used for storing Ethereum contract storage in the // StorageKey is the key used for storing Ethereum contract storage in the
// Cosmos SDK multi-store. // Cosmos SDK multi-store.
StorageKey = types.NewKVStoreKey("storage") StorageKey = sdk.NewKVStoreKey("storage")
// CodeKey is the key used for storing Ethereum contract code in the Cosmos // CodeKey is the key used for storing Ethereum contract code in the Cosmos
// SDK multi-store. // SDK multi-store.
CodeKey = types.NewKVStoreKey("code") CodeKey = sdk.NewKVStoreKey("code")
) )
const ( const (
@ -75,8 +78,8 @@ func NewDatabase(stateDB, codeDB dbm.DB) (*Database, error) {
// Create the underlying multi-store stores that will persist account and // Create the underlying multi-store stores that will persist account and
// account storage data. // account storage data.
db.stateStore.MountStoreWithDB(AccountsKey, types.StoreTypeIAVL, nil) db.stateStore.MountStoreWithDB(AccountsKey, sdk.StoreTypeIAVL, nil)
db.stateStore.MountStoreWithDB(StorageKey, types.StoreTypeIAVL, nil) db.stateStore.MountStoreWithDB(StorageKey, sdk.StoreTypeIAVL, nil)
// Load the latest account state from the Cosmos SDK multi-store. // Load the latest account state from the Cosmos SDK multi-store.
if err := db.stateStore.LoadLatestVersion(); err != nil { if err := db.stateStore.LoadLatestVersion(); err != nil {
@ -95,6 +98,7 @@ func NewDatabase(stateDB, codeDB dbm.DB) (*Database, error) {
return db, nil return db, nil
} }
// LatestVersion returns the latest version of the underlying mult-store.
func (db *Database) LatestVersion() int64 { func (db *Database) LatestVersion() int64 {
return db.stateStore.LastCommitID().Version return db.stateStore.LastCommitID().Version
} }
@ -192,7 +196,7 @@ func (db *Database) ContractCodeSize(addrHash, codeHash ethcommon.Hash) (int, er
// Commit commits the underlying Cosmos SDK multi-store returning the commit // Commit commits the underlying Cosmos SDK multi-store returning the commit
// ID. // ID.
func (db *Database) Commit() types.CommitID { func (db *Database) Commit() sdk.CommitID {
return db.stateStore.Commit() return db.stateStore.Commit()
} }

View File

@ -4,6 +4,7 @@ import (
"encoding/binary" "encoding/binary"
"github.com/cosmos/cosmos-sdk/store" "github.com/cosmos/cosmos-sdk/store"
ethcommon "github.com/ethereum/go-ethereum/common" ethcommon "github.com/ethereum/go-ethereum/common"
ethdb "github.com/ethereum/go-ethereum/ethdb" ethdb "github.com/ethereum/go-ethereum/ethdb"
ethtrie "github.com/ethereum/go-ethereum/trie" ethtrie "github.com/ethereum/go-ethereum/trie"

8
test/importer/doc.go Normal file
View File

@ -0,0 +1,8 @@
// The implementation below is to be considered highly unstable and a continual
// WIP. It is a means to replicate and test replaying Ethereum transactions
// using the Cosmos SDK and the EVM. The ultimate result will be what is known
// as Ethermint.
//
// Note: Some code is directly copied from go-ethereum.
package importer

View File

@ -1,24 +1,18 @@
// The implementation below is to be considered highly unstable and a continual package importer
// WIP. It is a means to replicate and test replaying Ethereum transactions
// using the Cosmos SDK and the EVM. The ultimate result will be what is known
// as Ethermint.
package main
import ( import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"encoding/json" "encoding/json"
"flag"
"fmt" "fmt"
"io" "io"
"os" "os"
"os/signal" "path"
"runtime/pprof"
"syscall"
"time" "time"
"github.com/cosmos/ethermint/core" "github.com/cosmos/ethermint/core"
"github.com/cosmos/ethermint/state" "github.com/cosmos/ethermint/state"
ethcommon "github.com/ethereum/go-ethereum/common" ethcommon "github.com/ethereum/go-ethereum/common"
ethmisc "github.com/ethereum/go-ethereum/consensus/misc" ethmisc "github.com/ethereum/go-ethereum/consensus/misc"
ethcore "github.com/ethereum/go-ethereum/core" ethcore "github.com/ethereum/go-ethereum/core"
@ -27,60 +21,32 @@ import (
ethvm "github.com/ethereum/go-ethereum/core/vm" ethvm "github.com/ethereum/go-ethereum/core/vm"
ethparams "github.com/ethereum/go-ethereum/params" ethparams "github.com/ethereum/go-ethereum/params"
ethrlp "github.com/ethereum/go-ethereum/rlp" ethrlp "github.com/ethereum/go-ethereum/rlp"
dbm "github.com/tendermint/tendermint/libs/db"
) )
var cpuprofile = flag.String("cpu-profile", "", "write cpu profile `file`")
var blockchain = flag.String("blockchain", "data/blockchain", "file containing blocks to load")
var datadir = flag.String("datadir", "", "directory for ethermint data")
var ( var (
// TODO: Document...
miner501 = ethcommon.HexToAddress("0x35e8e5dC5FBd97c5b421A80B596C030a2Be2A04D") miner501 = ethcommon.HexToAddress("0x35e8e5dC5FBd97c5b421A80B596C030a2Be2A04D")
genInvestor = ethcommon.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0") genInvestor = ethcommon.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0")
) )
// TODO: Document... // Importer implements a structure to facilitate testing of importing mainnet
func main() { // Ethereum blocks and transactions using the Cosmos SDK components and the
flag.Parse() // EVM.
type Importer struct {
if *cpuprofile != "" { EthermintDB *state.Database
f, err := os.Create(*cpuprofile) BlockchainFile string
if err != nil { Datadir string
fmt.Printf("could not create CPU profile: %v\n", err) InterruptCh <-chan bool
return
} }
if err := pprof.StartCPUProfile(f); err != nil { // Import performs an import given an Importer that has a Geth stateDB
fmt.Printf("could not start CPU profile: %v\n", err) // implementation and a blockchain exported file.
return func (imp *Importer) Import() {
} // only create genesis if it is a brand new database
if imp.EthermintDB.LatestVersion() == 0 {
defer pprof.StopCPUProfile()
}
sigs := make(chan os.Signal, 1)
interruptCh := make(chan bool, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigs
interruptCh <- true
}()
stateDB := dbm.NewDB("state", dbm.LevelDBBackend, *datadir)
codeDB := dbm.NewDB("code", dbm.LevelDBBackend, *datadir)
ethermintDB, err := state.NewDatabase(stateDB, codeDB)
if err != nil {
panic(err)
}
// Only create genesis if it is a brand new database
if ethermintDB.LatestVersion() == 0 {
// start with empty root hash (i.e. empty state) // start with empty root hash (i.e. empty state)
gethStateDB, err := ethstate.New(ethcommon.Hash{}, ethermintDB) gethStateDB, err := ethstate.New(ethcommon.Hash{}, imp.EthermintDB)
if err != nil { if err != nil {
panic(err) panic(fmt.Sprintf("failed to instantiate geth state.StateDB: %v", err))
} }
genBlock := ethcore.DefaultGenesisBlock() genBlock := ethcore.DefaultGenesisBlock()
@ -104,7 +70,7 @@ func main() {
panic(err) panic(err)
} }
commitID := ethermintDB.Commit() commitID := imp.EthermintDB.Commit()
fmt.Printf("commitID after genesis: %v\n", commitID) fmt.Printf("commitID after genesis: %v\n", commitID)
fmt.Printf("genesis state root hash: %x\n", genRoot[:]) fmt.Printf("genesis state root hash: %x\n", genRoot[:])
@ -112,9 +78,7 @@ func main() {
// file with blockchain data exported from geth by using "geth exportdb" // file with blockchain data exported from geth by using "geth exportdb"
// command. // command.
// input, err := os.Open(imp.BlockchainFile)
// TODO: Allow this to be configurable
input, err := os.Open(*blockchain)
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -134,15 +98,16 @@ func main() {
) )
var prevRoot ethcommon.Hash var prevRoot ethcommon.Hash
binary.BigEndian.PutUint64(prevRoot[:8], uint64(ethermintDB.LatestVersion())) binary.BigEndian.PutUint64(prevRoot[:8], uint64(imp.EthermintDB.LatestVersion()))
ethermintDB.Tracing = true imp.EthermintDB.Tracing = true
chainContext := core.NewChainContext() chainContext := core.NewChainContext()
vmConfig := ethvm.Config{} vmConfig := ethvm.Config{}
n := 0 n := 0
startTime := time.Now() startTime := time.Now()
interrupt := false interrupt := false
var lastSkipped uint64 var lastSkipped uint64
for !interrupt { for !interrupt {
if err = stream.Decode(&block); err == io.EOF { if err = stream.Decode(&block); err == io.EOF {
@ -153,12 +118,13 @@ func main() {
} }
// don't import blocks already imported // don't import blocks already imported
if block.NumberU64() < uint64(ethermintDB.LatestVersion()) { if block.NumberU64() < uint64(imp.EthermintDB.LatestVersion()) {
lastSkipped = block.NumberU64() lastSkipped = block.NumberU64()
continue continue
} }
if lastSkipped > 0 { if lastSkipped > 0 {
fmt.Printf("Skipped blocks up to %d\n", lastSkipped) fmt.Printf("skipped blocks up to %d\n", lastSkipped)
lastSkipped = 0 lastSkipped = 0
} }
@ -166,7 +132,7 @@ func main() {
chainContext.Coinbase = header.Coinbase chainContext.Coinbase = header.Coinbase
chainContext.SetHeader(block.NumberU64(), header) chainContext.SetHeader(block.NumberU64(), header)
gethStateDB, err := ethstate.New(prevRoot, ethermintDB) gethStateDB, err := ethstate.New(prevRoot, imp.EthermintDB)
if err != nil { if err != nil {
panic(fmt.Errorf("failed to instantiate geth state.StateDB at block %d: %v", block.NumberU64(), err)) panic(fmt.Errorf("failed to instantiate geth state.StateDB at block %d: %v", block.NumberU64(), err))
} }
@ -186,7 +152,6 @@ func main() {
gethStateDB.Prepare(tx.Hash(), block.Hash(), i) gethStateDB.Prepare(tx.Hash(), block.Hash(), i)
txHash := tx.Hash() txHash := tx.Hash()
// TODO: Why this address?
if bytes.Equal(txHash[:], ethcommon.FromHex("0xc438cfcc3b74a28741bda361032f1c6362c34aa0e1cedff693f31ec7d6a12717")) { if bytes.Equal(txHash[:], ethcommon.FromHex("0xc438cfcc3b74a28741bda361032f1c6362c34aa0e1cedff693f31ec7d6a12717")) {
vmConfig.Tracer = ethvm.NewStructLogger(&ethvm.LogConfig{}) vmConfig.Tracer = ethvm.NewStructLogger(&ethvm.LogConfig{})
vmConfig.Debug = true vmConfig.Debug = true
@ -194,9 +159,9 @@ func main() {
receipt, _, err := ethcore.ApplyTransaction(chainConfig, chainContext, nil, gp, gethStateDB, header, tx, usedGas, vmConfig) receipt, _, err := ethcore.ApplyTransaction(chainConfig, chainContext, nil, gp, gethStateDB, header, tx, usedGas, vmConfig)
if vmConfig.Tracer != nil { if vmConfig.Tracer != nil {
w, err := os.Create("structlogs.txt") w, err := os.Create(path.Join(imp.Datadir, "structlogs.txt"))
if err != nil { if err != nil {
panic(err) panic(fmt.Sprintf("failed to create file for VM tracing: %v", err))
} }
encoder := json.NewEncoder(w) encoder := json.NewEncoder(w)
@ -232,8 +197,7 @@ func main() {
} }
// commit block in Ethermint // commit block in Ethermint
ethermintDB.Commit() imp.EthermintDB.Commit()
//fmt.Printf("commitID after block %d: %v\n", block.NumberU64(), commitID)
switch block.NumberU64() { switch block.NumberU64() {
case 500: case 500:
@ -249,26 +213,27 @@ func main() {
// Check for interrupts // Check for interrupts
select { select {
case interrupt = <-interruptCh: case interrupt = <-imp.InterruptCh:
fmt.Printf("Interrupted, please wait for cleanup...\n") fmt.Println("interrupted, please wait for cleanup...")
default: default:
} }
} }
fmt.Printf("processed %d blocks\n", n) fmt.Printf("processed %d blocks\n", n)
ethermintDB.Tracing = true imp.EthermintDB.Tracing = true
// try to create a new geth stateDB from root of the block 500 // try to create a new geth stateDB from root of the block 500
fmt.Printf("root500: %x\n", root500[:]) fmt.Printf("root at block 500: %x\n", root500[:])
state500, err := ethstate.New(root500, ethermintDB) state500, err := ethstate.New(root500, imp.EthermintDB)
if err != nil { if err != nil {
panic(err) panic(err)
} }
miner501BalanceAt500 := state500.GetBalance(miner501) miner501BalanceAt500 := state500.GetBalance(miner501)
state501, err := ethstate.New(root501, ethermintDB) state501, err := ethstate.New(root501, imp.EthermintDB)
if err != nil { if err != nil {
panic(err) panic(err)
} }

66
test/importer/log.go Normal file
View File

@ -0,0 +1,66 @@
package importer
import (
"fmt"
ethmath "github.com/ethereum/go-ethereum/common/math"
ethvm "github.com/ethereum/go-ethereum/core/vm"
)
// 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 uint64 `json:"gas"`
GasCost uint64 `json:"gasCost"`
Depth int `json:"depth"`
Error error `json:"error,omitempty"`
Stack *[]string `json:"stack,omitempty"`
Memory *[]string `json:"memory,omitempty"`
Storage *map[string]string `json:"storage,omitempty"`
}
// FormatLogs formats EVM returned structured logs for json output.
func FormatLogs(logs []ethvm.StructLog) []StructLogRes {
formatted := make([]StructLogRes, len(logs))
for index, trace := range logs {
formatted[index] = StructLogRes{
Pc: trace.Pc,
Op: trace.Op.String(),
Gas: trace.Gas,
GasCost: trace.GasCost,
Depth: trace.Depth,
Error: trace.Err,
}
if trace.Stack != nil {
stack := make([]string, len(trace.Stack))
for i, stackValue := range trace.Stack {
stack[i] = fmt.Sprintf("%x", ethmath.PaddedBigBytes(stackValue, 32))
}
formatted[index].Stack = &stack
}
if trace.Memory != nil {
memory := make([]string, 0, (len(trace.Memory)+31)/32)
for i := 0; i+32 <= len(trace.Memory); i += 32 {
memory = append(memory, fmt.Sprintf("%x", trace.Memory[i:i+32]))
}
formatted[index].Memory = &memory
}
if trace.Storage != nil {
storage := make(map[string]string)
for i, storageValue := range trace.Storage {
storage[fmt.Sprintf("%x", i)] = fmt.Sprintf("%x", storageValue)
}
formatted[index].Storage = &storage
}
}
return formatted
}

43
test/importer/utils.go Normal file
View File

@ -0,0 +1,43 @@
package importer
import (
"math/big"
"github.com/ethereum/go-ethereum/consensus/ethash"
ethstate "github.com/ethereum/go-ethereum/core/state"
ethtypes "github.com/ethereum/go-ethereum/core/types"
ethparams "github.com/ethereum/go-ethereum/params"
)
// Some weird constants to avoid constant memory allocs for them.
var (
big8 = big.NewInt(8)
big32 = big.NewInt(32)
)
// accumulateRewards credits the coinbase of the given block with the mining
// reward. The total reward consists of the static block reward and rewards for
// included uncles. The coinbase of each uncle block is also rewarded.
func accumulateRewards(config *ethparams.ChainConfig, state *ethstate.StateDB, header *ethtypes.Header, uncles []*ethtypes.Header) {
// select the correct block reward based on chain progression
blockReward := ethash.FrontierBlockReward
if config.IsByzantium(header.Number) {
blockReward = ethash.ByzantiumBlockReward
}
// accumulate the rewards for the miner and any included uncles
reward := new(big.Int).Set(blockReward)
r := new(big.Int)
for _, uncle := range uncles {
r.Add(uncle.Number, big8)
r.Sub(r, header.Number)
r.Mul(r, blockReward)
r.Div(r, big8)
state.AddBalance(uncle.Coinbase, r)
r.Div(blockReward, big32)
reward.Add(reward, r)
}
state.AddBalance(header.Coinbase, reward)
}

67
test/run.go Normal file
View File

@ -0,0 +1,67 @@
package main
import (
"flag"
"fmt"
"os"
"os/signal"
"path"
"runtime/pprof"
"syscall"
"github.com/cosmos/ethermint/state"
"github.com/cosmos/ethermint/test/importer"
dbm "github.com/tendermint/tendermint/libs/db"
)
var (
cpuprofile = flag.String("cpu-profile", "", "write cpu profile `file`")
blockchain = flag.String("blockchain", "data/blockchain", "file containing blocks to load")
datadir = flag.String("datadir", path.Join(os.Getenv("HOME"), ".ethermint"), "directory for ethermint data")
)
func main() {
flag.Parse()
if *cpuprofile != "" {
f, err := os.Create(*cpuprofile)
if err != nil {
fmt.Printf("could not create CPU profile: %v\n", err)
return
}
if err := pprof.StartCPUProfile(f); err != nil {
fmt.Printf("could not start CPU profile: %v\n", err)
return
}
defer pprof.StopCPUProfile()
}
sigs := make(chan os.Signal, 1)
interruptCh := make(chan bool, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigs
interruptCh <- true
}()
stateDB := dbm.NewDB("state", dbm.LevelDBBackend, *datadir)
codeDB := dbm.NewDB("code", dbm.LevelDBBackend, *datadir)
ethermintDB, err := state.NewDatabase(stateDB, codeDB)
if err != nil {
panic(fmt.Sprintf("failed to initialize geth Database: %v", err))
}
importer := importer.Importer{
EthermintDB: ethermintDB,
BlockchainFile: *blockchain,
Datadir: *datadir,
InterruptCh: interruptCh,
}
importer.Import()
}