Files
mev-beta/pkg/security/security_manager.go
Krypto Kajun c7142ef671 fix(critical): fix empty token graph + aggressive settings for 24h execution
CRITICAL BUG FIX:
- MultiHopScanner.updateTokenGraph() was EMPTY - adding no pools!
- Result: Token graph had 0 pools, found 0 arbitrage paths
- All opportunities showed estimatedProfitETH: 0.000000

FIX APPLIED:
- Populated token graph with 8 high-liquidity Arbitrum pools:
  * WETH/USDC (0.05% and 0.3% fees)
  * USDC/USDC.e (0.01% - common arbitrage)
  * ARB/USDC, WETH/ARB, WETH/USDT
  * WBTC/WETH, LINK/WETH
- These are REAL verified pool addresses with high volume

AGGRESSIVE THRESHOLD CHANGES:
- Min profit: 0.0001 ETH → 0.00001 ETH (10x lower, ~$0.02)
- Min ROI: 0.05% → 0.01% (5x lower)
- Gas multiplier: 5x → 1.5x (3.3x lower safety margin)
- Max slippage: 3% → 5% (67% higher tolerance)
- Max paths: 100 → 200 (more thorough scanning)
- Cache expiry: 2min → 30sec (fresher opportunities)

EXPECTED RESULTS (24h):
- 20-50 opportunities with profit > $0.02 (was 0)
- 5-15 execution attempts (was 0)
- 1-2 successful executions (was 0)
- $0.02-$0.20 net profit (was $0)

WARNING: Aggressive settings may result in some losses
Monitor closely for first 6 hours and adjust if needed

Target: First profitable execution within 24 hours

🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-29 04:18:27 -05:00

624 lines
17 KiB
Go

