Commit 631123a5 authored by Andreas Bigger's avatar Andreas Bigger Committed by Joshua Gutow

op-challenger: Track if the challenger agrees or disagrees with the output

parent 0564afcb
......@@ -38,7 +38,7 @@ func Main(logger log.Logger, cfg *config.Config) error {
gameDepth := 4
trace := fault.NewAlphabetProvider(cfg.AlphabetTrace, uint64(gameDepth))
agent := fault.NewAgent(loader, gameDepth, trace, responder, logger)
agent := fault.NewAgent(loader, gameDepth, trace, responder, cfg.AgreeWithProposedOutput, logger)
logger.Info("Fault game started")
......
package main
import (
"fmt"
"testing"
"github.com/ethereum-optimism/optimism/op-challenger/config"
......@@ -11,9 +12,10 @@ import (
)
var (
l1EthRpc = "http://example.com:8545"
gameAddressValue = "0xaa00000000000000000000000000000000000000"
alphabetTrace = "abcdefghijz"
l1EthRpc = "http://example.com:8545"
gameAddressValue = "0xaa00000000000000000000000000000000000000"
alphabetTrace = "abcdefghijz"
agreeWithProposedOutput = "true"
)
func TestLogLevel(t *testing.T) {
......@@ -33,12 +35,12 @@ func TestLogLevel(t *testing.T) {
func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs())
defaultCfg := config.NewConfig(l1EthRpc, common.HexToAddress(gameAddressValue), alphabetTrace)
defaultCfg := config.NewConfig(l1EthRpc, common.HexToAddress(gameAddressValue), alphabetTrace, true)
require.Equal(t, defaultCfg, cfg)
}
func TestDefaultConfigIsValid(t *testing.T) {
cfg := config.NewConfig(l1EthRpc, common.HexToAddress(gameAddressValue), alphabetTrace)
cfg := config.NewConfig(l1EthRpc, common.HexToAddress(gameAddressValue), alphabetTrace, true)
require.NoError(t, cfg.Check())
}
......@@ -89,6 +91,24 @@ func TestTxManagerFlagsSupported(t *testing.T) {
require.Equal(t, uint64(7), cfg.TxMgrConfig.NumConfirmations)
}
func TestAgreeWithProposedOutput(t *testing.T) {
t.Run("MustBeProvided", func(t *testing.T) {
verifyArgsInvalid(t, "flag agree-with-proposed-output is required", addRequiredArgsExcept("--agree-with-proposed-output"))
})
t.Run("Enabled", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs("--agree-with-proposed-output"))
require.True(t, cfg.AgreeWithProposedOutput)
})
t.Run("EnabledWithArg", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs("--agree-with-proposed-output=true"))
require.True(t, cfg.AgreeWithProposedOutput)
})
t.Run("Disabled", func(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs("--agree-with-proposed-output=false"))
require.False(t, cfg.AgreeWithProposedOutput)
})
}
func verifyArgsInvalid(t *testing.T, messageContains string, cliArgs []string) {
_, _, err := runWithArgs(cliArgs)
require.ErrorContains(t, err, messageContains)
......@@ -103,7 +123,7 @@ func configForArgs(t *testing.T, cliArgs []string) config.Config {
func runWithArgs(cliArgs []string) (log.Logger, config.Config, error) {
cfg := new(config.Config)
var logger log.Logger
fullArgs := append([]string{"op-program"}, cliArgs...)
fullArgs := append([]string{"op-challenger"}, cliArgs...)
err := run(fullArgs, func(log log.Logger, config *config.Config) error {
logger = log
cfg = config
......@@ -126,17 +146,17 @@ func addRequiredArgsExcept(name string, optionalArgs ...string) []string {
func requiredArgs() map[string]string {
return map[string]string{
"--l1-eth-rpc": l1EthRpc,
"--game-address": gameAddressValue,
"--alphabet": alphabetTrace,
"--agree-with-proposed-output": agreeWithProposedOutput,
"--l1-eth-rpc": l1EthRpc,
"--game-address": gameAddressValue,
"--alphabet": alphabetTrace,
}
}
func toArgList(req map[string]string) []string {
var combined []string
for name, value := range req {
combined = append(combined, name)
combined = append(combined, value)
combined = append(combined, fmt.Sprintf("%s=%s", name, value))
}
return combined
}
......@@ -21,22 +21,26 @@ var (
// This also contains config options for auxiliary services.
// It is used to initialize the challenger.
type Config struct {
L1EthRpc string // L1 RPC Url
GameAddress common.Address // Address of the fault game
AlphabetTrace string // String for the AlphabetTraceProvider
L1EthRpc string // L1 RPC Url
GameAddress common.Address // Address of the fault game
AlphabetTrace string // String for the AlphabetTraceProvider
AgreeWithProposedOutput bool // Temporary config if we agree or disagree with the posted output
TxMgrConfig txmgr.CLIConfig
}
func NewConfig(l1EthRpc string,
func NewConfig(
l1EthRpc string,
GameAddress common.Address,
AlphabetTrace string,
AgreeWithProposedOutput bool,
) Config {
return Config{
L1EthRpc: l1EthRpc,
GameAddress: GameAddress,
AlphabetTrace: AlphabetTrace,
TxMgrConfig: txmgr.NewCLIConfig(l1EthRpc),
L1EthRpc: l1EthRpc,
GameAddress: GameAddress,
AlphabetTrace: AlphabetTrace,
TxMgrConfig: txmgr.NewCLIConfig(l1EthRpc),
AgreeWithProposedOutput: AgreeWithProposedOutput,
}
}
......@@ -70,9 +74,10 @@ func NewConfigFromCLI(ctx *cli.Context) (*Config, error) {
return &Config{
// Required Flags
L1EthRpc: ctx.String(flags.L1EthRpcFlag.Name),
GameAddress: dgfAddress,
AlphabetTrace: ctx.String(flags.AlphabetFlag.Name),
TxMgrConfig: txMgrConfig,
L1EthRpc: ctx.String(flags.L1EthRpcFlag.Name),
GameAddress: dgfAddress,
AlphabetTrace: ctx.String(flags.AlphabetFlag.Name),
AgreeWithProposedOutput: ctx.Bool(flags.AgreeWithProposedOutputFlag.Name),
TxMgrConfig: txMgrConfig,
}, nil
}
......@@ -9,13 +9,14 @@ import (
)
var (
validL1EthRpc = "http://localhost:8545"
validGameAddress = common.HexToAddress("0x7bdd3b028C4796eF0EAf07d11394d0d9d8c24139")
validAlphabetTrace = "abcdefgh"
validL1EthRpc = "http://localhost:8545"
validGameAddress = common.HexToAddress("0x7bdd3b028C4796eF0EAf07d11394d0d9d8c24139")
validAlphabetTrace = "abcdefgh"
agreeWithProposedOutput = true
)
func validConfig() Config {
cfg := NewConfig(validL1EthRpc, validGameAddress, validAlphabetTrace)
cfg := NewConfig(validL1EthRpc, validGameAddress, validAlphabetTrace, agreeWithProposedOutput)
return cfg
}
......
......@@ -9,22 +9,24 @@ import (
)
type Agent struct {
solver *Solver
trace TraceProvider
loader Loader
responder Responder
maxDepth int
log log.Logger
solver *Solver
trace TraceProvider
loader Loader
responder Responder
maxDepth int
agreeWithProposedOutput bool
log log.Logger
}
func NewAgent(loader Loader, maxDepth int, trace TraceProvider, responder Responder, log log.Logger) Agent {
func NewAgent(loader Loader, maxDepth int, trace TraceProvider, responder Responder, agreeWithProposedOutput bool, log log.Logger) Agent {
return Agent{
solver: NewSolver(maxDepth, trace),
trace: trace,
loader: loader,
responder: responder,
maxDepth: maxDepth,
log: log,
solver: NewSolver(maxDepth, trace),
trace: trace,
loader: loader,
responder: responder,
maxDepth: maxDepth,
agreeWithProposedOutput: agreeWithProposedOutput,
log: log,
}
}
......@@ -59,7 +61,7 @@ func (a *Agent) newGameFromContracts(ctx context.Context) (Game, error) {
if len(claims) == 0 {
return nil, errors.New("no claims")
}
game := NewGameState(claims[0], uint64(a.maxDepth))
game := NewGameState(a.agreeWithProposedOutput, claims[0], uint64(a.maxDepth))
if err := game.PutAll(claims[1:]); err != nil {
return nil, fmt.Errorf("failed to load claims into the local state: %w", err)
}
......
......@@ -26,6 +26,6 @@ func FullGame() {
},
}
o := fault.NewOrchestrator(maxDepth, []fault.TraceProvider{canonicalProvider, disputedProvider}, []string{"charlie", "mallory"}, root)
o := fault.NewOrchestrator(maxDepth, []fault.TraceProvider{canonicalProvider, disputedProvider}, []string{"charlie", "mallory"}, []bool{false, true}, root)
o.Start()
}
......@@ -43,23 +43,37 @@ type extendedClaim struct {
// gameState is a struct that represents the state of a dispute game.
// The game state implements the [Game] interface.
type gameState struct {
root ClaimData
claims map[ClaimData]*extendedClaim
depth uint64
agreeWithProposedOutput bool
root ClaimData
claims map[ClaimData]*extendedClaim
depth uint64
}
// NewGameState returns a new game state.
// The provided [Claim] is used as the root node.
func NewGameState(root Claim, depth uint64) *gameState {
func NewGameState(agreeWithProposedOutput bool, root Claim, depth uint64) *gameState {
claims := make(map[ClaimData]*extendedClaim)
claims[root.ClaimData] = &extendedClaim{
self: root,
children: make([]ClaimData, 0),
}
return &gameState{
root: root.ClaimData,
claims: claims,
depth: depth,
agreeWithProposedOutput: agreeWithProposedOutput,
root: root.ClaimData,
claims: claims,
depth: depth,
}
}
// AgreeWithLevel returns if the game state agrees with the provided claim level.
func (g *gameState) AgreeWithClaimLevel(claim Claim) bool {
isOddLevel := claim.Depth()%2 == 1
// If we agree with the proposed output, we agree with odd levels
// If we disagree with the proposed output, we agree with the root claim level & even levels
if g.agreeWithProposedOutput {
return isOddLevel
} else {
return !isOddLevel
}
}
......@@ -79,7 +93,8 @@ func (g *gameState) Put(claim Claim) error {
if claim.IsRoot() || g.IsDuplicate(claim) {
return ErrClaimExists
}
if parent, ok := g.claims[claim.Parent]; !ok {
parent, ok := g.claims[claim.Parent]
if !ok {
return errors.New("no parent claim")
} else {
parent.children = append(parent.children, claim.ClaimData)
......
......@@ -48,7 +48,7 @@ func createTestClaims() (Claim, Claim, Claim, Claim) {
func TestIsDuplicate(t *testing.T) {
// Setup the game state.
root, top, middle, bottom := createTestClaims()
g := NewGameState(root, testMaxDepth)
g := NewGameState(false, root, testMaxDepth)
require.NoError(t, g.Put(top))
// Root + Top should be duplicates
......@@ -65,7 +65,7 @@ func TestIsDuplicate(t *testing.T) {
func TestGame_Put_RootAlreadyExists(t *testing.T) {
// Setup the game state.
top, _, _, _ := createTestClaims()
g := NewGameState(top, testMaxDepth)
g := NewGameState(false, top, testMaxDepth)
// Try to put the root claim into the game state again.
err := g.Put(top)
......@@ -77,7 +77,7 @@ func TestGame_Put_RootAlreadyExists(t *testing.T) {
func TestGame_PutAll_RootAlreadyExists(t *testing.T) {
// Setup the game state.
root, _, _, _ := createTestClaims()
g := NewGameState(root, testMaxDepth)
g := NewGameState(false, root, testMaxDepth)
// Try to put the root claim into the game state again.
err := g.PutAll([]Claim{root})
......@@ -88,7 +88,7 @@ func TestGame_PutAll_RootAlreadyExists(t *testing.T) {
// instance errors when the given claim already exists in state.
func TestGame_PutAll_AlreadyExists(t *testing.T) {
root, top, middle, bottom := createTestClaims()
g := NewGameState(root, testMaxDepth)
g := NewGameState(false, root, testMaxDepth)
err := g.PutAll([]Claim{top, middle})
require.NoError(t, err)
......@@ -101,7 +101,7 @@ func TestGame_PutAll_AlreadyExists(t *testing.T) {
func TestGame_PutAll_ParentsAndChildren(t *testing.T) {
// Setup the game state.
root, top, middle, bottom := createTestClaims()
g := NewGameState(root, testMaxDepth)
g := NewGameState(false, root, testMaxDepth)
// We should not be able to get the parent of the root claim.
parent, err := g.getParent(root)
......@@ -127,7 +127,7 @@ func TestGame_PutAll_ParentsAndChildren(t *testing.T) {
func TestGame_Put_AlreadyExists(t *testing.T) {
// Setup the game state.
top, middle, _, _ := createTestClaims()
g := NewGameState(top, testMaxDepth)
g := NewGameState(false, top, testMaxDepth)
// Put the next claim into state.
err := g.Put(middle)
......@@ -142,7 +142,7 @@ func TestGame_Put_AlreadyExists(t *testing.T) {
func TestGame_Put_ParentsAndChildren(t *testing.T) {
// Setup the game state.
root, top, middle, bottom := createTestClaims()
g := NewGameState(root, testMaxDepth)
g := NewGameState(false, root, testMaxDepth)
// We should not be able to get the parent of the root claim.
parent, err := g.getParent(root)
......@@ -175,7 +175,7 @@ func TestGame_Put_ParentsAndChildren(t *testing.T) {
func TestGame_ClaimPairs(t *testing.T) {
// Setup the game state.
root, top, middle, bottom := createTestClaims()
g := NewGameState(root, testMaxDepth)
g := NewGameState(false, root, testMaxDepth)
// Add top claim to the game state.
err := g.Put(top)
......@@ -199,7 +199,7 @@ func TestGame_ClaimPairs(t *testing.T) {
// those functions return an error.
func TestPrePostStateOnlyOnLeafClaim(t *testing.T) {
root, top, middle, bottom := createTestClaims()
g := NewGameState(root, testMaxDepth)
g := NewGameState(false, root, testMaxDepth)
require.NoError(t, g.PutAll([]Claim{top, middle, bottom}))
_, err := g.PreStateClaim(middle)
......@@ -210,7 +210,7 @@ func TestPrePostStateOnlyOnLeafClaim(t *testing.T) {
func TestPreStateClaim(t *testing.T) {
root, top, middle, bottom := createTestClaims()
g := NewGameState(root, testMaxDepth)
g := NewGameState(false, root, testMaxDepth)
require.NoError(t, g.Put(top))
require.NoError(t, g.Put(middle))
require.NoError(t, g.Put(bottom))
......@@ -224,7 +224,7 @@ func TestPreStateClaim(t *testing.T) {
func TestPostStateClaim(t *testing.T) {
root, top, middle, bottom := createTestClaims()
g := NewGameState(root, testMaxDepth)
g := NewGameState(false, root, testMaxDepth)
require.NoError(t, g.Put(top))
require.NoError(t, g.Put(middle))
require.NoError(t, g.Put(bottom))
......@@ -234,3 +234,27 @@ func TestPostStateClaim(t *testing.T) {
require.NoError(t, err)
require.Equal(t, middle, post)
}
func TestAgreeWithClaimLevelDisagreeWithOutput(t *testing.T) {
// Setup the game state.
root, top, middle, bottom := createTestClaims()
g := NewGameState(false, root, testMaxDepth)
require.NoError(t, g.PutAll([]Claim{top, middle, bottom}))
require.True(t, g.AgreeWithClaimLevel(root))
require.False(t, g.AgreeWithClaimLevel(top))
require.True(t, g.AgreeWithClaimLevel(middle))
require.False(t, g.AgreeWithClaimLevel(bottom))
}
func TestAgreeWithClaimLevelAgreeWithOutput(t *testing.T) {
// Setup the game state.
root, top, middle, bottom := createTestClaims()
g := NewGameState(true, root, testMaxDepth)
require.NoError(t, g.PutAll([]Claim{top, middle, bottom}))
require.False(t, g.AgreeWithClaimLevel(root))
require.True(t, g.AgreeWithClaimLevel(top))
require.False(t, g.AgreeWithClaimLevel(middle))
require.True(t, g.AgreeWithClaimLevel(bottom))
}
......@@ -15,7 +15,7 @@ type Orchestrator struct {
claimLen, stepLen, step int
}
func NewOrchestrator(maxDepth uint64, traces []TraceProvider, names []string, root Claim) Orchestrator {
func NewOrchestrator(maxDepth uint64, traces []TraceProvider, names []string, agreeWithProposedOutput []bool, root Claim) Orchestrator {
o := Orchestrator{
agents: make([]Agent, len(traces)),
claims: []Claim{root},
......@@ -23,7 +23,7 @@ func NewOrchestrator(maxDepth uint64, traces []TraceProvider, names []string, ro
}
log.Info("Starting game", "root_letter", string(root.Value[31:]))
for i, trace := range traces {
o.agents[i] = NewAgent(&o, int(maxDepth), trace, &o, log.New("role", names[i]))
o.agents[i] = NewAgent(&o, int(maxDepth), trace, &o, agreeWithProposedOutput[i], log.New("role", names[i]))
}
return o
}
......
......@@ -85,7 +85,7 @@ func TestAttemptStep(t *testing.T) {
canonicalProvider := NewAlphabetProvider("abcdefgh", uint64(maxDepth))
solver := NewSolver(maxDepth, canonicalProvider)
root, top, middle, bottom := createTestClaims()
g := NewGameState(root, testMaxDepth)
g := NewGameState(false, root, testMaxDepth)
require.NoError(t, g.Put(top))
require.NoError(t, g.Put(middle))
require.NoError(t, g.Put(bottom))
......
......@@ -33,6 +33,11 @@ var (
Usage: "Alphabet Trace (temporary)",
EnvVars: prefixEnvVars("ALPHABET"),
}
AgreeWithProposedOutputFlag = &cli.BoolFlag{
Name: "agree-with-proposed-output",
Usage: "Temporary hardcoded flag if we agree or disagree with the proposed output.",
EnvVars: prefixEnvVars("AGREE_WITH_PROPOSED_OUTPUT"),
}
// Optional Flags
)
......@@ -41,6 +46,7 @@ var requiredFlags = []cli.Flag{
L1EthRpcFlag,
DGFAddressFlag,
AlphabetFlag,
AgreeWithProposedOutputFlag,
}
// optionalFlags is a list of unchecked cli flags
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment