Add full monorepo: virtual-banker, backend, frontend, docs, scripts, deployment
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
127
backend/indexer/reorg/reorg.go
Normal file
127
backend/indexer/reorg/reorg.go
Normal file
@@ -0,0 +1,127 @@
|
||||
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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user