mirror of
https://github.com/luxfi/migrate.git
synced 2026-10-30 15:25:11 +00:00
The Exporter/Importer interfaces declared methods the concrete VM types did not all implement, breaking `go build ./...`. Remove-vs-implement decision (driven by real call sites): - ExportStateTrie / ImportStateTrie: REMOVED from the Exporter/Importer interfaces. These were aspirational — no code anywhere invokes them on an interface value (the only references were the interface decls plus RPCImporter.ImportStateTrie, which just returns ErrStateImportNotSupported). Removing them unblocks the build without inventing per-chain state-trie migration logic. RPCImporter.ImportStateTrie is kept as a harmless extra method; the TrieNode/TrieImportResult types remain (still referenced). - ExportBlocksWithState: KEPT in the Exporter interface — it has a real caller (cmd/export/main.go). Implemented on the four chains that lacked it (xchain, pchain, qchain, zool2) by delegating to ExportBlocks, mirroring the existing cchain.ExportBlocksWithState (these chains carry state in blocks / rebuild via replay, so the two are equivalent — same pattern cchain already documents). Also repaired the pre-existing (independent) build breakage in cmd/export-genesis (luxfi/geth v1.16.39 API drift): pebbleDB now implements ethdb.KeyValueStore (Stat()/SyncKeyValue/DeleteRange; dropped removed NewSnapshot) and is wrapped with rawdb.NewDatabase for the ancient-aware readers; trie.NewDatabase -> triedb.NewDatabase; StateAccount.CodeHash ([]byte) handled via common.BytesToHash; pebble NewIter's (iter, error) return handled. Fixed a go vet finding in cmd/key-sniff (redundant Println newline). go build ./... + go vet ./... + go test ./... all pass. Co-authored-by: Hanzo Dev <[email protected]>
345 lines
8.5 KiB
Go
345 lines
8.5 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"log"
|
|
"os"
|
|
|
|
"github.com/cockroachdb/pebble"
|
|
"github.com/luxfi/geth/common"
|
|
"github.com/luxfi/geth/core/rawdb"
|
|
"github.com/luxfi/geth/core/types"
|
|
"github.com/luxfi/geth/ethdb"
|
|
"github.com/luxfi/geth/rlp"
|
|
"github.com/luxfi/geth/trie"
|
|
"github.com/luxfi/geth/triedb"
|
|
)
|
|
|
|
// JSONLBlock represents a block in JSONL format with state
|
|
type JSONLBlock struct {
|
|
Number uint64 `json:"number"`
|
|
Hash string `json:"hash"`
|
|
HeaderRLP string `json:"header_rlp"`
|
|
BodyRLP string `json:"body_rlp"`
|
|
ReceiptsRLP string `json:"receipts_rlp"`
|
|
StateChanges map[string]*JSONLAccount `json:"state_changes,omitempty"`
|
|
}
|
|
|
|
// JSONLAccount represents an account in JSONL format
|
|
type JSONLAccount struct {
|
|
Balance string `json:"balance"`
|
|
Nonce uint64 `json:"nonce"`
|
|
Code string `json:"code,omitempty"`
|
|
CodeHash string `json:"code_hash,omitempty"`
|
|
Storage map[string]string `json:"storage,omitempty"`
|
|
}
|
|
|
|
// pebbleDB wraps pebble.DB to implement ethdb.Database
|
|
type pebbleDB struct {
|
|
db *pebble.DB
|
|
}
|
|
|
|
func (p *pebbleDB) Get(key []byte) ([]byte, error) {
|
|
val, closer, err := p.db.Get(key)
|
|
if err != nil {
|
|
if err == pebble.ErrNotFound {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
defer closer.Close()
|
|
return append([]byte{}, val...), nil
|
|
}
|
|
|
|
func (p *pebbleDB) Has(key []byte) (bool, error) {
|
|
val, err := p.Get(key)
|
|
return val != nil, err
|
|
}
|
|
|
|
func (p *pebbleDB) Put(key []byte, value []byte) error {
|
|
return fmt.Errorf("read-only database")
|
|
}
|
|
|
|
func (p *pebbleDB) Delete(key []byte) error {
|
|
return fmt.Errorf("read-only database")
|
|
}
|
|
|
|
func (p *pebbleDB) NewBatch() ethdb.Batch {
|
|
return nil
|
|
}
|
|
|
|
func (p *pebbleDB) NewBatchWithSize(size int) ethdb.Batch {
|
|
return nil
|
|
}
|
|
|
|
func (p *pebbleDB) NewIterator(prefix []byte, start []byte) ethdb.Iterator {
|
|
opts := &pebble.IterOptions{}
|
|
if len(prefix) > 0 {
|
|
opts.LowerBound = prefix
|
|
opts.UpperBound = func(b []byte) []byte {
|
|
end := make([]byte, len(b))
|
|
copy(end, b)
|
|
for i := len(end) - 1; i >= 0; i-- {
|
|
end[i] = end[i] + 1
|
|
if end[i] != 0 {
|
|
return end
|
|
}
|
|
}
|
|
return nil
|
|
}(prefix)
|
|
}
|
|
iter, err := p.db.NewIter(opts)
|
|
if err != nil {
|
|
// Return an iterator that immediately reports the error.
|
|
return &pebbleIterator{err: err}
|
|
}
|
|
return &pebbleIterator{iter: iter}
|
|
}
|
|
|
|
func (p *pebbleDB) Stat() (string, error) {
|
|
return "", fmt.Errorf("stats not supported")
|
|
}
|
|
|
|
func (p *pebbleDB) SyncKeyValue() error {
|
|
return nil
|
|
}
|
|
|
|
func (p *pebbleDB) DeleteRange(start, end []byte) error {
|
|
return fmt.Errorf("read-only database")
|
|
}
|
|
|
|
func (p *pebbleDB) Compact(start []byte, limit []byte) error {
|
|
return fmt.Errorf("read-only database")
|
|
}
|
|
|
|
func (p *pebbleDB) Close() error {
|
|
return p.db.Close()
|
|
}
|
|
|
|
// pebbleIterator wraps pebble.Iterator to implement ethdb.Iterator
|
|
type pebbleIterator struct {
|
|
iter *pebble.Iterator
|
|
err error
|
|
}
|
|
|
|
func (it *pebbleIterator) Next() bool {
|
|
if it.iter == nil {
|
|
return false
|
|
}
|
|
return it.iter.Next()
|
|
}
|
|
|
|
func (it *pebbleIterator) Error() error {
|
|
if it.err != nil {
|
|
return it.err
|
|
}
|
|
if it.iter == nil {
|
|
return nil
|
|
}
|
|
return it.iter.Error()
|
|
}
|
|
|
|
func (it *pebbleIterator) Key() []byte {
|
|
if it.iter == nil {
|
|
return nil
|
|
}
|
|
return it.iter.Key()
|
|
}
|
|
|
|
func (it *pebbleIterator) Value() []byte {
|
|
if it.iter == nil {
|
|
return nil
|
|
}
|
|
return it.iter.Value()
|
|
}
|
|
|
|
func (it *pebbleIterator) Release() {
|
|
if it.iter != nil {
|
|
it.iter.Close()
|
|
}
|
|
}
|
|
|
|
func main() {
|
|
sourceDB := flag.String("source-db", "", "Path to source PebbleDB database")
|
|
output := flag.String("output", "", "Output JSONL file path")
|
|
blockNum := flag.Uint64("block", 0, "Block number to export (default: 0 = genesis)")
|
|
flag.Parse()
|
|
|
|
if *sourceDB == "" || *output == "" {
|
|
log.Fatal("Usage: export-genesis --source-db=<path> --output=<file> [--block=N]")
|
|
}
|
|
|
|
// Open PebbleDB in read-only mode
|
|
opts := &pebble.Options{
|
|
ReadOnly: true,
|
|
}
|
|
db, err := pebble.Open(*sourceDB, opts)
|
|
if err != nil {
|
|
log.Fatalf("Failed to open pebble database: %v", err)
|
|
}
|
|
defer db.Close()
|
|
|
|
// Wrap the raw key-value store so it satisfies ethdb.Database (the rawdb
|
|
// readers require an ancient store, which rawdb.NewDatabase provides as a
|
|
// no-op for key-value-only backends).
|
|
ethDB := rawdb.NewDatabase(&pebbleDB{db: db})
|
|
|
|
log.Printf("=== Export Genesis Configuration ===")
|
|
log.Printf("Source DB: %s", *sourceDB)
|
|
log.Printf("Block Number: %d", *blockNum)
|
|
log.Printf("Output: %s", *output)
|
|
|
|
// Read block hash
|
|
hash := rawdb.ReadCanonicalHash(ethDB, *blockNum)
|
|
if hash == (common.Hash{}) {
|
|
log.Fatalf("Block %d not found", *blockNum)
|
|
}
|
|
log.Printf("Block Hash: %s", hash.Hex())
|
|
|
|
// Read header
|
|
header := rawdb.ReadHeader(ethDB, hash, *blockNum)
|
|
if header == nil {
|
|
log.Fatalf("Header not found for block %d", *blockNum)
|
|
}
|
|
log.Printf("State Root: %s", header.Root.Hex())
|
|
|
|
// Read body
|
|
body := rawdb.ReadBody(ethDB, hash, *blockNum)
|
|
|
|
// Encode header to RLP
|
|
headerRLP, err := rlp.EncodeToBytes(header)
|
|
if err != nil {
|
|
log.Fatalf("Failed to encode header: %v", err)
|
|
}
|
|
|
|
// Encode body to RLP
|
|
var bodyRLP []byte
|
|
if body != nil {
|
|
bodyRLP, err = rlp.EncodeToBytes(body)
|
|
if err != nil {
|
|
log.Fatalf("Failed to encode body: %v", err)
|
|
}
|
|
}
|
|
|
|
// Read receipts
|
|
receipts := rawdb.ReadReceipts(ethDB, hash, *blockNum, header.Time, nil)
|
|
var receiptsRLP []byte
|
|
if len(receipts) > 0 {
|
|
receiptsRLP, err = rlp.EncodeToBytes(receipts)
|
|
if err != nil {
|
|
log.Fatalf("Failed to encode receipts: %v", err)
|
|
}
|
|
}
|
|
|
|
// Build JSONL block
|
|
jsonlBlock := &JSONLBlock{
|
|
Number: *blockNum,
|
|
Hash: hash.Hex(),
|
|
HeaderRLP: common.Bytes2Hex(headerRLP),
|
|
BodyRLP: common.Bytes2Hex(bodyRLP),
|
|
ReceiptsRLP: common.Bytes2Hex(receiptsRLP),
|
|
StateChanges: make(map[string]*JSONLAccount),
|
|
}
|
|
|
|
// Export state from trie
|
|
log.Printf("\n=== Exporting State ===")
|
|
log.Printf("Reading state trie at root: %s", header.Root.Hex())
|
|
|
|
trieDB := triedb.NewDatabase(ethDB, nil)
|
|
stateTrie, err := trie.NewStateTrie(trie.StateTrieID(header.Root), trieDB)
|
|
if err != nil {
|
|
log.Fatalf("Failed to open state trie: %v", err)
|
|
}
|
|
|
|
// Iterate through all accounts in state
|
|
accountCount := 0
|
|
totalBalance := uint64(0)
|
|
|
|
iter, err := stateTrie.NodeIterator(nil)
|
|
if err != nil {
|
|
log.Fatalf("Failed to create iterator: %v", err)
|
|
}
|
|
|
|
for iter.Next(true) {
|
|
if iter.Leaf() {
|
|
// Decode account
|
|
var acc types.StateAccount
|
|
if err := rlp.DecodeBytes(iter.LeafBlob(), &acc); err != nil {
|
|
log.Printf("Failed to decode account: %v", err)
|
|
continue
|
|
}
|
|
|
|
// Get address from hash (note: this is the hash, not the actual address)
|
|
// We can't recover the exact address without a reverse mapping
|
|
addrHash := common.BytesToHash(iter.LeafKey())
|
|
|
|
// Try to use the address hash as the address (works for most cases in genesis)
|
|
addr := common.BytesToAddress(addrHash.Bytes())
|
|
|
|
codeHash := common.BytesToHash(acc.CodeHash)
|
|
|
|
jsonlAcc := &JSONLAccount{
|
|
Balance: acc.Balance.String(),
|
|
Nonce: acc.Nonce,
|
|
CodeHash: codeHash.Hex(),
|
|
}
|
|
|
|
// Read code if exists
|
|
if codeHash != types.EmptyCodeHash && codeHash != (common.Hash{}) {
|
|
code := rawdb.ReadCode(ethDB, codeHash)
|
|
if len(code) > 0 {
|
|
jsonlAcc.Code = common.Bytes2Hex(code)
|
|
}
|
|
}
|
|
|
|
// Read storage
|
|
if acc.Root != types.EmptyRootHash {
|
|
storageTrie, err := trie.NewStateTrie(trie.StorageTrieID(header.Root, addrHash, acc.Root), trieDB)
|
|
if err == nil {
|
|
storageIter, err := storageTrie.NodeIterator(nil)
|
|
if err == nil {
|
|
jsonlAcc.Storage = make(map[string]string)
|
|
for storageIter.Next(true) {
|
|
if storageIter.Leaf() {
|
|
key := common.BytesToHash(storageIter.LeafKey())
|
|
value := common.BytesToHash(storageIter.LeafBlob())
|
|
jsonlAcc.Storage[key.Hex()] = value.Hex()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
jsonlBlock.StateChanges[addr.Hex()] = jsonlAcc
|
|
accountCount++
|
|
|
|
if accountCount%100 == 0 {
|
|
log.Printf("Exported %d accounts...", accountCount)
|
|
}
|
|
}
|
|
}
|
|
|
|
log.Printf("Total Accounts: %d", accountCount)
|
|
log.Printf("Total Balance: %d", totalBalance)
|
|
|
|
// Write to JSONL file
|
|
file, err := os.Create(*output)
|
|
if err != nil {
|
|
log.Fatalf("Failed to create output file: %v", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
encoder := json.NewEncoder(file)
|
|
if err := encoder.Encode(jsonlBlock); err != nil {
|
|
log.Fatalf("Failed to write JSONL: %v", err)
|
|
}
|
|
|
|
log.Printf("\n=== Export Complete ===")
|
|
log.Printf("Output: %s", *output)
|
|
log.Printf("Block: %d", *blockNum)
|
|
log.Printf("Hash: %s", hash.Hex())
|
|
log.Printf("Accounts: %d", len(jsonlBlock.StateChanges))
|
|
}
|