Files
mev-beta/test/integration/corruption_simulation_test.go
Krypto Kajun 850223a953 fix(multicall): resolve critical multicall parsing corruption issues
- Added comprehensive bounds checking to prevent buffer overruns in multicall parsing
- Implemented graduated validation system (Strict/Moderate/Permissive) to reduce false positives
- Added LRU caching system for address validation with 10-minute TTL
- Enhanced ABI decoder with missing Universal Router and Arbitrum-specific DEX signatures
- Fixed duplicate function declarations and import conflicts across multiple files
- Added error recovery mechanisms with multiple fallback strategies
- Updated tests to handle new validation behavior for suspicious addresses
- Fixed parser test expectations for improved validation system
- Applied gofmt formatting fixes to ensure code style compliance
- Fixed mutex copying issues in monitoring package by introducing MetricsSnapshot
- Resolved critical security vulnerabilities in heuristic address extraction
- Progress: Updated TODO audit from 10% to 35% complete

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

Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-17 00:12:55 -05:00

514 lines
16 KiB
Go

package integration
import (
"context"
"encoding/hex"
"fmt"
"sync"
"testing"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/assert"
"github.com/fraktal/mev-beta/internal/logger"
"github.com/fraktal/mev-beta/internal/monitoring"
"github.com/fraktal/mev-beta/internal/recovery"
"github.com/fraktal/mev-beta/internal/utils"
"github.com/fraktal/mev-beta/internal/validation"
)
// CorruptionSimulator simulates various corruption scenarios for testing
type CorruptionSimulator struct {
validator *validation.AddressValidator
converter *utils.SafeAddressConverter
integrityMonitor *monitoring.IntegrityMonitor
errorHandler *recovery.ErrorHandler
retryHandler *recovery.RetryHandler
logger *logger.Logger
}
// NewCorruptionSimulator creates a new corruption simulation test environment
func NewCorruptionSimulator(t *testing.T) *CorruptionSimulator {
log := logger.New("debug", "text", "")
validator := validation.NewAddressValidator()
converter := utils.NewSafeAddressConverter()
integrityMonitor := monitoring.NewIntegrityMonitor(log)
errorHandler := recovery.NewErrorHandler(log)
retryHandler := recovery.NewRetryHandler(log)
return &CorruptionSimulator{
validator: validator,
converter: converter,
integrityMonitor: integrityMonitor,
errorHandler: errorHandler,
retryHandler: retryHandler,
logger: log,
}
}
// CorruptionScenario represents a specific corruption test case
type CorruptionScenario struct {
Name string
CorruptedAddresses []string
ValidAddresses []string
ExpectedDetections int
ExpectedRecoveries int
Severity recovery.ErrorSeverity
}
func TestCorruption_TOKEN_0x000000_Scenarios(t *testing.T) {
simulator := NewCorruptionSimulator(t)
scenarios := []CorruptionScenario{
{
Name: "Critical TOKEN_0x000000 Pattern",
CorruptedAddresses: []string{
"0x0000000300000000000000000000000000000000", // Exact pattern
"0x0000000100000000000000000000000000000000", // Similar pattern
"0x0000000500000000000000000000000000000000", // Variant
},
ValidAddresses: []string{
"0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", // WETH
"0xaf88d065e77c8cC2239327C5EDb3A432268e5831", // USDC
},
ExpectedDetections: 3,
Severity: recovery.SeverityCritical,
},
{
Name: "Mixed Corruption Patterns",
CorruptedAddresses: []string{
"0x1234000000000000000000000000000000000000", // Partial corruption
"0x0000000000000000000000000000000000000001", // Almost zero
"0xabcd567800000000000000000000000000000000", // Trailing zeros
},
ValidAddresses: []string{
"0x912CE59144191C1204E64559FE8253a0e49E6548", // ARB
"0x2f2a2543B76A4166549F7aaB2e75Bef0aefC5B0f", // WBTC
},
ExpectedDetections: 3,
Severity: recovery.SeverityHigh,
},
{
Name: "Subtle Corruption",
CorruptedAddresses: []string{
"0x82aF49447D8a07e3bd95BD0d56f35241523fBaZ1", // Invalid hex char
"0x82af49447d8a07e3bd95bd0d56f35241523fbab", // Truncated address
},
ValidAddresses: []string{
"0xFd086bC7CD5C481DCC9C85ebE478A1C0b69FCbb9", // USDT
},
ExpectedDetections: 2,
Severity: recovery.SeverityMedium,
},
}
for _, scenario := range scenarios {
t.Run(scenario.Name, func(t *testing.T) {
simulator.runCorruptionScenario(t, scenario)
})
}
}
func (cs *CorruptionSimulator) runCorruptionScenario(t *testing.T, scenario CorruptionScenario) {
// Reset monitoring stats
cs.integrityMonitor = monitoring.NewIntegrityMonitor(cs.logger)
detectedCorruptions := 0
successfulRecoveries := 0
// Process corrupted addresses
for _, corruptedAddr := range scenario.CorruptedAddresses {
cs.logger.Debug("Processing corrupted address", "address", corruptedAddr)
// Validate address
result := cs.validator.ValidateAddress(corruptedAddr)
cs.integrityMonitor.RecordAddressProcessed()
if !result.IsValid {
detectedCorruptions++
addr := common.HexToAddress(corruptedAddr)
cs.integrityMonitor.RecordCorruptionDetected(addr, result.CorruptionScore, "test_scenario")
cs.integrityMonitor.RecordValidationResult(false)
// Simulate recovery attempt
recoveryAction := cs.errorHandler.HandleError(
context.Background(),
recovery.ErrorTypeAddressCorruption,
scenario.Severity,
"test_component",
addr,
"Simulated corruption",
map[string]interface{}{"corruption_score": result.CorruptionScore},
)
cs.integrityMonitor.RecordRecoveryAction(recoveryAction)
// Count successful recoveries
if recoveryAction == recovery.ActionUseFallbackData ||
recoveryAction == recovery.ActionRetryWithBackoff {
successfulRecoveries++
}
} else {
cs.integrityMonitor.RecordValidationResult(true)
}
}
// Process valid addresses
for _, validAddr := range scenario.ValidAddresses {
cs.logger.Debug("Processing valid address", "address", validAddr)
result := cs.validator.ValidateAddress(validAddr)
cs.integrityMonitor.RecordAddressProcessed()
if result.IsValid {
cs.integrityMonitor.RecordValidationResult(true)
} else {
t.Errorf("Valid address %s was rejected: %v", validAddr, result.ErrorMessages)
}
}
// Verify results
assert.Equal(t, scenario.ExpectedDetections, detectedCorruptions,
"Should detect expected number of corruptions")
metrics := cs.integrityMonitor.GetMetrics()
assert.Equal(t, int64(detectedCorruptions), metrics.CorruptAddressesDetected,
"Monitoring should track detected corruptions")
totalAddresses := len(scenario.CorruptedAddresses) + len(scenario.ValidAddresses)
assert.Equal(t, int64(totalAddresses), metrics.TotalAddressesProcessed,
"Should track all processed addresses")
// Health score should reflect corruption level
expectedHealthScore := 1.0 - (float64(detectedCorruptions) / float64(totalAddresses))
assert.InDelta(t, expectedHealthScore, metrics.HealthScore, 0.2,
"Health score should reflect corruption rate")
t.Logf("Scenario %s: Detected %d/%d corruptions, Health=%.3f",
scenario.Name, detectedCorruptions, scenario.ExpectedDetections, metrics.HighScore)
}
func TestCorruption_HighVolumeStressTest(t *testing.T) {
if testing.Short() {
t.Skip("Skipping stress test in short mode")
}
simulator := NewCorruptionSimulator(t)
const (
numWorkers = 20
addressesPerWorker = 1000
corruptionRate = 0.1 // 10% corruption rate
)
var wg sync.WaitGroup
startTime := time.Now()
// Launch worker goroutines
for workerID := 0; workerID < numWorkers; workerID++ {
wg.Add(1)
go func(id int) {
defer wg.Done()
for i := 0; i < addressesPerWorker; i++ {
var address string
// Generate corrupted or valid address based on corruption rate
if float64(i%100) < corruptionRate*100 {
// Generate corrupted address
address = generateCorruptedAddress(id, i)
} else {
// Generate valid-looking address
address = generateValidAddress(id, i)
}
// Process address through validation pipeline
result := simulator.validator.ValidateAddress(address)
simulator.integrityMonitor.RecordAddressProcessed()
if result.IsValid {
simulator.integrityMonitor.RecordValidationResult(true)
} else {
addr := common.HexToAddress(address)
simulator.integrityMonitor.RecordCorruptionDetected(addr, result.CorruptionScore, "stress_test")
simulator.integrityMonitor.RecordValidationResult(false)
// Simulate recovery
recoveryAction := simulator.errorHandler.HandleError(
context.Background(),
recovery.ErrorTypeAddressCorruption,
recovery.SeverityMedium,
"stress_test",
addr,
"Stress test corruption",
nil,
)
simulator.integrityMonitor.RecordRecoveryAction(recoveryAction)
}
}
}(workerID)
}
// Wait for completion
wg.Wait()
duration := time.Since(startTime)
// Analyze results
metrics := simulator.integrityMonitor.GetMetrics()
totalAddresses := int64(numWorkers * addressesPerWorker)
actualCorruptionRate := float64(metrics.CorruptAddressesDetected) / float64(totalAddresses)
t.Logf("Stress Test Results:")
t.Logf(" Duration: %v", duration)
t.Logf(" Total Addresses: %d", totalAddresses)
t.Logf(" Corruptions Detected: %d", metrics.CorruptAddressesDetected)
t.Logf(" Actual Corruption Rate: %.2f%%", actualCorruptionRate*100)
t.Logf(" Health Score: %.3f", metrics.HealthScore)
t.Logf(" Throughput: %.0f addresses/sec", float64(totalAddresses)/duration.Seconds())
// Verify performance and accuracy
assert.Equal(t, totalAddresses, metrics.TotalAddressesProcessed)
assert.InDelta(t, corruptionRate, actualCorruptionRate, 0.05) // Within 5% of expected
assert.Greater(t, metrics.RetryOperationsTriggered, int64(0)) // Should have triggered recoveries
// Should process at least 1000 addresses per second
throughput := float64(totalAddresses) / duration.Seconds()
assert.Greater(t, throughput, 1000.0, "Should maintain high throughput under stress")
}
func TestCorruption_RecoveryMechanisms(t *testing.T) {
simulator := NewCorruptionSimulator(t)
testCases := []struct {
name string
corruptedAddr string
errorType recovery.ErrorType
severity recovery.ErrorSeverity
expectedAction recovery.RecoveryAction
}{
{
name: "Critical corruption requires fallback",
corruptedAddr: "0x0000000300000000000000000000000000000000",
errorType: recovery.ErrorTypeAddressCorruption,
severity: recovery.SeverityCritical,
expectedAction: recovery.ActionUseFallbackData,
},
{
name: "Medium corruption allows retry",
corruptedAddr: "0x123400000000000000000000000000000000000",
errorType: recovery.ErrorTypeValidationFailed,
severity: recovery.SeverityMedium,
expectedAction: recovery.ActionRetryWithBackoff,
},
{
name: "Low corruption can be skipped",
corruptedAddr: "0x82af49447d8a07e3bd95bd0d56f35241523fbab1", // Wrong checksum
errorType: recovery.ErrorTypeValidationFailed,
severity: recovery.SeverityLow,
expectedAction: recovery.ActionSkipAndContinue,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
addr := common.HexToAddress(tc.corruptedAddr)
action := simulator.errorHandler.HandleError(
context.Background(),
tc.errorType,
tc.severity,
"test_recovery",
addr,
"Recovery test",
nil,
)
assert.Equal(t, tc.expectedAction, action,
"Should select appropriate recovery action for %s", tc.name)
// Record action and verify it's tracked
simulator.integrityMonitor.RecordRecoveryAction(action)
metrics := simulator.integrityMonitor.GetMetrics()
assert.Greater(t, metrics.RecoveryActions[action], int64(0),
"Should track recovery action usage")
})
}
}
func TestCorruption_RetryMechanisms(t *testing.T) {
simulator := NewCorruptionSimulator(t)
// Configure retry for testing
retryConfig := recovery.RetryConfig{
MaxAttempts: 3,
InitialDelay: 1 * time.Millisecond,
MaxDelay: 10 * time.Millisecond,
BackoffFactor: 2.0,
JitterEnabled: false,
TimeoutPerAttempt: 100 * time.Millisecond,
}
simulator.retryHandler.SetConfig("test_retry", retryConfig)
t.Run("Successful retry after corruption", func(t *testing.T) {
attempts := 0
operation := func(ctx context.Context, attempt int) error {
attempts++
if attempt == 1 {
// Simulate corruption on first attempt
return fmt.Errorf("address corruption detected")
}
// Success on retry
return nil
}
result := simulator.retryHandler.ExecuteWithRetry(context.Background(), "test_retry", operation)
assert.True(t, result.Success)
assert.Equal(t, 2, result.Attempts)
assert.Equal(t, 2, attempts)
})
t.Run("Persistent corruption fails after max attempts", func(t *testing.T) {
attempts := 0
operation := func(ctx context.Context, attempt int) error {
attempts++
return fmt.Errorf("persistent corruption")
}
result := simulator.retryHandler.ExecuteWithRetry(context.Background(), "test_retry", operation)
assert.False(t, result.Success)
assert.Equal(t, 3, result.Attempts) // Max attempts
assert.Equal(t, 3, attempts)
})
}
func TestCorruption_EndToEndPipeline(t *testing.T) {
simulator := NewCorruptionSimulator(t)
// Simulate complete transaction processing pipeline
testTransactions := []struct {
txHash string
inputData string
addresses []string
shouldFail bool
}{
{
txHash: "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
inputData: "0x022c0d9f000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000",
addresses: []string{
"0x0000000300000000000000000000000000000000", // Corrupted
"0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", // Valid WETH
},
shouldFail: true, // Should fail due to corruption
},
{
txHash: "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890",
inputData: "0x128acb08000000000000000000000000af88d065e77c8cc2239327c5edb3a432268e583100000000000000000000000082af49447d8a07e3bd95bd0d56f35241523fbab1",
addresses: []string{
"0xaf88d065e77c8cC2239327C5EDb3A432268e5831", // Valid USDC
"0x82aF49447D8a07e3bd95BD0d56f35241523fBab1", // Valid WETH
},
shouldFail: false, // Should succeed
},
}
for _, tx := range testTransactions {
t.Run(fmt.Sprintf("Transaction_%s", tx.txHash[:10]), func(t *testing.T) {
success := simulator.processTransaction(tx.txHash, tx.inputData, tx.addresses)
if tx.shouldFail {
assert.False(t, success, "Transaction with corruption should fail")
} else {
assert.True(t, success, "Transaction with valid addresses should succeed")
}
})
}
// Verify overall system health
metrics := simulator.integrityMonitor.GetMetrics()
assert.Greater(t, metrics.TotalAddressesProcessed, int64(0))
if metrics.CorruptAddressesDetected > 0 {
assert.Greater(t, metrics.RetryOperationsTriggered+metrics.FallbackOperationsUsed, int64(0),
"Should have triggered recovery mechanisms for corruption")
}
}
// processTransaction simulates processing a transaction through the full pipeline
func (cs *CorruptionSimulator) processTransaction(txHash, inputData string, addresses []string) bool {
allValid := true
for _, addr := range addresses {
cs.integrityMonitor.RecordAddressProcessed()
// Validate address
result := cs.validator.ValidateAddress(addr)
if !result.IsValid {
allValid = false
// Record corruption
address := common.HexToAddress(addr)
cs.integrityMonitor.RecordCorruptionDetected(address, result.CorruptionScore, "pipeline_test")
cs.integrityMonitor.RecordValidationResult(false)
// Attempt recovery
recoveryAction := cs.errorHandler.HandleError(
context.Background(),
recovery.ErrorTypeAddressCorruption,
recovery.SeverityCritical,
"transaction_processor",
address,
"Address corruption in transaction",
map[string]interface{}{
"tx_hash": txHash,
"input_data": inputData,
},
)
cs.integrityMonitor.RecordRecoveryAction(recoveryAction)
// Only continue if recovery suggests it's safe
if recoveryAction == recovery.ActionEmergencyStop || recoveryAction == recovery.ActionCircuitBreaker {
return false
}
} else {
cs.integrityMonitor.RecordValidationResult(true)
}
// Simulate contract call
if result.IsValid || result.CorruptionScore < 50 { // Allow low corruption with retry
cs.integrityMonitor.RecordContractCallResult(true)
} else {
cs.integrityMonitor.RecordContractCallResult(false)
allValid = false
}
}
return allValid
}
// Helper functions for generating test addresses
func generateCorruptedAddress(workerID, index int) string {
patterns := []string{
"0x000000%02d00000000000000000000000000000000", // TOKEN_0x000000 style
"0x%04x000000000000000000000000000000000000", // Partial corruption
"0x000000000000000000000000000000000000%04x", // Trailing corruption
}
pattern := patterns[index%len(patterns)]
return fmt.Sprintf(pattern, workerID*1000+index)
}
func generateValidAddress(workerID, index int) string {
bytes := make([]byte, common.AddressLength)
seed := workerID*97 + index*31
for i := 0; i < len(bytes); i++ {
seed = (seed*131 + i*17) & 0xff
bytes[i] = byte(seed)
}
return "0x" + hex.EncodeToString(bytes)
}