- 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>
514 lines
16 KiB
Go
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)
|
|
}
|