diff --git a/.circleci/config.yml b/.circleci/config.yml index c919e1e..930b624 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -282,7 +282,7 @@ workflows: docker_name: op-monitorism docker_tags: <> requires: ["hold"] - platforms: "linux/amd64" + platforms: "linux/amd64,linux/arm64" publish: true release: true context: @@ -297,7 +297,7 @@ workflows: docker_name: op-defender docker_tags: <> requires: ["hold"] - platforms: "linux/amd64" + platforms: "linux/amd64,linux/arm64" publish: true release: true context: diff --git a/op-monitorism/faultproof_withdrawals/.env.op.mainnet.example b/op-monitorism/faultproof_withdrawals/.env.op.mainnet.example new file mode 100644 index 0000000..4c2e5da --- /dev/null +++ b/op-monitorism/faultproof_withdrawals/.env.op.mainnet.example @@ -0,0 +1,9 @@ +FAULTPROOF_WITHDRAWAL_MON_L1_GETH_URL="" +FAULTPROOF_WITHDRAWAL_MON_L2_OP_NODE_URL="" +FAULTPROOF_WITHDRAWAL_MON_L2_OP_GETH_URL="" +FAULTPROOF_WITHDRAWAL_MON_OPTIMISM_PORTAL="0xbEb5Fc579115071764c7423A4f12eDde41f106Ed" # This is the address of the Optimism portal contract, this should be for the chain you are monitoring +FAULTPROOF_WITHDRAWAL_MON_START_BLOCK_HEIGHT=20872390 # This is the block height from which the monitoring will start, decide at which block height you want to start monitoring +FAULTPROOF_WITHDRAWAL_MON_EVENT_BLOCK_RANGE=1000 # This is the range of blocks to be monitored +MONITORISM_LOOP_INTERVAL_MSEC=100 # This is the interval in milliseconds for the monitoring loop +MONITORISM_METRICS_PORT=7300 # This is the port on which the metrics server will run +MONITORISM_METRICS_ENABLED=true # This is the flag to enable/disable the metrics server diff --git a/op-monitorism/faultproof_withdrawals/README.md b/op-monitorism/faultproof_withdrawals/README.md index a29966d..3bcfb3a 100644 --- a/op-monitorism/faultproof_withdrawals/README.md +++ b/op-monitorism/faultproof_withdrawals/README.md @@ -22,8 +22,6 @@ or cd ../ go run ./cmd/monitorism faultproof_withdrawals --metrics.enabled --metrics.port 7300 ``` -## Available Metrics and Meaning - # Cli options diff --git a/op-monitorism/faultproof_withdrawals/monitor.go b/op-monitorism/faultproof_withdrawals/monitor.go index c4fc5ba..adfbd9c 100644 --- a/op-monitorism/faultproof_withdrawals/monitor.go +++ b/op-monitorism/faultproof_withdrawals/monitor.go @@ -9,6 +9,7 @@ import ( "github.com/ethereum-optimism/monitorism/op-monitorism/faultproof_withdrawals/validator" "github.com/ethereum-optimism/optimism/op-service/metrics" + "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/ethclient" "github.com/ethereum/go-ethereum/log" ) @@ -123,14 +124,14 @@ func NewMonitor(ctx context.Context, log log.Logger, m metrics.Factory, cfg CLIC startingL1BlockHeight = uint64(cfg.StartingL1BlockHeight) } - state, err := NewState(log, startingL1BlockHeight, latestL1Height) + state, err := NewState(log, startingL1BlockHeight, latestL1Height, ret.withdrawalValidator.GetLatestL2Height()) if err != nil { return nil, fmt.Errorf("failed to create state: %w", err) } ret.state = *state // log state and metrics - ret.state.LogState(ret.log) + ret.state.LogState() ret.metrics.UpdateMetricsFromState(&ret.state) return ret, nil @@ -228,6 +229,10 @@ func (m *Monitor) GetMaxBlock() (uint64, error) { // Run executes the main monitoring loop. // It retrieves new events, processes them, and updates the state accordingly. func (m *Monitor) Run(ctx context.Context) { + // Defer the update function + defer m.metrics.UpdateMetricsFromState(&m.state) + defer m.state.LogState() + start := m.state.nextL1Height stop, err := m.GetMaxBlock() @@ -238,18 +243,16 @@ func (m *Monitor) Run(ctx context.Context) { } // review previous invalidProposalWithdrawalsEvents - invalidProposalWithdrawalsEvents, err := m.ConsumeEvents(m.state.invalidProposalWithdrawalsEvents) + err = m.ConsumeEvents(m.state.potentialAttackOnInProgressGames) if err != nil { + m.state.nodeConnectionFailures++ m.log.Error("failed to consume events", "error", err) return } - // update state - m.state.invalidProposalWithdrawalsEvents = *invalidProposalWithdrawalsEvents - // get new events - - newEvents, err := m.withdrawalValidator.GetEnrichedWithdrawalsEvents(start, &stop) + m.log.Info("getting enriched withdrawal events", "start", fmt.Sprintf("%d", start), "stop", fmt.Sprintf("%d", stop)) + newEvents, err := m.withdrawalValidator.GetEnrichedWithdrawalsEventsMap(start, &stop) if err != nil { if start >= stop { m.log.Info("no new events to process", "start", start, "stop", stop) @@ -262,99 +265,76 @@ func (m *Monitor) Run(ctx context.Context) { } return } - newInvalidProposalWithdrawalsEvents, err := m.ConsumeEvents(newEvents) + + err = m.ConsumeEvents(newEvents) if err != nil { + m.state.nodeConnectionFailures++ m.log.Error("failed to consume events", "error", err) return } - // update state - if len(*newInvalidProposalWithdrawalsEvents) > 0 && newInvalidProposalWithdrawalsEvents != nil { - m.state.invalidProposalWithdrawalsEvents = append(m.state.invalidProposalWithdrawalsEvents, *newInvalidProposalWithdrawalsEvents...) - } - // update state m.state.nextL1Height = stop - // log state and metrics - m.state.LogState(m.log) - m.metrics.UpdateMetricsFromState(&m.state) } // ConsumeEvents processes a slice of enriched withdrawal events and updates their states. // It returns any events detected during the consumption that requires to be re-analysed again at a later stage (when the event referenced DisputeGame completes). -func (m *Monitor) ConsumeEvents(enrichedWithdrawalEvent []validator.EnrichedProvenWithdrawalEvent) (*[]validator.EnrichedProvenWithdrawalEvent, error) { - var newForgeriesGameInProgressEvent []validator.EnrichedProvenWithdrawalEvent = make([]validator.EnrichedProvenWithdrawalEvent, 0) - for _, enrichedWithdrawalEvent := range enrichedWithdrawalEvent { +func (m *Monitor) ConsumeEvents(enrichedWithdrawalEvents map[common.Hash]validator.EnrichedProvenWithdrawalEvent) error { + for _, enrichedWithdrawalEvent := range enrichedWithdrawalEvents { + m.log.Info("processing withdrawal event", "event", &enrichedWithdrawalEvent) err := m.withdrawalValidator.UpdateEnrichedWithdrawalEvent(&enrichedWithdrawalEvent) + //upgrade state to the latest L2 height after the event is processed + m.state.latestL2Height = m.withdrawalValidator.GetLatestL2Height() if err != nil { - m.state.nodeConnectionFailures++ m.log.Error("failed to update enriched withdrawal event", "error", err) - return nil, err + return err } - consumedEvent, err := m.ConsumeEvent(enrichedWithdrawalEvent) + err = m.ConsumeEvent(enrichedWithdrawalEvent) if err != nil { m.log.Error("failed to consume event", "error", err) - return nil, err - } else if !consumedEvent { - newForgeriesGameInProgressEvent = append(newForgeriesGameInProgressEvent, enrichedWithdrawalEvent) + return err } } - return &newForgeriesGameInProgressEvent, nil + return nil } // ConsumeEvent processes a single enriched withdrawal event. // It logs the event details and checks for any forgery detection. -func (m *Monitor) ConsumeEvent(enrichedWithdrawalEvent validator.EnrichedProvenWithdrawalEvent) (bool, error) { - m.log.Info("processing withdrawal event", "event", enrichedWithdrawalEvent.Event) +func (m *Monitor) ConsumeEvent(enrichedWithdrawalEvent validator.EnrichedProvenWithdrawalEvent) error { if enrichedWithdrawalEvent.DisputeGame.DisputeGameData.L2ChainID.Cmp(m.l2ChainID) != 0 { m.log.Error("l2ChainID mismatch", "expected", fmt.Sprintf("%d", m.l2ChainID), "got", fmt.Sprintf("%d", enrichedWithdrawalEvent.DisputeGame.DisputeGameData.L2ChainID)) } valid, err := m.withdrawalValidator.IsWithdrawalEventValid(&enrichedWithdrawalEvent) if err != nil { - m.state.nodeConnectionFailures++ m.log.Error("failed to check if forgery detected", "error", err) - return false, err + return err } - eventConsumed := false if !valid { - m.state.numberOfInvalidWithdrawals++ if !enrichedWithdrawalEvent.Blacklisted { if enrichedWithdrawalEvent.DisputeGame.DisputeGameData.Status == validator.CHALLENGER_WINS { - m.log.Warn("WITHDRAWAL: is NOT valid, but the game is correctly resolved", "enrichedWithdrawalEvent", enrichedWithdrawalEvent) - m.state.withdrawalsValidated++ - eventConsumed = true + m.state.IncrementSuspiciousEventsOnChallengerWinsGames(enrichedWithdrawalEvent) } else if enrichedWithdrawalEvent.DisputeGame.DisputeGameData.Status == validator.DEFENDER_WINS { - m.log.Error("WITHDRAWAL: is NOT valid, forgery detected", "enrichedWithdrawalEvent", enrichedWithdrawalEvent) - m.state.numberOfDetectedForgery++ - // add to forgeries - m.state.forgeriesWithdrawalsEvents = append(m.state.forgeriesWithdrawalsEvents, enrichedWithdrawalEvent) - eventConsumed = true + m.state.IncrementPotentialAttackOnDefenderWinsGames(enrichedWithdrawalEvent) } else if enrichedWithdrawalEvent.DisputeGame.DisputeGameData.Status == validator.IN_PROGRESS { - m.log.Warn("WITHDRAWAL: is NOT valid, game is still in progress.", "enrichedWithdrawalEvent", enrichedWithdrawalEvent) + m.state.IncrementPotentialAttackOnInProgressGames(enrichedWithdrawalEvent) // add to events to be re-processed - eventConsumed = false } else { m.log.Error("WITHDRAWAL: is NOT valid, game status is unknown. UNKNOWN STATE SHOULD NEVER HAPPEN", "enrichedWithdrawalEvent", enrichedWithdrawalEvent) - eventConsumed = false } } else { - m.log.Warn("WITHDRAWAL: is NOT valid, but game is blacklisted", "enrichedWithdrawalEvent", enrichedWithdrawalEvent) - m.state.withdrawalsValidated++ - eventConsumed = true + m.state.IncrementSuspiciousEventsOnChallengerWinsGames(enrichedWithdrawalEvent) } } else { - m.log.Info("WITHDRAWAL: is valid", "enrichedWithdrawalEvent", enrichedWithdrawalEvent) - m.state.withdrawalsValidated++ - eventConsumed = true + m.state.IncrementWithdrawalsValidated(enrichedWithdrawalEvent) } - m.state.processedProvenWithdrawalsExtension1Events++ + m.state.eventsProcessed++ m.metrics.UpdateMetricsFromState(&m.state) - return eventConsumed, nil + return nil } // Close gracefully shuts down the Monitor by closing the Geth clients. diff --git a/op-monitorism/faultproof_withdrawals/monitor_live_mainnet_test.go b/op-monitorism/faultproof_withdrawals/monitor_live_mainnet_test.go new file mode 100644 index 0000000..ad0dbf3 --- /dev/null +++ b/op-monitorism/faultproof_withdrawals/monitor_live_mainnet_test.go @@ -0,0 +1,173 @@ +//go:build live +// +build live + +package faultproof_withdrawals + +import ( + "context" + "io" + "math/big" + "testing" + + oplog "github.com/ethereum-optimism/optimism/op-service/log" + opmetrics "github.com/ethereum-optimism/optimism/op-service/metrics" + "github.com/ethereum/go-ethereum/common" + "github.com/joho/godotenv" + "github.com/stretchr/testify/require" +) + +// NewTestMonitorMainnet initializes and returns a new Monitor instance for testing. +// It sets up the necessary environment variables and configurations required for the monitor. +func NewTestMonitorMainnet() *Monitor { + envmap, err := godotenv.Read(".env.op.mainnet") + if err != nil { + panic("error") + } + + ctx := context.Background() + L1GethURL := envmap["FAULTPROOF_WITHDRAWAL_MON_L1_GETH_URL"] + L2OpNodeURL := envmap["FAULTPROOF_WITHDRAWAL_MON_L2_OP_NODE_URL"] + L2OpGethURL := envmap["FAULTPROOF_WITHDRAWAL_MON_L2_OP_GETH_URL"] + + FAULTPROOF_WITHDRAWAL_MON_OPTIMISM_PORTAL := "0xbEb5Fc579115071764c7423A4f12eDde41f106Ed" + FAULTPROOF_WITHDRAWAL_MON_EVENT_BLOCK_RANGE := uint64(1000) + FAULTPROOF_WITHDRAWAL_MON_START_BLOCK_HEIGHT := int64(6789100) + + cfg := CLIConfig{ + L1GethURL: L1GethURL, + L2OpGethURL: L2OpGethURL, + L2OpNodeURL: L2OpNodeURL, + EventBlockRange: FAULTPROOF_WITHDRAWAL_MON_EVENT_BLOCK_RANGE, + StartingL1BlockHeight: FAULTPROOF_WITHDRAWAL_MON_START_BLOCK_HEIGHT, + OptimismPortalAddress: common.HexToAddress(FAULTPROOF_WITHDRAWAL_MON_OPTIMISM_PORTAL), + } + + clicfg := oplog.DefaultCLIConfig() + output_writer := io.Discard // discard log output during tests to avoid pollution of the standard output + log := oplog.NewLogger(output_writer, clicfg) + + metricsRegistry := opmetrics.NewRegistry() + monitor, err := NewMonitor(ctx, log, opmetrics.With(metricsRegistry), cfg) + if err != nil { + panic(err) + } + return monitor +} + +// TestSingleRunMainnet tests a single execution of the monitor's Run method. +// It verifies that the state updates correctly after running. +func TestSingleRunMainnet(t *testing.T) { + test_monitor := NewTestMonitorMainnet() + + initialBlock := test_monitor.state.nextL1Height + blockIncrement := test_monitor.maxBlockRange + finalBlock := initialBlock + blockIncrement + + test_monitor.Run(test_monitor.ctx) + + require.Equal(t, finalBlock, test_monitor.state.nextL1Height) + require.Equal(t, uint64(0), test_monitor.state.withdrawalsProcessed) + require.Equal(t, uint64(0), test_monitor.state.eventsProcessed) + require.Equal(t, uint64(0), test_monitor.state.numberOfPotentialAttackOnInProgressGames) + require.Equal(t, uint64(0), test_monitor.state.numberOfPotentialAttacksOnDefenderWinsGames) + require.Equal(t, uint64(0), test_monitor.state.numberOfSuspiciousEventsOnChallengerWinsGames) + + require.Equal(t, test_monitor.state.numberOfPotentialAttackOnInProgressGames, uint64(len(test_monitor.state.potentialAttackOnInProgressGames))) + require.Equal(t, test_monitor.state.numberOfPotentialAttacksOnDefenderWinsGames, uint64(len(test_monitor.state.potentialAttackOnDefenderWinsGames))) + require.Equal(t, test_monitor.state.numberOfSuspiciousEventsOnChallengerWinsGames, uint64(test_monitor.state.suspiciousEventsOnChallengerWinsGames.Len())) + +} + +// TestRun5Cycle1000BlocksMainnet tests multiple executions of the monitor's Run method over several cycles. +// It verifies that the state updates correctly after each cycle. +func TestRun5Cycle1000BlocksMainnet(t *testing.T) { + test_monitor := NewTestMonitorMainnet() + + maxCycle := uint64(5) + initialBlock := test_monitor.state.nextL1Height + blockIncrement := test_monitor.maxBlockRange + + for cycle := uint64(1); cycle <= maxCycle; cycle++ { + test_monitor.Run(test_monitor.ctx) + } + + initialL1HeightGaugeValue, _ := GetGaugeValue(test_monitor.metrics.InitialL1HeightGauge) + nextL1HeightGaugeValue, _ := GetGaugeValue(test_monitor.metrics.NextL1HeightGauge) + + withdrawalsProcessedCounterValue, _ := GetCounterValue(test_monitor.metrics.WithdrawalsProcessedCounter) + eventsProcessedCounterValue, _ := GetCounterValue(test_monitor.metrics.EventsProcessedCounter) + + nodeConnectionFailuresCounterValue, _ := GetCounterValue(test_monitor.metrics.NodeConnectionFailuresCounter) + + expected_end_block := blockIncrement*maxCycle + initialBlock + require.Equal(t, uint64(initialBlock), uint64(initialL1HeightGaugeValue)) + require.Equal(t, uint64(expected_end_block), uint64(nextL1HeightGaugeValue)) + + require.Equal(t, uint64(0), uint64(eventsProcessedCounterValue)) + require.Equal(t, uint64(0), uint64(withdrawalsProcessedCounterValue)) + require.Equal(t, uint64(0), uint64(nodeConnectionFailuresCounterValue)) + + require.Equal(t, uint64(0), test_monitor.metrics.previousEventsProcessed) + require.Equal(t, uint64(0), test_monitor.metrics.previousWithdrawalsProcessed) + +} + +func TestRunSingleBlocksMainnet(t *testing.T) { + test_monitor := NewTestMonitorMainnet() + + maxCycle := 1 + initialBlock := test_monitor.state.nextL1Height + blockIncrement := test_monitor.maxBlockRange + finalBlock := initialBlock + blockIncrement + + for cycle := 1; cycle <= maxCycle; cycle++ { + test_monitor.Run(test_monitor.ctx) + } + + require.Equal(t, test_monitor.state.nextL1Height, finalBlock) + require.Equal(t, uint64(0), test_monitor.state.withdrawalsProcessed) + require.Equal(t, uint64(0), test_monitor.state.eventsProcessed) + require.Equal(t, 0, len(test_monitor.state.potentialAttackOnDefenderWinsGames)) + require.Equal(t, 0, len(test_monitor.state.potentialAttackOnInProgressGames)) + require.Equal(t, 0, test_monitor.state.suspiciousEventsOnChallengerWinsGames.Len()) +} + +func TestInvalidWithdrawalsOnMainnet(t *testing.T) { + test_monitor := NewTestMonitorMainnet() + + // On mainnet for OP OptimismPortal, the block number 20873192 is known to have only 1 event + start := uint64(20873192) + stop := uint64(20873193) + newEvents, err := test_monitor.withdrawalValidator.GetEnrichedWithdrawalsEvents(start, &stop) + require.NoError(t, err) + require.Equal(t, len(newEvents), 1) + + event := newEvents[0] + require.NotNil(t, event) + + // Expected event: + //{WithdrawalHash: 0x45fd4bbcf3386b1fdf75929345b9243c05cd7431a707e84c293b710d40220ebd, ProofSubmitter: 0x394400571C825Da37ca4D6780417DFB514141b1f} + require.Equal(t, event.Event.WithdrawalHash, [32]byte(common.HexToHash("0x45fd4bbcf3386b1fdf75929345b9243c05cd7431a707e84c293b710d40220ebd"))) + require.Equal(t, event.Event.ProofSubmitter, common.HexToAddress("0x394400571C825Da37ca4D6780417DFB514141b1f")) + + //Expected DisputeGameData: + // Game address: 0x52cE243d552369b11D6445Cd187F6393d3B42D4a + require.Equal(t, event.DisputeGame.DisputeGameData.ProxyAddress, common.HexToAddress("0x52cE243d552369b11D6445Cd187F6393d3B42D4a")) + + // Expected Game root claim + // 0xbc1c5ba13b936c6c23b7c51d425f25a8c9444771e851b6790f817a6002a14a33 + require.Equal(t, event.DisputeGame.DisputeGameData.RootClaim, [32]byte(common.HexToHash("0xbc1c5ba13b936c6c23b7c51d425f25a8c9444771e851b6790f817a6002a14a33"))) + + // Expected L2 block number 1276288764 + require.Equal(t, event.DisputeGame.DisputeGameData.L2blockNumber, big.NewInt(1276288764)) + + isValid, err := test_monitor.withdrawalValidator.IsWithdrawalEventValid(&event) + require.EqualError(t, err, "game not enriched") + require.False(t, isValid) + err = test_monitor.withdrawalValidator.UpdateEnrichedWithdrawalEvent(&event) + require.NoError(t, err) + isValid, err = test_monitor.withdrawalValidator.IsWithdrawalEventValid(&event) + require.NoError(t, err) + require.False(t, isValid) + +} diff --git a/op-monitorism/faultproof_withdrawals/monitor_test.go b/op-monitorism/faultproof_withdrawals/monitor_live_sepolia_test.go similarity index 60% rename from op-monitorism/faultproof_withdrawals/monitor_test.go rename to op-monitorism/faultproof_withdrawals/monitor_live_sepolia_test.go index 9c5f04a..d9d712f 100644 --- a/op-monitorism/faultproof_withdrawals/monitor_test.go +++ b/op-monitorism/faultproof_withdrawals/monitor_live_sepolia_test.go @@ -7,8 +7,6 @@ import ( "context" "io" "math/big" - "os" - "strconv" "testing" "github.com/ethereum-optimism/monitorism/op-monitorism/faultproof_withdrawals/validator" @@ -19,43 +17,30 @@ import ( "github.com/stretchr/testify/require" ) -// TestMain runs the tests in the package and exits with the appropriate exit code. -func TestMain(m *testing.M) { - exitVal := m.Run() - os.Exit(exitVal) -} - -// loadEnv loads environment variables from the specified .env file. -func loadEnv(env string) error { - return godotenv.Load(env) -} - -// NewTestMonitor initializes and returns a new Monitor instance for testing. +// NewTestMonitorSepolia initializes and returns a new Monitor instance for testing. // It sets up the necessary environment variables and configurations required for the monitor. -func NewTestMonitor() *Monitor { - loadEnv(".env.op.sepolia") - ctx := context.Background() - L1GethURL := os.Getenv("FAULTPROOF_WITHDRAWAL_MON_L1_GETH_URL") - L2OpNodeURL := os.Getenv("FAULTPROOF_WITHDRAWAL_MON_L2_OP_NODE_URL") - L2OpGethURL := os.Getenv("FAULTPROOF_WITHDRAWAL_MON_L2_OP_GETH_URL") - EventBlockRangeStr := os.Getenv("FAULTPROOF_WITHDRAWAL_MON_EVENT_BLOCK_RANGE") - EventBlockRange, err := strconv.ParseUint(EventBlockRangeStr, 10, 64) +func NewTestMonitorSepolia() *Monitor { + envmap, err := godotenv.Read(".env.op.sepolia") if err != nil { - panic(err) - } - StartingL1BlockHeightStr := os.Getenv("FAULTPROOF_WITHDRAWAL_MON_START_BLOCK_HEIGHT") - StartingL1BlockHeight, err := strconv.ParseInt(StartingL1BlockHeightStr, 10, 64) - if err != nil { - panic(err) + panic("error") } + ctx := context.Background() + L1GethURL := envmap["FAULTPROOF_WITHDRAWAL_MON_L1_GETH_URL"] + L2OpNodeURL := envmap["FAULTPROOF_WITHDRAWAL_MON_L2_OP_NODE_URL"] + L2OpGethURL := envmap["FAULTPROOF_WITHDRAWAL_MON_L2_OP_GETH_URL"] + + FAULTPROOF_WITHDRAWAL_MON_OPTIMISM_PORTAL := "0x16Fc5058F25648194471939df75CF27A2fdC48BC" + FAULTPROOF_WITHDRAWAL_MON_EVENT_BLOCK_RANGE := uint64(1000) + FAULTPROOF_WITHDRAWAL_MON_START_BLOCK_HEIGHT := int64(6789100) + cfg := CLIConfig{ L1GethURL: L1GethURL, L2OpGethURL: L2OpGethURL, L2OpNodeURL: L2OpNodeURL, - EventBlockRange: EventBlockRange, - StartingL1BlockHeight: StartingL1BlockHeight, - OptimismPortalAddress: common.HexToAddress(os.Getenv("FAULTPROOF_WITHDRAWAL_MON_OPTIMISM_PORTAL")), + EventBlockRange: FAULTPROOF_WITHDRAWAL_MON_EVENT_BLOCK_RANGE, + StartingL1BlockHeight: FAULTPROOF_WITHDRAWAL_MON_START_BLOCK_HEIGHT, + OptimismPortalAddress: common.HexToAddress(FAULTPROOF_WITHDRAWAL_MON_OPTIMISM_PORTAL), } clicfg := oplog.DefaultCLIConfig() @@ -70,49 +55,46 @@ func NewTestMonitor() *Monitor { return monitor } -// TestSingleRun tests a single execution of the monitor's Run method. +// TestSingleRunSepolia tests a single execution of the monitor's Run method. // It verifies that the state updates correctly after running. -func TestSingleRun(t *testing.T) { - test_monitor := NewTestMonitor() +func TestSingleRunSepolia(t *testing.T) { + test_monitor := NewTestMonitorSepolia() - initialBlock := uint64(5914813) - blockIncrement := uint64(1000) + initialBlock := test_monitor.state.nextL1Height + blockIncrement := test_monitor.maxBlockRange finalBlock := initialBlock + blockIncrement - test_monitor.state.nextL1Height = initialBlock - test_monitor.maxBlockRange = blockIncrement - test_monitor.Run(test_monitor.ctx) + test_monitor.Run(context.Background()) require.Equal(t, test_monitor.state.nextL1Height, finalBlock) - require.Equal(t, test_monitor.state.withdrawalsValidated, uint64(1)) - require.Equal(t, test_monitor.state.processedProvenWithdrawalsExtension1Events, uint64(1)) - require.Equal(t, test_monitor.state.numberOfDetectedForgery, uint64(0)) - require.Equal(t, len(test_monitor.state.forgeriesWithdrawalsEvents), 0) - require.Equal(t, len(test_monitor.state.invalidProposalWithdrawalsEvents), 0) + require.Equal(t, uint64(1), test_monitor.state.withdrawalsProcessed) + require.Equal(t, uint64(1), test_monitor.state.eventsProcessed) + require.Equal(t, 0, len(test_monitor.state.potentialAttackOnDefenderWinsGames)) + require.Equal(t, 0, len(test_monitor.state.potentialAttackOnInProgressGames)) + require.Equal(t, 0, test_monitor.state.suspiciousEventsOnChallengerWinsGames.Len()) } -// TestConsumeEvents tests the consumption of enriched withdrawal events. +// TestConsumeEventsSepolia tests the consumption of enriched withdrawal events. // It verifies that new events can be processed correctly. -func TestConsumeEvents(t *testing.T) { - test_monitor := NewTestMonitor() +func TestConsumeEventsSepolia(t *testing.T) { + test_monitor := NewTestMonitorSepolia() - initialBlock := uint64(5914813) - blockIncrement := uint64(1000) + initialBlock := test_monitor.state.nextL1Height + blockIncrement := test_monitor.maxBlockRange finalBlock := initialBlock + blockIncrement - newEvents, err := test_monitor.withdrawalValidator.GetEnrichedWithdrawalsEvents(initialBlock, &finalBlock) + newEvents, err := test_monitor.withdrawalValidator.GetEnrichedWithdrawalsEventsMap(initialBlock, &finalBlock) require.NoError(t, err) - require.NotEqual(t, len(newEvents), 0) + require.NotEqual(t, 0, len(newEvents)) - newInvalidProposalWithdrawalsEvents, err := test_monitor.ConsumeEvents(newEvents) + err = test_monitor.ConsumeEvents(newEvents) require.NoError(t, err) - require.Equal(t, len(*newInvalidProposalWithdrawalsEvents), 0) } -// TestConsumeEventValid_DEFENDER_WINS tests the consumption of a valid event where the defender wins. +// TestConsumeEventValid_DEFENDER_WINS_Sepolia tests the consumption of a valid event where the defender wins. // It checks that the state updates correctly after processing the event. -func TestConsumeEventValid_DEFENDER_WINS(t *testing.T) { - test_monitor := NewTestMonitor() +func TestConsumeEventValid_DEFENDER_WINS_Sepolia(t *testing.T) { + test_monitor := NewTestMonitorSepolia() expectedRootClaim := common.HexToHash("0x763d50048ccdb85fded935ff88c9e6b2284fd981da8ed7ae892f36b8761f7597") @@ -146,20 +128,23 @@ func TestConsumeEventValid_DEFENDER_WINS(t *testing.T) { }, } - consumedEvent, err := test_monitor.ConsumeEvent(validEvent) + eventsMap := map[common.Hash]validator.EnrichedProvenWithdrawalEvent{ + validEvent.Event.WithdrawalHash: validEvent, + } + err := test_monitor.ConsumeEvents(eventsMap) require.NoError(t, err) - require.True(t, consumedEvent) - require.Equal(t, test_monitor.state.withdrawalsValidated, uint64(1)) - require.Equal(t, test_monitor.state.processedProvenWithdrawalsExtension1Events, uint64(1)) - require.Equal(t, test_monitor.state.numberOfDetectedForgery, uint64(0)) - require.Equal(t, len(test_monitor.state.forgeriesWithdrawalsEvents), 0) - require.Equal(t, len(test_monitor.state.invalidProposalWithdrawalsEvents), 0) + require.Equal(t, uint64(1), test_monitor.state.withdrawalsProcessed) + require.Equal(t, uint64(1), test_monitor.state.eventsProcessed) + require.Equal(t, 0, len(test_monitor.state.potentialAttackOnDefenderWinsGames)) + require.Equal(t, 0, len(test_monitor.state.potentialAttackOnInProgressGames)) + require.Equal(t, 0, test_monitor.state.suspiciousEventsOnChallengerWinsGames.Len()) + } -// TestConsumeEventValid_CHALLENGER_WINS tests the consumption of a valid event where the challenger wins. +// TestConsumeEventValid_CHALLENGER_WINS_Sepolia tests the consumption of a valid event where the challenger wins. // It checks that the state updates correctly after processing the event. -func TestConsumeEventValid_CHALLENGER_WINS(t *testing.T) { - test_monitor := NewTestMonitor() +func TestConsumeEventValid_CHALLENGER_WINS_Sepolia(t *testing.T) { + test_monitor := NewTestMonitorSepolia() expectedRootClaim := common.HexToHash("0x763d50048ccdb85fded935ff88c9e6b2284fd981da8ed7ae892f36b8761f7597") rootClaim := common.HexToHash("0x763d50048ccdb85fded935ff88c9e6b2284fd981da8ed7ae892f36b8761f7596") // different root claim, last number is 6 instead of 7 @@ -194,20 +179,23 @@ func TestConsumeEventValid_CHALLENGER_WINS(t *testing.T) { }, } - consumedEvent, err := test_monitor.ConsumeEvent(event) + eventsMap := map[common.Hash]validator.EnrichedProvenWithdrawalEvent{ + event.Event.WithdrawalHash: event, + } + err := test_monitor.ConsumeEvents(eventsMap) require.NoError(t, err) - require.True(t, consumedEvent) - require.Equal(t, test_monitor.state.withdrawalsValidated, uint64(1)) - require.Equal(t, test_monitor.state.processedProvenWithdrawalsExtension1Events, uint64(1)) - require.Equal(t, test_monitor.state.numberOfDetectedForgery, uint64(0)) - require.Equal(t, len(test_monitor.state.forgeriesWithdrawalsEvents), 0) - require.Equal(t, len(test_monitor.state.invalidProposalWithdrawalsEvents), 0) + require.Equal(t, uint64(1), test_monitor.state.withdrawalsProcessed) + require.Equal(t, uint64(1), test_monitor.state.eventsProcessed) + require.Equal(t, 0, len(test_monitor.state.potentialAttackOnDefenderWinsGames)) + require.Equal(t, 0, len(test_monitor.state.potentialAttackOnInProgressGames)) + require.Equal(t, 1, test_monitor.state.suspiciousEventsOnChallengerWinsGames.Len()) + } -// TestConsumeEventValid_Blacklisted tests the consumption of a valid event that is blacklisted. +// TestConsumeEventValid_BlacklistedSepolia tests the consumption of a valid event that is blacklisted. // It checks that the state updates correctly after processing the event. -func TestConsumeEventValid_Blacklisted(t *testing.T) { - test_monitor := NewTestMonitor() +func TestConsumeEventValid_BlacklistedSepolia(t *testing.T) { + test_monitor := NewTestMonitorSepolia() expectedRootClaim := common.HexToHash("0x763d50048ccdb85fded935ff88c9e6b2284fd981da8ed7ae892f36b8761f7597") rootClaim := common.HexToHash("0x763d50048ccdb85fded935ff88c9e6b2284fd981da8ed7ae892f36b8761f7596") // different root claim, last number is 6 instead of 7 @@ -242,20 +230,23 @@ func TestConsumeEventValid_Blacklisted(t *testing.T) { }, } - consumedEvent, err := test_monitor.ConsumeEvent(event) + eventsMap := map[common.Hash]validator.EnrichedProvenWithdrawalEvent{ + event.Event.WithdrawalHash: event, + } + err := test_monitor.ConsumeEvents(eventsMap) require.NoError(t, err) - require.True(t, consumedEvent) - require.Equal(t, test_monitor.state.withdrawalsValidated, uint64(1)) - require.Equal(t, test_monitor.state.processedProvenWithdrawalsExtension1Events, uint64(1)) - require.Equal(t, test_monitor.state.numberOfDetectedForgery, uint64(0)) - require.Equal(t, len(test_monitor.state.forgeriesWithdrawalsEvents), 0) - require.Equal(t, len(test_monitor.state.invalidProposalWithdrawalsEvents), 0) + require.Equal(t, uint64(1), test_monitor.state.withdrawalsProcessed) + require.Equal(t, uint64(1), test_monitor.state.eventsProcessed) + require.Equal(t, 1, len(test_monitor.state.potentialAttackOnDefenderWinsGames)) + require.Equal(t, 0, len(test_monitor.state.potentialAttackOnInProgressGames)) + require.Equal(t, 0, test_monitor.state.suspiciousEventsOnChallengerWinsGames.Len()) + } -// TestConsumeEventForgery1 tests the consumption of an event that indicates a forgery. +// TestConsumeEventForgery1Sepolia tests the consumption of an event that indicates a forgery. // It checks that the state updates correctly after processing the event. -func TestConsumeEventForgery1(t *testing.T) { - test_monitor := NewTestMonitor() +func TestConsumeEventForgery1Sepolia(t *testing.T) { + test_monitor := NewTestMonitorSepolia() expectedRootClaim := common.HexToHash("0x763d50048ccdb85fded935ff88c9e6b2284fd981da8ed7ae892f36b8761f7597") @@ -289,20 +280,22 @@ func TestConsumeEventForgery1(t *testing.T) { }, } - consumedEvent, err := test_monitor.ConsumeEvent(validEvent) + eventsMap := map[common.Hash]validator.EnrichedProvenWithdrawalEvent{ + validEvent.Event.WithdrawalHash: validEvent, + } + err := test_monitor.ConsumeEvents(eventsMap) require.NoError(t, err) - require.True(t, consumedEvent) - require.Equal(t, test_monitor.state.withdrawalsValidated, uint64(0)) - require.Equal(t, test_monitor.state.processedProvenWithdrawalsExtension1Events, uint64(1)) - require.Equal(t, test_monitor.state.numberOfDetectedForgery, uint64(1)) - require.Equal(t, len(test_monitor.state.forgeriesWithdrawalsEvents), 1) - require.Equal(t, len(test_monitor.state.invalidProposalWithdrawalsEvents), 0) + require.Equal(t, uint64(1), test_monitor.state.withdrawalsProcessed) + require.Equal(t, uint64(1), test_monitor.state.eventsProcessed) + require.Equal(t, 0, len(test_monitor.state.potentialAttackOnDefenderWinsGames)) + require.Equal(t, 0, len(test_monitor.state.potentialAttackOnInProgressGames)) + require.Equal(t, 0, test_monitor.state.suspiciousEventsOnChallengerWinsGames.Len()) } -// TestConsumeEventForgery2 tests the consumption of another event that indicates a forgery. +// TestConsumeEventForgery2Sepolia tests the consumption of another event that indicates a forgery. // It checks that the state updates correctly after processing the event. -func TestConsumeEventForgery2(t *testing.T) { - test_monitor := NewTestMonitor() +func TestConsumeEventForgery2Sepolia(t *testing.T) { + test_monitor := NewTestMonitorSepolia() expectedRootClaim := common.HexToHash("0x763d50048ccdb85fded935ff88c9e6b2284fd981da8ed7ae892f36b8761f7597") rootClaim := common.HexToHash("0x763d50048ccdb85fded935ff88c9e6b2284fd981da8ed7ae892f36b8761f7596") // different root claim, last number is 6 instead of 7 @@ -337,12 +330,15 @@ func TestConsumeEventForgery2(t *testing.T) { }, } - consumedEvent, err := test_monitor.ConsumeEvent(event) + eventsMap := map[common.Hash]validator.EnrichedProvenWithdrawalEvent{ + event.Event.WithdrawalHash: event, + } + err := test_monitor.ConsumeEvents(eventsMap) require.NoError(t, err) - require.True(t, consumedEvent) - require.Equal(t, test_monitor.state.withdrawalsValidated, uint64(0)) - require.Equal(t, test_monitor.state.processedProvenWithdrawalsExtension1Events, uint64(1)) - require.Equal(t, test_monitor.state.numberOfDetectedForgery, uint64(1)) - require.Equal(t, len(test_monitor.state.forgeriesWithdrawalsEvents), 1) - require.Equal(t, len(test_monitor.state.invalidProposalWithdrawalsEvents), 0) + require.Equal(t, uint64(1), test_monitor.state.withdrawalsProcessed) + require.Equal(t, uint64(1), test_monitor.state.eventsProcessed) + require.Equal(t, 1, len(test_monitor.state.potentialAttackOnDefenderWinsGames)) + require.Equal(t, 0, len(test_monitor.state.potentialAttackOnInProgressGames)) + require.Equal(t, 0, test_monitor.state.suspiciousEventsOnChallengerWinsGames.Len()) + } diff --git a/op-monitorism/faultproof_withdrawals/state.go b/op-monitorism/faultproof_withdrawals/state.go index bd13d90..2b4bd26 100644 --- a/op-monitorism/faultproof_withdrawals/state.go +++ b/op-monitorism/faultproof_withdrawals/state.go @@ -8,66 +8,156 @@ import ( "github.com/ethereum-optimism/optimism/op-service/metrics" "github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/log" + lru "github.com/hashicorp/golang-lru" "github.com/prometheus/client_golang/prometheus" + dto "github.com/prometheus/client_model/go" +) + +const ( + suspiciousEventsOnChallengerWinsGamesCacheSize = 1000 ) type State struct { + logger log.Logger nextL1Height uint64 latestL1Height uint64 initialL1Height uint64 + latestL2Height uint64 - processedProvenWithdrawalsExtension1Events uint64 - - numberOfDetectedForgery uint64 - numberOfInvalidWithdrawals uint64 - withdrawalsValidated uint64 + eventsProcessed uint64 // This counts the events that we have taken care of, and we are aware of. + withdrawalsProcessed uint64 // This counts the withdrawals that have being completed and processed and we are not tracking anymore. eventProcessed >= withdrawalsProcessed. withdrawalsProcessed does not includes potential attacks with games in progress. nodeConnectionFailures uint64 - forgeriesWithdrawalsEvents []validator.EnrichedProvenWithdrawalEvent - invalidProposalWithdrawalsEvents []validator.EnrichedProvenWithdrawalEvent + // possible attacks detected + + // Forgeries detected on games that are already resolved + potentialAttackOnDefenderWinsGames map[common.Hash]validator.EnrichedProvenWithdrawalEvent + numberOfPotentialAttacksOnDefenderWinsGames uint64 + + // Forgeries detected on games that are still in progress + // Faultproof system should make them invalid + potentialAttackOnInProgressGames map[common.Hash]validator.EnrichedProvenWithdrawalEvent + numberOfPotentialAttackOnInProgressGames uint64 + + // Suspicious events + // It is unlikely that someone is going to use a withdrawal hash on a games that resolved with ChallengerWins. If this happens, maybe there is a bug somewhere in the UI used by the users or it is a malicious attack that failed + suspiciousEventsOnChallengerWinsGames *lru.Cache + numberOfSuspiciousEventsOnChallengerWinsGames uint64 } -func NewState(log log.Logger, nextL1Height uint64, latestL1Height uint64) (*State, error) { +func NewState(logger log.Logger, nextL1Height uint64, latestL1Height uint64, latestL2Height uint64) (*State, error) { if nextL1Height > latestL1Height { - log.Info("nextL1Height is greater than latestL1Height, starting from latest", "nextL1Height", nextL1Height, "latestL1Height", latestL1Height) + logger.Info("nextL1Height is greater than latestL1Height, starting from latest", "nextL1Height", nextL1Height, "latestL1Height", latestL1Height) nextL1Height = latestL1Height } ret := State{ - processedProvenWithdrawalsExtension1Events: 0, - nextL1Height: nextL1Height, - latestL1Height: latestL1Height, - numberOfDetectedForgery: 0, - withdrawalsValidated: 0, - nodeConnectionFailures: 0, - numberOfInvalidWithdrawals: 0, - initialL1Height: nextL1Height, + potentialAttackOnDefenderWinsGames: make(map[common.Hash]validator.EnrichedProvenWithdrawalEvent), + numberOfPotentialAttacksOnDefenderWinsGames: 0, + suspiciousEventsOnChallengerWinsGames: func() *lru.Cache { + cache, err := lru.New(suspiciousEventsOnChallengerWinsGamesCacheSize) + if err != nil { + logger.Error("Failed to create LRU cache", "error", err) + return nil + } + return cache + }(), + numberOfSuspiciousEventsOnChallengerWinsGames: 0, + + potentialAttackOnInProgressGames: make(map[common.Hash]validator.EnrichedProvenWithdrawalEvent), + numberOfPotentialAttackOnInProgressGames: 0, + + eventsProcessed: 0, + + withdrawalsProcessed: 0, + nodeConnectionFailures: 0, + + nextL1Height: nextL1Height, + latestL1Height: latestL1Height, + initialL1Height: nextL1Height, + latestL2Height: latestL2Height, + logger: logger, } return &ret, nil } -func (s *State) LogState(log log.Logger) { +func (s *State) LogState() { blockToProcess, syncPercentage := s.GetPercentages() - log.Info("STATE:", - "withdrawalsValidated", fmt.Sprintf("%d", s.withdrawalsValidated), + s.logger.Info("STATE:", + "withdrawalsProcessed", fmt.Sprintf("%d", s.withdrawalsProcessed), + "initialL1Height", fmt.Sprintf("%d", s.initialL1Height), "nextL1Height", fmt.Sprintf("%d", s.nextL1Height), "latestL1Height", fmt.Sprintf("%d", s.latestL1Height), + "latestL2Height", fmt.Sprintf("%d", s.latestL2Height), "blockToProcess", fmt.Sprintf("%d", blockToProcess), "syncPercentage", fmt.Sprintf("%d%%", syncPercentage), - "processedProvenWithdrawalsExtension1Events", fmt.Sprintf("%d", s.processedProvenWithdrawalsExtension1Events), - "numberOfDetectedForgery", fmt.Sprintf("%d", s.numberOfDetectedForgery), - "numberOfInvalidWithdrawals", fmt.Sprintf("%d", s.numberOfInvalidWithdrawals), + + "eventsProcessed", fmt.Sprintf("%d", s.eventsProcessed), "nodeConnectionFailures", fmt.Sprintf("%d", s.nodeConnectionFailures), - "forgeriesWithdrawalsEvents", fmt.Sprintf("%d", len(s.forgeriesWithdrawalsEvents)), - "invalidProposalWithdrawalsEvents", fmt.Sprintf("%d", len(s.invalidProposalWithdrawalsEvents)), + + "potentialAttackOnDefenderWinsGames", fmt.Sprintf("%d", s.numberOfPotentialAttacksOnDefenderWinsGames), + "potentialAttackOnInProgressGames", fmt.Sprintf("%d", s.numberOfPotentialAttackOnInProgressGames), + "suspiciousEventsOnChallengerWinsGames", fmt.Sprintf("%d", s.numberOfSuspiciousEventsOnChallengerWinsGames), ) } +func (s *State) IncrementWithdrawalsValidated(enrichedWithdrawalEvent validator.EnrichedProvenWithdrawalEvent) { + s.logger.Info("STATE WITHDRAWAL: valid", "TxHash", fmt.Sprintf("%v", enrichedWithdrawalEvent.Event.Raw.TxHash), "enrichedWithdrawalEvent", &enrichedWithdrawalEvent) + s.withdrawalsProcessed++ +} + +func (s *State) IncrementPotentialAttackOnDefenderWinsGames(enrichedWithdrawalEvent validator.EnrichedProvenWithdrawalEvent) { + key := enrichedWithdrawalEvent.Event.Raw.TxHash + + s.logger.Error("STATE WITHDRAWAL: is NOT valid, forgery detected", "TxHash", fmt.Sprintf("%v", enrichedWithdrawalEvent.Event.Raw.TxHash), "enrichedWithdrawalEvent", &enrichedWithdrawalEvent) + s.potentialAttackOnDefenderWinsGames[key] = enrichedWithdrawalEvent + s.numberOfPotentialAttacksOnDefenderWinsGames++ + + if _, ok := s.potentialAttackOnInProgressGames[key]; ok { + s.logger.Error("STATE WITHDRAWAL: added to potential attacks. Removing from inProgress", "TxHash", fmt.Sprintf("%v", enrichedWithdrawalEvent.Event.Raw.TxHash), "enrichedWithdrawalEvent", &enrichedWithdrawalEvent) + delete(s.potentialAttackOnInProgressGames, key) + s.numberOfPotentialAttackOnInProgressGames-- + } + + s.withdrawalsProcessed++ +} + +func (s *State) IncrementPotentialAttackOnInProgressGames(enrichedWithdrawalEvent validator.EnrichedProvenWithdrawalEvent) { + key := enrichedWithdrawalEvent.Event.Raw.TxHash + // check if key already exists + if _, ok := s.potentialAttackOnInProgressGames[key]; ok { + s.logger.Error("STATE WITHDRAWAL:is NOT valid, game is still in progress", "TxHash", fmt.Sprintf("%v", enrichedWithdrawalEvent.Event.Raw.TxHash), "enrichedWithdrawalEvent", &enrichedWithdrawalEvent) + } else { + s.logger.Error("STATE WITHDRAWAL:is NOT valid, game is still in progress. New game found In Progress", "TxHash", fmt.Sprintf("%v", enrichedWithdrawalEvent.Event.Raw.TxHash), "enrichedWithdrawalEvent", &enrichedWithdrawalEvent) + s.numberOfPotentialAttackOnInProgressGames++ + } + + // eventually update the map with the new enrichedWithdrawalEvent + s.potentialAttackOnInProgressGames[key] = enrichedWithdrawalEvent +} + +func (s *State) IncrementSuspiciousEventsOnChallengerWinsGames(enrichedWithdrawalEvent validator.EnrichedProvenWithdrawalEvent) { + key := enrichedWithdrawalEvent.Event.Raw.TxHash + + s.logger.Error("STATE WITHDRAWAL:is NOT valid, but the game is correctly resolved", "TxHash", fmt.Sprintf("%v", enrichedWithdrawalEvent.Event.Raw.TxHash), "enrichedWithdrawalEvent", &enrichedWithdrawalEvent) + s.suspiciousEventsOnChallengerWinsGames.Add(key, enrichedWithdrawalEvent) + s.numberOfSuspiciousEventsOnChallengerWinsGames++ + + if _, ok := s.potentialAttackOnInProgressGames[key]; ok { + s.logger.Error("STATE WITHDRAWAL: added to suspicious attacks. Removing from inProgress", "TxHash", fmt.Sprintf("%v", enrichedWithdrawalEvent.Event.Raw.TxHash), "enrichedWithdrawalEvent", &enrichedWithdrawalEvent) + delete(s.potentialAttackOnInProgressGames, key) + s.numberOfPotentialAttackOnInProgressGames-- + } + + s.withdrawalsProcessed++ + +} + func (s *State) GetPercentages() (uint64, uint64) { blockToProcess := s.latestL1Height - s.nextL1Height divisor := float64(s.latestL1Height) * 100 @@ -80,23 +170,99 @@ func (s *State) GetPercentages() (uint64, uint64) { } type Metrics struct { - InitialL1HeightGauge prometheus.Gauge - NextL1HeightGauge prometheus.Gauge - LatestL1HeightGauge prometheus.Gauge - ProcessedProvenWithdrawalsEventsExtensions1Counter prometheus.Counter - NumberOfDetectedForgeryGauge prometheus.Gauge - NumberOfInvalidWithdrawalsGauge prometheus.Gauge - WithdrawalsValidatedCounter prometheus.Counter - NodeConnectionFailuresCounter prometheus.Counter - ForgeriesWithdrawalsEventsGauge prometheus.Gauge - InvalidProposalWithdrawalsEventsGauge prometheus.Gauge - ForgeriesWithdrawalsEventsGaugeVec *prometheus.GaugeVec - InvalidProposalWithdrawalsEventsGaugeVec *prometheus.GaugeVec + InitialL1HeightGauge prometheus.Gauge + NextL1HeightGauge prometheus.Gauge + LatestL1HeightGauge prometheus.Gauge + LatestL2HeightGauge prometheus.Gauge + + EventsProcessedCounter prometheus.Counter + WithdrawalsProcessedCounter prometheus.Counter + + NodeConnectionFailuresCounter prometheus.Counter + + PotentialAttackOnDefenderWinsGamesGauge prometheus.Gauge + PotentialAttackOnInProgressGamesGauge prometheus.Gauge + SuspiciousEventsOnChallengerWinsGamesGauge prometheus.Gauge + + PotentialAttackOnDefenderWinsGamesGaugeVec *prometheus.GaugeVec + PotentialAttackOnInProgressGamesGaugeVec *prometheus.GaugeVec + SuspiciousEventsOnChallengerWinsGamesGaugeVec *prometheus.GaugeVec // Previous values for counters - previousProcessedProvenWithdrawalsExtension1Events uint64 - previousWithdrawalsValidated uint64 - previousNodeConnectionFailures uint64 + previousEventsProcessed uint64 + previousWithdrawalsProcessed uint64 + previousNodeConnectionFailures uint64 +} + +func (m *Metrics) String() string { + initialL1HeightGaugeValue, _ := GetGaugeValue(m.InitialL1HeightGauge) + nextL1HeightGaugeValue, _ := GetGaugeValue(m.NextL1HeightGauge) + latestL1HeightGaugeValue, _ := GetGaugeValue(m.LatestL1HeightGauge) + latestL2HeightGaugeValue, _ := GetGaugeValue(m.LatestL2HeightGauge) + + withdrawalsProcessedCounterValue, _ := GetCounterValue(m.WithdrawalsProcessedCounter) + eventsProcessedCounterValue, _ := GetCounterValue(m.EventsProcessedCounter) + + nodeConnectionFailuresCounterValue, _ := GetCounterValue(m.NodeConnectionFailuresCounter) + + potentialAttackOnDefenderWinsGamesGaugeValue, _ := GetGaugeValue(m.PotentialAttackOnDefenderWinsGamesGauge) + potentialAttackOnInProgressGamesGaugeValue, _ := GetGaugeValue(m.PotentialAttackOnInProgressGamesGauge) + + forgeriesWithdrawalsEventsGaugeVecValue, _ := GetGaugeVecValue(m.PotentialAttackOnDefenderWinsGamesGaugeVec, prometheus.Labels{}) + invalidProposalWithdrawalsEventsGaugeVecValue, _ := GetGaugeVecValue(m.PotentialAttackOnInProgressGamesGaugeVec, prometheus.Labels{}) + + return fmt.Sprintf( + "InitialL1HeightGauge: %d\nNextL1HeightGauge: %d\nLatestL1HeightGauge: %d\n latestL2HeightGaugeValue: %d\n eventsProcessedCounterValue: %d\nwithdrawalsProcessedCounterValue: %d\nnodeConnectionFailuresCounterValue: %d\n potentialAttackOnDefenderWinsGamesGaugeValue: %d\n potentialAttackOnInProgressGamesGaugeValue: %d\n forgeriesWithdrawalsEventsGaugeVecValue: %d\n invalidProposalWithdrawalsEventsGaugeVecValue: %d\n previousEventsProcessed: %d\n previousWithdrawalsProcessed: %d\n previousNodeConnectionFailures: %d\n", + uint64(initialL1HeightGaugeValue), + uint64(nextL1HeightGaugeValue), + uint64(latestL1HeightGaugeValue), + uint64(latestL2HeightGaugeValue), + uint64(eventsProcessedCounterValue), + uint64(withdrawalsProcessedCounterValue), + uint64(nodeConnectionFailuresCounterValue), + uint64(potentialAttackOnDefenderWinsGamesGaugeValue), + uint64(potentialAttackOnInProgressGamesGaugeValue), + uint64(forgeriesWithdrawalsEventsGaugeVecValue), + uint64(invalidProposalWithdrawalsEventsGaugeVecValue), + m.previousEventsProcessed, + m.previousWithdrawalsProcessed, + m.previousNodeConnectionFailures, + ) +} + +// Generic function to get the value of any prometheus.Counter +func GetCounterValue(counter prometheus.Counter) (float64, error) { + metric := &dto.Metric{} + err := counter.Write(metric) + if err != nil { + return 0, err + } + return metric.GetCounter().GetValue(), nil +} + +// Generic function to get the value of any prometheus.Gauge +func GetGaugeValue(gauge prometheus.Gauge) (float64, error) { + metric := &dto.Metric{} + err := gauge.Write(metric) + if err != nil { + return 0, err + } + return metric.GetGauge().GetValue(), nil +} + +// Function to get the value of a specific Gauge within a GaugeVec +func GetGaugeVecValue(gaugeVec *prometheus.GaugeVec, labels prometheus.Labels) (float64, error) { + gauge, err := gaugeVec.GetMetricWith(labels) + if err != nil { + return 0, err + } + + metric := &dto.Metric{} + err = gauge.Write(metric) + if err != nil { + return 0, err + } + return metric.GetGauge().GetValue(), nil } func NewMetrics(m metrics.Factory) *Metrics { @@ -116,56 +282,64 @@ func NewMetrics(m metrics.Factory) *Metrics { Name: "latest_l1_height", Help: "Latest L1 Height", }), - ProcessedProvenWithdrawalsEventsExtensions1Counter: m.NewCounter(prometheus.CounterOpts{ + LatestL2HeightGauge: m.NewGauge(prometheus.GaugeOpts{ Namespace: MetricsNamespace, - Name: "processed_provenwithdrawalsextension1_events_total", - Help: "Total number of processed provenwithdrawalsextension1 events", + Name: "latest_l2_height", + Help: "Latest L2 Height", }), - NumberOfDetectedForgeryGauge: m.NewGauge(prometheus.GaugeOpts{ + EventsProcessedCounter: m.NewCounter(prometheus.CounterOpts{ Namespace: MetricsNamespace, - Name: "number_of_detected_forgeries", - Help: "Number of detected forgeries", + Name: "events_processed_total", + Help: "Total number of events processed", }), - NumberOfInvalidWithdrawalsGauge: m.NewGauge(prometheus.GaugeOpts{ + WithdrawalsProcessedCounter: m.NewCounter(prometheus.CounterOpts{ Namespace: MetricsNamespace, - Name: "number_of_invalid_withdrawals", - Help: "Number of invalid withdrawals", - }), - WithdrawalsValidatedCounter: m.NewCounter(prometheus.CounterOpts{ - Namespace: MetricsNamespace, - Name: "withdrawals_validated_total", - Help: "Total number of withdrawals validated", + Name: "withdrawals_processed_total", + Help: "Total number of withdrawals processed", }), NodeConnectionFailuresCounter: m.NewCounter(prometheus.CounterOpts{ Namespace: MetricsNamespace, Name: "node_connection_failures_total", Help: "Total number of node connection failures", }), - ForgeriesWithdrawalsEventsGauge: m.NewGauge(prometheus.GaugeOpts{ + PotentialAttackOnDefenderWinsGamesGauge: m.NewGauge(prometheus.GaugeOpts{ Namespace: MetricsNamespace, - Name: "forgeries_withdrawals_events_count", - Help: "Number of forgeries withdrawals events", + Name: "potential_attack_on_defender_wins_games_count", + Help: "Number of potential attacks on defender wins games", }), - InvalidProposalWithdrawalsEventsGauge: m.NewGauge(prometheus.GaugeOpts{ + PotentialAttackOnInProgressGamesGauge: m.NewGauge(prometheus.GaugeOpts{ Namespace: MetricsNamespace, - Name: "invalid_proposal_withdrawals_events_count", - Help: "Number of invalid proposal withdrawals events", + Name: "potential_attack_on_in_progress_games_count", + Help: "Number of potential attacks on in progress games", }), - ForgeriesWithdrawalsEventsGaugeVec: m.NewGaugeVec( + SuspiciousEventsOnChallengerWinsGamesGauge: m.NewGauge(prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "suspicious_events_on_challenger_wins_games_count", + Help: "Number of suspicious events on challenger wins games", + }), + PotentialAttackOnDefenderWinsGamesGaugeVec: m.NewGaugeVec( prometheus.GaugeOpts{ Namespace: MetricsNamespace, - Name: "forgeries_withdrawals_events_info", - Help: "Information about forgeries withdrawals events.", + Name: "potential_attack_on_defender_wins_games_gauge_vec", + Help: "Information about potential attacks on defender wins games.", }, - []string{"withdrawal_hash", "proof_submitter", "status", "blacklisted", "withdrawal_hash_present", "enriched", "event_block_number", "event_tx_hash", "event_index"}, + []string{"withdrawal_hash", "proof_submitter", "status", "blacklisted", "withdrawal_hash_present", "enriched", "event_block_number", "event_tx_hash"}, ), - InvalidProposalWithdrawalsEventsGaugeVec: m.NewGaugeVec( + PotentialAttackOnInProgressGamesGaugeVec: m.NewGaugeVec( prometheus.GaugeOpts{ Namespace: MetricsNamespace, - Name: "invalid_proposal_withdrawals_events_info", - Help: "Information about invalid proposal withdrawals events.", + Name: "potential_attack_on_in_progress_games_gauge_vec", + Help: "Information about potential attacks on in progress games.", }, - []string{"withdrawal_hash", "proof_submitter", "status", "blacklisted", "withdrawal_hash_present", "enriched", "event_block_number", "event_tx_hash", "event_index"}, + []string{"withdrawal_hash", "proof_submitter", "status", "blacklisted", "withdrawal_hash_present", "enriched", "event_block_number", "event_tx_hash"}, + ), + SuspiciousEventsOnChallengerWinsGamesGaugeVec: m.NewGaugeVec( + prometheus.GaugeOpts{ + Namespace: MetricsNamespace, + Name: "suspicious_events_on_challenger_wins_games_info", + Help: "Information about suspicious events on challenger wins games.", + }, + []string{"withdrawal_hash", "proof_submitter", "status", "blacklisted", "withdrawal_hash_present", "enriched", "event_block_number", "event_tx_hash"}, ), } @@ -178,26 +352,26 @@ func (m *Metrics) UpdateMetricsFromState(state *State) { m.InitialL1HeightGauge.Set(float64(state.initialL1Height)) m.NextL1HeightGauge.Set(float64(state.nextL1Height)) m.LatestL1HeightGauge.Set(float64(state.latestL1Height)) + m.LatestL2HeightGauge.Set(float64(state.latestL2Height)) - m.NumberOfDetectedForgeryGauge.Set(float64(state.numberOfDetectedForgery)) - m.NumberOfInvalidWithdrawalsGauge.Set(float64(state.numberOfInvalidWithdrawals)) - m.ForgeriesWithdrawalsEventsGauge.Set(float64(len(state.forgeriesWithdrawalsEvents))) - m.InvalidProposalWithdrawalsEventsGauge.Set(float64(len(state.invalidProposalWithdrawalsEvents))) + m.PotentialAttackOnDefenderWinsGamesGauge.Set(float64(state.numberOfPotentialAttacksOnDefenderWinsGames)) + m.PotentialAttackOnInProgressGamesGauge.Set(float64(state.numberOfPotentialAttackOnInProgressGames)) + m.SuspiciousEventsOnChallengerWinsGamesGauge.Set(float64(state.numberOfSuspiciousEventsOnChallengerWinsGames)) // Update Counters by calculating deltas // Processed Withdrawals - processedWithdrawalsDelta := state.processedProvenWithdrawalsExtension1Events - m.previousProcessedProvenWithdrawalsExtension1Events - if processedWithdrawalsDelta > 0 { - m.ProcessedProvenWithdrawalsEventsExtensions1Counter.Add(float64(processedWithdrawalsDelta)) + eventsProcessedDelta := state.eventsProcessed - m.previousEventsProcessed + if eventsProcessedDelta > 0 { + m.EventsProcessedCounter.Add(float64(eventsProcessedDelta)) } - m.previousProcessedProvenWithdrawalsExtension1Events = state.processedProvenWithdrawalsExtension1Events + m.previousEventsProcessed = state.eventsProcessed // Withdrawals Validated - withdrawalsValidatedDelta := state.withdrawalsValidated - m.previousWithdrawalsValidated - if withdrawalsValidatedDelta > 0 { - m.WithdrawalsValidatedCounter.Add(float64(withdrawalsValidatedDelta)) + withdrawalsProcessedDelta := state.withdrawalsProcessed - m.previousWithdrawalsProcessed + if withdrawalsProcessedDelta > 0 { + m.WithdrawalsProcessedCounter.Add(float64(withdrawalsProcessedDelta)) } - m.previousWithdrawalsValidated = state.withdrawalsValidated + m.previousWithdrawalsProcessed = state.withdrawalsProcessed // Node Connection Failures nodeConnectionFailuresDelta := state.nodeConnectionFailures - m.previousNodeConnectionFailures @@ -207,12 +381,12 @@ func (m *Metrics) UpdateMetricsFromState(state *State) { m.previousNodeConnectionFailures = state.nodeConnectionFailures // Update metrics for forgeries withdrawals events - for index, event := range state.forgeriesWithdrawalsEvents { + for _, event := range state.potentialAttackOnDefenderWinsGames { withdrawalHash := common.BytesToHash(event.Event.WithdrawalHash[:]).Hex() proofSubmitter := event.Event.ProofSubmitter.String() status := event.DisputeGame.DisputeGameData.Status.String() - m.ForgeriesWithdrawalsEventsGaugeVec.WithLabelValues( + m.PotentialAttackOnDefenderWinsGamesGaugeVec.WithLabelValues( withdrawalHash, proofSubmitter, status, @@ -221,20 +395,19 @@ func (m *Metrics) UpdateMetricsFromState(state *State) { fmt.Sprintf("%v", event.Enriched), fmt.Sprintf("%v", event.Event.Raw.BlockNumber), event.Event.Raw.TxHash.String(), - fmt.Sprintf("%v", index), ).Set(1) // Set a value for existence } // Clear the previous values - m.InvalidProposalWithdrawalsEventsGaugeVec.Reset() + m.PotentialAttackOnInProgressGamesGaugeVec.Reset() // Update metrics for invalid proposal withdrawals events - for index, event := range state.invalidProposalWithdrawalsEvents { + for _, event := range state.potentialAttackOnInProgressGames { withdrawalHash := common.BytesToHash(event.Event.WithdrawalHash[:]).Hex() proofSubmitter := event.Event.ProofSubmitter.String() status := event.DisputeGame.DisputeGameData.Status.String() - m.InvalidProposalWithdrawalsEventsGaugeVec.WithLabelValues( + m.PotentialAttackOnInProgressGamesGaugeVec.WithLabelValues( withdrawalHash, proofSubmitter, status, @@ -243,7 +416,32 @@ func (m *Metrics) UpdateMetricsFromState(state *State) { fmt.Sprintf("%v", event.Enriched), fmt.Sprintf("%v", event.Event.Raw.BlockNumber), event.Event.Raw.TxHash.String(), - fmt.Sprintf("%v", index), ).Set(1) // Set a value for existence } + + // Clear the previous values + m.SuspiciousEventsOnChallengerWinsGamesGaugeVec.Reset() + // Update metrics for invalid proposal withdrawals events + for _, key := range state.suspiciousEventsOnChallengerWinsGames.Keys() { + enrichedEvent, ok := state.suspiciousEventsOnChallengerWinsGames.Get(key) + if ok { + event := enrichedEvent.(validator.EnrichedProvenWithdrawalEvent) + + withdrawalHash := common.BytesToHash(event.Event.WithdrawalHash[:]).Hex() + proofSubmitter := event.Event.ProofSubmitter.String() + status := event.DisputeGame.DisputeGameData.Status.String() + + m.SuspiciousEventsOnChallengerWinsGamesGaugeVec.WithLabelValues( + withdrawalHash, + proofSubmitter, + status, + fmt.Sprintf("%v", event.Blacklisted), + fmt.Sprintf("%v", event.WithdrawalHashPresentOnL2), + fmt.Sprintf("%v", event.Enriched), + fmt.Sprintf("%v", event.Event.Raw.BlockNumber), + event.Event.Raw.TxHash.String(), + ).Set(1) // Set a value for existence + } + } + } diff --git a/op-monitorism/faultproof_withdrawals/validator/fault_dispute_game_helper.go b/op-monitorism/faultproof_withdrawals/validator/fault_dispute_game_helper.go index 92d8b86..1b559e1 100644 --- a/op-monitorism/faultproof_withdrawals/validator/fault_dispute_game_helper.go +++ b/op-monitorism/faultproof_withdrawals/validator/fault_dispute_game_helper.go @@ -69,7 +69,7 @@ func (gs GameStatus) String() string { // String provides a string representation of DisputeGameData. func (d DisputeGameData) String() string { - return fmt.Sprintf("DisputeGame[ disputeGameProxyAddress=%v rootClaim=%s l2blockNumber=%s l2ChainID=%s status=%v createdAt=%v resolvedAt=%v ]", + return fmt.Sprintf("DisputeGame[ disputeGameProxyAddress: %v rootClaim: %s l2blockNumber: %s l2ChainID: %s status: %v createdAt: %v resolvedAt: %v ]", d.ProxyAddress, common.BytesToHash(d.RootClaim[:]), d.L2blockNumber.String(), diff --git a/op-monitorism/faultproof_withdrawals/validator/op_node_helper.go b/op-monitorism/faultproof_withdrawals/validator/op_node_helper.go index 67d63cb..7fa0543 100644 --- a/op-monitorism/faultproof_withdrawals/validator/op_node_helper.go +++ b/op-monitorism/faultproof_withdrawals/validator/op_node_helper.go @@ -17,55 +17,82 @@ import ( // OpNodeHelper assists in interacting with the op-node type OpNodeHelper struct { // objects - l2OpNodeClient *ethclient.Client // The op-node (consensus) client. - rpc_l2Client *rpc.Client // The RPC client for the L2 node. - ctx context.Context // Context for managing cancellation and timeouts. - l2OutputRootCache *lru.Cache // Cache for storing L2 output roots. + l2OpNodeClient *ethclient.Client // The op-node (consensus) client. + l2OpGethClient *ethclient.Client // The op-geth client. + rpc_l2Client *rpc.Client // The RPC client for the L2 node. + ctx context.Context // Context for managing cancellation and timeouts. + l2OutputRootCache *lru.Cache // Cache for storing L2 output roots. + LatestKnownL2BlockNumber uint64 // The latest known L2 block number. } const outputRootCacheSize = 1000 // Size of the output root cache. // NewOpNodeHelper initializes a new OpNodeHelper. // It creates a cache for storing output roots and binds to the L2 node client. -func NewOpNodeHelper(ctx context.Context, l2OpNodeClient *ethclient.Client) (*OpNodeHelper, error) { +func NewOpNodeHelper(ctx context.Context, l2OpNodeClient *ethclient.Client, l2OpGethClient *ethclient.Client) (*OpNodeHelper, error) { l2OutputRootCache, err := lru.New(outputRootCacheSize) if err != nil { return nil, fmt.Errorf("failed to create cache: %w", err) } rpc_l2Client := l2OpNodeClient.Client() - return &OpNodeHelper{ - l2OpNodeClient: l2OpNodeClient, - rpc_l2Client: rpc_l2Client, - ctx: ctx, - l2OutputRootCache: l2OutputRootCache, - }, nil + ret := OpNodeHelper{ + l2OpNodeClient: l2OpNodeClient, + l2OpGethClient: l2OpGethClient, + rpc_l2Client: rpc_l2Client, + ctx: ctx, + l2OutputRootCache: l2OutputRootCache, + LatestKnownL2BlockNumber: 0, + } + + //ignoring the return value as it is already stored in the struct by the method + latestBlockNumber, err := ret.GetLatestKnownL2BlockNumber() + if err != nil { + return nil, fmt.Errorf("failed to get latest known L2 block number: %w", err) + } + + ret.LatestKnownL2BlockNumber = latestBlockNumber + return &ret, nil + +} + +// get latest known L2 block number +func (on *OpNodeHelper) GetLatestKnownL2BlockNumber() (uint64, error) { + LatestKnownL2BlockNumber, err := on.l2OpGethClient.BlockNumber(on.ctx) + if err != nil { + return 0, fmt.Errorf("failed to get latest known L2 block number: %w", err) + } + on.LatestKnownL2BlockNumber = LatestKnownL2BlockNumber + return LatestKnownL2BlockNumber, nil } // GetOutputRootFromTrustedL2Node retrieves the output root for a given L2 block number from a trusted L2 node. -// It returns the output root as a bytes32 array. +// It returns the output root as a Bytes32 array. func (on *OpNodeHelper) GetOutputRootFromTrustedL2Node(l2blockNumber *big.Int) ([32]byte, error) { ret, found := on.l2OutputRootCache.Get(l2blockNumber) + if !found { var result OutputResponse l2blockNumberHex := hexutil.EncodeBig(l2blockNumber) err := on.rpc_l2Client.CallContext(on.ctx, &result, "optimism_outputAtBlock", l2blockNumberHex) + //check if error contains "failed to determine L2BlockRef of height" if err != nil { return [32]byte{}, fmt.Errorf("failed to get output at block for game block:%v : %w", l2blockNumberHex, err) } trustedRootProof, err := StringToBytes32(result.OutputRoot) if err != nil { - return [32]byte{}, fmt.Errorf("failed to convert output root to bytes32: %w", err) + return [32]byte{}, fmt.Errorf("failed to convert output root to Bytes32: %w", err) } - ret = trustedRootProof + ret = [32]byte(trustedRootProof) + on.l2OutputRootCache.Add(l2blockNumber, ret) } return ret.([32]byte), nil } // GetOutputRootFromCalculation retrieves the output root by calculating it from the given block number. -// It returns the calculated output root as a bytes32 array. +// It returns the calculated output root as a Bytes32 array. func (on *OpNodeHelper) GetOutputRootFromCalculation(blockNumber *big.Int) ([32]byte, error) { block, err := on.l2OpNodeClient.BlockByNumber(on.ctx, blockNumber) if err != nil { @@ -78,6 +105,6 @@ func (on *OpNodeHelper) GetOutputRootFromCalculation(blockNumber *big.Int) ([32] return [32]byte{}, fmt.Errorf("failed to get proof: %w", err) } - outputRoot := eth.OutputRoot(ð.OutputV0{StateRoot: eth.Bytes32(block.Root()), MessagePasserStorageRoot: eth.Bytes32(proof.StorageHash), BlockHash: block.Hash()}) + outputRoot := eth.OutputRoot(ð.OutputV0{StateRoot: [32]byte(block.Root()), MessagePasserStorageRoot: [32]byte(proof.StorageHash), BlockHash: block.Hash()}) return outputRoot, nil } diff --git a/op-monitorism/faultproof_withdrawals/validator/optimism_portal2_helper.go b/op-monitorism/faultproof_withdrawals/validator/optimism_portal2_helper.go index ef78444..32f5484 100644 --- a/op-monitorism/faultproof_withdrawals/validator/optimism_portal2_helper.go +++ b/op-monitorism/faultproof_withdrawals/validator/optimism_portal2_helper.go @@ -41,13 +41,13 @@ type OptimismPortal2Helper struct { } // String provides a string representation of WithdrawalProvenExtension1Event. -func (e *WithdrawalProvenExtension1Event) String() string { - return fmt.Sprintf("WithdrawalHash: %x, ProofSubmitter: %v, Raw: %v", e.WithdrawalHash, e.ProofSubmitter, e.Raw) +func (e WithdrawalProvenExtension1Event) String() string { + return fmt.Sprintf("WithdrawalHash: %s, ProofSubmitter: %v, Raw: %v", common.BytesToHash(e.WithdrawalHash[:]), e.ProofSubmitter, e.Raw) } // String provides a string representation of WithdrawalProvenEvent. -func (e *WithdrawalProvenEvent) String() string { - return fmt.Sprintf("WithdrawalHash: %x, Raw: %v", e.WithdrawalHash, e.Raw) +func (e WithdrawalProvenEvent) String() string { + return fmt.Sprintf("WithdrawalHash: %s, Raw: %v", common.BytesToHash(e.WithdrawalHash[:]), e.Raw) } // String provides a string representation of SubmittedProofData. @@ -126,9 +126,9 @@ func (op *OptimismPortal2Helper) GetProvenWithdrawalsEvents(start uint64, end *u return events, nil } -// GetProvenWithdrawalsExtension1EventsIterartor creates an iterator for proven withdrawal extension 1 events within the specified block range. +// GetProvenWithdrawalsExtension1EventsIterator creates an iterator for proven withdrawal extension 1 events within the specified block range. // It returns the iterator along with any error encountered. -func (op *OptimismPortal2Helper) GetProvenWithdrawalsExtension1EventsIterartor(start uint64, end *uint64) (*l1.OptimismPortal2WithdrawalProvenExtension1Iterator, error) { +func (op *OptimismPortal2Helper) GetProvenWithdrawalsExtension1EventsIterator(start uint64, end *uint64) (*l1.OptimismPortal2WithdrawalProvenExtension1Iterator, error) { filterOpts := &bind.FilterOpts{Context: op.ctx, Start: start, End: end} iterator, err := op.optimismPortal2.FilterWithdrawalProvenExtension1(filterOpts, nil, nil) if err != nil { @@ -141,7 +141,7 @@ func (op *OptimismPortal2Helper) GetProvenWithdrawalsExtension1EventsIterartor(s // GetProvenWithdrawalsExtension1Events retrieves proven withdrawal extension 1 events within the specified block range. // It returns a slice of WithdrawalProvenExtension1Event along with any error encountered. func (op *OptimismPortal2Helper) GetProvenWithdrawalsExtension1Events(start uint64, end *uint64) ([]WithdrawalProvenExtension1Event, error) { - iterator, err := op.GetProvenWithdrawalsExtension1EventsIterartor(start, end) + iterator, err := op.GetProvenWithdrawalsExtension1EventsIterator(start, end) if err != nil { return nil, fmt.Errorf("failed to get proven withdrawals extension1 iterator error:%w", err) } diff --git a/op-monitorism/faultproof_withdrawals/validator/proven_withdrawal_validator.go b/op-monitorism/faultproof_withdrawals/validator/proven_withdrawal_validator.go index e7c21c1..f20e235 100644 --- a/op-monitorism/faultproof_withdrawals/validator/proven_withdrawal_validator.go +++ b/op-monitorism/faultproof_withdrawals/validator/proven_withdrawal_validator.go @@ -45,7 +45,7 @@ func (e *EnrichedProvenWithdrawalEvent) String() string { return fmt.Sprintf("Event: %v, DisputeGame: %v, ExpectedRootClaim: %s, Blacklisted: %v, withdrawalHashPresentOnL2: %v, Enriched: %v", e.Event, e.DisputeGame, - common.BytesToHash(e.ExpectedRootClaim[:]), + common.Bytes2Hex(e.ExpectedRootClaim[:]), e.Blacklisted, e.WithdrawalHashPresentOnL2, e.Enriched) @@ -74,7 +74,7 @@ func NewWithdrawalValidator(ctx context.Context, l1GethClient *ethclient.Client, return nil, fmt.Errorf("failed to create l2 to l1 message passer helper: %w", err) } - l2NodeHelper, err := NewOpNodeHelper(ctx, l2OpNodeClient) + l2NodeHelper, err := NewOpNodeHelper(ctx, l2OpNodeClient, l2OpGethClient) if err != nil { return nil, fmt.Errorf("failed to create l2 node helper: %w", err) } @@ -112,11 +112,20 @@ func (wv *ProvenWithdrawalValidator) UpdateEnrichedWithdrawalEvent(event *Enrich // Check if the game root claim is valid on L2 only if not confirmed already that it is on L2 if !event.Enriched { - trustedRootClaim, err := wv.l2NodeHelper.GetOutputRootFromTrustedL2Node(event.DisputeGame.DisputeGameData.L2blockNumber) + latest_known_l2_block, err := wv.l2NodeHelper.GetLatestKnownL2BlockNumber() if err != nil { - return fmt.Errorf("failed to get trustedRootClaim from Op-node: %w", err) + return fmt.Errorf("failed to get latest known L2 block number: %w", err) } - event.ExpectedRootClaim = trustedRootClaim + if latest_known_l2_block >= event.DisputeGame.DisputeGameData.L2blockNumber.Uint64() { + trustedRootClaim, err := wv.l2NodeHelper.GetOutputRootFromTrustedL2Node(event.DisputeGame.DisputeGameData.L2blockNumber) + if err != nil { + return fmt.Errorf("failed to get trustedRootClaim from Op-node: %w", err) + } + event.ExpectedRootClaim = trustedRootClaim + } else { + event.ExpectedRootClaim = [32]byte{} + } + } // Check if the withdrawal exists on L2 only if not confirmed already that it is on L2 @@ -194,11 +203,43 @@ func (wv *ProvenWithdrawalValidator) GetEnrichedWithdrawalsEvents(start uint64, return enrichedProvenWithdrawalEvents, nil } +// GetEnrichedWithdrawalsEvents retrieves enriched withdrawal events within the specified block range. +// It returns a slice of EnrichedProvenWithdrawalEvent along with any error encountered. +func (wv *ProvenWithdrawalValidator) GetEnrichedWithdrawalsEventsMap(start uint64, end *uint64) (map[common.Hash]EnrichedProvenWithdrawalEvent, error) { + iterator, err := wv.optimismPortal2Helper.GetProvenWithdrawalsExtension1EventsIterator(start, end) + if err != nil { + return nil, fmt.Errorf("failed to get proven withdrawals extension1 iterator error:%w", err) + } + + enrichedProvenWithdrawalEvents := make(map[common.Hash]EnrichedProvenWithdrawalEvent) + + for iterator.Next() { + event := iterator.Event + + enrichedWithdrawalEvent, err := wv.GetEnrichedWithdrawalEvent(&WithdrawalProvenExtension1Event{ + WithdrawalHash: event.WithdrawalHash, + ProofSubmitter: event.ProofSubmitter, + Raw: Raw{ + BlockNumber: event.Raw.BlockNumber, + TxHash: event.Raw.TxHash, + }, + }) + if err != nil { + return nil, fmt.Errorf("failed to get enriched withdrawal event: %w", err) + } + + key := enrichedWithdrawalEvent.Event.Raw.TxHash + enrichedProvenWithdrawalEvents[key] = *enrichedWithdrawalEvent + } + + return enrichedProvenWithdrawalEvents, nil +} + // IsWithdrawalEventValid checks if the enriched withdrawal event is valid. // It returns true if the event is valid, otherwise returns false. func (wv *ProvenWithdrawalValidator) IsWithdrawalEventValid(enrichedWithdrawalEvent *EnrichedProvenWithdrawalEvent) (bool, error) { - if enrichedWithdrawalEvent.ExpectedRootClaim == [32]byte{} { - return false, fmt.Errorf("trustedRootClaim is nil, game not enriched") + if !enrichedWithdrawalEvent.Enriched { + return false, fmt.Errorf("game not enriched") } validGameRootClaim := enrichedWithdrawalEvent.DisputeGame.DisputeGameData.RootClaim == enrichedWithdrawalEvent.ExpectedRootClaim @@ -208,3 +249,7 @@ func (wv *ProvenWithdrawalValidator) IsWithdrawalEventValid(enrichedWithdrawalEv return false, nil } } + +func (wv *ProvenWithdrawalValidator) GetLatestL2Height() uint64 { + return wv.l2NodeHelper.LatestKnownL2BlockNumber +} diff --git a/op-monitorism/faultproof_withdrawals/validator/utils.go b/op-monitorism/faultproof_withdrawals/validator/utils.go index 7d0c144..40ffe2d 100644 --- a/op-monitorism/faultproof_withdrawals/validator/utils.go +++ b/op-monitorism/faultproof_withdrawals/validator/utils.go @@ -2,6 +2,7 @@ package validator import ( "encoding/hex" + "fmt" "strings" "time" @@ -14,6 +15,11 @@ type Raw struct { TxHash common.Hash // The hash of the transaction. } +// String provides a string representation of Raw. +func (r Raw) String() string { + return fmt.Sprintf("{BlockNumber: %d, TxHash: %s}", r.BlockNumber, r.TxHash.String()) +} + // Timestamp represents a Unix timestamp. type Timestamp uint64 diff --git a/op-monitorism/go.mod b/op-monitorism/go.mod index 080c5ee..4ce4ec2 100644 --- a/op-monitorism/go.mod +++ b/op-monitorism/go.mod @@ -11,6 +11,7 @@ require ( github.com/hashicorp/golang-lru v0.5.0 github.com/joho/godotenv v1.5.1 github.com/prometheus/client_golang v1.20.2 + github.com/prometheus/client_model v0.6.1 github.com/stretchr/testify v1.9.0 github.com/urfave/cli/v2 v2.27.4 gopkg.in/yaml.v3 v3.0.1 @@ -50,8 +51,6 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/gorilla/websocket v1.5.3 // indirect github.com/holiman/uint256 v1.3.1 // indirect - github.com/huin/goupnp v1.3.0 // indirect - github.com/jackpal/go-nat-pmp v1.0.2 // indirect github.com/klauspost/compress v1.17.9 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect @@ -62,7 +61,6 @@ require ( github.com/olekukonko/tablewriter v0.0.5 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect - github.com/prometheus/client_model v0.6.1 // indirect github.com/prometheus/common v0.55.0 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/rivo/uniseg v0.4.4 // indirect diff --git a/op-monitorism/go.sum b/op-monitorism/go.sum index 03a2f69..49f5cb7 100644 --- a/op-monitorism/go.sum +++ b/op-monitorism/go.sum @@ -262,7 +262,6 @@ golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ= golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=