feat: create v2-prep branch with comprehensive planning
Restructured project for V2 refactor: **Structure Changes:** - Moved all V1 code to orig/ folder (preserved with git mv) - Created docs/planning/ directory - Added orig/README_V1.md explaining V1 preservation **Planning Documents:** - 00_V2_MASTER_PLAN.md: Complete architecture overview - Executive summary of critical V1 issues - High-level component architecture diagrams - 5-phase implementation roadmap - Success metrics and risk mitigation - 07_TASK_BREAKDOWN.md: Atomic task breakdown - 99+ hours of detailed tasks - Every task < 2 hours (atomic) - Clear dependencies and success criteria - Organized by implementation phase **V2 Key Improvements:** - Per-exchange parsers (factory pattern) - Multi-layer strict validation - Multi-index pool cache - Background validation pipeline - Comprehensive observability **Critical Issues Addressed:** - Zero address tokens (strict validation + cache enrichment) - Parsing accuracy (protocol-specific parsers) - No audit trail (background validation channel) - Inefficient lookups (multi-index cache) - Stats disconnection (event-driven metrics) Next Steps: 1. Review planning documents 2. Begin Phase 1: Foundation (P1-001 through P1-010) 3. Implement parsers in Phase 2 4. Build cache system in Phase 3 5. Add validation pipeline in Phase 4 6. Migrate and test in Phase 5 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
122
orig/pkg/arbitrum/rate_limited_rpc.go
Normal file
122
orig/pkg/arbitrum/rate_limited_rpc.go
Normal file
@@ -0,0 +1,122 @@
|
||||
package arbitrum
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"golang.org/x/time/rate"
|
||||
|
||||
pkgerrors "github.com/fraktal/mev-beta/pkg/errors"
|
||||
)
|
||||
|
||||
// RateLimitedRPC wraps an ethclient.Client with rate limiting
|
||||
type RateLimitedRPC struct {
|
||||
client *ethclient.Client
|
||||
limiter *rate.Limiter
|
||||
retryCount int
|
||||
}
|
||||
|
||||
// NewRateLimitedRPC creates a new rate limited RPC client
|
||||
func NewRateLimitedRPC(client *ethclient.Client, requestsPerSecond float64, retryCount int) *RateLimitedRPC {
|
||||
// Create a rate limiter that allows requestsPerSecond requests per second
|
||||
// with a burst equal to 2x requests per second
|
||||
limiter := rate.NewLimiter(rate.Limit(requestsPerSecond), int(requestsPerSecond*2))
|
||||
|
||||
return &RateLimitedRPC{
|
||||
client: client,
|
||||
limiter: limiter,
|
||||
retryCount: retryCount,
|
||||
}
|
||||
}
|
||||
|
||||
// CallWithRetry calls an RPC method with rate limiting and retry logic
|
||||
func (r *RateLimitedRPC) CallWithRetry(ctx context.Context, method string, args ...interface{}) (interface{}, error) {
|
||||
for i := 0; i < r.retryCount; i++ {
|
||||
// Wait for rate limiter allowance
|
||||
if err := r.limiter.Wait(ctx); err != nil {
|
||||
return nil, fmt.Errorf("rate limiter error: %w", err)
|
||||
}
|
||||
|
||||
// Execute the call
|
||||
result, err := r.executeCall(ctx, method, args...)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Check if this is a rate limit error that warrants retrying
|
||||
if isRateLimitError(err) {
|
||||
// Apply exponential backoff before retrying
|
||||
backoffTime := time.Duration(1<<uint(i)) * time.Second
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return nil, pkgerrors.WrapContextError(ctx.Err(), "RateLimitedRPC.CallWithRetry.rateLimitBackoff",
|
||||
map[string]interface{}{
|
||||
"method": method,
|
||||
"attempt": i + 1,
|
||||
"maxRetries": r.retryCount,
|
||||
"backoffTime": backoffTime.String(),
|
||||
"lastError": err.Error(),
|
||||
})
|
||||
case <-time.After(backoffTime):
|
||||
// Continue to next retry
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
// For non-rate limit errors, return immediately
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("max retries (%d) exceeded for method %s", r.retryCount, method)
|
||||
}
|
||||
|
||||
// executeCall executes the actual RPC call
|
||||
func (r *RateLimitedRPC) executeCall(ctx context.Context, method string, args ...interface{}) (interface{}, error) {
|
||||
// Since we don't have the specific method signatures here, we'll just
|
||||
// return a generic success response for demonstration
|
||||
// In a real implementation, you would actually call the appropriate method
|
||||
|
||||
// For now, we'll just simulate a successful call
|
||||
return "success", nil
|
||||
}
|
||||
|
||||
// isRateLimitError checks if an error is a rate limit error
|
||||
func isRateLimitError(err error) bool {
|
||||
if err == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
errStr := err.Error()
|
||||
|
||||
// Common rate limit error indicators
|
||||
rateLimitIndicators := []string{
|
||||
"rate limit",
|
||||
"rate-limit",
|
||||
"rps limit",
|
||||
"request limit",
|
||||
"too many requests",
|
||||
"429",
|
||||
"exceeded",
|
||||
"limit exceeded",
|
||||
}
|
||||
|
||||
for _, indicator := range rateLimitIndicators {
|
||||
if containsIgnoreCase(errStr, indicator) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// containsIgnoreCase checks if a string contains a substring (case insensitive)
|
||||
func containsIgnoreCase(s, substr string) bool {
|
||||
// Convert both strings to lowercase for comparison
|
||||
sLower := strings.ToLower(s)
|
||||
substrLower := strings.ToLower(substr)
|
||||
|
||||
return strings.Contains(sLower, substrLower)
|
||||
}
|
||||
Reference in New Issue
Block a user