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
5edd8208
Unverified
Commit
5edd8208
authored
Dec 01, 2023
by
Adrian Sutton
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
challenger: Switch output_cannon to use the new contract type.
parent
918459c4
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
459 additions
and
79 deletions
+459
-79
register.go
op-challenger/game/fault/register.go
+5
-8
game_helper.go
op-e2e/e2eutils/disputegame/game_helper.go
+0
-20
helper.go
op-e2e/e2eutils/disputegame/helper.go
+25
-15
output_cannon_helper.go
op-e2e/e2eutils/disputegame/output_cannon_helper.go
+1
-33
output_game_helper.go
op-e2e/e2eutils/disputegame/output_game_helper.go
+418
-0
output_cannon_test.go
op-e2e/faultproofs/output_cannon_test.go
+10
-3
No files found.
op-challenger/game/fault/register.go
View file @
5edd8208
...
@@ -23,7 +23,7 @@ import (
...
@@ -23,7 +23,7 @@ import (
var
(
var
(
cannonGameType
=
uint8
(
0
)
cannonGameType
=
uint8
(
0
)
outputCannonGameType
=
uint8
(
0
)
// TODO(client-pod#260): Switch the output cannon game type to 1
outputCannonGameType
=
uint8
(
253
)
// TODO(client-pod#260): Switch the output cannon game type to 1
outputAlphabetGameType
=
uint8
(
254
)
outputAlphabetGameType
=
uint8
(
254
)
alphabetGameType
=
uint8
(
255
)
alphabetGameType
=
uint8
(
255
)
)
)
...
@@ -133,9 +133,7 @@ func registerOutputCannon(
...
@@ -133,9 +133,7 @@ func registerOutputCannon(
caller
*
batching
.
MultiCaller
,
caller
*
batching
.
MultiCaller
,
l2Client
cannon
.
L2HeaderSource
)
{
l2Client
cannon
.
L2HeaderSource
)
{
resourceCreator
:=
func
(
addr
common
.
Address
)
(
gameTypeResources
,
error
)
{
resourceCreator
:=
func
(
addr
common
.
Address
)
(
gameTypeResources
,
error
)
{
// Currently still using the old fault dispute game contracts for output_cannon
contract
,
err
:=
contracts
.
NewOutputBisectionGameContract
(
addr
,
caller
)
// as the output bisection+cannon contract isn't being deployed.
contract
,
err
:=
contracts
.
NewFaultDisputeGameContract
(
addr
,
caller
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
...
@@ -156,7 +154,7 @@ type outputCannonResources struct {
...
@@ -156,7 +154,7 @@ type outputCannonResources struct {
m
metrics
.
Metricer
m
metrics
.
Metricer
cfg
*
config
.
Config
cfg
*
config
.
Config
l2Client
cannon
.
L2HeaderSource
l2Client
cannon
.
L2HeaderSource
contract
*
contracts
.
FaultDisputeGameContract
// TODO(client-pod#260): Use the OutputBisectionGame
Contract
contract
*
contracts
.
OutputBisectionGame
Contract
}
}
func
(
r
*
outputCannonResources
)
Contract
()
GameContract
{
func
(
r
*
outputCannonResources
)
Contract
()
GameContract
{
...
@@ -165,12 +163,11 @@ func (r *outputCannonResources) Contract() GameContract {
...
@@ -165,12 +163,11 @@ func (r *outputCannonResources) Contract() GameContract {
func
(
r
*
outputCannonResources
)
CreateAccessor
(
ctx
context
.
Context
,
logger
log
.
Logger
,
gameDepth
uint64
,
dir
string
)
(
faultTypes
.
TraceAccessor
,
error
)
{
func
(
r
*
outputCannonResources
)
CreateAccessor
(
ctx
context
.
Context
,
logger
log
.
Logger
,
gameDepth
uint64
,
dir
string
)
(
faultTypes
.
TraceAccessor
,
error
)
{
// TODO(client-pod#44): Validate absolute pre-state for split games
// TODO(client-pod#44): Validate absolute pre-state for split games
// TODO(client-pod#43): Updated contracts should expose this as the pre and post state blocks
agreed
,
disputed
,
err
:=
r
.
contract
.
GetBlockRange
(
ctx
)
agreed
,
disputed
,
err
:=
r
.
contract
.
GetProposals
(
ctx
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
accessor
,
err
:=
outputs
.
NewOutputCannonTraceAccessor
(
ctx
,
logger
,
r
.
m
,
r
.
cfg
,
r
.
l2Client
,
r
.
contract
,
dir
,
gameDepth
,
agreed
.
L2BlockNumber
.
Uint64
(),
disputed
.
L2BlockNumber
.
Uint64
()
)
accessor
,
err
:=
outputs
.
NewOutputCannonTraceAccessor
(
ctx
,
logger
,
r
.
m
,
r
.
cfg
,
r
.
l2Client
,
r
.
contract
,
dir
,
gameDepth
,
agreed
,
disputed
)
if
err
!=
nil
{
if
err
!=
nil
{
return
nil
,
err
return
nil
,
err
}
}
...
...
op-e2e/e2eutils/disputegame/game_helper.go
View file @
5edd8208
...
@@ -17,8 +17,6 @@ import (
...
@@ -17,8 +17,6 @@ import (
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/require"
)
)
const
defaultTimeout
=
5
*
time
.
Minute
type
FaultGameHelper
struct
{
type
FaultGameHelper
struct
{
t
*
testing
.
T
t
*
testing
.
T
require
*
require
.
Assertions
require
*
require
.
Assertions
...
@@ -59,14 +57,6 @@ func (g *FaultGameHelper) WaitForClaimCount(ctx context.Context, count int64) {
...
@@ -59,14 +57,6 @@ func (g *FaultGameHelper) WaitForClaimCount(ctx context.Context, count int64) {
}
}
}
}
type
ContractClaim
struct
{
ParentIndex
uint32
Countered
bool
Claim
[
32
]
byte
Position
*
big
.
Int
Clock
*
big
.
Int
}
func
(
g
*
FaultGameHelper
)
MaxDepth
(
ctx
context
.
Context
)
int64
{
func
(
g
*
FaultGameHelper
)
MaxDepth
(
ctx
context
.
Context
)
int64
{
depth
,
err
:=
g
.
game
.
MAXGAMEDEPTH
(
&
bind
.
CallOpts
{
Context
:
ctx
})
depth
,
err
:=
g
.
game
.
MAXGAMEDEPTH
(
&
bind
.
CallOpts
{
Context
:
ctx
})
g
.
require
.
NoError
(
err
,
"Failed to load game depth"
)
g
.
require
.
NoError
(
err
,
"Failed to load game depth"
)
...
@@ -252,12 +242,6 @@ func (g *FaultGameHelper) WaitForInactivity(ctx context.Context, numInactiveBloc
...
@@ -252,12 +242,6 @@ func (g *FaultGameHelper) WaitForInactivity(ctx context.Context, numInactiveBloc
}
}
}
}
// Mover is a function that either attacks or defends the claim at parentClaimIdx
type
Mover
func
(
parentClaimIdx
int64
)
// Stepper is a function that attempts to perform a step against the claim at parentClaimIdx
type
Stepper
func
(
parentClaimIdx
int64
)
// DefendRootClaim uses the supplied Mover to perform moves in an attempt to defend the root claim.
// DefendRootClaim uses the supplied Mover to perform moves in an attempt to defend the root claim.
// It is assumed that the output root being disputed is valid and that an honest op-challenger is already running.
// It is assumed that the output root being disputed is valid and that an honest op-challenger is already running.
// When the game has reached the maximum depth it waits for the honest challenger to counter the leaf claim with step.
// When the game has reached the maximum depth it waits for the honest challenger to counter the leaf claim with step.
...
@@ -337,10 +321,6 @@ func (g *FaultGameHelper) Defend(ctx context.Context, claimIdx int64, claim comm
...
@@ -337,10 +321,6 @@ func (g *FaultGameHelper) Defend(ctx context.Context, claimIdx int64, claim comm
g
.
require
.
NoError
(
err
,
"Defend transaction was not OK"
)
g
.
require
.
NoError
(
err
,
"Defend transaction was not OK"
)
}
}
type
ErrWithData
interface
{
ErrorData
()
interface
{}
}
// StepFails attempts to call step and verifies that it fails with ValidStep()
// StepFails attempts to call step and verifies that it fails with ValidStep()
func
(
g
*
FaultGameHelper
)
StepFails
(
claimIdx
int64
,
isAttack
bool
,
stateData
[]
byte
,
proof
[]
byte
)
{
func
(
g
*
FaultGameHelper
)
StepFails
(
claimIdx
int64
,
isAttack
bool
,
stateData
[]
byte
,
proof
[]
byte
)
{
g
.
t
.
Logf
(
"Attempting step against claim %v isAttack: %v"
,
claimIdx
,
isAttack
)
g
.
t
.
Logf
(
"Attempting step against claim %v isAttack: %v"
,
claimIdx
,
isAttack
)
...
...
op-e2e/e2eutils/disputegame/helper.go
View file @
5edd8208
...
@@ -22,6 +22,7 @@ import (
...
@@ -22,6 +22,7 @@ import (
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/dial"
"github.com/ethereum-optimism/optimism/op-service/dial"
"github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common"
...
@@ -34,7 +35,7 @@ import (
...
@@ -34,7 +35,7 @@ import (
const
alphabetGameType
uint8
=
255
const
alphabetGameType
uint8
=
255
const
cannonGameType
uint8
=
0
const
cannonGameType
uint8
=
0
const
outputCannonGameType
uint8
=
0
// TODO(client-pod#43): This should be a unique game type
const
outputCannonGameType
uint8
=
253
const
alphabetGameDepth
=
4
const
alphabetGameDepth
=
4
var
lastAlphabetTraceIndex
=
big
.
NewInt
(
1
<<
alphabetGameDepth
-
1
)
var
lastAlphabetTraceIndex
=
big
.
NewInt
(
1
<<
alphabetGameDepth
-
1
)
...
@@ -140,7 +141,10 @@ func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet s
...
@@ -140,7 +141,10 @@ func (h *FactoryHelper) StartAlphabetGame(ctx context.Context, claimedAlphabet s
}
}
func
(
h
*
FactoryHelper
)
StartOutputCannonGame
(
ctx
context
.
Context
,
rollupEndpoint
string
,
rootClaim
common
.
Hash
)
*
OutputCannonGameHelper
{
func
(
h
*
FactoryHelper
)
StartOutputCannonGame
(
ctx
context
.
Context
,
rollupEndpoint
string
,
rootClaim
common
.
Hash
)
*
OutputCannonGameHelper
{
extraData
,
_
,
_
:=
h
.
createDisputeGameExtraData
(
ctx
)
rollupClient
,
err
:=
dial
.
DialRollupClientWithTimeout
(
ctx
,
30
*
time
.
Second
,
testlog
.
Logger
(
h
.
t
,
log
.
LvlInfo
),
rollupEndpoint
)
h
.
require
.
NoError
(
err
)
extraData
,
_
:=
h
.
createBisectionGameExtraData
(
ctx
,
rollupClient
)
ctx
,
cancel
:=
context
.
WithTimeout
(
ctx
,
1
*
time
.
Minute
)
ctx
,
cancel
:=
context
.
WithTimeout
(
ctx
,
1
*
time
.
Minute
)
defer
cancel
()
defer
cancel
()
...
@@ -154,23 +158,20 @@ func (h *FactoryHelper) StartOutputCannonGame(ctx context.Context, rollupEndpoin
...
@@ -154,23 +158,20 @@ func (h *FactoryHelper) StartOutputCannonGame(ctx context.Context, rollupEndpoin
h
.
require
.
Len
(
rcpt
.
Logs
,
1
,
"should have emitted a single DisputeGameCreated event"
)
h
.
require
.
Len
(
rcpt
.
Logs
,
1
,
"should have emitted a single DisputeGameCreated event"
)
createdEvent
,
err
:=
h
.
factory
.
ParseDisputeGameCreated
(
*
rcpt
.
Logs
[
0
])
createdEvent
,
err
:=
h
.
factory
.
ParseDisputeGameCreated
(
*
rcpt
.
Logs
[
0
])
h
.
require
.
NoError
(
err
)
h
.
require
.
NoError
(
err
)
game
,
err
:=
bindings
.
NewFaultDisputeGame
(
createdEvent
.
DisputeProxy
,
h
.
client
)
game
,
err
:=
bindings
.
NewOutputBisectionGame
(
createdEvent
.
DisputeProxy
,
h
.
client
)
h
.
require
.
NoError
(
err
)
rollupClient
,
err
:=
dial
.
DialRollupClientWithTimeout
(
ctx
,
30
*
time
.
Second
,
testlog
.
Logger
(
h
.
t
,
log
.
LvlInfo
),
rollupEndpoint
)
h
.
require
.
NoError
(
err
)
h
.
require
.
NoError
(
err
)
return
&
OutputCannonGameHelper
{
return
&
OutputCannonGameHelper
{
FaultGameHelper
:
FaultGameHelper
{
OutputGameHelper
:
OutputGameHelper
{
t
:
h
.
t
,
t
:
h
.
t
,
require
:
h
.
require
,
require
:
h
.
require
,
client
:
h
.
client
,
client
:
h
.
client
,
opts
:
h
.
opts
,
opts
:
h
.
opts
,
game
:
game
,
game
:
game
,
factoryAddr
:
h
.
factoryAddr
,
factoryAddr
:
h
.
factoryAddr
,
addr
:
createdEvent
.
DisputeProxy
,
addr
:
createdEvent
.
DisputeProxy
,
rollupClient
:
rollupClient
,
},
},
rollupClient
:
rollupClient
,
}
}
}
}
...
@@ -277,6 +278,15 @@ func (h *FactoryHelper) createCannonGame(ctx context.Context, rootClaim common.H
...
@@ -277,6 +278,15 @@ func (h *FactoryHelper) createCannonGame(ctx context.Context, rootClaim common.H
}
}
}
}
func
(
h
*
FactoryHelper
)
createBisectionGameExtraData
(
ctx
context
.
Context
,
client
*
sources
.
RollupClient
)
(
extraData
[]
byte
,
l2BlockNumber
uint64
)
{
syncStatus
,
err
:=
client
.
SyncStatus
(
ctx
)
h
.
require
.
NoError
(
err
,
"failed to get sync status"
)
l2BlockNumber
=
syncStatus
.
SafeL2
.
Number
extraData
=
make
([]
byte
,
32
)
binary
.
BigEndian
.
PutUint64
(
extraData
,
l2BlockNumber
)
return
}
func
(
h
*
FactoryHelper
)
createDisputeGameExtraData
(
ctx
context
.
Context
)
(
extraData
[]
byte
,
l1Head
*
big
.
Int
,
l2BlockNumber
uint64
)
{
func
(
h
*
FactoryHelper
)
createDisputeGameExtraData
(
ctx
context
.
Context
)
(
extraData
[]
byte
,
l1Head
*
big
.
Int
,
l2BlockNumber
uint64
)
{
l2BlockNumber
=
h
.
waitForProposals
(
ctx
)
l2BlockNumber
=
h
.
waitForProposals
(
ctx
)
l1Head
=
h
.
checkpointL1Block
(
ctx
)
l1Head
=
h
.
checkpointL1Block
(
ctx
)
...
...
op-e2e/e2eutils/disputegame/output_cannon_helper.go
View file @
5edd8208
...
@@ -2,19 +2,14 @@ package disputegame
...
@@ -2,19 +2,14 @@ package disputegame
import
(
import
(
"context"
"context"
"math/big"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core"
)
)
type
OutputCannonGameHelper
struct
{
type
OutputCannonGameHelper
struct
{
FaultGameHelper
OutputGameHelper
rollupClient
*
sources
.
RollupClient
}
}
func
(
g
*
OutputCannonGameHelper
)
StartChallenger
(
func
(
g
*
OutputCannonGameHelper
)
StartChallenger
(
...
@@ -39,30 +34,3 @@ func (g *OutputCannonGameHelper) StartChallenger(
...
@@ -39,30 +34,3 @@ func (g *OutputCannonGameHelper) StartChallenger(
})
})
return
c
return
c
}
}
func
(
g
*
OutputCannonGameHelper
)
WaitForCorrectOutputRoot
(
ctx
context
.
Context
,
claimIdx
int64
)
{
g
.
WaitForClaimCount
(
ctx
,
claimIdx
+
1
)
claim
:=
g
.
getClaim
(
ctx
,
claimIdx
)
err
,
blockNum
:=
g
.
blockNumForClaim
(
ctx
,
claim
)
g
.
require
.
NoError
(
err
)
output
,
err
:=
g
.
rollupClient
.
OutputAtBlock
(
ctx
,
blockNum
)
g
.
require
.
NoErrorf
(
err
,
"Failed to get output at block %v"
,
blockNum
)
g
.
require
.
EqualValuesf
(
output
.
OutputRoot
,
claim
.
Claim
,
"Incorrect output root at claim %v. Expected to be from block %v"
,
claimIdx
,
blockNum
)
}
func
(
g
*
OutputCannonGameHelper
)
blockNumForClaim
(
ctx
context
.
Context
,
claim
ContractClaim
)
(
error
,
uint64
)
{
proposals
,
err
:=
g
.
game
.
Proposals
(
&
bind
.
CallOpts
{
Context
:
ctx
})
g
.
require
.
NoError
(
err
,
"failed to retrieve proposals"
)
prestateBlockNum
:=
proposals
.
Starting
.
L2BlockNumber
disputedBlockNum
:=
proposals
.
Disputed
.
L2BlockNumber
gameDepth
:=
g
.
MaxDepth
(
ctx
)
// TODO(client-pod#43): Load this from the contract
topDepth
:=
gameDepth
/
2
traceIdx
:=
types
.
NewPositionFromGIndex
(
claim
.
Position
)
.
TraceIndex
(
int
(
topDepth
))
blockNum
:=
new
(
big
.
Int
)
.
Add
(
prestateBlockNum
,
traceIdx
)
.
Uint64
()
+
1
if
blockNum
>
disputedBlockNum
.
Uint64
()
{
blockNum
=
disputedBlockNum
.
Uint64
()
}
return
err
,
blockNum
}
op-e2e/e2eutils/disputegame/output_game_helper.go
0 → 100644
View file @
5edd8208
package
disputegame
import
(
"context"
"fmt"
"math/big"
"testing"
"time"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/wait"
"github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
gethtypes
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/ethclient"
"github.com/stretchr/testify/require"
)
const
defaultTimeout
=
5
*
time
.
Minute
type
OutputGameHelper
struct
{
t
*
testing
.
T
require
*
require
.
Assertions
client
*
ethclient
.
Client
opts
*
bind
.
TransactOpts
game
*
bindings
.
OutputBisectionGame
factoryAddr
common
.
Address
addr
common
.
Address
rollupClient
*
sources
.
RollupClient
}
func
(
g
*
OutputGameHelper
)
Addr
()
common
.
Address
{
return
g
.
addr
}
func
(
g
*
OutputGameHelper
)
SplitDepth
(
ctx
context
.
Context
)
int64
{
splitDepth
,
err
:=
g
.
game
.
SPLITDEPTH
(
&
bind
.
CallOpts
{
Context
:
ctx
})
g
.
require
.
NoError
(
err
,
"failed to load split depth"
)
return
splitDepth
.
Int64
()
}
func
(
g
*
OutputGameHelper
)
WaitForCorrectOutputRoot
(
ctx
context
.
Context
,
claimIdx
int64
)
{
g
.
WaitForClaimCount
(
ctx
,
claimIdx
+
1
)
claim
:=
g
.
getClaim
(
ctx
,
claimIdx
)
err
,
blockNum
:=
g
.
blockNumForClaim
(
ctx
,
claim
)
g
.
require
.
NoError
(
err
)
output
,
err
:=
g
.
rollupClient
.
OutputAtBlock
(
ctx
,
blockNum
)
g
.
require
.
NoErrorf
(
err
,
"Failed to get output at block %v"
,
blockNum
)
g
.
require
.
EqualValuesf
(
output
.
OutputRoot
,
claim
.
Claim
,
"Incorrect output root at claim %v. Expected to be from block %v"
,
claimIdx
,
blockNum
)
}
func
(
g
*
OutputGameHelper
)
blockNumForClaim
(
ctx
context
.
Context
,
claim
ContractClaim
)
(
error
,
uint64
)
{
opts
:=
&
bind
.
CallOpts
{
Context
:
ctx
}
prestateBlockNum
,
err
:=
g
.
game
.
GENESISBLOCKNUMBER
(
opts
)
g
.
require
.
NoError
(
err
,
"failed to retrieve proposals"
)
disputedBlockNum
,
err
:=
g
.
game
.
L2BlockNumber
(
opts
)
g
.
require
.
NoError
(
err
,
"failed to retrieve l2 block number"
)
topDepth
:=
g
.
SplitDepth
(
ctx
)
traceIdx
:=
types
.
NewPositionFromGIndex
(
claim
.
Position
)
.
TraceIndex
(
int
(
topDepth
))
blockNum
:=
new
(
big
.
Int
)
.
Add
(
prestateBlockNum
,
traceIdx
)
.
Uint64
()
+
1
if
blockNum
>
disputedBlockNum
.
Uint64
()
{
blockNum
=
disputedBlockNum
.
Uint64
()
}
return
err
,
blockNum
}
func
(
g
*
OutputGameHelper
)
GameDuration
(
ctx
context
.
Context
)
time
.
Duration
{
duration
,
err
:=
g
.
game
.
GAMEDURATION
(
&
bind
.
CallOpts
{
Context
:
ctx
})
g
.
require
.
NoError
(
err
,
"failed to get game duration"
)
return
time
.
Duration
(
duration
)
*
time
.
Second
}
// WaitForClaimCount waits until there are at least count claims in the game.
// This does not check that the number of claims is exactly the specified count to avoid intermittent failures
// where a challenger posts an additional claim before this method sees the number of claims it was waiting for.
func
(
g
*
OutputGameHelper
)
WaitForClaimCount
(
ctx
context
.
Context
,
count
int64
)
{
timedCtx
,
cancel
:=
context
.
WithTimeout
(
ctx
,
defaultTimeout
)
defer
cancel
()
err
:=
wait
.
For
(
timedCtx
,
time
.
Second
,
func
()
(
bool
,
error
)
{
actual
,
err
:=
g
.
game
.
ClaimDataLen
(
&
bind
.
CallOpts
{
Context
:
timedCtx
})
if
err
!=
nil
{
return
false
,
err
}
g
.
t
.
Log
(
"Waiting for claim count"
,
"current"
,
actual
,
"expected"
,
count
,
"game"
,
g
.
addr
)
return
actual
.
Cmp
(
big
.
NewInt
(
count
))
>=
0
,
nil
})
if
err
!=
nil
{
g
.
LogGameData
(
ctx
)
g
.
require
.
NoErrorf
(
err
,
"Did not find expected claim count %v"
,
count
)
}
}
type
ContractClaim
struct
{
ParentIndex
uint32
Countered
bool
Claim
[
32
]
byte
Position
*
big
.
Int
Clock
*
big
.
Int
}
func
(
g
*
OutputGameHelper
)
MaxDepth
(
ctx
context
.
Context
)
int64
{
depth
,
err
:=
g
.
game
.
MAXGAMEDEPTH
(
&
bind
.
CallOpts
{
Context
:
ctx
})
g
.
require
.
NoError
(
err
,
"Failed to load game depth"
)
return
depth
.
Int64
()
}
func
(
g
*
OutputGameHelper
)
waitForClaim
(
ctx
context
.
Context
,
errorMsg
string
,
predicate
func
(
claim
ContractClaim
)
bool
)
{
timedCtx
,
cancel
:=
context
.
WithTimeout
(
ctx
,
defaultTimeout
)
defer
cancel
()
err
:=
wait
.
For
(
timedCtx
,
time
.
Second
,
func
()
(
bool
,
error
)
{
count
,
err
:=
g
.
game
.
ClaimDataLen
(
&
bind
.
CallOpts
{
Context
:
timedCtx
})
if
err
!=
nil
{
return
false
,
fmt
.
Errorf
(
"retrieve number of claims: %w"
,
err
)
}
// Search backwards because the new claims are at the end and more likely the ones we want.
for
i
:=
count
.
Int64
()
-
1
;
i
>=
0
;
i
--
{
claimData
,
err
:=
g
.
game
.
ClaimData
(
&
bind
.
CallOpts
{
Context
:
timedCtx
},
big
.
NewInt
(
i
))
if
err
!=
nil
{
return
false
,
fmt
.
Errorf
(
"retrieve claim %v: %w"
,
i
,
err
)
}
if
predicate
(
claimData
)
{
return
true
,
nil
}
}
return
false
,
nil
})
if
err
!=
nil
{
// Avoid waiting time capturing game data when there's no error
g
.
require
.
NoErrorf
(
err
,
"%v
\n
%v"
,
errorMsg
,
g
.
gameData
(
ctx
))
}
}
func
(
g
*
OutputGameHelper
)
waitForNoClaim
(
ctx
context
.
Context
,
errorMsg
string
,
predicate
func
(
claim
ContractClaim
)
bool
)
{
timedCtx
,
cancel
:=
context
.
WithTimeout
(
ctx
,
defaultTimeout
)
defer
cancel
()
err
:=
wait
.
For
(
timedCtx
,
time
.
Second
,
func
()
(
bool
,
error
)
{
count
,
err
:=
g
.
game
.
ClaimDataLen
(
&
bind
.
CallOpts
{
Context
:
timedCtx
})
if
err
!=
nil
{
return
false
,
fmt
.
Errorf
(
"retrieve number of claims: %w"
,
err
)
}
// Search backwards because the new claims are at the end and more likely the ones we will fail on.
for
i
:=
count
.
Int64
()
-
1
;
i
>=
0
;
i
--
{
claimData
,
err
:=
g
.
game
.
ClaimData
(
&
bind
.
CallOpts
{
Context
:
timedCtx
},
big
.
NewInt
(
i
))
if
err
!=
nil
{
return
false
,
fmt
.
Errorf
(
"retrieve claim %v: %w"
,
i
,
err
)
}
if
predicate
(
claimData
)
{
return
false
,
nil
}
}
return
true
,
nil
})
if
err
!=
nil
{
// Avoid waiting time capturing game data when there's no error
g
.
require
.
NoErrorf
(
err
,
"%v
\n
%v"
,
errorMsg
,
g
.
gameData
(
ctx
))
}
}
func
(
g
*
OutputGameHelper
)
GetClaimValue
(
ctx
context
.
Context
,
claimIdx
int64
)
common
.
Hash
{
g
.
WaitForClaimCount
(
ctx
,
claimIdx
+
1
)
claim
:=
g
.
getClaim
(
ctx
,
claimIdx
)
return
claim
.
Claim
}
func
(
g
*
OutputGameHelper
)
GetClaimPosition
(
ctx
context
.
Context
,
claimIdx
int64
)
types
.
Position
{
g
.
WaitForClaimCount
(
ctx
,
claimIdx
+
1
)
claim
:=
g
.
getClaim
(
ctx
,
claimIdx
)
return
types
.
NewPositionFromGIndex
(
claim
.
Position
)
}
// getClaim retrieves the claim data for a specific index.
// Note that it is deliberately not exported as tests should use WaitForClaim to avoid race conditions.
func
(
g
*
OutputGameHelper
)
getClaim
(
ctx
context
.
Context
,
claimIdx
int64
)
ContractClaim
{
claimData
,
err
:=
g
.
game
.
ClaimData
(
&
bind
.
CallOpts
{
Context
:
ctx
},
big
.
NewInt
(
claimIdx
))
if
err
!=
nil
{
g
.
require
.
NoErrorf
(
err
,
"retrieve claim %v"
,
claimIdx
)
}
return
claimData
}
// getClaimPosition retrieves the [types.Position] of a claim at a specific index.
func
(
g
*
OutputGameHelper
)
getClaimPosition
(
ctx
context
.
Context
,
claimIdx
int64
)
types
.
Position
{
return
types
.
NewPositionFromGIndex
(
g
.
getClaim
(
ctx
,
claimIdx
)
.
Position
)
}
func
(
g
*
OutputGameHelper
)
WaitForClaimAtDepth
(
ctx
context
.
Context
,
depth
int
)
{
g
.
waitForClaim
(
ctx
,
fmt
.
Sprintf
(
"Could not find claim depth %v"
,
depth
),
func
(
claim
ContractClaim
)
bool
{
pos
:=
types
.
NewPositionFromGIndex
(
claim
.
Position
)
return
pos
.
Depth
()
==
depth
})
}
func
(
g
*
OutputGameHelper
)
WaitForClaimAtMaxDepth
(
ctx
context
.
Context
,
countered
bool
)
{
maxDepth
:=
g
.
MaxDepth
(
ctx
)
g
.
waitForClaim
(
ctx
,
fmt
.
Sprintf
(
"Could not find claim depth %v with countered=%v"
,
maxDepth
,
countered
),
func
(
claim
ContractClaim
)
bool
{
pos
:=
types
.
NewPositionFromGIndex
(
claim
.
Position
)
return
int64
(
pos
.
Depth
())
==
maxDepth
&&
claim
.
Countered
==
countered
})
}
func
(
g
*
OutputGameHelper
)
WaitForAllClaimsCountered
(
ctx
context
.
Context
)
{
g
.
waitForNoClaim
(
ctx
,
"Did not find all claims countered"
,
func
(
claim
ContractClaim
)
bool
{
return
!
claim
.
Countered
})
}
func
(
g
*
OutputGameHelper
)
Resolve
(
ctx
context
.
Context
)
{
ctx
,
cancel
:=
context
.
WithTimeout
(
ctx
,
time
.
Minute
)
defer
cancel
()
tx
,
err
:=
g
.
game
.
Resolve
(
g
.
opts
)
g
.
require
.
NoError
(
err
)
_
,
err
=
wait
.
ForReceiptOK
(
ctx
,
g
.
client
,
tx
.
Hash
())
g
.
require
.
NoError
(
err
)
}
func
(
g
*
OutputGameHelper
)
Status
(
ctx
context
.
Context
)
Status
{
status
,
err
:=
g
.
game
.
Status
(
&
bind
.
CallOpts
{
Context
:
ctx
})
g
.
require
.
NoError
(
err
)
return
Status
(
status
)
}
func
(
g
*
OutputGameHelper
)
WaitForGameStatus
(
ctx
context
.
Context
,
expected
Status
)
{
g
.
t
.
Logf
(
"Waiting for game %v to have status %v"
,
g
.
addr
,
expected
)
timedCtx
,
cancel
:=
context
.
WithTimeout
(
ctx
,
defaultTimeout
)
defer
cancel
()
err
:=
wait
.
For
(
timedCtx
,
time
.
Second
,
func
()
(
bool
,
error
)
{
ctx
,
cancel
:=
context
.
WithTimeout
(
timedCtx
,
30
*
time
.
Second
)
defer
cancel
()
status
,
err
:=
g
.
game
.
Status
(
&
bind
.
CallOpts
{
Context
:
ctx
})
if
err
!=
nil
{
return
false
,
fmt
.
Errorf
(
"game status unavailable: %w"
,
err
)
}
g
.
t
.
Logf
(
"Game %v has state %v, waiting for state %v"
,
g
.
addr
,
Status
(
status
),
expected
)
return
expected
==
Status
(
status
),
nil
})
g
.
require
.
NoErrorf
(
err
,
"wait for game status. Game state:
\n
%v"
,
g
.
gameData
(
ctx
))
}
func
(
g
*
OutputGameHelper
)
WaitForInactivity
(
ctx
context
.
Context
,
numInactiveBlocks
int
,
untilGameEnds
bool
)
{
g
.
t
.
Logf
(
"Waiting for game %v to have no activity for %v blocks"
,
g
.
addr
,
numInactiveBlocks
)
headCh
:=
make
(
chan
*
gethtypes
.
Header
,
100
)
headSub
,
err
:=
g
.
client
.
SubscribeNewHead
(
ctx
,
headCh
)
g
.
require
.
NoError
(
err
)
defer
headSub
.
Unsubscribe
()
var
lastActiveBlock
uint64
for
{
if
untilGameEnds
&&
g
.
Status
(
ctx
)
!=
StatusInProgress
{
break
}
select
{
case
head
:=
<-
headCh
:
if
lastActiveBlock
==
0
{
lastActiveBlock
=
head
.
Number
.
Uint64
()
continue
}
else
if
lastActiveBlock
+
uint64
(
numInactiveBlocks
)
<
head
.
Number
.
Uint64
()
{
return
}
block
,
err
:=
g
.
client
.
BlockByNumber
(
ctx
,
head
.
Number
)
g
.
require
.
NoError
(
err
)
numActions
:=
0
for
_
,
tx
:=
range
block
.
Transactions
()
{
if
tx
.
To
()
.
Hex
()
==
g
.
addr
.
Hex
()
{
numActions
++
}
}
if
numActions
!=
0
{
g
.
t
.
Logf
(
"Game %v has %v actions in block %d. Resetting inactivity timeout"
,
g
.
addr
,
numActions
,
block
.
NumberU64
())
lastActiveBlock
=
head
.
Number
.
Uint64
()
}
case
err
:=
<-
headSub
.
Err
()
:
g
.
require
.
NoError
(
err
)
case
<-
ctx
.
Done
()
:
g
.
require
.
Fail
(
"Context canceled"
,
ctx
.
Err
())
}
}
}
// Mover is a function that either attacks or defends the claim at parentClaimIdx
type
Mover
func
(
parentClaimIdx
int64
)
// Stepper is a function that attempts to perform a step against the claim at parentClaimIdx
type
Stepper
func
(
parentClaimIdx
int64
)
// DefendRootClaim uses the supplied Mover to perform moves in an attempt to defend the root claim.
// It is assumed that the output root being disputed is valid and that an honest op-challenger is already running.
// When the game has reached the maximum depth it waits for the honest challenger to counter the leaf claim with step.
func
(
g
*
OutputGameHelper
)
DefendRootClaim
(
ctx
context
.
Context
,
performMove
Mover
)
{
maxDepth
:=
g
.
MaxDepth
(
ctx
)
for
claimCount
:=
int64
(
1
);
claimCount
<
maxDepth
;
{
g
.
LogGameData
(
ctx
)
claimCount
++
// Wait for the challenger to counter
g
.
WaitForClaimCount
(
ctx
,
claimCount
)
// Respond with our own move
performMove
(
claimCount
-
1
)
claimCount
++
g
.
WaitForClaimCount
(
ctx
,
claimCount
)
}
// Wait for the challenger to call step and counter our invalid claim
g
.
WaitForClaimAtMaxDepth
(
ctx
,
true
)
}
// ChallengeRootClaim uses the supplied Mover and Stepper to perform moves and steps in an attempt to challenge the root claim.
// It is assumed that the output root being disputed is invalid and that an honest op-challenger is already running.
// When the game has reached the maximum depth it calls the Stepper to attempt to counter the leaf claim.
// Since the output root is invalid, it should not be possible for the Stepper to call step successfully.
func
(
g
*
OutputGameHelper
)
ChallengeRootClaim
(
ctx
context
.
Context
,
performMove
Mover
,
attemptStep
Stepper
)
{
maxDepth
:=
g
.
MaxDepth
(
ctx
)
for
claimCount
:=
int64
(
1
);
claimCount
<
maxDepth
;
{
g
.
LogGameData
(
ctx
)
// Perform our move
performMove
(
claimCount
-
1
)
claimCount
++
g
.
WaitForClaimCount
(
ctx
,
claimCount
)
// Wait for the challenger to counter
claimCount
++
g
.
WaitForClaimCount
(
ctx
,
claimCount
)
}
// Confirm the game has reached max depth and the last claim hasn't been countered
g
.
WaitForClaimAtMaxDepth
(
ctx
,
false
)
g
.
LogGameData
(
ctx
)
// It's on us to call step if we want to win but shouldn't be possible
attemptStep
(
maxDepth
)
}
func
(
g
*
OutputGameHelper
)
WaitForNewClaim
(
ctx
context
.
Context
,
checkPoint
int64
)
(
int64
,
error
)
{
return
g
.
waitForNewClaim
(
ctx
,
checkPoint
,
defaultTimeout
)
}
func
(
g
*
OutputGameHelper
)
waitForNewClaim
(
ctx
context
.
Context
,
checkPoint
int64
,
timeout
time
.
Duration
)
(
int64
,
error
)
{
timedCtx
,
cancel
:=
context
.
WithTimeout
(
ctx
,
timeout
)
defer
cancel
()
var
newClaimLen
int64
err
:=
wait
.
For
(
timedCtx
,
time
.
Second
,
func
()
(
bool
,
error
)
{
actual
,
err
:=
g
.
game
.
ClaimDataLen
(
&
bind
.
CallOpts
{
Context
:
ctx
})
if
err
!=
nil
{
return
false
,
err
}
newClaimLen
=
actual
.
Int64
()
return
actual
.
Cmp
(
big
.
NewInt
(
checkPoint
))
>
0
,
nil
})
return
newClaimLen
,
err
}
func
(
g
*
OutputGameHelper
)
Attack
(
ctx
context
.
Context
,
claimIdx
int64
,
claim
common
.
Hash
)
{
tx
,
err
:=
g
.
game
.
Attack
(
g
.
opts
,
big
.
NewInt
(
claimIdx
),
claim
)
g
.
require
.
NoError
(
err
,
"Attack transaction did not send"
)
_
,
err
=
wait
.
ForReceiptOK
(
ctx
,
g
.
client
,
tx
.
Hash
())
g
.
require
.
NoError
(
err
,
"Attack transaction was not OK"
)
}
func
(
g
*
OutputGameHelper
)
Defend
(
ctx
context
.
Context
,
claimIdx
int64
,
claim
common
.
Hash
)
{
tx
,
err
:=
g
.
game
.
Defend
(
g
.
opts
,
big
.
NewInt
(
claimIdx
),
claim
)
g
.
require
.
NoError
(
err
,
"Defend transaction did not send"
)
_
,
err
=
wait
.
ForReceiptOK
(
ctx
,
g
.
client
,
tx
.
Hash
())
g
.
require
.
NoError
(
err
,
"Defend transaction was not OK"
)
}
type
ErrWithData
interface
{
ErrorData
()
interface
{}
}
// StepFails attempts to call step and verifies that it fails with ValidStep()
func
(
g
*
OutputGameHelper
)
StepFails
(
claimIdx
int64
,
isAttack
bool
,
stateData
[]
byte
,
proof
[]
byte
)
{
g
.
t
.
Logf
(
"Attempting step against claim %v isAttack: %v"
,
claimIdx
,
isAttack
)
_
,
err
:=
g
.
game
.
Step
(
g
.
opts
,
big
.
NewInt
(
claimIdx
),
isAttack
,
stateData
,
proof
)
errData
,
ok
:=
err
.
(
ErrWithData
)
g
.
require
.
Truef
(
ok
,
"Error should provide ErrorData method: %v"
,
err
)
g
.
require
.
Equal
(
"0xfb4e40dd"
,
errData
.
ErrorData
(),
"Revert reason should be abi encoded ValidStep()"
)
}
// ResolveClaim resolves a single subgame
func
(
g
*
OutputGameHelper
)
ResolveClaim
(
ctx
context
.
Context
,
claimIdx
int64
)
{
tx
,
err
:=
g
.
game
.
ResolveClaim
(
g
.
opts
,
big
.
NewInt
(
claimIdx
))
g
.
require
.
NoError
(
err
,
"ResolveClaim transaction did not send"
)
_
,
err
=
wait
.
ForReceiptOK
(
ctx
,
g
.
client
,
tx
.
Hash
())
g
.
require
.
NoError
(
err
,
"ResolveClaim transaction was not OK"
)
}
func
(
g
*
OutputGameHelper
)
gameData
(
ctx
context
.
Context
)
string
{
opts
:=
&
bind
.
CallOpts
{
Context
:
ctx
}
maxDepth
:=
int
(
g
.
MaxDepth
(
ctx
))
splitDepth
:=
int
(
g
.
SplitDepth
(
ctx
))
claimCount
,
err
:=
g
.
game
.
ClaimDataLen
(
opts
)
info
:=
fmt
.
Sprintf
(
"Claim count: %v
\n
"
,
claimCount
)
g
.
require
.
NoError
(
err
,
"Fetching claim count"
)
for
i
:=
int64
(
0
);
i
<
claimCount
.
Int64
();
i
++
{
claim
,
err
:=
g
.
game
.
ClaimData
(
opts
,
big
.
NewInt
(
i
))
g
.
require
.
NoErrorf
(
err
,
"Fetch claim %v"
,
i
)
pos
:=
types
.
NewPositionFromGIndex
(
claim
.
Position
)
info
=
info
+
fmt
.
Sprintf
(
"%v - Position: %v, Depth: %v, IndexAtDepth: %v Trace Index: %v, Value: %v, Countered: %v, ParentIndex: %v
\n
"
,
i
,
claim
.
Position
.
Int64
(),
pos
.
Depth
(),
pos
.
IndexAtDepth
(),
pos
.
TraceIndex
(
maxDepth
),
common
.
Hash
(
claim
.
Claim
)
.
Hex
(),
claim
.
Countered
,
claim
.
ParentIndex
)
}
status
,
err
:=
g
.
game
.
Status
(
opts
)
g
.
require
.
NoError
(
err
,
"Load game status"
)
return
fmt
.
Sprintf
(
"Game %v - %v - Split Depth: %v - Max Depth: %v:
\n
%v
\n
"
,
g
.
addr
,
Status
(
status
),
splitDepth
,
maxDepth
,
info
)
}
func
(
g
*
OutputGameHelper
)
LogGameData
(
ctx
context
.
Context
)
{
g
.
t
.
Log
(
g
.
gameData
(
ctx
))
}
op-e2e/faultproofs/output_cannon_test.go
View file @
5edd8208
...
@@ -31,12 +31,19 @@ func TestOutputCannonGame(t *testing.T) {
...
@@ -31,12 +31,19 @@ func TestOutputCannonGame(t *testing.T) {
)
)
game
.
LogGameData
(
ctx
)
game
.
LogGameData
(
ctx
)
maxDepth
:=
game
.
MaxDepth
(
ctx
)
// Challenger should post an output root to counter claims down to the leaf level of the top game
// Challenger should post an output root to counter claims down to the leaf level of the top game
// TODO(client-pod#43): Load the depth of the top game from the contract instead of deriving it
splitDepth
:=
game
.
SplitDepth
(
ctx
)
for
i
:=
int64
(
1
);
i
<
=
maxDepth
/
2
+
1
;
i
+=
2
{
for
i
:=
int64
(
1
);
i
<
splitDepth
;
i
+=
2
{
game
.
WaitForCorrectOutputRoot
(
ctx
,
i
)
game
.
WaitForCorrectOutputRoot
(
ctx
,
i
)
game
.
Attack
(
ctx
,
i
,
common
.
Hash
{
0xaa
})
game
.
Attack
(
ctx
,
i
,
common
.
Hash
{
0xaa
})
game
.
LogGameData
(
ctx
)
game
.
LogGameData
(
ctx
)
}
}
game
.
WaitForCorrectOutputRoot
(
ctx
,
splitDepth
)
// Post the first cannon output root (with 01 status code to show the output root is invalid)
game
.
Attack
(
ctx
,
splitDepth
,
common
.
Hash
{
0x01
})
// Challenger should counter
game
.
WaitForClaimAtDepth
(
ctx
,
int
(
splitDepth
+
2
))
game
.
LogGameData
(
ctx
)
}
}
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