Commit 5951ed36 authored by Joshua Gutow's avatar Joshua Gutow

op-challenger: Perform steps in the agent

Also lots of cleanup.
parent 4c733eba
......@@ -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)
}
......@@ -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
}
......@@ -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
}
......
......@@ -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)
}
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