Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
fea26bd3
Unverified
Commit
fea26bd3
authored
Feb 07, 2024
by
refcell
Committed by
GitHub
Feb 08, 2024
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat(op-dispute-mon): rollup output root checking (#9422)
Co-authored-by:
clabby
<
ben@clab.by
>
parent
c9340197
Changes
10
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
10 changed files
with
599 additions
and
249 deletions
+599
-249
config.go
op-dispute-mon/config/config.go
+8
-2
config_test.go
op-dispute-mon/config/config_test.go
+10
-1
flags.go
op-dispute-mon/flags/flags.go
+7
-0
metrics.go
op-dispute-mon/metrics/metrics.go
+14
-1
noop.go
op-dispute-mon/metrics/noop.go
+1
-0
detector.go
op-dispute-mon/mon/detector.go
+150
-0
detector_test.go
op-dispute-mon/mon/detector_test.go
+320
-0
monitor.go
op-dispute-mon/mon/monitor.go
+23
-61
monitor_test.go
op-dispute-mon/mon/monitor_test.go
+43
-180
service.go
op-dispute-mon/mon/service.go
+23
-4
No files found.
op-dispute-mon/config/config.go
View file @
fea26bd3
...
@@ -14,6 +14,7 @@ import (
...
@@ -14,6 +14,7 @@ import (
var
(
var
(
ErrMissingL1EthRPC
=
errors
.
New
(
"missing l1 eth rpc url"
)
ErrMissingL1EthRPC
=
errors
.
New
(
"missing l1 eth rpc url"
)
ErrMissingGameFactoryAddress
=
errors
.
New
(
"missing game factory address"
)
ErrMissingGameFactoryAddress
=
errors
.
New
(
"missing game factory address"
)
ErrMissingRollupRpc
=
errors
.
New
(
"missing rollup rpc url"
)
)
)
const
(
const
(
...
@@ -31,8 +32,10 @@ const (
...
@@ -31,8 +32,10 @@ const (
type
Config
struct
{
type
Config
struct
{
L1EthRpc
string
// L1 RPC Url
L1EthRpc
string
// L1 RPC Url
GameFactoryAddress
common
.
Address
// Address of the dispute game factory
GameFactoryAddress
common
.
Address
// Address of the dispute game factory
MonitorInterval
time
.
Duration
// Frequency to check for new games to monitor.
RollupRpc
string
// The rollup node RPC URL.
GameWindow
time
.
Duration
// Maximum window to look for games to monitor.
MonitorInterval
time
.
Duration
// Frequency to check for new games to monitor.
GameWindow
time
.
Duration
// Maximum window to look for games to monitor.
MetricsConfig
opmetrics
.
CLIConfig
MetricsConfig
opmetrics
.
CLIConfig
PprofConfig
oppprof
.
CLIConfig
PprofConfig
oppprof
.
CLIConfig
...
@@ -55,6 +58,9 @@ func (c Config) Check() error {
...
@@ -55,6 +58,9 @@ func (c Config) Check() error {
if
c
.
L1EthRpc
==
""
{
if
c
.
L1EthRpc
==
""
{
return
ErrMissingL1EthRPC
return
ErrMissingL1EthRPC
}
}
if
c
.
RollupRpc
==
""
{
return
ErrMissingRollupRpc
}
if
c
.
GameFactoryAddress
==
(
common
.
Address
{})
{
if
c
.
GameFactoryAddress
==
(
common
.
Address
{})
{
return
ErrMissingGameFactoryAddress
return
ErrMissingGameFactoryAddress
}
}
...
...
op-dispute-mon/config/config_test.go
View file @
fea26bd3
...
@@ -11,10 +11,13 @@ import (
...
@@ -11,10 +11,13 @@ import (
var
(
var
(
validL1EthRpc
=
"http://localhost:8545"
validL1EthRpc
=
"http://localhost:8545"
validGameFactoryAddress
=
common
.
Address
{
0x23
}
validGameFactoryAddress
=
common
.
Address
{
0x23
}
validRollupRpc
=
"http://localhost:8555"
)
)
func
validConfig
()
Config
{
func
validConfig
()
Config
{
return
NewConfig
(
validGameFactoryAddress
,
validL1EthRpc
)
cfg
:=
NewConfig
(
validGameFactoryAddress
,
validL1EthRpc
)
cfg
.
RollupRpc
=
validRollupRpc
return
cfg
}
}
func
TestValidConfigIsValid
(
t
*
testing
.
T
)
{
func
TestValidConfigIsValid
(
t
*
testing
.
T
)
{
...
@@ -32,3 +35,9 @@ func TestGameFactoryAddressRequired(t *testing.T) {
...
@@ -32,3 +35,9 @@ func TestGameFactoryAddressRequired(t *testing.T) {
config
.
GameFactoryAddress
=
common
.
Address
{}
config
.
GameFactoryAddress
=
common
.
Address
{}
require
.
ErrorIs
(
t
,
config
.
Check
(),
ErrMissingGameFactoryAddress
)
require
.
ErrorIs
(
t
,
config
.
Check
(),
ErrMissingGameFactoryAddress
)
}
}
func
TestRollupRpcRequired
(
t
*
testing
.
T
)
{
config
:=
validConfig
()
config
.
RollupRpc
=
""
require
.
ErrorIs
(
t
,
config
.
Check
(),
ErrMissingRollupRpc
)
}
op-dispute-mon/flags/flags.go
View file @
fea26bd3
...
@@ -33,6 +33,11 @@ var (
...
@@ -33,6 +33,11 @@ var (
EnvVars
:
prefixEnvVars
(
"GAME_FACTORY_ADDRESS"
),
EnvVars
:
prefixEnvVars
(
"GAME_FACTORY_ADDRESS"
),
}
}
// Optional Flags
// Optional Flags
RollupRpcFlag
=
&
cli
.
StringFlag
{
Name
:
"rollup-rpc"
,
Usage
:
"HTTP provider URL for the rollup node"
,
EnvVars
:
prefixEnvVars
(
"ROLLUP_RPC"
),
}
MonitorIntervalFlag
=
&
cli
.
DurationFlag
{
MonitorIntervalFlag
=
&
cli
.
DurationFlag
{
Name
:
"monitor-interval"
,
Name
:
"monitor-interval"
,
Usage
:
"The interval at which the dispute monitor will check for new games to monitor."
,
Usage
:
"The interval at which the dispute monitor will check for new games to monitor."
,
...
@@ -56,6 +61,7 @@ var requiredFlags = []cli.Flag{
...
@@ -56,6 +61,7 @@ var requiredFlags = []cli.Flag{
// optionalFlags is a list of unchecked cli flags
// optionalFlags is a list of unchecked cli flags
var
optionalFlags
=
[]
cli
.
Flag
{
var
optionalFlags
=
[]
cli
.
Flag
{
RollupRpcFlag
,
MonitorIntervalFlag
,
MonitorIntervalFlag
,
GameWindowFlag
,
GameWindowFlag
,
}
}
...
@@ -97,6 +103,7 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
...
@@ -97,6 +103,7 @@ func NewConfigFromCLI(ctx *cli.Context) (*config.Config, error) {
L1EthRpc
:
ctx
.
String
(
L1EthRpcFlag
.
Name
),
L1EthRpc
:
ctx
.
String
(
L1EthRpcFlag
.
Name
),
GameFactoryAddress
:
gameFactoryAddress
,
GameFactoryAddress
:
gameFactoryAddress
,
RollupRpc
:
ctx
.
String
(
RollupRpcFlag
.
Name
),
MonitorInterval
:
ctx
.
Duration
(
MonitorIntervalFlag
.
Name
),
MonitorInterval
:
ctx
.
Duration
(
MonitorIntervalFlag
.
Name
),
GameWindow
:
ctx
.
Duration
(
GameWindowFlag
.
Name
),
GameWindow
:
ctx
.
Duration
(
GameWindowFlag
.
Name
),
...
...
op-dispute-mon/metrics/metrics.go
View file @
fea26bd3
...
@@ -20,6 +20,7 @@ type Metricer interface {
...
@@ -20,6 +20,7 @@ type Metricer interface {
RecordUp
()
RecordUp
()
RecordGamesStatus
(
inProgress
,
defenderWon
,
challengerWon
int
)
RecordGamesStatus
(
inProgress
,
defenderWon
,
challengerWon
int
)
RecordGameAgreement
(
status
string
,
count
int
)
caching
.
Metrics
caching
.
Metrics
}
}
...
@@ -37,7 +38,8 @@ type Metrics struct {
...
@@ -37,7 +38,8 @@ type Metrics struct {
info
prometheus
.
GaugeVec
info
prometheus
.
GaugeVec
up
prometheus
.
Gauge
up
prometheus
.
Gauge
trackedGames
prometheus
.
GaugeVec
trackedGames
prometheus
.
GaugeVec
gamesAgreement
prometheus
.
GaugeVec
}
}
func
(
m
*
Metrics
)
Registry
()
*
prometheus
.
Registry
{
func
(
m
*
Metrics
)
Registry
()
*
prometheus
.
Registry
{
...
@@ -76,6 +78,13 @@ func NewMetrics() *Metrics {
...
@@ -76,6 +78,13 @@ func NewMetrics() *Metrics {
},
[]
string
{
},
[]
string
{
"status"
,
"status"
,
}),
}),
gamesAgreement
:
*
factory
.
NewGaugeVec
(
prometheus
.
GaugeOpts
{
Namespace
:
Namespace
,
Name
:
"games_agreement"
,
Help
:
"Number of games broken down by whether the result agrees with the reference node"
,
},
[]
string
{
"status"
,
}),
}
}
}
}
...
@@ -112,3 +121,7 @@ func (m *Metrics) RecordGamesStatus(inProgress, defenderWon, challengerWon int)
...
@@ -112,3 +121,7 @@ func (m *Metrics) RecordGamesStatus(inProgress, defenderWon, challengerWon int)
m
.
trackedGames
.
WithLabelValues
(
"defender_won"
)
.
Set
(
float64
(
defenderWon
))
m
.
trackedGames
.
WithLabelValues
(
"defender_won"
)
.
Set
(
float64
(
defenderWon
))
m
.
trackedGames
.
WithLabelValues
(
"challenger_won"
)
.
Set
(
float64
(
challengerWon
))
m
.
trackedGames
.
WithLabelValues
(
"challenger_won"
)
.
Set
(
float64
(
challengerWon
))
}
}
func
(
m
*
Metrics
)
RecordGameAgreement
(
status
string
,
count
int
)
{
m
.
gamesAgreement
.
WithLabelValues
(
status
)
.
Set
(
float64
(
count
))
}
op-dispute-mon/metrics/noop.go
View file @
fea26bd3
...
@@ -11,3 +11,4 @@ func (*NoopMetricsImpl) CacheAdd(_ string, _ int, _ bool) {}
...
@@ -11,3 +11,4 @@ func (*NoopMetricsImpl) CacheAdd(_ string, _ int, _ bool) {}
func
(
*
NoopMetricsImpl
)
CacheGet
(
_
string
,
_
bool
)
{}
func
(
*
NoopMetricsImpl
)
CacheGet
(
_
string
,
_
bool
)
{}
func
(
*
NoopMetricsImpl
)
RecordGamesStatus
(
inProgress
,
defenderWon
,
challengerWon
int
)
{}
func
(
*
NoopMetricsImpl
)
RecordGamesStatus
(
inProgress
,
defenderWon
,
challengerWon
int
)
{}
func
(
*
NoopMetricsImpl
)
RecordGameAgreement
(
status
string
,
count
int
)
{}
op-dispute-mon/mon/detector.go
0 → 100644
View file @
fea26bd3
package
mon
import
(
"context"
"fmt"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
type
statusBatch
struct
{
inProgress
,
defenderWon
,
challengerWon
int
}
func
(
s
*
statusBatch
)
Add
(
status
types
.
GameStatus
)
{
switch
status
{
case
types
.
GameStatusInProgress
:
s
.
inProgress
++
case
types
.
GameStatusDefenderWon
:
s
.
defenderWon
++
case
types
.
GameStatusChallengerWon
:
s
.
challengerWon
++
}
}
type
detectionBatch
struct
{
inProgress
int
agreeDefenderWins
int
disagreeDefenderWins
int
agreeChallengerWins
int
disagreeChallengerWins
int
}
func
(
d
*
detectionBatch
)
merge
(
other
detectionBatch
)
{
d
.
inProgress
+=
other
.
inProgress
d
.
agreeDefenderWins
+=
other
.
agreeDefenderWins
d
.
disagreeDefenderWins
+=
other
.
disagreeDefenderWins
d
.
agreeChallengerWins
+=
other
.
agreeChallengerWins
d
.
disagreeChallengerWins
+=
other
.
disagreeChallengerWins
}
type
OutputRollupClient
interface
{
OutputAtBlock
(
ctx
context
.
Context
,
blockNum
uint64
)
(
*
eth
.
OutputResponse
,
error
)
}
type
MetadataCreator
interface
{
CreateContract
(
game
types
.
GameMetadata
)
(
MetadataLoader
,
error
)
}
type
DetectorMetrics
interface
{
RecordGameAgreement
(
status
string
,
count
int
)
RecordGamesStatus
(
inProgress
,
defenderWon
,
challengerWon
int
)
}
type
detector
struct
{
logger
log
.
Logger
metrics
DetectorMetrics
creator
MetadataCreator
outputClient
OutputRollupClient
}
func
newDetector
(
logger
log
.
Logger
,
metrics
DetectorMetrics
,
creator
MetadataCreator
,
outputClient
OutputRollupClient
)
*
detector
{
return
&
detector
{
logger
:
logger
,
metrics
:
metrics
,
creator
:
creator
,
outputClient
:
outputClient
,
}
}
func
(
d
*
detector
)
Detect
(
ctx
context
.
Context
,
games
[]
types
.
GameMetadata
)
{
statBatch
:=
statusBatch
{}
detectBatch
:=
detectionBatch
{}
for
_
,
game
:=
range
games
{
// Fetch the game metadata to ensure the game status is recorded
// regardless of whether the game agreement is checked.
l2BlockNum
,
rootClaim
,
status
,
err
:=
d
.
fetchGameMetadata
(
ctx
,
game
)
if
err
!=
nil
{
d
.
logger
.
Error
(
"Failed to fetch game metadata"
,
"err"
,
err
)
continue
}
statBatch
.
Add
(
status
)
processed
,
err
:=
d
.
checkAgreement
(
ctx
,
game
.
Proxy
,
l2BlockNum
,
rootClaim
,
status
)
if
err
!=
nil
{
d
.
logger
.
Error
(
"Failed to process game"
,
"err"
,
err
)
continue
}
detectBatch
.
merge
(
processed
)
}
d
.
metrics
.
RecordGamesStatus
(
statBatch
.
inProgress
,
statBatch
.
defenderWon
,
statBatch
.
challengerWon
)
d
.
recordBatch
(
detectBatch
)
}
func
(
d
*
detector
)
recordBatch
(
batch
detectionBatch
)
{
d
.
metrics
.
RecordGameAgreement
(
"in_progress"
,
batch
.
inProgress
)
d
.
metrics
.
RecordGameAgreement
(
"agree_defender_wins"
,
batch
.
agreeDefenderWins
)
d
.
metrics
.
RecordGameAgreement
(
"disagree_defender_wins"
,
batch
.
disagreeDefenderWins
)
d
.
metrics
.
RecordGameAgreement
(
"agree_challenger_wins"
,
batch
.
agreeChallengerWins
)
d
.
metrics
.
RecordGameAgreement
(
"disagree_challenger_wins"
,
batch
.
disagreeChallengerWins
)
}
func
(
d
*
detector
)
fetchGameMetadata
(
ctx
context
.
Context
,
game
types
.
GameMetadata
)
(
uint64
,
common
.
Hash
,
types
.
GameStatus
,
error
)
{
loader
,
err
:=
d
.
creator
.
CreateContract
(
game
)
if
err
!=
nil
{
return
0
,
common
.
Hash
{},
0
,
fmt
.
Errorf
(
"failed to create contract: %w"
,
err
)
}
blockNum
,
rootClaim
,
status
,
err
:=
loader
.
GetGameMetadata
(
ctx
)
if
err
!=
nil
{
return
0
,
common
.
Hash
{},
0
,
fmt
.
Errorf
(
"failed to fetch game metadata: %w"
,
err
)
}
return
blockNum
,
rootClaim
,
status
,
nil
}
func
(
d
*
detector
)
checkAgreement
(
ctx
context
.
Context
,
addr
common
.
Address
,
blockNum
uint64
,
rootClaim
common
.
Hash
,
status
types
.
GameStatus
)
(
detectionBatch
,
error
)
{
agree
,
err
:=
d
.
checkRootAgreement
(
ctx
,
blockNum
,
rootClaim
)
if
err
!=
nil
{
return
detectionBatch
{},
err
}
batch
:=
detectionBatch
{}
switch
status
{
case
types
.
GameStatusInProgress
:
batch
.
inProgress
++
case
types
.
GameStatusDefenderWon
:
if
agree
{
batch
.
agreeDefenderWins
++
}
else
{
batch
.
disagreeDefenderWins
++
d
.
logger
.
Error
(
"Defender won but root claim does not match"
,
"gameAddr"
,
addr
,
"rootClaim"
,
rootClaim
)
}
case
types
.
GameStatusChallengerWon
:
if
agree
{
batch
.
agreeChallengerWins
++
}
else
{
batch
.
disagreeChallengerWins
++
d
.
logger
.
Error
(
"Challenger won but root claim does not match"
,
"gameAddr"
,
addr
,
"rootClaim"
,
rootClaim
)
}
}
return
batch
,
nil
}
func
(
d
*
detector
)
checkRootAgreement
(
ctx
context
.
Context
,
blockNum
uint64
,
rootClaim
common
.
Hash
)
(
bool
,
error
)
{
output
,
err
:=
d
.
outputClient
.
OutputAtBlock
(
ctx
,
blockNum
)
if
err
!=
nil
{
return
false
,
fmt
.
Errorf
(
"failed to get output at block: %w"
,
err
)
}
return
rootClaim
==
common
.
Hash
(
output
.
OutputRoot
),
nil
}
op-dispute-mon/mon/detector_test.go
0 → 100644
View file @
fea26bd3
package
mon
import
(
"context"
"errors"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/game/types"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
var
(
mockRootClaim
=
common
.
HexToHash
(
"0x10"
)
)
func
TestDetector_Detect
(
t
*
testing
.
T
)
{
t
.
Parallel
()
t
.
Run
(
"NoGames"
,
func
(
t
*
testing
.
T
)
{
detector
,
metrics
,
_
,
_
:=
setupDetectorTest
(
t
)
detector
.
Detect
(
context
.
Background
(),
[]
types
.
GameMetadata
{})
metrics
.
Equals
(
t
,
0
,
0
,
0
)
metrics
.
Mapped
(
t
,
map
[
string
]
int
{})
})
t
.
Run
(
"MetadataFetchFails"
,
func
(
t
*
testing
.
T
)
{
detector
,
metrics
,
creator
,
_
:=
setupDetectorTest
(
t
)
creator
.
err
=
errors
.
New
(
"boom"
)
detector
.
Detect
(
context
.
Background
(),
[]
types
.
GameMetadata
{{}})
metrics
.
Equals
(
t
,
0
,
0
,
0
)
metrics
.
Mapped
(
t
,
map
[
string
]
int
{})
})
t
.
Run
(
"CheckAgreementFails"
,
func
(
t
*
testing
.
T
)
{
detector
,
metrics
,
creator
,
rollup
:=
setupDetectorTest
(
t
)
rollup
.
err
=
errors
.
New
(
"boom"
)
creator
.
loader
=
&
mockMetadataLoader
{
status
:
types
.
GameStatusInProgress
}
detector
.
Detect
(
context
.
Background
(),
[]
types
.
GameMetadata
{{}})
metrics
.
Equals
(
t
,
1
,
0
,
0
)
// Status should still be metriced here!
metrics
.
Mapped
(
t
,
map
[
string
]
int
{})
})
t
.
Run
(
"SingleGame"
,
func
(
t
*
testing
.
T
)
{
detector
,
metrics
,
creator
,
_
:=
setupDetectorTest
(
t
)
loader
:=
&
mockMetadataLoader
{
status
:
types
.
GameStatusInProgress
}
creator
.
loader
=
loader
detector
.
Detect
(
context
.
Background
(),
[]
types
.
GameMetadata
{{}})
metrics
.
Equals
(
t
,
1
,
0
,
0
)
metrics
.
Mapped
(
t
,
map
[
string
]
int
{
"in_progress"
:
1
})
})
t
.
Run
(
"MultipleGames"
,
func
(
t
*
testing
.
T
)
{
detector
,
metrics
,
creator
,
_
:=
setupDetectorTest
(
t
)
loader
:=
&
mockMetadataLoader
{
status
:
types
.
GameStatusInProgress
}
creator
.
loader
=
loader
detector
.
Detect
(
context
.
Background
(),
[]
types
.
GameMetadata
{{},
{},
{}})
metrics
.
Equals
(
t
,
3
,
0
,
0
)
metrics
.
Mapped
(
t
,
map
[
string
]
int
{
"in_progress"
:
3
})
})
}
func
TestDetector_RecordBatch
(
t
*
testing
.
T
)
{
tests
:=
[]
struct
{
name
string
batch
detectionBatch
expect
func
(
*
testing
.
T
,
*
mockDetectorMetricer
)
}{
{
name
:
"no games"
,
batch
:
detectionBatch
{},
expect
:
func
(
t
*
testing
.
T
,
metrics
*
mockDetectorMetricer
)
{},
},
{
name
:
"in_progress"
,
batch
:
detectionBatch
{
inProgress
:
1
},
expect
:
func
(
t
*
testing
.
T
,
metrics
*
mockDetectorMetricer
)
{
require
.
Equal
(
t
,
1
,
metrics
.
gameAgreement
[
"in_progress"
])
},
},
{
name
:
"agree_defender_wins"
,
batch
:
detectionBatch
{
agreeDefenderWins
:
1
},
expect
:
func
(
t
*
testing
.
T
,
metrics
*
mockDetectorMetricer
)
{
require
.
Equal
(
t
,
1
,
metrics
.
gameAgreement
[
"agree_defender_wins"
])
},
},
{
name
:
"disagree_defender_wins"
,
batch
:
detectionBatch
{
disagreeDefenderWins
:
1
},
expect
:
func
(
t
*
testing
.
T
,
metrics
*
mockDetectorMetricer
)
{
require
.
Equal
(
t
,
1
,
metrics
.
gameAgreement
[
"disagree_defender_wins"
])
},
},
{
name
:
"agree_challenger_wins"
,
batch
:
detectionBatch
{
agreeChallengerWins
:
1
},
expect
:
func
(
t
*
testing
.
T
,
metrics
*
mockDetectorMetricer
)
{
require
.
Equal
(
t
,
1
,
metrics
.
gameAgreement
[
"agree_challenger_wins"
])
},
},
{
name
:
"disagree_challenger_wins"
,
batch
:
detectionBatch
{
disagreeChallengerWins
:
1
},
expect
:
func
(
t
*
testing
.
T
,
metrics
*
mockDetectorMetricer
)
{
require
.
Equal
(
t
,
1
,
metrics
.
gameAgreement
[
"disagree_challenger_wins"
])
},
},
}
for
_
,
test
:=
range
tests
{
test
:=
test
t
.
Run
(
test
.
name
,
func
(
t
*
testing
.
T
)
{
monitor
,
metrics
,
_
,
_
:=
setupDetectorTest
(
t
)
monitor
.
recordBatch
(
test
.
batch
)
test
.
expect
(
t
,
metrics
)
})
}
}
func
TestDetector_FetchGameMetadata
(
t
*
testing
.
T
)
{
t
.
Parallel
()
t
.
Run
(
"CreateContractFails"
,
func
(
t
*
testing
.
T
)
{
detector
,
_
,
creator
,
_
:=
setupDetectorTest
(
t
)
creator
.
err
=
errors
.
New
(
"boom"
)
_
,
_
,
_
,
err
:=
detector
.
fetchGameMetadata
(
context
.
Background
(),
types
.
GameMetadata
{})
require
.
ErrorIs
(
t
,
err
,
creator
.
err
)
})
t
.
Run
(
"GetGameMetadataFails"
,
func
(
t
*
testing
.
T
)
{
detector
,
_
,
creator
,
_
:=
setupDetectorTest
(
t
)
loader
:=
&
mockMetadataLoader
{
err
:
errors
.
New
(
"boom"
)}
creator
.
loader
=
loader
_
,
_
,
_
,
err
:=
detector
.
fetchGameMetadata
(
context
.
Background
(),
types
.
GameMetadata
{})
require
.
Error
(
t
,
err
)
})
t
.
Run
(
"Success"
,
func
(
t
*
testing
.
T
)
{
detector
,
_
,
creator
,
_
:=
setupDetectorTest
(
t
)
loader
:=
&
mockMetadataLoader
{
status
:
types
.
GameStatusInProgress
}
creator
.
loader
=
loader
_
,
_
,
status
,
err
:=
detector
.
fetchGameMetadata
(
context
.
Background
(),
types
.
GameMetadata
{})
require
.
NoError
(
t
,
err
)
require
.
Equal
(
t
,
types
.
GameStatusInProgress
,
status
)
})
}
func
TestDetector_CheckAgreement_Fails
(
t
*
testing
.
T
)
{
detector
,
_
,
_
,
rollup
:=
setupDetectorTest
(
t
)
rollup
.
err
=
errors
.
New
(
"boom"
)
_
,
err
:=
detector
.
checkAgreement
(
context
.
Background
(),
common
.
Address
{},
0
,
common
.
Hash
{},
types
.
GameStatusInProgress
)
require
.
ErrorIs
(
t
,
err
,
rollup
.
err
)
}
func
TestDetector_CheckAgreement_Succeeds
(
t
*
testing
.
T
)
{
tests
:=
[]
struct
{
name
string
rootClaim
common
.
Hash
status
types
.
GameStatus
expectBatch
func
(
*
detectionBatch
)
err
error
}{
{
name
:
"in_progress"
,
expectBatch
:
func
(
batch
*
detectionBatch
)
{
require
.
Equal
(
t
,
1
,
batch
.
inProgress
)
},
},
{
name
:
"agree_defender_wins"
,
rootClaim
:
mockRootClaim
,
status
:
types
.
GameStatusDefenderWon
,
expectBatch
:
func
(
batch
*
detectionBatch
)
{
require
.
Equal
(
t
,
1
,
batch
.
agreeDefenderWins
)
},
},
{
name
:
"disagree_defender_wins"
,
status
:
types
.
GameStatusDefenderWon
,
expectBatch
:
func
(
batch
*
detectionBatch
)
{
require
.
Equal
(
t
,
1
,
batch
.
disagreeDefenderWins
)
},
},
{
name
:
"agree_challenger_wins"
,
rootClaim
:
mockRootClaim
,
status
:
types
.
GameStatusChallengerWon
,
expectBatch
:
func
(
batch
*
detectionBatch
)
{
require
.
Equal
(
t
,
1
,
batch
.
agreeChallengerWins
)
},
},
{
name
:
"disagree_challenger_wins"
,
status
:
types
.
GameStatusChallengerWon
,
expectBatch
:
func
(
batch
*
detectionBatch
)
{
require
.
Equal
(
t
,
1
,
batch
.
disagreeChallengerWins
)
},
},
}
for
_
,
test
:=
range
tests
{
test
:=
test
t
.
Run
(
test
.
name
,
func
(
t
*
testing
.
T
)
{
detector
,
_
,
_
,
_
:=
setupDetectorTest
(
t
)
batch
,
err
:=
detector
.
checkAgreement
(
context
.
Background
(),
common
.
Address
{},
0
,
test
.
rootClaim
,
test
.
status
)
require
.
NoError
(
t
,
err
)
test
.
expectBatch
(
&
batch
)
})
}
}
func
TestDetector_CheckRootAgreement
(
t
*
testing
.
T
)
{
t
.
Parallel
()
t
.
Run
(
"OutputFetchFails"
,
func
(
t
*
testing
.
T
)
{
detector
,
_
,
_
,
rollup
:=
setupDetectorTest
(
t
)
rollup
.
err
=
errors
.
New
(
"boom"
)
agree
,
err
:=
detector
.
checkRootAgreement
(
context
.
Background
(),
0
,
mockRootClaim
)
require
.
ErrorIs
(
t
,
err
,
rollup
.
err
)
require
.
False
(
t
,
agree
)
})
t
.
Run
(
"OutputMismatch"
,
func
(
t
*
testing
.
T
)
{
detector
,
_
,
_
,
_
:=
setupDetectorTest
(
t
)
agree
,
err
:=
detector
.
checkRootAgreement
(
context
.
Background
(),
0
,
common
.
Hash
{})
require
.
NoError
(
t
,
err
)
require
.
False
(
t
,
agree
)
})
t
.
Run
(
"OutputMatches"
,
func
(
t
*
testing
.
T
)
{
detector
,
_
,
_
,
_
:=
setupDetectorTest
(
t
)
agree
,
err
:=
detector
.
checkRootAgreement
(
context
.
Background
(),
0
,
mockRootClaim
)
require
.
NoError
(
t
,
err
)
require
.
True
(
t
,
agree
)
})
}
func
setupDetectorTest
(
t
*
testing
.
T
)
(
*
detector
,
*
mockDetectorMetricer
,
*
mockMetadataCreator
,
*
stubRollupClient
)
{
logger
:=
testlog
.
Logger
(
t
,
log
.
LvlDebug
)
metrics
:=
&
mockDetectorMetricer
{}
loader
:=
&
mockMetadataLoader
{}
creator
:=
&
mockMetadataCreator
{
loader
:
loader
}
rollupClient
:=
&
stubRollupClient
{}
detector
:=
newDetector
(
logger
,
metrics
,
creator
,
rollupClient
)
return
detector
,
metrics
,
creator
,
rollupClient
}
type
stubRollupClient
struct
{
blockNum
uint64
err
error
}
func
(
s
*
stubRollupClient
)
OutputAtBlock
(
ctx
context
.
Context
,
blockNum
uint64
)
(
*
eth
.
OutputResponse
,
error
)
{
s
.
blockNum
=
blockNum
return
&
eth
.
OutputResponse
{
OutputRoot
:
eth
.
Bytes32
(
mockRootClaim
)},
s
.
err
}
type
mockMetadataCreator
struct
{
calls
int
err
error
loader
*
mockMetadataLoader
}
func
(
m
*
mockMetadataCreator
)
CreateContract
(
game
types
.
GameMetadata
)
(
MetadataLoader
,
error
)
{
m
.
calls
++
if
m
.
err
!=
nil
{
return
nil
,
m
.
err
}
return
m
.
loader
,
nil
}
type
mockMetadataLoader
struct
{
calls
int
status
types
.
GameStatus
err
error
}
func
(
m
*
mockMetadataLoader
)
GetGameMetadata
(
ctx
context
.
Context
)
(
uint64
,
common
.
Hash
,
types
.
GameStatus
,
error
)
{
m
.
calls
++
if
m
.
err
!=
nil
{
return
0
,
common
.
Hash
{},
m
.
status
,
m
.
err
}
return
0
,
common
.
Hash
{},
m
.
status
,
nil
}
type
mockDetectorMetricer
struct
{
inProgress
int
defenderWon
int
challengerWon
int
gameAgreement
map
[
string
]
int
}
func
(
m
*
mockDetectorMetricer
)
Equals
(
t
*
testing
.
T
,
inProgress
,
defenderWon
,
challengerWon
int
)
{
require
.
Equal
(
t
,
inProgress
,
m
.
inProgress
)
require
.
Equal
(
t
,
defenderWon
,
m
.
defenderWon
)
require
.
Equal
(
t
,
challengerWon
,
m
.
challengerWon
)
}
func
(
m
*
mockDetectorMetricer
)
Mapped
(
t
*
testing
.
T
,
expected
map
[
string
]
int
)
{
for
k
,
v
:=
range
m
.
gameAgreement
{
require
.
Equal
(
t
,
expected
[
k
],
v
)
}
}
func
(
m
*
mockDetectorMetricer
)
RecordGamesStatus
(
inProgress
,
defenderWon
,
challengerWon
int
)
{
m
.
inProgress
=
inProgress
m
.
defenderWon
=
defenderWon
m
.
challengerWon
=
challengerWon
}
func
(
m
*
mockDetectorMetricer
)
RecordGameAgreement
(
status
string
,
count
int
)
{
if
m
.
gameAgreement
==
nil
{
m
.
gameAgreement
=
make
(
map
[
string
]
int
)
}
m
.
gameAgreement
[
status
]
+=
count
}
op-dispute-mon/mon/monitor.go
View file @
fea26bd3
...
@@ -13,61 +13,48 @@ import (
...
@@ -13,61 +13,48 @@ import (
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/log"
)
)
type
blockNumberFetcher
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
type
Detect
func
(
ctx
context
.
Context
,
games
[]
types
.
GameMetadata
)
type
blockHashFetcher
func
(
ctx
context
.
Context
,
number
*
big
.
Int
)
(
common
.
Hash
,
error
)
type
BlockHashFetcher
func
(
ctx
context
.
Context
,
number
*
big
.
Int
)
(
common
.
Hash
,
error
)
type
BlockNumberFetcher
func
(
ctx
context
.
Context
)
(
uint64
,
error
)
// gameSource loads information about the games available to play
type
FactoryGameFetcher
func
(
ctx
context
.
Context
,
blockHash
common
.
Hash
,
earliestTimestamp
uint64
)
([]
types
.
GameMetadata
,
error
)
type
gameSource
interface
{
GetGamesAtOrAfter
(
ctx
context
.
Context
,
blockHash
common
.
Hash
,
earliestTimestamp
uint64
)
([]
types
.
GameMetadata
,
error
)
}
type
MonitorMetricer
interface
{
RecordGamesStatus
(
inProgress
,
defenderWon
,
challengerWon
int
)
}
type
MetadataCreator
interface
{
CreateContract
(
game
types
.
GameMetadata
)
(
MetadataLoader
,
error
)
}
type
gameMonitor
struct
{
type
gameMonitor
struct
{
logger
log
.
Logger
logger
log
.
Logger
metrics
MonitorMetricer
clock
clock
.
Clock
done
chan
struct
{}
ctx
context
.
Context
ctx
context
.
Context
cancel
context
.
CancelFunc
cancel
context
.
CancelFunc
clock
clock
.
Clock
gameWindow
time
.
Duration
monitorInterval
time
.
Duration
monitorInterval
time
.
Duration
done
chan
struct
{}
source
gameSource
detect
Detect
metadata
MetadataCreator
fetchGames
FactoryGameFetcher
gameWindow
time
.
Duration
fetchBlockHash
BlockHashFetcher
fetchBlockNumber
blockNumberFetcher
fetchBlockNumber
BlockNumberFetcher
fetchBlockHash
blockHashFetcher
}
}
func
newGameMonitor
(
func
newGameMonitor
(
ctx
context
.
Context
,
ctx
context
.
Context
,
logger
log
.
Logger
,
logger
log
.
Logger
,
metrics
MonitorMetricer
,
cl
clock
.
Clock
,
cl
clock
.
Clock
,
monitorInterval
time
.
Duration
,
monitorInterval
time
.
Duration
,
source
gameSource
,
metadata
MetadataCreator
,
gameWindow
time
.
Duration
,
gameWindow
time
.
Duration
,
fetchBlockNumber
blockNumberFetcher
,
detect
Detect
,
fetchBlockHash
blockHashFetcher
,
factory
FactoryGameFetcher
,
fetchBlockNumber
BlockNumberFetcher
,
fetchBlockHash
BlockHashFetcher
,
)
*
gameMonitor
{
)
*
gameMonitor
{
return
&
gameMonitor
{
return
&
gameMonitor
{
logger
:
logger
,
logger
:
logger
,
metrics
:
metrics
,
ctx
:
ctx
,
clock
:
cl
,
clock
:
cl
,
ctx
:
ctx
,
done
:
make
(
chan
struct
{}),
done
:
make
(
chan
struct
{}),
monitorInterval
:
monitorInterval
,
monitorInterval
:
monitorInterval
,
source
:
source
,
metadata
:
metadata
,
gameWindow
:
gameWindow
,
gameWindow
:
gameWindow
,
detect
:
detect
,
fetchGames
:
factory
,
fetchBlockNumber
:
fetchBlockNumber
,
fetchBlockNumber
:
fetchBlockNumber
,
fetchBlockHash
:
fetchBlockHash
,
fetchBlockHash
:
fetchBlockHash
,
}
}
...
@@ -95,36 +82,11 @@ func (m *gameMonitor) monitorGames() error {
...
@@ -95,36 +82,11 @@ func (m *gameMonitor) monitorGames() error {
if
err
!=
nil
{
if
err
!=
nil
{
return
fmt
.
Errorf
(
"Failed to fetch block hash: %w"
,
err
)
return
fmt
.
Errorf
(
"Failed to fetch block hash: %w"
,
err
)
}
}
games
,
err
:=
m
.
source
.
GetGamesAtOrAfter
(
m
.
ctx
,
blockHash
,
m
.
minGameTimestamp
())
games
,
err
:=
m
.
fetchGames
(
m
.
ctx
,
blockHash
,
m
.
minGameTimestamp
())
if
err
!=
nil
{
if
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to load games: %w"
,
err
)
return
fmt
.
Errorf
(
"failed to load games: %w"
,
err
)
}
}
return
m
.
recordGamesStatus
(
m
.
ctx
,
games
)
m
.
detect
(
m
.
ctx
,
games
)
}
func
(
m
*
gameMonitor
)
recordGamesStatus
(
ctx
context
.
Context
,
games
[]
types
.
GameMetadata
)
error
{
inProgress
,
defenderWon
,
challengerWon
:=
0
,
0
,
0
for
_
,
game
:=
range
games
{
loader
,
err
:=
m
.
metadata
.
CreateContract
(
game
)
if
err
!=
nil
{
m
.
logger
.
Error
(
"Failed to create contract"
,
"err"
,
err
)
continue
}
_
,
_
,
status
,
err
:=
loader
.
GetGameMetadata
(
ctx
)
if
err
!=
nil
{
m
.
logger
.
Error
(
"Failed to get game metadata"
,
"err"
,
err
)
continue
}
switch
status
{
case
types
.
GameStatusInProgress
:
inProgress
++
case
types
.
GameStatusDefenderWon
:
defenderWon
++
case
types
.
GameStatusChallengerWon
:
challengerWon
++
}
}
m
.
metrics
.
RecordGamesStatus
(
inProgress
,
defenderWon
,
challengerWon
)
return
nil
return
nil
}
}
...
...
op-dispute-mon/mon/monitor_test.go
View file @
fea26bd3
This diff is collapsed.
Click to expand it.
op-dispute-mon/mon/service.go
View file @
fea26bd3
...
@@ -21,6 +21,7 @@ import (
...
@@ -21,6 +21,7 @@ import (
"github.com/ethereum-optimism/optimism/op-service/httputil"
"github.com/ethereum-optimism/optimism/op-service/httputil"
opmetrics
"github.com/ethereum-optimism/optimism/op-service/metrics"
opmetrics
"github.com/ethereum-optimism/optimism/op-service/metrics"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
"github.com/ethereum-optimism/optimism/op-service/oppprof"
"github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
"github.com/ethereum-optimism/optimism/op-service/sources/batching"
)
)
...
@@ -33,7 +34,9 @@ type Service struct {
...
@@ -33,7 +34,9 @@ type Service struct {
cl
clock
.
Clock
cl
clock
.
Clock
metadata
*
metadataCreator
metadata
*
metadataCreator
rollupClient
*
sources
.
RollupClient
detector
*
detector
l1Client
*
ethclient
.
Client
l1Client
*
ethclient
.
Client
...
@@ -71,6 +74,10 @@ func (s *Service) initFromConfig(ctx context.Context, cfg *config.Config) error
...
@@ -71,6 +74,10 @@ func (s *Service) initFromConfig(ctx context.Context, cfg *config.Config) error
if
err
:=
s
.
initFactoryContract
(
cfg
);
err
!=
nil
{
if
err
:=
s
.
initFactoryContract
(
cfg
);
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to create factory contract bindings: %w"
,
err
)
return
fmt
.
Errorf
(
"failed to create factory contract bindings: %w"
,
err
)
}
}
if
err
:=
s
.
initOutputRollupClient
(
ctx
,
cfg
);
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to init rollup client: %w"
,
err
)
}
s
.
initDetector
()
s
.
initMetadataCreator
()
s
.
initMetadataCreator
()
s
.
initMonitor
(
ctx
,
cfg
)
s
.
initMonitor
(
ctx
,
cfg
)
...
@@ -80,6 +87,19 @@ func (s *Service) initFromConfig(ctx context.Context, cfg *config.Config) error
...
@@ -80,6 +87,19 @@ func (s *Service) initFromConfig(ctx context.Context, cfg *config.Config) error
return
nil
return
nil
}
}
func
(
s
*
Service
)
initDetector
()
{
s
.
detector
=
newDetector
(
s
.
logger
,
s
.
metrics
,
s
.
metadata
,
s
.
rollupClient
)
}
func
(
s
*
Service
)
initOutputRollupClient
(
ctx
context
.
Context
,
cfg
*
config
.
Config
)
error
{
outputRollupClient
,
err
:=
dial
.
DialRollupClientWithTimeout
(
ctx
,
dial
.
DefaultDialTimeout
,
s
.
logger
,
cfg
.
RollupRpc
)
if
err
!=
nil
{
return
fmt
.
Errorf
(
"failed to dial rollup client: %w"
,
err
)
}
s
.
rollupClient
=
outputRollupClient
return
nil
}
func
(
s
*
Service
)
initMetadataCreator
()
{
func
(
s
*
Service
)
initMetadataCreator
()
{
s
.
metadata
=
NewMetadataCreator
(
s
.
metrics
,
batching
.
NewMultiCaller
(
s
.
l1Client
.
Client
(),
batching
.
DefaultBatchSize
))
s
.
metadata
=
NewMetadataCreator
(
s
.
metrics
,
batching
.
NewMultiCaller
(
s
.
l1Client
.
Client
(),
batching
.
DefaultBatchSize
))
}
}
...
@@ -149,12 +169,11 @@ func (s *Service) initMonitor(ctx context.Context, cfg *config.Config) {
...
@@ -149,12 +169,11 @@ func (s *Service) initMonitor(ctx context.Context, cfg *config.Config) {
s
.
monitor
=
newGameMonitor
(
s
.
monitor
=
newGameMonitor
(
ctx
,
ctx
,
s
.
logger
,
s
.
logger
,
s
.
metrics
,
s
.
cl
,
s
.
cl
,
cfg
.
MonitorInterval
,
cfg
.
MonitorInterval
,
s
.
factoryContract
,
s
.
metadata
,
cfg
.
GameWindow
,
cfg
.
GameWindow
,
s
.
detector
.
Detect
,
s
.
factoryContract
.
GetGamesAtOrAfter
,
s
.
l1Client
.
BlockNumber
,
s
.
l1Client
.
BlockNumber
,
blockHashFetcher
,
blockHashFetcher
,
)
)
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment