diff --git a/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 98ed348d8c..0702ad15a5 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -538,10 +538,9 @@ func importHistory(ctx *cli.Context) error { default: return fmt.Errorf("unknown --era.format %q (expected 'era1' or 'erae')", format) } - if err := utils.ImportHistory(chain, dir, network, from); err != nil { + if err := utils.ImportHistory(chain, db, dir, network, from); err != nil { return err } - fmt.Printf("Import done in %v\n", time.Since(start)) return nil } diff --git a/cmd/utils/cmd.go b/cmd/utils/cmd.go index e490f613b3..a6d4b64ef5 100644 --- a/cmd/utils/cmd.go +++ b/cmd/utils/cmd.go @@ -252,7 +252,7 @@ func readList(filename string) ([]string, error) { // ImportHistory imports Era1 files containing historical block information, // starting from genesis. The assumption is held that the provided chain // segment in Era1 file should all be canonical and verified. -func ImportHistory(chain *core.BlockChain, dir string, network string, from func(f era.ReadAtSeekCloser) (era.Era, error)) error { +func ImportHistory(chain *core.BlockChain, db ethdb.Database, dir string, network string, from func(f era.ReadAtSeekCloser) (era.Era, error)) error { if chain.CurrentSnapBlock().Number.BitLen() != 0 { return errors.New("history import only supported when starting from genesis") } @@ -275,6 +275,7 @@ func ImportHistory(chain *core.BlockChain, dir string, network string, from func imported = 0 h = sha256.New() buf = bytes.NewBuffer(nil) + idxBatch = db.NewBatch() ) for i, file := range entries { @@ -297,6 +298,7 @@ func ImportHistory(chain *core.BlockChain, dir string, network string, from func if got != checksums[i] { return fmt.Errorf("%s checksum mismatch: have %s want %s", file, got, checksums[i]) } + // Import all block data from Era1. e, err := from(f) if err != nil { @@ -321,6 +323,24 @@ func ImportHistory(chain *core.BlockChain, dir string, network string, from func return fmt.Errorf("error inserting blocks %d-%d: %w", blocks[0].NumberU64(), blocks[len(blocks)-1].NumberU64(), err) } + + // Index tx lookups for the same batch we just inserted. + for _, block := range blocks { + txHashes := make([]common.Hash, len(block.Transactions())) + for j, tx := range block.Transactions() { + txHashes[j] = tx.Hash() + } + if len(txHashes) > 0 { + rawdb.WriteEraTxLookupEntries(idxBatch, block.NumberU64(), txHashes) + } + } + if idxBatch.ValueSize() >= ethdb.IdealBatchSize { + if err := idxBatch.Write(); err != nil { + return fmt.Errorf("error writing tx index batch: %w", err) + } + idxBatch.Reset() + } + imported += len(blocks) if time.Since(reported) >= 8*time.Second { head := blocks[len(blocks)-1].NumberU64() @@ -334,6 +354,7 @@ func ImportHistory(chain *core.BlockChain, dir string, network string, from func return nil } ) + for it.Next() { block, err := it.Block() if err != nil { @@ -357,7 +378,17 @@ func ImportHistory(chain *core.BlockChain, dir string, network string, from func if err := it.Error(); err != nil { return err } - return flush() + if err := flush(); err != nil { + return err + } + + // Flush any remaining index writes and mark this epoch fully indexed. + rawdb.WriteEraIndexTail(idxBatch, uint64(i)) + if err := idxBatch.Write(); err != nil { + return fmt.Errorf("error writing era index tail for epoch %d: %w", i, err) + } + idxBatch.Reset() + return nil }() if err != nil { return err diff --git a/cmd/utils/history_test.go b/cmd/utils/history_test.go index 6631946129..4b94117587 100644 --- a/cmd/utils/history_test.go +++ b/cmd/utils/history_test.go @@ -182,7 +182,7 @@ func TestHistoryImportAndExport(t *testing.T) { if err != nil { t.Fatalf("unable to initialize chain: %v", err) } - if err := ImportHistory(imported, dir, "mainnet", tt.from); err != nil { + if err := ImportHistory(imported, db2, dir, "mainnet", tt.from); err != nil { t.Fatalf("failed to import chain: %v", err) } if have, want := imported.CurrentHeader(), chain.CurrentHeader(); have.Hash() != want.Hash() { diff --git a/core/rawdb/accessors_indexes.go b/core/rawdb/accessors_indexes.go index 8c8c3ec9bb..e8dcbad741 100644 --- a/core/rawdb/accessors_indexes.go +++ b/core/rawdb/accessors_indexes.go @@ -61,7 +61,8 @@ func DecodeTxLookupEntry(data []byte, db ethdb.Reader) *uint64 { func ReadTxLookupEntry(db ethdb.Reader, hash common.Hash) *uint64 { data, _ := db.Get(txLookupKey(hash)) if len(data) == 0 { - return nil + // Fallback: check era-derived index + return ReadEraTxLookupEntry(db, hash) } return DecodeTxLookupEntry(data, db) } @@ -134,6 +135,44 @@ func DeleteAllTxLookupEntries(db ethdb.KeyValueStore, condition func(common.Hash } } +// ReadEraTxLookupEntry retrieves the positional metadata associated with a transaction +// hash from the era1-derived index. +func ReadEraTxLookupEntry(db ethdb.Reader, hash common.Hash) *uint64 { + data, _ := db.Get(eraTxLookupKey(hash)) + if len(data) == 0 { + return nil + } + return DecodeTxLookupEntry(data, db) +} + +// WriteEraTxLookupEntries stores positional metadata for transactions from era1 files, +// enabling hash based transaction and receipt lookups for pruned history. +func WriteEraTxLookupEntries(db ethdb.KeyValueWriter, number uint64, hashes []common.Hash) { + numberBytes := new(big.Int).SetUint64(number).Bytes() + for _, hash := range hashes { + if err := db.Put(eraTxLookupKey(hash), numberBytes); err != nil { + log.Crit("Failed to store era1 transaction lookup entry", "err", err) + } + } +} + +// ReadEraIndexTail retrieves the last fully indexed era1 epoch. +func ReadEraIndexTail(db ethdb.Reader) *uint64 { + data, _ := db.Get(eraIndexTailKey) + if len(data) == 0 { + return nil + } + epoch := binary.BigEndian.Uint64(data) + return &epoch +} + +// WriteEraIndexTail stores the last fully indexed era1 epoch. +func WriteEraIndexTail(db ethdb.KeyValueWriter, epoch uint64) { + if err := db.Put(eraIndexTailKey, encodeBlockNumber(epoch)); err != nil { + log.Crit("Failed to store era1 index tail", "err", err) + } +} + // findTxInBlockBody traverses the given RLP-encoded block body, searching for // the transaction specified by its hash. func findTxInBlockBody(blockbody rlp.RawValue, target common.Hash) (*types.Transaction, uint64, error) { diff --git a/core/rawdb/schema.go b/core/rawdb/schema.go index 54c76143b4..0b5c5b6d55 100644 --- a/core/rawdb/schema.go +++ b/core/rawdb/schema.go @@ -121,6 +121,9 @@ var ( CodePrefix = []byte("c") // CodePrefix + code hash -> account code skeletonHeaderPrefix = []byte("S") // skeletonHeaderPrefix + num (uint64 big endian) -> header + eraTxLookupPrefix = []byte("e") // eraTxLookupPrefix + hash -> transaction/receipt lookup metadata + eraIndexTailKey = []byte("eraIndexTail") // eraIndexTailKey -> last fully indexed epoch + // Path-based storage scheme of merkle patricia trie. TrieNodeAccountPrefix = []byte("A") // TrieNodeAccountPrefix + hexPath -> trie node TrieNodeStoragePrefix = []byte("O") // TrieNodeStoragePrefix + accountHash + hexPath -> trie node @@ -225,6 +228,11 @@ func txLookupKey(hash common.Hash) []byte { return append(txLookupPrefix, hash.Bytes()...) } +// eraTxLookupKey = eraTxLookupPrefix + hash +func eraTxLookupKey(hash common.Hash) []byte { + return append(eraTxLookupPrefix, hash.Bytes()...) +} + // accountSnapshotKey = SnapshotAccountPrefix + hash func accountSnapshotKey(hash common.Hash) []byte { return append(SnapshotAccountPrefix, hash.Bytes()...)