Files
explorer-monorepo/backend/indexer/reorg/reorg.go

128 lines
3.0 KiB
Go

package reorg
import (
"context"
"fmt"
"log"
"math/big"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/jackc/pgx/v5/pgxpool"
)
// ReorgHandler handles blockchain reorganizations
type ReorgHandler struct {
db *pgxpool.Pool
client *ethclient.Client
chainID int
}
// NewReorgHandler creates a new reorg handler
func NewReorgHandler(db *pgxpool.Pool, client *ethclient.Client, chainID int) *ReorgHandler {
return &ReorgHandler{
db: db,
client: client,
chainID: chainID,
}
}
// DetectReorg detects if a reorg has occurred
func (rh *ReorgHandler) DetectReorg(ctx context.Context, blockNumber int64) (bool, int64, error) {
// Get stored block hash
var storedHash string
query := `SELECT hash FROM blocks WHERE chain_id = $1 AND number = $2`
err := rh.db.QueryRow(ctx, query, rh.chainID, blockNumber).Scan(&storedHash)
if err != nil {
return false, 0, err
}
// Get current block hash from chain
block, err := rh.client.BlockByNumber(ctx, big.NewInt(blockNumber))
if err != nil {
return false, 0, err
}
currentHash := block.Hash().Hex()
// Compare hashes
if storedHash != currentHash {
// Reorg detected, find common ancestor
commonAncestor, err := rh.findCommonAncestor(ctx, blockNumber)
if err != nil {
return false, 0, err
}
return true, commonAncestor, nil
}
return false, 0, nil
}
// findCommonAncestor finds the common ancestor block
func (rh *ReorgHandler) findCommonAncestor(ctx context.Context, startBlock int64) (int64, error) {
// Binary search to find common ancestor
low := int64(0)
high := startBlock
for low <= high {
mid := (low + high) / 2
var storedHash string
err := rh.db.QueryRow(ctx,
`SELECT hash FROM blocks WHERE chain_id = $1 AND number = $2`,
rh.chainID, mid,
).Scan(&storedHash)
if err != nil {
high = mid - 1
continue
}
block, err := rh.client.BlockByNumber(ctx, big.NewInt(mid))
if err != nil {
high = mid - 1
continue
}
if storedHash == block.Hash().Hex() {
low = mid + 1
} else {
high = mid - 1
}
}
return high, nil
}
// HandleReorg handles a detected reorg
func (rh *ReorgHandler) HandleReorg(ctx context.Context, commonAncestor int64) error {
log.Printf("Handling reorg: common ancestor at block %d", commonAncestor)
tx, err := rh.db.Begin(ctx)
if err != nil {
return fmt.Errorf("failed to begin transaction: %w", err)
}
defer tx.Rollback(ctx)
// Mark blocks as orphaned
_, err = tx.Exec(ctx, `
UPDATE blocks
SET orphaned = true, orphaned_at = NOW()
WHERE chain_id = $1 AND number > $2 AND orphaned = false
`, rh.chainID, commonAncestor)
if err != nil {
return fmt.Errorf("failed to mark blocks as orphaned: %w", err)
}
// Delete orphaned data (cascade will handle related records)
_, err = tx.Exec(ctx, `
DELETE FROM blocks
WHERE chain_id = $1 AND number > $2 AND orphaned = true
`, rh.chainID, commonAncestor)
if err != nil {
return fmt.Errorf("failed to delete orphaned blocks: %w", err)
}
return tx.Commit(ctx)
}