128 lines
3.0 KiB
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)
|
|
}
|
|
|