Skip to content

Commit

Permalink
add more checks for non-transformed data
Browse files Browse the repository at this point in the history
  • Loading branch information
alecps committed Dec 17, 2024
1 parent d6a326e commit 3bb956f
Show file tree
Hide file tree
Showing 4 changed files with 143 additions and 11 deletions.
52 changes: 49 additions & 3 deletions op-chain-ops/cmd/celo-migrate/ancients.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ package main

import (
"context"
"errors"
"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"
)

Expand All @@ -22,6 +25,43 @@ 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
}

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) {
Expand Down Expand Up @@ -135,6 +175,11 @@ 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 {
log.Error("Failed to ensure block range has no gaps", "error", err)
// return fmt.Errorf("failed to ensure block range has no gaps: %w", err)
}

out <- blockRange
}
}
Expand All @@ -145,7 +190,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 := startBlock - 1 // Will underflow when startBlock is 0, but then overflow back to 0

for blockRange := range in {
select {
Expand All @@ -155,8 +200,9 @@ 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)
// TODO(Alec)
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
prevBlockNumber = blockNumber
Expand Down
33 changes: 32 additions & 1 deletion op-chain-ops/cmd/celo-migrate/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
if _, err := os.Stat(chaindataPath); errors.Is(err, os.ErrNotExist) {
Expand Down
4 changes: 3 additions & 1 deletion op-chain-ops/cmd/celo-migrate/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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")
},
}
Expand Down
65 changes: 59 additions & 6 deletions op-chain-ops/cmd/celo-migrate/non-ancients.go
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
package main

import (
"bytes"
"fmt"
"os"
"os/exec"
"strings"

"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 {
Expand Down Expand Up @@ -83,12 +86,16 @@ 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 != 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

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)
}
}
}
Expand All @@ -99,8 +106,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)
Expand All @@ -112,18 +125,58 @@ 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
batch := newDB.NewBatch()
rawdb.WriteBodyRLP(batch, hash, number, newBody)
_ = batch.Put(headerKey(number, hash), newHeader)
err = batch.Put(headerKey(number, hash), newHeader)
if err := batch.Write(); err != nil {
return fmt.Errorf("failed to write header and body: block %d - %x: %w", number, hash, err)
}

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
}

0 comments on commit 3bb956f

Please sign in to comment.