Completed clean root directory structure: - Root now contains only: .git, .env, docs/, orig/ - Moved all remaining files and directories to orig/: - Config files (.claude, .dockerignore, .drone.yml, etc.) - All .env variants (except active .env) - Git config (.gitconfig, .github, .gitignore, etc.) - Tool configs (.golangci.yml, .revive.toml, etc.) - Documentation (*.md files, @prompts) - Build files (Dockerfiles, Makefile, go.mod, go.sum) - Docker compose files - All source directories (scripts, tests, tools, etc.) - Runtime directories (logs, monitoring, reports) - Dependency files (node_modules, lib, cache) - Special files (--delete) - Removed empty runtime directories (bin/, data/) V2 structure is now clean: - docs/planning/ - V2 planning documents - orig/ - Complete V1 codebase preserved - .env - Active environment config (not in git) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
331 lines
9.7 KiB
Go
331 lines
9.7 KiB
Go
//go:build stress
|
|
// +build stress
|
|
|
|
package stress_test
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"math/big"
|
|
"math/rand"
|
|
"os"
|
|
"os/signal"
|
|
"sync"
|
|
"sync/atomic"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
"github.com/holiman/uint256"
|
|
|
|
"github.com/fraktal/mev-beta/internal/logger"
|
|
"github.com/fraktal/mev-beta/pkg/arbitrum"
|
|
"github.com/fraktal/mev-beta/pkg/events"
|
|
"github.com/fraktal/mev-beta/pkg/marketdata"
|
|
"github.com/fraktal/mev-beta/pkg/pools"
|
|
"github.com/fraktal/mev-beta/pkg/profitcalc"
|
|
"github.com/fraktal/mev-beta/pkg/scanner/market"
|
|
"github.com/fraktal/mev-beta/pkg/scanner/swap"
|
|
)
|
|
|
|
// StressTestRunner runs comprehensive stress tests for the MEV bot
|
|
type StressTestRunner struct {
|
|
suite *StressTestSuite
|
|
logger *logger.Logger
|
|
wg sync.WaitGroup
|
|
ctx context.Context
|
|
cancel context.CancelFunc
|
|
shutdown chan struct{}
|
|
}
|
|
|
|
// NewStressTestRunner creates a new stress test runner
|
|
func NewStressTestRunner() *StressTestRunner {
|
|
// Initialize logger
|
|
log := logger.New("debug", "text", "logs/stress_test.log")
|
|
|
|
// Create test components
|
|
protocolRegistry := arbitrum.NewArbitrumProtocolRegistry(log)
|
|
poolCache := pools.NewPoolCache(10000, time.Hour)
|
|
marketDiscovery := market.NewMarketDiscovery(nil, log, "")
|
|
strategyEngine := arbitrum.NewMEVStrategyEngine(log, protocolRegistry)
|
|
profitCalculator := profitcalc.NewProfitCalculatorWithClient(log, nil)
|
|
marketDataLogger := marketdata.NewMarketDataLogger(log, nil)
|
|
swapAnalyzer := swap.NewSwapAnalyzer(log, marketDataLogger, profitCalculator, nil)
|
|
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
|
|
runner := &StressTestRunner{
|
|
logger: log,
|
|
ctx: ctx,
|
|
cancel: cancel,
|
|
shutdown: make(chan struct{}),
|
|
}
|
|
|
|
// Create stress test suite
|
|
runner.suite = NewStressTestSuite(
|
|
log,
|
|
protocolRegistry,
|
|
poolCache,
|
|
marketDiscovery,
|
|
strategyEngine,
|
|
profitCalculator,
|
|
nil, // mevAnalyzer
|
|
nil, // slippageProtector
|
|
nil, // capitalOptimizer
|
|
nil, // profitTracker
|
|
)
|
|
|
|
return runner
|
|
}
|
|
|
|
// RunAllStressTests runs all stress tests
|
|
func (str *StressTestRunner) RunAllStressTests() {
|
|
str.logger.Info("🚀 Starting comprehensive stress tests...")
|
|
|
|
// Set up graceful shutdown
|
|
str.setupGracefulShutdown()
|
|
|
|
// Run individual stress tests
|
|
tests := []struct {
|
|
name string
|
|
fn func() *StressTestResult
|
|
}{
|
|
{"Market Scanner Stress Test", str.suite.RunMarketScannerStressTest},
|
|
{"Swap Analyzer Stress Test", str.suite.RunSwapAnalyzerStressTest},
|
|
{"Pool Discovery Stress Test", str.suite.RunPoolDiscoveryStressTest},
|
|
{"Arbitrage Engine Stress Test", str.suite.RunArbitrageEngineStressTest},
|
|
{"Event Processing Stress Test", str.suite.RunEventProcessingStressTest},
|
|
{"Profit Calculation Stress Test", str.suite.RunProfitCalculationStressTest},
|
|
{"Concurrency Stress Test", str.suite.RunConcurrencyStressTest},
|
|
{"Memory Stress Test", str.suite.RunMemoryStressTest},
|
|
{"Performance Regression Test", str.suite.RunPerformanceRegressionTest},
|
|
}
|
|
|
|
results := make([]*StressTestResult, len(tests))
|
|
|
|
// Run tests concurrently
|
|
for i, test := range tests {
|
|
str.wg.Add(1)
|
|
go func(idx int, t struct {
|
|
name string
|
|
fn func() *StressTestResult
|
|
}) {
|
|
defer str.wg.Done()
|
|
|
|
str.logger.Info(fmt.Sprintf("🧪 Running %s...", t.name))
|
|
result := t.fn()
|
|
results[idx] = result
|
|
|
|
if result.Passed {
|
|
str.logger.Info(fmt.Sprintf("✅ %s PASSED", t.name))
|
|
} else {
|
|
str.logger.Error(fmt.Sprintf("❌ %s FAILED", t.name))
|
|
}
|
|
}(i, test)
|
|
}
|
|
|
|
// Wait for all tests to complete
|
|
str.wg.Wait()
|
|
|
|
// Generate and log summary report
|
|
str.generateSummaryReport(results)
|
|
|
|
// Close resources
|
|
close(str.shutdown)
|
|
}
|
|
|
|
// generateSummaryReport generates and logs a summary report of stress test results
|
|
func (str *StressTestRunner) generateSummaryReport(results []*StressTestResult) {
|
|
str.logger.Info("📊 STRESS TEST SUMMARY REPORT")
|
|
str.logger.Info("================================")
|
|
|
|
passed := 0
|
|
failed := 0
|
|
totalTests := len(results)
|
|
|
|
for _, result := range results {
|
|
if result.Passed {
|
|
passed++
|
|
} else {
|
|
failed++
|
|
}
|
|
|
|
status := "✅ PASS"
|
|
if !result.Passed {
|
|
status = "❌ FAIL"
|
|
}
|
|
|
|
str.logger.Info(fmt.Sprintf("%s %s - Score: %.1f%%", status, result.TestName, result.PerformanceScore))
|
|
}
|
|
|
|
str.logger.Info("================================")
|
|
str.logger.Info(fmt.Sprintf("TOTAL: %d tests, %d passed, %d failed", totalTests, passed, failed))
|
|
str.logger.Info(fmt.Sprintf("SUCCESS RATE: %.1f%%", float64(passed)/float64(totalTests)*100))
|
|
|
|
if failed == 0 {
|
|
str.logger.Info("🎉 ALL STRESS TESTS PASSED!")
|
|
} else {
|
|
str.logger.Warn(fmt.Sprintf("⚠️ %d STRESS TESTS FAILED - REVIEW RESULTS", failed))
|
|
}
|
|
}
|
|
|
|
// setupGracefulShutdown sets up signal handling for graceful shutdown
|
|
func (str *StressTestRunner) setupGracefulShutdown() {
|
|
c := make(chan os.Signal, 1)
|
|
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
|
|
|
|
go func() {
|
|
<-c
|
|
str.logger.Info("🛑 Shutdown signal received, stopping stress tests...")
|
|
str.cancel()
|
|
close(str.shutdown)
|
|
}()
|
|
}
|
|
|
|
// generateTestLoad generates synthetic load for stress testing
|
|
func (str *StressTestRunner) generateTestLoad(duration time.Duration, transactionsPerSecond int) {
|
|
str.logger.Info(fmt.Sprintf("💥 Generating synthetic load: %d TPS for %v", transactionsPerSecond, duration))
|
|
|
|
// Calculate interval between transactions for precise TPS
|
|
interval := time.Duration(int64(time.Second) / int64(transactionsPerSecond))
|
|
if interval == 0 {
|
|
interval = time.Nanosecond // Minimum interval for very high TPS
|
|
}
|
|
ticker := time.NewTicker(interval)
|
|
defer ticker.Stop()
|
|
|
|
startTime := time.Now()
|
|
transactionCount := uint64(0)
|
|
|
|
for {
|
|
select {
|
|
case <-str.ctx.Done():
|
|
str.logger.Info(fmt.Sprintf("⏹️ Load generation stopped. Generated %d transactions in %v", transactionCount, time.Since(startTime)))
|
|
return
|
|
case <-ticker.C:
|
|
// Generate synthetic transaction
|
|
str.generateSyntheticTransaction()
|
|
atomic.AddUint64(&transactionCount, 1)
|
|
|
|
// Check if duration has elapsed
|
|
if time.Since(startTime) >= duration {
|
|
str.logger.Info(fmt.Sprintf("⏰ Load generation completed. Generated %d transactions in %v", transactionCount, time.Since(startTime)))
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// generateSyntheticTransaction generates a synthetic transaction for stress testing
|
|
func (str *StressTestRunner) generateSyntheticTransaction() {
|
|
// Generate random transaction data
|
|
txHash := common.BigToHash(big.NewInt(rand.Int63()))
|
|
blockNumber := uint64(rand.Int63n(100000000) + 100000000)
|
|
poolAddr := common.BigToAddress(big.NewInt(rand.Int63()))
|
|
token0 := common.BigToAddress(big.NewInt(rand.Int63()))
|
|
token1 := common.BigToAddress(big.NewInt(rand.Int63()))
|
|
amount0 := big.NewInt(rand.Int63n(1000000000000000000)) // Up to 1 ETH
|
|
amount1 := big.NewInt(rand.Int63n(1000000000000000000)) // Up to 1 ETH
|
|
|
|
// Create event with random protocol
|
|
protocols := []string{"uniswap_v2", "uniswap_v3", "sushiswap", "camelot_v2", "camelot_v3", "balancer_v2", "curve", "algebra"}
|
|
protocol := protocols[rand.Intn(len(protocols))]
|
|
|
|
event := events.Event{
|
|
Timestamp: time.Now(),
|
|
BlockNumber: blockNumber,
|
|
TransactionHash: txHash,
|
|
LogIndex: uint(rand.Intn(100)),
|
|
Type: events.Swap,
|
|
Protocol: protocol,
|
|
PoolAddress: poolAddr,
|
|
Token0: token0,
|
|
Token1: token1,
|
|
Amount0: amount0,
|
|
Amount1: amount1,
|
|
Liquidity: uint256.NewInt(uint64(rand.Int63n(1000000000000000000))), // Up to 1 ETH equivalent
|
|
SqrtPriceX96: uint256.NewInt(uint64(rand.Int63n(1000000000000000000))), // Random sqrt price
|
|
Tick: int32(rand.Int31n(100000) - 50000), // Random tick between -50000 and 50000
|
|
}
|
|
|
|
// Process event through the system
|
|
// Note: In a real implementation, this would call the actual processing methods
|
|
// For stress testing, we'll just simulate the processing
|
|
time.Sleep(time.Duration(rand.Intn(5)) * time.Millisecond)
|
|
}
|
|
|
|
func main() {
|
|
fmt.Println("🚀 MEV Bot Stress Test Runner")
|
|
fmt.Println("=============================")
|
|
|
|
// Create stress test runner
|
|
runner := NewStressTestRunner()
|
|
defer runner.logger.Close()
|
|
|
|
// Parse command line arguments
|
|
args := os.Args[1:]
|
|
if len(args) == 0 {
|
|
fmt.Println("Usage: stress-test-runner [options]")
|
|
fmt.Println("Options:")
|
|
fmt.Println(" --load-test Run load generation test")
|
|
fmt.Println(" --full-suite Run full stress test suite")
|
|
fmt.Println(" --duration <sec> Set test duration (default: 60)")
|
|
fmt.Println(" --tps <count> Set transactions per second (default: 1000)")
|
|
os.Exit(1)
|
|
}
|
|
|
|
// Parse arguments
|
|
duration := 60 * time.Second
|
|
tps := 1000
|
|
runLoadTest := false
|
|
runFullSuite := false
|
|
|
|
for i := 0; i < len(args); i++ {
|
|
switch args[i] {
|
|
case "--duration":
|
|
if i+1 < len(args) {
|
|
if sec, err := fmt.Sscanf(args[i+1], "%d", &duration); err == nil && sec == 1 {
|
|
duration = time.Duration(sec) * time.Second
|
|
}
|
|
i++
|
|
}
|
|
case "--tps":
|
|
if i+1 < len(args) {
|
|
if count, err := fmt.Sscanf(args[i+1], "%d", &tps); err == nil && count == 1 {
|
|
tps = int(count)
|
|
}
|
|
i++
|
|
}
|
|
case "--load-test":
|
|
runLoadTest = true
|
|
case "--full-suite":
|
|
runFullSuite = true
|
|
}
|
|
}
|
|
|
|
// Run selected tests
|
|
if runLoadTest {
|
|
fmt.Printf("🏃 Running load test for %v at %d TPS...\n", duration, tps)
|
|
go runner.generateTestLoad(duration, tps)
|
|
|
|
// Wait for completion or shutdown
|
|
select {
|
|
case <-runner.shutdown:
|
|
fmt.Println("🛑 Load test interrupted")
|
|
case <-time.After(duration + 5*time.Second):
|
|
fmt.Println("✅ Load test completed")
|
|
}
|
|
}
|
|
|
|
if runFullSuite {
|
|
fmt.Println("🧪 Running full stress test suite...")
|
|
runner.RunAllStressTests()
|
|
fmt.Println("✅ Full stress test suite completed")
|
|
}
|
|
|
|
if !runLoadTest && !runFullSuite {
|
|
fmt.Println("❌ No test specified. Use --load-test or --full-suite")
|
|
os.Exit(1)
|
|
}
|
|
}
|