diff --git a/op-chain-ops/cmd/celo-migrate/ancients.go b/op-chain-ops/cmd/celo-migrate/ancients.go index 06542af89c7e..d1291767bc5b 100644 --- a/op-chain-ops/cmd/celo-migrate/ancients.go +++ b/op-chain-ops/cmd/celo-migrate/ancients.go @@ -6,23 +6,12 @@ import ( "fmt" "path/filepath" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" "golang.org/x/sync/errgroup" ) -// RLPBlockRange is a range of blocks in RLP format -type RLPBlockRange struct { - start uint64 - hashes [][]byte - headers [][]byte - bodies [][]byte - receipts [][]byte - tds [][]byte -} - // NewChainFreezer is a small utility method around NewFreezer that sets the // default parameters for the chain storage. func NewChainFreezer(datadir string, namespace string, readonly bool) (*rawdb.Freezer, error) { @@ -39,12 +28,12 @@ func NewChainFreezer(datadir string, namespace string, readonly bool) (*rawdb.Fr return rawdb.NewFreezer(datadir, namespace, readonly, freezerTableSize, chainFreezerNoSnappy) } -func migrateAncientsDb(ctx context.Context, oldDBPath, newDBPath string, batchSize, bufferSize uint64) (numAncientsNewBefore uint64, numAncientsNewAfter uint64, err error) { +func migrateAncientsDb(ctx context.Context, oldDBPath, newDBPath string, batchSize, bufferSize uint64) (numAncientsNewBefore uint64, lastAncient *RLPBlockElement, err error) { defer timer("ancients")() oldFreezer, err := NewChainFreezer(filepath.Join(oldDBPath, "ancient"), "", false) // Can't be readonly because we need the .meta files to be created if err != nil { - return 0, 0, fmt.Errorf("failed to open old freezer: %w", err) + return 0, nil, fmt.Errorf("failed to open old freezer: %w", err) } defer func() { err = errors.Join(err, oldFreezer.Close()) @@ -52,7 +41,7 @@ func migrateAncientsDb(ctx context.Context, oldDBPath, newDBPath string, batchSi newFreezer, err := NewChainFreezer(filepath.Join(newDBPath, "ancient"), "", false) if err != nil { - return 0, 0, fmt.Errorf("failed to open new freezer: %w", err) + return 0, nil, fmt.Errorf("failed to open new freezer: %w", err) } defer func() { err = errors.Join(err, newFreezer.Close()) @@ -60,17 +49,21 @@ func migrateAncientsDb(ctx context.Context, oldDBPath, newDBPath string, batchSi numAncientsOld, err := oldFreezer.Ancients() if err != nil { - return 0, 0, fmt.Errorf("failed to get number of ancients in old freezer: %w", err) + return 0, nil, fmt.Errorf("failed to get number of ancients in old freezer: %w", err) } numAncientsNewBefore, err = newFreezer.Ancients() if err != nil { - return 0, 0, fmt.Errorf("failed to get number of ancients in new freezer: %w", err) + return 0, nil, fmt.Errorf("failed to get number of ancients in new freezer: %w", err) } if numAncientsNewBefore >= numAncientsOld { log.Info("Ancient Block Migration Skipped", "process", "ancients", "ancientsInOldDB", numAncientsOld, "ancientsInNewDB", numAncientsNewBefore) - return numAncientsNewBefore, numAncientsNewBefore, nil + lastAncient, err = loadLastAncient(newFreezer) + if err != nil { + return 0, nil, err + } + return numAncientsNewBefore, lastAncient, nil } log.Info("Ancient Block Migration Started", "process", "ancients", "startBlock", numAncientsNewBefore, "endBlock", numAncientsOld-1, "count", numAncientsOld-numAncientsNewBefore, "step", batchSize) @@ -86,97 +79,116 @@ func migrateAncientsDb(ctx context.Context, oldDBPath, newDBPath string, batchSi g.Go(func() error { return writeAncientBlocks(ctx, newFreezer, transformChan, numAncientsOld) }) if err = g.Wait(); err != nil { - return 0, 0, fmt.Errorf("failed to migrate ancients: %w", err) + return 0, nil, fmt.Errorf("failed to migrate ancients: %w", err) } - numAncientsNewAfter, err = newFreezer.Ancients() + lastAncient, err = loadLastAncient(newFreezer) if err != nil { - return 0, 0, fmt.Errorf("failed to get number of ancients in new freezer: %w", err) + return 0, nil, err } + numAncientsNewAfter := lastAncient.number + 1 if numAncientsNewAfter != numAncientsOld { - return 0, 0, fmt.Errorf("failed to migrate all ancients from old to new db. Expected %d, got %d", numAncientsOld, numAncientsNewAfter) + return 0, nil, fmt.Errorf("failed to migrate all ancients from old to new db. Expected %d, got %d", numAncientsOld, numAncientsNewAfter) } log.Info("Ancient Block Migration Ended", "process", "ancients", "ancientsInOldDB", numAncientsOld, "ancientsInNewDB", numAncientsNewAfter, "migrated", numAncientsNewAfter-numAncientsNewBefore) - return numAncientsNewBefore, numAncientsNewAfter, nil + return numAncientsNewBefore, lastAncient, nil } func readAncientBlocks(ctx context.Context, freezer *rawdb.Freezer, startBlock, endBlock, batchSize uint64, out chan<- RLPBlockRange) error { defer close(out) - for i := startBlock; i < endBlock; i += batchSize { select { case <-ctx.Done(): return ctx.Err() default: - count := min(batchSize, endBlock-i+1) + count := min(batchSize, endBlock-i) start := i - - blockRange := RLPBlockRange{ - start: start, - hashes: make([][]byte, count), - headers: make([][]byte, count), - bodies: make([][]byte, count), - receipts: make([][]byte, count), - tds: make([][]byte, count), + // If we are not at genesis include the last block of + // the previous range so we can check for continuity between ranges. + if start > 0 { + start = start - 1 + count = count + 1 } - var err error - blockRange.hashes, err = freezer.AncientRange(rawdb.ChainFreezerHashTable, start, count, 0) - if err != nil { - return fmt.Errorf("failed to read hashes from old freezer: %w", err) - } - blockRange.headers, err = freezer.AncientRange(rawdb.ChainFreezerHeaderTable, start, count, 0) - if err != nil { - return fmt.Errorf("failed to read headers from old freezer: %w", err) - } - blockRange.bodies, err = freezer.AncientRange(rawdb.ChainFreezerBodiesTable, start, count, 0) - if err != nil { - return fmt.Errorf("failed to read bodies from old freezer: %w", err) - } - blockRange.receipts, err = freezer.AncientRange(rawdb.ChainFreezerReceiptTable, start, count, 0) + blockRange, err := loadAncientRange(freezer, start, count) if err != nil { - return fmt.Errorf("failed to read receipts from old freezer: %w", err) + return fmt.Errorf("failed to load ancient block range: %w", err) } - blockRange.tds, err = freezer.AncientRange(rawdb.ChainFreezerDifficultyTable, start, count, 0) - if err != nil { - return fmt.Errorf("failed to read tds from old freezer: %w", err) + + if err = blockRange.CheckContinuity(nil); err != nil { + return err } - out <- blockRange + if start > 0 { + blockRange.DropFirst() + } + out <- *blockRange } } return nil } +func loadAncientRange(freezer *rawdb.Freezer, start, count uint64) (*RLPBlockRange, error) { + blockRange := &RLPBlockRange{ + start: start, + hashes: make([][]byte, count), + headers: make([][]byte, count), + bodies: make([][]byte, count), + receipts: make([][]byte, count), + tds: make([][]byte, count), + } + + var err error + blockRange.hashes, err = freezer.AncientRange(rawdb.ChainFreezerHashTable, start, count, 0) + if err != nil { + return nil, fmt.Errorf("failed to read hashes from old freezer: %w", err) + } + blockRange.headers, err = freezer.AncientRange(rawdb.ChainFreezerHeaderTable, start, count, 0) + if err != nil { + return nil, fmt.Errorf("failed to read headers from old freezer: %w", err) + } + blockRange.bodies, err = freezer.AncientRange(rawdb.ChainFreezerBodiesTable, start, count, 0) + if err != nil { + return nil, fmt.Errorf("failed to read bodies from old freezer: %w", err) + } + blockRange.receipts, err = freezer.AncientRange(rawdb.ChainFreezerReceiptTable, start, count, 0) + if err != nil { + return nil, fmt.Errorf("failed to read receipts from old freezer: %w", err) + } + blockRange.tds, err = freezer.AncientRange(rawdb.ChainFreezerDifficultyTable, start, count, 0) + if err != nil { + return nil, fmt.Errorf("failed to read tds from old freezer: %w", err) + } + + return blockRange, nil +} + +// Get the last ancient block data so we can check for continuity between ancients and non-ancients +func loadLastAncient(freezer *rawdb.Freezer) (*RLPBlockElement, error) { + numAncients, err := freezer.Ancients() + if err != nil { + return nil, fmt.Errorf("failed to get number of ancients in freezer: %w", err) + } + blockRange, err := loadAncientRange(freezer, numAncients-1, 1) + if err != nil { + return nil, err + } + return blockRange.Element(0) +} + func transformBlocks(ctx context.Context, in <-chan RLPBlockRange, out chan<- RLPBlockRange) error { // Transform blocks from the in channel and send them to the out channel defer close(out) + for blockRange := range in { select { case <-ctx.Done(): return ctx.Err() default: - for i := range blockRange.hashes { - blockNumber := blockRange.start + uint64(i) - - newHeader, err := transformHeader(blockRange.headers[i]) - if err != nil { - return fmt.Errorf("can't transform header: %w", err) - } - newBody, err := transformBlockBody(blockRange.bodies[i]) - if err != nil { - return fmt.Errorf("can't transform body: %w", err) - } - - if yes, newHash := hasSameHash(newHeader, blockRange.hashes[i]); !yes { - log.Error("Hash mismatch", "block", blockNumber, "oldHash", common.BytesToHash(blockRange.hashes[i]), "newHash", newHash) - return fmt.Errorf("hash mismatch at block %d", blockNumber) - } - - blockRange.headers[i] = newHeader - blockRange.bodies[i] = newBody + if err := blockRange.Transform(); err != nil { + return err } out <- blockRange } @@ -216,7 +228,7 @@ func writeAncientBlocks(ctx context.Context, freezer *rawdb.Freezer, in <-chan R return fmt.Errorf("failed to write block range: %w", err) } blockRangeEnd := blockRange.start + uint64(len(blockRange.hashes)) - 1 - log.Info("Wrote ancient blocks", "start", blockRange.start, "end", blockRangeEnd, "count", len(blockRange.hashes), "remaining", totalAncientBlocks-blockRangeEnd) + log.Info("Wrote ancient blocks", "start", blockRange.start, "end", blockRangeEnd, "count", len(blockRange.hashes), "remaining", totalAncientBlocks-(blockRangeEnd+1)) } } return nil diff --git a/op-chain-ops/cmd/celo-migrate/continuity.go b/op-chain-ops/cmd/celo-migrate/continuity.go new file mode 100644 index 000000000000..7f28979ba12f --- /dev/null +++ b/op-chain-ops/cmd/celo-migrate/continuity.go @@ -0,0 +1,140 @@ +package main + +import ( + "errors" + "fmt" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/rlp" +) + +// RLPBlockRange is a range of blocks in RLP format +type RLPBlockRange struct { + start uint64 + hashes [][]byte + headers [][]byte + bodies [][]byte + receipts [][]byte + tds [][]byte +} + +// RLPBlockElement contains all relevant block data in RLP format +type RLPBlockElement struct { + decodedHeader *types.Header + number uint64 + hash []byte + header []byte + body []byte + receipts []byte + td []byte +} + +func (r *RLPBlockRange) Element(i uint64) (*RLPBlockElement, error) { + header := types.Header{} + err := rlp.DecodeBytes(r.headers[i], &header) + if err != nil { + return nil, fmt.Errorf("can't decode header: %w", err) + } + return &RLPBlockElement{ + decodedHeader: &header, + number: r.start + i, // TODO(Alec): how to use this? + hash: r.hashes[i], + header: r.headers[i], + body: r.bodies[i], + receipts: r.receipts[i], + td: r.tds[i], + }, nil +} + +func (r *RLPBlockRange) DropFirst() { + r.start = r.start + 1 + r.hashes = r.hashes[1:] + r.headers = r.headers[1:] + r.bodies = r.bodies[1:] + r.receipts = r.receipts[1:] + r.tds = r.tds[1:] +} + +// CheckContinuity checks if the block data in the range is continuous +// by comparing the header number and parent hash of each block with the previous block, +// and by checking if the number of elements retrieved from each table is the same. +// It takes in a pointer to the last element in the preceding range, and re-assigns it to +// the last element in the current range so that continuity can be checked across ranges. +func (r *RLPBlockRange) CheckContinuity(prevElement *RLPBlockElement) error { + if err := r.CheckLengths(); err != nil { + return err + } + for i := range r.hashes { + currElement, err := r.Element(uint64(i)) + if err != nil { + return err + } + if prevElement != nil { + if err := currElement.Follows(prevElement); err != nil { + return err + } + } + prevElement = currElement + } + return nil +} + +// CheckLengths makes sure the number of elements retrieved from each table is the same +func (r *RLPBlockRange) CheckLengths() error { + var err error + count := len(r.hashes) + // TODO(Alec) should this take in an expected length parameter? + // if len(r.hashes) != count { + // err = fmt.Errorf("Expected count mismatch in block range hashes: expected %d, actual %d", count, len(r.hashes)) + // } + if len(r.bodies) != count { + err = errors.Join(err, fmt.Errorf("Expected count mismatch in block range bodies: expected %d, actual %d", count, len(r.bodies))) + } + if len(r.headers) != count { + err = errors.Join(err, fmt.Errorf("Expected count mismatch in block range headers: expected %d, actual %d", count, len(r.headers))) + } + if len(r.receipts) != count { + err = errors.Join(err, fmt.Errorf("Expected count mismatch in block range receipts: expected %d, actual %d", count, len(r.receipts))) + } + if len(r.tds) != count { + err = errors.Join(err, fmt.Errorf("Expected count mismatch in block range total difficulties: expected %d, actual %d", count, len(r.tds))) + } + return err +} + +// Transform transforms the necessary block data in the range +func (r *RLPBlockRange) Transform() error { + for i := range r.hashes { + blockNumber := r.start + uint64(i) + + newHeader, newBody, err := transform(r.headers[i], r.bodies[i], r.hashes[i], blockNumber) + if err != nil { + return err + } + + r.headers[i] = newHeader + r.bodies[i] = newBody + } + + return nil +} + +// Follows checks if the current block has a number one greater than the previous block +// and if the parent hash of the current block matches the hash of the previous block. +func (e *RLPBlockElement) Follows(prev *RLPBlockElement) error { + if e.Header().Number.Uint64() != prev.Header().Number.Uint64()+1 { + return fmt.Errorf("header number mismatch: expected %d, actual %d", prev.Header().Number.Uint64()+1, e.Header().Number.Uint64()) + } + // We compare the parent hash with the stored hash of the previous block because + // at this point the header object will not calculate the correct hash since it + // first needs to be transformed. + if e.Header().ParentHash != common.Hash(prev.hash) { + return fmt.Errorf("parent hash mismatch between blocks %d and %d", e.Header().Number.Uint64(), prev.Header().Number.Uint64()) + } + return nil +} + +func (e *RLPBlockElement) Header() *types.Header { + return e.decodedHeader +} diff --git a/op-chain-ops/cmd/celo-migrate/db.go b/op-chain-ops/cmd/celo-migrate/db.go index 1449c417bf95..9fa68ed98310 100644 --- a/op-chain-ops/cmd/celo-migrate/db.go +++ b/op-chain-ops/cmd/celo-migrate/db.go @@ -21,7 +21,13 @@ const ( ) var ( - headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header + headerPrefix = []byte("h") // headerPrefix + num (uint64 big endian) + hash -> header + headerTDSuffix = []byte("t") // headerPrefix + num (uint64 big endian) + hash + headerTDSuffix -> td + headerHashSuffix = []byte("n") // headerPrefix + num (uint64 big endian) + headerHashSuffix -> hash + headerNumberPrefix = []byte("H") // headerNumberPrefix + hash -> num (uint64 big endian) + + blockBodyPrefix = []byte("b") // blockBodyPrefix + num (uint64 big endian) + hash -> block body + blockReceiptsPrefix = []byte("r") // blockReceiptsPrefix + num (uint64 big endian) + hash -> block receipts ) // encodeBlockNumber encodes a block number as big endian uint64 @@ -36,6 +42,31 @@ func headerKey(number uint64, hash common.Hash) []byte { return append(append(headerPrefix, encodeBlockNumber(number)...), hash.Bytes()...) } +// headerTDKey = headerPrefix + num (uint64 big endian) + hash + headerTDSuffix +func headerTDKey(number uint64, hash common.Hash) []byte { + return append(headerKey(number, hash), headerTDSuffix...) +} + +// headerHashKey = headerPrefix + num (uint64 big endian) + headerHashSuffix +func headerHashKey(number uint64) []byte { + return append(append(headerPrefix, encodeBlockNumber(number)...), headerHashSuffix...) +} + +// headerNumberKey = headerNumberPrefix + hash +func headerNumberKey(hash common.Hash) []byte { + return append(headerNumberPrefix, hash.Bytes()...) +} + +// blockBodyKey = blockBodyPrefix + num (uint64 big endian) + hash +func blockBodyKey(number uint64, hash common.Hash) []byte { + return append(append(blockBodyPrefix, encodeBlockNumber(number)...), hash.Bytes()...) +} + +// blockReceiptsKey = blockReceiptsPrefix + num (uint64 big endian) + hash +func blockReceiptsKey(number uint64, hash common.Hash) []byte { + return append(append(blockReceiptsPrefix, encodeBlockNumber(number)...), hash.Bytes()...) +} + // Opens a database with access to AncientsDb func openDB(chaindataPath string, readOnly bool) (ethdb.Database, error) { // Will throw an error if the chaindataPath does not exist @@ -61,7 +92,7 @@ func openDB(chaindataPath string, readOnly bool) (ethdb.Database, error) { // Opens a database without access to AncientsDb func openDBWithoutFreezer(chaindataPath string, readOnly bool) (ethdb.Database, error) { - if _, err := os.Stat(chaindataPath); errors.Is(err, os.ErrNotExist) { + if _, err := os.Stat(chaindataPath); err != nil { return nil, err } diff --git a/op-chain-ops/cmd/celo-migrate/main.go b/op-chain-ops/cmd/celo-migrate/main.go index 84f746fd22e0..bc68c6c4761f 100644 --- a/op-chain-ops/cmd/celo-migrate/main.go +++ b/op-chain-ops/cmd/celo-migrate/main.go @@ -218,7 +218,9 @@ func main() { if isSubcommand { return err } - _ = cli.ShowAppHelp(ctx) + if err := cli.ShowAppHelp(ctx); err != nil { + log.Error("failed to show cli help", "err", err) + } return fmt.Errorf("please provide a valid command") }, } @@ -243,14 +245,14 @@ func runFullMigration(opts fullMigrationOptions) error { log.Info("Source db is synced to correct height", "head", head.Number.Uint64(), "migrationBlock", opts.migrationBlockNumber) - var numAncients uint64 var strayAncientBlocks []*rawdb.NumberHash + var lastAncient *RLPBlockElement - if strayAncientBlocks, numAncients, err = runPreMigration(opts.preMigrationOptions); err != nil { + if strayAncientBlocks, lastAncient, err = runPreMigration(opts.preMigrationOptions); err != nil { return fmt.Errorf("failed to run pre-migration: %w", err) } - if err = runNonAncientMigration(opts.newDBPath, strayAncientBlocks, opts.batchSize, numAncients); err != nil { + if err = runNonAncientMigration(opts.newDBPath, strayAncientBlocks, opts.batchSize, lastAncient); err != nil { return fmt.Errorf("failed to run non-ancient migration: %w", err) } if err = runStateMigration(opts.newDBPath, opts.stateMigrationOptions); err != nil { @@ -262,14 +264,14 @@ func runFullMigration(opts fullMigrationOptions) error { return nil } -func runPreMigration(opts preMigrationOptions) ([]*rawdb.NumberHash, uint64, error) { +func runPreMigration(opts preMigrationOptions) ([]*rawdb.NumberHash, *RLPBlockElement, error) { defer timer("pre-migration")() log.Info("Pre-Migration Started", "oldDBPath", opts.oldDBPath, "newDBPath", opts.newDBPath, "batchSize", opts.batchSize, "memoryLimit", opts.memoryLimit) // Check that `rsync` command is available. We use this to copy the db excluding ancients, which we will copy separately if _, err := exec.LookPath("rsync"); err != nil { - return nil, 0, fmt.Errorf("please install `rsync` to run block migration") + return nil, nil, fmt.Errorf("please install `rsync` to run block migration") } debug.SetMemoryLimit(opts.memoryLimit * 1 << 20) // Set memory limit, converting from MiB to bytes @@ -277,21 +279,21 @@ func runPreMigration(opts preMigrationOptions) ([]*rawdb.NumberHash, uint64, err var err error if err = createNewDbPathIfNotExists(opts.newDBPath); err != nil { - return nil, 0, fmt.Errorf("failed to create new db path: %w", err) + return nil, nil, fmt.Errorf("failed to create new db path: %w", err) } if opts.resetNonAncients { if err = cleanupNonAncientDb(opts.newDBPath); err != nil { - return nil, 0, fmt.Errorf("failed to cleanup non-ancient db: %w", err) + return nil, nil, fmt.Errorf("failed to cleanup non-ancient db: %w", err) } } var numAncientsNewBefore uint64 - var numAncientsNewAfter uint64 var strayAncientBlocks []*rawdb.NumberHash + var lastAncient *RLPBlockElement g, ctx := errgroup.WithContext(context.Background()) g.Go(func() error { - if numAncientsNewBefore, numAncientsNewAfter, err = migrateAncientsDb(ctx, opts.oldDBPath, opts.newDBPath, opts.batchSize, opts.bufferSize); err != nil { + if numAncientsNewBefore, lastAncient, err = migrateAncientsDb(ctx, opts.oldDBPath, opts.newDBPath, opts.batchSize, opts.bufferSize); err != nil { return fmt.Errorf("failed to migrate ancients database: %w", err) } // Scanning for stray ancient blocks is slow, so we do it as soon as we can after the lock on oldDB is released by migrateAncientsDb @@ -307,15 +309,17 @@ func runPreMigration(opts preMigrationOptions) ([]*rawdb.NumberHash, uint64, err }) if err = g.Wait(); err != nil { - return nil, 0, fmt.Errorf("failed to migrate blocks: %w", err) + return nil, nil, fmt.Errorf("failed to migrate blocks: %w", err) } + numAncientsNewAfter := lastAncient.number + 1 + log.Info("Pre-Migration Finished", "oldDBPath", opts.oldDBPath, "newDBPath", opts.newDBPath, "migratedAncients", numAncientsNewAfter-numAncientsNewBefore, "strayAncientBlocks", len(strayAncientBlocks)) - return strayAncientBlocks, numAncientsNewAfter, nil + return strayAncientBlocks, lastAncient, nil } -func runNonAncientMigration(newDBPath string, strayAncientBlocks []*rawdb.NumberHash, batchSize, numAncients uint64) (err error) { +func runNonAncientMigration(newDBPath string, strayAncientBlocks []*rawdb.NumberHash, batchSize uint64, lastAncient *RLPBlockElement) (err error) { defer timer("non-ancient migration")() newDB, err := openDBWithoutFreezer(newDBPath, false) @@ -329,12 +333,11 @@ func runNonAncientMigration(newDBPath string, strayAncientBlocks []*rawdb.Number // get the last block number hash := rawdb.ReadHeadHeaderHash(newDB) lastBlock := *rawdb.ReadHeaderNumber(newDB, hash) - lastAncient := numAncients - 1 - log.Info("Non-Ancient Block Migration Started", "process", "non-ancients", "newDBPath", newDBPath, "batchSize", batchSize, "startBlock", numAncients, "endBlock", lastBlock, "count", lastBlock-lastAncient, "lastAncientBlock", lastAncient) + log.Info("Non-Ancient Block Migration Started", "process", "non-ancients", "newDBPath", newDBPath, "batchSize", batchSize, "startBlock", lastAncient.number+1, "endBlock", lastBlock, "count", lastBlock-lastAncient.number, "lastAncientBlock", lastAncient.number) var numNonAncients uint64 - if numNonAncients, err = migrateNonAncientsDb(newDB, lastBlock, numAncients, batchSize); err != nil { + if numNonAncients, err = migrateNonAncientsDb(newDB, lastBlock, batchSize, lastAncient); err != nil { return fmt.Errorf("failed to migrate non-ancients database: %w", err) } diff --git a/op-chain-ops/cmd/celo-migrate/non-ancients.go b/op-chain-ops/cmd/celo-migrate/non-ancients.go index 44843d2080cd..a5692ac91344 100644 --- a/op-chain-ops/cmd/celo-migrate/non-ancients.go +++ b/op-chain-ops/cmd/celo-migrate/non-ancients.go @@ -62,61 +62,119 @@ func copyDbExceptAncients(oldDbPath, newDbPath string) error { return nil } -func migrateNonAncientsDb(newDB ethdb.Database, lastBlock, numAncients, batchSize uint64) (uint64, error) { +func migrateNonAncientsDb(newDB ethdb.Database, lastBlock, batchSize uint64, lastAncient *RLPBlockElement) (uint64, error) { defer timer("migrateNonAncientsDb")() - // Delete bad blocks, we could migrate them, but we have no need of the historical bad blocks. AFAICS bad blocks + // Delete bad blocks, we could migrate them, but we have no need for the historical bad blocks. AFAICS bad blocks // are stored solely so that they can be retrieved or traced via the debug API, but we are no longer interested // in these old bad blocks. rawdb.DeleteBadBlocks(newDB) - // The genesis block is the only block that should remain stored in the non-ancient db even after it is frozen. - if numAncients > 0 { + if lastAncient != nil { + // The genesis block is the only block that should remain stored in the non-ancient db even after it is frozen. log.Info("Migrating genesis block in non-ancient db", "process", "non-ancients") - if err := migrateNonAncientBlock(0, rawdb.ReadCanonicalHash(newDB, 0), newDB); err != nil { + if err := migrateNonAncientBlocks(newDB, 0, 1, nil); err != nil { return 0, err } } - for i := numAncients; i <= lastBlock; i += batchSize { - numbersHash := rawdb.ReadAllHashesInRange(newDB, i, i+batchSize-1) - - log.Info("Processing Block Range", "process", "non-ancients", "from", i, "to(inclusve)", i+batchSize-1, "count", len(numbersHash)) - for _, numberHash := range numbersHash { - if err := migrateNonAncientBlock(numberHash.Number, numberHash.Hash, newDB); err != nil { - return 0, err - } + prevBlockElement := *lastAncient + for i := lastAncient.number + 1; i <= lastBlock; i += batchSize { + if err := migrateNonAncientBlocks(newDB, i, batchSize, &prevBlockElement); err != nil { + return 0, err } } - migratedCount := lastBlock - numAncients + 1 + migratedCount := lastBlock - lastAncient.number return migratedCount, nil } -func migrateNonAncientBlock(number uint64, hash common.Hash, newDB ethdb.Database) error { - // read header and body - header := rawdb.ReadHeaderRLP(newDB, hash, number) - body := rawdb.ReadBodyRLP(newDB, hash, number) +func migrateNonAncientBlocks(newDB ethdb.Database, start, count uint64, prevBlockElement *RLPBlockElement) error { + log.Info("Processing Block Range", "process", "non-ancients", "from", start, "to(inclusve)", start+count-1, "count", count) - // transform header and body - newHeader, err := transformHeader(header) + blockRange, err := loadNonAncientRange(newDB, start, count) if err != nil { - return fmt.Errorf("failed to transform header: block %d - %x: %w", number, hash, err) + return err } - newBody, err := transformBlockBody(body) - if err != nil { - return fmt.Errorf("failed to transform body: block %d - %x: %w", number, hash, err) + if err = blockRange.CheckContinuity(prevBlockElement); err != nil { + return fmt.Errorf("failed continuity check for non-ancient blocks: %w", err) + } + if err = blockRange.Transform(); err != nil { + return err + } + if err = writeNonAncientBlockRange(newDB, blockRange); err != nil { + return err + } + + return nil +} + +func loadNonAncientRange(newDB ethdb.Database, start, count uint64) (*RLPBlockRange, error) { + blockRange := &RLPBlockRange{ + start: start, + hashes: make([][]byte, count), + headers: make([][]byte, count), + bodies: make([][]byte, count), + receipts: make([][]byte, count), + tds: make([][]byte, count), } - if yes, newHash := hasSameHash(newHeader, hash[:]); !yes { - log.Error("Hash mismatch", "block", number, "oldHash", hash, "newHash", newHash) - return fmt.Errorf("hash mismatch at block %d - %x", number, hash) + numbersHash := rawdb.ReadAllHashesInRange(newDB, start, start+count-1) // minus 1 because start is included in count + + var err error + + for i, numberHash := range numbersHash { + // TODO(Alec) + // numberRLP, err := newDB.Get(headerNumberKey(hash)) + // if err != nil { + // return nil, fmt.Errorf("failed to find number for hash in newDB leveldb block %d - %x: %w", number, hash, err) + // } + // e.number = binary.BigEndian.Uint64(numberRLP) + + number := numberHash.Number + hash := numberHash.Hash + + blockRange.hashes[i], err = newDB.Get(headerHashKey(number)) + if err != nil { + return nil, fmt.Errorf("failed to find canonical hash in newDB leveldb: block %d - %x: %w", number, hash, err) + } + blockRange.headers[i], err = newDB.Get(headerKey(number, hash)) + if err != nil { + return nil, fmt.Errorf("failed to read header: block %d - %x: %w", number, hash, err) + } + blockRange.bodies[i], err = newDB.Get(blockBodyKey(number, hash)) + if err != nil { + return nil, fmt.Errorf("failed to read body: block %d - %x: %w", number, hash, err) + } + blockRange.receipts[i], err = newDB.Get(blockReceiptsKey(number, hash)) + if err != nil { + return nil, fmt.Errorf("failed to find receipts in newDB leveldb: block %d - %x: %w", number, hash, err) + } + blockRange.tds[i], err = newDB.Get(headerTDKey(number, hash)) + if err != nil { + return nil, fmt.Errorf("failed to find total difficulty in newDB leveldb: block %d - %x: %w", number, hash, err) + } + + // TODO(Alec) preserve these checks? + // if !bytes.Equal(hashFromDB, hash[:]) { + // return fmt.Errorf("canonical hash mismatch in newDB leveldb: block %d - %x: %w", number, hash, err) + // } + + // if !bytes.Equal(numberFromDB, encodeBlockNumber(number)) { + // log.Error("Number for hash mismatch", "block", number, "numberFromDB", numberFromDB, "hash", hash) + // return fmt.Errorf("number for hash mismatch in newDB leveldb: block %d - %x: %w", number, hash, err) + // } } - // write header and body + return blockRange, nil +} + +// write transformed header and body to newDB +func writeNonAncientBlock(newDB ethdb.Database, header, body, hashBytes []byte, number uint64) error { + hash := common.BytesToHash(hashBytes) batch := newDB.NewBatch() - rawdb.WriteBodyRLP(batch, hash, number, newBody) - if err := batch.Put(headerKey(number, hash), newHeader); err != nil { + rawdb.WriteBodyRLP(batch, hash, number, body) + if err := batch.Put(headerKey(number, hash), header); err != nil { return fmt.Errorf("failed to write header: block %d - %x: %w", number, hash, err) } if err := batch.Write(); err != nil { @@ -125,3 +183,12 @@ func migrateNonAncientBlock(number uint64, hash common.Hash, newDB ethdb.Databas return nil } + +func writeNonAncientBlockRange(newDB ethdb.Database, blockRange *RLPBlockRange) error { + for i := range blockRange.hashes { + if err := writeNonAncientBlock(newDB, blockRange.headers[i], blockRange.bodies[i], blockRange.hashes[i], blockRange.start+uint64(i)); err != nil { + return err + } + } + return nil +} diff --git a/op-chain-ops/cmd/celo-migrate/transform.go b/op-chain-ops/cmd/celo-migrate/transform.go index 5a80e8a51566..f848dde6c957 100644 --- a/op-chain-ops/cmd/celo-migrate/transform.go +++ b/op-chain-ops/cmd/celo-migrate/transform.go @@ -77,6 +77,24 @@ func hasSameHash(newHeader, oldHash []byte) (bool, common.Hash) { return bytes.Equal(oldHash, newHash.Bytes()), newHash } +// checkTransformedHeader checks that the transformed header has the same hash and block number as expected +func checkTransformedHeader(header, expectedHash []byte, expectedNumber uint64) error { + // Check that transformed header has the same hash + if yes, newHash := hasSameHash(header, expectedHash); !yes { + return fmt.Errorf("hash mismatch after transform at block %d - %x. Expected %d, actual %d", expectedNumber, expectedHash, expectedHash, newHash) + } + // Check that transformed header has the same block number + headerDecoded := new(types.Header) + if err := rlp.DecodeBytes(header, &headerDecoded); err != nil { + return err + } + if headerDecoded.Number.Uint64() != expectedNumber { + return fmt.Errorf("block number mismatch after transform at block %d - %x. Expected %d, actual %d", expectedNumber, expectedHash, expectedNumber, headerDecoded.Number.Uint64()) + } + + return nil +} + // transformBlockBody migrates the block body from the old format to the new format (works with []byte input output) func transformBlockBody(oldBodyData []byte) ([]byte, error) { // decode body into celo-blockchain Body structure @@ -107,3 +125,20 @@ func transformBlockBody(oldBodyData []byte) ([]byte, error) { return newBodyData, nil } + +// transform header and body +func transform(header, body, hash []byte, number uint64) (newHeader []byte, newBody []byte, err error) { + newHeader, err = transformHeader(header) + if err != nil { + return nil, nil, fmt.Errorf("failed to transform header: block %d - %x: %w", number, hash, err) + } + if err = checkTransformedHeader(newHeader, hash[:], number); err != nil { + return nil, nil, err + } + newBody, err = transformBlockBody(body) + if err != nil { + return nil, nil, fmt.Errorf("failed to transform body: block %d - %x: %w", number, hash, err) + } + + return newHeader, newBody, nil +}