14 KiB
Layer 2 Optimizations Implementation Guide
Created: 2025-11-01 Status: Ready for Phase 1 Risk Level: Low (All changes are non-breaking)
Overview
This guide provides step-by-step instructions for implementing Arbitrum-specific optimizations based on our comprehensive Layer 2 research. All changes are non-breaking and can be rolled back if needed.
Quick Start
Step 1: Review Research
Read: docs/L2_MEV_BOT_RESEARCH_REPORT.md
- Validates our current implementation ✅
- Identifies non-breaking improvements 🟡
- Provides competitive analysis 📊
Step 2: Test Configuration
# Backup current config
cp config/arbitrum_production.yaml config/arbitrum_production.yaml.backup
# Test merge optimized config (dry run)
./scripts/validate-l2-config.sh --dry-run
# Apply Phase 1 optimizations
./scripts/apply-l2-optimizations.sh --phase 1
Step 3: Monitor Results
# Watch live with L2-specific metrics
./scripts/watch-l2-metrics.sh
# Compare with baseline
./scripts/compare-performance.sh --baseline before --current after
Phase-by-Phase Implementation
Phase 1: Configuration Tuning (Week 1)
Effort: 1-2 hours | Risk: Low | Reversible: Yes
What's Changing
opportunity_ttl: 30s → 5s (tuned for 250ms blocks)max_path_age: 60s → 10s (tuned for 250ms blocks)- Add
execution_deadline: 3s (new parameter)
Implementation Steps
1. Enable Phase 1 in config:
# config/arbitrum_production.yaml
# Add at the end of the file:
# ===== LAYER 2 OPTIMIZATIONS (Phase 1) =====
features:
use_arbitrum_optimized_timeouts: true
use_dynamic_ttl: false # Start with static, enable later
arbitrage_optimized:
opportunity_ttl: "5s" # 20 blocks @ 250ms
max_path_age: "10s" # 40 blocks @ 250ms
execution_deadline: "3s" # 12 blocks @ 250ms
# Backward compatibility
legacy_opportunity_ttl: "30s" # For rollback
legacy_max_path_age: "60s" # For rollback
2. Update code to read new config:
// internal/config/config.go
type ArbitrageOptimized struct {
OpportunityTTL time.Duration `yaml:"opportunity_ttl"`
MaxPathAge time.Duration `yaml:"max_path_age"`
ExecutionDeadline time.Duration `yaml:"execution_deadline"`
// Legacy values for rollback
LegacyOpportunityTTL time.Duration `yaml:"legacy_opportunity_ttl"`
LegacyMaxPathAge time.Duration `yaml:"legacy_max_path_age"`
}
type Features struct {
UseArbitrumOptimizedTimeouts bool `yaml:"use_arbitrum_optimized_timeouts"`
UseDynamicTTL bool `yaml:"use_dynamic_ttl"`
}
// In Config struct
type Config struct {
// ... existing fields ...
Features Features `yaml:"features"`
ArbitrageOptimized ArbitrageOptimized `yaml:"arbitrage_optimized"`
}
// Helper to get active TTL
func (c *Config) GetOpportunityTTL() time.Duration {
if c.Features.UseArbitrumOptimizedTimeouts {
return c.ArbitrageOptimized.OpportunityTTL
}
return c.Arbitrage.OpportunityTTL // Legacy
}
3. Update arbitrage service:
// pkg/arbitrage/service.go
func (s *ArbitrageService) isOpportunityValid(opp *types.ArbitrageOpportunity) bool {
// Use configurable TTL
ttl := s.config.GetOpportunityTTL()
age := time.Since(opp.Timestamp)
if age > ttl {
s.logger.Debug(fmt.Sprintf(
"Opportunity expired: age=%s, ttl=%s",
age, ttl,
))
return false
}
return true
}
4. Test Phase 1:
# Start bot with Phase 1 config
PROVIDER_CONFIG_PATH=$PWD/config/providers_runtime.yaml timeout 60 ./mev-bot start
# Monitor logs for:
# - Opportunities detected
# - Opportunities expired (should see more due to shorter TTL)
# - Execution attempts
# - Success rate
5. Validate Results:
# After 1 hour, compare metrics
./scripts/analyze-l2-phase1.sh
# Check for:
# - Reduced stale opportunity execution ✅
# - Similar or better success rate ✅
# - No increase in errors ✅
6. Rollback if Needed:
# Set in config/arbitrum_production.yaml
features:
use_arbitrum_optimized_timeouts: false # Back to legacy
Phase 2: Transaction Pre-filtering (Week 2)
Effort: 4-6 hours | Risk: Medium | Reversible: Yes
What's Changing
- Filter non-DEX transactions at monitor level
- Expected 80-90% reduction in processed transactions
- Improved latency and reduced CPU usage
Implementation Steps
1. Create DEX filter module:
// pkg/monitor/dex_filter.go
package monitor
import (
"encoding/hex"
"sync"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
)
type DEXFilter struct {
knownDEXAddresses map[common.Address]bool
swapSignatures map[string]bool
mu sync.RWMutex
// Statistics
totalTx uint64
filteredTx uint64
passedTx uint64
}
func NewDEXFilter(dexAddresses []common.Address, swapSigs []string) *DEXFilter {
filter := &DEXFilter{
knownDEXAddresses: make(map[common.Address]bool),
swapSignatures: make(map[string]bool),
}
// Build lookup maps
for _, addr := range dexAddresses {
filter.knownDEXAddresses[addr] = true
}
for _, sig := range swapSigs {
filter.swapSignatures[sig] = true
}
return filter
}
func (f *DEXFilter) ShouldProcess(tx *types.Transaction) bool {
f.mu.Lock()
f.totalTx++
f.mu.Unlock()
// Must have a recipient
if tx.To() == nil {
f.incrementFiltered()
return false
}
// Check if recipient is known DEX
if f.knownDEXAddresses[*tx.To()] {
f.incrementPassed()
return true
}
// Check function signature
if len(tx.Data()) >= 4 {
sig := hex.EncodeToString(tx.Data()[:4])
if f.swapSignatures[sig] {
f.incrementPassed()
return true
}
}
f.incrementFiltered()
return false
}
func (f *DEXFilter) GetStats() (total, filtered, passed uint64) {
f.mu.RLock()
defer f.mu.RUnlock()
return f.totalTx, f.filteredTx, f.passedTx
}
func (f *DEXFilter) incrementFiltered() {
f.mu.Lock()
f.filteredTx++
f.mu.Unlock()
}
func (f *DEXFilter) incrementPassed() {
f.mu.Lock()
f.passedTx++
f.mu.Unlock()
}
2. Integrate into monitor:
// pkg/monitor/concurrent.go
type ArbitrumMonitor struct {
// ... existing fields ...
dexFilter *DEXFilter
filterEnabled bool
}
func (m *ArbitrumMonitor) processTransaction(tx *types.Transaction) {
// Apply filter if enabled
if m.filterEnabled && !m.dexFilter.ShouldProcess(tx) {
// Log occasionally (1% sample rate)
if rand.Float64() < 0.01 {
m.logger.Debug(fmt.Sprintf(
"Filtered non-DEX tx: %s to %s",
tx.Hash().Hex()[:10],
tx.To().Hex()[:10],
))
}
return
}
// Process as normal
m.processSwapTransaction(tx)
}
// Periodic stats logging
func (m *ArbitrumMonitor) logFilterStats() {
total, filtered, passed := m.dexFilter.GetStats()
filterRate := float64(filtered) / float64(total) * 100
m.logger.Info(fmt.Sprintf(
"DEX Filter Stats: total=%d, passed=%d (%.1f%%), filtered=%d (%.1f%%)",
total, passed, 100-filterRate, filtered, filterRate,
))
}
3. Enable Phase 2:
# config/arbitrum_production.yaml
features:
enable_dex_prefilter: true # Enable filtering
log_filtered_transactions: true # Log for monitoring
dex_filter:
enabled: true
filter_mode: "whitelist"
log_filtered: true
filtered_log_sample_rate: 0.01 # Log 1%
4. Test and Monitor:
# Start with filtering enabled
PROVIDER_CONFIG_PATH=$PWD/config/providers_runtime.yaml timeout 60 ./mev-bot start
# Watch filter statistics
tail -f logs/mev_bot.log | grep "DEX Filter Stats"
# Should see ~80-90% filtering rate
# Example: filtered=890, passed=110 (11%)
5. Validate No Missed Opportunities:
# Check that we're not filtering profitable transactions
./scripts/validate-filter-accuracy.sh
# Reviews:
# - Opportunities before filtering: X
# - Opportunities after filtering: Y
# - Difference should be <1%
Phase 3: Sequencer Feed (Week 3)
Effort: 8-12 hours | Risk: Medium | Reversible: Yes
Status
⏸️ Defer to Phase 3 - Requires more extensive testing
Reason
- Direct sequencer feed monitoring requires careful testing
- Risk of connection issues impacting operations
- Phase 1 & 2 provide significant improvements already
Future Implementation
When ready for Phase 3, see docs/L2_MEV_BOT_RESEARCH_REPORT.md Section 3.2 for detailed implementation plan.
Phase 4-5: Timeboost (Month 2+)
Effort: 16-24 hours | Risk: High | Reversible: Partial
Status
🔮 Future Feature - Only if competition demands
Decision Criteria
Implement Timeboost if:
- ✅ Phases 1-2 deployed successfully
- ✅ Consistently profitable for >1 month
- ✅ Evidence of opportunities being sniped by express lane users
- ✅ Average opportunity profit >$100
- ✅ Sufficient capital for express lane bidding ($1000+ reserved)
Implementation
See docs/L2_MEV_BOT_RESEARCH_REPORT.md Section 3.1 for complete Timeboost integration guide.
Testing Checklist
Pre-Deployment Tests
- Config validates without errors
- All DEX addresses are correct
- Swap signatures are complete
- Backward compatibility config present
- Rollback procedure tested
Phase 1 Tests
- Opportunities expire faster (5s vs 30s)
- No increase in error rate
- Similar or better success rate
- Reduced stale opportunity execution
- System remains stable
Phase 2 Tests
- 80-90% transaction filtering rate
- No missed DEX transactions
- Reduced CPU usage
- Improved latency
- Filter stats logging works
- Sample logging at correct rate (1%)
Performance Tests
- Load test with 1000+ tx/sec
- Memory usage stable
- No goroutine leaks
- Latency within targets (<200ms)
- Success rate maintained or improved
Monitoring & Metrics
Key Metrics to Track
Phase 1 (Timing):
- Opportunity TTL hits (count)
- Average opportunity age at execution
- Stale opportunity rejections
- Execution success rate
Phase 2 (Filtering):
- Total transactions processed
- Transactions filtered (%)
- Transactions passed (%)
- CPU usage (before/after)
- Memory usage (before/after)
- Average detection latency
Comparative:
- Opportunities detected (before/after)
- Opportunities executed (before/after)
- Total profit (before/after)
- Success rate (before/after)
Monitoring Commands
# Real-time L2-specific monitoring
./scripts/watch-l2-metrics.sh
# Generate performance report
./scripts/generate-l2-report.sh --period 24h
# Compare with baseline
./scripts/compare-performance.sh \
--baseline logs/baseline_metrics.json \
--current logs/current_metrics.json
Rollback Procedures
Emergency Rollback (Immediate)
If critical issues detected:
# Stop bot
pkill mev-bot
# Restore backup config
cp config/arbitrum_production.yaml.backup config/arbitrum_production.yaml
# Restart
PROVIDER_CONFIG_PATH=$PWD/config/providers_runtime.yaml ./mev-bot start
Feature-Specific Rollback
Phase 1:
features:
use_arbitrum_optimized_timeouts: false
Phase 2:
features:
enable_dex_prefilter: false
Automatic Rollback
The system includes automatic rollback on high failure rate:
legacy_config:
auto_rollback_on_failure: true
rollback_threshold_failures: 10 # After 10 consecutive failures
Success Criteria
Phase 1 Success
- ✅ Reduced stale opportunity execution by >50%
- ✅ Maintained or improved success rate
- ✅ No increase in error rate
- ✅ System stability maintained
Phase 2 Success
- ✅ 80-90% transaction filtering achieved
- ✅ <1% missed DEX transactions
- ✅ >30% reduction in CPU usage
- ✅ >20% improvement in detection latency
- ✅ No degradation in opportunity detection
Overall Success
- ✅ Maintained profitability
- ✅ Improved competitive position
- ✅ Reduced resource usage
- ✅ Better alignment with L2 characteristics
- ✅ No breaking changes or downtime
Troubleshooting
Issue: Increased Opportunity Expiration
Symptom: Many opportunities expiring before execution Cause: TTL too short (5s might be aggressive) Fix:
arbitrage_optimized:
opportunity_ttl: "7s" # Increase to 7s (28 blocks)
Issue: Filter Missing Opportunities
Symptom: Fewer opportunities detected with filter enabled Fix:
- Check filtered transaction logs
- Identify missed DEX addresses or signatures
- Add to filter configuration
- Redeploy
Issue: High CPU Usage with Filter
Symptom: CPU usage higher than expected Cause: Inefficient filter lookups Fix:
dex_filter:
cache_lookups: true
cache_ttl: "5m"
Next Steps After Deployment
- Week 1: Deploy Phase 1, monitor for 7 days
- Week 2: If Phase 1 successful, deploy Phase 2
- Week 3: Monitor combined Phase 1+2 performance
- Week 4: Gather data for Phase 3 decision
- Month 2+: Evaluate Timeboost based on competition
Support & Documentation
- Research Report:
docs/L2_MEV_BOT_RESEARCH_REPORT.md - Configuration:
config/arbitrum_optimized.yaml - Scripts:
scripts/l2-*.sh - Monitoring:
scripts/watch-l2-metrics.sh
Status: ✅ Ready for Phase 1 Deployment Risk Level: 🟢 Low (Non-Breaking Changes) Estimated Impact: 📈 20-30% Performance Improvement