Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Continuity Check Script to celo-migrate #282

Merged
merged 14 commits into from
Jan 28, 2025
2 changes: 1 addition & 1 deletion op-chain-ops/cmd/celo-migrate/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ If node operators would like to practice a `full migration` they can do so and r
> [!IMPORTANT]
> The pre-migration should be run using a chaindata snapshot, rather than a db that is being used by a node. To avoid network downtime, we recommend that node operators do not stop any nodes in order to perform the pre-migration.

Node operators should inspect their migration logs after the dry-run to ensure the migration completed succesfully and direct any questions to the Celo developer community on Discord before the actual migration.
Node operators should inspect their migration logs after the dry-run to ensure the migration completed successfully and direct any questions to the Celo developer community on Discord before the actual migration.

##### Final migration

Expand Down
98 changes: 56 additions & 42 deletions op-chain-ops/cmd/celo-migrate/ancients.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,6 @@ import (
"golang.org/x/sync/errgroup"
)

// RLPBlockRange is a range of blocks in RLP format
type RLPBlockRange struct {
start uint64
hashes [][]byte
headers [][]byte
bodies [][]byte
receipts [][]byte
tds [][]byte
}

// NewChainFreezer is a small utility method around NewFreezer that sets the
// default parameters for the chain storage.
func NewChainFreezer(datadir string, namespace string, readonly bool) (*rawdb.Freezer, error) {
Expand Down Expand Up @@ -104,56 +94,67 @@ 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)

for i := startBlock; i < endBlock; i += batchSize {
count := min(batchSize, endBlock-i+1)
count := min(batchSize, endBlock-i)
alecps marked this conversation as resolved.
Show resolved Hide resolved
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),
}
var err error

blockRange.hashes, err = freezer.AncientRange(rawdb.ChainFreezerHashTable, start, count, 0)
blockRange, err := loadAncientRange(freezer, start, count)
alecps marked this conversation as resolved.
Show resolved Hide resolved
if err != nil {
return fmt.Errorf("failed to read hashes from old freezer: %w", err)
}
blockRange.headers, err = freezer.AncientRange(rawdb.ChainFreezerHeaderTable, start, count, 0)
if err != nil {
return fmt.Errorf("failed to read headers from old freezer: %w", err)
}
blockRange.bodies, err = freezer.AncientRange(rawdb.ChainFreezerBodiesTable, start, count, 0)
if err != nil {
return fmt.Errorf("failed to read bodies from old freezer: %w", err)
}
blockRange.receipts, err = freezer.AncientRange(rawdb.ChainFreezerReceiptTable, start, count, 0)
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)
}

select {
case <-ctx.Done():
return ctx.Err()
case out <- blockRange:
case out <- *blockRange:
}

log.Info("Read ancient blocks", "start", start, "end", start+count-1, "count", count)
}
return nil
}

func loadAncientRange(freezer *rawdb.Freezer, start, count uint64) (*RLPBlockRange, error) {
log.Info("Loading ancient block range", "start", start, "end", start+count-1, "count", count)

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 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 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 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 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 freezer: %w", err)
}

return blockRange, nil
}

func transformBlocks(ctx context.Context, in <-chan RLPBlockRange, out chan<- RLPBlockRange) error {
// Transform blocks from the in channel and send them to the out channel
defer close(out)

for blockRange := range in {
for i := range blockRange.hashes {
blockNumber := blockRange.start + uint64(i)
Expand Down Expand Up @@ -217,7 +218,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))
alecps marked this conversation as resolved.
Show resolved Hide resolved
}
}
return nil
Expand All @@ -242,3 +243,16 @@ func getStrayAncientBlocks(dbPath string) (blocks []*rawdb.NumberHash, err error

return rawdb.ReadAllHashesInRange(db, 1, numAncients-1), nil
}

// Get the last ancient block data so we can check for continuity between ancients and non-ancients
func loadLastAncient(freezer *rawdb.Freezer) (*RLPBlockElement, error) {
numAncients, err := freezer.Ancients()
if err != nil {
return nil, fmt.Errorf("failed to get number of ancients in freezer: %w", err)
}
blockRange, err := loadAncientRange(freezer, numAncients-1, 1)
if err != nil {
return nil, err
}
return blockRange.Element(0)
}
131 changes: 131 additions & 0 deletions op-chain-ops/cmd/celo-migrate/continuity.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package main

import (
"errors"
"fmt"

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"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
hash []byte
header []byte
body []byte
receipts []byte
td []byte
}

// 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) (err error) {
if e.Number() != prev.Number()+1 {
err = errors.Join(err, fmt.Errorf("header number mismatch indicating a gap in block numbers: expected %d, actual %d", prev.Number()+1, e.Number()))
}
// 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.BytesToHash(prev.hash) {
err = errors.Join(err, fmt.Errorf("parent hash mismatch between blocks %d and %d", e.Number(), prev.Number()))
}
return err
}

func (e *RLPBlockElement) Header() *types.Header {
return e.decodedHeader
}

func (e *RLPBlockElement) Number() uint64 {
return e.Header().Number.Uint64()
}

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
}

// 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 the last element in the preceding range, and returns the last element
// in the current range so that continuity can be checked across ranges.
func (r *RLPBlockRange) CheckContinuity(prevElement *RLPBlockElement, expectedLength uint64) (*RLPBlockElement, error) {
log.Info("Checking data continuity for block range",
"start", r.start,
"end", r.start+expectedLength-1,
"count", expectedLength,
"prevElement", func() interface{} {
if prevElement != nil {
return prevElement.Number()
}
return "nil"
}(),
)

if err := r.CheckLengths(expectedLength); err != nil {
return nil, err
}
for i := range r.hashes {
currElement, err := r.Element(uint64(i))
if err != nil {
return nil, err
}
if currElement.Number() != r.start+uint64(i) {
return nil, fmt.Errorf("decoded header number mismatch indicating a gap in block numbers: expected %d, actual %d", r.start+uint64(i), currElement.Number())
}
if prevElement != nil {
log.Debug("Checking continuity", "block", currElement.Number(), "prev", prevElement.Number())
if err := currElement.Follows(prevElement); err != nil {
return nil, err
}
}
prevElement = currElement
}
return prevElement, nil
}

// CheckLengths makes sure the number of elements retrieved from each table is the same
func (r *RLPBlockRange) CheckLengths(expectedLength uint64) error {
var err error
if uint64(len(r.hashes)) != expectedLength {
err = fmt.Errorf("Unexpected number of hashes for block range: expected %d, actual %d", expectedLength, len(r.hashes))
}
if uint64(len(r.bodies)) != expectedLength {
err = errors.Join(err, fmt.Errorf("Unexpected number of bodies for block range: expected %d, actual %d", expectedLength, len(r.bodies)))
}
if uint64(len(r.headers)) != expectedLength {
err = errors.Join(err, fmt.Errorf("Unexpected number of headers for block range: expected %d, actual %d", expectedLength, len(r.headers)))
}
if uint64(len(r.receipts)) != expectedLength {
err = errors.Join(err, fmt.Errorf("Unexpected number of receipts for block range: expected %d, actual %d", expectedLength, len(r.receipts)))
}
if uint64(len(r.tds)) != expectedLength {
err = errors.Join(err, fmt.Errorf("Unexpected number of total difficulties for block range: expected %d, actual %d", expectedLength, len(r.tds)))
}
return err
}
Loading
Loading