diff --git a/op-challenger/fault/agent.go b/op-challenger/fault/agent.go index 341f88f3aaf85ecd4128d66a438a60228fce1c77..681777aefe19ccf623b8d90eca6e0c9c41ddc6c2 100644 --- a/op-challenger/fault/agent.go +++ b/op-challenger/fault/agent.go @@ -2,53 +2,68 @@ package fault import ( "context" - "sync" + "errors" "github.com/ethereum/go-ethereum/log" ) type Agent struct { - mu sync.Mutex - game Game solver *Solver trace TraceProvider + loader Loader responder Responder maxDepth int log log.Logger } -func NewAgent(game Game, maxDepth int, trace TraceProvider, responder Responder, log log.Logger) Agent { +func NewAgent(loader Loader, maxDepth int, trace TraceProvider, responder Responder, log log.Logger) Agent { return Agent{ - game: game, solver: NewSolver(maxDepth, trace), trace: trace, + loader: loader, responder: responder, maxDepth: maxDepth, log: log, } } -// AddClaim stores a claim in the local state. -// This function shares a lock with PerformActions. -func (a *Agent) AddClaim(claim Claim) error { - a.mu.Lock() - defer a.mu.Unlock() - return a.game.Put(claim) +// Act iterates the game & performs all of the next actions. +func (a *Agent) Act() error { + game, err := a.newGameFromContracts(context.Background()) + if err != nil { + a.log.Error("Failed to create new game", "err", err) + return err + } + // Create counter claims + for _, claim := range game.Claims() { + _ = a.move(claim, game) + } + // Step on all leaf claims + for _, claim := range game.Claims() { + _ = a.step(claim, game) + } + return nil } -// PerformActions iterates the game & performs all of the next actions. -// Note: PerformActions & AddClaim share a lock so the responder cannot -// call AddClaim on the same thread. -func (a *Agent) PerformActions() { - a.mu.Lock() - defer a.mu.Unlock() - for _, claim := range a.game.Claims() { - _ = a.move(claim) +// newGameFromContracts initializes a new game state from the state in the contract +func (a *Agent) newGameFromContracts(ctx context.Context) (Game, error) { + claims, err := a.loader.FetchClaims(ctx) + if err != nil { + return nil, err + } + if len(claims) == 0 { + return nil, errors.New("no claims") } + game := NewGameState(claims[0], uint64(a.maxDepth)) + if err := game.PutAll(claims[1:]); err != nil { + return nil, err + } + return game, nil } // move determines & executes the next move given a claim pair -func (a *Agent) move(claim Claim) error { +func (a *Agent) move(claim Claim, game Game) error { + a.log.Info("Fetching claims") nextMove, err := a.solver.NextMove(claim) if err != nil { a.log.Warn("Failed to execute the next move", "err", err) @@ -62,10 +77,33 @@ func (a *Agent) move(claim Claim) error { log := a.log.New("is_defend", move.DefendsParent(), "depth", move.Depth(), "index_at_depth", move.IndexAtDepth(), "value", move.Value, "letter", string(move.Value[31:]), "trace_index", move.Value[30], "parent_letter", string(claim.Value[31:]), "parent_trace_index", claim.Value[30]) - if a.game.IsDuplicate(move) { + if game.IsDuplicate(move) { log.Debug("Duplicate move") return nil } log.Info("Performing move") return a.responder.Respond(context.TODO(), move) } + +// step attempts to execute the step through the responder +func (a *Agent) step(claim Claim, game Game) error { + if claim.Depth() != a.maxDepth { + return nil + } + a.log.Info("Attempting step", "claim_depth", claim.Depth(), "maxDepth", a.maxDepth) + + step, err := a.solver.AttemptStep(claim) + if err != nil { + a.log.Info("Failed to get a step", "err", err) + return err + } + + a.log.Info("Performing step", + "depth", step.LeafClaim.Depth(), "index_at_depth", step.LeafClaim.IndexAtDepth(), "value", step.LeafClaim.Value, + "is_attack", step.IsAttack) + callData := StepCallData{ + ClaimIndex: uint64(step.LeafClaim.ContractIndex), + IsAttack: step.IsAttack, + } + return a.responder.Step(context.TODO(), callData) +} diff --git a/op-challenger/fault/orchestrator.go b/op-challenger/fault/orchestrator.go index f41326fbefb140d39463c09adbb32bdb31c463fb..40b45374830187cfad5fb7f42f23f28b746a9ae2 100644 --- a/op-challenger/fault/orchestrator.go +++ b/op-challenger/fault/orchestrator.go @@ -2,74 +2,74 @@ package fault import ( "context" - "os" - "time" "github.com/ethereum/go-ethereum/log" ) type Orchestrator struct { - agents []Agent - outputChs []chan Claim - responses chan Claim + agents []Agent + claims []Claim + steps []StepCallData + + // tracking when to exit + claimLen, stepLen, step int } func NewOrchestrator(maxDepth uint64, traces []TraceProvider, names []string, root Claim) Orchestrator { o := Orchestrator{ - responses: make(chan Claim, 100), - outputChs: make([]chan Claim, len(traces)), - agents: make([]Agent, len(traces)), + agents: make([]Agent, len(traces)), + claims: []Claim{root}, + steps: make([]StepCallData, 0), } log.Info("Starting game", "root_letter", string(root.Value[31:])) for i, trace := range traces { - game := NewGameState(root, maxDepth) - o.agents[i] = NewAgent(game, int(maxDepth), trace, &o, log.New("role", names[i])) - o.outputChs[i] = make(chan Claim) + o.agents[i] = NewAgent(&o, int(maxDepth), trace, &o, log.New("role", names[i])) } return o } func (o *Orchestrator) Respond(_ context.Context, response Claim) error { - o.responses <- response + response.ContractIndex = len(o.claims) + o.claims = append(o.claims, response) return nil } -func (o *Orchestrator) Step(ctx context.Context, stepData StepCallData) error { +func (o *Orchestrator) Step(_ context.Context, stepData StepCallData) error { + log.Info("Step recorded", "step", stepData) + o.steps = append(o.steps, stepData) return nil } -func (o *Orchestrator) Start() { - for i := 0; i < len(o.agents); i++ { - go runAgent(&o.agents[i], o.outputChs[i]) - } - o.responderThread() +func (o *Orchestrator) FetchClaims(ctx context.Context) ([]Claim, error) { + c := make([]Claim, len(o.claims)) + copy(c, o.claims) + return c, nil } -func runAgent(agent *Agent, claimCh <-chan Claim) { +func (o *Orchestrator) Start() { for { - agent.PerformActions() - // Note: Should drain the channel here - claim := <-claimCh - _ = agent.AddClaim(claim) - + for _, a := range o.agents { + _ = a.Act() + } + if o.shouldExit() { + log.Info("exiting") + return + } } } -func (o *Orchestrator) responderThread() { - timer := time.NewTimer(200 * time.Millisecond) - defer timer.Stop() - for { - select { - case resp := <-o.responses: - timer.Reset(200 * time.Millisecond) - for _, ch := range o.outputChs { - // Copy it. Should be immutable, but be sure. - resp := resp - ch <- resp - } - case <-timer.C: - os.Exit(0) - } +func (o *Orchestrator) shouldExit() bool { + cl := o.claimLen + sl := o.stepLen + + o.claimLen = len(o.claims) + o.stepLen = len(o.steps) + noProgress := o.claimLen == cl && o.stepLen == sl + if noProgress { + o.step = o.step + 1 + } else { + o.step = 0 } + return noProgress && o.step == 1 } diff --git a/op-challenger/fault/solver.go b/op-challenger/fault/solver.go index ea57e5f75489f709df686ce6a9097b824bef85ee..607b765fbb8a28925110028a20954c88b83fbe60 100644 --- a/op-challenger/fault/solver.go +++ b/op-challenger/fault/solver.go @@ -31,14 +31,13 @@ func (s *Solver) NextMove(claim Claim) (*Claim, error) { } type StepData struct { - LeafClaim Claim - StateClaim Claim - IsAttack bool + LeafClaim Claim + IsAttack bool } // AttemptStep determines what step should occur for a given leaf claim. // An error will be returned if the claim is not at the max depth. -func (s *Solver) AttemptStep(claim Claim, state Game) (StepData, error) { +func (s *Solver) AttemptStep(claim Claim) (StepData, error) { if claim.Depth() != s.gameDepth { return StepData{}, errors.New("cannot step on non-leaf claims") } @@ -46,20 +45,9 @@ func (s *Solver) AttemptStep(claim Claim, state Game) (StepData, error) { if err != nil { return StepData{}, err } - var selectorFn func(Claim) (Claim, error) - if claimCorrect { - selectorFn = state.PostStateClaim - } else { - selectorFn = state.PreStateClaim - } - stateClaim, err := selectorFn(claim) - if err != nil { - return StepData{}, err - } return StepData{ - LeafClaim: claim, - StateClaim: stateClaim, - IsAttack: claimCorrect, + LeafClaim: claim, + IsAttack: claimCorrect, }, nil } diff --git a/op-challenger/fault/solver_test.go b/op-challenger/fault/solver_test.go index d4f9b6804021e71b35baac7ac3e5c3ad1d706739..1a70d386aa35aceca446ec6bd21a4585e8cb2125 100644 --- a/op-challenger/fault/solver_test.go +++ b/op-challenger/fault/solver_test.go @@ -89,12 +89,11 @@ func TestAttemptStep(t *testing.T) { require.NoError(t, g.Put(middle)) require.NoError(t, g.Put(bottom)) - step, err := solver.AttemptStep(bottom, g) + step, err := solver.AttemptStep(bottom) require.NoError(t, err) require.Equal(t, bottom, step.LeafClaim) - require.Equal(t, middle, step.StateClaim) require.True(t, step.IsAttack) - _, err = solver.AttemptStep(middle, g) + _, err = solver.AttemptStep(middle) require.Error(t, err) }