Files
mev-beta/orig/internal/recovery/fallback_provider.go
Administrator 803de231ba feat: create v2-prep branch with comprehensive planning
Restructured project for V2 refactor:

**Structure Changes:**
- Moved all V1 code to orig/ folder (preserved with git mv)
- Created docs/planning/ directory
- Added orig/README_V1.md explaining V1 preservation

**Planning Documents:**
- 00_V2_MASTER_PLAN.md: Complete architecture overview
  - Executive summary of critical V1 issues
  - High-level component architecture diagrams
  - 5-phase implementation roadmap
  - Success metrics and risk mitigation

- 07_TASK_BREAKDOWN.md: Atomic task breakdown
  - 99+ hours of detailed tasks
  - Every task < 2 hours (atomic)
  - Clear dependencies and success criteria
  - Organized by implementation phase

**V2 Key Improvements:**
- Per-exchange parsers (factory pattern)
- Multi-layer strict validation
- Multi-index pool cache
- Background validation pipeline
- Comprehensive observability

**Critical Issues Addressed:**
- Zero address tokens (strict validation + cache enrichment)
- Parsing accuracy (protocol-specific parsers)
- No audit trail (background validation channel)
- Inefficient lookups (multi-index cache)
- Stats disconnection (event-driven metrics)

Next Steps:
1. Review planning documents
2. Begin Phase 1: Foundation (P1-001 through P1-010)
3. Implement parsers in Phase 2
4. Build cache system in Phase 3
5. Add validation pipeline in Phase 4
6. Migrate and test in Phase 5

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-11-10 10:14:26 +01:00

385 lines
12 KiB
Go

