Skip to content
This repository has been archived by the owner on May 29, 2024. It is now read-only.

Withdrawal safety for all key L2->L1 events #186

Merged
merged 9 commits into from
Feb 28, 2024
119 changes: 85 additions & 34 deletions e2e/heuristic_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ func TestContractEvent(t *testing.T) {

// TestWithdrawalSafetyAllInvariants ... Tests the E2E flow of a withdrawal
// safety heuristic session. This test ensures that an alert is produced in the event
// of a highly suspicious withdrawal.
// of a highly suspicious withdrawal at every step of the withdrawal flow.
func TestWithdrawalSafetyAllInvariants(t *testing.T) {
ts := e2e.CreateSysTestSuite(t, "")
defer ts.Close()
Expand Down Expand Up @@ -259,6 +259,22 @@ func TestWithdrawalSafetyAllInvariants(t *testing.T) {
core.L2ToL1MessagePasser: fakeAddr.String(),
},
},
{
Network: core.Layer2.String(),
HeuristicType: core.WithdrawalSafety.String(),
StartHeight: nil,
EndHeight: nil,
AlertingParams: &core.AlertPolicy{
Sev: core.LOW.String(),
Msg: alertMsg,
},
SessionParams: map[string]interface{}{
"threshold": 0.20,
"coefficient_threshold": 0.20,
core.L1Portal: ts.Cfg.L1Deployments.OptimismPortalProxy.String(),
core.L2ToL1MessagePasser: predeploys.L2ToL1MessagePasserAddr.String(),
},
},
})
require.NoError(t, err, "Error bootstrapping heuristic session")

Expand All @@ -284,12 +300,33 @@ func TestWithdrawalSafetyAllInvariants(t *testing.T) {
_, err = wait.ForReceiptOK(context.Background(), ts.L1Client, depositTx.Hash())
require.NoError(t, err)

// Initiate and prove a withdrawal
// Initiate withdrawal
withdrawTx, err := l2ToL1MessagePasser.InitiateWithdrawal(l2Opts, aliceAddr, big.NewInt(100_000), calldata)
require.NoError(t, err)
withdrawReceipt, err := wait.ForReceiptOK(context.Background(), ts.L2Client, withdrawTx.Hash())
initReceipt, err := wait.ForReceiptOK(context.Background(), ts.L2Client, withdrawTx.Hash())
require.NoError(t, err)

// Wait for Pessimism to process initiation
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
id := ids[0].PathID
height, err := ts.Subsystems.PathHeight(id)
if err != nil {
return false, err
}

return height != nil && height.Uint64() > initReceipt.BlockNumber.Uint64(), nil
}))

// Ensure Pessimism has detected what it considers an unsafe withdrawal
alerts := ts.TestSlackSvr.SlackAlerts()
require.Equal(t, 1, len(alerts), "expected 1 alerts")
assert.Contains(t, alerts[0].Text, core.WithdrawalSafety.String(), "expected alert to be for withdrawal_safety")
assert.Contains(t, alerts[0].Text, alertMsg, "expected alert to have alert message")

// Ensure that specific invariant messages are included in the alert
assert.Contains(t, alerts[0].Text, alertMsg, registry.GreaterThanPortal)

ts.TestSlackSvr.ClearAlerts()
// Mock the indexer call to return a really high withdrawal amount
ts.TestIxClient.EXPECT().GetAllWithdrawalsByAddress(gomock.Any()).Return([]api_mods.WithdrawalItem{
{
Expand All @@ -298,7 +335,7 @@ func TestWithdrawalSafetyAllInvariants(t *testing.T) {
},
}, nil).AnyTimes()

_, proveReceipt := op_e2e.ProveWithdrawal(t, *ts.Cfg, ts.L1Client, ts.Sys.EthInstances["sequencer"], ts.Cfg.Secrets.Alice, withdrawReceipt)
params, proveReceipt := op_e2e.ProveWithdrawal(t, *ts.Cfg, ts.L1Client, ts.Sys.EthInstances["sequencer"], ts.Cfg.Secrets.Alice, initReceipt)

// Wait for Pessimism to process the proven withdrawal and send a notification to the mocked Slack server.
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
Expand All @@ -312,7 +349,7 @@ func TestWithdrawalSafetyAllInvariants(t *testing.T) {
}))

