Files
zeekay d32e075809 refactor: update to vm v1.0.18, migrate log and metrics packages
- Update vm dependency to v1.0.18
- Remove internal log package in favor of luxfi/log
- Migrate prometheus to gatherer package
2026-01-11 21:38:34 -08:00

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,
})
}