//go:build integration && legacy // +build integration,legacy package sequencer import ( "context" "fmt" "math/big" "testing" "time" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/fraktal/mev-beta/internal/logger" "github.com/fraktal/mev-beta/pkg/arbitrum" ) // TestSequencerParserIntegration tests the parser against simulated sequencer data func TestSequencerParserIntegration(t *testing.T) { // Skip if no RPC endpoint configured rpcEndpoint := "wss://arbitrum-mainnet.core.chainstack.com/53c30e7a941160679fdcc396c894fc57" if rpcEndpoint == "" { t.Skip("RPC endpoint not configured") } // Create test components log := logger.New("debug", "text", "") client, err := ethclient.Dial(rpcEndpoint) require.NoError(t, err) defer client.Close() // Create parser parser := arbitrum.NewL2MessageParser(log) require.NotNil(t, parser) // Create sequencer simulator config := &SimulatorConfig{ ReplaySpeed: 10.0, // 10x speed for testing StartBlock: 250000000, // Recent Arbitrum block BatchSize: 10, EnableMetrics: true, } simulator := NewArbitrumSequencerSimulator(log, client, config) require.NotNil(t, simulator) // Load real block data endBlock := config.StartBlock + 9 // Load 10 blocks err = simulator.LoadRealBlockData(config.StartBlock, endBlock) require.NoError(t, err) // Subscribe to sequencer blocks blockChan := simulator.Subscribe() require.NotNil(t, blockChan) // Start simulation ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) defer cancel() err = simulator.StartSimulation(ctx) require.NoError(t, err) // Collect and validate parsed transactions var processedBlocks int var totalTransactions int var dexTransactions int var mevTransactions int var parseErrors int for { select { case block := <-blockChan: if block == nil { t.Log("Received nil block, simulation ended") goto AnalyzeResults } // Process each transaction in the block for _, tx := range block.Transactions { totalTransactions++ // Test parser with sequencer transaction data result := testTransactionParsing(t, parser, tx) if !result.Success { parseErrors++ t.Logf("Parse error for tx %s: %v", tx.Hash.Hex(), result.Error) } if tx.IsDEXTransaction { dexTransactions++ } if tx.IsMEVTransaction { mevTransactions++ } } processedBlocks++ t.Logf("Processed block %d with %d transactions (DEX: %d, MEV: %d)", block.Number, len(block.Transactions), countDEXInBlock(block), countMEVInBlock(block)) case <-ctx.Done(): t.Log("Test timeout reached") goto AnalyzeResults } if processedBlocks >= 10 { break } } AnalyzeResults: // Stop simulation simulator.Stop() // Validate results require.Greater(t, processedBlocks, 0, "Should have processed at least one block") require.Greater(t, totalTransactions, 0, "Should have processed transactions") // Calculate success rates parseSuccessRate := float64(totalTransactions-parseErrors) / float64(totalTransactions) * 100 dexPercentage := float64(dexTransactions) / float64(totalTransactions) * 100 mevPercentage := float64(mevTransactions) / float64(totalTransactions) * 100 t.Logf("=== SEQUENCER PARSER VALIDATION RESULTS ===") t.Logf("Blocks processed: %d", processedBlocks) t.Logf("Total transactions: %d", totalTransactions) t.Logf("DEX transactions: %d (%.2f%%)", dexTransactions, dexPercentage) t.Logf("MEV transactions: %d (%.2f%%)", mevTransactions, mevPercentage) t.Logf("Parse errors: %d", parseErrors) t.Logf("Parse success rate: %.2f%%", parseSuccessRate) // Assert minimum requirements assert.Greater(t, parseSuccessRate, 95.0, "Parse success rate should be > 95%") assert.Greater(t, dexPercentage, 5.0, "Should find DEX transactions in real blocks") // Get simulation metrics metrics := simulator.GetMetrics() t.Logf("Simulation metrics: %.2f blocks/s, %.2f tx/s", metrics.BlocksPerSecond, metrics.TxPerSecond) } // ParseResult contains the result of parsing a transaction type ParseResult struct { Success bool Error error SwapEvents int LiquidityEvents int TotalEvents int ParsedValue *big.Int GasUsed uint64 Protocol string } // testTransactionParsing tests parsing a single transaction func testTransactionParsing(t *testing.T, parser *arbitrum.L2MessageParser, tx *SequencerTransaction) *ParseResult { result := &ParseResult{ Success: true, ParsedValue: big.NewInt(0), } // Test basic transaction parsing if tx.Receipt == nil { result.Error = fmt.Errorf("transaction missing receipt") result.Success = false return result } // Count different event types for _, log := range tx.Receipt.Logs { result.TotalEvents++ switch log.EventName { case "Swap": result.SwapEvents++ result.Protocol = log.Protocol // Validate swap event parsing if err := validateSwapEvent(log); err != nil { result.Error = fmt.Errorf("swap event validation failed: %w", err) result.Success = false return result } case "Mint", "Burn": result.LiquidityEvents++ // Validate liquidity event parsing if err := validateLiquidityEvent(log); err != nil { result.Error = fmt.Errorf("liquidity event validation failed: %w", err) result.Success = false return result } } } // Validate transaction-level data if err := validateTransactionData(tx); err != nil { result.Error = fmt.Errorf("transaction validation failed: %w", err) result.Success = false return result } result.GasUsed = tx.GasUsed // Estimate parsed value from swap events if result.SwapEvents > 0 { result.ParsedValue = estimateSwapValue(tx) } return result } // validateSwapEvent validates that a swap event has all required fields func validateSwapEvent(log *SequencerLog) error { if log.EventName != "Swap" { return fmt.Errorf("expected Swap event, got %s", log.EventName) } if log.Protocol == "" { return fmt.Errorf("swap event missing protocol") } // Validate parsed arguments args := log.ParsedArgs if args == nil { return fmt.Errorf("swap event missing parsed arguments") } // Check for required fields based on protocol switch log.Protocol { case "UniswapV3": requiredFields := []string{"sender", "recipient", "amount0", "amount1", "sqrtPriceX96", "liquidity", "tick"} for _, field := range requiredFields { if _, exists := args[field]; !exists { return fmt.Errorf("UniswapV3 swap missing field: %s", field) } } // Validate amounts are not nil amount0, ok := args["amount0"].(*big.Int) if !ok || amount0 == nil { return fmt.Errorf("invalid amount0 in UniswapV3 swap") } amount1, ok := args["amount1"].(*big.Int) if !ok || amount1 == nil { return fmt.Errorf("invalid amount1 in UniswapV3 swap") } case "UniswapV2": requiredFields := []string{"sender", "to", "amount0In", "amount1In", "amount0Out", "amount1Out"} for _, field := range requiredFields { if _, exists := args[field]; !exists { return fmt.Errorf("UniswapV2 swap missing field: %s", field) } } } return nil } // validateLiquidityEvent validates that a liquidity event has all required fields func validateLiquidityEvent(log *SequencerLog) error { if log.EventName != "Mint" && log.EventName != "Burn" { return fmt.Errorf("expected Mint or Burn event, got %s", log.EventName) } if log.Protocol == "" { return fmt.Errorf("liquidity event missing protocol") } // Additional validation can be added here return nil } // validateTransactionData validates transaction-level data func validateTransactionData(tx *SequencerTransaction) error { // Validate addresses if tx.Hash == (common.Hash{}) { return fmt.Errorf("transaction missing hash") } if tx.From == (common.Address{}) { return fmt.Errorf("transaction missing from address") } // Validate gas data if tx.Gas == 0 { return fmt.Errorf("transaction has zero gas limit") } if tx.GasUsed > tx.Gas { return fmt.Errorf("transaction used more gas than limit: %d > %d", tx.GasUsed, tx.Gas) } // Validate pricing if tx.GasPrice == nil || tx.GasPrice.Sign() < 0 { return fmt.Errorf("transaction has invalid gas price") } // For EIP-1559 transactions, validate fee structure if tx.Type == 2 { // DynamicFeeTxType if tx.MaxFeePerGas == nil || tx.MaxPriorityFeePerGas == nil { return fmt.Errorf("EIP-1559 transaction missing fee fields") } if tx.MaxFeePerGas.Cmp(tx.MaxPriorityFeePerGas) < 0 { return fmt.Errorf("maxFeePerGas < maxPriorityFeePerGas") } } // Validate sequencer-specific fields if tx.L1BlockNumber == 0 { return fmt.Errorf("transaction missing L1 block number") } if tx.L2BlockTimestamp == 0 { return fmt.Errorf("transaction missing L2 timestamp") } return nil } // estimateSwapValue estimates the USD value of a swap transaction func estimateSwapValue(tx *SequencerTransaction) *big.Int { if tx.SwapValue != nil { return tx.SwapValue } // Fallback estimation based on gas usage gasValue := new(big.Int).Mul(big.NewInt(int64(tx.GasUsed)), tx.EffectiveGasPrice) return new(big.Int).Mul(gasValue, big.NewInt(50)) // Estimate swap is 50x gas cost } // TestHighValueTransactionParsing tests parsing of high-value transactions func TestHighValueTransactionParsing(t *testing.T) { log := logger.New("debug", "text", "") parser := arbitrum.NewL2MessageParser(log) // Create mock high-value transaction highValueTx := &SequencerTransaction{ Hash: common.HexToHash("0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef"), From: common.HexToAddress("0x1234567890123456789012345678901234567890"), To: &[]common.Address{common.HexToAddress("0xE592427A0AEce92De3Edee1F18E0157C05861564")}[0], // Uniswap V3 router Value: func() *big.Int { v := new(big.Int); v.SetString("100000000000000000000", 10); return v }(), // 100 ETH Gas: 500000, GasUsed: 450000, GasPrice: big.NewInt(1e10), // 10 gwei EffectiveGasPrice: big.NewInt(1e10), IsDEXTransaction: true, DEXProtocol: "UniswapV3", SwapValue: func() *big.Int { v := new(big.Int); v.SetString("1000000000000000000000", 10); return v }(), // 1000 ETH equivalent Receipt: &SequencerReceipt{ Status: 1, GasUsed: 450000, Logs: []*SequencerLog{ { Address: common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"), Topics: []common.Hash{common.HexToHash("0xc42079f94a6350d7e6235f29174924f928cc2ac818eb64fed8004e115fbcca67")}, EventName: "Swap", Protocol: "UniswapV3", ParsedArgs: map[string]interface{}{ "sender": common.HexToAddress("0x1234567890123456789012345678901234567890"), "recipient": common.HexToAddress("0x1234567890123456789012345678901234567890"), "amount0": big.NewInt(-1e18), // -1 ETH "amount1": big.NewInt(2000e6), // +2000 USDC "sqrtPriceX96": big.NewInt(1000000000000000000), "liquidity": big.NewInt(1e12), "tick": big.NewInt(195000), }, }, }, }, } // Test parsing result := testTransactionParsing(t, parser, highValueTx) require.True(t, result.Success, "High-value transaction parsing should succeed: %v", result.Error) // Validate specific fields for high-value transactions assert.Equal(t, 1, result.SwapEvents, "Should detect 1 swap event") assert.Equal(t, "UniswapV3", result.Protocol, "Should identify UniswapV3 protocol") threshold := new(big.Int) threshold.SetString("100000000000000000000", 10) assert.True(t, result.ParsedValue.Cmp(threshold) > 0, "Should parse high swap value") t.Logf("High-value transaction parsed successfully: %s ETH value", new(big.Float).Quo(new(big.Float).SetInt(result.ParsedValue), big.NewFloat(1e18)).String()) } // TestMEVTransactionDetection tests detection and parsing of MEV transactions func TestMEVTransactionDetection(t *testing.T) { log := logger.New("debug", "text", "") parser := arbitrum.NewL2MessageParser(log) // Create mock MEV transaction (arbitrage) mevTx := &SequencerTransaction{ Hash: common.HexToHash("0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"), From: common.HexToAddress("0xabcdef1234567890123456789012345678901234"), Gas: 1000000, GasUsed: 950000, GasPrice: big.NewInt(5e10), // 50 gwei (high) EffectiveGasPrice: big.NewInt(5e10), IsDEXTransaction: true, IsMEVTransaction: true, MEVType: "arbitrage", DEXProtocol: "MultiDEX", SwapValue: func() *big.Int { v := new(big.Int); v.SetString("500000000000000000000", 10); return v }(), // 500 ETH equivalent Receipt: &SequencerReceipt{ Status: 1, GasUsed: 950000, Logs: []*SequencerLog{ // First swap (buy) { Address: common.HexToAddress("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640"), EventName: "Swap", Protocol: "UniswapV3", ParsedArgs: map[string]interface{}{ "amount0": big.NewInt(-1e18), // Buy 1 ETH "amount1": big.NewInt(2000e6), // Pay 2000 USDC }, }, // Second swap (sell) { Address: common.HexToAddress("0x1111111254fb6c44bAC0beD2854e76F90643097d"), EventName: "Swap", Protocol: "SushiSwap", ParsedArgs: map[string]interface{}{ "amount0": big.NewInt(1e18), // Sell 1 ETH "amount1": big.NewInt(-2010e6), // Receive 2010 USDC }, }, }, }, } // Test parsing result := testTransactionParsing(t, parser, mevTx) require.True(t, result.Success, "MEV transaction parsing should succeed: %v", result.Error) // Validate MEV-specific detection assert.Equal(t, 2, result.SwapEvents, "Should detect 2 swap events in arbitrage") threshold2 := new(big.Int) threshold2.SetString("100000000000000000000", 10) assert.True(t, result.ParsedValue.Cmp(threshold2) > 0, "Should detect high-value MEV") // Calculate estimated profit (simplified) profit := big.NewInt(10e6) // 10 USDC profit t.Logf("MEV arbitrage transaction parsed: %d swap events, estimated profit: %s USDC", result.SwapEvents, new(big.Float).Quo(new(big.Float).SetInt(profit), big.NewFloat(1e6)).String()) } // TestParserPerformance tests parser performance with sequencer-speed data func TestParserPerformance(t *testing.T) { log := logger.New("warn", "text", "") // Reduce logging for performance test parser := arbitrum.NewL2MessageParser(log) // Create test transactions numTransactions := 1000 transactions := make([]*SequencerTransaction, numTransactions) for i := 0; i < numTransactions; i++ { transactions[i] = createMockTransaction(i) } // Measure parsing performance startTime := time.Now() var successCount int for _, tx := range transactions { result := testTransactionParsing(t, parser, tx) if result.Success { successCount++ } } elapsed := time.Since(startTime) txPerSecond := float64(numTransactions) / elapsed.Seconds() t.Logf("=== PARSER PERFORMANCE RESULTS ===") t.Logf("Transactions processed: %d", numTransactions) t.Logf("Successful parses: %d", successCount) t.Logf("Time elapsed: %v", elapsed) t.Logf("Transactions per second: %.2f", txPerSecond) // Performance requirements assert.Greater(t, txPerSecond, 500.0, "Parser should process >500 tx/s") assert.Greater(t, float64(successCount)/float64(numTransactions), 0.95, "Success rate should be >95%") } // createMockTransaction creates a mock transaction for testing func createMockTransaction(index int) *SequencerTransaction { return &SequencerTransaction{ Hash: common.HexToHash(fmt.Sprintf("0x%064d", index)), From: common.HexToAddress(fmt.Sprintf("0x%040d", index)), Gas: 200000, GasUsed: 150000, GasPrice: big.NewInt(1e10), EffectiveGasPrice: big.NewInt(1e10), IsDEXTransaction: index%3 == 0, // Every 3rd transaction is DEX DEXProtocol: "UniswapV3", Receipt: &SequencerReceipt{ Status: 1, GasUsed: 150000, Logs: []*SequencerLog{ { EventName: "Swap", Protocol: "UniswapV3", ParsedArgs: map[string]interface{}{ "amount0": big.NewInt(1e17), // 0.1 ETH "amount1": big.NewInt(200e6), // 200 USDC }, }, }, }, } } // Helper functions for counting transactions func countDEXInBlock(block *SequencerBlock) int { count := 0 for _, tx := range block.Transactions { if tx.IsDEXTransaction { count++ } } return count } func countMEVInBlock(block *SequencerBlock) int { count := 0 for _, tx := range block.Transactions { if tx.IsMEVTransaction { count++ } } return count }