Files
zeekay f24821fe46 Add migration tools and update dependencies
- Added export-full, export-kv, export-trie commands
- Added import-kv, key-sniff, survey-buckets, validate-state tools
- Updated go.mod dependencies
- Improved importer/exporter functionality
2025-12-13 09:53:45 +00:00

955 lines
25 KiB
Go

// Package migrate provides generic blockchain import/export functionality
// for migrating data between different VM implementations.
// Supports SubnetEVM→C-Chain, Zoo L2→mainnet, P/X/Q chain migrations.
package migrate
import (
"encoding/hex"
"encoding/json"
"fmt"
"math/big"
"strings"
"time"
"github.com/luxfi/geth/common"
"github.com/luxfi/geth/core/types"
)
// HexBytes is a byte slice that marshals/unmarshals as hex (with or without 0x prefix)
type HexBytes []byte
// UnmarshalJSON decodes hex string (with or without 0x prefix) to bytes
func (h *HexBytes) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
s = strings.TrimPrefix(s, "0x")
if s == "" {
*h = nil
return nil
}
b, err := hex.DecodeString(s)
if err != nil {
return err
}
*h = b
return nil
}
// MarshalJSON encodes bytes as hex string with 0x prefix
func (h HexBytes) MarshalJSON() ([]byte, error) {
if h == nil {
return []byte(`""`), nil
}
return json.Marshal("0x" + hex.EncodeToString(h))
}
// BigInt is a wrapper for big.Int that handles JSON unmarshaling from both
// string ("123") and number (123) formats, with optional 0x hex prefix support
type BigInt big.Int
// UnmarshalJSON decodes either a string or number into BigInt
func (b *BigInt) UnmarshalJSON(data []byte) error {
// Handle null
if string(data) == "null" {
return nil
}
// Try parsing as string first (handles "123" and "0x7b")
var s string
if err := json.Unmarshal(data, &s); err == nil {
s = strings.TrimSpace(s)
if s == "" {
return nil
}
// Handle hex format
if strings.HasPrefix(s, "0x") || strings.HasPrefix(s, "0X") {
i, ok := new(big.Int).SetString(s[2:], 16)
if !ok {
return fmt.Errorf("invalid hex big.Int: %s", s)
}
*b = BigInt(*i)
return nil
}
// Handle decimal format
i, ok := new(big.Int).SetString(s, 10)
if !ok {
return fmt.Errorf("invalid decimal big.Int: %s", s)
}
*b = BigInt(*i)
return nil
}
// Try parsing as raw number
i := new(big.Int)
if err := i.UnmarshalJSON(data); err != nil {
return err
}
*b = BigInt(*i)
return nil
}
// MarshalJSON encodes BigInt as a decimal string
func (b BigInt) MarshalJSON() ([]byte, error) {
return json.Marshal((*big.Int)(&b).String())
}
// Int returns the underlying *big.Int
func (b *BigInt) Int() *big.Int {
if b == nil {
return nil
}
return (*big.Int)(b)
}
// ToBigInt converts *BigInt to *big.Int
func (b *BigInt) ToBigInt() *big.Int {
if b == nil {
return nil
}
return (*big.Int)(b)
}
// FromBigInt creates a *BigInt from *big.Int
func FromBigInt(i *big.Int) *BigInt {
if i == nil {
return nil
}
b := BigInt(*i)
return &b
}
// FlexNonce is a block nonce that can unmarshal from hex strings with or without 0x prefix
type FlexNonce types.BlockNonce
// UnmarshalJSON decodes a hex string (with or without 0x prefix) into FlexNonce
func (n *FlexNonce) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
s = strings.TrimPrefix(s, "0x")
s = strings.TrimPrefix(s, "0X")
if s == "" {
*n = FlexNonce{}
return nil
}
// Pad to 16 chars (8 bytes = 16 hex chars)
for len(s) < 16 {
s = "0" + s
}
b, err := hex.DecodeString(s)
if err != nil {
return fmt.Errorf("invalid nonce hex: %w", err)
}
if len(b) != 8 {
return fmt.Errorf("nonce must be 8 bytes, got %d", len(b))
}
copy(n[:], b)
return nil
}
// ToBlockNonce converts FlexNonce to types.BlockNonce
func (n FlexNonce) ToBlockNonce() types.BlockNonce {
return types.BlockNonce(n)
}
// VMType identifies the type of virtual machine
type VMType string
const (
VMTypeSubnetEVM VMType = "subnet-evm" // SubnetEVM - export via PebbleDB, import via BadgerDB
VMTypeCChain VMType = "c-chain" // C-Chain (Coreth) - export via RPC, import via BadgerDB
VMTypeZooL2 VMType = "zoo-l2" // Zoo L2 - export via PebbleDB, import via BadgerDB
VMTypePChain VMType = "p-chain" // P-Chain - export/import via Platform API
VMTypeXChain VMType = "x-chain" // X-Chain - export/import via AVM API
VMTypeQChain VMType = "q-chain" // Q-Chain - export/import via RPC
VMTypeCustom VMType = "custom" // Custom VM
)
// BlockData represents a generic block with all necessary data for migration.
// Field names use lowercase for consistency with MigrateAPI import format.
// The MarshalJSON/UnmarshalJSON methods ensure consistent serialization.
type BlockData struct {
// Core block identifiers - matches migrate_importBlocks API format
Number uint64 `json:"height"` // "height" to match import API
Hash common.Hash `json:"hash"` // hex string with 0x prefix
ParentHash common.Hash `json:"parentHash,omitempty"`
Timestamp uint64 `json:"timestamp,omitempty"`
// State roots
StateRoot common.Hash `json:"stateRoot,omitempty"`
ReceiptsRoot common.Hash `json:"receiptsRoot,omitempty"`
TransactionsRoot common.Hash `json:"transactionsRoot,omitempty"`
// Block metadata
GasLimit uint64 `json:"gasLimit,omitempty"`
GasUsed uint64 `json:"gasUsed,omitempty"`
Difficulty *big.Int `json:"difficulty,omitempty"`
TotalDifficulty *big.Int `json:"totalDifficulty,omitempty"`
Coinbase common.Address `json:"coinbase,omitempty"`
Nonce types.BlockNonce `json:"nonce,omitempty"`
MixHash common.Hash `json:"mixHash,omitempty"`
ExtraData []byte `json:"extraData,omitempty"`
BaseFee *big.Int `json:"baseFee,omitempty"` // EIP-1559
// RLP encoded data - matches migrate_importBlocks API format (hex with 0x prefix)
Header HexBytes `json:"header,omitempty"` // RLP encoded header
Body HexBytes `json:"body,omitempty"` // RLP encoded body
Receipts HexBytes `json:"receipts,omitempty"` // RLP encoded receipts
// Decoded transactions
Transactions []*Transaction `json:"transactions,omitempty"`
UncleHeaders [][]byte `json:"uncleHeaders,omitempty"`
// State changes for this block - matches migrate_importBlocks stateChanges format
StateChanges map[common.Address]*Account `json:"stateChanges,omitempty"`
// VM-specific extensions
Extensions map[string]interface{} `json:"extensions,omitempty"`
}
// blockDataJSON is a helper struct for JSON unmarshaling that handles multiple formats:
// 1. New canonical format (camelCase, matches MigrateAPI): height, hash, header, body, receipts
// 2. Legacy export format (PascalCase): Number, Hash, Header, Body, Receipts
// 3. RPC format: number, miner, baseFeePerGas, etc.
type blockDataJSON struct {
// Primary fields (new canonical format)
Height uint64 `json:"height"`
Hash common.Hash `json:"hash"`
ParentHash common.Hash `json:"parentHash"`
Timestamp uint64 `json:"timestamp"`
// State roots
StateRoot common.Hash `json:"stateRoot"`
ReceiptsRoot common.Hash `json:"receiptsRoot"`
TransactionsRoot common.Hash `json:"transactionsRoot"`
// Block metadata
GasLimit uint64 `json:"gasLimit"`
GasUsed uint64 `json:"gasUsed"`
Difficulty *BigInt `json:"difficulty"`
TotalDifficulty *BigInt `json:"totalDifficulty"`
Coinbase common.Address `json:"coinbase"`
Nonce FlexNonce `json:"nonce"`
MixHash common.Hash `json:"mixHash"`
ExtraData HexBytes `json:"extraData"`
BaseFee *BigInt `json:"baseFee"`
// RLP encoded data (new canonical format - matches MigrateAPI)
Header HexBytes `json:"header"`
Body HexBytes `json:"body"`
Receipts HexBytes `json:"receipts"`
// Decoded transactions
Transactions []*Transaction `json:"transactions"`
UncleHeaders [][]byte `json:"uncleHeaders"`
// State changes
StateChanges map[common.Address]*Account `json:"stateChanges"`
// Extensions
Extensions map[string]interface{} `json:"extensions"`
}
// UnmarshalJSON handles multiple JSON formats for backwards compatibility:
// 1. New canonical format (matches MigrateAPI): height, hash, header, body, receipts
// 2. Legacy export format (PascalCase): Number, Hash, Header, Body, Receipts
// 3. RPC format: number, miner, baseFeePerGas
func (b *BlockData) UnmarshalJSON(data []byte) error {
// First parse into raw map to detect format
var raw map[string]json.RawMessage
if err := json.Unmarshal(data, &raw); err != nil {
return err
}
// Try primary helper struct (new canonical format)
var helper blockDataJSON
if err := json.Unmarshal(data, &helper); err != nil {
return err
}
// Copy from helper
b.Number = helper.Height
b.Hash = helper.Hash
b.ParentHash = helper.ParentHash
b.Timestamp = helper.Timestamp
b.StateRoot = helper.StateRoot
b.ReceiptsRoot = helper.ReceiptsRoot
b.TransactionsRoot = helper.TransactionsRoot
b.GasLimit = helper.GasLimit
b.GasUsed = helper.GasUsed
b.Coinbase = helper.Coinbase
b.Nonce = helper.Nonce.ToBlockNonce()
b.MixHash = helper.MixHash
b.ExtraData = helper.ExtraData
b.Header = helper.Header
b.Body = helper.Body
b.Receipts = helper.Receipts
b.Transactions = helper.Transactions
b.UncleHeaders = helper.UncleHeaders
b.StateChanges = helper.StateChanges
b.Extensions = helper.Extensions
// Convert BigInt to *big.Int
if helper.Difficulty != nil {
b.Difficulty = helper.Difficulty.ToBigInt()
}
if helper.TotalDifficulty != nil {
b.TotalDifficulty = helper.TotalDifficulty.ToBigInt()
}
if helper.BaseFee != nil {
b.BaseFee = helper.BaseFee.ToBigInt()
}
// Handle legacy PascalCase format (Number instead of height)
if b.Number == 0 {
if val, ok := raw["Number"]; ok {
json.Unmarshal(val, &b.Number)
}
if val, ok := raw["number"]; ok && b.Number == 0 {
json.Unmarshal(val, &b.Number)
}
}
// Handle legacy Hash if not set
if b.Hash == (common.Hash{}) {
if val, ok := raw["Hash"]; ok {
json.Unmarshal(val, &b.Hash)
}
}
// Handle legacy ParentHash
if b.ParentHash == (common.Hash{}) {
if val, ok := raw["ParentHash"]; ok {
json.Unmarshal(val, &b.ParentHash)
}
}
// Handle legacy Timestamp
if b.Timestamp == 0 {
if val, ok := raw["Timestamp"]; ok {
json.Unmarshal(val, &b.Timestamp)
}
}
// Handle legacy state roots (PascalCase)
if b.StateRoot == (common.Hash{}) {
if val, ok := raw["StateRoot"]; ok {
json.Unmarshal(val, &b.StateRoot)
}
}
if b.ReceiptsRoot == (common.Hash{}) {
if val, ok := raw["ReceiptsRoot"]; ok {
json.Unmarshal(val, &b.ReceiptsRoot)
}
}
if b.TransactionsRoot == (common.Hash{}) {
if val, ok := raw["TransactionsRoot"]; ok {
json.Unmarshal(val, &b.TransactionsRoot)
}
}
// Handle legacy gas fields (PascalCase)
if b.GasLimit == 0 {
if val, ok := raw["GasLimit"]; ok {
json.Unmarshal(val, &b.GasLimit)
}
}
if b.GasUsed == 0 {
if val, ok := raw["GasUsed"]; ok {
json.Unmarshal(val, &b.GasUsed)
}
}
// Handle legacy coinbase (PascalCase or "miner" from RPC)
if b.Coinbase == (common.Address{}) {
if val, ok := raw["Coinbase"]; ok {
json.Unmarshal(val, &b.Coinbase)
}
if val, ok := raw["miner"]; ok && b.Coinbase == (common.Address{}) {
json.Unmarshal(val, &b.Coinbase)
}
}
// Handle legacy MixHash
if b.MixHash == (common.Hash{}) {
if val, ok := raw["MixHash"]; ok {
json.Unmarshal(val, &b.MixHash)
}
}
// Handle legacy ExtraData
if len(b.ExtraData) == 0 {
if val, ok := raw["ExtraData"]; ok {
var hex HexBytes
if json.Unmarshal(val, &hex) == nil {
b.ExtraData = hex
}
}
}
// Handle legacy BaseFee (PascalCase or "baseFeePerGas" from RPC)
if b.BaseFee == nil {
if val, ok := raw["BaseFee"]; ok {
var bf BigInt
if json.Unmarshal(val, &bf) == nil {
b.BaseFee = bf.ToBigInt()
}
}
if val, ok := raw["baseFeePerGas"]; ok && b.BaseFee == nil {
var bf BigInt
if json.Unmarshal(val, &bf) == nil {
b.BaseFee = bf.ToBigInt()
}
}
}
// Handle legacy Difficulty (PascalCase)
if b.Difficulty == nil {
if val, ok := raw["Difficulty"]; ok {
var d BigInt
if json.Unmarshal(val, &d) == nil {
b.Difficulty = d.ToBigInt()
}
}
}
// Handle legacy TotalDifficulty (PascalCase)
if b.TotalDifficulty == nil {
if val, ok := raw["TotalDifficulty"]; ok {
var td BigInt
if json.Unmarshal(val, &td) == nil {
b.TotalDifficulty = td.ToBigInt()
}
}
}
// Handle legacy RLP fields (PascalCase: Header, Body, Receipts)
if len(b.Header) == 0 {
if val, ok := raw["Header"]; ok {
var hex HexBytes
if json.Unmarshal(val, &hex) == nil {
b.Header = hex
}
}
}
if len(b.Body) == 0 {
if val, ok := raw["Body"]; ok {
var hex HexBytes
if json.Unmarshal(val, &hex) == nil {
b.Body = hex
}
}
}
if len(b.Receipts) == 0 {
if val, ok := raw["Receipts"]; ok {
var hex HexBytes
if json.Unmarshal(val, &hex) == nil {
b.Receipts = hex
}
}
}
// Handle legacy Transactions (PascalCase)
if len(b.Transactions) == 0 {
if val, ok := raw["Transactions"]; ok {
json.Unmarshal(val, &b.Transactions)
}
}
// Handle legacy UncleHeaders (PascalCase)
if len(b.UncleHeaders) == 0 {
if val, ok := raw["UncleHeaders"]; ok {
json.Unmarshal(val, &b.UncleHeaders)
}
}
// Handle legacy StateChanges (PascalCase)
if len(b.StateChanges) == 0 {
if val, ok := raw["StateChanges"]; ok {
json.Unmarshal(val, &b.StateChanges)
}
}
// Handle legacy Extensions (PascalCase)
if len(b.Extensions) == 0 {
if val, ok := raw["Extensions"]; ok {
json.Unmarshal(val, &b.Extensions)
}
}
return nil
}
// Transaction represents a generic transaction
type Transaction struct {
Hash common.Hash
Nonce uint64
From common.Address
To *common.Address // nil for contract creation
Value *big.Int
Gas uint64
GasPrice *big.Int
Data []byte
V, R, S *big.Int
// EIP-1559 fields
GasTipCap *big.Int
GasFeeCap *big.Int
AccessList types.AccessList
// Receipt data
Receipt *Receipt
}
// Receipt represents a transaction receipt
type Receipt struct {
Status uint64
CumulativeGasUsed uint64
Bloom types.Bloom
Logs []*types.Log
TransactionHash common.Hash
ContractAddress *common.Address
GasUsed uint64
}
// Account represents an account in the state trie
type Account struct {
Address common.Address
Nonce uint64
Balance *big.Int
CodeHash common.Hash
StorageRoot common.Hash
Code []byte
Storage map[common.Hash]common.Hash
}
// StateChange represents a state change for a single account in a block.
// This is the diff format for per-block state migration:
// - For genesis (block 0): full account snapshot (not a diff)
// - For blocks N>0: post-state diff relative to parent
type StateChange struct {
// Address of the account (20 bytes, hex with 0x prefix)
Address common.Address `json:"address,omitempty"`
// Account fields - set to new values after this block
Nonce uint64 `json:"nonce"`
Balance *big.Int `json:"balance"`
// Code - only included if code changed this block (contract creation)
Code []byte `json:"code,omitempty"`
// Storage - map of slot -> value for changed slots
// Zero value (0x0...0) means delete the slot from storage trie
Storage map[common.Hash]common.Hash `json:"storage,omitempty"`
// Deleted - true if account was self-destructed/deleted
// If true, remove account from state trie entirely
Deleted bool `json:"deleted,omitempty"`
}
// stateChangeJSON is the JSON representation with hex encoding
type stateChangeJSON struct {
Address string `json:"address,omitempty"`
Nonce uint64 `json:"nonce,omitempty"`
Balance string `json:"balance,omitempty"` // hex-encoded
Code string `json:"code,omitempty"` // hex-encoded
Storage map[string]string `json:"storage,omitempty"` // hex keys and values
Deleted bool `json:"deleted,omitempty"`
}
// MarshalJSON implements json.Marshaler for StateChange with hex encoding
func (s *StateChange) MarshalJSON() ([]byte, error) {
sj := stateChangeJSON{
Nonce: s.Nonce,
Deleted: s.Deleted,
}
if s.Address != (common.Address{}) {
sj.Address = s.Address.Hex()
}
if s.Balance != nil && s.Balance.Sign() >= 0 {
sj.Balance = "0x" + s.Balance.Text(16)
}
if len(s.Code) > 0 {
sj.Code = "0x" + common.Bytes2Hex(s.Code)
}
if len(s.Storage) > 0 {
sj.Storage = make(map[string]string)
for k, v := range s.Storage {
sj.Storage[k.Hex()] = v.Hex()
}
}
return json.Marshal(sj)
}
// UnmarshalJSON implements json.Unmarshaler for StateChange with hex decoding
func (s *StateChange) UnmarshalJSON(data []byte) error {
var sj stateChangeJSON
if err := json.Unmarshal(data, &sj); err != nil {
return err
}
if sj.Address != "" {
s.Address = common.HexToAddress(sj.Address)
}
s.Nonce = sj.Nonce
s.Deleted = sj.Deleted
if sj.Balance != "" {
s.Balance = new(big.Int)
s.Balance.SetString(strings.TrimPrefix(sj.Balance, "0x"), 16)
}
if sj.Code != "" {
s.Code = common.FromHex(sj.Code)
}
if len(sj.Storage) > 0 {
s.Storage = make(map[common.Hash]common.Hash)
for k, v := range sj.Storage {
s.Storage[common.HexToHash(k)] = common.HexToHash(v)
}
}
return nil
}
// accountJSON is the JSON representation of Account with hex encoding
type accountJSON struct {
Address string `json:"address,omitempty"`
Nonce uint64 `json:"nonce,omitempty"`
Balance string `json:"balance,omitempty"` // hex-encoded
CodeHash string `json:"codeHash,omitempty"`
StorageRoot string `json:"storageRoot,omitempty"`
Code string `json:"code,omitempty"` // hex-encoded
Storage map[string]string `json:"storage,omitempty"` // hex keys and values
}
// MarshalJSON implements json.Marshaler for Account with hex encoding
func (a *Account) MarshalJSON() ([]byte, error) {
aj := accountJSON{
Nonce: a.Nonce,
}
if a.Address != (common.Address{}) {
aj.Address = a.Address.Hex()
}
if a.Balance != nil && a.Balance.Sign() != 0 {
aj.Balance = "0x" + a.Balance.Text(16)
}
if a.CodeHash != (common.Hash{}) {
aj.CodeHash = a.CodeHash.Hex()
}
if a.StorageRoot != (common.Hash{}) {
aj.StorageRoot = a.StorageRoot.Hex()
}
if len(a.Code) > 0 {
aj.Code = "0x" + common.Bytes2Hex(a.Code)
}
if len(a.Storage) > 0 {
aj.Storage = make(map[string]string)
for k, v := range a.Storage {
aj.Storage[k.Hex()] = v.Hex()
}
}
return json.Marshal(aj)
}
// UnmarshalJSON implements json.Unmarshaler for Account with hex decoding
func (a *Account) UnmarshalJSON(data []byte) error {
var aj accountJSON
if err := json.Unmarshal(data, &aj); err != nil {
return err
}
if aj.Address != "" {
a.Address = common.HexToAddress(aj.Address)
}
a.Nonce = aj.Nonce
if aj.Balance != "" {
a.Balance = new(big.Int)
a.Balance.SetString(strings.TrimPrefix(aj.Balance, "0x"), 16)
}
if aj.CodeHash != "" {
a.CodeHash = common.HexToHash(aj.CodeHash)
}
if aj.StorageRoot != "" {
a.StorageRoot = common.HexToHash(aj.StorageRoot)
}
if aj.Code != "" {
a.Code = common.FromHex(aj.Code)
}
if len(aj.Storage) > 0 {
a.Storage = make(map[common.Hash]common.Hash)
for k, v := range aj.Storage {
a.Storage[common.HexToHash(k)] = common.HexToHash(v)
}
}
return nil
}
// Info contains metadata about a blockchain/VM
type Info struct {
VMType VMType
NetworkID uint64
ChainID *big.Int
GenesisHash common.Hash
CurrentHeight uint64
TotalDifficulty *big.Int
StateRoot common.Hash
// VM-specific info
VMVersion string
DatabaseType string // "pebble", "badger", "leveldb"
IsPruned bool
ArchiveMode bool
// Feature flags
HasWarpMessages bool
HasProposerVM bool
// VM-specific extensions (for P-Chain, X-Chain, etc.)
Extensions map[string]interface{}
}
// Config contains configuration for a blockchain/VM
type Config struct {
NetworkID uint64
ChainID *big.Int
// Genesis configuration
GenesisBlock *BlockData
GenesisAlloc map[common.Address]*Account
// Fork heights
HomesteadBlock *big.Int
EIP150Block *big.Int
EIP155Block *big.Int
EIP158Block *big.Int
ByzantiumBlock *big.Int
ConstantinopleBlock *big.Int
PetersburgBlock *big.Int
IstanbulBlock *big.Int
BerlinBlock *big.Int
LondonBlock *big.Int
// Special configurations
IsCoreth bool
HasNetID bool
NetID string
Precompiles map[common.Address]string
// VM-specific extensions (for P-Chain, X-Chain, etc.)
Extensions map[string]interface{}
}
// ExporterConfig configures an exporter instance
type ExporterConfig struct {
VMType VMType
DatabasePath string
DatabaseType string // "pebble", "badger", "leveldb"
// For network-based export
RPCURL string
NetworkID uint64
// Namespace for multi-chain databases
NetNamespace []byte
NetID string
// Export options
ExportState bool
ExportReceipts bool
VerifyIntegrity bool
MaxConcurrency int
// Compatibility modes
CorethCompat bool
}
// ImporterConfig configures an importer instance
type ImporterConfig struct {
VMType VMType
DatabasePath string
DatabaseType string
// For RPC-based import
RPCURL string
// Import options
ExecuteBlocks bool
VerifyState bool
BatchSize int
MaxConcurrency int
// State management
PreserveState bool
MergeState bool
// Compatibility modes
CorethCompat bool
GenesisTime time.Time
}
// MigrationOptions configures the migration process
type MigrationOptions struct {
// Block range
StartBlock uint64
EndBlock uint64
// Processing options
BatchSize int
MaxConcurrency int
VerifyEachBlock bool
// State options
MigrateState bool
StateHeight uint64
// Error handling
ContinueOnError bool
RetryFailures int
// Progress reporting
ProgressCallback func(current, total uint64)
// Special migrations
RegenesisMode bool
PreserveNonces bool
RemapAddresses map[common.Address]common.Address
}
// MigrationResult contains the result of a migration
type MigrationResult struct {
Success bool
BlocksMigrated uint64
StatesMigrated uint64
StartTime time.Time
EndTime time.Time
Errors []error
// Verification results
SourceStateRoot common.Hash
DestStateRoot common.Hash
StateMatches bool
}
// TrieNodeType identifies the type of trie node
type TrieNodeType string
const (
TrieNodeTypeAccount TrieNodeType = "account" // Account trie node (PathDB: "A" prefix)
TrieNodeTypeStorage TrieNodeType = "storage" // Storage trie node (PathDB: "O" prefix)
TrieNodeTypeCode TrieNodeType = "code" // Contract code (prefix: "c")
TrieNodeTypeStateID TrieNodeType = "stateID" // State ID mapping (prefix: "L")
)
// TrieNode represents a raw state trie node for direct database import.
// This is used for raw KV export/import which is required for state migration
// since RPC block import cannot recreate state (only headers/bodies/receipts).
type TrieNode struct {
// Type of trie node
Type TrieNodeType `json:"type"`
// Raw database key (includes prefix: A, O, c, or L)
Key []byte `json:"key"`
// Raw database value (trie node data, code bytes, or state ID)
Value []byte `json:"value"`
// Optional metadata
Path []byte `json:"path,omitempty"` // Hex path for PathDB nodes
Owner common.Hash `json:"owner,omitempty"` // Account hash for storage trie nodes
StateID uint64 `json:"stateId,omitempty"` // State ID for L* entries
}
// trieNodeJSON is the JSON representation with hex encoding
type trieNodeJSON struct {
Type TrieNodeType `json:"type"`
Key string `json:"key"` // hex-encoded
Value string `json:"value"` // hex-encoded
Path string `json:"path,omitempty"`
Owner string `json:"owner,omitempty"`
StateID uint64 `json:"stateId,omitempty"`
}
// MarshalJSON implements json.Marshaler for TrieNode
func (t *TrieNode) MarshalJSON() ([]byte, error) {
tj := trieNodeJSON{
Type: t.Type,
Key: "0x" + hex.EncodeToString(t.Key),
Value: "0x" + hex.EncodeToString(t.Value),
StateID: t.StateID,
}
if len(t.Path) > 0 {
tj.Path = "0x" + hex.EncodeToString(t.Path)
}
if t.Owner != (common.Hash{}) {
tj.Owner = t.Owner.Hex()
}
return json.Marshal(tj)
}
// UnmarshalJSON implements json.Unmarshaler for TrieNode
func (t *TrieNode) UnmarshalJSON(data []byte) error {
var tj trieNodeJSON
if err := json.Unmarshal(data, &tj); err != nil {
return err
}
t.Type = tj.Type
t.StateID = tj.StateID
// Decode hex fields
if tj.Key != "" {
k, err := hex.DecodeString(strings.TrimPrefix(tj.Key, "0x"))
if err != nil {
return fmt.Errorf("invalid key hex: %w", err)
}
t.Key = k
}
if tj.Value != "" {
v, err := hex.DecodeString(strings.TrimPrefix(tj.Value, "0x"))
if err != nil {
return fmt.Errorf("invalid value hex: %w", err)
}
t.Value = v
}
if tj.Path != "" {
p, err := hex.DecodeString(strings.TrimPrefix(tj.Path, "0x"))
if err != nil {
return fmt.Errorf("invalid path hex: %w", err)
}
t.Path = p
}
if tj.Owner != "" {
t.Owner = common.HexToHash(tj.Owner)
}
return nil
}
// TrieExportStats contains statistics for a trie export
type TrieExportStats struct {
AccountNodes int64 `json:"accountNodes"`
StorageNodes int64 `json:"storageNodes"`
CodeBlobs int64 `json:"codeBlobs"`
StateIDs int64 `json:"stateIds"`
TotalBytes int64 `json:"totalBytes"`
ExportTimeMs int64 `json:"exportTimeMs"`
StateRoot common.Hash `json:"stateRoot,omitempty"`
}
// TrieImportResult contains the result of a trie import
type TrieImportResult struct {
Success bool `json:"success"`
NodesImported int64 `json:"nodesImported"`
BytesWritten int64 `json:"bytesWritten"`
ImportTimeMs int64 `json:"importTimeMs"`
Errors []string `json:"errors,omitempty"`
}