package recovery
import (
"context"
"fmt"
"sync"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/fraktal/mev-beta/internal/logger"
"github.com/fraktal/mev-beta/internal/registry"
)
// DefaultFallbackProvider implements FallbackDataProvider with multiple data sources
type DefaultFallbackProvider struct {
mu sync.RWMutex
logger *logger.Logger
contractRegistry *registry.ContractRegistry
staticTokenData map[common.Address]*FallbackTokenInfo
staticPoolData map[common.Address]*FallbackPoolInfo
cacheTimeout time.Duration
enabled bool
}
// NewDefaultFallbackProvider creates a new fallback data provider
func NewDefaultFallbackProvider(logger *logger.Logger, contractRegistry *registry.ContractRegistry) *DefaultFallbackProvider {
provider := &DefaultFallbackProvider{
logger: logger,
contractRegistry: contractRegistry,
staticTokenData: make(map[common.Address]*FallbackTokenInfo),
staticPoolData: make(map[common.Address]*FallbackPoolInfo),
cacheTimeout: 5 * time.Minute,
enabled: true,
}
// Initialize with known safe data
provider.initializeStaticData()
return provider
}
// initializeStaticData populates the provider with known good data for critical Arbitrum contracts
func (fp *DefaultFallbackProvider) initializeStaticData() {
fp.mu.Lock()
defer fp.mu.Unlock()
// Major Arbitrum tokens with verified addresses
fp.staticTokenData[common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1")] = &FallbackTokenInfo{
Address: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"),
Symbol: "WETH",
Name: "Wrapped Ether",
Decimals: 18,
IsVerified: true,
Source: "static_fallback",
Confidence: 1.0,
}
fp.staticTokenData[common.HexToAddress("0xaf88d065e77c8cC2239327C5EDb3A432268e5831")] = &FallbackTokenInfo{
Address: common.HexToAddress("0xaf88d065e77c8cC2239327C5EDb3A432268e5831"),
Symbol: "USDC",
Name: "USD Coin",
Decimals: 6,
IsVerified: true,
Source: "static_fallback",
Confidence: 1.0,
}
fp.staticTokenData[common.HexToAddress("0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9")] = &FallbackTokenInfo{
Address: common.HexToAddress("0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"),
Symbol: "USDT",
Name: "Tether USD",
Decimals: 6,
IsVerified: true,
Source: "static_fallback",
Confidence: 1.0,
}
fp.staticTokenData[common.HexToAddress("0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f")] = &FallbackTokenInfo{
Address: common.HexToAddress("0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f"),
Symbol: "WBTC",
Name: "Wrapped BTC",
Decimals: 8,
IsVerified: true,
Source: "static_fallback",
Confidence: 1.0,
}
fp.staticTokenData[common.HexToAddress("0x912CE59144191C1204E64559FE8253a0e49E6548")] = &FallbackTokenInfo{
Address: common.HexToAddress("0x912CE59144191C1204E64559FE8253a0e49E6548"),
Symbol: "ARB",
Name: "Arbitrum",
Decimals: 18,
IsVerified: true,
Source: "static_fallback",
Confidence: 1.0,
}
// High-volume Uniswap V3 pools with verified addresses and token pairs
fp.staticPoolData[common.HexToAddress("0xC6962004f452bE9203591991D15f6b388e09E8D0")] = &FallbackPoolInfo{
Address: common.HexToAddress("0xC6962004f452bE9203591991D15f6b388e09E8D0"),
Token0: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), // WETH
Token1: common.HexToAddress("0xaf88d065e77c8cC2239327C5EDb3A432268e5831"), // USDC
Protocol: "UniswapV3",
Fee: 500, // 0.05%
IsVerified: true,
Source: "static_fallback",
Confidence: 1.0,
}
fp.staticPoolData[common.HexToAddress("0x641C00A822e8b671738d32a431a4Fb6074E5c79d")] = &FallbackPoolInfo{
Address: common.HexToAddress("0x641C00A822e8b671738d32a431a4Fb6074E5c79d"),
Token0: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), // WETH
Token1: common.HexToAddress("0xaf88d065e77c8cC2239327C5EDb3A432268e5831"), // USDC
Protocol: "UniswapV3",
Fee: 3000, // 0.3%
IsVerified: true,
Source: "static_fallback",
Confidence: 1.0,
}
fp.staticPoolData[common.HexToAddress("0x17c14D2c404D167802b16C450d3c99F88F2c4F4d")] = &FallbackPoolInfo{
Address: common.HexToAddress("0x17c14D2c404D167802b16C450d3c99F88F2c4F4d"),
Token0: common.HexToAddress("0xaf88d065e77c8cC2239327C5EDb3A432268e5831"), // USDC
Token1: common.HexToAddress("0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9"), // USDT
Protocol: "UniswapV3",
Fee: 100, // 0.01%
IsVerified: true,
Source: "static_fallback",
Confidence: 1.0,
}
fp.staticPoolData[common.HexToAddress("0x2f5e87C032bc4F8526F320c012A4e678F1fa6cAB")] = &FallbackPoolInfo{
Address: common.HexToAddress("0x2f5e87C032bc4F8526F320c012A4e678F1fa6cAB"),
Token0: common.HexToAddress("0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f"), // WBTC
Token1: common.HexToAddress("0x82aF49447D8a07e3bd95BD0d56f35241523fBab1"), // WETH
Protocol: "UniswapV3",
Fee: 500, // 0.05%
IsVerified: true,
Source: "static_fallback",
Confidence: 1.0,
}
fp.logger.Info("Initialized fallback provider with static data",
"tokens", len(fp.staticTokenData),
"pools", len(fp.staticPoolData))
}
// GetFallbackTokenInfo provides fallback token information
func (fp *DefaultFallbackProvider) GetFallbackTokenInfo(ctx context.Context, address common.Address) (*FallbackTokenInfo, error) {
if !fp.enabled {
return nil, fmt.Errorf("fallback provider disabled")
}
fp.mu.RLock()
defer fp.mu.RUnlock()
// First, try static data
if tokenInfo, exists := fp.staticTokenData[address]; exists {
fp.logger.Debug("Fallback token info from static data",
"address", address.Hex(),
"symbol", tokenInfo.Symbol,
"source", tokenInfo.Source)
return tokenInfo, nil
}
// Second, try contract registry if available
if fp.contractRegistry != nil {
if contractInfo, err := fp.contractRegistry.GetContractInfo(ctx, address); err == nil && contractInfo != nil {
tokenInfo := &FallbackTokenInfo{
Address: address,
Symbol: contractInfo.Symbol,
Name: contractInfo.Name,
Decimals: contractInfo.Decimals,
IsVerified: contractInfo.IsVerified,
Source: "contract_registry",
Confidence: contractInfo.Confidence,
}
fp.logger.Debug("Fallback token info from registry",
"address", address.Hex(),
"symbol", tokenInfo.Symbol,
"confidence", tokenInfo.Confidence)
return tokenInfo, nil
}
}
// Third, provide minimal safe fallback for unknown tokens
tokenInfo := &FallbackTokenInfo{
Address: address,
Symbol: fmt.Sprintf("UNK_%s", address.Hex()[:8]),
Name: "Unknown Token",
Decimals: 18, // Safe default
IsVerified: false,
Source: "generated_fallback",
Confidence: 0.1,
}
fp.logger.Warn("Using generated fallback token info",
"address", address.Hex(),
"symbol", tokenInfo.Symbol)
return tokenInfo, nil
}
// GetFallbackPoolInfo provides fallback pool information
func (fp *DefaultFallbackProvider) GetFallbackPoolInfo(ctx context.Context, address common.Address) (*FallbackPoolInfo, error) {
if !fp.enabled {
return nil, fmt.Errorf("fallback provider disabled")
}
fp.mu.RLock()
defer fp.mu.RUnlock()
// First, try static data
if poolInfo, exists := fp.staticPoolData[address]; exists {
fp.logger.Debug("Fallback pool info from static data",
"address", address.Hex(),
"protocol", poolInfo.Protocol,
"token0", poolInfo.Token0.Hex(),
"token1", poolInfo.Token1.Hex())
return poolInfo, nil
}
// Second, try contract registry if available
if fp.contractRegistry != nil {
if poolInfo := fp.contractRegistry.GetPoolInfo(address); poolInfo != nil {
fallbackInfo := &FallbackPoolInfo{
Address: address,
Token0: poolInfo.Token0,
Token1: poolInfo.Token1,
Protocol: poolInfo.Protocol,
Fee: poolInfo.Fee,
IsVerified: poolInfo.IsVerified,
Source: "contract_registry",
Confidence: poolInfo.Confidence,
}
fp.logger.Debug("Fallback pool info from registry",
"address", address.Hex(),
"protocol", fallbackInfo.Protocol,
"confidence", fallbackInfo.Confidence)
return fallbackInfo, nil
}
}
// No fallback available for unknown pools - return error
return nil, fmt.Errorf("no fallback data available for pool %s", address.Hex())
}
// GetFallbackContractType provides fallback contract type information
func (fp *DefaultFallbackProvider) GetFallbackContractType(ctx context.Context, address common.Address) (string, error) {
if !fp.enabled {
return "", fmt.Errorf("fallback provider disabled")
}
fp.mu.RLock()
defer fp.mu.RUnlock()
// Check if it's a known token
if _, exists := fp.staticTokenData[address]; exists {
return "ERC20", nil
}
// Check if it's a known pool
if _, exists := fp.staticPoolData[address]; exists {
return "Pool", nil
}
// Try contract registry
if fp.contractRegistry != nil {
if contractInfo, err := fp.contractRegistry.GetContractInfo(ctx, address); err == nil && contractInfo != nil {
return contractInfo.Type.String(), nil
}
}
// Default to unknown
return "Unknown", nil
}
// AddStaticTokenData adds static token data for fallback use
func (fp *DefaultFallbackProvider) AddStaticTokenData(address common.Address, info *FallbackTokenInfo) {
fp.mu.Lock()
defer fp.mu.Unlock()
fp.staticTokenData[address] = info
fp.logger.Debug("Added static token data",
"address", address.Hex(),
"symbol", info.Symbol)
}
// AddStaticPoolData adds static pool data for fallback use
func (fp *DefaultFallbackProvider) AddStaticPoolData(address common.Address, info *FallbackPoolInfo) {
fp.mu.Lock()
defer fp.mu.Unlock()
fp.staticPoolData[address] = info
fp.logger.Debug("Added static pool data",
"address", address.Hex(),
"protocol", info.Protocol)
}
// IsAddressKnown checks if an address is in the static fallback data
func (fp *DefaultFallbackProvider) IsAddressKnown(address common.Address) bool {
fp.mu.RLock()
defer fp.mu.RUnlock()
_, isToken := fp.staticTokenData[address]
_, isPool := fp.staticPoolData[address]
return isToken || isPool
}
// GetKnownAddresses returns all known addresses in the fallback provider
func (fp *DefaultFallbackProvider) GetKnownAddresses() (tokens []common.Address, pools []common.Address) {
fp.mu.RLock()
defer fp.mu.RUnlock()
for addr := range fp.staticTokenData {
tokens = append(tokens, addr)
}
for addr := range fp.staticPoolData {
pools = append(pools, addr)
}
return tokens, pools
}
// ValidateAddressWithFallback performs validation using fallback data
func (fp *DefaultFallbackProvider) ValidateAddressWithFallback(ctx context.Context, address common.Address, expectedType string) (bool, float64, error) {
if !fp.enabled {
return false, 0.0, fmt.Errorf("fallback provider disabled")
}
// Check if address is known in our static data
if fp.IsAddressKnown(address) {
actualType, err := fp.GetFallbackContractType(ctx, address)
if err != nil {
return false, 0.0, err
}
if actualType == expectedType {
return true, 1.0, nil // High confidence for known addresses
}
return false, 0.0, fmt.Errorf("type mismatch: expected %s, got %s", expectedType, actualType)
}
// For unknown addresses, provide low confidence validation
return true, 0.3, nil // Allow with low confidence
}
// GetStats returns statistics about the fallback provider
func (fp *DefaultFallbackProvider) GetStats() map[string]interface{} {
fp.mu.RLock()
defer fp.mu.RUnlock()
return map[string]interface{}{
"enabled": fp.enabled,
"static_tokens_count": len(fp.staticTokenData),
"static_pools_count": len(fp.staticPoolData),
"cache_timeout": fp.cacheTimeout.String(),
"has_registry": fp.contractRegistry != nil,
}
}
// Enable enables the fallback provider
func (fp *DefaultFallbackProvider) Enable() {
fp.mu.Lock()
defer fp.mu.Unlock()
fp.enabled = true
fp.logger.Info("Fallback provider enabled")
}
// Disable disables the fallback provider
func (fp *DefaultFallbackProvider) Disable() {
fp.mu.Lock()
defer fp.mu.Unlock()
fp.enabled = false
fp.logger.Info("Fallback provider disabled")
}