From 469727ce5855e48083453709de432a1b9ffe220d Mon Sep 17 00:00:00 2001 From: alecps Date: Mon, 9 Dec 2024 15:21:41 -0500 Subject: [PATCH 01/24] check for gaps in block numbers and throw if found during migration --- op-chain-ops/cmd/celo-migrate/ancients.go | 8 ++++++++ op-chain-ops/cmd/celo-migrate/non-ancients.go | 7 +++++++ 2 files changed, 15 insertions(+) diff --git a/op-chain-ops/cmd/celo-migrate/ancients.go b/op-chain-ops/cmd/celo-migrate/ancients.go index 06542af89c7e..076430bc1c4a 100644 --- a/op-chain-ops/cmd/celo-migrate/ancients.go +++ b/op-chain-ops/cmd/celo-migrate/ancients.go @@ -153,6 +153,9 @@ func readAncientBlocks(ctx context.Context, freezer *rawdb.Freezer, startBlock, 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) + + prevBlockNumber := uint64(0) + for blockRange := range in { select { case <-ctx.Done(): @@ -161,6 +164,11 @@ func transformBlocks(ctx context.Context, in <-chan RLPBlockRange, out chan<- RL for i := range blockRange.hashes { blockNumber := blockRange.start + uint64(i) + if blockNumber != 0 && blockNumber != prevBlockNumber+1 { + return fmt.Errorf("Gap found between ancient blocks numbered %d and %d. Please delete the target directory and repeat the migration with an uncorrupted source directory.", prevBlockNumber, blockNumber) + } + prevBlockNumber = blockNumber + newHeader, err := transformHeader(blockRange.headers[i]) if err != nil { return fmt.Errorf("can't transform header: %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..c20a3324bd2e 100644 --- a/op-chain-ops/cmd/celo-migrate/non-ancients.go +++ b/op-chain-ops/cmd/celo-migrate/non-ancients.go @@ -78,11 +78,18 @@ func migrateNonAncientsDb(newDB ethdb.Database, lastBlock, numAncients, batchSiz } } + prevBlockNumber := uint64(numAncients - 1) + 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 numberHash.Number != 0 && numberHash.Number != prevBlockNumber+1 { + return 0, fmt.Errorf("Gap found between non-ancient blocks numbered %d and %d. Please delete the target directory and repeat the migration with an uncorrupted source directory.", prevBlockNumber, numberHash.Number) + } + prevBlockNumber = numberHash.Number + if err := migrateNonAncientBlock(numberHash.Number, numberHash.Hash, newDB); err != nil { return 0, err } From 93991293a0fef689d51f94494f2cef55ed4b929f Mon Sep 17 00:00:00 2001 From: alecps Date: Tue, 10 Dec 2024 15:07:12 -0500 Subject: [PATCH 02/24] fix err msg nit --- op-chain-ops/cmd/celo-migrate/ancients.go | 2 +- op-chain-ops/cmd/celo-migrate/non-ancients.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/op-chain-ops/cmd/celo-migrate/ancients.go b/op-chain-ops/cmd/celo-migrate/ancients.go index 076430bc1c4a..406a7bf48714 100644 --- a/op-chain-ops/cmd/celo-migrate/ancients.go +++ b/op-chain-ops/cmd/celo-migrate/ancients.go @@ -165,7 +165,7 @@ func transformBlocks(ctx context.Context, in <-chan RLPBlockRange, out chan<- RL blockNumber := blockRange.start + uint64(i) if blockNumber != 0 && blockNumber != prevBlockNumber+1 { - return fmt.Errorf("Gap found between ancient blocks numbered %d and %d. Please delete the target directory and repeat the migration with an uncorrupted source directory.", prevBlockNumber, blockNumber) + return fmt.Errorf("gap found between ancient blocks numbered %d and %d. Please delete the target directory and repeat the migration with an uncorrupted source directory.", prevBlockNumber, blockNumber) } prevBlockNumber = blockNumber diff --git a/op-chain-ops/cmd/celo-migrate/non-ancients.go b/op-chain-ops/cmd/celo-migrate/non-ancients.go index c20a3324bd2e..544cd3d027ec 100644 --- a/op-chain-ops/cmd/celo-migrate/non-ancients.go +++ b/op-chain-ops/cmd/celo-migrate/non-ancients.go @@ -86,7 +86,7 @@ func migrateNonAncientsDb(newDB ethdb.Database, lastBlock, numAncients, batchSiz log.Info("Processing Block Range", "process", "non-ancients", "from", i, "to(inclusve)", i+batchSize-1, "count", len(numbersHash)) for _, numberHash := range numbersHash { if numberHash.Number != 0 && numberHash.Number != prevBlockNumber+1 { - return 0, fmt.Errorf("Gap found between non-ancient blocks numbered %d and %d. Please delete the target directory and repeat the migration with an uncorrupted source directory.", prevBlockNumber, numberHash.Number) + return 0, fmt.Errorf("gap found between non-ancient blocks numbered %d and %d. Please delete the target directory and repeat the migration with an uncorrupted source directory.", prevBlockNumber, numberHash.Number) } prevBlockNumber = numberHash.Number From 31b6ba3f0bb4b0ca6536c0110e56e6c9559837d0 Mon Sep 17 00:00:00 2001 From: alecps Date: Tue, 10 Dec 2024 15:19:55 -0500 Subject: [PATCH 03/24] add comment on contiguous blocks --- op-chain-ops/cmd/celo-migrate/ancients.go | 1 + 1 file changed, 1 insertion(+) diff --git a/op-chain-ops/cmd/celo-migrate/ancients.go b/op-chain-ops/cmd/celo-migrate/ancients.go index 406a7bf48714..7760a5560eb9 100644 --- a/op-chain-ops/cmd/celo-migrate/ancients.go +++ b/op-chain-ops/cmd/celo-migrate/ancients.go @@ -167,6 +167,7 @@ func transformBlocks(ctx context.Context, in <-chan RLPBlockRange, out chan<- RL if blockNumber != 0 && blockNumber != prevBlockNumber+1 { return fmt.Errorf("gap found between ancient blocks numbered %d and %d. Please delete the target directory and repeat the migration with an uncorrupted source directory.", prevBlockNumber, blockNumber) } + // Block ranges are contiguous and in order because they are read sequentially from the freezer prevBlockNumber = blockNumber newHeader, err := transformHeader(blockRange.headers[i]) From 20737a8335bac1ae0fe90a829fa6c8133d4fadf7 Mon Sep 17 00:00:00 2001 From: alecps Date: Thu, 12 Dec 2024 15:21:11 -0500 Subject: [PATCH 04/24] fix for re-running pre-migration --- op-chain-ops/cmd/celo-migrate/ancients.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/op-chain-ops/cmd/celo-migrate/ancients.go b/op-chain-ops/cmd/celo-migrate/ancients.go index 7760a5560eb9..926700247b44 100644 --- a/op-chain-ops/cmd/celo-migrate/ancients.go +++ b/op-chain-ops/cmd/celo-migrate/ancients.go @@ -82,7 +82,7 @@ func migrateAncientsDb(ctx context.Context, oldDBPath, newDBPath string, batchSi g.Go(func() error { return readAncientBlocks(ctx, oldFreezer, numAncientsNewBefore, numAncientsOld, batchSize, readChan) }) - g.Go(func() error { return transformBlocks(ctx, readChan, transformChan) }) + g.Go(func() error { return transformBlocks(ctx, readChan, transformChan, numAncientsNewBefore) }) g.Go(func() error { return writeAncientBlocks(ctx, newFreezer, transformChan, numAncientsOld) }) if err = g.Wait(); err != nil { @@ -150,11 +150,11 @@ func readAncientBlocks(ctx context.Context, freezer *rawdb.Freezer, startBlock, return nil } -func transformBlocks(ctx context.Context, in <-chan RLPBlockRange, out chan<- RLPBlockRange) error { +func transformBlocks(ctx context.Context, in <-chan RLPBlockRange, out chan<- RLPBlockRange, startBlock uint64) error { // Transform blocks from the in channel and send them to the out channel defer close(out) - prevBlockNumber := uint64(0) + prevBlockNumber := startBlock - 1 for blockRange := range in { select { From 7cca9e80b4d3687b1b0a2f7063a874c98155a2f4 Mon Sep 17 00:00:00 2001 From: Alec Schaefer Date: Fri, 13 Dec 2024 13:58:06 -0500 Subject: [PATCH 05/24] Remove redundant zero check Co-authored-by: piersy --- op-chain-ops/cmd/celo-migrate/ancients.go | 2 +- op-chain-ops/cmd/celo-migrate/non-ancients.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/op-chain-ops/cmd/celo-migrate/ancients.go b/op-chain-ops/cmd/celo-migrate/ancients.go index 926700247b44..162fba0813fc 100644 --- a/op-chain-ops/cmd/celo-migrate/ancients.go +++ b/op-chain-ops/cmd/celo-migrate/ancients.go @@ -164,7 +164,7 @@ func transformBlocks(ctx context.Context, in <-chan RLPBlockRange, out chan<- RL for i := range blockRange.hashes { blockNumber := blockRange.start + uint64(i) - if blockNumber != 0 && blockNumber != prevBlockNumber+1 { + if blockNumber != prevBlockNumber+1 { return fmt.Errorf("gap found between ancient blocks numbered %d and %d. Please delete the target directory and repeat the migration with an uncorrupted source directory.", prevBlockNumber, blockNumber) } // Block ranges are contiguous and in order because they are read sequentially from the freezer diff --git a/op-chain-ops/cmd/celo-migrate/non-ancients.go b/op-chain-ops/cmd/celo-migrate/non-ancients.go index 544cd3d027ec..3bc57d3bde65 100644 --- a/op-chain-ops/cmd/celo-migrate/non-ancients.go +++ b/op-chain-ops/cmd/celo-migrate/non-ancients.go @@ -85,7 +85,7 @@ func migrateNonAncientsDb(newDB ethdb.Database, lastBlock, numAncients, batchSiz log.Info("Processing Block Range", "process", "non-ancients", "from", i, "to(inclusve)", i+batchSize-1, "count", len(numbersHash)) for _, numberHash := range numbersHash { - if numberHash.Number != 0 && numberHash.Number != prevBlockNumber+1 { + if numberHash.Number != prevBlockNumber+1 { return 0, fmt.Errorf("gap found between non-ancient blocks numbered %d and %d. Please delete the target directory and repeat the migration with an uncorrupted source directory.", prevBlockNumber, numberHash.Number) } prevBlockNumber = numberHash.Number From bbb403513b9d16750293d3e9012c89e69e4ff40e Mon Sep 17 00:00:00 2001 From: alecps Date: Tue, 17 Dec 2024 13:49:45 -0500 Subject: [PATCH 06/24] drive by: add error checking in misc places where it was missed --- op-chain-ops/cmd/celo-migrate/main.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/op-chain-ops/cmd/celo-migrate/main.go b/op-chain-ops/cmd/celo-migrate/main.go index 84f746fd22e0..115315cfd672 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") }, } From e7ec3640690cf75fccc3b9a252cdd857abb4dacc Mon Sep 17 00:00:00 2001 From: alecps Date: Tue, 17 Dec 2024 13:54:11 -0500 Subject: [PATCH 07/24] add leveldb keys for non-transformed data --- op-chain-ops/cmd/celo-migrate/db.go | 33 ++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/op-chain-ops/cmd/celo-migrate/db.go b/op-chain-ops/cmd/celo-migrate/db.go index 1449c417bf95..40775c3d7b1b 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 From a0aad1eef8e96eafa5578d9612e6a225fb37a5cf Mon Sep 17 00:00:00 2001 From: alecps Date: Tue, 17 Dec 2024 13:55:53 -0500 Subject: [PATCH 08/24] check for header and body only in leveldb when migrating non-ancients --- op-chain-ops/cmd/celo-migrate/non-ancients.go | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/op-chain-ops/cmd/celo-migrate/non-ancients.go b/op-chain-ops/cmd/celo-migrate/non-ancients.go index 3bc57d3bde65..6e0dcbba4ccb 100644 --- a/op-chain-ops/cmd/celo-migrate/non-ancients.go +++ b/op-chain-ops/cmd/celo-migrate/non-ancients.go @@ -102,8 +102,14 @@ func migrateNonAncientsDb(newDB ethdb.Database, lastBlock, numAncients, batchSiz 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) + header, err := newDB.Get(headerKey(number, hash)) + if err != nil { + return fmt.Errorf("failed to read header: block %d - %x: %w", number, hash, err) + } + body, err := newDB.Get(blockBodyKey(number, hash)) + if err != nil { + return fmt.Errorf("failed to read body: block %d - %x: %w", number, hash, err) + } // transform header and body newHeader, err := transformHeader(header) From f59e3bb824e69d3f29e7591d8ea3b5716658b23b Mon Sep 17 00:00:00 2001 From: alecps Date: Tue, 17 Dec 2024 13:57:51 -0500 Subject: [PATCH 09/24] add check that transformed header has the expected block number --- op-chain-ops/cmd/celo-migrate/non-ancients.go | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/op-chain-ops/cmd/celo-migrate/non-ancients.go b/op-chain-ops/cmd/celo-migrate/non-ancients.go index 6e0dcbba4ccb..3ba6b95d5385 100644 --- a/op-chain-ops/cmd/celo-migrate/non-ancients.go +++ b/op-chain-ops/cmd/celo-migrate/non-ancients.go @@ -121,9 +121,18 @@ func migrateNonAncientBlock(number uint64, hash common.Hash, newDB ethdb.Databas return fmt.Errorf("failed to transform body: block %d - %x: %w", number, hash, err) } + // Check that transformed header has the same hash 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) + return fmt.Errorf("hash mismatch after transform at block %d - %x", number, hash) + } + // Check that transformed header has the same block number + newHeaderDecoded := new(types.Header) + if err = rlp.DecodeBytes(newHeader, &newHeaderDecoded); err != nil { + return err + } + if newHeaderDecoded.Number.Uint64() != number { + return fmt.Errorf("block number mismatch after transform at block %d - %x. Expected %d, actual %d", number, hash, number, newHeaderDecoded.Number.Uint64()) } // write header and body From 67fe3290a5ce4cdfe08b029b0efb22411ce612ff Mon Sep 17 00:00:00 2001 From: alecps Date: Tue, 17 Dec 2024 14:02:45 -0500 Subject: [PATCH 10/24] add checkOtherDataForNonAncientBlock to non-ancient migration --- op-chain-ops/cmd/celo-migrate/non-ancients.go | 46 +++++++++++++++++-- 1 file changed, 42 insertions(+), 4 deletions(-) diff --git a/op-chain-ops/cmd/celo-migrate/non-ancients.go b/op-chain-ops/cmd/celo-migrate/non-ancients.go index 3ba6b95d5385..5330a225fcc4 100644 --- a/op-chain-ops/cmd/celo-migrate/non-ancients.go +++ b/op-chain-ops/cmd/celo-migrate/non-ancients.go @@ -1,6 +1,7 @@ package main import ( + "bytes" "fmt" "os" "os/exec" @@ -8,8 +9,10 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" ) func copyDbExceptAncients(oldDbPath, newDbPath string) error { @@ -78,20 +81,24 @@ func migrateNonAncientsDb(newDB ethdb.Database, lastBlock, numAncients, batchSiz } } - prevBlockNumber := uint64(numAncients - 1) + prevBlockNumber := uint64(numAncients - 1) // Will underflow if numAncients is 0 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 numberHash.Number != prevBlockNumber+1 { - return 0, fmt.Errorf("gap found between non-ancient blocks numbered %d and %d. Please delete the target directory and repeat the migration with an uncorrupted source directory.", prevBlockNumber, numberHash.Number) + if numberHash.Number != prevBlockNumber+1 { // prevBlocNumber will overflow back to 0 here if numAncients is 0 + return 0, fmt.Errorf("gap found between non-ancient blocks numbered %d and %d. Please delete the target directory and repeat the migration with an uncorrupted source directory", prevBlockNumber, numberHash.Number) } prevBlockNumber = numberHash.Number if err := migrateNonAncientBlock(numberHash.Number, numberHash.Hash, newDB); err != nil { - return 0, err + return 0, fmt.Errorf("failed to migrate non-ancient block %d - %x: %w", numberHash.Number, numberHash.Hash, err) + } + + if err := checkOtherDataForNonAncientBlock(numberHash.Number, numberHash.Hash, newDB); err != nil { + return 0, fmt.Errorf("failed to ensure all non-transformed data is present for non-ancient block %d - %x: %w. Please delete the target directory and repeat the migration with an uncorrupted source directory", numberHash.Number, numberHash.Hash, err) } } } @@ -147,3 +154,34 @@ func migrateNonAncientBlock(number uint64, hash common.Hash, newDB ethdb.Databas return nil } + +// checkOtherDataForNonAncientBlock checks that all the data that is not transformed is succesfully copied for non-ancient blocks. +// I.e. receipts, total difficulty, canonical hash, and block number. +// If an error is returned, it is likely the source directory is corrupted and the migration should be restarted with a clean source directory. +func checkOtherDataForNonAncientBlock(number uint64, hash common.Hash, newDB ethdb.Database) error { + // Ensure receipts and total difficulty are present in non-ancient db + if has, err := newDB.Has(blockReceiptsKey(number, hash)); !has || err != nil { + return fmt.Errorf("failed to find receipts in newDB leveldb: block %d - %x: %w", number, hash, err) + } + if has, err := newDB.Has(headerTDKey(number, hash)); !has || err != nil { + return fmt.Errorf("failed to find total difficulty in newDB leveldb: block %d - %x: %w", number, hash, err) + } + // Ensure canonical hash and number are present in non-ancient db and that they match expected values + hashFromDB, err := newDB.Get(headerHashKey(number)) + if err != nil { + return fmt.Errorf("failed to find canonical hash in newDB leveldb: block %d - %x: %w", number, hash, err) + } + if !bytes.Equal(hashFromDB, hash[:]) { + return fmt.Errorf("canonical hash mismatch in newDB leveldb: block %d - %x: %w", number, hash, err) + } + numberFromDB, err := newDB.Get(headerNumberKey(hash)) + if err != nil { + return fmt.Errorf("failed to find number for hash 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) + } + + return nil +} From 8db450d3c2a3b13b7a285bba0e4c7b9fc98cec9f Mon Sep 17 00:00:00 2001 From: alecps Date: Tue, 17 Dec 2024 14:23:04 -0500 Subject: [PATCH 11/24] add checkTransformedHeader helper --- op-chain-ops/cmd/celo-migrate/ancients.go | 5 ++--- op-chain-ops/cmd/celo-migrate/non-ancients.go | 14 +------------- op-chain-ops/cmd/celo-migrate/transform.go | 15 +++++++++++++++ 3 files changed, 18 insertions(+), 16 deletions(-) diff --git a/op-chain-ops/cmd/celo-migrate/ancients.go b/op-chain-ops/cmd/celo-migrate/ancients.go index 162fba0813fc..70c0bce80752 100644 --- a/op-chain-ops/cmd/celo-migrate/ancients.go +++ b/op-chain-ops/cmd/celo-migrate/ancients.go @@ -179,9 +179,8 @@ func transformBlocks(ctx context.Context, in <-chan RLPBlockRange, out chan<- RL 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) + if err := checkTransformedHeader(newHeader, blockRange.hashes[i], blockNumber); err != nil { + return err } blockRange.headers[i] = newHeader diff --git a/op-chain-ops/cmd/celo-migrate/non-ancients.go b/op-chain-ops/cmd/celo-migrate/non-ancients.go index 5330a225fcc4..d95720f14f45 100644 --- a/op-chain-ops/cmd/celo-migrate/non-ancients.go +++ b/op-chain-ops/cmd/celo-migrate/non-ancients.go @@ -9,10 +9,8 @@ import ( "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" ) func copyDbExceptAncients(oldDbPath, newDbPath string) error { @@ -128,19 +126,9 @@ func migrateNonAncientBlock(number uint64, hash common.Hash, newDB ethdb.Databas return fmt.Errorf("failed to transform body: block %d - %x: %w", number, hash, err) } - // Check that transformed header has the same hash - if yes, newHash := hasSameHash(newHeader, hash[:]); !yes { - log.Error("Hash mismatch", "block", number, "oldHash", hash, "newHash", newHash) - return fmt.Errorf("hash mismatch after transform at block %d - %x", number, hash) - } - // Check that transformed header has the same block number - newHeaderDecoded := new(types.Header) - if err = rlp.DecodeBytes(newHeader, &newHeaderDecoded); err != nil { + if err := checkTransformedHeader(newHeader, hash[:], number); err != nil { return err } - if newHeaderDecoded.Number.Uint64() != number { - return fmt.Errorf("block number mismatch after transform at block %d - %x. Expected %d, actual %d", number, hash, number, newHeaderDecoded.Number.Uint64()) - } // write header and body batch := newDB.NewBatch() diff --git a/op-chain-ops/cmd/celo-migrate/transform.go b/op-chain-ops/cmd/celo-migrate/transform.go index 5a80e8a51566..63530845f7ba 100644 --- a/op-chain-ops/cmd/celo-migrate/transform.go +++ b/op-chain-ops/cmd/celo-migrate/transform.go @@ -77,6 +77,21 @@ func hasSameHash(newHeader, oldHash []byte) (bool, common.Hash) { return bytes.Equal(oldHash, newHash.Bytes()), newHash } +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()) + } +} + // 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 From 39f2b63013edeeebf04344567f28403ef553b374 Mon Sep 17 00:00:00 2001 From: alecps Date: Tue, 17 Dec 2024 14:25:02 -0500 Subject: [PATCH 12/24] add CheckRLPBlockRangeForGaps to ancient migration --- op-chain-ops/cmd/celo-migrate/ancients.go | 53 ++++++++++++++++++++--- 1 file changed, 48 insertions(+), 5 deletions(-) diff --git a/op-chain-ops/cmd/celo-migrate/ancients.go b/op-chain-ops/cmd/celo-migrate/ancients.go index 70c0bce80752..53f01a01d882 100644 --- a/op-chain-ops/cmd/celo-migrate/ancients.go +++ b/op-chain-ops/cmd/celo-migrate/ancients.go @@ -6,10 +6,11 @@ import ( "fmt" "path/filepath" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" + "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" "golang.org/x/sync/errgroup" ) @@ -23,6 +24,44 @@ type RLPBlockRange struct { tds [][]byte } +// CheckRLPBlockRangeForGaps checks for gaps in the given RLP block range by comparing the lengths for each table and checking the header numbers +func CheckRLPBlockRangeForGaps(blockRange RLPBlockRange, expectedLength uint64) (err error) { + if uint64(len(blockRange.hashes)) != expectedLength { + err = fmt.Errorf("Expected count mismatch in block range hashes: expected %d, actual %d", expectedLength, len(blockRange.hashes)) + } + if uint64(len(blockRange.bodies)) != expectedLength { + err = errors.Join(err, fmt.Errorf("Expected count mismatch in block range bodies: expected %d, actual %d", expectedLength, len(blockRange.bodies))) + } + if uint64(len(blockRange.headers)) != expectedLength { + err = errors.Join(err, fmt.Errorf("Expected count mismatch in block range headers: expected %d, actual %d", expectedLength, len(blockRange.headers))) + } + if uint64(len(blockRange.receipts)) != expectedLength { + err = errors.Join(err, fmt.Errorf("Expected count mismatch in block range receipts: expected %d, actual %d", expectedLength, len(blockRange.receipts))) + } + if uint64(len(blockRange.tds)) != expectedLength { + err = errors.Join(err, fmt.Errorf("Expected count mismatch in block range total difficulties: expected %d, actual %d", expectedLength, len(blockRange.tds))) + } + + if err != nil { + return err + } + + // Cbecm that block number in header matches the expected block number + for i := uint64(0); i < expectedLength; i++ { + header := new(types.Header) + err := rlp.DecodeBytes(blockRange.headers[i], &header) + if err != nil { + return fmt.Errorf("can't decode header: %w", err) + } + expectedBlockNumber := blockRange.start + i + if header.Number.Uint64() != expectedBlockNumber { + return fmt.Errorf("header number mismatch: expected %d, actual %d", expectedBlockNumber, header.Number.Uint64()) + } + } + + return nil +} + // 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) { @@ -144,6 +183,10 @@ func readAncientBlocks(ctx context.Context, freezer *rawdb.Freezer, startBlock, return fmt.Errorf("failed to read tds from old freezer: %w", err) } + if err := CheckRLPBlockRangeForGaps(blockRange, count); err != nil { + return fmt.Errorf("failed to ensure ancient block range has no gaps: %w", err) + } + out <- blockRange } } @@ -154,7 +197,7 @@ func transformBlocks(ctx context.Context, in <-chan RLPBlockRange, out chan<- RL // Transform blocks from the in channel and send them to the out channel defer close(out) - prevBlockNumber := startBlock - 1 + prevBlockNumber := uint64(startBlock - 1) // Will underflow when startBlock is 0, but then overflow back to 0 for blockRange := range in { select { @@ -164,10 +207,10 @@ func transformBlocks(ctx context.Context, in <-chan RLPBlockRange, out chan<- RL for i := range blockRange.hashes { blockNumber := blockRange.start + uint64(i) - if blockNumber != prevBlockNumber+1 { - return fmt.Errorf("gap found between ancient blocks numbered %d and %d. Please delete the target directory and repeat the migration with an uncorrupted source directory.", prevBlockNumber, blockNumber) + if blockNumber != prevBlockNumber+1 { // Overflows back to 0 when startBlock is 0 + return fmt.Errorf("gap found between ancient blocks numbered %d and %d. Please delete the target directory and repeat the migration with an uncorrupted source directory", prevBlockNumber, blockNumber) } - // Block ranges are contiguous and in order because they are read sequentially from the freezer + // Block ranges are in order because they are read sequentially from the freezer prevBlockNumber = blockNumber newHeader, err := transformHeader(blockRange.headers[i]) From cb1bc364c7e6f36760c418e84d0febd53ef07bdd Mon Sep 17 00:00:00 2001 From: alecps Date: Tue, 17 Dec 2024 14:26:36 -0500 Subject: [PATCH 13/24] add missing return --- op-chain-ops/cmd/celo-migrate/transform.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/op-chain-ops/cmd/celo-migrate/transform.go b/op-chain-ops/cmd/celo-migrate/transform.go index 63530845f7ba..ef585a0b5c05 100644 --- a/op-chain-ops/cmd/celo-migrate/transform.go +++ b/op-chain-ops/cmd/celo-migrate/transform.go @@ -90,6 +90,8 @@ func checkTransformedHeader(header, expectedHash []byte, expectedNumber uint64) 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) From 66c7b869d759cffc00de0b7960593da29faaea71 Mon Sep 17 00:00:00 2001 From: alecps Date: Tue, 17 Dec 2024 19:58:48 -0500 Subject: [PATCH 14/24] fix minor indexing error in logging --- op-chain-ops/cmd/celo-migrate/ancients.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/op-chain-ops/cmd/celo-migrate/ancients.go b/op-chain-ops/cmd/celo-migrate/ancients.go index 53f01a01d882..e0c27aea4cf2 100644 --- a/op-chain-ops/cmd/celo-migrate/ancients.go +++ b/op-chain-ops/cmd/celo-migrate/ancients.go @@ -149,7 +149,7 @@ func readAncientBlocks(ctx context.Context, freezer *rawdb.Freezer, startBlock, case <-ctx.Done(): return ctx.Err() default: - count := min(batchSize, endBlock-i+1) + count := min(batchSize, endBlock-i) start := i blockRange := RLPBlockRange{ @@ -267,7 +267,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 From 32cbb26614b0c6e77bc0353cc547d31289ab669d Mon Sep 17 00:00:00 2001 From: alecps Date: Wed, 18 Dec 2024 15:47:08 -0500 Subject: [PATCH 15/24] fix typos --- op-chain-ops/cmd/celo-migrate/ancients.go | 2 +- op-chain-ops/cmd/celo-migrate/non-ancients.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/op-chain-ops/cmd/celo-migrate/ancients.go b/op-chain-ops/cmd/celo-migrate/ancients.go index e0c27aea4cf2..ec85c019e00d 100644 --- a/op-chain-ops/cmd/celo-migrate/ancients.go +++ b/op-chain-ops/cmd/celo-migrate/ancients.go @@ -46,7 +46,7 @@ func CheckRLPBlockRangeForGaps(blockRange RLPBlockRange, expectedLength uint64) return err } - // Cbecm that block number in header matches the expected block number + // Check that block number in header matches the expected block number for i := uint64(0); i < expectedLength; i++ { header := new(types.Header) err := rlp.DecodeBytes(blockRange.headers[i], &header) diff --git a/op-chain-ops/cmd/celo-migrate/non-ancients.go b/op-chain-ops/cmd/celo-migrate/non-ancients.go index d95720f14f45..1105134750d1 100644 --- a/op-chain-ops/cmd/celo-migrate/non-ancients.go +++ b/op-chain-ops/cmd/celo-migrate/non-ancients.go @@ -143,7 +143,7 @@ func migrateNonAncientBlock(number uint64, hash common.Hash, newDB ethdb.Databas return nil } -// checkOtherDataForNonAncientBlock checks that all the data that is not transformed is succesfully copied for non-ancient blocks. +// checkOtherDataForNonAncientBlock checks that all the data that is not transformed is successfully copied for non-ancient blocks. // I.e. receipts, total difficulty, canonical hash, and block number. // If an error is returned, it is likely the source directory is corrupted and the migration should be restarted with a clean source directory. func checkOtherDataForNonAncientBlock(number uint64, hash common.Hash, newDB ethdb.Database) error { From f9964c082cbfb0f1dccbe3caefff88fc00a7c977 Mon Sep 17 00:00:00 2001 From: alecps Date: Wed, 18 Dec 2024 17:22:15 -0500 Subject: [PATCH 16/24] WIP add parent hash checks --- op-chain-ops/cmd/celo-migrate/ancients.go | 47 ++++++++++++++++++----- 1 file changed, 38 insertions(+), 9 deletions(-) diff --git a/op-chain-ops/cmd/celo-migrate/ancients.go b/op-chain-ops/cmd/celo-migrate/ancients.go index ec85c019e00d..fa2e19ca2487 100644 --- a/op-chain-ops/cmd/celo-migrate/ancients.go +++ b/op-chain-ops/cmd/celo-migrate/ancients.go @@ -24,8 +24,10 @@ type RLPBlockRange struct { tds [][]byte } -// CheckRLPBlockRangeForGaps checks for gaps in the given RLP block range by comparing the lengths for each table and checking the header numbers -func CheckRLPBlockRangeForGaps(blockRange RLPBlockRange, expectedLength uint64) (err error) { +// CheckRLPBlockRangeForGaps checks for gaps in the given RLPBlockRange by comparing the lengths for each table and checking the header numbers. +// It also checks that the parent hash of each block matches the hash of the previous block, and can check for continuity between RLPBlockRanges +// by taking in the header of the preceeding block, and returning the last decoded header of the given RLPBlockRange so it can be passed into the next call. +func CheckRLPBlockRangeForGaps(blockRange RLPBlockRange, expectedLength uint64, prevHeader *types.Header) (lastHeader *types.Header, err error) { if uint64(len(blockRange.hashes)) != expectedLength { err = fmt.Errorf("Expected count mismatch in block range hashes: expected %d, actual %d", expectedLength, len(blockRange.hashes)) } @@ -43,23 +45,36 @@ func CheckRLPBlockRangeForGaps(blockRange RLPBlockRange, expectedLength uint64) } if err != nil { - return err + return nil, err } - // Check that block number in header matches the expected block number for i := uint64(0); i < expectedLength; i++ { header := new(types.Header) - err := rlp.DecodeBytes(blockRange.headers[i], &header) + err := rlp.DecodeBytes(blockRange.headers[i], header) if err != nil { - return fmt.Errorf("can't decode header: %w", err) + return nil, fmt.Errorf("can't decode header: %w", err) } + // Check that block number in header matches the expected block number expectedBlockNumber := blockRange.start + i if header.Number.Uint64() != expectedBlockNumber { - return fmt.Errorf("header number mismatch: expected %d, actual %d", expectedBlockNumber, header.Number.Uint64()) + return nil, fmt.Errorf("header number mismatch: expected %d, actual %d", expectedBlockNumber, header.Number.Uint64()) } + if prevHeader != nil { + // Check that the block numbers are contiguous + if prevHeader.Number.Uint64() != header.Number.Uint64()-1 { + return nil, fmt.Errorf("gap found between blocks %d and %d", prevHeader.Number.Uint64(), header.Number.Uint64()) + } + // Check that the parent hash of the current block matches the hash of the previous block + prevHeaderHash := prevHeader.Hash() + if prevHeaderHash != header.ParentHash { + log.Error("Parent hash mismatch", "blockNumber", header.Number.Uint64(), "expectedParentHash", prevHeaderHash, "actualParentHash", header.ParentHash, "currentBlockHash", header.Hash()) + // return nil, fmt.Errorf("parent hash mismatch between blocks %d and %d", prevHeader.Number.Uint64(), header.Number.Uint64()) + } + } + prevHeader = header } - return nil + return prevHeader, nil } // NewChainFreezer is a small utility method around NewFreezer that sets the @@ -144,6 +159,19 @@ func migrateAncientsDb(ctx context.Context, oldDBPath, newDBPath string, batchSi func readAncientBlocks(ctx context.Context, freezer *rawdb.Freezer, startBlock, endBlock, batchSize uint64, out chan<- RLPBlockRange) error { defer close(out) + var prevHeader *types.Header // used to check for continuity between RLPBlockRanges + if startBlock > 0 { + prevHeaderBytes, err := freezer.Ancient(rawdb.ChainFreezerHeaderTable, startBlock-1) + if err != nil { + return fmt.Errorf("failed to read previous header: %w", err) + } + prevHeader = new(types.Header) + err = rlp.DecodeBytes(prevHeaderBytes, prevHeader) + if err != nil { + return fmt.Errorf("can't decode previous header: %w", err) + } + } + for i := startBlock; i < endBlock; i += batchSize { select { case <-ctx.Done(): @@ -183,7 +211,8 @@ func readAncientBlocks(ctx context.Context, freezer *rawdb.Freezer, startBlock, return fmt.Errorf("failed to read tds from old freezer: %w", err) } - if err := CheckRLPBlockRangeForGaps(blockRange, count); err != nil { + prevHeader, err = CheckRLPBlockRangeForGaps(blockRange, count, prevHeader) + if err != nil { return fmt.Errorf("failed to ensure ancient block range has no gaps: %w", err) } From 92ac998bb3584626a649947f2036301413430a91 Mon Sep 17 00:00:00 2001 From: alecps Date: Thu, 19 Dec 2024 17:28:34 -0500 Subject: [PATCH 17/24] fix parent hash check in ancient migration --- op-chain-ops/cmd/celo-migrate/ancients.go | 45 ++++++++++++++++------- 1 file changed, 32 insertions(+), 13 deletions(-) diff --git a/op-chain-ops/cmd/celo-migrate/ancients.go b/op-chain-ops/cmd/celo-migrate/ancients.go index fa2e19ca2487..80717273ed47 100644 --- a/op-chain-ops/cmd/celo-migrate/ancients.go +++ b/op-chain-ops/cmd/celo-migrate/ancients.go @@ -6,6 +6,7 @@ import ( "fmt" "path/filepath" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" @@ -27,7 +28,13 @@ type RLPBlockRange struct { // CheckRLPBlockRangeForGaps checks for gaps in the given RLPBlockRange by comparing the lengths for each table and checking the header numbers. // It also checks that the parent hash of each block matches the hash of the previous block, and can check for continuity between RLPBlockRanges // by taking in the header of the preceeding block, and returning the last decoded header of the given RLPBlockRange so it can be passed into the next call. -func CheckRLPBlockRangeForGaps(blockRange RLPBlockRange, expectedLength uint64, prevHeader *types.Header) (lastHeader *types.Header, err error) { +func CheckRLPBlockRangeForGaps(blockRange RLPBlockRange, expectedLength uint64, prevHeader *types.Header, prevHeaderCanonicalHash *common.Hash) (lastHeader *types.Header, lastHeaderCanonicalHash *common.Hash, err error) { + // If there is no previous header or canonical hash provided and the range doesn't start at 0, throw an error. + if blockRange.start != 0 && (prevHeader == nil || prevHeaderCanonicalHash == nil) { + return nil, nil, fmt.Errorf("prevHeader and prevHeaderCanonicalHash must be provided if blockRange.start is not 0") + } + + // Make sure the number of elements retrieved from each table matches the expected length if uint64(len(blockRange.hashes)) != expectedLength { err = fmt.Errorf("Expected count mismatch in block range hashes: expected %d, actual %d", expectedLength, len(blockRange.hashes)) } @@ -43,38 +50,42 @@ func CheckRLPBlockRangeForGaps(blockRange RLPBlockRange, expectedLength uint64, if uint64(len(blockRange.tds)) != expectedLength { err = errors.Join(err, fmt.Errorf("Expected count mismatch in block range total difficulties: expected %d, actual %d", expectedLength, len(blockRange.tds))) } - if err != nil { - return nil, err + return nil, nil, err } for i := uint64(0); i < expectedLength; i++ { header := new(types.Header) err := rlp.DecodeBytes(blockRange.headers[i], header) if err != nil { - return nil, fmt.Errorf("can't decode header: %w", err) + return nil, nil, fmt.Errorf("can't decode header: %w", err) } // Check that block number in header matches the expected block number expectedBlockNumber := blockRange.start + i if header.Number.Uint64() != expectedBlockNumber { - return nil, fmt.Errorf("header number mismatch: expected %d, actual %d", expectedBlockNumber, header.Number.Uint64()) + return nil, nil, fmt.Errorf("header number mismatch: expected %d, actual %d", expectedBlockNumber, header.Number.Uint64()) } if prevHeader != nil { // Check that the block numbers are contiguous if prevHeader.Number.Uint64() != header.Number.Uint64()-1 { - return nil, fmt.Errorf("gap found between blocks %d and %d", prevHeader.Number.Uint64(), header.Number.Uint64()) + return nil, nil, fmt.Errorf("gap found between blocks %d and %d", prevHeader.Number.Uint64(), header.Number.Uint64()) } + } + if prevHeaderCanonicalHash != nil { // Check that the parent hash of the current block matches the hash of the previous block - prevHeaderHash := prevHeader.Hash() - if prevHeaderHash != header.ParentHash { - log.Error("Parent hash mismatch", "blockNumber", header.Number.Uint64(), "expectedParentHash", prevHeaderHash, "actualParentHash", header.ParentHash, "currentBlockHash", header.Hash()) - // return nil, fmt.Errorf("parent hash mismatch between blocks %d and %d", prevHeader.Number.Uint64(), header.Number.Uint64()) + // Note: We can't compare 'prevHeader.Hash() != header.ParentHash' here because the canonical hash is produced over a filtered version of the header (without the Istanbul extra data). + // The custom celo hasing logic that supported this is not ported over the celo's op-geth branch, and we have not yet transformed the block headers to remove the extra data. + // This means that the result of `prevHeader.Hash()` will not match the canonical hash stored in the freezer and in `header.ParentHash`. + if *prevHeaderCanonicalHash != header.ParentHash { + return nil, nil, fmt.Errorf("parent hash mismatch between blocks %d and %d. Expected %s, got %s", expectedBlockNumber-1, expectedBlockNumber, *prevHeaderCanonicalHash, header.ParentHash) } } prevHeader = header + hash := common.BytesToHash(blockRange.hashes[i]) + prevHeaderCanonicalHash = &hash } - return prevHeader, nil + return prevHeader, prevHeaderCanonicalHash, nil } // NewChainFreezer is a small utility method around NewFreezer that sets the @@ -159,7 +170,9 @@ func migrateAncientsDb(ctx context.Context, oldDBPath, newDBPath string, batchSi func readAncientBlocks(ctx context.Context, freezer *rawdb.Freezer, startBlock, endBlock, batchSize uint64, out chan<- RLPBlockRange) error { defer close(out) - var prevHeader *types.Header // used to check for continuity between RLPBlockRanges + // used to check for continuity between RLPBlockRanges + var prevHeader *types.Header + var prevHeaderCanonicalHash *common.Hash if startBlock > 0 { prevHeaderBytes, err := freezer.Ancient(rawdb.ChainFreezerHeaderTable, startBlock-1) if err != nil { @@ -170,6 +183,12 @@ func readAncientBlocks(ctx context.Context, freezer *rawdb.Freezer, startBlock, if err != nil { return fmt.Errorf("can't decode previous header: %w", err) } + prevHeaderCanonicalHashBytes, err := freezer.Ancient(rawdb.ChainFreezerHashTable, startBlock-1) + if err != nil { + return fmt.Errorf("failed to read previous header: %w", err) + } + hash := common.BytesToHash(prevHeaderCanonicalHashBytes) + prevHeaderCanonicalHash = &hash } for i := startBlock; i < endBlock; i += batchSize { @@ -211,7 +230,7 @@ func readAncientBlocks(ctx context.Context, freezer *rawdb.Freezer, startBlock, return fmt.Errorf("failed to read tds from old freezer: %w", err) } - prevHeader, err = CheckRLPBlockRangeForGaps(blockRange, count, prevHeader) + prevHeader, prevHeaderCanonicalHash, err = CheckRLPBlockRangeForGaps(blockRange, count, prevHeader, prevHeaderCanonicalHash) if err != nil { return fmt.Errorf("failed to ensure ancient block range has no gaps: %w", err) } From c2c2789f6b934bf59feb14ea708b0429b2b5af33 Mon Sep 17 00:00:00 2001 From: piersy Date: Fri, 20 Dec 2024 19:20:07 +0000 Subject: [PATCH 18/24] Alternative approach to checking continuity (#288) --- op-chain-ops/cmd/celo-migrate/ancients.go | 229 ++++++++++++---------- 1 file changed, 120 insertions(+), 109 deletions(-) diff --git a/op-chain-ops/cmd/celo-migrate/ancients.go b/op-chain-ops/cmd/celo-migrate/ancients.go index 80717273ed47..8c7a99038661 100644 --- a/op-chain-ops/cmd/celo-migrate/ancients.go +++ b/op-chain-ops/cmd/celo-migrate/ancients.go @@ -25,67 +25,56 @@ type RLPBlockRange struct { tds [][]byte } -// CheckRLPBlockRangeForGaps checks for gaps in the given RLPBlockRange by comparing the lengths for each table and checking the header numbers. -// It also checks that the parent hash of each block matches the hash of the previous block, and can check for continuity between RLPBlockRanges -// by taking in the header of the preceeding block, and returning the last decoded header of the given RLPBlockRange so it can be passed into the next call. -func CheckRLPBlockRangeForGaps(blockRange RLPBlockRange, expectedLength uint64, prevHeader *types.Header, prevHeaderCanonicalHash *common.Hash) (lastHeader *types.Header, lastHeaderCanonicalHash *common.Hash, err error) { - // If there is no previous header or canonical hash provided and the range doesn't start at 0, throw an error. - if blockRange.start != 0 && (prevHeader == nil || prevHeaderCanonicalHash == nil) { - return nil, nil, fmt.Errorf("prevHeader and prevHeaderCanonicalHash must be provided if blockRange.start is not 0") - } +type RLPBlockElement struct { + decodedHeader *types.Header + hash []byte + header []byte + body []byte + receipts []byte + td []byte +} - // Make sure the number of elements retrieved from each table matches the expected length - if uint64(len(blockRange.hashes)) != expectedLength { - err = fmt.Errorf("Expected count mismatch in block range hashes: expected %d, actual %d", expectedLength, len(blockRange.hashes)) - } - if uint64(len(blockRange.bodies)) != expectedLength { - err = errors.Join(err, fmt.Errorf("Expected count mismatch in block range bodies: expected %d, actual %d", expectedLength, len(blockRange.bodies))) - } - if uint64(len(blockRange.headers)) != expectedLength { - err = errors.Join(err, fmt.Errorf("Expected count mismatch in block range headers: expected %d, actual %d", expectedLength, len(blockRange.headers))) - } - if uint64(len(blockRange.receipts)) != expectedLength { - err = errors.Join(err, fmt.Errorf("Expected count mismatch in block range receipts: expected %d, actual %d", expectedLength, len(blockRange.receipts))) - } - if uint64(len(blockRange.tds)) != expectedLength { - err = errors.Join(err, fmt.Errorf("Expected count mismatch in block range total difficulties: expected %d, actual %d", expectedLength, len(blockRange.tds))) - } +func (r *RLPBlockRange) Element(i uint64) (*RLPBlockElement, error) { + header := types.Header{} + err := rlp.DecodeBytes(r.headers[i], &header) if err != nil { - return nil, nil, err + return nil, fmt.Errorf("can't decode header: %w", err) } + return &RLPBlockElement{ + decodedHeader: &header, + hash: r.hashes[i], + header: r.headers[i], + body: r.bodies[i], + receipts: r.receipts[i], + td: r.tds[i], + }, nil +} - for i := uint64(0); i < expectedLength; i++ { - header := new(types.Header) - err := rlp.DecodeBytes(blockRange.headers[i], header) - if err != nil { - return nil, nil, fmt.Errorf("can't decode header: %w", err) - } - // Check that block number in header matches the expected block number - expectedBlockNumber := blockRange.start + i - if header.Number.Uint64() != expectedBlockNumber { - return nil, nil, fmt.Errorf("header number mismatch: expected %d, actual %d", expectedBlockNumber, header.Number.Uint64()) - } - if prevHeader != nil { - // Check that the block numbers are contiguous - if prevHeader.Number.Uint64() != header.Number.Uint64()-1 { - return nil, nil, fmt.Errorf("gap found between blocks %d and %d", prevHeader.Number.Uint64(), header.Number.Uint64()) - } - } - if prevHeaderCanonicalHash != nil { - // Check that the parent hash of the current block matches the hash of the previous block - // Note: We can't compare 'prevHeader.Hash() != header.ParentHash' here because the canonical hash is produced over a filtered version of the header (without the Istanbul extra data). - // The custom celo hasing logic that supported this is not ported over the celo's op-geth branch, and we have not yet transformed the block headers to remove the extra data. - // This means that the result of `prevHeader.Hash()` will not match the canonical hash stored in the freezer and in `header.ParentHash`. - if *prevHeaderCanonicalHash != header.ParentHash { - return nil, nil, fmt.Errorf("parent hash mismatch between blocks %d and %d. Expected %s, got %s", expectedBlockNumber-1, expectedBlockNumber, *prevHeaderCanonicalHash, header.ParentHash) - } - } - prevHeader = header - hash := common.BytesToHash(blockRange.hashes[i]) - prevHeaderCanonicalHash = &hash - } +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:] +} - return prevHeader, prevHeaderCanonicalHash, nil +func (e *RLPBlockElement) Header() *types.Header { + + return e.decodedHeader +} + +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 } // NewChainFreezer is a small utility method around NewFreezer that sets the @@ -169,28 +158,6 @@ func migrateAncientsDb(ctx context.Context, oldDBPath, newDBPath string, batchSi func readAncientBlocks(ctx context.Context, freezer *rawdb.Freezer, startBlock, endBlock, batchSize uint64, out chan<- RLPBlockRange) error { defer close(out) - - // used to check for continuity between RLPBlockRanges - var prevHeader *types.Header - var prevHeaderCanonicalHash *common.Hash - if startBlock > 0 { - prevHeaderBytes, err := freezer.Ancient(rawdb.ChainFreezerHeaderTable, startBlock-1) - if err != nil { - return fmt.Errorf("failed to read previous header: %w", err) - } - prevHeader = new(types.Header) - err = rlp.DecodeBytes(prevHeaderBytes, prevHeader) - if err != nil { - return fmt.Errorf("can't decode previous header: %w", err) - } - prevHeaderCanonicalHashBytes, err := freezer.Ancient(rawdb.ChainFreezerHashTable, startBlock-1) - if err != nil { - return fmt.Errorf("failed to read previous header: %w", err) - } - hash := common.BytesToHash(prevHeaderCanonicalHashBytes) - prevHeaderCanonicalHash = &hash - } - for i := startBlock; i < endBlock; i += batchSize { select { case <-ctx.Done(): @@ -198,49 +165,93 @@ func readAncientBlocks(ctx context.Context, freezer *rawdb.Freezer, startBlock, default: 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) + blockRange, err := loadRange(freezer, start, count) 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) - if err != nil { - return 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 fmt.Errorf("failed to read tds from old freezer: %w", err) + return fmt.Errorf("failed to load ancient block range: %w", err) } - prevHeader, prevHeaderCanonicalHash, err = CheckRLPBlockRangeForGaps(blockRange, count, prevHeader, prevHeaderCanonicalHash) - if err != nil { - return fmt.Errorf("failed to ensure ancient block range has no gaps: %w", err) + // Check continuity between blocks + var prevElement *RLPBlockElement + for i := uint64(0); i < count; i++ { + currElement, err := blockRange.Element(i) + if err != nil { + return err + } + if prevElement != nil { + if err := currElement.Follows(prevElement); err != nil { + return err + } + } + prevElement = currElement } - out <- blockRange + if start > 0 { + blockRange.DropFirst() + } + out <- *blockRange } } return nil } +func loadRange(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) + } + + // Make sure the number of elements retrieved from each table matches the expected length + if uint64(len(blockRange.hashes)) != count { + err = fmt.Errorf("Expected count mismatch in block range hashes: expected %d, actual %d", count, len(blockRange.hashes)) + } + if uint64(len(blockRange.bodies)) != count { + err = errors.Join(err, fmt.Errorf("Expected count mismatch in block range bodies: expected %d, actual %d", count, len(blockRange.bodies))) + } + if uint64(len(blockRange.headers)) != count { + err = errors.Join(err, fmt.Errorf("Expected count mismatch in block range headers: expected %d, actual %d", count, len(blockRange.headers))) + } + if uint64(len(blockRange.receipts)) != count { + err = errors.Join(err, fmt.Errorf("Expected count mismatch in block range receipts: expected %d, actual %d", count, len(blockRange.receipts))) + } + if uint64(len(blockRange.tds)) != count { + err = errors.Join(err, fmt.Errorf("Expected count mismatch in block range total difficulties: expected %d, actual %d", count, len(blockRange.tds))) + } + return blockRange, err +} + func transformBlocks(ctx context.Context, in <-chan RLPBlockRange, out chan<- RLPBlockRange, startBlock uint64) error { // Transform blocks from the in channel and send them to the out channel defer close(out) From 8781e3dcfe049e7e9ad7a972e82cdc6c62296480 Mon Sep 17 00:00:00 2001 From: alecps Date: Mon, 6 Jan 2025 17:39:17 -0500 Subject: [PATCH 19/24] fix error check --- op-chain-ops/cmd/celo-migrate/db.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/op-chain-ops/cmd/celo-migrate/db.go b/op-chain-ops/cmd/celo-migrate/db.go index 40775c3d7b1b..9fa68ed98310 100644 --- a/op-chain-ops/cmd/celo-migrate/db.go +++ b/op-chain-ops/cmd/celo-migrate/db.go @@ -92,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 } From 407e10304f827b74aa35df1d6801674a91aa8736 Mon Sep 17 00:00:00 2001 From: alecps Date: Mon, 6 Jan 2025 17:42:57 -0500 Subject: [PATCH 20/24] saving progress on consolidating continuity checks between ancient and non-ancient blocks --- op-chain-ops/cmd/celo-migrate/ancients.go | 107 +++++----------- op-chain-ops/cmd/celo-migrate/main.go | 95 +++++++++++--- op-chain-ops/cmd/celo-migrate/non-ancients.go | 118 ++++++++++++------ 3 files changed, 194 insertions(+), 126 deletions(-) diff --git a/op-chain-ops/cmd/celo-migrate/ancients.go b/op-chain-ops/cmd/celo-migrate/ancients.go index 8c7a99038661..f70d3458b3d8 100644 --- a/op-chain-ops/cmd/celo-migrate/ancients.go +++ b/op-chain-ops/cmd/celo-migrate/ancients.go @@ -6,77 +6,12 @@ import ( "fmt" "path/filepath" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" - "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" "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 -} - -type RLPBlockElement struct { - decodedHeader *types.Header - 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, - 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:] -} - -func (e *RLPBlockElement) Header() *types.Header { - - return e.decodedHeader -} - -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 -} - // 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) { @@ -93,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, numAncientsNewAfter 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, 0, nil, fmt.Errorf("failed to open old freezer: %w", err) } defer func() { err = errors.Join(err, oldFreezer.Close()) @@ -106,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, 0, nil, fmt.Errorf("failed to open new freezer: %w", err) } defer func() { err = errors.Join(err, newFreezer.Close()) @@ -114,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, 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, 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, numAncientsNewBefore) + if err != nil { + return 0, 0, nil, err + } + return numAncientsNewBefore, numAncientsNewBefore, lastAncient, nil } log.Info("Ancient Block Migration Started", "process", "ancients", "startBlock", numAncientsNewBefore, "endBlock", numAncientsOld-1, "count", numAncientsOld-numAncientsNewBefore, "step", batchSize) @@ -140,20 +79,25 @@ 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, 0, nil, fmt.Errorf("failed to migrate ancients: %w", err) } numAncientsNewAfter, err = newFreezer.Ancients() if err != nil { - return 0, 0, fmt.Errorf("failed to get number of ancients in new freezer: %w", err) + return 0, 0, nil, fmt.Errorf("failed to get number of ancients in new freezer: %w", err) } 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, 0, nil, fmt.Errorf("failed to migrate all ancients from old to new db. Expected %d, got %d", numAncientsOld, numAncientsNewAfter) + } + + lastAncient, err = loadLastAncient(newFreezer, numAncientsNewAfter) + if err != nil { + return 0, 0, nil, err } log.Info("Ancient Block Migration Ended", "process", "ancients", "ancientsInOldDB", numAncientsOld, "ancientsInNewDB", numAncientsNewAfter, "migrated", numAncientsNewAfter-numAncientsNewBefore) - return numAncientsNewBefore, numAncientsNewAfter, nil + return numAncientsNewBefore, numAncientsNewAfter, lastAncient, nil } func readAncientBlocks(ctx context.Context, freezer *rawdb.Freezer, startBlock, endBlock, batchSize uint64, out chan<- RLPBlockRange) error { @@ -172,7 +116,7 @@ func readAncientBlocks(ctx context.Context, freezer *rawdb.Freezer, startBlock, count = count + 1 } - blockRange, err := loadRange(freezer, start, count) + blockRange, err := loadAncientRange(freezer, start, count) if err != nil { return fmt.Errorf("failed to load ancient block range: %w", err) } @@ -201,7 +145,7 @@ func readAncientBlocks(ctx context.Context, freezer *rawdb.Freezer, startBlock, return nil } -func loadRange(freezer *rawdb.Freezer, start, count uint64) (*RLPBlockRange, error) { +func loadAncientRange(freezer *rawdb.Freezer, start, count uint64) (*RLPBlockRange, error) { blockRange := &RLPBlockRange{ start: start, hashes: make([][]byte, count), @@ -252,6 +196,15 @@ func loadRange(freezer *rawdb.Freezer, start, count uint64) (*RLPBlockRange, err return blockRange, err } +// Get the last ancient block data so we can check for continuity between ancients and non-ancients +func loadLastAncient(freezer *rawdb.Freezer, numAncients uint64) (*RLPBlockElement, error) { + 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, startBlock uint64) error { // Transform blocks from the in channel and send them to the out channel defer close(out) diff --git a/op-chain-ops/cmd/celo-migrate/main.go b/op-chain-ops/cmd/celo-migrate/main.go index 115315cfd672..12a296c55606 100644 --- a/op-chain-ops/cmd/celo-migrate/main.go +++ b/op-chain-ops/cmd/celo-migrate/main.go @@ -21,10 +21,12 @@ import ( "github.com/urfave/cli/v2" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" "golang.org/x/sync/errgroup" @@ -194,7 +196,7 @@ func main() { Usage: "Perform a pre-migration of ancient blocks and copy over all other data without transforming it. This should be run a day before the full migration command is run to minimize downtime.", Flags: preMigrationFlags, Action: func(ctx *cli.Context) error { - if _, _, err := runPreMigration(parsePreMigrationOptions(ctx)); err != nil { + if _, _, _, err := runPreMigration(parsePreMigrationOptions(ctx)); err != nil { return fmt.Errorf("failed to run pre-migration: %w", err) } log.Info("Finished pre migration successfully!") @@ -230,6 +232,69 @@ func main() { } } +// RLPBlockRange is a range of blocks in RLP format +type RLPBlockRange struct { + start uint64 + hashes [][]byte + headers [][]byte + bodies [][]byte + receipts [][]byte + tds [][]byte +} + +type RLPBlockElement struct { + decodedHeader *types.Header + number uint64 + hash []byte + header []byte // TODO(Alec): why this? + 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:] +} + +func (e *RLPBlockElement) Header() *types.Header { + return e.decodedHeader +} + +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 runFullMigration(opts fullMigrationOptions) error { defer timer("full migration")() @@ -247,12 +312,13 @@ func runFullMigration(opts fullMigrationOptions) error { var numAncients uint64 var strayAncientBlocks []*rawdb.NumberHash + var lastAncient *RLPBlockElement - if strayAncientBlocks, numAncients, err = runPreMigration(opts.preMigrationOptions); err != nil { + if strayAncientBlocks, numAncients, 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, numAncients, lastAncient); err != nil { return fmt.Errorf("failed to run non-ancient migration: %w", err) } if err = runStateMigration(opts.newDBPath, opts.stateMigrationOptions); err != nil { @@ -264,14 +330,14 @@ func runFullMigration(opts fullMigrationOptions) error { return nil } -func runPreMigration(opts preMigrationOptions) ([]*rawdb.NumberHash, uint64, error) { +func runPreMigration(opts preMigrationOptions) ([]*rawdb.NumberHash, uint64, *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, 0, nil, fmt.Errorf("please install `rsync` to run block migration") } debug.SetMemoryLimit(opts.memoryLimit * 1 << 20) // Set memory limit, converting from MiB to bytes @@ -279,21 +345,22 @@ 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, 0, 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, 0, 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, numAncientsNewAfter, 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 @@ -309,15 +376,16 @@ 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, 0, nil, fmt.Errorf("failed to migrate blocks: %w", err) } log.Info("Pre-Migration Finished", "oldDBPath", opts.oldDBPath, "newDBPath", opts.newDBPath, "migratedAncients", numAncientsNewAfter-numAncientsNewBefore, "strayAncientBlocks", len(strayAncientBlocks)) - return strayAncientBlocks, numAncientsNewAfter, nil + return strayAncientBlocks, numAncientsNewAfter, lastAncient, nil } -func runNonAncientMigration(newDBPath string, strayAncientBlocks []*rawdb.NumberHash, batchSize, numAncients uint64) (err error) { +// TODO(Alec) do we need to pass numAncients here? +func runNonAncientMigration(newDBPath string, strayAncientBlocks []*rawdb.NumberHash, batchSize, numAncients uint64, lastAncient *RLPBlockElement) (err error) { defer timer("non-ancient migration")() newDB, err := openDBWithoutFreezer(newDBPath, false) @@ -331,12 +399,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", numAncients, "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, numAncients, 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 1105134750d1..da8d4f18dc28 100644 --- a/op-chain-ops/cmd/celo-migrate/non-ancients.go +++ b/op-chain-ops/cmd/celo-migrate/non-ancients.go @@ -2,6 +2,7 @@ package main import ( "bytes" + "encoding/binary" "fmt" "os" "os/exec" @@ -11,6 +12,7 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" + "github.com/ethereum/go-ethereum/rlp" ) func copyDbExceptAncients(oldDbPath, newDbPath string) error { @@ -63,41 +65,49 @@ 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, numAncients, 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 { + // 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 { + genesisBlockElement, err := readRLPBlockElement(newDB, 0, rawdb.ReadCanonicalHash(newDB, 0)) + if err != nil { + return 0, err + } + if err := migrateNonAncientBlock(newDB, genesisBlockElement); err != nil { return 0, err } } - prevBlockNumber := uint64(numAncients - 1) // Will underflow if numAncients is 0 - + prevBlockElement := lastAncient for i := numAncients; i <= lastBlock; i += batchSize { - numbersHash := rawdb.ReadAllHashesInRange(newDB, i, i+batchSize-1) + numbersHash := rawdb.ReadAllHashesInRange(newDB, i, i+batchSize-1) // TODO(Alec) log.Info("Processing Block Range", "process", "non-ancients", "from", i, "to(inclusve)", i+batchSize-1, "count", len(numbersHash)) for _, numberHash := range numbersHash { - if numberHash.Number != prevBlockNumber+1 { // prevBlocNumber will overflow back to 0 here if numAncients is 0 - return 0, fmt.Errorf("gap found between non-ancient blocks numbered %d and %d. Please delete the target directory and repeat the migration with an uncorrupted source directory", prevBlockNumber, numberHash.Number) + + blockElement, err := readRLPBlockElement(newDB, numberHash.Number, numberHash.Hash) + if err != nil { + return 0, fmt.Errorf("failed to read RLP block element for non-ancient block %d - %x: %w", numberHash.Number, numberHash.Hash, err) } - prevBlockNumber = numberHash.Number - if err := migrateNonAncientBlock(numberHash.Number, numberHash.Hash, newDB); err != nil { - return 0, fmt.Errorf("failed to migrate non-ancient block %d - %x: %w", numberHash.Number, numberHash.Hash, err) + if prevBlockElement != nil { + if err := blockElement.Follows(prevBlockElement); err != nil { + return 0, err + } } - if err := checkOtherDataForNonAncientBlock(numberHash.Number, numberHash.Hash, newDB); err != nil { - return 0, fmt.Errorf("failed to ensure all non-transformed data is present for non-ancient block %d - %x: %w. Please delete the target directory and repeat the migration with an uncorrupted source directory", numberHash.Number, numberHash.Hash, err) + if err := migrateNonAncientBlock(newDB, blockElement); err != nil { + return 0, fmt.Errorf("failed to migrate non-ancient block %d - %x: %w", numberHash.Number, numberHash.Hash, err) } + + prevBlockElement = blockElement } } @@ -105,44 +115,82 @@ func migrateNonAncientsDb(newDB ethdb.Database, lastBlock, numAncients, batchSiz return migratedCount, nil } -func migrateNonAncientBlock(number uint64, hash common.Hash, newDB ethdb.Database) error { - // read header and body - header, err := newDB.Get(headerKey(number, hash)) - if err != nil { - return fmt.Errorf("failed to read header: block %d - %x: %w", number, hash, err) - } - body, err := newDB.Get(blockBodyKey(number, hash)) - if err != nil { - return fmt.Errorf("failed to read body: block %d - %x: %w", number, hash, err) - } - +func migrateNonAncientBlock(newDB ethdb.Database, block *RLPBlockElement) error { // transform header and body - newHeader, err := transformHeader(header) + newHeader, err := transformHeader(block.header) if err != nil { - return fmt.Errorf("failed to transform header: block %d - %x: %w", number, hash, err) + return fmt.Errorf("failed to transform header: block %d - %x: %w", block.number, block.hash, err) } - newBody, err := transformBlockBody(body) + newBody, err := transformBlockBody(block.body) if err != nil { - return fmt.Errorf("failed to transform body: block %d - %x: %w", number, hash, err) + return fmt.Errorf("failed to transform body: block %d - %x: %w", block.number, block.hash, err) } - if err := checkTransformedHeader(newHeader, hash[:], number); err != nil { + if err := checkTransformedHeader(newHeader, block.hash[:], block.number); err != nil { return err } // write header and body + hash := common.BytesToHash(block.hash) batch := newDB.NewBatch() - rawdb.WriteBodyRLP(batch, hash, number, newBody) - if err := batch.Put(headerKey(number, hash), newHeader); err != nil { - return fmt.Errorf("failed to write header: block %d - %x: %w", number, hash, err) + rawdb.WriteBodyRLP(batch, hash, block.number, newBody) + if err := batch.Put(headerKey(block.number, hash), newHeader); err != nil { + return fmt.Errorf("failed to write header: block %d - %x: %w", block.number, block.hash, err) } if err := batch.Write(); err != nil { - return fmt.Errorf("failed to write header and body: block %d - %x: %w", number, hash, err) + return fmt.Errorf("failed to write header and body: block %d - %x: %w", block.number, block.hash, err) } return nil } +func readRLPBlockElement(newDB ethdb.Database, number uint64, hash common.Hash) (*RLPBlockElement, error) { + e := &RLPBlockElement{} + var err error + + 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) + + e.hash, 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) + } + e.header, err = newDB.Get(headerKey(number, hash)) + if err != nil { + return nil, fmt.Errorf("failed to read header: block %d - %x: %w", number, hash, err) + } + e.body, err = newDB.Get(blockBodyKey(number, hash)) + if err != nil { + return nil, fmt.Errorf("failed to read body: block %d - %x: %w", number, hash, err) + } + e.receipts, 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) + } + e.td, 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) + } + + // 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) + // } + + err = rlp.DecodeBytes(e.header, &e.decodedHeader) + if err != nil { + return nil, fmt.Errorf("can't decode header: %w", err) + } + return e, nil +} + // checkOtherDataForNonAncientBlock checks that all the data that is not transformed is successfully copied for non-ancient blocks. // I.e. receipts, total difficulty, canonical hash, and block number. // If an error is returned, it is likely the source directory is corrupted and the migration should be restarted with a clean source directory. @@ -164,7 +212,7 @@ func checkOtherDataForNonAncientBlock(number uint64, hash common.Hash, newDB eth } numberFromDB, err := newDB.Get(headerNumberKey(hash)) if err != nil { - return fmt.Errorf("failed to find number for hash in newDB leveldb: block %d - %x: %w", number, hash, err) + return fmt.Errorf("failed to find number for hash 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) From d7b3f80a87ccd05931e54c192d4354f290a4393b Mon Sep 17 00:00:00 2001 From: alecps Date: Tue, 7 Jan 2025 12:47:48 -0500 Subject: [PATCH 21/24] saving more progress --- op-chain-ops/cmd/celo-migrate/ancients.go | 16 +--- op-chain-ops/cmd/celo-migrate/continuity.go | 89 +++++++++++++++++++ op-chain-ops/cmd/celo-migrate/main.go | 65 -------------- op-chain-ops/cmd/celo-migrate/non-ancients.go | 57 ++++++++---- 4 files changed, 134 insertions(+), 93 deletions(-) create mode 100644 op-chain-ops/cmd/celo-migrate/continuity.go diff --git a/op-chain-ops/cmd/celo-migrate/ancients.go b/op-chain-ops/cmd/celo-migrate/ancients.go index f70d3458b3d8..8bdbea6aacf0 100644 --- a/op-chain-ops/cmd/celo-migrate/ancients.go +++ b/op-chain-ops/cmd/celo-migrate/ancients.go @@ -121,19 +121,8 @@ func readAncientBlocks(ctx context.Context, freezer *rawdb.Freezer, startBlock, return fmt.Errorf("failed to load ancient block range: %w", err) } - // Check continuity between blocks - var prevElement *RLPBlockElement - for i := uint64(0); i < count; i++ { - currElement, err := blockRange.Element(i) - if err != nil { - return err - } - if prevElement != nil { - if err := currElement.Follows(prevElement); err != nil { - return err - } - } - prevElement = currElement + if err = blockRange.CheckContinuity(nil); err != nil { + return err } if start > 0 { @@ -177,6 +166,7 @@ func loadAncientRange(freezer *rawdb.Freezer, start, count uint64) (*RLPBlockRan return nil, fmt.Errorf("failed to read tds from old freezer: %w", err) } + // TODO(Alec): Should this be moved to CheckContinuity? // Make sure the number of elements retrieved from each table matches the expected length if uint64(len(blockRange.hashes)) != count { err = fmt.Errorf("Expected count mismatch in block range hashes: expected %d, actual %d", count, len(blockRange.hashes)) 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..067387318373 --- /dev/null +++ b/op-chain-ops/cmd/celo-migrate/continuity.go @@ -0,0 +1,89 @@ +package main + +import ( + "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 // TODO(Alec): why this? + 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:] +} + +func (r *RLPBlockRange) CheckContinuity(prevElement *RLPBlockElement) error { + for i := range r.hashes { // TODO(Alec): what if there are different lengths? + 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 +} + +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/main.go b/op-chain-ops/cmd/celo-migrate/main.go index 12a296c55606..22fbc73f18f3 100644 --- a/op-chain-ops/cmd/celo-migrate/main.go +++ b/op-chain-ops/cmd/celo-migrate/main.go @@ -21,12 +21,10 @@ import ( "github.com/urfave/cli/v2" - "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" "github.com/ethereum/go-ethereum/rpc" "golang.org/x/sync/errgroup" @@ -232,69 +230,6 @@ func main() { } } -// RLPBlockRange is a range of blocks in RLP format -type RLPBlockRange struct { - start uint64 - hashes [][]byte - headers [][]byte - bodies [][]byte - receipts [][]byte - tds [][]byte -} - -type RLPBlockElement struct { - decodedHeader *types.Header - number uint64 - hash []byte - header []byte // TODO(Alec): why this? - 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:] -} - -func (e *RLPBlockElement) Header() *types.Header { - return e.decodedHeader -} - -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 runFullMigration(opts fullMigrationOptions) error { defer timer("full migration")() diff --git a/op-chain-ops/cmd/celo-migrate/non-ancients.go b/op-chain-ops/cmd/celo-migrate/non-ancients.go index da8d4f18dc28..24ff7ef6620e 100644 --- a/op-chain-ops/cmd/celo-migrate/non-ancients.go +++ b/op-chain-ops/cmd/celo-migrate/non-ancients.go @@ -87,26 +87,25 @@ func migrateNonAncientsDb(newDB ethdb.Database, lastBlock, numAncients, batchSiz prevBlockElement := lastAncient for i := numAncients; i <= lastBlock; i += batchSize { - numbersHash := rawdb.ReadAllHashesInRange(newDB, i, i+batchSize-1) // TODO(Alec) + blockRange, err := loadNonAncientRange(newDB, i, batchSize) + if err != nil { + return 0, err + } - log.Info("Processing Block Range", "process", "non-ancients", "from", i, "to(inclusve)", i+batchSize-1, "count", len(numbersHash)) - for _, numberHash := range numbersHash { + log.Info("Processing Block Range", "process", "non-ancients", "from", i, "to(inclusve)", i+batchSize-1, "count", len(blockRange.hashes)) - blockElement, err := readRLPBlockElement(newDB, numberHash.Number, numberHash.Hash) - if err != nil { - return 0, fmt.Errorf("failed to read RLP block element for non-ancient block %d - %x: %w", numberHash.Number, numberHash.Hash, err) - } + if err := blockRange.CheckContinuity(prevBlockElement); err != nil { + return 0, fmt.Errorf("failed continuity check for non-ancient blocks: %w", err) + } - if prevBlockElement != nil { - if err := blockElement.Follows(prevBlockElement); err != nil { - return 0, err - } + for i := range blockRange.hashes { + blockElement, err := blockRange.Element(uint64(i)) + if err != nil { + return 0, err } - - if err := migrateNonAncientBlock(newDB, blockElement); err != nil { - return 0, fmt.Errorf("failed to migrate non-ancient block %d - %x: %w", numberHash.Number, numberHash.Hash, err) + if err = migrateNonAncientBlock(newDB, blockElement); err != nil { + return 0, err } - prevBlockElement = blockElement } } @@ -115,6 +114,34 @@ func migrateNonAncientsDb(newDB ethdb.Database, lastBlock, numAncients, batchSiz return migratedCount, 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), + } + + numbersHash := rawdb.ReadAllHashesInRange(newDB, start, start+count-1) // minus 1 because start is included in count + + for i, numberHash := range numbersHash { + blockElement, err := readRLPBlockElement(newDB, numberHash.Number, numberHash.Hash) + if err != nil { + return nil, fmt.Errorf("failed to read RLP block element for non-ancient block %d - %x: %w", numberHash.Number, numberHash.Hash, err) + } + + blockRange.hashes[i] = blockElement.hash + blockRange.headers[i] = blockElement.header + blockRange.bodies[i] = blockElement.body + blockRange.receipts[i] = blockElement.receipts + blockRange.tds[i] = blockElement.td + } + + return blockRange, nil +} + func migrateNonAncientBlock(newDB ethdb.Database, block *RLPBlockElement) error { // transform header and body newHeader, err := transformHeader(block.header) From 1c84877d15ad044ef3dff54048091656ad15fb8c Mon Sep 17 00:00:00 2001 From: alecps Date: Tue, 7 Jan 2025 16:55:22 -0500 Subject: [PATCH 22/24] saving more progress / cleanup --- op-chain-ops/cmd/celo-migrate/ancients.go | 27 +--- op-chain-ops/cmd/celo-migrate/continuity.go | 36 ++++- op-chain-ops/cmd/celo-migrate/non-ancients.go | 134 ++++++------------ op-chain-ops/cmd/celo-migrate/transform.go | 1 + 4 files changed, 81 insertions(+), 117 deletions(-) diff --git a/op-chain-ops/cmd/celo-migrate/ancients.go b/op-chain-ops/cmd/celo-migrate/ancients.go index 8bdbea6aacf0..c260d03c36f2 100644 --- a/op-chain-ops/cmd/celo-migrate/ancients.go +++ b/op-chain-ops/cmd/celo-migrate/ancients.go @@ -166,24 +166,7 @@ func loadAncientRange(freezer *rawdb.Freezer, start, count uint64) (*RLPBlockRan return nil, fmt.Errorf("failed to read tds from old freezer: %w", err) } - // TODO(Alec): Should this be moved to CheckContinuity? - // Make sure the number of elements retrieved from each table matches the expected length - if uint64(len(blockRange.hashes)) != count { - err = fmt.Errorf("Expected count mismatch in block range hashes: expected %d, actual %d", count, len(blockRange.hashes)) - } - if uint64(len(blockRange.bodies)) != count { - err = errors.Join(err, fmt.Errorf("Expected count mismatch in block range bodies: expected %d, actual %d", count, len(blockRange.bodies))) - } - if uint64(len(blockRange.headers)) != count { - err = errors.Join(err, fmt.Errorf("Expected count mismatch in block range headers: expected %d, actual %d", count, len(blockRange.headers))) - } - if uint64(len(blockRange.receipts)) != count { - err = errors.Join(err, fmt.Errorf("Expected count mismatch in block range receipts: expected %d, actual %d", count, len(blockRange.receipts))) - } - if uint64(len(blockRange.tds)) != count { - err = errors.Join(err, fmt.Errorf("Expected count mismatch in block range total difficulties: expected %d, actual %d", count, len(blockRange.tds))) - } - return blockRange, err + return blockRange, nil } // Get the last ancient block data so we can check for continuity between ancients and non-ancients @@ -199,8 +182,6 @@ func transformBlocks(ctx context.Context, in <-chan RLPBlockRange, out chan<- RL // Transform blocks from the in channel and send them to the out channel defer close(out) - prevBlockNumber := uint64(startBlock - 1) // Will underflow when startBlock is 0, but then overflow back to 0 - for blockRange := range in { select { case <-ctx.Done(): @@ -209,12 +190,6 @@ func transformBlocks(ctx context.Context, in <-chan RLPBlockRange, out chan<- RL for i := range blockRange.hashes { blockNumber := blockRange.start + uint64(i) - if blockNumber != prevBlockNumber+1 { // Overflows back to 0 when startBlock is 0 - return fmt.Errorf("gap found between ancient blocks numbered %d and %d. Please delete the target directory and repeat the migration with an uncorrupted source directory", prevBlockNumber, blockNumber) - } - // Block ranges are in order because they are read sequentially from the freezer - prevBlockNumber = blockNumber - newHeader, err := transformHeader(blockRange.headers[i]) if err != nil { return fmt.Errorf("can't transform header: %w", err) diff --git a/op-chain-ops/cmd/celo-migrate/continuity.go b/op-chain-ops/cmd/celo-migrate/continuity.go index 067387318373..63df8a850756 100644 --- a/op-chain-ops/cmd/celo-migrate/continuity.go +++ b/op-chain-ops/cmd/celo-migrate/continuity.go @@ -1,6 +1,7 @@ package main import ( + "errors" "fmt" "github.com/ethereum/go-ethereum/common" @@ -23,7 +24,7 @@ type RLPBlockElement struct { decodedHeader *types.Header number uint64 hash []byte - header []byte // TODO(Alec): why this? + header []byte body []byte receipts []byte td []byte @@ -55,8 +56,14 @@ func (r *RLPBlockRange) DropFirst() { 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. func (r *RLPBlockRange) CheckContinuity(prevElement *RLPBlockElement) error { - for i := range r.hashes { // TODO(Alec): what if there are different lengths? + if err := r.CheckLengths(); err != nil { + return err + } + for i := range r.hashes { currElement, err := r.Element(uint64(i)) if err != nil { return err @@ -71,6 +78,31 @@ func (r *RLPBlockRange) CheckContinuity(prevElement *RLPBlockElement) error { 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 +} + +// 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()) diff --git a/op-chain-ops/cmd/celo-migrate/non-ancients.go b/op-chain-ops/cmd/celo-migrate/non-ancients.go index 24ff7ef6620e..feb4a42dec0d 100644 --- a/op-chain-ops/cmd/celo-migrate/non-ancients.go +++ b/op-chain-ops/cmd/celo-migrate/non-ancients.go @@ -1,8 +1,6 @@ package main import ( - "bytes" - "encoding/binary" "fmt" "os" "os/exec" @@ -12,7 +10,6 @@ import ( "github.com/ethereum/go-ethereum/core/rawdb" "github.com/ethereum/go-ethereum/ethdb" "github.com/ethereum/go-ethereum/log" - "github.com/ethereum/go-ethereum/rlp" ) func copyDbExceptAncients(oldDbPath, newDbPath string) error { @@ -76,7 +73,11 @@ func migrateNonAncientsDb(newDB ethdb.Database, lastBlock, numAncients, batchSiz if numAncients > 0 { // 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") - genesisBlockElement, err := readRLPBlockElement(newDB, 0, rawdb.ReadCanonicalHash(newDB, 0)) + genesisBlockRange, err := loadNonAncientRange(newDB, 0, 1) + if err != nil { + return 0, err + } + genesisBlockElement, err := genesisBlockRange.Element(0) if err != nil { return 0, err } @@ -99,6 +100,7 @@ func migrateNonAncientsDb(newDB ethdb.Database, lastBlock, numAncients, batchSiz } for i := range blockRange.hashes { + // TODO(Alec) make this cleaner? blockElement, err := blockRange.Element(uint64(i)) if err != nil { return 0, err @@ -126,17 +128,49 @@ func loadNonAncientRange(newDB ethdb.Database, start, count uint64) (*RLPBlockRa numbersHash := rawdb.ReadAllHashesInRange(newDB, start, start+count-1) // minus 1 because start is included in count + var err error + for i, numberHash := range numbersHash { - blockElement, err := readRLPBlockElement(newDB, numberHash.Number, numberHash.Hash) + // 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 read RLP block element for non-ancient block %d - %x: %w", numberHash.Number, numberHash.Hash, err) + 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) + // } - blockRange.hashes[i] = blockElement.hash - blockRange.headers[i] = blockElement.header - blockRange.bodies[i] = blockElement.body - blockRange.receipts[i] = blockElement.receipts - blockRange.tds[i] = blockElement.td + // 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) + // } } return blockRange, nil @@ -170,81 +204,3 @@ func migrateNonAncientBlock(newDB ethdb.Database, block *RLPBlockElement) error return nil } - -func readRLPBlockElement(newDB ethdb.Database, number uint64, hash common.Hash) (*RLPBlockElement, error) { - e := &RLPBlockElement{} - var err error - - 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) - - e.hash, 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) - } - e.header, err = newDB.Get(headerKey(number, hash)) - if err != nil { - return nil, fmt.Errorf("failed to read header: block %d - %x: %w", number, hash, err) - } - e.body, err = newDB.Get(blockBodyKey(number, hash)) - if err != nil { - return nil, fmt.Errorf("failed to read body: block %d - %x: %w", number, hash, err) - } - e.receipts, 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) - } - e.td, 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) - } - - // 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) - // } - - err = rlp.DecodeBytes(e.header, &e.decodedHeader) - if err != nil { - return nil, fmt.Errorf("can't decode header: %w", err) - } - return e, nil -} - -// checkOtherDataForNonAncientBlock checks that all the data that is not transformed is successfully copied for non-ancient blocks. -// I.e. receipts, total difficulty, canonical hash, and block number. -// If an error is returned, it is likely the source directory is corrupted and the migration should be restarted with a clean source directory. -func checkOtherDataForNonAncientBlock(number uint64, hash common.Hash, newDB ethdb.Database) error { - // Ensure receipts and total difficulty are present in non-ancient db - if has, err := newDB.Has(blockReceiptsKey(number, hash)); !has || err != nil { - return fmt.Errorf("failed to find receipts in newDB leveldb: block %d - %x: %w", number, hash, err) - } - if has, err := newDB.Has(headerTDKey(number, hash)); !has || err != nil { - return fmt.Errorf("failed to find total difficulty in newDB leveldb: block %d - %x: %w", number, hash, err) - } - // Ensure canonical hash and number are present in non-ancient db and that they match expected values - hashFromDB, err := newDB.Get(headerHashKey(number)) - if err != nil { - return fmt.Errorf("failed to find canonical hash in newDB leveldb: block %d - %x: %w", number, hash, err) - } - if !bytes.Equal(hashFromDB, hash[:]) { - return fmt.Errorf("canonical hash mismatch in newDB leveldb: block %d - %x: %w", number, hash, err) - } - numberFromDB, err := newDB.Get(headerNumberKey(hash)) - if err != nil { - return fmt.Errorf("failed to find number for hash 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) - } - - return nil -} diff --git a/op-chain-ops/cmd/celo-migrate/transform.go b/op-chain-ops/cmd/celo-migrate/transform.go index ef585a0b5c05..481290c2c98c 100644 --- a/op-chain-ops/cmd/celo-migrate/transform.go +++ b/op-chain-ops/cmd/celo-migrate/transform.go @@ -77,6 +77,7 @@ 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 { From 2ac2407656291aed8e8f487b2917e037f3e2df5a Mon Sep 17 00:00:00 2001 From: alecps Date: Wed, 8 Jan 2025 18:23:11 -0500 Subject: [PATCH 23/24] more refactoring --- op-chain-ops/cmd/celo-migrate/ancients.go | 24 +---- op-chain-ops/cmd/celo-migrate/continuity.go | 19 ++++ op-chain-ops/cmd/celo-migrate/non-ancients.go | 88 ++++++++----------- op-chain-ops/cmd/celo-migrate/transform.go | 17 ++++ 4 files changed, 78 insertions(+), 70 deletions(-) diff --git a/op-chain-ops/cmd/celo-migrate/ancients.go b/op-chain-ops/cmd/celo-migrate/ancients.go index c260d03c36f2..d12d2d6f34d0 100644 --- a/op-chain-ops/cmd/celo-migrate/ancients.go +++ b/op-chain-ops/cmd/celo-migrate/ancients.go @@ -75,7 +75,7 @@ func migrateAncientsDb(ctx context.Context, oldDBPath, newDBPath string, batchSi g.Go(func() error { return readAncientBlocks(ctx, oldFreezer, numAncientsNewBefore, numAncientsOld, batchSize, readChan) }) - g.Go(func() error { return transformBlocks(ctx, readChan, transformChan, numAncientsNewBefore) }) + g.Go(func() error { return transformBlocks(ctx, readChan, transformChan) }) g.Go(func() error { return writeAncientBlocks(ctx, newFreezer, transformChan, numAncientsOld) }) if err = g.Wait(); err != nil { @@ -178,7 +178,7 @@ func loadLastAncient(freezer *rawdb.Freezer, numAncients uint64) (*RLPBlockEleme return blockRange.Element(0) } -func transformBlocks(ctx context.Context, in <-chan RLPBlockRange, out chan<- RLPBlockRange, startBlock uint64) error { +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) @@ -187,24 +187,8 @@ func transformBlocks(ctx context.Context, in <-chan RLPBlockRange, out chan<- RL 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 err := checkTransformedHeader(newHeader, blockRange.hashes[i], blockNumber); err != nil { - return err - } - - blockRange.headers[i] = newHeader - blockRange.bodies[i] = newBody + if err := blockRange.Transform(); err != nil { + return err } out <- blockRange } diff --git a/op-chain-ops/cmd/celo-migrate/continuity.go b/op-chain-ops/cmd/celo-migrate/continuity.go index 63df8a850756..7f28979ba12f 100644 --- a/op-chain-ops/cmd/celo-migrate/continuity.go +++ b/op-chain-ops/cmd/celo-migrate/continuity.go @@ -59,6 +59,8 @@ func (r *RLPBlockRange) DropFirst() { // 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 @@ -101,6 +103,23 @@ func (r *RLPBlockRange) CheckLengths() error { 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 { diff --git a/op-chain-ops/cmd/celo-migrate/non-ancients.go b/op-chain-ops/cmd/celo-migrate/non-ancients.go index feb4a42dec0d..efee70a58c60 100644 --- a/op-chain-ops/cmd/celo-migrate/non-ancients.go +++ b/op-chain-ops/cmd/celo-migrate/non-ancients.go @@ -73,47 +73,40 @@ func migrateNonAncientsDb(newDB ethdb.Database, lastBlock, numAncients, batchSiz if numAncients > 0 { // 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") - genesisBlockRange, err := loadNonAncientRange(newDB, 0, 1) - if err != nil { - return 0, err - } - genesisBlockElement, err := genesisBlockRange.Element(0) - if err != nil { - return 0, err - } - if err := migrateNonAncientBlock(newDB, genesisBlockElement); err != nil { + if err := migrateNonAncientBlocks(newDB, 0, 1, nil); err != nil { return 0, err } } - prevBlockElement := lastAncient + prevBlockElement := *lastAncient for i := numAncients; i <= lastBlock; i += batchSize { - blockRange, err := loadNonAncientRange(newDB, i, batchSize) - if err != nil { + if err := migrateNonAncientBlocks(newDB, i, batchSize, &prevBlockElement); err != nil { return 0, err } + } - log.Info("Processing Block Range", "process", "non-ancients", "from", i, "to(inclusve)", i+batchSize-1, "count", len(blockRange.hashes)) + migratedCount := lastBlock - numAncients + 1 + return migratedCount, nil +} - if err := blockRange.CheckContinuity(prevBlockElement); err != nil { - return 0, fmt.Errorf("failed continuity check for non-ancient blocks: %w", err) - } +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) - for i := range blockRange.hashes { - // TODO(Alec) make this cleaner? - blockElement, err := blockRange.Element(uint64(i)) - if err != nil { - return 0, err - } - if err = migrateNonAncientBlock(newDB, blockElement); err != nil { - return 0, err - } - prevBlockElement = blockElement - } + blockRange, err := loadNonAncientRange(newDB, start, count) + if err != nil { + return 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 } - migratedCount := lastBlock - numAncients + 1 - return migratedCount, nil + return nil } func loadNonAncientRange(newDB ethdb.Database, start, count uint64) (*RLPBlockRange, error) { @@ -176,31 +169,26 @@ func loadNonAncientRange(newDB ethdb.Database, start, count uint64) (*RLPBlockRa return blockRange, nil } -func migrateNonAncientBlock(newDB ethdb.Database, block *RLPBlockElement) error { - // transform header and body - newHeader, err := transformHeader(block.header) - if err != nil { - return fmt.Errorf("failed to transform header: block %d - %x: %w", block.number, block.hash, err) - } - newBody, err := transformBlockBody(block.body) - if err != nil { - return fmt.Errorf("failed to transform body: block %d - %x: %w", block.number, block.hash, err) - } - - if err := checkTransformedHeader(newHeader, block.hash[:], block.number); err != nil { - return err - } - - // write header and body - hash := common.BytesToHash(block.hash) +// 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, block.number, newBody) - if err := batch.Put(headerKey(block.number, hash), newHeader); err != nil { - return fmt.Errorf("failed to write header: block %d - %x: %w", block.number, block.hash, err) + 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 { - return fmt.Errorf("failed to write header and body: block %d - %x: %w", block.number, block.hash, err) + return fmt.Errorf("failed to write header and body: block %d - %x: %w", number, hash, err) } 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 481290c2c98c..f848dde6c957 100644 --- a/op-chain-ops/cmd/celo-migrate/transform.go +++ b/op-chain-ops/cmd/celo-migrate/transform.go @@ -125,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 +} From b3ec85c0a77f708def5fec4f69a20a844c23093a Mon Sep 17 00:00:00 2001 From: alecps Date: Thu, 9 Jan 2025 19:29:46 -0500 Subject: [PATCH 24/24] get rid of numAncients variable in favor of lastAncient block element --- op-chain-ops/cmd/celo-migrate/ancients.go | 38 +++++++++---------- op-chain-ops/cmd/celo-migrate/main.go | 31 ++++++++------- op-chain-ops/cmd/celo-migrate/non-ancients.go | 8 ++-- 3 files changed, 38 insertions(+), 39 deletions(-) diff --git a/op-chain-ops/cmd/celo-migrate/ancients.go b/op-chain-ops/cmd/celo-migrate/ancients.go index d12d2d6f34d0..d1291767bc5b 100644 --- a/op-chain-ops/cmd/celo-migrate/ancients.go +++ b/op-chain-ops/cmd/celo-migrate/ancients.go @@ -28,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, lastAncient *RLPBlockElement, 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, nil, 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()) @@ -41,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, nil, 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()) @@ -49,21 +49,21 @@ func migrateAncientsDb(ctx context.Context, oldDBPath, newDBPath string, batchSi numAncientsOld, err := oldFreezer.Ancients() if err != nil { - return 0, 0, nil, 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, nil, 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) - lastAncient, err = loadLastAncient(newFreezer, numAncientsNewBefore) + lastAncient, err = loadLastAncient(newFreezer) if err != nil { - return 0, 0, nil, err + return 0, nil, err } - return numAncientsNewBefore, numAncientsNewBefore, lastAncient, nil + return numAncientsNewBefore, lastAncient, nil } log.Info("Ancient Block Migration Started", "process", "ancients", "startBlock", numAncientsNewBefore, "endBlock", numAncientsOld-1, "count", numAncientsOld-numAncientsNewBefore, "step", batchSize) @@ -79,25 +79,21 @@ 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, nil, 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, nil, 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, nil, fmt.Errorf("failed to migrate all ancients from old to new db. Expected %d, got %d", numAncientsOld, numAncientsNewAfter) - } - - lastAncient, err = loadLastAncient(newFreezer, numAncientsNewAfter) - if err != nil { - return 0, 0, nil, err + 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, lastAncient, nil + return numAncientsNewBefore, lastAncient, nil } func readAncientBlocks(ctx context.Context, freezer *rawdb.Freezer, startBlock, endBlock, batchSize uint64, out chan<- RLPBlockRange) error { @@ -170,7 +166,11 @@ func loadAncientRange(freezer *rawdb.Freezer, start, count uint64) (*RLPBlockRan } // Get the last ancient block data so we can check for continuity between ancients and non-ancients -func loadLastAncient(freezer *rawdb.Freezer, numAncients uint64) (*RLPBlockElement, error) { +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 diff --git a/op-chain-ops/cmd/celo-migrate/main.go b/op-chain-ops/cmd/celo-migrate/main.go index 22fbc73f18f3..bc68c6c4761f 100644 --- a/op-chain-ops/cmd/celo-migrate/main.go +++ b/op-chain-ops/cmd/celo-migrate/main.go @@ -194,7 +194,7 @@ func main() { Usage: "Perform a pre-migration of ancient blocks and copy over all other data without transforming it. This should be run a day before the full migration command is run to minimize downtime.", Flags: preMigrationFlags, Action: func(ctx *cli.Context) error { - if _, _, _, err := runPreMigration(parsePreMigrationOptions(ctx)); err != nil { + if _, _, err := runPreMigration(parsePreMigrationOptions(ctx)); err != nil { return fmt.Errorf("failed to run pre-migration: %w", err) } log.Info("Finished pre migration successfully!") @@ -245,15 +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, lastAncient, 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, lastAncient); 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 { @@ -265,14 +264,14 @@ func runFullMigration(opts fullMigrationOptions) error { return nil } -func runPreMigration(opts preMigrationOptions) ([]*rawdb.NumberHash, uint64, *RLPBlockElement, 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, nil, 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 @@ -280,22 +279,21 @@ func runPreMigration(opts preMigrationOptions) ([]*rawdb.NumberHash, uint64, *RL var err error if err = createNewDbPathIfNotExists(opts.newDBPath); err != nil { - return nil, 0, nil, 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, nil, 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, lastAncient, 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 @@ -311,16 +309,17 @@ func runPreMigration(opts preMigrationOptions) ([]*rawdb.NumberHash, uint64, *RL }) if err = g.Wait(); err != nil { - return nil, 0, nil, 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, lastAncient, nil + return strayAncientBlocks, lastAncient, nil } -// TODO(Alec) do we need to pass numAncients here? -func runNonAncientMigration(newDBPath string, strayAncientBlocks []*rawdb.NumberHash, batchSize, numAncients uint64, lastAncient *RLPBlockElement) (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) @@ -335,10 +334,10 @@ func runNonAncientMigration(newDBPath string, strayAncientBlocks []*rawdb.Number hash := rawdb.ReadHeadHeaderHash(newDB) lastBlock := *rawdb.ReadHeaderNumber(newDB, hash) - log.Info("Non-Ancient Block Migration Started", "process", "non-ancients", "newDBPath", newDBPath, "batchSize", batchSize, "startBlock", numAncients, "endBlock", lastBlock, "count", lastBlock-lastAncient.number, "lastAncientBlock", lastAncient.number) + 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, lastAncient); 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 efee70a58c60..a5692ac91344 100644 --- a/op-chain-ops/cmd/celo-migrate/non-ancients.go +++ b/op-chain-ops/cmd/celo-migrate/non-ancients.go @@ -62,7 +62,7 @@ func copyDbExceptAncients(oldDbPath, newDbPath string) error { return nil } -func migrateNonAncientsDb(newDB ethdb.Database, lastBlock, numAncients, batchSize uint64, lastAncient *RLPBlockElement) (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 for the historical bad blocks. AFAICS bad blocks @@ -70,7 +70,7 @@ func migrateNonAncientsDb(newDB ethdb.Database, lastBlock, numAncients, batchSiz // in these old bad blocks. rawdb.DeleteBadBlocks(newDB) - 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 := migrateNonAncientBlocks(newDB, 0, 1, nil); err != nil { @@ -79,13 +79,13 @@ func migrateNonAncientsDb(newDB ethdb.Database, lastBlock, numAncients, batchSiz } prevBlockElement := *lastAncient - for i := numAncients; i <= lastBlock; i += batchSize { + 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 }