package importer import ( "bytes" "encoding/binary" "encoding/json" "fmt" "io" "os" "path" "time" "github.com/cosmos/ethermint/core" "github.com/cosmos/ethermint/state" ethcommon "github.com/ethereum/go-ethereum/common" ethmisc "github.com/ethereum/go-ethereum/consensus/misc" ethcore "github.com/ethereum/go-ethereum/core" ethstate "github.com/ethereum/go-ethereum/core/state" ethtypes "github.com/ethereum/go-ethereum/core/types" ethvm "github.com/ethereum/go-ethereum/core/vm" ethparams "github.com/ethereum/go-ethereum/params" ethrlp "github.com/ethereum/go-ethereum/rlp" ) var ( miner501 = ethcommon.HexToAddress("0x35e8e5dC5FBd97c5b421A80B596C030a2Be2A04D") genInvestor = ethcommon.HexToAddress("0x756F45E3FA69347A9A973A725E3C98bC4db0b5a0") ) // Importer implements a structure to facilitate testing of importing mainnet // Ethereum blocks and transactions using the Cosmos SDK components and the // EVM. type Importer struct { EthermintDB *state.Database BlockchainFile string Datadir string InterruptCh <-chan bool } // Import performs an import given an Importer that has a Geth stateDB // implementation and a blockchain exported file. // nolint func (imp *Importer) Import() { // only create genesis if it is a brand new database if imp.EthermintDB.LatestVersion() == 0 { // start with empty root hash (i.e. empty state) gethStateDB, err := ethstate.New(ethcommon.Hash{}, imp.EthermintDB) if err != nil { panic(fmt.Sprintf("failed to instantiate geth state.StateDB: %v", err)) } genBlock := ethcore.DefaultGenesisBlock() for addr, account := range genBlock.Alloc { gethStateDB.AddBalance(addr, account.Balance) gethStateDB.SetCode(addr, account.Code) gethStateDB.SetNonce(addr, account.Nonce) for key, value := range account.Storage { gethStateDB.SetState(addr, key, value) } } // get balance of one of the genesis account having 200 ETH b := gethStateDB.GetBalance(genInvestor) fmt.Printf("balance of %s: %s\n", genInvestor.String(), b) // commit the geth stateDB with 'false' to delete empty objects genRoot, err := gethStateDB.Commit(false) if err != nil { panic(err) } commitID := imp.EthermintDB.Commit() fmt.Printf("commitID after genesis: %v\n", commitID) fmt.Printf("genesis state root hash: %x\n", genRoot[:]) } // file with blockchain data exported from geth by using "geth exportdb" // command. input, err := os.Open(imp.BlockchainFile) if err != nil { panic(err) } defer input.Close() // ethereum mainnet config chainConfig := ethparams.MainnetChainConfig // create RLP stream for exported blocks stream := ethrlp.NewStream(input, 0) var ( block ethtypes.Block root500 ethcommon.Hash // root hash after block 500 root501 ethcommon.Hash // root hash after block 501 ) var prevRoot ethcommon.Hash binary.BigEndian.PutUint64(prevRoot[:8], uint64(imp.EthermintDB.LatestVersion())) imp.EthermintDB.Tracing = true chainContext := core.NewChainContext() vmConfig := ethvm.Config{} n := 0 startTime := time.Now() interrupt := false var lastSkipped uint64 for !interrupt { if err = stream.Decode(&block); err == io.EOF { err = nil break } else if err != nil { panic(fmt.Errorf("failed to decode at block %d: %s", block.NumberU64(), err)) } // don't import blocks already imported if block.NumberU64() < uint64(imp.EthermintDB.LatestVersion()) { lastSkipped = block.NumberU64() continue } if lastSkipped > 0 { fmt.Printf("skipped blocks up to %d\n", lastSkipped) lastSkipped = 0 } header := block.Header() chainContext.Coinbase = header.Coinbase chainContext.SetHeader(block.NumberU64(), header) gethStateDB, err := ethstate.New(prevRoot, imp.EthermintDB) if err != nil { panic(fmt.Errorf("failed to instantiate geth state.StateDB at block %d: %v", block.NumberU64(), err)) } var ( receipts ethtypes.Receipts usedGas = new(uint64) allLogs []*ethtypes.Log gp = new(ethcore.GasPool).AddGas(block.GasLimit()) ) if chainConfig.DAOForkSupport && chainConfig.DAOForkBlock != nil && chainConfig.DAOForkBlock.Cmp(block.Number()) == 0 { ethmisc.ApplyDAOHardFork(gethStateDB) } for i, tx := range block.Transactions() { gethStateDB.Prepare(tx.Hash(), block.Hash(), i) txHash := tx.Hash() if bytes.Equal(txHash[:], ethcommon.FromHex("0xc438cfcc3b74a28741bda361032f1c6362c34aa0e1cedff693f31ec7d6a12717")) { vmConfig.Tracer = ethvm.NewStructLogger(ðvm.LogConfig{}) vmConfig.Debug = true } receipt, _, err := ethcore.ApplyTransaction(chainConfig, chainContext, nil, gp, gethStateDB, header, tx, usedGas, vmConfig) if vmConfig.Tracer != nil { w, err := os.Create(path.Join(imp.Datadir, "structlogs.txt")) if err != nil { panic(fmt.Sprintf("failed to create file for VM tracing: %v", err)) } encoder := json.NewEncoder(w) logs := FormatLogs(vmConfig.Tracer.(*ethvm.StructLogger).StructLogs()) if err := encoder.Encode(logs); err != nil { panic(err) } if err := w.Close(); err != nil { panic(err) } vmConfig.Debug = false vmConfig.Tracer = nil } if err != nil { panic(fmt.Errorf("at block %d, tx %x: %v", block.NumberU64(), tx.Hash(), err)) } receipts = append(receipts, receipt) allLogs = append(allLogs, receipt.Logs...) } // apply mining rewards to the geth stateDB accumulateRewards(chainConfig, gethStateDB, header, block.Uncles()) // commit block in geth prevRoot, err = gethStateDB.Commit(chainConfig.IsEIP158(block.Number())) if err != nil { panic(fmt.Errorf("at block %d: %v", block.NumberU64(), err)) } // commit block in Ethermint imp.EthermintDB.Commit() switch block.NumberU64() { case 500: root500 = prevRoot case 501: root501 = prevRoot } n++ if (n % 10000) == 0 { fmt.Printf("processed %d blocks, time so far: %v\n", n, time.Since(startTime)) } // Check for interrupts select { case interrupt = <-imp.InterruptCh: fmt.Println("interrupted, please wait for cleanup...") default: } } fmt.Printf("processed %d blocks\n", n) imp.EthermintDB.Tracing = true // try to create a new geth stateDB from root of the block 500 fmt.Printf("root at block 500: %x\n", root500[:]) state500, err := ethstate.New(root500, imp.EthermintDB) if err != nil { panic(err) } miner501BalanceAt500 := state500.GetBalance(miner501) state501, err := ethstate.New(root501, imp.EthermintDB) if err != nil { panic(err) } miner501BalanceAt501 := state501.GetBalance(miner501) fmt.Printf("investor's balance after block 500: %d\n", state500.GetBalance(genInvestor)) fmt.Printf("miner of block 501's balance after block 500: %d\n", miner501BalanceAt500) fmt.Printf("miner of block 501's balance after block 501: %d\n", miner501BalanceAt501) }