core/rawdb: simple legacy receipt converter (#24028)
* cmd,core: add simple legacy receipt converter core/rawdb: use forEach in migrate core/rawdb: batch reads in forEach core/rawdb: make forEach anonymous fn cmd/geth: check for legacy receipts on node startup fix err msg Co-authored-by: rjl493456442 <garyrong0905@gmail.com> fix log Co-authored-by: rjl493456442 <garyrong0905@gmail.com> fix some review comments add warning to cmd drop isLegacy fn from migrateTable params add test for windows rename test replacing in windows case * minor fix * sanity check for tail-deletion * add log before moving files around * speed-up hack for mainnet * fix mainnet check, use networkid instead * check mainnet genesis * review fixes * resume previous migration attempt * core/rawdb: lint fix Co-authored-by: Martin Holst Swende <martin@swende.se>
This commit is contained in:
@ -145,6 +145,12 @@ func (db *nofreezedb) ReadAncients(fn func(reader ethdb.AncientReader) error) (e
|
||||
return fn(db)
|
||||
}
|
||||
|
||||
// MigrateTable processes the entries in a given table in sequence
|
||||
// converting them to a new format if they're of an old format.
|
||||
func (db *nofreezedb) MigrateTable(kind string, convert convertLegacyFn) error {
|
||||
return errNotSupported
|
||||
}
|
||||
|
||||
// NewDatabase creates a high level database on top of a given key-value data
|
||||
// store without a freezer moving immutable chain segments into cold storage.
|
||||
func NewDatabase(db ethdb.KeyValueStore) ethdb.Database {
|
||||
|
@ -19,6 +19,7 @@ package rawdb
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math"
|
||||
"os"
|
||||
"path/filepath"
|
||||
@ -617,3 +618,116 @@ func (f *freezer) freezeRange(nfdb *nofreezedb, number, limit uint64) (hashes []
|
||||
|
||||
return hashes, err
|
||||
}
|
||||
|
||||
// convertLegacyFn takes a raw freezer entry in an older format and
|
||||
// returns it in the new format.
|
||||
type convertLegacyFn = func([]byte) ([]byte, error)
|
||||
|
||||
// MigrateTable processes the entries in a given table in sequence
|
||||
// converting them to a new format if they're of an old format.
|
||||
func (f *freezer) MigrateTable(kind string, convert convertLegacyFn) error {
|
||||
if f.readonly {
|
||||
return errReadOnly
|
||||
}
|
||||
f.writeLock.Lock()
|
||||
defer f.writeLock.Unlock()
|
||||
|
||||
table, ok := f.tables[kind]
|
||||
if !ok {
|
||||
return errUnknownTable
|
||||
}
|
||||
// forEach iterates every entry in the table serially and in order, calling `fn`
|
||||
// with the item as argument. If `fn` returns an error the iteration stops
|
||||
// and that error will be returned.
|
||||
forEach := func(t *freezerTable, offset uint64, fn func(uint64, []byte) error) error {
|
||||
var (
|
||||
items = atomic.LoadUint64(&t.items)
|
||||
batchSize = uint64(1024)
|
||||
maxBytes = uint64(1024 * 1024)
|
||||
)
|
||||
for i := offset; i < items; {
|
||||
if i+batchSize > items {
|
||||
batchSize = items - i
|
||||
}
|
||||
data, err := t.RetrieveItems(i, batchSize, maxBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for j, item := range data {
|
||||
if err := fn(i+uint64(j), item); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
i += uint64(len(data))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
// TODO(s1na): This is a sanity-check since as of now no process does tail-deletion. But the migration
|
||||
// process assumes no deletion at tail and needs to be modified to account for that.
|
||||
if table.itemOffset > 0 || table.itemHidden > 0 {
|
||||
return fmt.Errorf("migration not supported for tail-deleted freezers")
|
||||
}
|
||||
ancientsPath := filepath.Dir(table.index.Name())
|
||||
// Set up new dir for the migrated table, the content of which
|
||||
// we'll at the end move over to the ancients dir.
|
||||
migrationPath := filepath.Join(ancientsPath, "migration")
|
||||
newTable, err := NewFreezerTable(migrationPath, kind, FreezerNoSnappy[kind], false)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
var (
|
||||
batch = newTable.newBatch()
|
||||
out []byte
|
||||
start = time.Now()
|
||||
logged = time.Now()
|
||||
offset = newTable.items
|
||||
)
|
||||
if offset > 0 {
|
||||
log.Info("found previous migration attempt", "migrated", offset)
|
||||
}
|
||||
// Iterate through entries and transform them
|
||||
if err := forEach(table, offset, func(i uint64, blob []byte) error {
|
||||
if i%10000 == 0 && time.Since(logged) > 16*time.Second {
|
||||
log.Info("Processing legacy elements", "count", i, "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
logged = time.Now()
|
||||
}
|
||||
out, err = convert(blob)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := batch.AppendRaw(i, out); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := batch.commit(); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Info("Replacing old table files with migrated ones", "elapsed", common.PrettyDuration(time.Since(start)))
|
||||
// Release and delete old table files. Note this won't
|
||||
// delete the index file.
|
||||
table.releaseFilesAfter(0, true)
|
||||
|
||||
if err := newTable.Close(); err != nil {
|
||||
return err
|
||||
}
|
||||
files, err := ioutil.ReadDir(migrationPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// Move migrated files to ancients dir.
|
||||
for _, f := range files {
|
||||
// This will replace the old index file as a side-effect.
|
||||
if err := os.Rename(filepath.Join(migrationPath, f.Name()), filepath.Join(ancientsPath, f.Name())); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// Delete by now empty dir.
|
||||
if err := os.Remove(migrationPath); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import (
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
@ -337,3 +338,92 @@ func checkAncientCount(t *testing.T, f *freezer, kind string, n uint64) {
|
||||
t.Errorf("Ancient(%q, %d) returned unexpected error %q", kind, index, err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRenameWindows(t *testing.T) {
|
||||
var (
|
||||
fname = "file.bin"
|
||||
fname2 = "file2.bin"
|
||||
data = []byte{1, 2, 3, 4}
|
||||
data2 = []byte{2, 3, 4, 5}
|
||||
data3 = []byte{3, 5, 6, 7}
|
||||
dataLen = 4
|
||||
)
|
||||
|
||||
// Create 2 temp dirs
|
||||
dir1, err := os.MkdirTemp("", "rename-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(dir1)
|
||||
dir2, err := os.MkdirTemp("", "rename-test")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(dir2)
|
||||
|
||||
// Create file in dir1 and fill with data
|
||||
f, err := os.Create(path.Join(dir1, fname))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f2, err := os.Create(path.Join(dir1, fname2))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
f3, err := os.Create(path.Join(dir2, fname2))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := f.Write(data); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := f2.Write(data2); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := f3.Write(data3); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := f.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := f2.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := f3.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Rename(f.Name(), path.Join(dir2, fname)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := os.Rename(f2.Name(), path.Join(dir2, fname2)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Check file contents
|
||||
f, err = os.Open(path.Join(dir2, fname))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
defer os.Remove(f.Name())
|
||||
buf := make([]byte, dataLen)
|
||||
if _, err := f.Read(buf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(buf, data) {
|
||||
t.Errorf("unexpected file contents. Got %v\n", buf)
|
||||
}
|
||||
|
||||
f, err = os.Open(path.Join(dir2, fname2))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer f.Close()
|
||||
defer os.Remove(f.Name())
|
||||
if _, err := f.Read(buf); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !bytes.Equal(buf, data2) {
|
||||
t.Errorf("unexpected file contents. Got %v\n", buf)
|
||||
}
|
||||
}
|
||||
|
@ -113,6 +113,12 @@ func (t *table) Sync() error {
|
||||
return t.db.Sync()
|
||||
}
|
||||
|
||||
// MigrateTable processes the entries in a given table in sequence
|
||||
// converting them to a new format if they're of an old format.
|
||||
func (t *table) MigrateTable(kind string, convert convertLegacyFn) error {
|
||||
return t.db.MigrateTable(kind, convert)
|
||||
}
|
||||
|
||||
// Put inserts the given value into the database at a prefixed version of the
|
||||
// provided key.
|
||||
func (t *table) Put(key []byte, value []byte) error {
|
||||
|
53
core/types/legacy.go
Normal file
53
core/types/legacy.go
Normal file
@ -0,0 +1,53 @@
|
||||
// Copyright 2021 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package types
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/ethereum/go-ethereum/rlp"
|
||||
)
|
||||
|
||||
// IsLegacyStoredReceipts tries to parse the RLP-encoded blob
|
||||
// first as an array of v3 stored receipt, then v4 stored receipt and
|
||||
// returns true if successful.
|
||||
func IsLegacyStoredReceipts(raw []byte) (bool, error) {
|
||||
var v3 []v3StoredReceiptRLP
|
||||
if err := rlp.DecodeBytes(raw, &v3); err == nil {
|
||||
return true, nil
|
||||
}
|
||||
var v4 []v4StoredReceiptRLP
|
||||
if err := rlp.DecodeBytes(raw, &v4); err == nil {
|
||||
return true, nil
|
||||
}
|
||||
var v5 []storedReceiptRLP
|
||||
// Check to see valid fresh stored receipt
|
||||
if err := rlp.DecodeBytes(raw, &v5); err == nil {
|
||||
return false, nil
|
||||
}
|
||||
return false, errors.New("value is not a valid receipt encoding")
|
||||
}
|
||||
|
||||
// ConvertLegacyStoredReceipts takes the RLP encoding of an array of legacy
|
||||
// stored receipts and returns a fresh RLP-encoded stored receipt.
|
||||
func ConvertLegacyStoredReceipts(raw []byte) ([]byte, error) {
|
||||
var receipts []ReceiptForStorage
|
||||
if err := rlp.DecodeBytes(raw, &receipts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return rlp.EncodeToBytes(&receipts)
|
||||
}
|
Reference in New Issue
Block a user