// Ensure Pessimism has detected what it considers an unsafe withdrawal
alerts := ts.TestSlackSvr.SlackAlerts()
alerts = ts.TestSlackSvr.SlackAlerts()
require.Equal(t, 1, len(alerts), "expected 1 alerts")
assert.Contains(t, alerts[0].Text, core.WithdrawalSafety.String(), "expected alert to be for withdrawal_safety")
assert.Contains(t, alerts[0].Text, fakeAddr.String(), "expected alert to be for dummy L2ToL1MessagePasser")
Expand All @@ -323,34 +360,34 @@ func TestWithdrawalSafetyAllInvariants(t *testing.T) {
assert.Contains(t, alerts[0].Text, alertMsg, registry.GreaterThanPortal)
assert.Contains(t, alerts[0].Text, alertMsg, fmt.Sprintf(registry.GreaterThanThreshold, 20.0))

// TODO(#178) - Feat - Support WithdrawalProven processing in withdrawal_safety heuristic
// Mock the indexer call to return a really low withdrawal amount
// ts.TestIxClient.EXPECT().GetAllWithdrawalsByAddress(gomock.Any()).Return([]api_mods.WithdrawalItem{
// {
// TransactionHash: "0x123",
// Amount: "1",
// },
// }, nil).AnyTimes()
ts.TestIxClient.EXPECT().GetAllWithdrawalsByAddress(gomock.Any()).Return([]api_mods.WithdrawalItem{
{
TransactionHash: "0x123",
Amount: "1",
},
}, nil).AnyTimes()

// Finalize the withdrawal
// finalizeReceipt := op_e2e.FinalizeWithdrawal(t, *ts.Cfg, ts.L1Client, ts.Cfg.Secrets.Alice, proveReceipt, proveParams)

// // Wait for Pessimism to process the finalized withdrawal and send a notification to the mocked Slack server.
// require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
// id := ids[0].PathID
// height, err := ts.Subsystems.PathHeight(id)
// if err != nil {
// return false, err
// }

// return height.Uint64() > finalizeReceipt.BlockNumber.Uint64(), nil
// }))

// alerts = ts.TestSlackSvr.SlackAlerts()
// require.Equal(t, 3, len(alerts), "expected 3 alerts")
// assert.Contains(t, alerts[0].Text, "unsafe_withdrawal", "expected alert to be for unsafe_withdrawal")
// assert.Contains(t, alerts[0].Text, fakeAddr.String(), "expected alert to be for dummy L2ToL1MessagePasser")
// assert.Contains(t, alerts[0].Text, alertMsg, "expected alert to have alert message")
finalizeReceipt := op_e2e.FinalizeWithdrawal(t, *ts.Cfg, ts.L1Client, ts.Cfg.Secrets.Alice, proveReceipt, params)

// Wait for Pessimism to process the finalized withdrawal and send a notification to the mocked Slack server.
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
id := ids[0].PathID
height, err := ts.Subsystems.PathHeight(id)
if err != nil {
return false, err
}

return height.Uint64() > finalizeReceipt.BlockNumber.Uint64(), nil
}))

alerts = ts.TestSlackSvr.SlackAlerts()
require.Equal(t, 1, len(alerts), "expected 1 alert")
assert.Contains(t, alerts[0].Text, core.WithdrawalSafety.String())
assert.Contains(t, alerts[0].Text, alertMsg, "expected alert to have alert message")

// Ensure that specific invariant messages are included in the alert
assert.Contains(t, alerts[0].Text, alertMsg, registry.TooSimilarToMax)
}

// TestWithdrawalSafetyNoInvariants ... Verify that no alerts are produced in the event
Expand Down Expand Up @@ -418,7 +455,7 @@ func TestWithdrawalSafetyNoInvariants(t *testing.T) {
},
}, nil).AnyTimes()

_, proveReceipt := op_e2e.ProveWithdrawal(t, *ts.Cfg, ts.L1Client, ts.Sys.EthInstances["sequencer"], ts.Cfg.Secrets.Alice, withdrawReceipt)
params, proveReceipt := op_e2e.ProveWithdrawal(t, *ts.Cfg, ts.L1Client, ts.Sys.EthInstances["sequencer"], ts.Cfg.Secrets.Alice, withdrawReceipt)

// Wait for Pessimism to process the proven withdrawal and send a notification to the mocked Slack server.
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
Expand All @@ -431,9 +468,23 @@ func TestWithdrawalSafetyNoInvariants(t *testing.T) {
return height != nil && height.Uint64() > proveReceipt.BlockNumber.Uint64(), nil
}))

// Ensure that this withdrawal triggered no alerts
// Finalize the withdrawal
finalizeReceipt := op_e2e.FinalizeWithdrawal(t, *ts.Cfg, ts.L1Client, ts.Cfg.Secrets.Alice, proveReceipt, params)

// Wait for Pessimism to process the finalized withdrawal and send a notification to the mocked Slack server.
require.NoError(t, wait.For(context.Background(), 500*time.Millisecond, func() (bool, error) {
id := ids[0].PathID
height, err := ts.Subsystems.PathHeight(id)
if err != nil {
return false, err
}

return height.Uint64() > finalizeReceipt.BlockNumber.Uint64(), nil
}))

// Ensure that this withdrawal flow triggered no alerts
alerts := ts.TestSlackSvr.SlackAlerts()
require.Equal(t, 0, len(alerts), "expected 0 alerts")
require.Equal(t, 0, len(alerts))
}

// TestFaultDetector ... Ensures that an alert is produced in the presence of a faulty L2Output root
Expand Down
2 changes: 1 addition & 1 deletion internal/api/models/heuristic.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ type SessionRequestParams struct {

// Params ... Returns the heuristic session params
func (hrp *SessionRequestParams) Params() *core.SessionParams {
isp := core.NewSessionParams()
isp := core.NewSessionParams(hrp.NetworkType())

for k, v := range hrp.SessionParams {
isp.SetValue(k, v)
Expand Down
4 changes: 3 additions & 1 deletion internal/core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ const (

// SessionParams ... Parameters used to initialize a heuristic session
type SessionParams struct {
Net Network
params map[string]any
}

Expand All @@ -112,8 +113,9 @@ func (sp *SessionParams) Bytes() []byte {
}

// NewSessionParams ... Initializes heuristic session params
func NewSessionParams() *SessionParams {
func NewSessionParams(n Network) *SessionParams {
isp := &SessionParams{
Net: n,
params: make(map[string]any, 0),
}
isp.params[NestedArgs] = []any{}
Expand Down
2 changes: 1 addition & 1 deletion internal/core/core_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ func Test_EngineRelay(t *testing.T) {
}

func Test_SessionParams(t *testing.T) {
isp := core.NewSessionParams()
isp := core.NewSessionParams(core.Layer1)
assert.NotNil(t, isp, "SessionParams should not be nil")

isp.SetValue("tst", "tst")
Expand Down
Loading
Loading