2022-01-20 07:34:50 +00:00
|
|
|
package app
|
|
|
|
|
|
|
|
import (
|
|
|
|
"compress/gzip"
|
|
|
|
"context"
|
2022-02-22 13:47:23 +00:00
|
|
|
"errors"
|
2022-01-20 07:34:50 +00:00
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os"
|
|
|
|
"os/signal"
|
|
|
|
"strings"
|
|
|
|
"syscall"
|
|
|
|
|
|
|
|
"github.com/ledgerwatch/erigon-lib/kv"
|
2022-11-14 16:33:57 +00:00
|
|
|
"github.com/ledgerwatch/log/v3"
|
|
|
|
"github.com/urfave/cli/v2"
|
|
|
|
|
2022-01-20 07:34:50 +00:00
|
|
|
"github.com/ledgerwatch/erigon/cmd/utils"
|
|
|
|
"github.com/ledgerwatch/erigon/core"
|
|
|
|
"github.com/ledgerwatch/erigon/core/rawdb"
|
|
|
|
"github.com/ledgerwatch/erigon/core/types"
|
|
|
|
"github.com/ledgerwatch/erigon/eth"
|
|
|
|
"github.com/ledgerwatch/erigon/rlp"
|
2023-05-07 06:28:15 +00:00
|
|
|
"github.com/ledgerwatch/erigon/turbo/debug"
|
2022-01-20 07:34:50 +00:00
|
|
|
turboNode "github.com/ledgerwatch/erigon/turbo/node"
|
|
|
|
"github.com/ledgerwatch/erigon/turbo/stages"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
importBatchSize = 2500
|
|
|
|
)
|
|
|
|
|
|
|
|
var importCommand = cli.Command{
|
2022-11-15 20:38:31 +00:00
|
|
|
Action: MigrateFlags(importChain),
|
2022-01-20 07:34:50 +00:00
|
|
|
Name: "import",
|
|
|
|
Usage: "Import a blockchain file",
|
|
|
|
ArgsUsage: "<filename> (<filename 2> ... <filename N>) ",
|
|
|
|
Flags: []cli.Flag{
|
2022-11-14 16:33:57 +00:00
|
|
|
&utils.DataDirFlag,
|
|
|
|
&utils.ChainFlag,
|
2022-01-20 07:34:50 +00:00
|
|
|
},
|
2023-04-27 03:42:12 +00:00
|
|
|
//Category: "BLOCKCHAIN COMMANDS",
|
2022-01-20 07:34:50 +00:00
|
|
|
Description: `
|
|
|
|
The import command imports blocks from an RLP-encoded form. The form can be one file
|
|
|
|
with several RLP-encoded blocks, or several files can be used.
|
|
|
|
|
|
|
|
If only one file is used, import error will result in failure. If several files are used,
|
|
|
|
processing will proceed even if an individual RLP-file import failure occurs.`,
|
|
|
|
}
|
|
|
|
|
2023-05-07 06:28:15 +00:00
|
|
|
func importChain(cliCtx *cli.Context) error {
|
|
|
|
if cliCtx.NArg() < 1 {
|
2022-01-20 07:34:50 +00:00
|
|
|
utils.Fatalf("This command requires an argument.")
|
|
|
|
}
|
|
|
|
|
2023-05-07 06:28:15 +00:00
|
|
|
var logger log.Logger
|
|
|
|
var err error
|
|
|
|
if logger, err = debug.Setup(cliCtx, true /* rootLogger */); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
nodeCfg := turboNode.NewNodConfigUrfave(cliCtx)
|
|
|
|
ethCfg := turboNode.NewEthConfigUrfave(cliCtx, nodeCfg)
|
2022-01-20 07:34:50 +00:00
|
|
|
|
2023-05-07 06:28:15 +00:00
|
|
|
stack := makeConfigNode(nodeCfg, logger)
|
2022-01-20 07:34:50 +00:00
|
|
|
defer stack.Close()
|
|
|
|
|
2023-05-07 06:28:15 +00:00
|
|
|
ethereum, err := eth.New(stack, ethCfg, logger)
|
2022-01-20 07:34:50 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-05-09 17:11:31 +00:00
|
|
|
err = ethereum.Init(stack, ethCfg)
|
2023-01-17 06:20:31 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2022-01-20 07:34:50 +00:00
|
|
|
|
2023-05-07 06:28:15 +00:00
|
|
|
if err := ImportChain(ethereum, ethereum.ChainDB(), cliCtx.Args().First()); err != nil {
|
2022-01-20 07:34:50 +00:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func ImportChain(ethereum *eth.Ethereum, chainDB kv.RwDB, fn string) error {
|
|
|
|
// Watch for Ctrl-C while the import is running.
|
|
|
|
// If a signal is received, the import will stop at the next batch.
|
|
|
|
interrupt := make(chan os.Signal, 1)
|
|
|
|
stop := make(chan struct{})
|
|
|
|
signal.Notify(interrupt, syscall.SIGINT, syscall.SIGTERM)
|
|
|
|
defer signal.Stop(interrupt)
|
|
|
|
defer close(interrupt)
|
|
|
|
go func() {
|
|
|
|
if _, ok := <-interrupt; ok {
|
|
|
|
log.Info("Interrupted during import, stopping at next batch")
|
|
|
|
}
|
|
|
|
close(stop)
|
|
|
|
}()
|
|
|
|
checkInterrupt := func() bool {
|
|
|
|
select {
|
|
|
|
case <-stop:
|
|
|
|
return true
|
|
|
|
default:
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
log.Info("Importing blockchain", "file", fn)
|
|
|
|
|
|
|
|
// Open the file handle and potentially unwrap the gzip stream
|
|
|
|
fh, err := os.Open(fn)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
defer fh.Close()
|
|
|
|
|
|
|
|
var reader io.Reader = fh
|
|
|
|
if strings.HasSuffix(fn, ".gz") {
|
|
|
|
if reader, err = gzip.NewReader(reader); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
stream := rlp.NewStream(reader, 0)
|
|
|
|
|
|
|
|
// Run actual the import.
|
|
|
|
blocks := make(types.Blocks, importBatchSize)
|
|
|
|
n := 0
|
|
|
|
for batch := 0; ; batch++ {
|
|
|
|
// Load a batch of RLP blocks.
|
|
|
|
if checkInterrupt() {
|
|
|
|
return fmt.Errorf("interrupted")
|
|
|
|
}
|
|
|
|
i := 0
|
|
|
|
for ; i < importBatchSize; i++ {
|
|
|
|
var b types.Block
|
2022-02-22 13:47:23 +00:00
|
|
|
if err := stream.Decode(&b); errors.Is(err, io.EOF) {
|
2022-01-20 07:34:50 +00:00
|
|
|
break
|
|
|
|
} else if err != nil {
|
|
|
|
return fmt.Errorf("at block %d: %v", n, err)
|
|
|
|
}
|
|
|
|
// don't import first block
|
|
|
|
if b.NumberU64() == 0 {
|
|
|
|
i--
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
blocks[i] = &b
|
|
|
|
n++
|
|
|
|
}
|
|
|
|
if i == 0 {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
// Import the batch.
|
|
|
|
if checkInterrupt() {
|
|
|
|
return fmt.Errorf("interrupted")
|
|
|
|
}
|
|
|
|
|
|
|
|
missing := missingBlocks(chainDB, blocks[:i])
|
|
|
|
if len(missing) == 0 {
|
|
|
|
log.Info("Skipping batch as all blocks present", "batch", batch, "first", blocks[0].Hash(), "last", blocks[i-1].Hash())
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
// RLP decoding worked, try to insert into chain:
|
|
|
|
missingChain := &core.ChainPack{
|
|
|
|
Blocks: missing,
|
|
|
|
TopBlock: missing[len(missing)-1],
|
|
|
|
}
|
|
|
|
|
|
|
|
if err := InsertChain(ethereum, missingChain); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func ChainHasBlock(chainDB kv.RwDB, block *types.Block) bool {
|
|
|
|
var chainHasBlock bool
|
|
|
|
|
|
|
|
chainDB.View(context.Background(), func(tx kv.Tx) (err error) {
|
|
|
|
chainHasBlock = rawdb.HasBlock(tx, block.Hash(), block.NumberU64())
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
return chainHasBlock
|
|
|
|
}
|
|
|
|
|
|
|
|
func missingBlocks(chainDB kv.RwDB, blocks []*types.Block) []*types.Block {
|
|
|
|
var headBlock *types.Block
|
|
|
|
chainDB.View(context.Background(), func(tx kv.Tx) (err error) {
|
|
|
|
hash := rawdb.ReadHeadHeaderHash(tx)
|
|
|
|
number := rawdb.ReadHeaderNumber(tx, hash)
|
|
|
|
headBlock = rawdb.ReadBlock(tx, hash, *number)
|
|
|
|
return nil
|
|
|
|
})
|
|
|
|
|
|
|
|
for i, block := range blocks {
|
|
|
|
// If we're behind the chain head, only check block, state is available at head
|
|
|
|
if headBlock.NumberU64() > block.NumberU64() {
|
|
|
|
if !ChainHasBlock(chainDB, block) {
|
|
|
|
return blocks[i:]
|
|
|
|
}
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
if !ChainHasBlock(chainDB, block) {
|
|
|
|
return blocks[i:]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func InsertChain(ethereum *eth.Ethereum, chain *core.ChainPack) error {
|
|
|
|
sentryControlServer := ethereum.SentryControlServer()
|
|
|
|
initialCycle := false
|
|
|
|
|
|
|
|
for _, b := range chain.Blocks {
|
|
|
|
sentryControlServer.Hd.AddMinedHeader(b.Header())
|
2023-01-06 12:43:46 +00:00
|
|
|
sentryControlServer.Bd.AddToPrefetch(b.Header(), b.RawBody())
|
2022-01-20 07:34:50 +00:00
|
|
|
}
|
|
|
|
|
2022-05-06 14:15:01 +00:00
|
|
|
sentryControlServer.Hd.MarkAllVerified()
|
2022-01-20 07:34:50 +00:00
|
|
|
|
2022-12-21 07:39:19 +00:00
|
|
|
_, err := stages.StageLoopStep(ethereum.SentryCtx(), ethereum.ChainConfig(), ethereum.ChainDB(), ethereum.StagedSync(), ethereum.Notifications(), initialCycle, sentryControlServer.UpdateHead)
|
2022-01-20 07:34:50 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|