Add enhanced concurrency patterns, rate limiting, market management, and pipeline processing
This commit is contained in:
@@ -3,74 +3,147 @@ package monitor
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"math/big"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/your-username/mev-beta/internal/config"
|
||||
"github.com/your-username/mev-beta/internal/logger"
|
||||
"github.com/your-username/mev-beta/internal/ratelimit"
|
||||
"github.com/your-username/mev-beta/pkg/market"
|
||||
"github.com/your-username/mev-beta/pkg/scanner"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"golang.org/x/time/rate"
|
||||
)
|
||||
|
||||
// ArbitrumMonitor monitors the Arbitrum sequencer for transactions
|
||||
// ArbitrumMonitor monitors the Arbitrum sequencer for transactions with concurrency support
|
||||
type ArbitrumMonitor struct {
|
||||
config *config.ArbitrumConfig
|
||||
botConfig *config.BotConfig
|
||||
client *ethclient.Client
|
||||
rpcEndpoint string
|
||||
logger *logger.Logger
|
||||
rateLimiter *ratelimit.LimiterManager
|
||||
marketMgr *market.MarketManager
|
||||
scanner *scanner.MarketScanner
|
||||
pipeline *market.Pipeline
|
||||
fanManager *market.FanManager
|
||||
limiter *rate.Limiter
|
||||
pollInterval time.Duration
|
||||
running bool
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
// NewArbitrumMonitor creates a new Arbitrum monitor
|
||||
func NewArbitrumMonitor(rpcEndpoint string, pollInterval time.Duration) (*ArbitrumMonitor, error) {
|
||||
client, err := ethclient.Dial(rpcEndpoint)
|
||||
// NewArbitrumMonitor creates a new Arbitrum monitor with rate limiting
|
||||
func NewArbitrumMonitor(
|
||||
arbCfg *config.ArbitrumConfig,
|
||||
botCfg *config.BotConfig,
|
||||
logger *logger.Logger,
|
||||
rateLimiter *ratelimit.LimiterManager,
|
||||
marketMgr *market.MarketManager,
|
||||
scanner *scanner.MarketScanner,
|
||||
) (*ArbitrumMonitor, error) {
|
||||
// Create Ethereum client
|
||||
client, err := ethclient.Dial(arbCfg.RPCEndpoint)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to connect to Arbitrum node: %v", err)
|
||||
}
|
||||
|
||||
// Create rate limiter based on config
|
||||
limiter := rate.NewLimiter(
|
||||
rate.Limit(arbCfg.RateLimit.RequestsPerSecond),
|
||||
arbCfg.RateLimit.Burst,
|
||||
)
|
||||
|
||||
// Create pipeline
|
||||
pipeline := market.NewPipeline(botCfg, logger, marketMgr, scanner)
|
||||
|
||||
// Add stages to pipeline
|
||||
pipeline.AddStage(market.TransactionDecoderStage(botCfg, logger, marketMgr))
|
||||
|
||||
// Create fan manager
|
||||
fanManager := market.NewFanManager(
|
||||
&config.Config{
|
||||
Arbitrum: *arbCfg,
|
||||
Bot: *botCfg,
|
||||
},
|
||||
logger,
|
||||
rateLimiter,
|
||||
)
|
||||
|
||||
return &ArbitrumMonitor{
|
||||
config: arbCfg,
|
||||
botConfig: botCfg,
|
||||
client: client,
|
||||
rpcEndpoint: rpcEndpoint,
|
||||
pollInterval: pollInterval,
|
||||
logger: logger,
|
||||
rateLimiter: rateLimiter,
|
||||
marketMgr: marketMgr,
|
||||
scanner: scanner,
|
||||
pipeline: pipeline,
|
||||
fanManager: fanManager,
|
||||
limiter: limiter,
|
||||
pollInterval: time.Duration(botCfg.PollingInterval) * time.Second,
|
||||
running: false,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Start begins monitoring the Arbitrum sequencer
|
||||
func (m *ArbitrumMonitor) Start(ctx context.Context) error {
|
||||
log.Println("Starting Arbitrum sequencer monitoring...")
|
||||
m.mu.Lock()
|
||||
m.running = true
|
||||
m.mu.Unlock()
|
||||
|
||||
m.logger.Info("Starting Arbitrum sequencer monitoring...")
|
||||
|
||||
// Get the latest block to start from
|
||||
if err := m.rateLimiter.WaitForLimit(ctx, m.config.RPCEndpoint); err != nil {
|
||||
return fmt.Errorf("rate limit error: %v", err)
|
||||
}
|
||||
|
||||
header, err := m.client.HeaderByNumber(ctx, nil)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get latest block header: %v", err)
|
||||
}
|
||||
|
||||
lastBlock := header.Number.Uint64()
|
||||
log.Printf("Starting from block: %d", lastBlock)
|
||||
m.logger.Info(fmt.Sprintf("Starting from block: %d", lastBlock))
|
||||
|
||||
for {
|
||||
m.mu.RLock()
|
||||
running := m.running
|
||||
m.mu.RUnlock()
|
||||
|
||||
if !running {
|
||||
break
|
||||
}
|
||||
|
||||
for m.running {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
m.running = false
|
||||
m.Stop()
|
||||
return nil
|
||||
case <-time.After(m.pollInterval):
|
||||
// Get the latest block
|
||||
if err := m.rateLimiter.WaitForLimit(ctx, m.config.RPCEndpoint); err != nil {
|
||||
m.logger.Error(fmt.Sprintf("Rate limit error: %v", err))
|
||||
continue
|
||||
}
|
||||
|
||||
header, err := m.client.HeaderByNumber(ctx, nil)
|
||||
if err != nil {
|
||||
log.Printf("Failed to get latest block header: %v", err)
|
||||
m.logger.Error(fmt.Sprintf("Failed to get latest block header: %v", err))
|
||||
continue
|
||||
}
|
||||
|
||||
currentBlock := header.Number.Uint64()
|
||||
|
||||
|
||||
// Process blocks from lastBlock+1 to currentBlock
|
||||
for blockNum := lastBlock + 1; blockNum <= currentBlock; blockNum++ {
|
||||
if err := m.processBlock(ctx, blockNum); err != nil {
|
||||
log.Printf("Failed to process block %d: %v", blockNum, err)
|
||||
m.logger.Error(fmt.Sprintf("Failed to process block %d: %v", blockNum, err))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
lastBlock = currentBlock
|
||||
}
|
||||
}
|
||||
@@ -80,24 +153,33 @@ func (m *ArbitrumMonitor) Start(ctx context.Context) error {
|
||||
|
||||
// Stop stops the monitor
|
||||
func (m *ArbitrumMonitor) Stop() {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
m.running = false
|
||||
m.logger.Info("Stopping Arbitrum monitor...")
|
||||
}
|
||||
|
||||
// processBlock processes a single block for potential swap transactions
|
||||
func (m *ArbitrumMonitor) processBlock(ctx context.Context, blockNumber uint64) error {
|
||||
log.Printf("Processing block %d", blockNumber)
|
||||
|
||||
m.logger.Debug(fmt.Sprintf("Processing block %d", blockNumber))
|
||||
|
||||
// Wait for rate limiter
|
||||
if err := m.rateLimiter.WaitForLimit(ctx, m.config.RPCEndpoint); err != nil {
|
||||
return fmt.Errorf("rate limit error: %v", err)
|
||||
}
|
||||
|
||||
// Get block by number
|
||||
block, err := m.client.BlockByNumber(ctx, big.NewInt(int64(blockNumber)))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to get block %d: %v", blockNumber, err)
|
||||
}
|
||||
|
||||
// Process each transaction in the block
|
||||
for _, tx := range block.Transactions() {
|
||||
if err := m.processTransaction(ctx, tx); err != nil {
|
||||
log.Printf("Failed to process transaction %s: %v", tx.Hash().Hex(), err)
|
||||
}
|
||||
// Process transactions using pipeline
|
||||
transactions := block.Transactions()
|
||||
|
||||
// Process transactions through the pipeline
|
||||
if err := m.pipeline.ProcessTransactions(ctx, transactions); err != nil {
|
||||
m.logger.Error(fmt.Sprintf("Pipeline processing error: %v", err))
|
||||
}
|
||||
|
||||
return nil
|
||||
@@ -108,17 +190,17 @@ func (m *ArbitrumMonitor) processTransaction(ctx context.Context, tx *types.Tran
|
||||
// Check if this is a potential swap transaction
|
||||
// This is a simplified check - in practice, you would check for
|
||||
// specific function signatures of Uniswap-like contracts
|
||||
|
||||
|
||||
// For now, we'll just log all transactions
|
||||
from, err := m.client.TransactionSender(ctx, tx, common.Hash{}, 0)
|
||||
if err != nil {
|
||||
// This can happen for pending transactions
|
||||
from = common.HexToAddress("0x0")
|
||||
}
|
||||
|
||||
log.Printf("Transaction: %s, From: %s, To: %s, Value: %s ETH",
|
||||
tx.Hash().Hex(),
|
||||
from.Hex(),
|
||||
|
||||
m.logger.Debug(fmt.Sprintf("Transaction: %s, From: %s, To: %s, Value: %s ETH",
|
||||
tx.Hash().Hex(),
|
||||
from.Hex(),
|
||||
func() string {
|
||||
if tx.To() != nil {
|
||||
return tx.To().Hex()
|
||||
@@ -126,15 +208,15 @@ func (m *ArbitrumMonitor) processTransaction(ctx context.Context, tx *types.Tran
|
||||
return "contract creation"
|
||||
}(),
|
||||
new(big.Float).Quo(new(big.Float).SetInt(tx.Value()), big.NewFloat(1e18)).String(),
|
||||
)
|
||||
|
||||
))
|
||||
|
||||
// TODO: Add logic to detect swap transactions and analyze them
|
||||
// This would involve:
|
||||
// 1. Checking if the transaction is calling a Uniswap-like contract
|
||||
// 2. Decoding the swap function call
|
||||
// 3. Extracting the token addresses and amounts
|
||||
// 4. Calculating potential price impact
|
||||
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -142,12 +224,12 @@ func (m *ArbitrumMonitor) processTransaction(ctx context.Context, tx *types.Tran
|
||||
func (m *ArbitrumMonitor) GetPendingTransactions(ctx context.Context) ([]*types.Transaction, error) {
|
||||
// This is a simplified implementation
|
||||
// In practice, you might need to use a different approach to access pending transactions
|
||||
|
||||
|
||||
// Query for pending transactions
|
||||
txs := make([]*types.Transaction, 0)
|
||||
|
||||
|
||||
// Note: ethclient doesn't directly expose pending transactions
|
||||
// You might need to use a different approach or a custom RPC call
|
||||
|
||||
|
||||
return txs, nil
|
||||
}
|
||||
Reference in New Issue
Block a user