mirror of
https://github.com/luxfi/evm.git
synced 2026-09-24 00:27:44 +00:00
- Update vm dependency to v1.0.18 - Remove internal log package in favor of luxfi/log - Migrate prometheus to gatherer package
223 lines
5.5 KiB
Go
223 lines
5.5 KiB
Go
// Copyright (C) 2019-2025, Lux Industries, Inc. All rights reserved.
|
|
// See the file LICENSE for licensing terms.
|
|
|
|
// export reads blocks from an EVM pebbledb and exports to RLP
|
|
package main
|
|
|
|
import (
|
|
"encoding/binary"
|
|
"encoding/hex"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
|
|
"github.com/luxfi/database"
|
|
"github.com/luxfi/database/manager"
|
|
"github.com/luxfi/geth/core/types"
|
|
"github.com/luxfi/geth/rlp"
|
|
)
|
|
|
|
// EVM namespace for Zoo mainnet
|
|
var zooNamespace, _ = hex.DecodeString("337fb73f9bcdac8c31a2d5f7b877ab1e8a2b7f2a1e9bf02a0a0e6c6fd164f1d1")
|
|
|
|
func main() {
|
|
dbPath := flag.String("db", "", "Path to pebbledb")
|
|
output := flag.String("output", "blocks.rlp", "Output RLP file")
|
|
flag.Parse()
|
|
|
|
if *dbPath == "" {
|
|
fmt.Println("Usage: export -db /path/to/pebbledb -output blocks.rlp")
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Open database in readonly mode
|
|
db, err := openDatabase(*dbPath, true)
|
|
if err != nil {
|
|
fmt.Printf("Failed to open database: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
defer db.Close()
|
|
|
|
fmt.Printf("Database opened: %s\n", *dbPath)
|
|
|
|
// Find the tip height
|
|
tipHeight, tipHash, err := findTip(db)
|
|
if err != nil {
|
|
fmt.Printf("Failed to find tip: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
fmt.Printf("Tip block: height=%d, hash=%s\n", tipHeight, hex.EncodeToString(tipHash))
|
|
|
|
// Create output file
|
|
outFile, err := os.Create(*output)
|
|
if err != nil {
|
|
fmt.Printf("Failed to create output file: %v\n", err)
|
|
os.Exit(1)
|
|
}
|
|
defer outFile.Close()
|
|
|
|
// Export blocks from 0 to tip
|
|
fmt.Printf("Exporting blocks 0 to %d...\n", tipHeight)
|
|
|
|
exported := 0
|
|
for height := uint64(0); height <= tipHeight; height++ {
|
|
block, err := getBlockByHeight(db, height)
|
|
if err != nil {
|
|
fmt.Printf("Warning: failed to get block %d: %v\n", height, err)
|
|
continue
|
|
}
|
|
|
|
// Encode block to RLP
|
|
blockRLP, err := rlp.EncodeToBytes(block)
|
|
if err != nil {
|
|
fmt.Printf("Warning: failed to encode block %d: %v\n", height, err)
|
|
continue
|
|
}
|
|
|
|
// Write length-prefixed RLP
|
|
lengthBuf := make([]byte, 4)
|
|
binary.BigEndian.PutUint32(lengthBuf, uint32(len(blockRLP)))
|
|
outFile.Write(lengthBuf)
|
|
outFile.Write(blockRLP)
|
|
|
|
exported++
|
|
if height%100 == 0 || height == tipHeight {
|
|
fmt.Printf("Exported block %d/%d\n", height, tipHeight)
|
|
}
|
|
}
|
|
|
|
fmt.Printf("\nExported %d blocks to %s\n", exported, *output)
|
|
}
|
|
|
|
func findTip(db database.Database) (uint64, []byte, error) {
|
|
// Look for AcceptorTipKey
|
|
tipKey := append(zooNamespace, []byte("AcceptorTipKey")...)
|
|
tipHash, err := db.Get(tipKey)
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("AcceptorTipKey not found: %v", err)
|
|
}
|
|
tipHashCopy := make([]byte, len(tipHash))
|
|
copy(tipHashCopy, tipHash)
|
|
|
|
// Get height from hash
|
|
height, err := getHeightByHash(db, tipHashCopy)
|
|
if err != nil {
|
|
return 0, nil, fmt.Errorf("failed to get height for tip: %v", err)
|
|
}
|
|
|
|
return height, tipHashCopy, nil
|
|
}
|
|
|
|
func getHeightByHash(db database.Database, hash []byte) (uint64, error) {
|
|
// Key: namespace + 'H' + hash
|
|
key := append(zooNamespace, 'H')
|
|
key = append(key, hash...)
|
|
|
|
value, err := db.Get(key)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
if len(value) != 8 {
|
|
return 0, fmt.Errorf("invalid height value length: %d", len(value))
|
|
}
|
|
|
|
return binary.BigEndian.Uint64(value), nil
|
|
}
|
|
|
|
func getHashByHeight(db database.Database, height uint64) ([]byte, error) {
|
|
// Iterate to find header with this height
|
|
// Key format: namespace + 'h' + be8(height) + hash
|
|
prefix := append(zooNamespace, 'h')
|
|
heightBytes := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(heightBytes, height)
|
|
prefix = append(prefix, heightBytes...)
|
|
|
|
iter := db.NewIteratorWithPrefix(prefix)
|
|
defer iter.Release()
|
|
|
|
if iter.Next() {
|
|
key := iter.Key()
|
|
// Key is: namespace(32) + 'h'(1) + height(8) + hash(32) = 73 bytes
|
|
if len(key) >= 73 {
|
|
hash := key[41:73]
|
|
hashCopy := make([]byte, 32)
|
|
copy(hashCopy, hash)
|
|
return hashCopy, nil
|
|
}
|
|
}
|
|
|
|
return nil, fmt.Errorf("hash not found for height %d", height)
|
|
}
|
|
|
|
func getBlockByHeight(db database.Database, height uint64) (*types.Block, error) {
|
|
hash, err := getHashByHeight(db, height)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
header, err := getHeader(db, height, hash)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get header: %v", err)
|
|
}
|
|
|
|
body, err := getBody(db, height, hash)
|
|
if err != nil {
|
|
// Body might be empty for genesis
|
|
body = &types.Body{}
|
|
}
|
|
|
|
return types.NewBlockWithHeader(header).WithBody(*body), nil
|
|
}
|
|
|
|
func getHeader(db database.Database, height uint64, hash []byte) (*types.Header, error) {
|
|
// Key: namespace + 'h' + be8(height) + hash
|
|
key := append(zooNamespace, 'h')
|
|
heightBytes := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(heightBytes, height)
|
|
key = append(key, heightBytes...)
|
|
key = append(key, hash...)
|
|
|
|
value, err := db.Get(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var header types.Header
|
|
if err := rlp.DecodeBytes(value, &header); err != nil {
|
|
return nil, fmt.Errorf("failed to decode header: %v", err)
|
|
}
|
|
|
|
return &header, nil
|
|
}
|
|
|
|
func getBody(db database.Database, height uint64, hash []byte) (*types.Body, error) {
|
|
// Key: namespace + 'b' + be8(height) + hash
|
|
key := append(zooNamespace, 'b')
|
|
heightBytes := make([]byte, 8)
|
|
binary.BigEndian.PutUint64(heightBytes, height)
|
|
key = append(key, heightBytes...)
|
|
key = append(key, hash...)
|
|
|
|
value, err := db.Get(key)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var body types.Body
|
|
if err := rlp.DecodeBytes(value, &body); err != nil {
|
|
return nil, fmt.Errorf("failed to decode body: %v", err)
|
|
}
|
|
|
|
return &body, nil
|
|
}
|
|
|
|
func openDatabase(path string, readOnly bool) (database.Database, error) {
|
|
mgr := manager.NewManager("", nil)
|
|
return mgr.New(&manager.Config{
|
|
Type: "pebbledb",
|
|
Path: path,
|
|
ReadOnly: readOnly,
|
|
})
|
|
}
|