Commit 86c30689 authored by mergify[bot]'s avatar mergify[bot] Committed by GitHub

Merge branch 'develop' into inphi/fpvm-specs

parents 5a9ac9f7 edb93245
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
...@@ -38,7 +38,7 @@ func Main(logger log.Logger, cfg *config.Config) error { ...@@ -38,7 +38,7 @@ func Main(logger log.Logger, cfg *config.Config) error {
gameDepth := 4 gameDepth := 4
trace := fault.NewAlphabetProvider(cfg.AlphabetTrace, uint64(gameDepth)) 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") logger.Info("Fault game started")
......
...@@ -14,4 +14,4 @@ MALLORY_KEY="28d7045146193f5f4eeb151c4843544b1b0d30a7ac1680c845a416fac65a7715" ...@@ -14,4 +14,4 @@ MALLORY_KEY="28d7045146193f5f4eeb151c4843544b1b0d30a7ac1680c845a416fac65a7715"
FAULT_GAME_ADDRESS="0x8daf17a20c9dba35f005b6324f493785d239719d" FAULT_GAME_ADDRESS="0x8daf17a20c9dba35f005b6324f493785d239719d"
./bin/op-challenger --l1-eth-rpc http://localhost:8545 --alphabet "abcdefgh" --game-address $FAULT_GAME_ADDRESS --private-key $CHARLIE_KEY --num-confirmations 1 ./bin/op-challenger --l1-eth-rpc http://localhost:8545 --alphabet "abcdefgh" --game-address $FAULT_GAME_ADDRESS --private-key $CHARLIE_KEY --num-confirmations 1 --agree-with-proposed-output=true
package main package main
import ( import (
"fmt"
"testing" "testing"
"github.com/ethereum-optimism/optimism/op-challenger/config" "github.com/ethereum-optimism/optimism/op-challenger/config"
...@@ -11,9 +12,10 @@ import ( ...@@ -11,9 +12,10 @@ import (
) )
var ( var (
l1EthRpc = "http://example.com:8545" l1EthRpc = "http://example.com:8545"
gameAddressValue = "0xaa00000000000000000000000000000000000000" gameAddressValue = "0xaa00000000000000000000000000000000000000"
alphabetTrace = "abcdefghijz" alphabetTrace = "abcdefghijz"
agreeWithProposedOutput = "true"
) )
func TestLogLevel(t *testing.T) { func TestLogLevel(t *testing.T) {
...@@ -33,12 +35,12 @@ func TestLogLevel(t *testing.T) { ...@@ -33,12 +35,12 @@ func TestLogLevel(t *testing.T) {
func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) { func TestDefaultCLIOptionsMatchDefaultConfig(t *testing.T) {
cfg := configForArgs(t, addRequiredArgs()) 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) require.Equal(t, defaultCfg, cfg)
} }
func TestDefaultConfigIsValid(t *testing.T) { 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()) require.NoError(t, cfg.Check())
} }
...@@ -89,6 +91,24 @@ func TestTxManagerFlagsSupported(t *testing.T) { ...@@ -89,6 +91,24 @@ func TestTxManagerFlagsSupported(t *testing.T) {
require.Equal(t, uint64(7), cfg.TxMgrConfig.NumConfirmations) 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) { func verifyArgsInvalid(t *testing.T, messageContains string, cliArgs []string) {
_, _, err := runWithArgs(cliArgs) _, _, err := runWithArgs(cliArgs)
require.ErrorContains(t, err, messageContains) require.ErrorContains(t, err, messageContains)
...@@ -103,7 +123,7 @@ func configForArgs(t *testing.T, cliArgs []string) config.Config { ...@@ -103,7 +123,7 @@ func configForArgs(t *testing.T, cliArgs []string) config.Config {
func runWithArgs(cliArgs []string) (log.Logger, config.Config, error) { func runWithArgs(cliArgs []string) (log.Logger, config.Config, error) {
cfg := new(config.Config) cfg := new(config.Config)
var logger log.Logger 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 { err := run(fullArgs, func(log log.Logger, config *config.Config) error {
logger = log logger = log
cfg = config cfg = config
...@@ -126,17 +146,17 @@ func addRequiredArgsExcept(name string, optionalArgs ...string) []string { ...@@ -126,17 +146,17 @@ func addRequiredArgsExcept(name string, optionalArgs ...string) []string {
func requiredArgs() map[string]string { func requiredArgs() map[string]string {
return map[string]string{ return map[string]string{
"--l1-eth-rpc": l1EthRpc, "--agree-with-proposed-output": agreeWithProposedOutput,
"--game-address": gameAddressValue, "--l1-eth-rpc": l1EthRpc,
"--alphabet": alphabetTrace, "--game-address": gameAddressValue,
"--alphabet": alphabetTrace,
} }
} }
func toArgList(req map[string]string) []string { func toArgList(req map[string]string) []string {
var combined []string var combined []string
for name, value := range req { for name, value := range req {
combined = append(combined, name) combined = append(combined, fmt.Sprintf("%s=%s", name, value))
combined = append(combined, value)
} }
return combined return combined
} }
...@@ -21,22 +21,26 @@ var ( ...@@ -21,22 +21,26 @@ var (
// This also contains config options for auxiliary services. // This also contains config options for auxiliary services.
// It is used to initialize the challenger. // It is used to initialize the challenger.
type Config struct { type Config struct {
L1EthRpc string // L1 RPC Url L1EthRpc string // L1 RPC Url
GameAddress common.Address // Address of the fault game GameAddress common.Address // Address of the fault game
AlphabetTrace string // String for the AlphabetTraceProvider AlphabetTrace string // String for the AlphabetTraceProvider
AgreeWithProposedOutput bool // Temporary config if we agree or disagree with the posted output
TxMgrConfig txmgr.CLIConfig TxMgrConfig txmgr.CLIConfig
} }
func NewConfig(l1EthRpc string, func NewConfig(
l1EthRpc string,
GameAddress common.Address, GameAddress common.Address,
AlphabetTrace string, AlphabetTrace string,
AgreeWithProposedOutput bool,
) Config { ) Config {
return Config{ return Config{
L1EthRpc: l1EthRpc, L1EthRpc: l1EthRpc,
GameAddress: GameAddress, GameAddress: GameAddress,
AlphabetTrace: AlphabetTrace, AlphabetTrace: AlphabetTrace,
TxMgrConfig: txmgr.NewCLIConfig(l1EthRpc), TxMgrConfig: txmgr.NewCLIConfig(l1EthRpc),
AgreeWithProposedOutput: AgreeWithProposedOutput,
} }
} }
...@@ -70,9 +74,10 @@ func NewConfigFromCLI(ctx *cli.Context) (*Config, error) { ...@@ -70,9 +74,10 @@ func NewConfigFromCLI(ctx *cli.Context) (*Config, error) {
return &Config{ return &Config{
// Required Flags // Required Flags
L1EthRpc: ctx.String(flags.L1EthRpcFlag.Name), L1EthRpc: ctx.String(flags.L1EthRpcFlag.Name),
GameAddress: dgfAddress, GameAddress: dgfAddress,
AlphabetTrace: ctx.String(flags.AlphabetFlag.Name), AlphabetTrace: ctx.String(flags.AlphabetFlag.Name),
TxMgrConfig: txMgrConfig, AgreeWithProposedOutput: ctx.Bool(flags.AgreeWithProposedOutputFlag.Name),
TxMgrConfig: txMgrConfig,
}, nil }, nil
} }
...@@ -9,13 +9,14 @@ import ( ...@@ -9,13 +9,14 @@ import (
) )
var ( var (
validL1EthRpc = "http://localhost:8545" validL1EthRpc = "http://localhost:8545"
validGameAddress = common.HexToAddress("0x7bdd3b028C4796eF0EAf07d11394d0d9d8c24139") validGameAddress = common.HexToAddress("0x7bdd3b028C4796eF0EAf07d11394d0d9d8c24139")
validAlphabetTrace = "abcdefgh" validAlphabetTrace = "abcdefgh"
agreeWithProposedOutput = true
) )
func validConfig() Config { func validConfig() Config {
cfg := NewConfig(validL1EthRpc, validGameAddress, validAlphabetTrace) cfg := NewConfig(validL1EthRpc, validGameAddress, validAlphabetTrace, agreeWithProposedOutput)
return cfg return cfg
} }
......
...@@ -9,22 +9,24 @@ import ( ...@@ -9,22 +9,24 @@ import (
) )
type Agent struct { type Agent struct {
solver *Solver solver *Solver
trace TraceProvider trace TraceProvider
loader Loader loader Loader
responder Responder responder Responder
maxDepth int maxDepth int
log log.Logger 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{ return Agent{
solver: NewSolver(maxDepth, trace), solver: NewSolver(maxDepth, trace),
trace: trace, trace: trace,
loader: loader, loader: loader,
responder: responder, responder: responder,
maxDepth: maxDepth, maxDepth: maxDepth,
log: log, agreeWithProposedOutput: agreeWithProposedOutput,
log: log,
} }
} }
...@@ -59,7 +61,7 @@ func (a *Agent) newGameFromContracts(ctx context.Context) (Game, error) { ...@@ -59,7 +61,7 @@ func (a *Agent) newGameFromContracts(ctx context.Context) (Game, error) {
if len(claims) == 0 { if len(claims) == 0 {
return nil, errors.New("no claims") 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 { if err := game.PutAll(claims[1:]); err != nil {
return nil, fmt.Errorf("failed to load claims into the local state: %w", err) return nil, fmt.Errorf("failed to load claims into the local state: %w", err)
} }
...@@ -68,7 +70,7 @@ func (a *Agent) newGameFromContracts(ctx context.Context) (Game, error) { ...@@ -68,7 +70,7 @@ func (a *Agent) newGameFromContracts(ctx context.Context) (Game, error) {
// move determines & executes the next move given a claim // move determines & executes the next move given a claim
func (a *Agent) move(claim Claim, game Game) error { func (a *Agent) move(claim Claim, game Game) error {
nextMove, err := a.solver.NextMove(claim) nextMove, err := a.solver.NextMove(claim, game.AgreeWithClaimLevel(claim))
if err != nil { if err != nil {
a.log.Warn("Failed to execute the next move", "err", err) a.log.Warn("Failed to execute the next move", "err", err)
return err return err
......
...@@ -26,6 +26,6 @@ func FullGame() { ...@@ -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() o.Start()
} }
...@@ -53,19 +53,19 @@ func SolverExampleOne() { ...@@ -53,19 +53,19 @@ func SolverExampleOne() {
fmt.Println() fmt.Println()
PrettyPrintAlphabetClaim("Root claim", root) PrettyPrintAlphabetClaim("Root claim", root)
claim1, err := cannonicalSolver.NextMove(root) claim1, err := cannonicalSolver.NextMove(root, false)
if err != nil { if err != nil {
fmt.Printf("error getting claim from provider: %v", err) fmt.Printf("error getting claim from provider: %v", err)
} }
PrettyPrintAlphabetClaim("Cannonical move", *claim1) PrettyPrintAlphabetClaim("Cannonical move", *claim1)
claim2, err := disputedSolver.NextMove(*claim1) claim2, err := disputedSolver.NextMove(*claim1, false)
if err != nil { if err != nil {
fmt.Printf("error getting claim from provider: %v", err) fmt.Printf("error getting claim from provider: %v", err)
} }
PrettyPrintAlphabetClaim("Disputed moved", *claim2) PrettyPrintAlphabetClaim("Disputed moved", *claim2)
claim3, err := cannonicalSolver.NextMove(*claim2) claim3, err := cannonicalSolver.NextMove(*claim2, false)
if err != nil { if err != nil {
fmt.Printf("error getting claim from provider: %v", err) fmt.Printf("error getting claim from provider: %v", err)
} }
......
...@@ -33,6 +33,9 @@ type Game interface { ...@@ -33,6 +33,9 @@ type Game interface {
// PostStateClaim gets the claim which commits to the post-state of this specific claim. // PostStateClaim gets the claim which commits to the post-state of this specific claim.
// This will return an error if it is called with a non-leaf claim. // This will return an error if it is called with a non-leaf claim.
PostStateClaim(claim Claim) (Claim, error) PostStateClaim(claim Claim) (Claim, error)
// AgreeWithLevel returns if the game state agrees with the provided claim level.
AgreeWithClaimLevel(claim Claim) bool
} }
type extendedClaim struct { type extendedClaim struct {
...@@ -43,23 +46,37 @@ type extendedClaim struct { ...@@ -43,23 +46,37 @@ type extendedClaim struct {
// gameState is a struct that represents the state of a dispute game. // gameState is a struct that represents the state of a dispute game.
// The game state implements the [Game] interface. // The game state implements the [Game] interface.
type gameState struct { type gameState struct {
root ClaimData agreeWithProposedOutput bool
claims map[ClaimData]*extendedClaim root ClaimData
depth uint64 claims map[ClaimData]*extendedClaim
depth uint64
} }
// NewGameState returns a new game state. // NewGameState returns a new game state.
// The provided [Claim] is used as the root node. // 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 := make(map[ClaimData]*extendedClaim)
claims[root.ClaimData] = &extendedClaim{ claims[root.ClaimData] = &extendedClaim{
self: root, self: root,
children: make([]ClaimData, 0), children: make([]ClaimData, 0),
} }
return &gameState{ return &gameState{
root: root.ClaimData, agreeWithProposedOutput: agreeWithProposedOutput,
claims: claims, root: root.ClaimData,
depth: depth, 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 +96,8 @@ func (g *gameState) Put(claim Claim) error { ...@@ -79,7 +96,8 @@ func (g *gameState) Put(claim Claim) error {
if claim.IsRoot() || g.IsDuplicate(claim) { if claim.IsRoot() || g.IsDuplicate(claim) {
return ErrClaimExists return ErrClaimExists
} }
if parent, ok := g.claims[claim.Parent]; !ok { parent, ok := g.claims[claim.Parent]
if !ok {
return errors.New("no parent claim") return errors.New("no parent claim")
} else { } else {
parent.children = append(parent.children, claim.ClaimData) parent.children = append(parent.children, claim.ClaimData)
......
...@@ -48,7 +48,7 @@ func createTestClaims() (Claim, Claim, Claim, Claim) { ...@@ -48,7 +48,7 @@ func createTestClaims() (Claim, Claim, Claim, Claim) {
func TestIsDuplicate(t *testing.T) { func TestIsDuplicate(t *testing.T) {
// Setup the game state. // Setup the game state.
root, top, middle, bottom := createTestClaims() 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(top))
// Root + Top should be duplicates // Root + Top should be duplicates
...@@ -65,7 +65,7 @@ func TestIsDuplicate(t *testing.T) { ...@@ -65,7 +65,7 @@ func TestIsDuplicate(t *testing.T) {
func TestGame_Put_RootAlreadyExists(t *testing.T) { func TestGame_Put_RootAlreadyExists(t *testing.T) {
// Setup the game state. // Setup the game state.
top, _, _, _ := createTestClaims() top, _, _, _ := createTestClaims()
g := NewGameState(top, testMaxDepth) g := NewGameState(false, top, testMaxDepth)
// Try to put the root claim into the game state again. // Try to put the root claim into the game state again.
err := g.Put(top) err := g.Put(top)
...@@ -77,7 +77,7 @@ func TestGame_Put_RootAlreadyExists(t *testing.T) { ...@@ -77,7 +77,7 @@ func TestGame_Put_RootAlreadyExists(t *testing.T) {
func TestGame_PutAll_RootAlreadyExists(t *testing.T) { func TestGame_PutAll_RootAlreadyExists(t *testing.T) {
// Setup the game state. // Setup the game state.
root, _, _, _ := createTestClaims() root, _, _, _ := createTestClaims()
g := NewGameState(root, testMaxDepth) g := NewGameState(false, root, testMaxDepth)
// Try to put the root claim into the game state again. // Try to put the root claim into the game state again.
err := g.PutAll([]Claim{root}) err := g.PutAll([]Claim{root})
...@@ -88,7 +88,7 @@ func TestGame_PutAll_RootAlreadyExists(t *testing.T) { ...@@ -88,7 +88,7 @@ func TestGame_PutAll_RootAlreadyExists(t *testing.T) {
// instance errors when the given claim already exists in state. // instance errors when the given claim already exists in state.
func TestGame_PutAll_AlreadyExists(t *testing.T) { func TestGame_PutAll_AlreadyExists(t *testing.T) {
root, top, middle, bottom := createTestClaims() root, top, middle, bottom := createTestClaims()
g := NewGameState(root, testMaxDepth) g := NewGameState(false, root, testMaxDepth)
err := g.PutAll([]Claim{top, middle}) err := g.PutAll([]Claim{top, middle})
require.NoError(t, err) require.NoError(t, err)
...@@ -101,7 +101,7 @@ func TestGame_PutAll_AlreadyExists(t *testing.T) { ...@@ -101,7 +101,7 @@ func TestGame_PutAll_AlreadyExists(t *testing.T) {
func TestGame_PutAll_ParentsAndChildren(t *testing.T) { func TestGame_PutAll_ParentsAndChildren(t *testing.T) {
// Setup the game state. // Setup the game state.
root, top, middle, bottom := createTestClaims() 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. // We should not be able to get the parent of the root claim.
parent, err := g.getParent(root) parent, err := g.getParent(root)
...@@ -127,7 +127,7 @@ func TestGame_PutAll_ParentsAndChildren(t *testing.T) { ...@@ -127,7 +127,7 @@ func TestGame_PutAll_ParentsAndChildren(t *testing.T) {
func TestGame_Put_AlreadyExists(t *testing.T) { func TestGame_Put_AlreadyExists(t *testing.T) {
// Setup the game state. // Setup the game state.
top, middle, _, _ := createTestClaims() top, middle, _, _ := createTestClaims()
g := NewGameState(top, testMaxDepth) g := NewGameState(false, top, testMaxDepth)
// Put the next claim into state. // Put the next claim into state.
err := g.Put(middle) err := g.Put(middle)
...@@ -142,7 +142,7 @@ func TestGame_Put_AlreadyExists(t *testing.T) { ...@@ -142,7 +142,7 @@ func TestGame_Put_AlreadyExists(t *testing.T) {
func TestGame_Put_ParentsAndChildren(t *testing.T) { func TestGame_Put_ParentsAndChildren(t *testing.T) {
// Setup the game state. // Setup the game state.
root, top, middle, bottom := createTestClaims() 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. // We should not be able to get the parent of the root claim.
parent, err := g.getParent(root) parent, err := g.getParent(root)
...@@ -175,7 +175,7 @@ func TestGame_Put_ParentsAndChildren(t *testing.T) { ...@@ -175,7 +175,7 @@ func TestGame_Put_ParentsAndChildren(t *testing.T) {
func TestGame_ClaimPairs(t *testing.T) { func TestGame_ClaimPairs(t *testing.T) {
// Setup the game state. // Setup the game state.
root, top, middle, bottom := createTestClaims() root, top, middle, bottom := createTestClaims()
g := NewGameState(root, testMaxDepth) g := NewGameState(false, root, testMaxDepth)
// Add top claim to the game state. // Add top claim to the game state.
err := g.Put(top) err := g.Put(top)
...@@ -199,7 +199,7 @@ func TestGame_ClaimPairs(t *testing.T) { ...@@ -199,7 +199,7 @@ func TestGame_ClaimPairs(t *testing.T) {
// those functions return an error. // those functions return an error.
func TestPrePostStateOnlyOnLeafClaim(t *testing.T) { func TestPrePostStateOnlyOnLeafClaim(t *testing.T) {
root, top, middle, bottom := createTestClaims() root, top, middle, bottom := createTestClaims()
g := NewGameState(root, testMaxDepth) g := NewGameState(false, root, testMaxDepth)
require.NoError(t, g.PutAll([]Claim{top, middle, bottom})) require.NoError(t, g.PutAll([]Claim{top, middle, bottom}))
_, err := g.PreStateClaim(middle) _, err := g.PreStateClaim(middle)
...@@ -210,7 +210,7 @@ func TestPrePostStateOnlyOnLeafClaim(t *testing.T) { ...@@ -210,7 +210,7 @@ func TestPrePostStateOnlyOnLeafClaim(t *testing.T) {
func TestPreStateClaim(t *testing.T) { func TestPreStateClaim(t *testing.T) {
root, top, middle, bottom := createTestClaims() 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(top))
require.NoError(t, g.Put(middle)) require.NoError(t, g.Put(middle))
require.NoError(t, g.Put(bottom)) require.NoError(t, g.Put(bottom))
...@@ -224,7 +224,7 @@ func TestPreStateClaim(t *testing.T) { ...@@ -224,7 +224,7 @@ func TestPreStateClaim(t *testing.T) {
func TestPostStateClaim(t *testing.T) { func TestPostStateClaim(t *testing.T) {
root, top, middle, bottom := createTestClaims() 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(top))
require.NoError(t, g.Put(middle)) require.NoError(t, g.Put(middle))
require.NoError(t, g.Put(bottom)) require.NoError(t, g.Put(bottom))
...@@ -234,3 +234,27 @@ func TestPostStateClaim(t *testing.T) { ...@@ -234,3 +234,27 @@ func TestPostStateClaim(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, middle, post) 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 { ...@@ -15,7 +15,7 @@ type Orchestrator struct {
claimLen, stepLen, step int 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{ o := Orchestrator{
agents: make([]Agent, len(traces)), agents: make([]Agent, len(traces)),
claims: []Claim{root}, claims: []Claim{root},
...@@ -23,7 +23,7 @@ func NewOrchestrator(maxDepth uint64, traces []TraceProvider, names []string, ro ...@@ -23,7 +23,7 @@ func NewOrchestrator(maxDepth uint64, traces []TraceProvider, names []string, ro
} }
log.Info("Starting game", "root_letter", string(root.Value[31:])) log.Info("Starting game", "root_letter", string(root.Value[31:]))
for i, trace := range traces { 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 return o
} }
......
...@@ -21,7 +21,11 @@ func NewSolver(gameDepth int, traceProvider TraceProvider) *Solver { ...@@ -21,7 +21,11 @@ func NewSolver(gameDepth int, traceProvider TraceProvider) *Solver {
} }
// NextMove returns the next move to make given the current state of the game. // NextMove returns the next move to make given the current state of the game.
func (s *Solver) NextMove(claim Claim) (*Claim, error) { func (s *Solver) NextMove(claim Claim, agreeWithClaimLevel bool) (*Claim, error) {
if agreeWithClaimLevel {
return nil, nil
}
// Special case of the root claim // Special case of the root claim
if claim.IsRoot() { if claim.IsRoot() {
return s.handleRoot(claim) return s.handleRoot(claim)
...@@ -29,6 +33,36 @@ func (s *Solver) NextMove(claim Claim) (*Claim, error) { ...@@ -29,6 +33,36 @@ func (s *Solver) NextMove(claim Claim) (*Claim, error) {
return s.handleMiddle(claim) return s.handleMiddle(claim)
} }
func (s *Solver) handleRoot(claim Claim) (*Claim, error) {
agree, err := s.agreeWithClaim(claim.ClaimData)
if err != nil {
return nil, err
}
// Attack the root claim if we do not agree with it
// Note: We always disagree with the claim level at this point,
// so if we agree with claim maybe we should also attack?
if !agree {
return s.attack(claim)
} else {
return nil, nil
}
}
func (s *Solver) handleMiddle(claim Claim) (*Claim, error) {
claimCorrect, err := s.agreeWithClaim(claim.ClaimData)
if err != nil {
return nil, err
}
if claim.Depth() == s.gameDepth {
return nil, errors.New("game depth reached")
}
if claimCorrect {
return s.defend(claim)
} else {
return s.attack(claim)
}
}
type StepData struct { type StepData struct {
LeafClaim Claim LeafClaim Claim
IsAttack bool IsAttack bool
...@@ -57,52 +91,6 @@ func (s *Solver) AttemptStep(claim Claim) (StepData, error) { ...@@ -57,52 +91,6 @@ func (s *Solver) AttemptStep(claim Claim) (StepData, error) {
}, nil }, nil
} }
func (s *Solver) handleRoot(claim Claim) (*Claim, error) {
agree, err := s.agreeWithClaim(claim.ClaimData)
if err != nil {
return nil, err
}
// Attack the root claim if we do not agree with it
if !agree {
return s.attack(claim)
} else {
return nil, nil
}
}
func (s *Solver) handleMiddle(claim Claim) (*Claim, error) {
parentCorrect, err := s.agreeWithClaim(claim.Parent)
if err != nil {
return nil, err
}
claimCorrect, err := s.agreeWithClaim(claim.ClaimData)
if err != nil {
return nil, err
}
if claim.Depth() == s.gameDepth {
return nil, errors.New("game depth reached")
}
if parentCorrect && claimCorrect {
// We agree with the parent, but the claim is disagreeing with it.
// Since we agree with the claim, the difference must be to the right of the claim
return s.defend(claim)
} else if parentCorrect && !claimCorrect {
// We agree with the parent, but the claim disagrees with it.
// Since we disagree with the claim, the difference must be to the left of the claim
return s.attack(claim)
} else if !parentCorrect && claimCorrect {
// Do nothing, we disagree with the parent, but this claim has correctly countered it
return nil, nil
} else if !parentCorrect && !claimCorrect {
// We disagree with the parent so want to counter it (which the claim is doing)
// but we also disagree with the claim so there must be a difference to the left of claim
// Note that we will create the correct counter-claim for parent when it is evaluated, no need to do it here
return s.attack(claim)
}
// This should not be reached
return nil, errors.New("no next move")
}
// attack returns a response that attacks the claim. // attack returns a response that attacks the claim.
func (s *Solver) attack(claim Claim) (*Claim, error) { func (s *Solver) attack(claim Claim) (*Claim, error) {
position := claim.Attack() position := claim.Attack()
......
...@@ -74,18 +74,36 @@ func TestSolver_NextMove_Opponent(t *testing.T) { ...@@ -74,18 +74,36 @@ func TestSolver_NextMove_Opponent(t *testing.T) {
} }
for _, test := range indices { for _, test := range indices {
res, err := solver.NextMove(test.claim) res, err := solver.NextMove(test.claim, false)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, test.response, res.ClaimData) require.Equal(t, test.response, res.ClaimData)
} }
} }
func TestNoMoveAgainstOwnLevel(t *testing.T) {
maxDepth := 3
mallory := NewAlphabetProvider("abcdepqr", uint64(maxDepth))
solver := NewSolver(maxDepth, mallory)
claim := Claim{
ClaimData: ClaimData{
Value: alphabetClaim(7, "z"),
Position: NewPosition(0, 0),
},
// Root claim has no parent
}
move, err := solver.NextMove(claim, true)
require.Nil(t, move)
require.Nil(t, err)
}
func TestAttemptStep(t *testing.T) { func TestAttemptStep(t *testing.T) {
maxDepth := 3 maxDepth := 3
canonicalProvider := NewAlphabetProvider("abcdefgh", uint64(maxDepth)) canonicalProvider := NewAlphabetProvider("abcdefgh", uint64(maxDepth))
solver := NewSolver(maxDepth, canonicalProvider) solver := NewSolver(maxDepth, canonicalProvider)
root, top, middle, bottom := createTestClaims() 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(top))
require.NoError(t, g.Put(middle)) require.NoError(t, g.Put(middle))
require.NoError(t, g.Put(bottom)) require.NoError(t, g.Put(bottom))
......
...@@ -33,6 +33,11 @@ var ( ...@@ -33,6 +33,11 @@ var (
Usage: "Alphabet Trace (temporary)", Usage: "Alphabet Trace (temporary)",
EnvVars: prefixEnvVars("ALPHABET"), 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 // Optional Flags
) )
...@@ -41,6 +46,7 @@ var requiredFlags = []cli.Flag{ ...@@ -41,6 +46,7 @@ var requiredFlags = []cli.Flag{
L1EthRpcFlag, L1EthRpcFlag,
DGFAddressFlag, DGFAddressFlag,
AlphabetFlag, AlphabetFlag,
AgreeWithProposedOutputFlag,
} }
// optionalFlags is a list of unchecked cli flags // optionalFlags is a list of unchecked cli flags
......
...@@ -15,4 +15,4 @@ MALLORY_KEY="28d7045146193f5f4eeb151c4843544b1b0d30a7ac1680c845a416fac65a7715" ...@@ -15,4 +15,4 @@ MALLORY_KEY="28d7045146193f5f4eeb151c4843544b1b0d30a7ac1680c845a416fac65a7715"
FAULT_GAME_ADDRESS="0x8daf17a20c9dba35f005b6324f493785d239719d" FAULT_GAME_ADDRESS="0x8daf17a20c9dba35f005b6324f493785d239719d"
./bin/op-challenger --l1-eth-rpc http://localhost:8545 --alphabet "abcdexyz" --game-address $FAULT_GAME_ADDRESS --private-key $MALLORY_KEY --num-confirmations 1 ./bin/op-challenger --l1-eth-rpc http://localhost:8545 --alphabet "abcdexyz" --game-address $FAULT_GAME_ADDRESS --private-key $MALLORY_KEY --num-confirmations 1 --agree-with-proposed-output=false
...@@ -27,7 +27,7 @@ type Frame struct { ...@@ -27,7 +27,7 @@ type Frame struct {
ID ChannelID `json:"id"` ID ChannelID `json:"id"`
FrameNumber uint16 `json:"frame_number"` FrameNumber uint16 `json:"frame_number"`
Data []byte `json:"data"` Data []byte `json:"data"`
IsLast bool `'json:"is_last"` IsLast bool `json:"is_last"`
} }
// MarshalBinary writes the frame to `w`. // MarshalBinary writes the frame to `w`.
......
...@@ -331,6 +331,9 @@ OptimismPortal_Test:test_receive_succeeds() (gas: 127513) ...@@ -331,6 +331,9 @@ OptimismPortal_Test:test_receive_succeeds() (gas: 127513)
OptimismPortal_Test:test_simple_isOutputFinalized_succeeds() (gas: 32971) OptimismPortal_Test:test_simple_isOutputFinalized_succeeds() (gas: 32971)
OptimismPortal_Test:test_unpause_onlyGuardian_reverts() (gas: 46098) OptimismPortal_Test:test_unpause_onlyGuardian_reverts() (gas: 46098)
OptimismPortal_Test:test_unpause_succeeds() (gas: 31756) OptimismPortal_Test:test_unpause_succeeds() (gas: 31756)
PreimageOracle_Test:test_computePreimageKey_succeeds() (gas: 6267)
PreimageOracle_Test:test_loadKeccak256PreimagePart_outOfBoundsOffset_reverts() (gas: 9025)
PreimageOracle_Test:test_loadKeccak256PreimagePart_succeeds() (gas: 77552)
ProxyAdmin_Test:test_chugsplashChangeProxyAdmin_succeeds() (gas: 35586) ProxyAdmin_Test:test_chugsplashChangeProxyAdmin_succeeds() (gas: 35586)
ProxyAdmin_Test:test_chugsplashGetProxyAdmin_succeeds() (gas: 15675) ProxyAdmin_Test:test_chugsplashGetProxyAdmin_succeeds() (gas: 15675)
ProxyAdmin_Test:test_chugsplashGetProxyImplementation_succeeds() (gas: 51084) ProxyAdmin_Test:test_chugsplashGetProxyImplementation_succeeds() (gas: 51084)
......
// SPDX-License-Identifier: MIT // SPDX-License-Identifier: MIT
pragma solidity 0.8.15; pragma solidity ^0.8.15;
/// @title PreimageOracle
/// @notice A contract for storing permissioned pre-images.
contract PreimageOracle { contract PreimageOracle {
/// @notice Mapping of pre-image keys to pre-image lengths.
mapping(bytes32 => uint256) public preimageLengths; mapping(bytes32 => uint256) public preimageLengths;
/// @notice Mapping of pre-image keys to pre-image parts.
mapping(bytes32 => mapping(uint256 => bytes32)) public preimageParts; mapping(bytes32 => mapping(uint256 => bytes32)) public preimageParts;
/// @notice Mapping of pre-image keys to pre-image part offsets.
mapping(bytes32 => mapping(uint256 => bool)) public preimagePartOk; mapping(bytes32 => mapping(uint256 => bool)) public preimagePartOk;
function readPreimage(bytes32 key, uint256 offset) /// @notice Reads a pre-image from the oracle.
/// @param _key The key of the pre-image to read.
/// @param _offset The offset of the pre-image to read.
/// @return dat_ The pre-image data.
/// @return datLen_ The length of the pre-image data.
function readPreimage(bytes32 _key, uint256 _offset)
external external
view view
returns (bytes32 dat, uint256 datLen) returns (bytes32 dat_, uint256 datLen_)
{ {
require(preimagePartOk[key][offset], "preimage must exist"); require(preimagePartOk[_key][_offset], "pre-image must exist");
datLen = 32;
uint256 length = preimageLengths[key]; // Calculate the length of the pre-image data
// add 8 for the length-prefix part // Add 8 for the length-prefix part
if (offset + 32 >= length + 8) { datLen_ = 32;
datLen = length + 8 - offset; uint256 length = preimageLengths[_key];
if (_offset + 32 >= length + 8) {
datLen_ = length + 8 - _offset;
} }
dat = preimageParts[key][offset];
// Retrieve the pre-image data
dat_ = preimageParts[_key][_offset];
} }
// TODO(CLI-4104): // TODO(CLI-4104):
...@@ -37,17 +53,47 @@ contract PreimageOracle { ...@@ -37,17 +53,47 @@ contract PreimageOracle {
preimageLengths[key] = size; preimageLengths[key] = size;
} }
// loadKeccak256PreimagePart prepares the pre-image to be read by keccak256 key, /// @notice Computes and returns the key for a pre-image.
// starting at the given offset, up to 32 bytes (clipped at preimage length, if out of data). /// @param _preimage The pre-image.
function loadKeccak256PreimagePart(uint256 partOffset, bytes calldata preimage) external { /// @return key_ The pre-image key.
function computePreimageKey(bytes calldata _preimage) external pure returns (bytes32 key_) {
uint256 size;
assembly {
size := calldataload(0x24)
// Leave slots 0x40 and 0x60 untouched,
// and everything after as scratch-memory.
let ptr := 0x80
// Store size as a big-endian uint64 at the start of pre-image
mstore(ptr, shl(192, size))
ptr := add(ptr, 8)
// Copy preimage payload into memory so we can hash and read it.
calldatacopy(ptr, _preimage.offset, size)
// Compute the pre-image keccak256 hash (aka the pre-image key)
let h := keccak256(ptr, size)
// Mask out prefix byte, replace with type 2 byte
key_ := or(and(h, not(shl(248, 0xFF))), shl(248, 2))
}
}
/// @notice Prepares a pre-image to be read by keccak256 key, starting at
/// the given offset and up to 32 bytes (clipped at pre-image length, if out of data).
/// @param _partOffset The offset of the pre-image to read.
/// @param _preimage The preimage data.
function loadKeccak256PreimagePart(uint256 _partOffset, bytes calldata _preimage) external {
uint256 size; uint256 size;
bytes32 key; bytes32 key;
bytes32 part; bytes32 part;
assembly { assembly {
// len(sig) + len(partOffset) + len(preimage offset) = 4 + 32 + 32 = 0x44 // len(sig) + len(partOffset) + len(preimage offset) = 4 + 32 + 32 = 0x44
size := calldataload(0x44) size := calldataload(0x44)
// revert if part offset > size+8 (i.e. parts must be within bounds) // revert if part offset > size+8 (i.e. parts must be within bounds)
if gt(partOffset, add(size, 8)) { if gt(_partOffset, add(size, 8)) {
revert(0, 0) revert(0, 0)
} }
// we leave solidity slots 0x40 and 0x60 untouched, // we leave solidity slots 0x40 and 0x60 untouched,
...@@ -57,16 +103,16 @@ contract PreimageOracle { ...@@ -57,16 +103,16 @@ contract PreimageOracle {
mstore(ptr, shl(192, size)) mstore(ptr, shl(192, size))
ptr := add(ptr, 8) ptr := add(ptr, 8)
// copy preimage payload into memory so we can hash and read it. // copy preimage payload into memory so we can hash and read it.
calldatacopy(ptr, preimage.offset, size) calldatacopy(ptr, _preimage.offset, size)
// Note that it includes the 8-byte big-endian uint64 length prefix. // Note that it includes the 8-byte big-endian uint64 length prefix.
// this will be zero-padded at the end, since memory at end is clean. // this will be zero-padded at the end, since memory at end is clean.
part := mload(add(sub(ptr, 8), partOffset)) part := mload(add(sub(ptr, 8), _partOffset))
let h := keccak256(ptr, size) // compute preimage keccak256 hash let h := keccak256(ptr, size) // compute preimage keccak256 hash
// mask out prefix byte, replace with type 2 byte // mask out prefix byte, replace with type 2 byte
key := or(and(h, not(shl(248, 0xFF))), shl(248, 2)) key := or(and(h, not(shl(248, 0xFF))), shl(248, 2))
} }
preimagePartOk[key][partOffset] = true; preimagePartOk[key][_partOffset] = true;
preimageParts[key][partOffset] = part; preimageParts[key][_partOffset] = part;
preimageLengths[key] = size; preimageLengths[key] = size;
} }
} }
// SPDX-License-Identifier: MIT
pragma solidity 0.7.6;
/// @title IPreimageOracle
/// @notice Interface for a preimage oracle.
interface IPreimageOracle {
/// @notice Reads a preimage from the oracle.
/// @param _key The key of the preimage to read.
/// @param _offset The offset of the preimage to read.
/// @return dat_ The preimage data.
/// @return datLen_ The length of the preimage data.
function readPreimage(bytes32 _key, uint256 _offset)
external
view
returns (bytes32 dat_, uint256 datLen_);
/// @notice Computes and returns the key for a pre-image.
/// @param _preimage The pre-image.
/// @return key_ The pre-image key.
function computePreimageKey(bytes calldata _preimage) external pure returns (bytes32 key_);
/// @notice Prepares a preimage to be read by keccak256 key, starting at
/// the given offset and up to 32 bytes (clipped at preimage length, if out of data).
/// @param _partOffset The offset of the preimage to read.
/// @param _preimage The preimage data.
function loadKeccak256PreimagePart(uint256 _partOffset, bytes calldata _preimage) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;
import { Test } from "forge-std/Test.sol";
import { PreimageOracle } from "../cannon/PreimageOracle.sol";
contract PreimageOracle_Test is Test {
PreimageOracle oracle;
/// @notice Sets up the testing suite.
function setUp() public {
oracle = new PreimageOracle();
vm.label(address(oracle), "PreimageOracle");
}
/// @notice Test the pre-image key computation with a known pre-image.
function test_computePreimageKey_succeeds() public {
bytes memory preimage = hex"deadbeef";
bytes32 key = oracle.computePreimageKey(preimage);
bytes32 known = 0x02fd4e189132273036449fc9e11198c739161b4c0116a9a2dccdfa1c492006f1;
assertEq(key, known);
}
/// @notice Tests that a pre-image is correctly set.
function test_loadKeccak256PreimagePart_succeeds() public {
// Set the pre-image
bytes memory preimage = hex"deadbeef";
bytes32 key = oracle.computePreimageKey(preimage);
uint256 offset = 0;
oracle.loadKeccak256PreimagePart(offset, preimage);
// Validate the pre-image part
bytes32 part = oracle.preimageParts(key, offset);
bytes32 expectedPart = 0x0000000000000004deadbeef0000000000000000000000000000000000000000;
assertEq(part, expectedPart);
// Validate the pre-image length
uint256 length = oracle.preimageLengths(key);
assertEq(length, preimage.length);
// Validate that the pre-image part is set
bool ok = oracle.preimagePartOk(key, offset);
assertTrue(ok);
}
/// @notice Tests that a pre-image cannot be set with an out-of-bounds offset.
function test_loadKeccak256PreimagePart_outOfBoundsOffset_reverts() public {
bytes memory preimage = hex"deadbeef";
uint256 offset = preimage.length + 9;
vm.expectRevert();
oracle.loadKeccak256PreimagePart(offset, preimage);
}
/// @notice Reading a pre-image part that has not been set should revert.
function testFuzz_readPreimage_missingPreimage_reverts(bytes32 key, uint256 offset) public {
vm.expectRevert("pre-image must exist");
oracle.readPreimage(key, offset);
}
}
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
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