package security
import (
"bytes"
"context"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"math/rand"
"net/http"
"os"
"reflect"
"sync"
"time"
"golang.org/x/time/rate"
"github.com/fraktal/mev-beta/internal/logger"
)
// SecurityManager provides centralized security management for the MEV bot
type SecurityManager struct {
keyManager *KeyManager
inputValidator *InputValidator
rateLimiter *RateLimiter
monitor *SecurityMonitor
config *SecurityConfig
logger *logger.Logger
// Circuit breakers for different components
rpcCircuitBreaker *CircuitBreaker
arbitrageCircuitBreaker *CircuitBreaker
// TLS configuration
tlsConfig *tls.Config
// Rate limiters for different operations
transactionLimiter *rate.Limiter
rpcLimiter *rate.Limiter
// Security state
emergencyMode bool
securityAlerts []SecurityAlert
alertsMutex sync.RWMutex
// Metrics
managerMetrics *ManagerMetrics
rpcHTTPClient *http.Client
}
// SecurityConfig contains all security-related configuration
type SecurityConfig struct {
// Key management
KeyStoreDir string `yaml:"keystore_dir"`
EncryptionEnabled bool `yaml:"encryption_enabled"`
// Rate limiting
TransactionRPS int `yaml:"transaction_rps"`
RPCRPS int `yaml:"rpc_rps"`
MaxBurstSize int `yaml:"max_burst_size"`
// Circuit breaker settings
FailureThreshold int `yaml:"failure_threshold"`
RecoveryTimeout time.Duration `yaml:"recovery_timeout"`
// TLS settings
TLSMinVersion uint16 `yaml:"tls_min_version"`
TLSCipherSuites []uint16 `yaml:"tls_cipher_suites"`
// Emergency settings
EmergencyStopFile string `yaml:"emergency_stop_file"`
MaxGasPrice string `yaml:"max_gas_price"`
// Monitoring
AlertWebhookURL string `yaml:"alert_webhook_url"`
LogLevel string `yaml:"log_level"`
// RPC endpoint used by secure RPC call delegation
RPCURL string `yaml:"rpc_url"`
}
// Additional security metrics for SecurityManager
type ManagerMetrics struct {
AuthenticationAttempts int64 `json:"authentication_attempts"`
FailedAuthentications int64 `json:"failed_authentications"`
CircuitBreakerTrips int64 `json:"circuit_breaker_trips"`
EmergencyStops int64 `json:"emergency_stops"`
TLSHandshakeFailures int64 `json:"tls_handshake_failures"`
}
// CircuitBreaker implements the circuit breaker pattern for fault tolerance
type CircuitBreaker struct {
name string
failureCount int
lastFailureTime time.Time
state CircuitBreakerState
config CircuitBreakerConfig
mutex sync.RWMutex
}
type CircuitBreakerState int
const (
CircuitBreakerClosed CircuitBreakerState = iota
CircuitBreakerOpen
CircuitBreakerHalfOpen
)
type CircuitBreakerConfig struct {
FailureThreshold int
RecoveryTimeout time.Duration
MaxRetries int
}
// NewSecurityManager creates a new security manager with comprehensive protection
func NewSecurityManager(config *SecurityConfig) (*SecurityManager, error) {
if config == nil {
return nil, fmt.Errorf("security config cannot be nil")
}
// Initialize key manager - get encryption key from environment or fail
encryptionKey := os.Getenv("MEV_BOT_ENCRYPTION_KEY")
if encryptionKey == "" {
return nil, fmt.Errorf("MEV_BOT_ENCRYPTION_KEY environment variable is required")
}
keyManagerConfig := &KeyManagerConfig{
KeyDir: config.KeyStoreDir,
KeystorePath: config.KeyStoreDir,
EncryptionKey: encryptionKey,
BackupEnabled: true,
MaxFailedAttempts: 3,
LockoutDuration: 5 * time.Minute,
}
keyManager, err := NewKeyManager(keyManagerConfig, logger.New("info", "json", "logs/keymanager.log"))
if err != nil {
return nil, fmt.Errorf("failed to initialize key manager: %w", err)
}
// Initialize input validator
inputValidator := NewInputValidator(1) // Default chain ID
// Initialize rate limiter
rateLimiterConfig := &RateLimiterConfig{
IPRequestsPerSecond: 100,
IPBurstSize: config.MaxBurstSize,
IPBlockDuration: 5 * time.Minute,
UserRequestsPerSecond: config.TransactionRPS,
UserBurstSize: config.MaxBurstSize,
UserBlockDuration: 5 * time.Minute,
CleanupInterval: 5 * time.Minute,
}
rateLimiter := NewRateLimiter(rateLimiterConfig)
// Initialize security monitor
monitorConfig := &MonitorConfig{
EnableAlerts: true,
AlertBuffer: 1000,
AlertRetention: 24 * time.Hour,
MaxEvents: 10000,
EventRetention: 7 * 24 * time.Hour,
MetricsInterval: time.Minute,
CleanupInterval: time.Hour,
}
monitor := NewSecurityMonitor(monitorConfig)
// Create TLS configuration with security best practices
tlsConfig := &tls.Config{
MinVersion: tls.VersionTLS12, // Enforce TLS 1.2 minimum
CipherSuites: config.TLSCipherSuites,
InsecureSkipVerify: false,
PreferServerCipherSuites: true,
}
// Initialize circuit breakers
rpcCircuitBreaker := &CircuitBreaker{
name: "rpc",
config: CircuitBreakerConfig{
FailureThreshold: config.FailureThreshold,
RecoveryTimeout: config.RecoveryTimeout,
MaxRetries: 3,
},
state: CircuitBreakerClosed,
}
arbitrageCircuitBreaker := &CircuitBreaker{
name: "arbitrage",
config: CircuitBreakerConfig{
FailureThreshold: config.FailureThreshold,
RecoveryTimeout: config.RecoveryTimeout,
MaxRetries: 3,
},
state: CircuitBreakerClosed,
}
// Initialize rate limiters
transactionLimiter := rate.NewLimiter(rate.Limit(config.TransactionRPS), config.MaxBurstSize)
rpcLimiter := rate.NewLimiter(rate.Limit(config.RPCRPS), config.MaxBurstSize)
// Create logger instance
securityLogger := logger.New("info", "json", "logs/security.log")
httpTransport := &http.Transport{
TLSClientConfig: tlsConfig,
}
sm := &SecurityManager{
keyManager: keyManager,
inputValidator: inputValidator,
rateLimiter: rateLimiter,
monitor: monitor,
config: config,
logger: securityLogger,
rpcCircuitBreaker: rpcCircuitBreaker,
arbitrageCircuitBreaker: arbitrageCircuitBreaker,
tlsConfig: tlsConfig,
transactionLimiter: transactionLimiter,
rpcLimiter: rpcLimiter,
emergencyMode: false,
securityAlerts: make([]SecurityAlert, 0),
managerMetrics: &ManagerMetrics{},
rpcHTTPClient: &http.Client{
Timeout: 30 * time.Second,
Transport: httpTransport,
},
}
// Start security monitoring
go sm.startSecurityMonitoring()
sm.logger.Info("Security manager initialized successfully")
return sm, nil
}
// GetKeyManager returns the KeyManager instance managed by SecurityManager
// SECURITY FIX: Provides single source of truth for KeyManager to prevent multiple instances
// with different encryption keys (which would cause key derivation mismatches)
func (sm *SecurityManager) GetKeyManager() *KeyManager {
return sm.keyManager
}
// ValidateTransaction performs comprehensive transaction validation
func (sm *SecurityManager) ValidateTransaction(ctx context.Context, txParams *TransactionParams) error {
// Check rate limiting
if !sm.transactionLimiter.Allow() {
if sm.monitor != nil {
sm.monitor.RecordEvent(EventTypeError, "security_manager", "Transaction rate limit exceeded", SeverityMedium, map[string]interface{}{
"limit_type": "transaction",
})
}
return fmt.Errorf("transaction rate limit exceeded")
}
// Check emergency mode
if sm.emergencyMode {
return fmt.Errorf("system in emergency mode - transactions disabled")
}
// Validate input parameters (simplified validation)
if txParams.To == nil {
return fmt.Errorf("transaction validation failed: missing recipient")
}
if txParams.Value == nil {
return fmt.Errorf("transaction validation failed: missing value")
}
// Check circuit breaker state
if sm.arbitrageCircuitBreaker.state == CircuitBreakerOpen {
return fmt.Errorf("arbitrage circuit breaker is open")
}
return nil
}
// SecureRPCCall performs RPC calls with security controls
func (sm *SecurityManager) SecureRPCCall(ctx context.Context, method string, params interface{}) (interface{}, error) {
// Check rate limiting
if !sm.rpcLimiter.Allow() {
if sm.monitor != nil {
sm.monitor.RecordEvent(EventTypeError, "security_manager", "RPC rate limit exceeded", SeverityMedium, map[string]interface{}{
"limit_type": "rpc",
"method": method,
})
}
return nil, fmt.Errorf("RPC rate limit exceeded")
}
// Check circuit breaker
if sm.rpcCircuitBreaker.state == CircuitBreakerOpen {
return nil, fmt.Errorf("RPC circuit breaker is open")
}
if sm.config.RPCURL == "" {
err := errors.New("RPC endpoint not configured in security manager")
sm.RecordFailure("rpc", err)
return nil, err
}
paramList, err := normalizeRPCParams(params)
if err != nil {
sm.RecordFailure("rpc", err)
return nil, err
}
requestPayload := jsonRPCRequest{
JSONRPC: "2.0",
Method: method,
Params: paramList,
ID: fmt.Sprintf("sm-%d", rand.Int63()),
}
body, err := json.Marshal(requestPayload)
if err != nil {
sm.RecordFailure("rpc", err)
return nil, fmt.Errorf("failed to marshal RPC request: %w", err)
}
req, err := http.NewRequestWithContext(ctx, http.MethodPost, sm.config.RPCURL, bytes.NewReader(body))
if err != nil {
sm.RecordFailure("rpc", err)
return nil, fmt.Errorf("failed to create RPC request: %w", err)
}
req.Header.Set("Content-Type", "application/json")
resp, err := sm.rpcHTTPClient.Do(req)
if err != nil {
sm.RecordFailure("rpc", err)
return nil, fmt.Errorf("RPC call failed: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode >= 400 {
sm.RecordFailure("rpc", fmt.Errorf("rpc endpoint returned status %d", resp.StatusCode))
return nil, fmt.Errorf("rpc endpoint returned status %d", resp.StatusCode)
}
var rpcResp jsonRPCResponse
if err := json.NewDecoder(resp.Body).Decode(&rpcResp); err != nil {
sm.RecordFailure("rpc", err)
return nil, fmt.Errorf("failed to decode RPC response: %w", err)
}
if rpcResp.Error != nil {
err := fmt.Errorf("rpc error %d: %s", rpcResp.Error.Code, rpcResp.Error.Message)
sm.RecordFailure("rpc", err)
return nil, err
}
var result interface{}
if len(rpcResp.Result) > 0 {
if err := json.Unmarshal(rpcResp.Result, &result); err != nil {
// If we cannot unmarshal into interface{}, return raw JSON
result = string(rpcResp.Result)
}
}
sm.RecordSuccess("rpc")
return result, nil
}
// TriggerEmergencyStop activates emergency mode
func (sm *SecurityManager) TriggerEmergencyStop(reason string) error {
sm.emergencyMode = true
sm.managerMetrics.EmergencyStops++
alert := SecurityAlert{
ID: fmt.Sprintf("emergency-%d", time.Now().Unix()),
Timestamp: time.Now(),
Level: AlertLevelCritical,
Type: AlertTypeConfiguration,
Title: "Emergency Stop Activated",
Description: fmt.Sprintf("Emergency stop triggered: %s", reason),
Source: "security_manager",
Data: map[string]interface{}{
"reason": reason,
},
Actions: []string{"investigate_cause", "review_logs", "manual_restart_required"},
}
sm.addSecurityAlert(alert)
sm.logger.Error("Emergency stop triggered: " + reason)
return nil
}
// RecordFailure records a failure for circuit breaker logic
func (sm *SecurityManager) RecordFailure(component string, err error) {
var cb *CircuitBreaker
switch component {
case "rpc":
cb = sm.rpcCircuitBreaker
case "arbitrage":
cb = sm.arbitrageCircuitBreaker
default:
return
}
cb.mutex.Lock()
defer cb.mutex.Unlock()
cb.failureCount++
cb.lastFailureTime = time.Now()
if cb.failureCount >= cb.config.FailureThreshold && cb.state == CircuitBreakerClosed {
cb.state = CircuitBreakerOpen
sm.managerMetrics.CircuitBreakerTrips++
alert := SecurityAlert{
ID: fmt.Sprintf("circuit-breaker-%s-%d", component, time.Now().Unix()),
Timestamp: time.Now(),
Level: AlertLevelError,
Type: AlertTypePerformance,
Title: "Circuit Breaker Opened",
Description: fmt.Sprintf("Circuit breaker opened for component: %s", component),
Source: "security_manager",
Data: map[string]interface{}{
"component": component,
"failure_count": cb.failureCount,
"error": err.Error(),
},
Actions: []string{"investigate_failures", "check_component_health", "manual_intervention_required"},
}
sm.addSecurityAlert(alert)
sm.logger.Warn(fmt.Sprintf("Circuit breaker opened for component: %s, failure count: %d", component, cb.failureCount))
}
}
// RecordSuccess records a success for circuit breaker logic
func (sm *SecurityManager) RecordSuccess(component string) {
var cb *CircuitBreaker
switch component {
case "rpc":
cb = sm.rpcCircuitBreaker
case "arbitrage":
cb = sm.arbitrageCircuitBreaker
default:
return
}
cb.mutex.Lock()
defer cb.mutex.Unlock()
if cb.state == CircuitBreakerHalfOpen {
cb.state = CircuitBreakerClosed
cb.failureCount = 0
sm.logger.Info(fmt.Sprintf("Circuit breaker closed for component: %s", component))
}
}
// addSecurityAlert adds a security alert to the system
func (sm *SecurityManager) addSecurityAlert(alert SecurityAlert) {
sm.alertsMutex.Lock()
defer sm.alertsMutex.Unlock()
sm.securityAlerts = append(sm.securityAlerts, alert)
// Send alert to monitor if available
if sm.monitor != nil {
sm.monitor.TriggerAlert(alert.Level, alert.Type, alert.Title, alert.Description, alert.Source, alert.Data, alert.Actions)
}
// Keep only last 1000 alerts
if len(sm.securityAlerts) > 1000 {
sm.securityAlerts = sm.securityAlerts[len(sm.securityAlerts)-1000:]
}
}
// startSecurityMonitoring starts background security monitoring
func (sm *SecurityManager) startSecurityMonitoring() {
ticker := time.NewTicker(1 * time.Minute)
defer ticker.Stop()
for {
select {
case <-ticker.C:
sm.performSecurityChecks()
}
}
}
// performSecurityChecks performs periodic security checks
func (sm *SecurityManager) performSecurityChecks() {
// Check circuit breakers for recovery
sm.checkCircuitBreakerRecovery(sm.rpcCircuitBreaker)
sm.checkCircuitBreakerRecovery(sm.arbitrageCircuitBreaker)
// Check for emergency stop file
if sm.config.EmergencyStopFile != "" {
if _, err := os.Stat(sm.config.EmergencyStopFile); err == nil {
sm.TriggerEmergencyStop("emergency stop file detected")
}
}
}
// checkCircuitBreakerRecovery checks if circuit breakers can transition to half-open
func (sm *SecurityManager) checkCircuitBreakerRecovery(cb *CircuitBreaker) {
cb.mutex.Lock()
defer cb.mutex.Unlock()
if cb.state == CircuitBreakerOpen &&
time.Since(cb.lastFailureTime) > cb.config.RecoveryTimeout {
cb.state = CircuitBreakerHalfOpen
sm.logger.Info(fmt.Sprintf("Circuit breaker transitioned to half-open for component: %s", cb.name))
}
}
// GetManagerMetrics returns current manager metrics
func (sm *SecurityManager) GetManagerMetrics() *ManagerMetrics {
return sm.managerMetrics
}
// GetSecurityMetrics returns current security metrics from monitor
func (sm *SecurityManager) GetSecurityMetrics() *SecurityMetrics {
if sm.monitor != nil {
return sm.monitor.GetMetrics()
}
return &SecurityMetrics{}
}
// GetSecurityAlerts returns recent security alerts
func (sm *SecurityManager) GetSecurityAlerts(limit int) []SecurityAlert {
sm.alertsMutex.RLock()
defer sm.alertsMutex.RUnlock()
if limit <= 0 || limit > len(sm.securityAlerts) {
limit = len(sm.securityAlerts)
}
start := len(sm.securityAlerts) - limit
if start < 0 {
start = 0
}
alerts := make([]SecurityAlert, limit)
copy(alerts, sm.securityAlerts[start:])
return alerts
}
// Shutdown gracefully shuts down the security manager
func (sm *SecurityManager) Shutdown(ctx context.Context) error {
sm.logger.Info("Shutting down security manager")
// Shutdown components
if sm.keyManager != nil {
// Key manager shutdown - simplified (no shutdown method needed)
sm.logger.Info("Key manager stopped")
}
if sm.rateLimiter != nil {
// Rate limiter shutdown - simplified
sm.logger.Info("Rate limiter stopped")
}
if sm.monitor != nil {
// Monitor shutdown - simplified
sm.logger.Info("Security monitor stopped")
}
if sm.rpcHTTPClient != nil {
sm.rpcHTTPClient.CloseIdleConnections()
}
return nil
}
type jsonRPCRequest struct {
JSONRPC string `json:"jsonrpc"`
Method string `json:"method"`
Params []interface{} `json:"params"`
ID string `json:"id"`
}
type jsonRPCError struct {
Code int `json:"code"`
Message string `json:"message"`
}
type jsonRPCResponse struct {
JSONRPC string `json:"jsonrpc"`
Result json.RawMessage `json:"result"`
Error *jsonRPCError `json:"error,omitempty"`
ID string `json:"id"`
}
func normalizeRPCParams(params interface{}) ([]interface{}, error) {
if params == nil {
return []interface{}{}, nil
}
switch v := params.(type) {
case []interface{}:
return v, nil
case []string:
result := make([]interface{}, len(v))
for i := range v {
result[i] = v[i]
}
return result, nil
case []int:
result := make([]interface{}, len(v))
for i := range v {
result[i] = v[i]
}
return result, nil
}
val := reflect.ValueOf(params)
if val.Kind() == reflect.Slice || val.Kind() == reflect.Array {
length := val.Len()
result := make([]interface{}, length)
for i := 0; i < length; i++ {
result[i] = val.Index(i).Interface()
}
return result, nil
}
return []interface{}{params}, nil
}