Commit 5ae45a1a authored by OptimismBot's avatar OptimismBot Committed by GitHub

Merge pull request #6256 from ethereum-optimism/jg/step_in_agent

op-challenger: Perform steps in the agent
parents 9e8647e8 b1d83700
...@@ -2,53 +2,68 @@ package fault ...@@ -2,53 +2,68 @@ package fault
import ( import (
"context" "context"
"sync" "errors"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
type Agent struct { type Agent struct {
mu sync.Mutex
game Game
solver *Solver solver *Solver
trace TraceProvider trace TraceProvider
loader Loader
responder Responder responder Responder
maxDepth int maxDepth int
log log.Logger 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{ return Agent{
game: game,
solver: NewSolver(maxDepth, trace), solver: NewSolver(maxDepth, trace),
trace: trace, trace: trace,
loader: loader,
responder: responder, responder: responder,
maxDepth: maxDepth, maxDepth: maxDepth,
log: log, log: log,
} }
} }
// AddClaim stores a claim in the local state. // Act iterates the game & performs all of the next actions.
// This function shares a lock with PerformActions. func (a *Agent) Act() error {
func (a *Agent) AddClaim(claim Claim) error { game, err := a.newGameFromContracts(context.Background())
a.mu.Lock() if err != nil {
defer a.mu.Unlock() a.log.Error("Failed to create new game", "err", err)
return a.game.Put(claim) 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. // newGameFromContracts initializes a new game state from the state in the contract
// Note: PerformActions & AddClaim share a lock so the responder cannot func (a *Agent) newGameFromContracts(ctx context.Context) (Game, error) {
// call AddClaim on the same thread. claims, err := a.loader.FetchClaims(ctx)
func (a *Agent) PerformActions() { if err != nil {
a.mu.Lock() return nil, err
defer a.mu.Unlock() }
for _, claim := range a.game.Claims() { if len(claims) == 0 {
_ = a.move(claim) 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 // 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) nextMove, err := a.solver.NextMove(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)
...@@ -62,10 +77,33 @@ func (a *Agent) move(claim Claim) error { ...@@ -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, 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], "letter", string(move.Value[31:]), "trace_index", move.Value[30],
"parent_letter", string(claim.Value[31:]), "parent_trace_index", claim.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") log.Debug("Duplicate move")
return nil return nil
} }
log.Info("Performing move") log.Info("Performing move")
return a.responder.Respond(context.TODO(), 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)
}
...@@ -2,74 +2,74 @@ package fault ...@@ -2,74 +2,74 @@ package fault
import ( import (
"context" "context"
"os"
"time"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
) )
type Orchestrator struct { type Orchestrator struct {
agents []Agent agents []Agent
outputChs []chan Claim claims []Claim
responses chan Claim steps []StepCallData
// tracking when to exit
claimLen, stepLen, step int
} }
func NewOrchestrator(maxDepth uint64, traces []TraceProvider, names []string, root Claim) Orchestrator { func NewOrchestrator(maxDepth uint64, traces []TraceProvider, names []string, root Claim) Orchestrator {
o := Orchestrator{ o := Orchestrator{
responses: make(chan Claim, 100), agents: make([]Agent, len(traces)),
outputChs: make([]chan Claim, len(traces)), claims: []Claim{root},
agents: make([]Agent, len(traces)), steps: make([]StepCallData, 0),
} }
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 {
game := NewGameState(root, maxDepth) o.agents[i] = NewAgent(&o, int(maxDepth), trace, &o, log.New("role", names[i]))
o.agents[i] = NewAgent(game, int(maxDepth), trace, &o, log.New("role", names[i]))
o.outputChs[i] = make(chan Claim)
} }
return o return o
} }
func (o *Orchestrator) Respond(_ context.Context, response Claim) error { 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 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 return nil
} }
func (o *Orchestrator) Start() { func (o *Orchestrator) FetchClaims(ctx context.Context) ([]Claim, error) {
for i := 0; i < len(o.agents); i++ { c := make([]Claim, len(o.claims))
go runAgent(&o.agents[i], o.outputChs[i]) copy(c, o.claims)
} return c, nil
o.responderThread()
} }
func runAgent(agent *Agent, claimCh <-chan Claim) { func (o *Orchestrator) Start() {
for { for {
agent.PerformActions() for _, a := range o.agents {
// Note: Should drain the channel here _ = a.Act()
claim := <-claimCh }
_ = agent.AddClaim(claim) if o.shouldExit() {
log.Info("exiting")
return
}
} }
} }
func (o *Orchestrator) responderThread() { func (o *Orchestrator) shouldExit() bool {
timer := time.NewTimer(200 * time.Millisecond) cl := o.claimLen
defer timer.Stop() sl := o.stepLen
for {
select { o.claimLen = len(o.claims)
case resp := <-o.responses: o.stepLen = len(o.steps)
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)
}
noProgress := o.claimLen == cl && o.stepLen == sl
if noProgress {
o.step = o.step + 1
} else {
o.step = 0
} }
return noProgress && o.step == 1
} }
...@@ -31,14 +31,13 @@ func (s *Solver) NextMove(claim Claim) (*Claim, error) { ...@@ -31,14 +31,13 @@ func (s *Solver) NextMove(claim Claim) (*Claim, error) {
} }
type StepData struct { type StepData struct {
LeafClaim Claim LeafClaim Claim
StateClaim Claim IsAttack bool
IsAttack bool
} }
// AttemptStep determines what step should occur for a given leaf claim. // 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. // 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 { if claim.Depth() != s.gameDepth {
return StepData{}, errors.New("cannot step on non-leaf claims") return StepData{}, errors.New("cannot step on non-leaf claims")
} }
...@@ -46,20 +45,9 @@ func (s *Solver) AttemptStep(claim Claim, state Game) (StepData, error) { ...@@ -46,20 +45,9 @@ func (s *Solver) AttemptStep(claim Claim, state Game) (StepData, error) {
if err != nil { if err != nil {
return StepData{}, err 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{ return StepData{
LeafClaim: claim, LeafClaim: claim,
StateClaim: stateClaim, IsAttack: claimCorrect,
IsAttack: claimCorrect,
}, nil }, nil
} }
......
...@@ -89,12 +89,11 @@ func TestAttemptStep(t *testing.T) { ...@@ -89,12 +89,11 @@ func TestAttemptStep(t *testing.T) {
require.NoError(t, g.Put(middle)) require.NoError(t, g.Put(middle))
require.NoError(t, g.Put(bottom)) require.NoError(t, g.Put(bottom))
step, err := solver.AttemptStep(bottom, g) step, err := solver.AttemptStep(bottom)
require.NoError(t, err) require.NoError(t, err)
require.Equal(t, bottom, step.LeafClaim) require.Equal(t, bottom, step.LeafClaim)
require.Equal(t, middle, step.StateClaim)
require.True(t, step.IsAttack) require.True(t, step.IsAttack)
_, err = solver.AttemptStep(middle, g) _, err = solver.AttemptStep(middle)
require.Error(t, err) require.Error(t, err)
} }
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