From fb08514b9b30828d969d75bb4fe70b1cc60d4088 Mon Sep 17 00:00:00 2001
From: Igor Mandrigin <mandrigin@users.noreply.github.com>
Date: Sun, 12 Apr 2020 18:41:06 +0300
Subject: [PATCH] Use `geth export` generated files in the stateless prototype
 (#447)

---
 cmd/state/commands/stateless.go               |   5 +-
 cmd/state/stateless/stateless.go              |  39 +++--
 .../stateless/stateless_block_providers.go    | 134 ++++++++++++++++++
 3 files changed, 162 insertions(+), 16 deletions(-)
 create mode 100644 cmd/state/stateless/stateless_block_providers.go

diff --git a/cmd/state/commands/stateless.go b/cmd/state/commands/stateless.go
index 5b9d73a1cb..55b8feb53f 100644
--- a/cmd/state/commands/stateless.go
+++ b/cmd/state/commands/stateless.go
@@ -20,13 +20,14 @@ var (
 	statelessResolver bool
 	witnessDatabase   string
 	writeHistory      bool
+	blockSource       string
 )
 
 func init() {
-	withChaindata(statelessCmd)
 	withStatsfile(statelessCmd)
 	withBlock(statelessCmd)
 
+	statelessCmd.Flags().StringVar(&blockSource, "blockSource", "", "Path to the block source: `db:///path/to/chaindata` or `exportfile:///path/to/my/exportfile`")
 	statelessCmd.Flags().StringVar(&statefile, "statefile", "state", "path to the file where the state will be periodically written during the analysis")
 	statelessCmd.Flags().Uint32Var(&triesize, "triesize", 4*1024*1024, "maximum size of a trie in bytes")
 	statelessCmd.Flags().BoolVar(&preroot, "preroot", false, "Attempt to compute hash of the trie without modifying it")
@@ -61,7 +62,7 @@ var statelessCmd = &cobra.Command{
 		stateless.Stateless(
 			ctx,
 			block,
-			chaindata,
+			blockSource,
 			statefile,
 			triesize,
 			preroot,
diff --git a/cmd/state/stateless/stateless.go b/cmd/state/stateless/stateless.go
index 82315938ee..bb6260312f 100644
--- a/cmd/state/stateless/stateless.go
+++ b/cmd/state/stateless/stateless.go
@@ -133,7 +133,7 @@ type CreateDbFunc func(string) (ethdb.Database, error)
 func Stateless(
 	ctx context.Context,
 	blockNum uint64,
-	chaindata string,
+	blockSourceURI string,
 	statefile string,
 	triesize uint32,
 	tryPreRoot bool,
@@ -168,19 +168,14 @@ func Stateless(
 	defer timeF.Close()
 	fmt.Fprintf(timeF, "blockNr,exec,resolve,stateless_exec,calc_root,mod_root\n")
 
-	ethDb, err := createDb(chaindata)
-	check(err)
-	defer ethDb.Close()
-	chainConfig := params.MainnetChainConfig
-
 	stats, err := NewStatsFile(statsfile)
 	check(err)
 	defer stats.Close()
 
-	vmConfig := vm.Config{}
-	engine := ethash.NewFullFaker()
-	bcb, err := core.NewBlockChain(ethDb, nil, chainConfig, engine, vm.Config{}, nil)
+	blockProvider, err := BlockProviderForURI(blockSourceURI, createDb)
 	check(err)
+	defer blockProvider.Close()
+
 	stateDb, err := createDb(statefile)
 	check(err)
 	defer stateDb.Close()
@@ -196,8 +191,16 @@ func Stateless(
 		genesisBlock, _, _, err1 := core.DefaultGenesisBlock().ToBlock(nil, writeHistory)
 		check(err1)
 		preRoot = genesisBlock.Header().Root
-	} else {
-		block := bcb.GetBlockByNumber(blockNum - 1)
+	}
+
+	chainConfig := params.MainnetChainConfig
+	vmConfig := vm.Config{}
+	engine := ethash.NewFullFaker()
+	bcb2, err := core.NewBlockChain(stateDb, nil, chainConfig, engine, vm.Config{}, nil)
+	check(err)
+
+	if blockNum > 1 {
+		block := bcb2.GetBlockByNumber(blockNum - 1)
 		fmt.Printf("Block number: %d\n", blockNum-1)
 		fmt.Printf("Block root hash: %x\n", block.Root())
 		preRoot = block.Root()
@@ -260,6 +263,9 @@ func Stateless(
 
 	}
 
+	err = blockProvider.FastFwd(blockNum)
+	check(err)
+
 	for !interrupt {
 		select {
 		case <-ctx.Done():
@@ -269,10 +275,14 @@ func Stateless(
 
 		trace := blockNum == 50492 // false // blockNum == 545080
 		tds.SetResolveReads(blockNum >= witnessThreshold)
-		block := bcb.GetBlockByNumber(blockNum)
+		block, err := blockProvider.NextBlock()
+		check(err)
 		if block == nil {
 			break
 		}
+		if block.NumberU64() != blockNum {
+			check(fmt.Errorf("block number mismatch (want=%v got=%v)", blockNum, block.NumberU64()))
+		}
 		execStart := time.Now()
 		statedb := state.New(tds)
 		gp := new(core.GasPool).AddGas(block.GasLimit())
@@ -285,7 +295,8 @@ func Stateless(
 		}
 		for i, tx := range block.Transactions() {
 			statedb.Prepare(tx.Hash(), block.Hash(), i)
-			receipt, err := core.ApplyTransaction(chainConfig, bcb, nil, gp, statedb, tds.TrieStateWriter(), header, tx, usedGas, vmConfig)
+			var receipt *types.Receipt
+			receipt, err = core.ApplyTransaction(chainConfig, bcb2, nil, gp, statedb, tds.TrieStateWriter(), header, tx, usedGas, vmConfig)
 			if err != nil {
 				fmt.Printf("tx %x failed: %v\n", tx.Hash(), err)
 				return
@@ -382,7 +393,7 @@ func Stateless(
 			ibs := state.New(s)
 			ibs.SetTrace(trace)
 			s.SetBlockNr(blockNum)
-			if err = runBlock(ibs, s, s, chainConfig, bcb, block); err != nil {
+			if err = runBlock(ibs, s, s, chainConfig, bcb2, block); err != nil {
 				fmt.Printf("Error running block %d through stateless2: %v\n", blockNum, err)
 				finalRootFail = true
 			} else if !binary {
diff --git a/cmd/state/stateless/stateless_block_providers.go b/cmd/state/stateless/stateless_block_providers.go
new file mode 100644
index 0000000000..6d60f37803
--- /dev/null
+++ b/cmd/state/stateless/stateless_block_providers.go
@@ -0,0 +1,134 @@
+package stateless
+
+import (
+	"compress/gzip"
+	"fmt"
+	"io"
+	"net/url"
+	"os"
+	"strings"
+
+	"github.com/ledgerwatch/turbo-geth/consensus/ethash"
+	"github.com/ledgerwatch/turbo-geth/core"
+	"github.com/ledgerwatch/turbo-geth/core/types"
+	"github.com/ledgerwatch/turbo-geth/core/vm"
+	"github.com/ledgerwatch/turbo-geth/ethdb"
+	"github.com/ledgerwatch/turbo-geth/params"
+	"github.com/ledgerwatch/turbo-geth/rlp"
+)
+
+const (
+	fileSchemeExportfile = "exportfile"
+	fileSchemeDb         = "db"
+)
+
+type BlockProvider interface {
+	io.Closer
+	FastFwd(uint64) error
+	NextBlock() (*types.Block, error)
+}
+
+func BlockProviderForURI(uri string, createDbFunc CreateDbFunc) (BlockProvider, error) {
+	url, err := url.Parse(uri)
+	if err != nil {
+		return nil, err
+	}
+	switch url.Scheme {
+	case fileSchemeExportfile:
+		fmt.Println("Source of blocks: export file @", url.Path)
+		return NewBlockProviderFromExportFile(url.Path)
+	case fileSchemeDb:
+		fallthrough
+	default:
+		fmt.Println("Source of blocks: db @", url.Path)
+		return NewBlockProviderFromDb(url.Path, createDbFunc)
+	}
+}
+
+type BlockChainBlockProvider struct {
+	currentBlock uint64
+	bc           *core.BlockChain
+	db           ethdb.Database
+}
+
+func NewBlockProviderFromDb(path string, createDbFunc CreateDbFunc) (BlockProvider, error) {
+	ethDb, err := createDbFunc(path)
+	if err != nil {
+		return nil, err
+	}
+	chainConfig := params.MainnetChainConfig
+	engine := ethash.NewFullFaker()
+	chain, err := core.NewBlockChain(ethDb, nil, chainConfig, engine, vm.Config{}, nil)
+	if err != nil {
+		return nil, err
+	}
+
+	return &BlockChainBlockProvider{
+		bc: chain,
+	}, nil
+}
+
+func (p *BlockChainBlockProvider) Close() error {
+	p.db.Close()
+	return nil
+}
+
+func (p *BlockChainBlockProvider) FastFwd(to uint64) error {
+	p.currentBlock = to
+	return nil
+}
+
+func (p *BlockChainBlockProvider) NextBlock() (*types.Block, error) {
+	block := p.bc.GetBlockByNumber(p.currentBlock)
+	p.currentBlock++
+	return block, nil
+}
+
+type ExportFileBlockProvider struct {
+	stream *rlp.Stream
+	fh     io.Closer
+}
+
+func NewBlockProviderFromExportFile(fn string) (BlockProvider, error) {
+	// Open the file handle and potentially unwrap the gzip stream
+	fh, err := os.Open(fn)
+	if err != nil {
+		return nil, err
+	}
+
+	var reader io.Reader = fh
+	if strings.HasSuffix(fn, ".gz") {
+		if reader, err = gzip.NewReader(reader); err != nil {
+			return nil, err
+		}
+	}
+	stream := rlp.NewStream(reader, 0)
+	return &ExportFileBlockProvider{stream, fh}, nil
+}
+
+func (p *ExportFileBlockProvider) Close() error {
+	return p.fh.Close()
+}
+
+func (p *ExportFileBlockProvider) FastFwd(to uint64) error {
+	var b types.Block
+	for {
+		if err := p.stream.Decode(&b); err == io.EOF {
+			return nil
+		} else if err != nil {
+			return fmt.Errorf("error fast fwd: %v", err)
+		} else if b.NumberU64() >= to-1 {
+			return nil
+		}
+	}
+}
+
+func (p *ExportFileBlockProvider) NextBlock() (*types.Block, error) {
+	var b types.Block
+	if err := p.stream.Decode(&b); err == io.EOF {
+		return nil, nil
+	} else if err != nil {
+		return nil, fmt.Errorf("error fast fwd: %v", err)
+	}
+	return &b, nil
+}
-- 
GitLab