From 469727ce5855e48083453709de432a1b9ffe220d Mon Sep 17 00:00:00 2001 From: alecps Date: Mon, 9 Dec 2024 15:21:41 -0500 Subject: [PATCH 01/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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/18] 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)