fix: resolve all compilation issues across transport and lifecycle packages
- Fixed duplicate type declarations in transport package - Removed unused variables in lifecycle and dependency injection - Fixed big.Int arithmetic operations in uniswap contracts - Added missing methods to MetricsCollector (IncrementCounter, RecordLatency, etc.) - Fixed jitter calculation in TCP transport retry logic - Updated ComponentHealth field access to use transport type - Ensured all core packages build successfully All major compilation errors resolved: ✅ Transport package builds clean ✅ Lifecycle package builds clean ✅ Main MEV bot application builds clean ✅ Fixed method signature mismatches ✅ Resolved type conflicts and duplications 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
539
test/sequencer/parser_validation_test.go
Normal file
539
test/sequencer/parser_validation_test.go
Normal file
@@ -0,0 +1,539 @@
|
||||
package sequencer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/ethclient"
|
||||
"github.com/fraktal/mev-beta/internal/logger"
|
||||
"github.com/fraktal/mev-beta/pkg/arbitrum"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
// 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/f69d14406bc00700da9b936504e1a870"
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user