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>
623 lines
18 KiB
Go
623 lines
18 KiB
Go
//go:build integration && legacy && forked
|
|
// +build integration,legacy,forked
|
|
|
|
package test_main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"math/rand"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"sync"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
|
|
"github.com/fraktal/mev-beta/internal/logger"
|
|
"github.com/fraktal/mev-beta/pkg/arbitrum"
|
|
)
|
|
|
|
// MockSequencerService simulates Arbitrum sequencer behavior for testing
|
|
type MockSequencerService struct {
|
|
config *SequencerConfig
|
|
logger *logger.Logger
|
|
storage *TransactionStorage
|
|
|
|
// Simulation state
|
|
currentBlock uint64
|
|
transactionQueue []*SequencerTransaction
|
|
subscribers map[string]chan *SequencerBlock
|
|
|
|
// Timing simulation
|
|
blockTimer *time.Ticker
|
|
batchTimer *time.Ticker
|
|
|
|
// Control
|
|
running bool
|
|
mu sync.RWMutex
|
|
|
|
// Metrics
|
|
metrics *SequencerMetrics
|
|
}
|
|
|
|
// SequencerTransaction represents a transaction in the sequencer
|
|
type SequencerTransaction struct {
|
|
*RealTransactionData
|
|
|
|
// Sequencer-specific fields
|
|
SubmittedAt time.Time `json:"submitted_at"`
|
|
ProcessedAt time.Time `json:"processed_at"`
|
|
BatchID string `json:"batch_id"`
|
|
SequenceNumber uint64 `json:"sequence_number"`
|
|
Priority int `json:"priority"`
|
|
CompressionSize int `json:"compression_size"`
|
|
ValidationTime time.Duration `json:"validation_time"`
|
|
InclusionDelay time.Duration `json:"inclusion_delay"`
|
|
}
|
|
|
|
// SequencerBlock represents a block produced by the sequencer
|
|
type SequencerBlock struct {
|
|
Number uint64 `json:"number"`
|
|
Hash common.Hash `json:"hash"`
|
|
ParentHash common.Hash `json:"parent_hash"`
|
|
Timestamp time.Time `json:"timestamp"`
|
|
Transactions []*SequencerTransaction `json:"transactions"`
|
|
BatchID string `json:"batch_id"`
|
|
GasUsed uint64 `json:"gas_used"`
|
|
GasLimit uint64 `json:"gas_limit"`
|
|
CompressionRatio float64 `json:"compression_ratio"`
|
|
ProcessingTime time.Duration `json:"processing_time"`
|
|
}
|
|
|
|
// SequencerMetrics tracks sequencer performance metrics
|
|
type SequencerMetrics struct {
|
|
BlocksProduced uint64 `json:"blocks_produced"`
|
|
TransactionsProcessed uint64 `json:"transactions_processed"`
|
|
DEXTransactionsFound uint64 `json:"dex_transactions_found"`
|
|
MEVTransactionsFound uint64 `json:"mev_transactions_found"`
|
|
AverageBlockTime time.Duration `json:"average_block_time"`
|
|
AverageTxPerBlock float64 `json:"average_tx_per_block"`
|
|
AverageCompressionRatio float64 `json:"average_compression_ratio"`
|
|
TotalProcessingTime time.Duration `json:"total_processing_time"`
|
|
ErrorCount uint64 `json:"error_count"`
|
|
|
|
// Real-time metrics
|
|
LastBlockTime time.Time `json:"last_block_time"`
|
|
QueueSize int `json:"queue_size"`
|
|
SubscriberCount int `json:"subscriber_count"`
|
|
|
|
mu sync.RWMutex
|
|
}
|
|
|
|
// NewMockSequencerService creates a new mock sequencer service
|
|
func NewMockSequencerService(config *SequencerConfig, logger *logger.Logger, storage *TransactionStorage) *MockSequencerService {
|
|
return &MockSequencerService{
|
|
config: config,
|
|
logger: logger,
|
|
storage: storage,
|
|
subscribers: make(map[string]chan *SequencerBlock),
|
|
metrics: &SequencerMetrics{},
|
|
}
|
|
}
|
|
|
|
// Start starts the mock sequencer service
|
|
func (mss *MockSequencerService) Start(ctx context.Context) error {
|
|
mss.mu.Lock()
|
|
defer mss.mu.Unlock()
|
|
|
|
if mss.running {
|
|
return fmt.Errorf("sequencer service already running")
|
|
}
|
|
|
|
mss.logger.Info("Starting mock Arbitrum sequencer service...")
|
|
|
|
// Initialize state
|
|
mss.currentBlock = mss.config.StartBlock
|
|
if mss.currentBlock == 0 {
|
|
mss.currentBlock = 150000000 // Start from recent Arbitrum block
|
|
}
|
|
|
|
// Load test data from storage
|
|
if err := mss.loadTestData(); err != nil {
|
|
return fmt.Errorf("failed to load test data: %w", err)
|
|
}
|
|
|
|
// Start block production timer
|
|
mss.blockTimer = time.NewTicker(mss.config.SequencerTiming)
|
|
|
|
// Start batch processing timer (faster than blocks)
|
|
batchInterval := mss.config.SequencerTiming / 4 // 4 batches per block
|
|
mss.batchTimer = time.NewTicker(batchInterval)
|
|
|
|
mss.running = true
|
|
|
|
// Start goroutines
|
|
go mss.blockProductionLoop(ctx)
|
|
go mss.batchProcessingLoop(ctx)
|
|
go mss.metricsUpdateLoop(ctx)
|
|
|
|
mss.logger.Info(fmt.Sprintf("Mock sequencer started - producing blocks every %v", mss.config.SequencerTiming))
|
|
return nil
|
|
}
|
|
|
|
// Stop stops the mock sequencer service
|
|
func (mss *MockSequencerService) Stop() {
|
|
mss.mu.Lock()
|
|
defer mss.mu.Unlock()
|
|
|
|
if !mss.running {
|
|
return
|
|
}
|
|
|
|
mss.logger.Info("Stopping mock sequencer service...")
|
|
|
|
mss.running = false
|
|
|
|
if mss.blockTimer != nil {
|
|
mss.blockTimer.Stop()
|
|
}
|
|
if mss.batchTimer != nil {
|
|
mss.batchTimer.Stop()
|
|
}
|
|
|
|
// Close subscriber channels
|
|
for id, ch := range mss.subscribers {
|
|
close(ch)
|
|
delete(mss.subscribers, id)
|
|
}
|
|
|
|
mss.logger.Info("Mock sequencer stopped")
|
|
}
|
|
|
|
// loadTestData loads transaction data from storage for simulation
|
|
func (mss *MockSequencerService) loadTestData() error {
|
|
// Get recent transactions from storage
|
|
stats := mss.storage.GetStorageStats()
|
|
if stats.TotalTransactions == 0 {
|
|
mss.logger.Warn("No test data available in storage - sequencer will run with minimal data")
|
|
return nil
|
|
}
|
|
|
|
// Load a subset of transactions for simulation
|
|
criteria := &DatasetCriteria{
|
|
MaxTransactions: 1000, // Load up to 1000 transactions
|
|
SortBy: "block",
|
|
SortDesc: true, // Get most recent first
|
|
}
|
|
|
|
dataset, err := mss.storage.ExportDataset(criteria)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to export dataset: %w", err)
|
|
}
|
|
|
|
// Convert to sequencer transactions
|
|
mss.transactionQueue = make([]*SequencerTransaction, 0, len(dataset.Transactions))
|
|
for i, tx := range dataset.Transactions {
|
|
seqTx := &SequencerTransaction{
|
|
RealTransactionData: tx,
|
|
SubmittedAt: time.Now().Add(-time.Duration(len(dataset.Transactions)-i) * time.Second),
|
|
SequenceNumber: uint64(i),
|
|
Priority: mss.calculateTransactionPriority(tx),
|
|
}
|
|
mss.transactionQueue = append(mss.transactionQueue, seqTx)
|
|
}
|
|
|
|
mss.logger.Info(fmt.Sprintf("Loaded %d transactions for sequencer simulation", len(mss.transactionQueue)))
|
|
return nil
|
|
}
|
|
|
|
// calculateTransactionPriority calculates transaction priority for sequencing
|
|
func (mss *MockSequencerService) calculateTransactionPriority(tx *RealTransactionData) int {
|
|
priority := 0
|
|
|
|
// Higher gas price = higher priority
|
|
if tx.GasPrice != nil {
|
|
priority += int(tx.GasPrice.Uint64() / 1e9) // Convert to gwei
|
|
}
|
|
|
|
// MEV transactions get higher priority
|
|
switch tx.MEVClassification {
|
|
case "potential_arbitrage":
|
|
priority += 100
|
|
case "large_swap":
|
|
priority += 50
|
|
case "high_slippage":
|
|
priority += 25
|
|
}
|
|
|
|
// Add randomness for realistic simulation
|
|
priority += rand.Intn(20) - 10
|
|
|
|
return priority
|
|
}
|
|
|
|
// blockProductionLoop produces blocks at regular intervals
|
|
func (mss *MockSequencerService) blockProductionLoop(ctx context.Context) {
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-mss.blockTimer.C:
|
|
if err := mss.produceBlock(); err != nil {
|
|
mss.logger.Error(fmt.Sprintf("Failed to produce block: %v", err))
|
|
mss.incrementErrorCount()
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// batchProcessingLoop processes transaction batches
|
|
func (mss *MockSequencerService) batchProcessingLoop(ctx context.Context) {
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-mss.batchTimer.C:
|
|
mss.processBatch()
|
|
}
|
|
}
|
|
}
|
|
|
|
// metricsUpdateLoop updates metrics periodically
|
|
func (mss *MockSequencerService) metricsUpdateLoop(ctx context.Context) {
|
|
ticker := time.NewTicker(10 * time.Second)
|
|
defer ticker.Stop()
|
|
|
|
for {
|
|
select {
|
|
case <-ctx.Done():
|
|
return
|
|
case <-ticker.C:
|
|
mss.updateMetrics()
|
|
}
|
|
}
|
|
}
|
|
|
|
// produceBlock creates and broadcasts a new block
|
|
func (mss *MockSequencerService) produceBlock() error {
|
|
startTime := time.Now()
|
|
|
|
mss.mu.Lock()
|
|
|
|
// Get transactions for this block
|
|
blockTxs := mss.selectTransactionsForBlock()
|
|
|
|
// Update current block number
|
|
mss.currentBlock++
|
|
|
|
// Create block
|
|
block := &SequencerBlock{
|
|
Number: mss.currentBlock,
|
|
Hash: mss.generateBlockHash(),
|
|
ParentHash: mss.generateParentHash(),
|
|
Timestamp: time.Now(),
|
|
Transactions: blockTxs,
|
|
BatchID: fmt.Sprintf("batch_%d_%d", mss.currentBlock, time.Now().Unix()),
|
|
GasLimit: 32000000, // Arbitrum block gas limit
|
|
}
|
|
|
|
// Calculate block metrics
|
|
var totalGasUsed uint64
|
|
var totalCompressionSize int
|
|
var totalOriginalSize int
|
|
|
|
for _, tx := range blockTxs {
|
|
totalGasUsed += tx.GasUsed
|
|
|
|
// Simulate compression
|
|
originalSize := len(tx.Data) + 200 // Approximate transaction overhead
|
|
compressedSize := int(float64(originalSize) * (0.3 + rand.Float64()*0.4)) // 30-70% compression
|
|
tx.CompressionSize = compressedSize
|
|
|
|
totalCompressionSize += compressedSize
|
|
totalOriginalSize += originalSize
|
|
|
|
// Set processing time
|
|
tx.ProcessedAt = time.Now()
|
|
tx.ValidationTime = time.Duration(rand.Intn(10)+1) * time.Millisecond
|
|
tx.InclusionDelay = time.Since(tx.SubmittedAt)
|
|
}
|
|
|
|
block.GasUsed = totalGasUsed
|
|
if totalOriginalSize > 0 {
|
|
block.CompressionRatio = float64(totalCompressionSize) / float64(totalOriginalSize)
|
|
}
|
|
block.ProcessingTime = time.Since(startTime)
|
|
|
|
mss.mu.Unlock()
|
|
|
|
// Update metrics
|
|
mss.updateBlockMetrics(block)
|
|
|
|
// Broadcast to subscribers
|
|
mss.broadcastBlock(block)
|
|
|
|
mss.logger.Debug(fmt.Sprintf("Produced block %d with %d transactions (%.2f%% compression, %v processing time)",
|
|
block.Number, len(block.Transactions), block.CompressionRatio*100, block.ProcessingTime))
|
|
|
|
return nil
|
|
}
|
|
|
|
// selectTransactionsForBlock selects transactions for inclusion in a block
|
|
func (mss *MockSequencerService) selectTransactionsForBlock() []*SequencerTransaction {
|
|
// Sort transactions by priority
|
|
sort.Slice(mss.transactionQueue, func(i, j int) bool {
|
|
return mss.transactionQueue[i].Priority > mss.transactionQueue[j].Priority
|
|
})
|
|
|
|
// Select transactions up to gas limit or batch size
|
|
var selectedTxs []*SequencerTransaction
|
|
var totalGas uint64
|
|
maxGas := uint64(32000000) // Arbitrum block gas limit
|
|
maxTxs := mss.config.BatchSize
|
|
|
|
for i, tx := range mss.transactionQueue {
|
|
if len(selectedTxs) >= maxTxs || totalGas+tx.GasUsed > maxGas {
|
|
break
|
|
}
|
|
|
|
selectedTxs = append(selectedTxs, tx)
|
|
totalGas += tx.GasUsed
|
|
|
|
// Remove from queue
|
|
mss.transactionQueue = append(mss.transactionQueue[:i], mss.transactionQueue[i+1:]...)
|
|
}
|
|
|
|
// Replenish queue with new simulated transactions if running low
|
|
if len(mss.transactionQueue) < 10 {
|
|
mss.generateSimulatedTransactions(50)
|
|
}
|
|
|
|
return selectedTxs
|
|
}
|
|
|
|
// generateSimulatedTransactions creates simulated transactions to keep the queue populated
|
|
func (mss *MockSequencerService) generateSimulatedTransactions(count int) {
|
|
for i := 0; i < count; i++ {
|
|
tx := mss.createSimulatedTransaction()
|
|
mss.transactionQueue = append(mss.transactionQueue, tx)
|
|
}
|
|
}
|
|
|
|
// createSimulatedTransaction creates a realistic simulated transaction
|
|
func (mss *MockSequencerService) createSimulatedTransaction() *SequencerTransaction {
|
|
// Create base transaction data
|
|
hash := common.HexToHash(fmt.Sprintf("0x%064x", rand.Uint64()))
|
|
|
|
protocols := []string{"UniswapV3", "UniswapV2", "SushiSwap", "Camelot", "1Inch"}
|
|
protocol := protocols[rand.Intn(len(protocols))]
|
|
|
|
mevTypes := []string{"regular_swap", "large_swap", "potential_arbitrage", "high_slippage"}
|
|
mevType := mevTypes[rand.Intn(len(mevTypes))]
|
|
|
|
// Simulate realistic swap values
|
|
minValue := 100.0
|
|
maxValue := 50000.0
|
|
valueUSD := minValue + rand.Float64()*(maxValue-minValue)
|
|
|
|
tx := &SequencerTransaction{
|
|
RealTransactionData: &RealTransactionData{
|
|
Hash: hash,
|
|
BlockNumber: mss.currentBlock + 1,
|
|
From: common.HexToAddress(fmt.Sprintf("0x%040x", rand.Uint64())),
|
|
To: &common.Address{},
|
|
GasUsed: uint64(21000 + rand.Intn(500000)), // 21k to 521k gas
|
|
EstimatedValueUSD: valueUSD,
|
|
MEVClassification: mevType,
|
|
SequencerTimestamp: time.Now(),
|
|
ParsedDEX: &arbitrum.DEXTransaction{
|
|
Protocol: protocol,
|
|
},
|
|
},
|
|
SubmittedAt: time.Now(),
|
|
SequenceNumber: uint64(len(mss.transactionQueue)),
|
|
Priority: mss.calculatePriorityFromValue(valueUSD, mevType),
|
|
}
|
|
|
|
return tx
|
|
}
|
|
|
|
// calculatePriorityFromValue calculates priority based on transaction value and type
|
|
func (mss *MockSequencerService) calculatePriorityFromValue(valueUSD float64, mevType string) int {
|
|
priority := int(valueUSD / 1000) // Base priority from value
|
|
|
|
switch mevType {
|
|
case "potential_arbitrage":
|
|
priority += 100
|
|
case "large_swap":
|
|
priority += 50
|
|
case "high_slippage":
|
|
priority += 25
|
|
}
|
|
|
|
return priority + rand.Intn(20) - 10
|
|
}
|
|
|
|
// processBatch processes a batch of transactions (simulation of mempool processing)
|
|
func (mss *MockSequencerService) processBatch() {
|
|
// Simulate adding new transactions to the queue
|
|
newTxCount := rand.Intn(10) + 1 // 1-10 new transactions per batch
|
|
mss.generateSimulatedTransactions(newTxCount)
|
|
|
|
// Simulate validation and preprocessing
|
|
for _, tx := range mss.transactionQueue[len(mss.transactionQueue)-newTxCount:] {
|
|
tx.ValidationTime = time.Duration(rand.Intn(5)+1) * time.Millisecond
|
|
}
|
|
}
|
|
|
|
// generateBlockHash generates a realistic block hash
|
|
func (mss *MockSequencerService) generateBlockHash() common.Hash {
|
|
return common.HexToHash(fmt.Sprintf("0x%064x", rand.Uint64()))
|
|
}
|
|
|
|
// generateParentHash generates a parent block hash
|
|
func (mss *MockSequencerService) generateParentHash() common.Hash {
|
|
return common.HexToHash(fmt.Sprintf("0x%064x", rand.Uint64()))
|
|
}
|
|
|
|
// updateBlockMetrics updates metrics after block production
|
|
func (mss *MockSequencerService) updateBlockMetrics(block *SequencerBlock) {
|
|
mss.metrics.mu.Lock()
|
|
defer mss.metrics.mu.Unlock()
|
|
|
|
mss.metrics.BlocksProduced++
|
|
mss.metrics.TransactionsProcessed += uint64(len(block.Transactions))
|
|
mss.metrics.TotalProcessingTime += block.ProcessingTime
|
|
mss.metrics.LastBlockTime = block.Timestamp
|
|
mss.metrics.QueueSize = len(mss.transactionQueue)
|
|
|
|
// Count DEX and MEV transactions
|
|
for _, tx := range block.Transactions {
|
|
if tx.ParsedDEX != nil {
|
|
mss.metrics.DEXTransactionsFound++
|
|
}
|
|
if tx.MEVClassification != "regular_swap" {
|
|
mss.metrics.MEVTransactionsFound++
|
|
}
|
|
}
|
|
|
|
// Update averages
|
|
if mss.metrics.BlocksProduced > 0 {
|
|
mss.metrics.AverageTxPerBlock = float64(mss.metrics.TransactionsProcessed) / float64(mss.metrics.BlocksProduced)
|
|
mss.metrics.AverageBlockTime = mss.metrics.TotalProcessingTime / time.Duration(mss.metrics.BlocksProduced)
|
|
}
|
|
|
|
// Update compression ratio
|
|
mss.metrics.AverageCompressionRatio = (mss.metrics.AverageCompressionRatio*float64(mss.metrics.BlocksProduced-1) + block.CompressionRatio) / float64(mss.metrics.BlocksProduced)
|
|
}
|
|
|
|
// updateMetrics updates real-time metrics
|
|
func (mss *MockSequencerService) updateMetrics() {
|
|
mss.metrics.mu.Lock()
|
|
defer mss.metrics.mu.Unlock()
|
|
|
|
mss.metrics.QueueSize = len(mss.transactionQueue)
|
|
mss.metrics.SubscriberCount = len(mss.subscribers)
|
|
}
|
|
|
|
// incrementErrorCount increments the error count
|
|
func (mss *MockSequencerService) incrementErrorCount() {
|
|
mss.metrics.mu.Lock()
|
|
defer mss.metrics.mu.Unlock()
|
|
mss.metrics.ErrorCount++
|
|
}
|
|
|
|
// broadcastBlock broadcasts a block to all subscribers
|
|
func (mss *MockSequencerService) broadcastBlock(block *SequencerBlock) {
|
|
mss.mu.RLock()
|
|
defer mss.mu.RUnlock()
|
|
|
|
for id, ch := range mss.subscribers {
|
|
select {
|
|
case ch <- block:
|
|
// Block sent successfully
|
|
default:
|
|
// Channel is full or closed, remove subscriber
|
|
mss.logger.Warn(fmt.Sprintf("Removing unresponsive subscriber: %s", id))
|
|
close(ch)
|
|
delete(mss.subscribers, id)
|
|
}
|
|
}
|
|
}
|
|
|
|
// Subscribe subscribes to block updates
|
|
func (mss *MockSequencerService) Subscribe(id string) <-chan *SequencerBlock {
|
|
mss.mu.Lock()
|
|
defer mss.mu.Unlock()
|
|
|
|
ch := make(chan *SequencerBlock, 10) // Buffer for 10 blocks
|
|
mss.subscribers[id] = ch
|
|
|
|
mss.logger.Debug(fmt.Sprintf("New subscriber: %s", id))
|
|
return ch
|
|
}
|
|
|
|
// Unsubscribe unsubscribes from block updates
|
|
func (mss *MockSequencerService) Unsubscribe(id string) {
|
|
mss.mu.Lock()
|
|
defer mss.mu.Unlock()
|
|
|
|
if ch, exists := mss.subscribers[id]; exists {
|
|
close(ch)
|
|
delete(mss.subscribers, id)
|
|
mss.logger.Debug(fmt.Sprintf("Unsubscribed: %s", id))
|
|
}
|
|
}
|
|
|
|
// GetMetrics returns current sequencer metrics
|
|
func (mss *MockSequencerService) GetMetrics() *SequencerMetrics {
|
|
mss.metrics.mu.RLock()
|
|
defer mss.metrics.mu.RUnlock()
|
|
|
|
// Return a copy
|
|
metricsCopy := *mss.metrics
|
|
return &metricsCopy
|
|
}
|
|
|
|
// GetCurrentBlock returns the current block number
|
|
func (mss *MockSequencerService) GetCurrentBlock() uint64 {
|
|
mss.mu.RLock()
|
|
defer mss.mu.RUnlock()
|
|
return mss.currentBlock
|
|
}
|
|
|
|
// GetQueueSize returns the current transaction queue size
|
|
func (mss *MockSequencerService) GetQueueSize() int {
|
|
mss.mu.RLock()
|
|
defer mss.mu.RUnlock()
|
|
return len(mss.transactionQueue)
|
|
}
|
|
|
|
// SimulateMEVBurst simulates a burst of MEV activity
|
|
func (mss *MockSequencerService) SimulateMEVBurst(txCount int) {
|
|
mss.mu.Lock()
|
|
defer mss.mu.Unlock()
|
|
|
|
mss.logger.Info(fmt.Sprintf("Simulating MEV burst with %d transactions", txCount))
|
|
|
|
for i := 0; i < txCount; i++ {
|
|
tx := mss.createSimulatedTransaction()
|
|
|
|
// Make it an MEV transaction
|
|
mevTypes := []string{"potential_arbitrage", "large_swap", "high_slippage"}
|
|
tx.MEVClassification = mevTypes[rand.Intn(len(mevTypes))]
|
|
tx.EstimatedValueUSD = 10000 + rand.Float64()*90000 // $10k-$100k
|
|
tx.Priority = mss.calculatePriorityFromValue(tx.EstimatedValueUSD, tx.MEVClassification)
|
|
|
|
mss.transactionQueue = append(mss.transactionQueue, tx)
|
|
}
|
|
}
|
|
|
|
// ExportMetrics exports sequencer metrics to a file
|
|
func (mss *MockSequencerService) ExportMetrics(filename string) error {
|
|
metrics := mss.GetMetrics()
|
|
|
|
data, err := json.MarshalIndent(metrics, "", " ")
|
|
if err != nil {
|
|
return fmt.Errorf("failed to marshal metrics: %w", err)
|
|
}
|
|
|
|
if err := mss.storage.saveToDataDir(filename, data); err != nil {
|
|
return fmt.Errorf("failed to save metrics: %w", err)
|
|
}
|
|
|
|
mss.logger.Info(fmt.Sprintf("Exported sequencer metrics to %s", filename))
|
|
return nil
|
|
}
|
|
|
|
// saveToDataDir is a helper method for storage
|
|
func (ts *TransactionStorage) saveToDataDir(filename string, data []byte) error {
|
|
filePath := filepath.Join(ts.dataDir, filename)
|
|
return os.WriteFile(filePath, data, 0644)
|
|
}
|
|
|
|
// GetRecentBlocks returns recently produced blocks
|
|
func (mss *MockSequencerService) GetRecentBlocks(count int) []*SequencerBlock {
|
|
// This would maintain a history of recent blocks in a real implementation
|
|
// For now, return empty slice as this is primarily for testing
|
|
return []*SequencerBlock{}
|
|
}
|