Skip to content

Commit 495bdb0

Browse files
committed
cmd: export preimages in RLP, support GZIP, uniform with block export
1 parent b6b6f52 commit 495bdb0

File tree

4 files changed

+156
-121
lines changed

4 files changed

+156
-121
lines changed

cmd/geth/chaincmd.go

Lines changed: 61 additions & 114 deletions
Original file line numberDiff line numberDiff line change
@@ -40,11 +40,6 @@ import (
4040
"gopkg.in/urfave/cli.v1"
4141
)
4242

43-
var (
44-
// secureKeyPrefix is the database key prefix used to store trie node preimages.
45-
secureKeyPrefix = []byte("secure-key-")
46-
)
47-
4843
var (
4944
initCommand = cli.Command{
5045
Action: utils.MigrateFlags(initGenesis),
@@ -100,6 +95,34 @@ Requires a first argument of the file to write to.
10095
Optional second and third arguments control the first and
10196
last block to write. In this mode, the file will be appended
10297
if already existing.`,
98+
}
99+
importPreimagesCommand = cli.Command{
100+
Action: utils.MigrateFlags(importPreimages),
101+
Name: "import-preimages",
102+
Usage: "Import the preimage database from an RLP stream",
103+
ArgsUsage: "<datafile>",
104+
Flags: []cli.Flag{
105+
utils.DataDirFlag,
106+
utils.CacheFlag,
107+
utils.LightModeFlag,
108+
},
109+
Category: "BLOCKCHAIN COMMANDS",
110+
Description: `
111+
The import-preimages command imports hash preimages from an RLP encoded stream.`,
112+
}
113+
exportPreimagesCommand = cli.Command{
114+
Action: utils.MigrateFlags(exportPreimages),
115+
Name: "export-preimages",
116+
Usage: "Export the preimage database into an RLP stream",
117+
ArgsUsage: "<dumpfile>",
118+
Flags: []cli.Flag{
119+
utils.DataDirFlag,
120+
utils.CacheFlag,
121+
utils.LightModeFlag,
122+
},
123+
Category: "BLOCKCHAIN COMMANDS",
124+
Description: `
125+
The export-preimages command export hash preimages to an RLP encoded stream`,
103126
}
104127
copydbCommand = cli.Command{
105128
Action: utils.MigrateFlags(copyDb),
@@ -146,34 +169,6 @@ Remove blockchain and state databases`,
146169
The arguments are interpreted as block numbers or hashes.
147170
Use "ethereum dump 0" to dump the genesis block.`,
148171
}
149-
preimageDumpCommand = cli.Command{
150-
Action: utils.MigrateFlags(dumpPreimage),
151-
Name: "preimagedump",
152-
Usage: "Dump the preimage database in json format",
153-
ArgsUsage: "<dumpfile>",
154-
Flags: []cli.Flag{
155-
utils.DataDirFlag,
156-
utils.CacheFlag,
157-
utils.LightModeFlag,
158-
},
159-
Category: "BLOCKCHAIN COMMANDS",
160-
Description: `
161-
Dump the preimage database in json format`,
162-
}
163-
preimageImportCommand = cli.Command{
164-
Action: utils.MigrateFlags(importPreimage),
165-
Name: "preimageimport",
166-
Usage: "Import the preimage data from the specified file",
167-
ArgsUsage: "<datafile>",
168-
Flags: []cli.Flag{
169-
utils.DataDirFlag,
170-
utils.CacheFlag,
171-
utils.LightModeFlag,
172-
},
173-
Category: "BLOCKCHAIN COMMANDS",
174-
Description: `
175-
Import the preimage data from the specified file`,
176-
}
177172
)
178173

179174
// initGenesis will initialise the given JSON format genesis file and writes it as
@@ -332,7 +327,39 @@ func exportChain(ctx *cli.Context) error {
332327
if err != nil {
333328
utils.Fatalf("Export error: %v\n", err)
334329
}
335-
fmt.Printf("Export done in %v", time.Since(start))
330+
fmt.Printf("Export done in %v\n", time.Since(start))
331+
return nil
332+
}
333+
334+
// importPreimages imports preimage data from the specified file.
335+
func importPreimages(ctx *cli.Context) error {
336+
if len(ctx.Args()) < 1 {
337+
utils.Fatalf("This command requires an argument.")
338+
}
339+
stack := makeFullNode(ctx)
340+
diskdb := utils.MakeChainDatabase(ctx, stack).(*ethdb.LDBDatabase)
341+
342+
start := time.Now()
343+
if err := utils.ImportPreimages(diskdb, ctx.Args().First()); err != nil {
344+
utils.Fatalf("Export error: %v\n", err)
345+
}
346+
fmt.Printf("Export done in %v\n", time.Since(start))
347+
return nil
348+
}
349+
350+
// exportPreimages dumps the preimage data to specified json file in streaming way.
351+
func exportPreimages(ctx *cli.Context) error {
352+
if len(ctx.Args()) < 1 {
353+
utils.Fatalf("This command requires an argument.")
354+
}
355+
stack := makeFullNode(ctx)
356+
diskdb := utils.MakeChainDatabase(ctx, stack).(*ethdb.LDBDatabase)
357+
358+
start := time.Now()
359+
if err := utils.ExportPreimages(diskdb, ctx.Args().First()); err != nil {
360+
utils.Fatalf("Export error: %v\n", err)
361+
}
362+
fmt.Printf("Export done in %v\n", time.Since(start))
336363
return nil
337364
}
338365

@@ -439,86 +466,6 @@ func dump(ctx *cli.Context) error {
439466
return nil
440467
}
441468

442-
// PreimageEntry represents a map between preimage and hash.
443-
type PreimageEntry struct {
444-
Hash string `json:"hash"`
445-
Preimage string `json:"preimage"`
446-
}
447-
448-
// dumpPreimage dumps the preimage data to specified json file in streaming way.
449-
func dumpPreimage(ctx *cli.Context) error {
450-
// Make sure the export json file has been specified.
451-
if len(ctx.Args()) < 1 {
452-
utils.Fatalf("This command requires an argument.")
453-
}
454-
455-
// Encode preimage data to json file in streaming way.
456-
file, err := os.Create(ctx.Args().First())
457-
if err != nil {
458-
return err
459-
}
460-
encoder := json.NewEncoder(file)
461-
462-
stack := makeFullNode(ctx)
463-
db := utils.MakeChainDatabase(ctx, stack)
464-
465-
// Dump all preimage entries.
466-
it := db.(*ethdb.LDBDatabase).NewIteratorByPrefix(secureKeyPrefix)
467-
for it.Next() {
468-
hash := it.Key()[len(secureKeyPrefix):]
469-
if err := encoder.Encode(PreimageEntry{common.Bytes2Hex(hash), common.Bytes2Hex(it.Value())}); err != nil {
470-
return err
471-
}
472-
}
473-
return nil
474-
}
475-
476-
// importPreimages imports preimage data from the specified file.
477-
func importPreimage(ctx *cli.Context) error {
478-
// Make sure the export json file has been specified.
479-
if len(ctx.Args()) < 1 {
480-
utils.Fatalf("This command requires an argument.")
481-
}
482-
483-
// Decode the preimage data in streaming way.
484-
file, err := os.Open(ctx.Args().First())
485-
if err != nil {
486-
return err
487-
}
488-
decoder := json.NewDecoder(file)
489-
490-
stack := makeFullNode(ctx)
491-
db := utils.MakeChainDatabase(ctx, stack)
492-
493-
var (
494-
entry PreimageEntry
495-
preimages = make(map[common.Hash][]byte)
496-
)
497-
498-
for decoder.More() {
499-
if err := decoder.Decode(&entry); err != nil {
500-
return err
501-
}
502-
preimages[common.HexToHash(entry.Hash)] = common.Hex2Bytes(entry.Preimage)
503-
// Flush to database in batch
504-
if len(preimages) > 1024 {
505-
err := core.WritePreimages(db, 0, preimages)
506-
if err != nil {
507-
return err
508-
}
509-
preimages = make(map[common.Hash][]byte)
510-
}
511-
}
512-
// Flush the last batch preimage data
513-
if len(preimages) > 0 {
514-
err := core.WritePreimages(db, 0, preimages)
515-
if err != nil {
516-
return err
517-
}
518-
}
519-
return nil
520-
}
521-
522469
// hashish returns true for strings that look like hashes.
523470
func hashish(x string) bool {
524471
_, err := strconv.Atoi(x)

cmd/geth/main.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,11 +155,11 @@ func init() {
155155
initCommand,
156156
importCommand,
157157
exportCommand,
158+
importPreimagesCommand,
159+
exportPreimagesCommand,
158160
copydbCommand,
159161
removedbCommand,
160162
dumpCommand,
161-
preimageDumpCommand,
162-
preimageImportCommand,
163163
// See monitorcmd.go:
164164
monitorCommand,
165165
// See accountcmd.go:

cmd/utils/cmd.go

Lines changed: 91 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,11 @@ import (
2727
"strings"
2828
"syscall"
2929

30+
"github.com/ethereum/go-ethereum/common"
3031
"github.com/ethereum/go-ethereum/core"
3132
"github.com/ethereum/go-ethereum/core/types"
33+
"github.com/ethereum/go-ethereum/crypto"
34+
"github.com/ethereum/go-ethereum/ethdb"
3235
"github.com/ethereum/go-ethereum/internal/debug"
3336
"github.com/ethereum/go-ethereum/log"
3437
"github.com/ethereum/go-ethereum/node"
@@ -105,6 +108,8 @@ func ImportChain(chain *core.BlockChain, fn string) error {
105108
}
106109

107110
log.Info("Importing blockchain", "file", fn)
111+
112+
// Open the file handle and potentially unwrap the gzip stream
108113
fh, err := os.Open(fn)
109114
if err != nil {
110115
return err
@@ -180,8 +185,12 @@ func missingBlocks(chain *core.BlockChain, blocks []*types.Block) []*types.Block
180185
return nil
181186
}
182187

188+
// ExportChain exports a blockchain into the specified file, truncating any data
189+
// already present in the file.
183190
func ExportChain(blockchain *core.BlockChain, fn string) error {
184191
log.Info("Exporting blockchain", "file", fn)
192+
193+
// Open the file handle and potentially wrap with a gzip stream
185194
fh, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm)
186195
if err != nil {
187196
return err
@@ -193,7 +202,7 @@ func ExportChain(blockchain *core.BlockChain, fn string) error {
193202
writer = gzip.NewWriter(writer)
194203
defer writer.(*gzip.Writer).Close()
195204
}
196-
205+
// Iterate over the blocks and export them
197206
if err := blockchain.Export(writer); err != nil {
198207
return err
199208
}
@@ -202,9 +211,12 @@ func ExportChain(blockchain *core.BlockChain, fn string) error {
202211
return nil
203212
}
204213

214+
// ExportAppendChain exports a blockchain into the specified file, appending to
215+
// the file if data already exists in it.
205216
func ExportAppendChain(blockchain *core.BlockChain, fn string, first uint64, last uint64) error {
206217
log.Info("Exporting blockchain", "file", fn)
207-
// TODO verify mode perms
218+
219+
// Open the file handle and potentially wrap with a gzip stream
208220
fh, err := os.OpenFile(fn, os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModePerm)
209221
if err != nil {
210222
return err
@@ -216,10 +228,86 @@ func ExportAppendChain(blockchain *core.BlockChain, fn string, first uint64, las
216228
writer = gzip.NewWriter(writer)
217229
defer writer.(*gzip.Writer).Close()
218230
}
219-
231+
// Iterate over the blocks and export them
220232
if err := blockchain.ExportN(writer, first, last); err != nil {
221233
return err
222234
}
223235
log.Info("Exported blockchain to", "file", fn)
224236
return nil
225237
}
238+
239+
// ImportPreimages imports a batch of exported hash preimages into the database.
240+
func ImportPreimages(db *ethdb.LDBDatabase, fn string) error {
241+
log.Info("Importing preimages", "file", fn)
242+
243+
// Open the file handle and potentially unwrap the gzip stream
244+
fh, err := os.Open(fn)
245+
if err != nil {
246+
return err
247+
}
248+
defer fh.Close()
249+
250+
var reader io.Reader = fh
251+
if strings.HasSuffix(fn, ".gz") {
252+
if reader, err = gzip.NewReader(reader); err != nil {
253+
return err
254+
}
255+
}
256+
stream := rlp.NewStream(reader, 0)
257+
258+
// Import the preimages in batches to prevent disk trashing
259+
preimages := make(map[common.Hash][]byte)
260+
261+
for {
262+
// Read the next entry and ensure it's not junk
263+
var blob []byte
264+
265+
if err := stream.Decode(&blob); err != nil {
266+
if err == io.EOF {
267+
break
268+
}
269+
return err
270+
}
271+
// Accumulate the preimages and flush when enough ws gathered
272+
preimages[crypto.Keccak256Hash(blob)] = common.CopyBytes(blob)
273+
if len(preimages) > 1024 {
274+
if err := core.WritePreimages(db, 0, preimages); err != nil {
275+
return err
276+
}
277+
preimages = make(map[common.Hash][]byte)
278+
}
279+
}
280+
// Flush the last batch preimage data
281+
if len(preimages) > 0 {
282+
return core.WritePreimages(db, 0, preimages)
283+
}
284+
return nil
285+
}
286+
287+
// ExportPreimages exports all known hash preimages into the specified file,
288+
// truncating any data already present in the file.
289+
func ExportPreimages(db *ethdb.LDBDatabase, fn string) error {
290+
log.Info("Exporting preimages", "file", fn)
291+
292+
// Open the file handle and potentially wrap with a gzip stream
293+
fh, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, os.ModePerm)
294+
if err != nil {
295+
return err
296+
}
297+
defer fh.Close()
298+
299+
var writer io.Writer = fh
300+
if strings.HasSuffix(fn, ".gz") {
301+
writer = gzip.NewWriter(writer)
302+
defer writer.(*gzip.Writer).Close()
303+
}
304+
// Iterate over the preimages and export them
305+
it := db.NewIteratorWithPrefix([]byte("secure-key-"))
306+
for it.Next() {
307+
if err := rlp.Encode(writer, it.Value()); err != nil {
308+
return err
309+
}
310+
}
311+
log.Info("Exported preimages", "file", fn)
312+
return nil
313+
}

ethdb/database.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -122,8 +122,8 @@ func (db *LDBDatabase) NewIterator() iterator.Iterator {
122122
return db.db.NewIterator(nil, nil)
123123
}
124124

125-
// NewIteratorByPrefix returns a iterator to iterate over subset of database content with a particular prefix.
126-
func (db *LDBDatabase) NewIteratorByPrefix(prefix []byte) iterator.Iterator {
125+
// NewIteratorWithPrefix returns a iterator to iterate over subset of database content with a particular prefix.
126+
func (db *LDBDatabase) NewIteratorWithPrefix(prefix []byte) iterator.Iterator {
127127
return db.db.NewIterator(util.BytesPrefix(prefix), nil)
128128
}
129129

0 commit comments

Comments
 (0)