From ad007d75a3245ba8c52c05669f1ab94877f3df70 Mon Sep 17 00:00:00 2001 From: Po Date: Sun, 3 Nov 2024 12:18:51 +0800 Subject: [PATCH 01/13] feat(MSFDG): add getSubValues --- op-challenger2/game/fault/agent.go | 3 +- op-challenger2/game/fault/agent_test.go | 8 ++ .../game/fault/contracts/faultdisputegame.go | 97 ++++++++++++++ .../fault/contracts/faultdisputegame_test.go | 125 ++++++++++++++++++ op-challenger2/game/fault/types/position.go | 2 +- op-service/sources/batching/bound.go | 4 + op-service/sources/batching/call.go | 5 + op-service/sources/batching/event_call.go | 37 ++++++ .../sources/batching/event_call_test.go | 75 +++++++++++ op-service/sources/batching/multicall.go | 20 +++ op-service/sources/batching/test/abi_stub.go | 21 +++ .../sources/batching/test/event_stub.go | 66 +++++++++ .../sources/batching/test/generic_stub.go | 8 ++ op-service/sources/batching/test/tx_stub.go | 45 +++++++ op-service/sources/batching/tx_call.go | 64 +++++++++ op-service/sources/batching/tx_call_test.go | 50 +++++++ 16 files changed, 628 insertions(+), 2 deletions(-) create mode 100644 op-service/sources/batching/event_call.go create mode 100644 op-service/sources/batching/event_call_test.go create mode 100644 op-service/sources/batching/test/event_stub.go create mode 100644 op-service/sources/batching/test/tx_stub.go create mode 100644 op-service/sources/batching/tx_call.go create mode 100644 op-service/sources/batching/tx_call_test.go diff --git a/op-challenger2/game/fault/agent.go b/op-challenger2/game/fault/agent.go index b3865dbb2a7f..9a82250f5921 100644 --- a/op-challenger2/game/fault/agent.go +++ b/op-challenger2/game/fault/agent.go @@ -30,6 +30,7 @@ type Responder interface { type ClaimLoader interface { GetAllClaims(ctx context.Context, block rpcblock.Block) ([]types.Claim, error) + GetAllClaimsWithSubValues(ctx context.Context) ([]types.Claim, error) IsL2BlockNumberChallenged(ctx context.Context, block rpcblock.Block) (bool, error) GetMaxGameDepth(ctx context.Context) (types.Depth, error) GetSplitDepth(ctx context.Context) (types.Depth, error) @@ -235,7 +236,7 @@ func (a *Agent) resolveClaims(ctx context.Context) error { // newGameFromContracts initializes a new game state from the state in the contract func (a *Agent) newGameFromContracts(ctx context.Context) (types.Game, error) { - claims, err := a.loader.GetAllClaims(ctx, rpcblock.Latest) + claims, err := a.loader.GetAllClaimsWithSubValues(ctx) if err != nil { return nil, fmt.Errorf("failed to fetch claims: %w", err) } diff --git a/op-challenger2/game/fault/agent_test.go b/op-challenger2/game/fault/agent_test.go index 0acf8ce32d25..f3e9ccccfde2 100644 --- a/op-challenger2/game/fault/agent_test.go +++ b/op-challenger2/game/fault/agent_test.go @@ -221,6 +221,14 @@ func (s *stubClaimLoader) GetAllClaims(_ context.Context, _ rpcblock.Block) ([]t return s.claims, nil } +func (s *stubClaimLoader) GetAllClaimsWithSubValues(_ context.Context) ([]types.Claim, error) { + s.callCount++ + if s.callCount > s.maxLoads && s.maxLoads != 0 { + return []types.Claim{}, nil + } + return s.claims, nil +} + func (s *stubClaimLoader) GetMaxGameDepth(_ context.Context) (types.Depth, error) { return s.maxDepth, nil } diff --git a/op-challenger2/game/fault/contracts/faultdisputegame.go b/op-challenger2/game/fault/contracts/faultdisputegame.go index 28fd34d68269..de945aad5ced 100644 --- a/op-challenger2/game/fault/contracts/faultdisputegame.go +++ b/op-challenger2/game/fault/contracts/faultdisputegame.go @@ -13,12 +13,15 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/contracts/metrics" "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/types" gameTypes "github.com/ethereum-optimism/optimism/op-challenger2/game/types" + "github.com/ethereum-optimism/optimism/op-e2e/bindings" "github.com/ethereum-optimism/optimism/op-service/sources/batching" "github.com/ethereum-optimism/optimism/op-service/sources/batching/rpcblock" "github.com/ethereum-optimism/optimism/op-service/txmgr" "github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots" "github.com/ethereum/go-ethereum/accounts/abi" + "github.com/ethereum/go-ethereum/accounts/abi/bind" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" ) @@ -58,6 +61,9 @@ var ( methodMaxAttackBranch = "maxAttackBranch" methodAttackV2 = "attackV2" methodStepV2 = "stepV2" + fieldSubValues = "_claims" + fieldAttackBranch = "_attackBranch" + eventMove = "Move" ) var ( @@ -140,6 +146,23 @@ func mustParseAbi(json []byte) *abi.ABI { return &loaded } +func SubValuesHash(values []common.Hash) common.Hash { + nelem := len(values) + hashes := make([]common.Hash, nelem) + copy(hashes, values) + for nelem != 1 { + for i := 0; i < nelem/2; i++ { + hashes[i] = crypto.Keccak256Hash(hashes[i*2][:], hashes[i*2+1][:]) + } + // directly copy the last item + if nelem%2 == 1 { + hashes[nelem/2] = hashes[nelem-1] + } + nelem = (nelem + 1) / 2 + } + return hashes[0] +} + // GetBalance returns the total amount of ETH controlled by this contract. // Note that the ETH is actually held by the DelayedWETH contract which may be shared by multiple games. // Returns the balance and the address of the contract that actually holds the balance. @@ -447,6 +470,79 @@ func (f *FaultDisputeGameContractLatest) GetAllClaims(ctx context.Context, block return claims, nil } +func (f *FaultDisputeGameContractLatest) GetAllClaimsWithSubValues(ctx context.Context) ([]types.Claim, error) { + claims, err := f.GetAllClaims(ctx, rpcblock.Latest) + if err != nil { + return nil, err + } + for idx, claim := range claims { + subValues, attackBranch, err := f.getSubValuesAndAttackBranch(ctx, &claim) + if err != nil { + return nil, fmt.Errorf("failed to load subValues: %w", err) + } + claim.SubValues = &subValues + claim.AttackBranch = attackBranch + claims[idx] = claim + } + return claims, nil +} + +func (f *FaultDisputeGameContractLatest) getSubValuesAndAttackBranch(ctx context.Context, aggClaim *types.Claim) ([]common.Hash, uint64, error) { + defer f.metrics.StartContractRequest("GetSubValues")() + + filter, err := bindings.NewFaultDisputeGameFilterer(f.contract.Addr(), f.multiCaller) + if err != nil { + return nil, 0, err + } + + parentIndex := [...]*big.Int{big.NewInt(int64(aggClaim.ParentContractIndex))} + claim := [...][32]byte{aggClaim.ClaimData.ValueBytes()} + claimant := [...]common.Address{aggClaim.Claimant} + moveIter, err := filter.FilterMove(&bind.FilterOpts{Context: ctx}, parentIndex[:], claim[:], claimant[:]) + if err != nil { + return nil, 0, fmt.Errorf("failed to filter move event log: %w", err) + } + ok := moveIter.Next() + if !ok { + return nil, 0, fmt.Errorf("failed to get move event log: %w", moveIter.Error()) + } + txHash := moveIter.Event.Raw.TxHash + + getTxByHashCall := batching.NewTxGetByHash(f.contract.Abi(), txHash, methodAttackV2) + result, err := f.multiCaller.SingleCall(ctx, rpcblock.Latest, getTxByHashCall) + if err != nil { + return nil, 0, fmt.Errorf("failed to load attackV2's calldata: %w", err) + } + + txn := result.GetTx() + var subValues []common.Hash + var attackBranch uint64 + if len(txn.BlobHashes()) > 0 { + // todo: fetch Blobs and unpack it into subValues and attackBranch + return nil, 0, fmt.Errorf("blob tx hasn't been supported") + } else { + inputMap, err := getTxByHashCall.UnpackCallData(&txn) + if err != nil { + return nil, 0, fmt.Errorf("failed to unpack tx resp: %w", err) + } + attackBranchU256 := *abi.ConvertType(inputMap[fieldAttackBranch], new(big.Int)).(*big.Int) + attackBranch = attackBranchU256.Uint64() + + maxAttackBranch, err := f.GetMaxAttackBranch(ctx) + if err != nil { + return nil, 0, err + } + valuesBytesLen := uint(32 * maxAttackBranch) + bytes := abi.ConvertType(inputMap[fieldSubValues], make([]byte, valuesBytesLen)).([]byte) + for i := uint64(0); i < maxAttackBranch; i++ { + hash := common.BytesToHash(bytes[i*32 : (i+1)*32]) + subValues = append(subValues, hash) + } + } + + return subValues, attackBranch, nil +} + func (f *FaultDisputeGameContractLatest) IsResolved(ctx context.Context, block rpcblock.Block, claims ...types.Claim) ([]bool, error) { defer f.metrics.StartContractRequest("IsResolved")() calls := make([]batching.Call, 0, len(claims)) @@ -670,4 +766,5 @@ type FaultDisputeGameContract interface { StepV2Tx(claimIdx uint64, attackBranch uint64, stateData []byte, proof types.StepProof) (txmgr.TxCandidate, error) GetNBits(ctx context.Context) (uint64, error) GetMaxAttackBranch(ctx context.Context) (uint64, error) + GetAllClaimsWithSubValues(ctx context.Context) ([]types.Claim, error) } diff --git a/op-challenger2/game/fault/contracts/faultdisputegame_test.go b/op-challenger2/game/fault/contracts/faultdisputegame_test.go index 2c699b27223c..9b8f6019cacb 100644 --- a/op-challenger2/game/fault/contracts/faultdisputegame_test.go +++ b/op-challenger2/game/fault/contracts/faultdisputegame_test.go @@ -23,6 +23,8 @@ import ( "github.com/ethereum-optimism/optimism/packages/contracts-bedrock/snapshots" "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/common" + coreTypes "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/crypto" "github.com/ethereum/go-ethereum/rlp" "github.com/stretchr/testify/require" ) @@ -851,3 +853,126 @@ func TestStepV2Tx(t *testing.T) { }) } } + +func TestGetAllClaimsWithSubValues(t *testing.T) { + for _, version := range versions { + t.Run(version.version, func(t *testing.T) { + stubRpc, game := setupFaultDisputeGameTest(t, version) + block := rpcblock.Latest + + claimant := common.Address{0xbb} + bond := big.NewInt(1044) + nBits := uint64(2) + claimsBytes := make([]byte, ((1< Date: Sun, 3 Nov 2024 19:22:37 +0800 Subject: [PATCH 02/13] op-challenger2 add attackV2 --- op-challenger2/game/fault/agent.go | 2 + .../game/fault/responder/responder.go | 9 +++ .../game/fault/solver/game_solver.go | 40 ++++++---- op-challenger2/game/fault/solver/solver.go | 80 ++++++++++++------- op-challenger2/game/fault/types/actions.go | 6 +- op-challenger2/metrics/metrics.go | 11 +++ op-challenger2/metrics/noop.go | 1 + 7 files changed, 101 insertions(+), 48 deletions(-) diff --git a/op-challenger2/game/fault/agent.go b/op-challenger2/game/fault/agent.go index b3865dbb2a7f..14fca8f60e84 100644 --- a/op-challenger2/game/fault/agent.go +++ b/op-challenger2/game/fault/agent.go @@ -139,6 +139,8 @@ func (a *Agent) performAction(ctx context.Context, wg *sync.WaitGroup, action ty switch action.Type { case types.ActionTypeMove: a.metrics.RecordGameMove() + case types.ActionTypeAttackV2: + a.metrics.RecordGameAttackV2() case types.ActionTypeStep: a.metrics.RecordGameStep() case types.ActionTypeChallengeL2BlockNumber: diff --git a/op-challenger2/game/fault/responder/responder.go b/op-challenger2/game/fault/responder/responder.go index d12a6b9f6558..66b5de7bd5d5 100644 --- a/op-challenger2/game/fault/responder/responder.go +++ b/op-challenger2/game/fault/responder/responder.go @@ -4,6 +4,7 @@ import ( "context" "errors" "fmt" + "math/big" "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/preimages" "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/types" @@ -19,6 +20,7 @@ type GameContract interface { CallResolveClaim(ctx context.Context, claimIdx uint64) error ResolveClaimTx(claimIdx uint64) (txmgr.TxCandidate, error) AttackTx(ctx context.Context, parent types.Claim, pivot common.Hash) (txmgr.TxCandidate, error) + AttackV2Tx(ctx context.Context, parent types.Claim, attackBranch uint64, daType uint64, claims []byte) (txmgr.TxCandidate, error) DefendTx(ctx context.Context, parent types.Claim, pivot common.Hash) (txmgr.TxCandidate, error) StepTx(claimIdx uint64, isAttack bool, stateData []byte, proof []byte) (txmgr.TxCandidate, error) ChallengeL2BlockNumberTx(challenge *types.InvalidL2BlockNumberChallenge) (txmgr.TxCandidate, error) @@ -117,6 +119,13 @@ func (r *FaultResponder) PerformAction(ctx context.Context, action types.Action) } else { candidate, err = r.contract.DefendTx(ctx, action.ParentClaim, action.Value) } + case types.ActionTypeAttackV2: + subValues := make([]byte, 0, len(*action.SubValues)) + for _, subValue := range *action.SubValues { + subValues = append(subValues, subValue[:]...) + } + daTypeUint64 := (*big.Int)(action.DAType).Uint64() + candidate, err = r.contract.AttackV2Tx(ctx, action.ParentClaim, action.AttackBranch, daTypeUint64, subValues) case types.ActionTypeStep: candidate, err = r.contract.StepTx(uint64(action.ParentClaim.ContractIndex), action.IsAttack, action.PreState, action.ProofData) case types.ActionTypeChallengeL2BlockNumber: diff --git a/op-challenger2/game/fault/solver/game_solver.go b/op-challenger2/game/fault/solver/game_solver.go index 75a9707d6462..c8a0283df606 100644 --- a/op-challenger2/game/fault/solver/game_solver.go +++ b/op-challenger2/game/fault/solver/game_solver.go @@ -95,21 +95,29 @@ func (s *GameSolver) calculateStep(ctx context.Context, game types.Game, claim t } func (s *GameSolver) calculateMove(ctx context.Context, game types.Game, claim types.Claim, honestClaims *honestClaimTracker) (*types.Action, error) { - move, err := s.claimSolver.NextMove(ctx, claim, game, honestClaims) - if err != nil { - return nil, fmt.Errorf("failed to calculate next move for claim index %v: %w", claim.ContractIndex, err) - } - if move == nil { - return nil, nil - } - honestClaims.AddHonestClaim(claim, *move) - if game.IsDuplicate(*move) { - return nil, nil + for branch, _ := range *claim.SubValues { + if claim.Position.Depth() == game.SplitDepth()+types.Depth(game.NBits()) && branch != 0 { + continue + } + move, err := s.claimSolver.NextMove(ctx, claim, game, honestClaims, uint64(branch)) + if err != nil { + return nil, fmt.Errorf("failed to calculate next move for claim index %v: %w", claim.ContractIndex, err) + } + if move == nil { + continue + } + honestClaims.AddHonestClaim(claim, *move) + if game.IsDuplicate(*move) { + continue + } + return &types.Action{ + Type: types.ActionTypeAttackV2, + ParentClaim: game.Claims()[move.ParentContractIndex], + Value: move.Value, + SubValues: move.SubValues, + AttackBranch: uint64(branch), + DAType: s.claimSolver.daType, + }, nil } - return &types.Action{ - Type: types.ActionTypeMove, - IsAttack: !game.DefendsParent(*move), - ParentClaim: game.Claims()[move.ParentContractIndex], - Value: move.Value, - }, nil + return nil, nil } diff --git a/op-challenger2/game/fault/solver/solver.go b/op-challenger2/game/fault/solver/solver.go index a82cc5bb88a0..8c683f80af97 100644 --- a/op-challenger2/game/fault/solver/solver.go +++ b/op-challenger2/game/fault/solver/solver.go @@ -7,6 +7,8 @@ import ( "fmt" "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/types" + "github.com/ethereum-optimism/optimism/op-challenger2/game/keccak/merkle" + "github.com/ethereum/go-ethereum/common" ) var ( @@ -63,7 +65,7 @@ func (s *claimSolver) shouldCounter(game types.Game, claim types.Claim, honestCl } // NextMove returns the next move to make given the current state of the game. -func (s *claimSolver) NextMove(ctx context.Context, claim types.Claim, game types.Game, honestClaims *honestClaimTracker) (*types.Claim, error) { +func (s *claimSolver) NextMove(ctx context.Context, claim types.Claim, game types.Game, honestClaims *honestClaimTracker, branch uint64) (*types.Claim, error) { if claim.Depth() == s.gameDepth { return nil, types.ErrGameDepthReached } @@ -74,12 +76,17 @@ func (s *claimSolver) NextMove(ctx context.Context, claim types.Claim, game type return nil, nil } - if agree, err := s.agreeWithClaim(ctx, game, claim); err != nil { + agree, err := s.agreeWithClaimV2(ctx, game, claim, branch) + if err != nil { return nil, err - } else if agree { - return s.defend(ctx, game, claim) + } + if agree { + if branch < game.MaxAttackBranch()-1 { + return nil, nil + } + return s.attackV2(ctx, game, claim, branch+1) } else { - return s.attack(ctx, game, claim) + return s.attackV2(ctx, game, claim, branch) } } @@ -134,37 +141,48 @@ func (s *claimSolver) AttemptStep(ctx context.Context, game types.Game, claim ty }, nil } -// attack returns a response that attacks the claim. -func (s *claimSolver) attack(ctx context.Context, game types.Game, claim types.Claim) (*types.Claim, error) { - position := claim.Attack() - value, err := s.trace.Get(ctx, game, claim, position) - if err != nil { - return nil, fmt.Errorf("attack claim: %w", err) - } - return &types.Claim{ - ClaimData: types.ClaimData{Value: value, Position: position}, - ParentContractIndex: claim.ContractIndex, - }, nil +// agreeWithClaim returns true if the claim is correct according to the internal [TraceProvider]. +func (s *claimSolver) agreeWithClaim(ctx context.Context, game types.Game, claim types.Claim) (bool, error) { + ourValue, err := s.trace.Get(ctx, game, claim, claim.Position) + return bytes.Equal(ourValue[:], claim.Value[:]), err } -// defend returns a response that defends the claim. -func (s *claimSolver) defend(ctx context.Context, game types.Game, claim types.Claim) (*types.Claim, error) { - if claim.IsRoot() { - return nil, nil - } - position := claim.Defend() - value, err := s.trace.Get(ctx, game, claim, position) - if err != nil { - return nil, fmt.Errorf("defend claim: %w", err) - } +func (s *claimSolver) agreeWithClaimV2(ctx context.Context, game types.Game, claim types.Claim, branch uint64) (bool, error) { + ourValue, err := s.trace.Get(ctx, game, claim, claim.Position.MoveRightN(branch)) + return bytes.Equal(ourValue[:], (*claim.SubValues)[branch][:]), err +} + +func (s *claimSolver) attackV2(ctx context.Context, game types.Game, claim types.Claim, branch uint64) (*types.Claim, error) { + var err error + var value common.Hash + var values []common.Hash + maxAttackBranch := game.MaxAttackBranch() + position := claim.MoveN(game.NBits(), branch) + for i := uint64(0); i < maxAttackBranch; i++ { + tmpPosition := position.MoveRightN(i) + if tmpPosition.Depth() == (game.SplitDepth()+types.Depth(game.NBits())) && i != 0 { + value = common.Hash{} + } else { + value, err = s.trace.Get(ctx, game, claim, tmpPosition) + if err != nil { + return nil, fmt.Errorf("attack claim: %w", err) + } + } + values = append(values, value) + } + hash := getClaimsHash(values) return &types.Claim{ - ClaimData: types.ClaimData{Value: value, Position: position}, + ClaimData: types.ClaimData{Value: hash, Position: position}, ParentContractIndex: claim.ContractIndex, + SubValues: &values, + AttackBranch: branch, }, nil } -// agreeWithClaim returns true if the claim is correct according to the internal [TraceProvider]. -func (s *claimSolver) agreeWithClaim(ctx context.Context, game types.Game, claim types.Claim) (bool, error) { - ourValue, err := s.trace.Get(ctx, game, claim, claim.Position) - return bytes.Equal(ourValue[:], claim.Value[:]), err +func getClaimsHash(values []common.Hash) common.Hash { + tree := merkle.NewBinaryMerkleTree() + for i := 0; i < len(values); i++ { + tree.AddLeaf(values[i]) + } + return tree.RootHash() } diff --git a/op-challenger2/game/fault/types/actions.go b/op-challenger2/game/fault/types/actions.go index e5093d6ac553..d6a22e4463a9 100644 --- a/op-challenger2/game/fault/types/actions.go +++ b/op-challenger2/game/fault/types/actions.go @@ -9,6 +9,7 @@ func (a ActionType) String() string { } const ( + ActionTypeAttackV2 ActionType = "attackV2" ActionTypeMove ActionType = "move" ActionTypeStep ActionType = "step" ActionTypeChallengeL2BlockNumber ActionType = "challenge-l2-block-number" @@ -22,7 +23,10 @@ type Action struct { IsAttack bool // Moves - Value common.Hash + Value common.Hash + SubValues *[]common.Hash + AttackBranch uint64 + DAType DAType // Steps PreState []byte diff --git a/op-challenger2/metrics/metrics.go b/op-challenger2/metrics/metrics.go index adb223c0eac7..3b01fcf603be 100644 --- a/op-challenger2/metrics/metrics.go +++ b/op-challenger2/metrics/metrics.go @@ -36,6 +36,7 @@ type Metricer interface { RecordGameStep() RecordGameMove() + RecordGameAttackV2() RecordGameL2Challenge() RecordCannonExecutionTime(t float64) RecordAsteriscExecutionTime(t float64) @@ -85,6 +86,7 @@ type Metrics struct { highestActedL1Block prometheus.Gauge moves prometheus.Counter + attackV2 prometheus.Counter steps prometheus.Counter l2Challenges prometheus.Counter @@ -142,6 +144,11 @@ func NewMetrics() *Metrics { Name: "moves", Help: "Number of game moves made by the challenge agent", }), + attackV2: factory.NewCounter(prometheus.CounterOpts{ + Namespace: Namespace, + Name: "attackV2", + Help: "Number of game attack v2 moves made by the challenge agent", + }), steps: factory.NewCounter(prometheus.CounterOpts{ Namespace: Namespace, Name: "steps", @@ -254,6 +261,10 @@ func (m *Metrics) RecordGameMove() { m.moves.Add(1) } +func (m *Metrics) RecordGameAttackV2() { + m.attackV2.Add(1) +} + func (m *Metrics) RecordGameStep() { m.steps.Add(1) } diff --git a/op-challenger2/metrics/noop.go b/op-challenger2/metrics/noop.go index 8519d6bab4b6..f53cf9f4c766 100644 --- a/op-challenger2/metrics/noop.go +++ b/op-challenger2/metrics/noop.go @@ -26,6 +26,7 @@ func (*NoopMetricsImpl) RecordInfo(version string) {} func (*NoopMetricsImpl) RecordUp() {} func (*NoopMetricsImpl) RecordGameMove() {} +func (*NoopMetricsImpl) RecordGameAttackV2() {} func (*NoopMetricsImpl) RecordGameStep() {} func (*NoopMetricsImpl) RecordGameL2Challenge() {} From 360b88b62f18b922684fec59cee49e51219fb348 Mon Sep 17 00:00:00 2001 From: Abe Orm Date: Sun, 10 Nov 2024 20:52:39 +0800 Subject: [PATCH 03/13] op-challenger2 add stepV2 --- .../game/fault/responder/responder.go | 8 ++++- .../game/fault/solver/game_solver.go | 33 ++++++++++--------- op-challenger2/game/fault/solver/solver.go | 8 ++--- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/op-challenger2/game/fault/responder/responder.go b/op-challenger2/game/fault/responder/responder.go index 66b5de7bd5d5..5d80bed69b9c 100644 --- a/op-challenger2/game/fault/responder/responder.go +++ b/op-challenger2/game/fault/responder/responder.go @@ -23,6 +23,7 @@ type GameContract interface { AttackV2Tx(ctx context.Context, parent types.Claim, attackBranch uint64, daType uint64, claims []byte) (txmgr.TxCandidate, error) DefendTx(ctx context.Context, parent types.Claim, pivot common.Hash) (txmgr.TxCandidate, error) StepTx(claimIdx uint64, isAttack bool, stateData []byte, proof []byte) (txmgr.TxCandidate, error) + StepV2Tx(claimIdx uint64, attackBranch uint64, stateData []byte, proof types.StepProof) (txmgr.TxCandidate, error) ChallengeL2BlockNumberTx(challenge *types.InvalidL2BlockNumberChallenge) (txmgr.TxCandidate, error) } @@ -127,7 +128,12 @@ func (r *FaultResponder) PerformAction(ctx context.Context, action types.Action) daTypeUint64 := (*big.Int)(action.DAType).Uint64() candidate, err = r.contract.AttackV2Tx(ctx, action.ParentClaim, action.AttackBranch, daTypeUint64, subValues) case types.ActionTypeStep: - candidate, err = r.contract.StepTx(uint64(action.ParentClaim.ContractIndex), action.IsAttack, action.PreState, action.ProofData) + stepProof := types.StepProof{ + PreStateItem: action.OracleData.VMStateDA.PreDA, + PostStateItem: action.OracleData.VMStateDA.PostDA, + VmProof: action.ProofData, + } + candidate, err = r.contract.StepV2Tx(uint64(action.ParentClaim.ContractIndex), action.AttackBranch, action.PreState, stepProof) case types.ActionTypeChallengeL2BlockNumber: candidate, err = r.contract.ChallengeL2BlockNumberTx(action.InvalidL2BlockNumberChallenge) } diff --git a/op-challenger2/game/fault/solver/game_solver.go b/op-challenger2/game/fault/solver/game_solver.go index c8a0283df606..5bdb2dca915b 100644 --- a/op-challenger2/game/fault/solver/game_solver.go +++ b/op-challenger2/game/fault/solver/game_solver.go @@ -77,25 +77,28 @@ func (s *GameSolver) calculateStep(ctx context.Context, game types.Game, claim t if claim.CounteredBy != (common.Address{}) { return nil, nil } - step, err := s.claimSolver.AttemptStep(ctx, game, claim, agreedClaims) - if err != nil { - return nil, err - } - if step == nil { - return nil, nil + for branch := range *claim.SubValues { + step, err := s.claimSolver.AttemptStep(ctx, game, claim, agreedClaims, uint64(branch)) + if err != nil { + return nil, err + } + if step == nil { + continue + } + return &types.Action{ + Type: types.ActionTypeStep, + ParentClaim: step.LeafClaim, + IsAttack: step.IsAttack, + PreState: step.PreState, + ProofData: step.ProofData, + OracleData: step.OracleData, + }, nil } - return &types.Action{ - Type: types.ActionTypeStep, - ParentClaim: step.LeafClaim, - IsAttack: step.IsAttack, - PreState: step.PreState, - ProofData: step.ProofData, - OracleData: step.OracleData, - }, nil + return nil, nil } func (s *GameSolver) calculateMove(ctx context.Context, game types.Game, claim types.Claim, honestClaims *honestClaimTracker) (*types.Action, error) { - for branch, _ := range *claim.SubValues { + for branch := range *claim.SubValues { if claim.Position.Depth() == game.SplitDepth()+types.Depth(game.NBits()) && branch != 0 { continue } diff --git a/op-challenger2/game/fault/solver/solver.go b/op-challenger2/game/fault/solver/solver.go index 8c683f80af97..48c99e69b8d6 100644 --- a/op-challenger2/game/fault/solver/solver.go +++ b/op-challenger2/game/fault/solver/solver.go @@ -101,7 +101,7 @@ type StepData struct { // AttemptStep determines what step, if any, should occur for a given leaf claim. // An error will be returned if the claim is not at the max depth. // Returns nil, nil if no step should be performed. -func (s *claimSolver) AttemptStep(ctx context.Context, game types.Game, claim types.Claim, honestClaims *honestClaimTracker) (*StepData, error) { +func (s *claimSolver) AttemptStep(ctx context.Context, game types.Game, claim types.Claim, honestClaims *honestClaimTracker, branch uint64) (*StepData, error) { if claim.Depth() != s.gameDepth { return nil, ErrStepNonLeafNode } @@ -112,7 +112,7 @@ func (s *claimSolver) AttemptStep(ctx context.Context, game types.Game, claim ty return nil, nil } - claimCorrect, err := s.agreeWithClaim(ctx, game, claim) + claimCorrect, err := s.agreeWithClaimV2(ctx, game, claim, branch) if err != nil { return nil, err } @@ -120,11 +120,11 @@ func (s *claimSolver) AttemptStep(ctx context.Context, game types.Game, claim ty var position types.Position if !claimCorrect { // Attack the claim by executing step index, so we need to get the pre-state of that index - position = claim.Position + position = claim.Position.MoveRightN(branch) } else { // Defend and use this claim as the starting point to execute the step after. // Thus, we need the pre-state of the next step. - position = claim.Position.MoveRight() + position = claim.Position.MoveRightN(branch + 1) } preState, proofData, oracleData, err := s.trace.GetStepData(ctx, game, claim, position) From 42d3a98737d9ec964fbe954266bc0b84c23ff4c8 Mon Sep 17 00:00:00 2001 From: Po Date: Fri, 15 Nov 2024 23:55:42 +0800 Subject: [PATCH 04/13] add getStepData tests for solver_test.go --- op-challenger2/game/fault/agent.go | 4 +- .../game/fault/solver/game_solver.go | 12 +- op-challenger2/game/fault/solver/solver.go | 36 +- .../game/fault/solver/solver_test.go | 420 +++++++++++++++++- op-challenger2/game/fault/test/alphabet.go | 41 +- .../game/fault/test/claim_builder.go | 15 +- op-challenger2/game/fault/trace/access.go | 5 - .../game/fault/trace/access_test.go | 15 - .../game/fault/trace/utils/daproof_test.go | 7 + op-challenger2/game/fault/types/types.go | 2 + 10 files changed, 473 insertions(+), 84 deletions(-) diff --git a/op-challenger2/game/fault/agent.go b/op-challenger2/game/fault/agent.go index 691a3381044d..42d9195e8de8 100644 --- a/op-challenger2/game/fault/agent.go +++ b/op-challenger2/game/fault/agent.go @@ -123,7 +123,7 @@ func (a *Agent) performAction(ctx context.Context, wg *sync.WaitGroup, action ty containsOracleData := action.OracleData != nil isLocal := containsOracleData && action.OracleData.IsLocal actionLog = actionLog.New( - "is_attack", action.IsAttack, + "attackBranch", action.AttackBranch, "parent", action.ParentClaim.ContractIndex, "prestate", common.Bytes2Hex(action.PreState), "proof", common.Bytes2Hex(action.ProofData), @@ -134,7 +134,7 @@ func (a *Agent) performAction(ctx context.Context, wg *sync.WaitGroup, action ty actionLog = actionLog.New("oracleKey", common.Bytes2Hex(action.OracleData.OracleKey)) } } else if action.Type == types.ActionTypeMove { - actionLog = actionLog.New("is_attack", action.IsAttack, "parent", action.ParentClaim.ContractIndex, "value", action.Value) + actionLog = actionLog.New("attackBranch", action.AttackBranch, "parent", action.ParentClaim.ContractIndex, "value", action.Value) } switch action.Type { diff --git a/op-challenger2/game/fault/solver/game_solver.go b/op-challenger2/game/fault/solver/game_solver.go index 5bdb2dca915b..507d9560b843 100644 --- a/op-challenger2/game/fault/solver/game_solver.go +++ b/op-challenger2/game/fault/solver/game_solver.go @@ -86,12 +86,12 @@ func (s *GameSolver) calculateStep(ctx context.Context, game types.Game, claim t continue } return &types.Action{ - Type: types.ActionTypeStep, - ParentClaim: step.LeafClaim, - IsAttack: step.IsAttack, - PreState: step.PreState, - ProofData: step.ProofData, - OracleData: step.OracleData, + Type: types.ActionTypeStep, + ParentClaim: step.LeafClaim, + AttackBranch: step.AttackBranch, + PreState: step.PreState, + ProofData: step.ProofData, + OracleData: step.OracleData, }, nil } return nil, nil diff --git a/op-challenger2/game/fault/solver/solver.go b/op-challenger2/game/fault/solver/solver.go index 48c99e69b8d6..58d4facf01ea 100644 --- a/op-challenger2/game/fault/solver/solver.go +++ b/op-challenger2/game/fault/solver/solver.go @@ -6,8 +6,8 @@ import ( "errors" "fmt" + "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/contracts" "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/types" - "github.com/ethereum-optimism/optimism/op-challenger2/game/keccak/merkle" "github.com/ethereum/go-ethereum/common" ) @@ -91,11 +91,11 @@ func (s *claimSolver) NextMove(ctx context.Context, claim types.Claim, game type } type StepData struct { - LeafClaim types.Claim - IsAttack bool - PreState []byte - ProofData []byte - OracleData *types.PreimageOracleData + LeafClaim types.Claim + AttackBranch uint64 + PreState []byte + ProofData []byte + OracleData *types.PreimageOracleData } // AttemptStep determines what step, if any, should occur for a given leaf claim. @@ -118,6 +118,7 @@ func (s *claimSolver) AttemptStep(ctx context.Context, game types.Game, claim ty } var position types.Position + attackBranch := branch if !claimCorrect { // Attack the claim by executing step index, so we need to get the pre-state of that index position = claim.Position.MoveRightN(branch) @@ -125,19 +126,20 @@ func (s *claimSolver) AttemptStep(ctx context.Context, game types.Game, claim ty // Defend and use this claim as the starting point to execute the step after. // Thus, we need the pre-state of the next step. position = claim.Position.MoveRightN(branch + 1) + attackBranch = branch + 1 } - preState, proofData, oracleData, err := s.trace.GetStepData(ctx, game, claim, position) + preState, proofData, oracleData, err := s.trace.GetStepData2(ctx, game, claim, position) if err != nil { return nil, err } return &StepData{ - LeafClaim: claim, - IsAttack: !claimCorrect, - PreState: preState, - ProofData: proofData, - OracleData: oracleData, + LeafClaim: claim, + AttackBranch: attackBranch, + PreState: preState, + ProofData: proofData, + OracleData: oracleData, }, nil } @@ -170,7 +172,7 @@ func (s *claimSolver) attackV2(ctx context.Context, game types.Game, claim types } values = append(values, value) } - hash := getClaimsHash(values) + hash := contracts.SubValuesHash(values) return &types.Claim{ ClaimData: types.ClaimData{Value: hash, Position: position}, ParentContractIndex: claim.ContractIndex, @@ -178,11 +180,3 @@ func (s *claimSolver) attackV2(ctx context.Context, game types.Game, claim types AttackBranch: branch, }, nil } - -func getClaimsHash(values []common.Hash) common.Hash { - tree := merkle.NewBinaryMerkleTree() - for i := 0; i < len(values); i++ { - tree.AddLeaf(values[i]) - } - return tree.RootHash() -} diff --git a/op-challenger2/game/fault/solver/solver_test.go b/op-challenger2/game/fault/solver/solver_test.go index 4fb5723fde8e..c42b45849fec 100644 --- a/op-challenger2/game/fault/solver/solver_test.go +++ b/op-challenger2/game/fault/solver/solver_test.go @@ -7,82 +7,120 @@ import ( faulttest "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/test" "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/trace" + "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/trace/alphabet" + "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/trace/split" "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/types" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" ) -func TestAttemptStep(t *testing.T) { - maxDepth := types.Depth(3) +func TestAttemptStepNary2(t *testing.T) { + maxDepth := types.Depth(7) startingL2BlockNumber := big.NewInt(0) nbits := uint64(1) splitDepth := types.Depth(3) claimBuilder := faulttest.NewAlphabetClaimBuilder2(t, startingL2BlockNumber, maxDepth, nbits, splitDepth) + traceDepth := maxDepth - splitDepth - types.Depth(nbits) // Last accessible leaf is the second last trace index // The root node is used for the last trace index and can only be attacked. - lastLeafTraceIndex := big.NewInt(1< Date: Sat, 16 Nov 2024 21:06:11 +0800 Subject: [PATCH 05/13] modify test --- .../game/fault/solver/game_solver.go | 3 + .../game/fault/solver/game_solver_test.go | 112 +++++++++++++++++- op-challenger2/game/fault/solver/rules.go | 25 ++-- op-challenger2/game/fault/solver/solver.go | 12 +- .../game/fault/solver/solver_test.go | 3 +- .../game/fault/test/claim_builder.go | 54 +++++++-- .../game/fault/test/game_builder.go | 44 +++++++ 7 files changed, 213 insertions(+), 40 deletions(-) diff --git a/op-challenger2/game/fault/solver/game_solver.go b/op-challenger2/game/fault/solver/game_solver.go index 5bdb2dca915b..51c098010026 100644 --- a/op-challenger2/game/fault/solver/game_solver.go +++ b/op-challenger2/game/fault/solver/game_solver.go @@ -102,6 +102,9 @@ func (s *GameSolver) calculateMove(ctx context.Context, game types.Game, claim t if claim.Position.Depth() == game.SplitDepth()+types.Depth(game.NBits()) && branch != 0 { continue } + if claim.IsRoot() && branch != 0 { + continue + } move, err := s.claimSolver.NextMove(ctx, claim, game, honestClaims, uint64(branch)) if err != nil { return nil, fmt.Errorf("failed to calculate next move for claim index %v: %w", claim.ContractIndex, err) diff --git a/op-challenger2/game/fault/solver/game_solver_test.go b/op-challenger2/game/fault/solver/game_solver_test.go index 9eb9a1a41e50..87930cb0a155 100644 --- a/op-challenger2/game/fault/solver/game_solver_test.go +++ b/op-challenger2/game/fault/solver/game_solver_test.go @@ -45,7 +45,7 @@ func TestCalculateNextActions_ChallengeL2BlockNumber(t *testing.T) { func TestCalculateNextActions(t *testing.T) { maxDepth := types.Depth(6) startingL2BlockNumber := big.NewInt(0) - nbits := uint64(1) + nbits := uint64(2) splitDepth := types.Depth(3) claimBuilder := faulttest.NewAlphabetClaimBuilder2(t, startingL2BlockNumber, maxDepth, nbits, splitDepth) @@ -215,11 +215,13 @@ func TestCalculateNextActions(t *testing.T) { t.Logf("Expect %v: Type: %v, ParentIdx: %v, Attack: %v, Value: %v, PreState: %v, ProofData: %v", i, action.Type, action.ParentClaim.ContractIndex, action.IsAttack, action.Value, hex.EncodeToString(action.PreState), hex.EncodeToString(action.ProofData)) require.Containsf(t, actions, action, "Expected claim %v missing", i) + break } require.Len(t, actions, len(builder.ExpectedActions), "Incorrect number of actions") verifyGameRules(t, postState, test.rootClaimCorrect) }) + break } } @@ -364,3 +366,111 @@ func applyActions(game types.Game, claimant common.Address, actions []types.Acti } return types.NewGameState2(claims, game.MaxDepth(), game.NBits(), game.SplitDepth()) } + +func TestCalculateNextActions2(t *testing.T) { + maxDepth := types.Depth(8) + startingL2BlockNumber := big.NewInt(0) + nbits := uint64(2) + splitDepth := types.Depth(4) + claimBuilder := faulttest.NewAlphabetClaimBuilder2(t, startingL2BlockNumber, maxDepth, nbits, splitDepth) + + tests := []struct { + name string + rootClaimCorrect bool + setupGame func(builder *faulttest.GameBuilder) + }{ + /* + { + name: "AttackRootClaim", + setupGame: func(builder *faulttest.GameBuilder) { + builder.Seq().ExpectAttackV2(0) + }, + }, + { + name: "DoNotAttackCorrectRootClaim_AgreeWithOutputRoot", + rootClaimCorrect: true, + setupGame: func(builder *faulttest.GameBuilder) {}, + }, + { + name: "DoNotPerformDuplicateMoves", + setupGame: func(builder *faulttest.GameBuilder) { + // Expected move has already been made. + builder.Seq().Attack() + }, + }, + */ + { + name: "RespondToAllClaimsAtDisagreeingLevel", + setupGame: func(builder *faulttest.GameBuilder) { + honestClaim := builder.Seq().Attack2(nil, 0) + honestClaim.Attack2(nil, 0).ExpectAttackV2(0) + }, + }, + } + + for _, test := range tests { + test := test + t.Run(test.name, func(t *testing.T) { + builder := claimBuilder.GameBuilder(faulttest.WithInvalidValue(!test.rootClaimCorrect)) + test.setupGame(builder) + game := builder.Game + solver := NewGameSolver(maxDepth, trace.NewSimpleTraceAccessor(claimBuilder.CorrectTraceProvider()), types.CallDataType) + postState, actions := runStep2(t, solver, game, claimBuilder.CorrectTraceProvider()) + for i, action := range builder.ExpectedActions { + t.Logf("Expect %v: Type: %v, ParentIdx: %v, Attack: %v, Value: %v, PreState: %v, ProofData: %v", + i, action.Type, action.ParentClaim.ContractIndex, action.IsAttack, action.Value, hex.EncodeToString(action.PreState), hex.EncodeToString(action.ProofData)) + require.Containsf(t, actions, action, "Expected claim %v missing", i) + break + } + require.Len(t, actions, len(builder.ExpectedActions), "Incorrect number of actions") + + verifyGameRules(t, postState, test.rootClaimCorrect) + }) + } +} + +func runStep2(t *testing.T, solver *GameSolver, game types.Game, correctTraceProvider types.TraceProvider) (types.Game, []types.Action) { + actions, err := solver.CalculateNextActions(context.Background(), game) + t.Logf("runStep2 actions: %v", actions) + require.NoError(t, err) + + postState := applyActions2(game, challengerAddr, actions) + + for i, action := range actions { + t.Logf("Move %v: Type: %v, ParentIdx: %v, Attack: %v, Value: %v, PreState: %v, ProofData: %v", + i, action.Type, action.ParentClaim.ContractIndex, action.IsAttack, action.Value, hex.EncodeToString(action.PreState), hex.EncodeToString(action.ProofData)) + // Check that every move the solver returns meets the generic validation rules + require.NoError(t, checkRules(game, action, correctTraceProvider), "Attempting to perform invalid action") + } + return postState, actions +} + +func applyActions2(game types.Game, claimant common.Address, actions []types.Action) types.Game { + claims := game.Claims() + for _, action := range actions { + switch action.Type { + case types.ActionTypeAttackV2: + newPosition := action.ParentClaim.Position.MoveN(game.NBits(), action.AttackBranch) + claim := types.Claim{ + ClaimData: types.ClaimData{ + Value: action.Value, + Bond: big.NewInt(0), + Position: newPosition, + }, + Claimant: claimant, + ContractIndex: len(claims), + ParentContractIndex: action.ParentClaim.ContractIndex, + SubValues: action.SubValues, + AttackBranch: action.AttackBranch, + } + claims = append(claims, claim) + case types.ActionTypeStep: + counteredClaim := claims[action.ParentClaim.ContractIndex] + counteredClaim.CounteredBy = claimant + claims[action.ParentClaim.ContractIndex] = counteredClaim + default: + panic(fmt.Errorf("unknown move type: %v", action.Type)) + } + } + return types.NewGameState2(claims, game.MaxDepth(), game.NBits(), game.SplitDepth()) +} diff --git a/op-challenger2/game/fault/solver/rules.go b/op-challenger2/game/fault/solver/rules.go index 88d790296a78..8a63d8d3e138 100644 --- a/op-challenger2/game/fault/solver/rules.go +++ b/op-challenger2/game/fault/solver/rules.go @@ -20,10 +20,10 @@ type actionRule func(game types.Game, action types.Action, correctTrace types.Tr var rules = []actionRule{ parentMustExist, onlyStepAtMaxDepth, - onlyMoveBeforeMaxDepth, + onlyAttackBeforeMaxDepth, doNotDuplicateExistingMoves, doNotStepAlreadyCounteredClaims, - doNotDefendRootClaim, + onlyAttackRootClaimZeroBranch, avoidPoisonedPrestate, detectPoisonedStepPrestate, detectFailedStep, @@ -66,15 +66,13 @@ func onlyStepAtMaxDepth(game types.Game, action types.Action, _ types.TraceProvi return nil } -// onlyMoveBeforeMaxDepth verifies that move actions are not performed against leaf claims -// Rationale: The action would be rejected by the contracts -func onlyMoveBeforeMaxDepth(game types.Game, action types.Action, _ types.TraceProvider) error { - if action.Type == types.ActionTypeMove { +func onlyAttackBeforeMaxDepth(game types.Game, action types.Action, _ types.TraceProvider) error { + if action.Type == types.ActionTypeAttackV2 { return nil } parentDepth := game.Claims()[action.ParentClaim.ContractIndex].Position.Depth() if parentDepth < game.MaxDepth() { - return fmt.Errorf("parent (%v) not at max depth (%v) but attempting to perform %v action instead of move", + return fmt.Errorf("parent (%v) not at max depth (%v) but attempting to perform %v action instead of attackV2", parentDepth, game.MaxDepth(), action.Type) } return nil @@ -103,11 +101,9 @@ func doNotStepAlreadyCounteredClaims(game types.Game, action types.Action, _ typ return nil } -// doNotDefendRootClaim checks the challenger doesn't attempt to defend the root claim -// Rationale: The action would be rejected by the contracts -func doNotDefendRootClaim(game types.Game, action types.Action, _ types.TraceProvider) error { - if game.Claims()[action.ParentClaim.ContractIndex].IsRootPosition() && !action.IsAttack { - return fmt.Errorf("defending the root claim at idx %v", action.ParentClaim.ContractIndex) +func onlyAttackRootClaimZeroBranch(game types.Game, action types.Action, _ types.TraceProvider) error { + if game.Claims()[action.ParentClaim.ContractIndex].IsRootPosition() && action.AttackBranch != 0 { + return fmt.Errorf("attacking the root claim at idx %v with branch %v", action.ParentClaim.ContractIndex, action.AttackBranch) } return nil } @@ -272,8 +268,5 @@ func resultingPosition(game types.Game, action types.Action) types.Position { if action.Type == types.ActionTypeStep { return parentPos } - if action.IsAttack { - return parentPos.Attack() - } - return parentPos.Defend() + return parentPos.MoveN(uint64(game.NBits()), action.AttackBranch) } diff --git a/op-challenger2/game/fault/solver/solver.go b/op-challenger2/game/fault/solver/solver.go index 48c99e69b8d6..48920927dd38 100644 --- a/op-challenger2/game/fault/solver/solver.go +++ b/op-challenger2/game/fault/solver/solver.go @@ -6,8 +6,8 @@ import ( "errors" "fmt" + "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/contracts" "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/types" - "github.com/ethereum-optimism/optimism/op-challenger2/game/keccak/merkle" "github.com/ethereum/go-ethereum/common" ) @@ -170,7 +170,7 @@ func (s *claimSolver) attackV2(ctx context.Context, game types.Game, claim types } values = append(values, value) } - hash := getClaimsHash(values) + hash := contracts.SubValuesHash(values) return &types.Claim{ ClaimData: types.ClaimData{Value: hash, Position: position}, ParentContractIndex: claim.ContractIndex, @@ -178,11 +178,3 @@ func (s *claimSolver) attackV2(ctx context.Context, game types.Game, claim types AttackBranch: branch, }, nil } - -func getClaimsHash(values []common.Hash) common.Hash { - tree := merkle.NewBinaryMerkleTree() - for i := 0; i < len(values); i++ { - tree.AddLeaf(values[i]) - } - return tree.RootHash() -} diff --git a/op-challenger2/game/fault/solver/solver_test.go b/op-challenger2/game/fault/solver/solver_test.go index 4fb5723fde8e..1106f73c176b 100644 --- a/op-challenger2/game/fault/solver/solver_test.go +++ b/op-challenger2/game/fault/solver/solver_test.go @@ -179,7 +179,8 @@ func TestAttemptStep(t *testing.T) { grandParentClaim := claims[parentClaim.ParentContractIndex] agreedClaims.AddHonestClaim(grandParentClaim, parentClaim) } - step, err := alphabetSolver.AttemptStep(ctx, game, lastClaim, agreedClaims) + // todo: add test for oracle data + step, err := alphabetSolver.AttemptStep(ctx, game, lastClaim, agreedClaims, 0) require.ErrorIs(t, err, tableTest.expectedErr) if !tableTest.expectNoStep && tableTest.expectedErr == nil { require.NotNil(t, step) diff --git a/op-challenger2/game/fault/test/claim_builder.go b/op-challenger2/game/fault/test/claim_builder.go index 91d07d6d1c0d..8b015a257fe9 100644 --- a/op-challenger2/game/fault/test/claim_builder.go +++ b/op-challenger2/game/fault/test/claim_builder.go @@ -2,6 +2,7 @@ package test import ( "context" + "fmt" "math" "math/big" "testing" @@ -161,22 +162,35 @@ func (c *ClaimBuilder) claim(pos types.Position, opts ...ClaimOpt) types.Claim { }, AttackBranch: cfg.branch, } - if cfg.claimant != (common.Address{}) { - claim.Claimant = cfg.claimant - } - if cfg.value != (common.Hash{}) { - claim.Value = cfg.value - } else if cfg.invalidValue { - claim.Value = c.incorrectClaim(pos) - } else { - claim.Value = c.CorrectClaimAtPosition(pos) - // when nbits is 1, subValues is also filled with claim.Value - claim.SubValues = &[]common.Hash{claim.Value} - } if cfg.subValues != nil { claim.SubValues = cfg.subValues + claim.Value = contracts.SubValuesHash(*claim.SubValues) + } else { + values := []common.Hash{} + if pos.ToGIndex().Cmp(big.NewInt(1)) == 0 { + if cfg.invalidValue { + values = append(values, c.incorrectClaim(pos)) + } else { + values = append(values, c.CorrectClaimAtPosition(pos)) + } + for i := uint64(0); i < c.MaxAttackBranch()-1; i++ { + values = append(values, common.Hash{}) + } + } else { + for i := uint64(0); i < c.MaxAttackBranch(); i++ { + pos := pos.MoveRightN(i) + if cfg.invalidValue { + values = append(values, c.incorrectClaim(pos)) + } else { + values = append(values, c.CorrectClaimAtPosition(pos)) + } + } + } + claim.SubValues = &values + claim.Value = contracts.SubValuesHash(*claim.SubValues) } claim.ParentContractIndex = cfg.parentIdx + fmt.Printf("claim.Value: %v\n", claim.Value) return claim } @@ -205,3 +219,19 @@ func (c *ClaimBuilder) DefendClaim(claim types.Claim, opts ...ClaimOpt) types.Cl pos := claim.Position.Defend() return c.claim(pos, append([]ClaimOpt{WithParent(claim)}, opts...)...) } + +func (c *ClaimBuilder) NBits() uint64 { + return c.nbits +} + +func (c *ClaimBuilder) MaxAttackBranch() uint64 { + return 1< Date: Sat, 16 Nov 2024 23:28:32 +0800 Subject: [PATCH 06/13] fix step maxAttackBranch test case --- op-challenger2/game/fault/solver/solver.go | 3 + .../game/fault/solver/solver_test.go | 74 ++++++++++++------- 2 files changed, 51 insertions(+), 26 deletions(-) diff --git a/op-challenger2/game/fault/solver/solver.go b/op-challenger2/game/fault/solver/solver.go index 58d4facf01ea..f0323db940e7 100644 --- a/op-challenger2/game/fault/solver/solver.go +++ b/op-challenger2/game/fault/solver/solver.go @@ -150,6 +150,9 @@ func (s *claimSolver) agreeWithClaim(ctx context.Context, game types.Game, claim } func (s *claimSolver) agreeWithClaimV2(ctx context.Context, game types.Game, claim types.Claim, branch uint64) (bool, error) { + if branch >= uint64(len(*claim.SubValues)) { + return true, fmt.Errorf("branch must be lesser than maxAttachBranch") + } ourValue, err := s.trace.Get(ctx, game, claim, claim.Position.MoveRightN(branch)) return bytes.Equal(ourValue[:], (*claim.SubValues)[branch][:]), err } diff --git a/op-challenger2/game/fault/solver/solver_test.go b/op-challenger2/game/fault/solver/solver_test.go index c42b45849fec..ce92b62ee9c9 100644 --- a/op-challenger2/game/fault/solver/solver_test.go +++ b/op-challenger2/game/fault/solver/solver_test.go @@ -11,6 +11,7 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/trace/split" "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/types" "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" ) @@ -370,12 +371,7 @@ func TestAttemptStepNary4(t *testing.T) { nbits := uint64(2) splitDepth := types.Depth(2) claimBuilder := faulttest.NewAlphabetClaimBuilder2(t, startingL2BlockNumber, maxDepth, nbits, splitDepth) - // traceDepth := maxDepth - splitDepth - types.Depth(nbits) - // Last accessible leaf is the second last trace index - // The root node is used for the last trace index and can only be attacked. - // lastLeafTraceIndex := big.NewInt(1< Date: Sun, 17 Nov 2024 20:59:51 +0800 Subject: [PATCH 07/13] fix bug by rules --- op-challenger2/game/fault/solver/rules.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/op-challenger2/game/fault/solver/rules.go b/op-challenger2/game/fault/solver/rules.go index 8a63d8d3e138..41c11f10a064 100644 --- a/op-challenger2/game/fault/solver/rules.go +++ b/op-challenger2/game/fault/solver/rules.go @@ -159,7 +159,8 @@ func avoidPoisonedPrestate(game types.Game, action types.Action, correctTrace ty if err != nil { return fmt.Errorf("failed to get correct trace at position %v: %w", preStateClaim.Position, err) } - if correctValue != preStateClaim.Value { + preStateClaimValue := (*preStateClaim.SubValues)[0] + if correctValue != preStateClaimValue { err = fmt.Errorf("prestate poisoned claim %v has invalid prestate and is left of honest claim countering %v at trace index %v", preStateClaim.ContractIndex, action.ParentClaim.ContractIndex, honestTraceIndex) return err } From 0805fd444ad46cef016304e8bee1fdbd77e5938a Mon Sep 17 00:00:00 2001 From: Po Date: Fri, 22 Nov 2024 00:18:46 +0800 Subject: [PATCH 08/13] add getstepData testcase --- op-challenger2/game/fault/solver/solver_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/op-challenger2/game/fault/solver/solver_test.go b/op-challenger2/game/fault/solver/solver_test.go index ce92b62ee9c9..10d93a3f08fd 100644 --- a/op-challenger2/game/fault/solver/solver_test.go +++ b/op-challenger2/game/fault/solver/solver_test.go @@ -557,7 +557,8 @@ func TestAttemptStepNary4(t *testing.T) { t.Run(tableTest.name, func(t *testing.T) { builder := claimBuilder.GameBuilder(faulttest.WithInvalidValue(tableTest.agreeWithOutputRoot)) tableTest.setupGame(builder) - alphabetSolver := newClaimSolver(maxDepth, trace.NewSimpleTraceAccessor(claimBuilder.CorrectTraceProvider()), types.CallDataType) + accessor := trace.NewSimpleTraceAccessor(claimBuilder.CorrectTraceProvider()) + alphabetSolver := newClaimSolver(maxDepth, accessor, types.CallDataType) game := builder.Game claims := game.Claims() lastClaim := claims[len(claims)-1] @@ -572,6 +573,8 @@ func TestAttemptStepNary4(t *testing.T) { } step, err := alphabetSolver.AttemptStep(ctx, game, lastClaim, agreedClaims, tableTest.attackBranch) require.ErrorIs(t, err, tableTest.expectedErr) + _, _, preimage, err := accessor.GetStepData2(ctx, game, lastClaim, lastClaim.MoveRightN(tableTest.expectAttackBranch)) + require.NoError(t, err) if !tableTest.expectNoStep && tableTest.expectedErr == nil { require.NotNil(t, step) require.Equal(t, lastClaim, step.LeafClaim) @@ -584,6 +587,8 @@ func TestAttemptStepNary4(t *testing.T) { require.Equal(t, tableTest.expectedOracleData.OracleOffset, step.OracleData.OracleOffset) require.Equal(t, tableTest.expectedVMStateData.PreDA, step.OracleData.VMStateDA.PreDA) require.Equal(t, tableTest.expectedVMStateData.PostDA, step.OracleData.VMStateDA.PostDA) + require.Equal(t, tableTest.expectedVMStateData.PreDA, preimage.VMStateDA.PreDA) + require.Equal(t, tableTest.expectedVMStateData.PostDA, preimage.VMStateDA.PostDA) require.Equal(t, tableTest.expectedLocalData.DaType, step.OracleData.OutputRootDAItem.DaType) require.Equal(t, tableTest.expectedLocalData.DataHash, step.OracleData.OutputRootDAItem.DataHash) require.Equal(t, tableTest.expectedLocalData.Proof, step.OracleData.OutputRootDAItem.Proof) From f65fdc3ca32062ac107287421738a30ca86440c4 Mon Sep 17 00:00:00 2001 From: Abe Orm Date: Sat, 23 Nov 2024 03:36:07 +0800 Subject: [PATCH 09/13] modify unittest TestCalculateNextActions --- .../game/fault/solver/game_solver.go | 6 +- .../game/fault/solver/game_solver_test.go | 528 +++++++++++------- op-challenger2/game/fault/solver/rules.go | 11 +- op-challenger2/game/fault/solver/solver.go | 18 +- .../game/fault/test/claim_builder.go | 57 +- .../game/fault/test/game_builder.go | 19 +- 6 files changed, 386 insertions(+), 253 deletions(-) diff --git a/op-challenger2/game/fault/solver/game_solver.go b/op-challenger2/game/fault/solver/game_solver.go index ac5f95ec0383..5f8ad8c579c1 100644 --- a/op-challenger2/game/fault/solver/game_solver.go +++ b/op-challenger2/game/fault/solver/game_solver.go @@ -20,7 +20,7 @@ func NewGameSolver(gameDepth types.Depth, trace types.TraceAccessor, daType type } func (s *GameSolver) AgreeWithRootClaim(ctx context.Context, game types.Game) (bool, error) { - return s.claimSolver.agreeWithClaim(ctx, game, game.Claims()[0]) + return s.claimSolver.agreeWithClaimV2(ctx, game, game.Claims()[0], 0) } func (s *GameSolver) CalculateNextActions(ctx context.Context, game types.Game) ([]types.Action, error) { @@ -114,14 +114,14 @@ func (s *GameSolver) calculateMove(ctx context.Context, game types.Game, claim t } honestClaims.AddHonestClaim(claim, *move) if game.IsDuplicate(*move) { - continue + break } return &types.Action{ Type: types.ActionTypeAttackV2, ParentClaim: game.Claims()[move.ParentContractIndex], Value: move.Value, SubValues: move.SubValues, - AttackBranch: uint64(branch), + AttackBranch: move.AttackBranch, DAType: s.claimSolver.daType, }, nil } diff --git a/op-challenger2/game/fault/solver/game_solver_test.go b/op-challenger2/game/fault/solver/game_solver_test.go index 87930cb0a155..e36f09da7042 100644 --- a/op-challenger2/game/fault/solver/game_solver_test.go +++ b/op-challenger2/game/fault/solver/game_solver_test.go @@ -17,12 +17,12 @@ import ( func TestCalculateNextActions_ChallengeL2BlockNumber(t *testing.T) { startingBlock := big.NewInt(5) - maxDepth := types.Depth(6) + maxDepth := types.Depth(8) challenge := &types.InvalidL2BlockNumberChallenge{ Output: ð.OutputResponse{OutputRoot: eth.Bytes32{0xbb}}, } - nbits := uint64(1) - splitDepth := types.Depth(3) + nbits := uint64(2) + splitDepth := types.Depth(4) claimBuilder := faulttest.NewAlphabetClaimBuilder2(t, startingBlock, maxDepth, nbits, splitDepth) traceProvider := faulttest.NewAlphabetWithProofProvider(t, startingBlock, maxDepth, nil, 0, faulttest.OracleDefaultKey) solver := NewGameSolver(maxDepth, trace.NewSimpleTraceAccessor(traceProvider), types.CallDataType) @@ -42,189 +42,6 @@ func TestCalculateNextActions_ChallengeL2BlockNumber(t *testing.T) { require.Equal(t, challenge, action.InvalidL2BlockNumberChallenge) } -func TestCalculateNextActions(t *testing.T) { - maxDepth := types.Depth(6) - startingL2BlockNumber := big.NewInt(0) - nbits := uint64(2) - splitDepth := types.Depth(3) - claimBuilder := faulttest.NewAlphabetClaimBuilder2(t, startingL2BlockNumber, maxDepth, nbits, splitDepth) - - tests := []struct { - name string - rootClaimCorrect bool - setupGame func(builder *faulttest.GameBuilder) - }{ - { - name: "AttackRootClaim", - setupGame: func(builder *faulttest.GameBuilder) { - builder.Seq().ExpectAttack() - }, - }, - { - name: "DoNotAttackCorrectRootClaim_AgreeWithOutputRoot", - rootClaimCorrect: true, - setupGame: func(builder *faulttest.GameBuilder) {}, - }, - { - name: "DoNotPerformDuplicateMoves", - setupGame: func(builder *faulttest.GameBuilder) { - // Expected move has already been made. - builder.Seq().Attack() - }, - }, - { - name: "RespondToAllClaimsAtDisagreeingLevel", - setupGame: func(builder *faulttest.GameBuilder) { - honestClaim := builder.Seq().Attack() - honestClaim.Attack().ExpectDefend() - honestClaim.Defend().ExpectDefend() - honestClaim.Attack(faulttest.WithValue(common.Hash{0xaa})).ExpectAttack() - honestClaim.Attack(faulttest.WithValue(common.Hash{0xbb})).ExpectAttack() - honestClaim.Defend(faulttest.WithValue(common.Hash{0xcc})).ExpectAttack() - honestClaim.Defend(faulttest.WithValue(common.Hash{0xdd})).ExpectAttack() - }, - }, - { - name: "StepAtMaxDepth", - setupGame: func(builder *faulttest.GameBuilder) { - lastHonestClaim := builder.Seq(). - Attack(). - Attack(). - Defend(). - Defend(). - Defend() - lastHonestClaim.Attack().ExpectStepDefend() - lastHonestClaim.Attack(faulttest.WithValue(common.Hash{0xdd})).ExpectStepAttack() - }, - }, - { - name: "PoisonedPreState", - setupGame: func(builder *faulttest.GameBuilder) { - // A claim hash that has no pre-image - maliciousStateHash := common.Hash{0x01, 0xaa} - - // Dishonest actor counters their own claims to set up a situation with an invalid prestate - // The honest actor should ignore path created by the dishonest actor, only supporting its own attack on the root claim - honestMove := builder.Seq().Attack() // This expected action is the winning move. - dishonestMove := honestMove.Attack(faulttest.WithValue(maliciousStateHash)) - // The expected action by the honest actor - dishonestMove.ExpectAttack() - // The honest actor will ignore this poisoned path - dishonestMove. - Defend(faulttest.WithValue(maliciousStateHash)). - Attack(faulttest.WithValue(maliciousStateHash)) - }, - }, - { - name: "Freeloader-ValidClaimAtInvalidAttackPosition", - setupGame: func(builder *faulttest.GameBuilder) { - builder.Seq(). - Attack(). // Honest response to invalid root - Defend().ExpectDefend(). // Defender agrees at this point, we should defend - Attack().ExpectDefend() // Freeloader attacks instead of defends - }, - }, - { - name: "Freeloader-InvalidClaimAtInvalidAttackPosition", - setupGame: func(builder *faulttest.GameBuilder) { - builder.Seq(). - Attack(). // Honest response to invalid root - Defend().ExpectDefend(). // Defender agrees at this point, we should defend - Attack(faulttest.WithValue(common.Hash{0xbb})).ExpectAttack() // Freeloader attacks with wrong claim instead of defends - }, - }, - { - name: "Freeloader-InvalidClaimAtValidDefensePosition", - setupGame: func(builder *faulttest.GameBuilder) { - builder.Seq(). - Attack(). // Honest response to invalid root - Defend().ExpectDefend(). // Defender agrees at this point, we should defend - Defend(faulttest.WithValue(common.Hash{0xbb})).ExpectAttack() // Freeloader defends with wrong claim, we should attack - }, - }, - { - name: "Freeloader-InvalidClaimAtValidAttackPosition", - setupGame: func(builder *faulttest.GameBuilder) { - builder.Seq(). - Attack(). // Honest response to invalid root - Defend(faulttest.WithValue(common.Hash{0xaa})).ExpectAttack(). // Defender disagrees at this point, we should attack - Attack(faulttest.WithValue(common.Hash{0xbb})).ExpectAttack() // Freeloader attacks with wrong claim instead of defends - }, - }, - { - name: "Freeloader-InvalidClaimAtInvalidDefensePosition", - setupGame: func(builder *faulttest.GameBuilder) { - builder.Seq(). - Attack(). // Honest response to invalid root - Defend(faulttest.WithValue(common.Hash{0xaa})).ExpectAttack(). // Defender disagrees at this point, we should attack - Defend(faulttest.WithValue(common.Hash{0xbb})) // Freeloader defends with wrong claim but we must not respond to avoid poisoning - }, - }, - { - name: "Freeloader-ValidClaimAtInvalidAttackPosition-RespondingToDishonestButCorrectAttack", - setupGame: func(builder *faulttest.GameBuilder) { - builder.Seq(). - Attack(). // Honest response to invalid root - Attack().ExpectDefend(). // Defender attacks with correct value, we should defend - Attack().ExpectDefend() // Freeloader attacks with wrong claim, we should defend - }, - }, - { - name: "Freeloader-DoNotCounterOwnClaim", - setupGame: func(builder *faulttest.GameBuilder) { - builder.Seq(). - Attack(). // Honest response to invalid root - Attack().ExpectDefend(). // Defender attacks with correct value, we should defend - Attack(). // Freeloader attacks instead, we should defend - Defend() // We do defend and we shouldn't counter our own claim - }, - }, - { - name: "Freeloader-ContinueDefendingAgainstFreeloader", - setupGame: func(builder *faulttest.GameBuilder) { - builder.Seq(). // invalid root - Attack(). // Honest response to invalid root - Attack().ExpectDefend(). // Defender attacks with correct value, we should defend - Attack(). // Freeloader attacks instead, we should defend - Defend(). // We do defend - Attack(faulttest.WithValue(common.Hash{0xaa})). // freeloader attacks our defense, we should attack - ExpectAttack() - }, - }, - { - name: "Freeloader-FreeloaderCountersRootClaim", - setupGame: func(builder *faulttest.GameBuilder) { - builder.Seq(). - ExpectAttack(). // Honest response to invalid root - Attack(faulttest.WithValue(common.Hash{0xaa})). // freeloader - ExpectAttack() // Honest response to freeloader - }, - }, - } - - for _, test := range tests { - test := test - t.Run(test.name, func(t *testing.T) { - builder := claimBuilder.GameBuilder(faulttest.WithInvalidValue(!test.rootClaimCorrect)) - test.setupGame(builder) - game := builder.Game - - solver := NewGameSolver(maxDepth, trace.NewSimpleTraceAccessor(claimBuilder.CorrectTraceProvider()), types.CallDataType) - postState, actions := runStep(t, solver, game, claimBuilder.CorrectTraceProvider()) - for i, action := range builder.ExpectedActions { - t.Logf("Expect %v: Type: %v, ParentIdx: %v, Attack: %v, Value: %v, PreState: %v, ProofData: %v", - i, action.Type, action.ParentClaim.ContractIndex, action.IsAttack, action.Value, hex.EncodeToString(action.PreState), hex.EncodeToString(action.ProofData)) - require.Containsf(t, actions, action, "Expected claim %v missing", i) - break - } - require.Len(t, actions, len(builder.ExpectedActions), "Incorrect number of actions") - - verifyGameRules(t, postState, test.rootClaimCorrect) - }) - break - } -} - func runStep(t *testing.T, solver *GameSolver, game types.Game, correctTraceProvider types.TraceProvider) (types.Game, []types.Action) { actions, err := solver.CalculateNextActions(context.Background(), game) require.NoError(t, err) @@ -367,7 +184,7 @@ func applyActions(game types.Game, claimant common.Address, actions []types.Acti return types.NewGameState2(claims, game.MaxDepth(), game.NBits(), game.SplitDepth()) } -func TestCalculateNextActions2(t *testing.T) { +func TestCalculateNextActions(t *testing.T) { maxDepth := types.Depth(8) startingL2BlockNumber := big.NewInt(0) nbits := uint64(2) @@ -379,31 +196,244 @@ func TestCalculateNextActions2(t *testing.T) { rootClaimCorrect bool setupGame func(builder *faulttest.GameBuilder) }{ - /* - { - name: "AttackRootClaim", - setupGame: func(builder *faulttest.GameBuilder) { - builder.Seq().ExpectAttackV2(0) - }, - }, - { - name: "DoNotAttackCorrectRootClaim_AgreeWithOutputRoot", - rootClaimCorrect: true, - setupGame: func(builder *faulttest.GameBuilder) {}, + { + name: "AttackRootClaim", + setupGame: func(builder *faulttest.GameBuilder) { + builder.Seq().ExpectAttackV2(0) }, - { - name: "DoNotPerformDuplicateMoves", - setupGame: func(builder *faulttest.GameBuilder) { - // Expected move has already been made. - builder.Seq().Attack() - }, + }, + { + name: "DoNotAttackCorrectRootClaim_AgreeWithOutputRoot", + rootClaimCorrect: true, + setupGame: func(builder *faulttest.GameBuilder) {}, + }, + { + name: "DoNotPerformDuplicateMoves", + setupGame: func(builder *faulttest.GameBuilder) { + builder.Seq().Attack2(nil, 0) }, - */ + }, { name: "RespondToAllClaimsAtDisagreeingLevel", setupGame: func(builder *faulttest.GameBuilder) { honestClaim := builder.Seq().Attack2(nil, 0) - honestClaim.Attack2(nil, 0).ExpectAttackV2(0) + honestClaim.Attack2(nil, 0).ExpectAttackV2(3) + }, + }, + { + name: "StepAtMaxDepth", + setupGame: func(builder *faulttest.GameBuilder) { + values := []common.Hash{{0x81}, {0x82}, {0x83}} + lastHonestClaim := builder.Seq(). + Attack2(nil, 0). // honest + Attack2(values, 3). // dishonest + Attack2(nil, 0) // honest + lastHonestClaim.Attack2([]common.Hash{{0x1}, {0x2}, {0x3}}, 0).ExpectStepV2(0) + }, + }, + { + name: "PoisonedPreState", + setupGame: func(builder *faulttest.GameBuilder) { + values := []common.Hash{{0x81}, {0x82}, {0x83}} + honest := builder.Seq().Attack2(nil, 0) + dishonest := honest.Attack2(values, 0) + dishonest.ExpectAttackV2(0) + dishonest.Attack2(values, 0) + dishonest.ExpectAttackV2(0) + }, + }, + { + name: "HonestRoot-OneLevelAttack", + rootClaimCorrect: true, + setupGame: func(builder *faulttest.GameBuilder) { + values := []common.Hash{{0x81}, {0x82}, {0x83}} + honest := builder.Seq() + honest.Attack2(nil, 0).ExpectAttackV2(3) + honest.Attack2(values, 0).ExpectAttackV2(0) + values = claimBuilder.GetClaimsAtPosition(builder.Game.RootClaim().Position.MoveN(nbits, 0), []uint64{1}) + honest.Attack2(values, 0).ExpectAttackV2(1) + values = claimBuilder.GetClaimsAtPosition(builder.Game.RootClaim().Position.MoveN(nbits, 0), []uint64{2}) + honest.Attack2(values, 0).ExpectAttackV2(2) + }, + }, + { + name: "DishonestRoot-OneLevelAttack", + setupGame: func(builder *faulttest.GameBuilder) { + values := []common.Hash{{0x81}, {0x82}, {0x83}} + dishonest := builder.Seq().ExpectAttackV2(0) + dishonest.Attack2(values, 0).ExpectAttackV2(0) + values = claimBuilder.GetClaimsAtPosition(builder.Game.RootClaim().Position.MoveN(nbits, 0), []uint64{1}) + dishonest.Attack2(values, 0).ExpectAttackV2(1) + values = claimBuilder.GetClaimsAtPosition(builder.Game.RootClaim().Position.MoveN(nbits, 0), []uint64{2}) + dishonest.Attack2(values, 0).ExpectAttackV2(2) + }, + }, + { + name: "HonestRoot-TwoLevelAttack-FirstLevelCorrect-SecondLevelCorrect", + rootClaimCorrect: true, + setupGame: func(builder *faulttest.GameBuilder) { + dishonest := builder.Seq().Attack2(nil, 0) + dishonest.Attack2(nil, 0).ExpectAttackV2(3) + dishonest.Attack2(nil, 1).ExpectAttackV2(3) + dishonest.Attack2(nil, 2).ExpectAttackV2(3) + dishonest.Attack2(nil, 3) + }, + }, + { + name: "HonestRoot-TwoLevelAttack-FirstLevelCorrect-SecondLevelIncorrect", + rootClaimCorrect: true, + setupGame: func(builder *faulttest.GameBuilder) { + values := []common.Hash{{0x81}, {0x82}, {0x83}} + dishonest := builder.Seq().Attack2(nil, 0).ExpectAttackV2(3) + lastPosition := builder.Game.Claims()[len(builder.Game.Claims())-1].Position + dishonest.Attack2(values, 0).ExpectAttackV2(0) + dishonest.Attack2(values, 1).ExpectAttackV2(0) + dishonest.Attack2(values, 2).ExpectAttackV2(0) + dishonest.Attack2(values, 3).ExpectAttackV2(0) + + values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 0), []uint64{0}) + dishonest.Attack2(values, 0).ExpectAttackV2(0) + values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 0), []uint64{1}) + dishonest.Attack2(values, 0).ExpectAttackV2(1) + values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 0), []uint64{2}) + dishonest.Attack2(values, 0).ExpectAttackV2(2) + + values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 1), []uint64{0}) + dishonest.Attack2(values, 1).ExpectAttackV2(0) + values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 1), []uint64{1}) + dishonest.Attack2(values, 1).ExpectAttackV2(1) + values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 1), []uint64{2}) + dishonest.Attack2(values, 1).ExpectAttackV2(2) + + values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 2), []uint64{0}) + dishonest.Attack2(values, 2).ExpectAttackV2(0) + values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 2), []uint64{1}) + dishonest.Attack2(values, 2).ExpectAttackV2(1) + values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 2), []uint64{2}) + dishonest.Attack2(values, 2).ExpectAttackV2(2) + + values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 3), []uint64{0}) + dishonest.Attack2(values, 3).ExpectAttackV2(0) + values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 3), []uint64{1}) + dishonest.Attack2(values, 3).ExpectAttackV2(1) + values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 3), []uint64{2}) + dishonest.Attack2(values, 3).ExpectAttackV2(2) + }, + }, + { + name: "HonestRoot-TwoLevelAttack-FirstLevelIncorrect-SecondLevelCorrect", + rootClaimCorrect: true, + setupGame: func(builder *faulttest.GameBuilder) { + values := []common.Hash{{0x81}, {0x82}, {0x83}} + honest := builder.Seq() + lastPosition := builder.Game.Claims()[len(builder.Game.Claims())-1].Position + honest.Attack2(values, 0).Attack2(nil, 0) + values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 0), []uint64{1}) + honest.Attack2(values, 0).ExpectAttackV2(1).Attack2(nil, 0).ExpectAttackV2(3) + values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 0), []uint64{2}) + honest.Attack2(values, 0).ExpectAttackV2(2).Attack2(nil, 0).ExpectAttackV2(3) + values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 0), []uint64{3}) + honest.Attack2(values, 0).ExpectAttackV2(3).Attack2(nil, 0).ExpectAttackV2(3) + }, + }, + { + name: "StepAtMaxDepth-LeftBranch-StepBranch0", + setupGame: func(builder *faulttest.GameBuilder) { + values := []common.Hash{{0x81}, {0x82}, {0x83}} + lastHonestClaim := builder.Seq(). + Attack2(nil, 0). // honest + Attack2(values, 0). // dishonest + Attack2(nil, 0) // honest + lastHonestClaim.Attack2([]common.Hash{{0x1}, {0x2}, {0x3}}, 0).ExpectStepV2(0) + }, + }, + { + name: "StepAtMaxDepth-LeftBranch-StepBranch1", + setupGame: func(builder *faulttest.GameBuilder) { + values := []common.Hash{{0x81}, {0x82}, {0x83}} + lastHonestClaim := builder.Seq(). + Attack2(nil, 0). // honest + Attack2(values, 0). // dishonest + Attack2(nil, 0) // honest + lastPosition := builder.Game.Claims()[len(builder.Game.Claims())-1].Position + values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 0), []uint64{1}) + lastHonestClaim.Attack2(values, 0).ExpectStepV2(1) + }, + }, + { + name: "StepAtMaxDepth-LeftBranch-StepBranch2", + setupGame: func(builder *faulttest.GameBuilder) { + values := []common.Hash{{0x81}, {0x82}, {0x83}} + lastHonestClaim := builder.Seq(). + Attack2(nil, 0). // honest + Attack2(values, 0). // dishonest + Attack2(nil, 0) // honest + lastPosition := builder.Game.Claims()[len(builder.Game.Claims())-1].Position + values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 0), []uint64{2}) + lastHonestClaim.Attack2(values, 0).ExpectStepV2(2) + }, + }, + { + name: "StepAtMaxDepth-LeftBranch-StepBranch3", + setupGame: func(builder *faulttest.GameBuilder) { + values := []common.Hash{{0x81}, {0x82}, {0x83}} + lastHonestClaim := builder.Seq(). + Attack2(nil, 0). // honest + Attack2(values, 0). // dishonest + Attack2(nil, 0) // honest + lastPosition := builder.Game.Claims()[len(builder.Game.Claims())-1].Position + values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 0), []uint64{}) + lastHonestClaim.Attack2(values, 0).ExpectStepV2(3) + }, + }, + { + name: "StepAtMaxDepth-MiddleBranch-StepBranch0", + setupGame: func(builder *faulttest.GameBuilder) { + values := []common.Hash{{0x81}, {0x82}, {0x83}} + lastHonestClaim := builder.Seq(). + Attack2(nil, 0). // honest + Attack2(values, 1). // dishonest + Attack2(nil, 0) // honest + lastHonestClaim.Attack2([]common.Hash{{0x1}, {0x2}, {0x3}}, 0).ExpectStepV2(0) + }, + }, + { + name: "StepAtMaxDepth-MiddleBranch-StepBranch1", + setupGame: func(builder *faulttest.GameBuilder) { + values := []common.Hash{{0x81}, {0x82}, {0x83}} + lastHonestClaim := builder.Seq(). + Attack2(nil, 0). // honest + Attack2(values, 1). // dishonest + Attack2(nil, 0) // honest + lastPosition := builder.Game.Claims()[len(builder.Game.Claims())-1].Position + values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 0), []uint64{1}) + lastHonestClaim.Attack2(values, 0).ExpectStepV2(1) + }, + }, + { + name: "StepAtMaxDepth-MiddleBranch-StepBranch2", + setupGame: func(builder *faulttest.GameBuilder) { + values := []common.Hash{{0x81}, {0x82}, {0x83}} + lastHonestClaim := builder.Seq(). + Attack2(nil, 0). // honest + Attack2(values, 1). // dishonest + Attack2(nil, 0) // honest + lastPosition := builder.Game.Claims()[len(builder.Game.Claims())-1].Position + values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 0), []uint64{2}) + lastHonestClaim.Attack2(values, 0).ExpectStepV2(2) + }, + }, + { + name: "StepAtMaxDepth-MiddleBranch-StepBranch3", + setupGame: func(builder *faulttest.GameBuilder) { + values := []common.Hash{{0x81}, {0x82}, {0x83}} + lastHonestClaim := builder.Seq(). + Attack2(nil, 0). // honest + Attack2(values, 1). // dishonest + Attack2(nil, 0) // honest + lastPosition := builder.Game.Claims()[len(builder.Game.Claims())-1].Position + values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 0), []uint64{}) + lastHonestClaim.Attack2(values, 0).ExpectStepV2(3) }, }, } @@ -414,16 +444,19 @@ func TestCalculateNextActions2(t *testing.T) { builder := claimBuilder.GameBuilder(faulttest.WithInvalidValue(!test.rootClaimCorrect)) test.setupGame(builder) game := builder.Game - solver := NewGameSolver(maxDepth, trace.NewSimpleTraceAccessor(claimBuilder.CorrectTraceProvider()), types.CallDataType) + accessor := trace.NewSimpleTraceAccessor(claimBuilder.CorrectTraceProvider()) + solver := NewGameSolver(maxDepth, accessor, types.CallDataType) postState, actions := runStep2(t, solver, game, claimBuilder.CorrectTraceProvider()) + preimage := getStepPreimage(t, builder, game, accessor) for i, action := range builder.ExpectedActions { - t.Logf("Expect %v: Type: %v, ParentIdx: %v, Attack: %v, Value: %v, PreState: %v, ProofData: %v", - i, action.Type, action.ParentClaim.ContractIndex, action.IsAttack, action.Value, hex.EncodeToString(action.PreState), hex.EncodeToString(action.ProofData)) + if action.Type == types.ActionTypeStep { + action.OracleData = preimage + } + t.Logf("Expect %v: Type: %v, Position: %v, ParentIdx: %v, Branch: %v, Value: %v, SubValues: %v, PreState: %v, ProofData: %v", + i, action.Type, action.ParentClaim.Position, action.ParentClaim.ContractIndex, action.AttackBranch, action.Value, action.SubValues, hex.EncodeToString(action.PreState), hex.EncodeToString(action.ProofData)) require.Containsf(t, actions, action, "Expected claim %v missing", i) - break } require.Len(t, actions, len(builder.ExpectedActions), "Incorrect number of actions") - verifyGameRules(t, postState, test.rootClaimCorrect) }) } @@ -431,14 +464,16 @@ func TestCalculateNextActions2(t *testing.T) { func runStep2(t *testing.T, solver *GameSolver, game types.Game, correctTraceProvider types.TraceProvider) (types.Game, []types.Action) { actions, err := solver.CalculateNextActions(context.Background(), game) - t.Logf("runStep2 actions: %v", actions) require.NoError(t, err) postState := applyActions2(game, challengerAddr, actions) for i, action := range actions { - t.Logf("Move %v: Type: %v, ParentIdx: %v, Attack: %v, Value: %v, PreState: %v, ProofData: %v", - i, action.Type, action.ParentClaim.ContractIndex, action.IsAttack, action.Value, hex.EncodeToString(action.PreState), hex.EncodeToString(action.ProofData)) + t.Logf("Real %v: Type: %v, Position: %v, ParentIdx: %v, Branch: %v, Value: %v, SubValues: %v, PreState: %v, ProofData: %v", + i, action.Type, + action.ParentClaim.Position, + action.ParentClaim.ContractIndex, + action.AttackBranch, action.Value, action.SubValues, hex.EncodeToString(action.PreState), hex.EncodeToString(action.ProofData)) // Check that every move the solver returns meets the generic validation rules require.NoError(t, checkRules(game, action, correctTraceProvider), "Attempting to perform invalid action") } @@ -474,3 +509,68 @@ func applyActions2(game types.Game, claimant common.Address, actions []types.Act } return types.NewGameState2(claims, game.MaxDepth(), game.NBits(), game.SplitDepth()) } + +func getStepPreimage(t *testing.T, builder *faulttest.GameBuilder, game types.Game, accessor types.TraceAccessor) *types.PreimageOracleData { + if len(builder.ExpectedActions) == 0 { + return nil + } + claims := game.Claims() + lastClaim := claims[len(claims)-1] + lastExpectedAction := builder.ExpectedActions[len(builder.ExpectedActions)-1] + if lastExpectedAction.Type != types.ActionTypeStep { + return nil + } + _, _, preimage, err := accessor.GetStepData2(context.Background(), game, lastClaim, lastClaim.MoveRightN(lastExpectedAction.AttackBranch)) + require.NoError(t, err) + return preimage +} + +func TestMultipleRounds2(t *testing.T) { + t.Parallel() + tests := []struct { + name string + actor actor + }{ + /* + { + name: "SingleRoot", + actor: doNothingActor, + }, + */ + { + name: "LinearAttackCorrect", + actor: correctAttackLastClaim, + }, + } + for _, test := range tests { + test := test + for _, rootClaimCorrect := range []bool{true, false} { + rootClaimCorrect := rootClaimCorrect + t.Run(fmt.Sprintf("%v-%v", test.name, rootClaimCorrect), func(t *testing.T) { + t.Parallel() + + maxDepth := types.Depth(8) + startingL2BlockNumber := big.NewInt(50) + nbits := uint64(2) + splitDepth := types.Depth(4) + claimBuilder := faulttest.NewAlphabetClaimBuilder2(t, startingL2BlockNumber, maxDepth, nbits, splitDepth) + builder := claimBuilder.GameBuilder(faulttest.WithInvalidValue(!rootClaimCorrect)) + game := builder.Game + + correctTrace := claimBuilder.CorrectTraceProvider() + solver := NewGameSolver(maxDepth, trace.NewSimpleTraceAccessor(correctTrace), types.CallDataType) + + roundNum := 0 + done := false + for !done { + t.Logf("------ ROUND %v ------", roundNum) + game, _ = runStep2(t, solver, game, correctTrace) + verifyGameRules(t, game, rootClaimCorrect) + + game, done = test.actor.Apply(t, game, correctTrace) + roundNum++ + } + }) + } + } +} diff --git a/op-challenger2/game/fault/solver/rules.go b/op-challenger2/game/fault/solver/rules.go index 41c11f10a064..45ce9af7c5c4 100644 --- a/op-challenger2/game/fault/solver/rules.go +++ b/op-challenger2/game/fault/solver/rules.go @@ -25,8 +25,8 @@ var rules = []actionRule{ doNotStepAlreadyCounteredClaims, onlyAttackRootClaimZeroBranch, avoidPoisonedPrestate, - detectPoisonedStepPrestate, - detectFailedStep, + //detectPoisonedStepPrestate, + //detectFailedStep, doNotCounterSelf, } @@ -191,10 +191,7 @@ func detectFailedStep(game types.Game, action types.Action, correctTrace types.T return nil } honestTraceIndex := position.TraceIndex(game.MaxDepth()) - poststateIndex := honestTraceIndex - if !action.IsAttack { - poststateIndex = new(big.Int).Add(honestTraceIndex, big.NewInt(1)) - } + poststateIndex := new(big.Int).Add(honestTraceIndex, big.NewInt(int64(action.AttackBranch))) // Walk back up the claims and find the claim required post state index claim := game.Claims()[action.ParentClaim.ContractIndex] poststateClaim, ok := game.AncestorWithTraceIndex(claim, poststateIndex) @@ -205,7 +202,7 @@ func detectFailedStep(game types.Game, action types.Action, correctTrace types.T if err != nil { return fmt.Errorf("failed to get correct trace at position %v: %w", poststateClaim.Position, err) } - validStep := correctValue == poststateClaim.Value + validStep := correctValue == (*poststateClaim.SubValues)[0] parentPostAgree := (claim.Depth()-poststateClaim.Depth())%2 == 0 if parentPostAgree == validStep { return fmt.Errorf("failed step against claim at %v using poststate from claim %v post state is correct? %v parentPostAgree? %v", diff --git a/op-challenger2/game/fault/solver/solver.go b/op-challenger2/game/fault/solver/solver.go index f0323db940e7..26095abd5c69 100644 --- a/op-challenger2/game/fault/solver/solver.go +++ b/op-challenger2/game/fault/solver/solver.go @@ -123,17 +123,18 @@ func (s *claimSolver) AttemptStep(ctx context.Context, game types.Game, claim ty // Attack the claim by executing step index, so we need to get the pre-state of that index position = claim.Position.MoveRightN(branch) } else { - // Defend and use this claim as the starting point to execute the step after. - // Thus, we need the pre-state of the next step. - position = claim.Position.MoveRightN(branch + 1) - attackBranch = branch + 1 + if branch == game.MaxAttackBranch()-1 { + // If we are at the max attack branch, we need to step on the next branch + position = claim.Position.MoveRightN(branch + 1) + attackBranch = branch + 1 + } else { + return nil, nil + } } - preState, proofData, oracleData, err := s.trace.GetStepData2(ctx, game, claim, position) if err != nil { return nil, err } - return &StepData{ LeafClaim: claim, AttackBranch: attackBranch, @@ -144,11 +145,6 @@ func (s *claimSolver) AttemptStep(ctx context.Context, game types.Game, claim ty } // agreeWithClaim returns true if the claim is correct according to the internal [TraceProvider]. -func (s *claimSolver) agreeWithClaim(ctx context.Context, game types.Game, claim types.Claim) (bool, error) { - ourValue, err := s.trace.Get(ctx, game, claim, claim.Position) - return bytes.Equal(ourValue[:], claim.Value[:]), err -} - func (s *claimSolver) agreeWithClaimV2(ctx context.Context, game types.Game, claim types.Claim, branch uint64) (bool, error) { if branch >= uint64(len(*claim.SubValues)) { return true, fmt.Errorf("branch must be lesser than maxAttachBranch") diff --git a/op-challenger2/game/fault/test/claim_builder.go b/op-challenger2/game/fault/test/claim_builder.go index 8b015a257fe9..cd2a99f6a6c1 100644 --- a/op-challenger2/game/fault/test/claim_builder.go +++ b/op-challenger2/game/fault/test/claim_builder.go @@ -2,7 +2,6 @@ package test import ( "context" - "fmt" "math" "math/big" "testing" @@ -167,7 +166,7 @@ func (c *ClaimBuilder) claim(pos types.Position, opts ...ClaimOpt) types.Claim { claim.Value = contracts.SubValuesHash(*claim.SubValues) } else { values := []common.Hash{} - if pos.ToGIndex().Cmp(big.NewInt(1)) == 0 { + if pos.IsRootPosition() { if cfg.invalidValue { values = append(values, c.incorrectClaim(pos)) } else { @@ -176,21 +175,21 @@ func (c *ClaimBuilder) claim(pos types.Position, opts ...ClaimOpt) types.Claim { for i := uint64(0); i < c.MaxAttackBranch()-1; i++ { values = append(values, common.Hash{}) } + } else if pos.Depth() == c.splitDepth+types.Depth(c.nbits) { + values = append(values, c.CorrectClaimAtPosition(pos)) + for i := uint64(0); i < c.MaxAttackBranch()-1; i++ { + values = append(values, common.Hash{}) + } } else { for i := uint64(0); i < c.MaxAttackBranch(); i++ { pos := pos.MoveRightN(i) - if cfg.invalidValue { - values = append(values, c.incorrectClaim(pos)) - } else { - values = append(values, c.CorrectClaimAtPosition(pos)) - } + values = append(values, c.CorrectClaimAtPosition(pos)) } } claim.SubValues = &values claim.Value = contracts.SubValuesHash(*claim.SubValues) } claim.ParentContractIndex = cfg.parentIdx - fmt.Printf("claim.Value: %v\n", claim.Value) return claim } @@ -235,3 +234,45 @@ func (c *ClaimBuilder) SplitDepth() types.Depth { func (c *ClaimBuilder) TraceRootDepth() types.Depth { return c.splitDepth + types.Depth(c.nbits) } + +func (c *ClaimBuilder) GetClaimsAtPosition(pos types.Position, invalidBranchList []uint64) []common.Hash { + values := []common.Hash{} + if pos.IsRootPosition() { + if contains(invalidBranchList, uint64(0)) { + values = append(values, c.incorrectClaim(pos)) + } else { + values = append(values, c.CorrectClaimAtPosition(pos)) + } + for i := uint64(0); i < c.MaxAttackBranch()-1; i++ { + values = append(values, common.Hash{}) + } + } else if pos.Depth() == c.splitDepth+types.Depth(c.nbits) { + if contains(invalidBranchList, uint64(0)) { + values = append(values, c.incorrectClaim(pos)) + } else { + values = append(values, c.CorrectClaimAtPosition(pos)) + } + for i := uint64(0); i < c.MaxAttackBranch()-1; i++ { + values = append(values, common.Hash{}) + } + } else { + for i := uint64(0); i < c.MaxAttackBranch(); i++ { + pos := pos.MoveRightN(i) + if contains(invalidBranchList, i) { + values = append(values, c.incorrectClaim(pos)) + } else { + values = append(values, c.CorrectClaimAtPosition(pos)) + } + } + } + return values +} + +func contains(slice []uint64, value uint64) bool { + for _, v := range slice { + if v == value { + return true + } + } + return false +} diff --git a/op-challenger2/game/fault/test/game_builder.go b/op-challenger2/game/fault/test/game_builder.go index 9f6933ec5f2b..5a0e0b18f9c3 100644 --- a/op-challenger2/game/fault/test/game_builder.go +++ b/op-challenger2/game/fault/test/game_builder.go @@ -1,7 +1,6 @@ package test import ( - "fmt" "math/big" "testing" @@ -170,6 +169,7 @@ func (s *GameBuilderSeq) ExpectAttackV2(branch uint64) *GameBuilderSeq { nBits := s.builder.NBits() maxAttackBranch := s.builder.MaxAttackBranch() position := s.lastClaim.Position.MoveN(nBits, branch) + for i := uint64(0); i < maxAttackBranch; i++ { tmpPosition := position.MoveRightN(i) if tmpPosition.Depth() == (s.builder.SplitDepth()+types.Depth(nBits)) && i != 0 { @@ -177,7 +177,6 @@ func (s *GameBuilderSeq) ExpectAttackV2(branch uint64) *GameBuilderSeq { } else { value = s.builder.CorrectClaimAtPosition(tmpPosition) } - fmt.Printf("i: %v, value: %v, position: %v\n", i, value, tmpPosition.ToGIndex()) values = append(values, value) } hash := contracts.SubValuesHash(values) @@ -193,15 +192,15 @@ func (s *GameBuilderSeq) ExpectAttackV2(branch uint64) *GameBuilderSeq { return s } -func (s *GameBuilderSeq) ExpectStepAttackV2(branch uint64) *GameBuilderSeq { - traceIdx := new(big.Int).Add(s.lastClaim.TraceIndex(s.builder.maxDepth), big.NewInt(1)) +func (s *GameBuilderSeq) ExpectStepV2(branch uint64) *GameBuilderSeq { + traceIdx := new(big.Int).Add(s.lastClaim.TraceIndex(s.builder.maxDepth), big.NewInt(int64(branch))) s.gameBuilder.ExpectedActions = append(s.gameBuilder.ExpectedActions, types.Action{ - Type: types.ActionTypeStep, - ParentClaim: s.lastClaim, - IsAttack: true, - PreState: s.builder.CorrectPreState(traceIdx), - ProofData: s.builder.CorrectProofData(traceIdx), - OracleData: s.builder.CorrectOracleData(traceIdx), + Type: types.ActionTypeStep, + ParentClaim: s.lastClaim, + AttackBranch: branch, + PreState: s.builder.CorrectPreState(traceIdx), + ProofData: s.builder.CorrectProofData(traceIdx), + OracleData: s.builder.CorrectOracleData(traceIdx), }) return s } From 32ca079a262fdbda5a7f3cb07e80270f7eb5b841 Mon Sep 17 00:00:00 2001 From: Po Date: Wed, 27 Nov 2024 06:41:39 +0000 Subject: [PATCH 10/13] fix: TestMultipleRoundsWithNbits1/2 --- op-challenger2/game/fault/solver/actors.go | 54 +++++++++++---- .../game/fault/solver/game_rules_test.go | 1 - .../game/fault/solver/game_solver.go | 5 +- .../game/fault/solver/game_solver_test.go | 69 ++++++++++++++++--- op-challenger2/game/fault/solver/solver.go | 8 +++ .../game/fault/solver/solver_test.go | 3 +- .../game/fault/test/claim_builder.go | 8 ++- .../game/fault/test/game_builder.go | 12 ++++ op-challenger2/game/fault/trace/access.go | 2 +- 9 files changed, 133 insertions(+), 29 deletions(-) diff --git a/op-challenger2/game/fault/solver/actors.go b/op-challenger2/game/fault/solver/actors.go index 38ddbbf39b39..aeef491b9493 100644 --- a/op-challenger2/game/fault/solver/actors.go +++ b/op-challenger2/game/fault/solver/actors.go @@ -51,20 +51,28 @@ var correctDefendLastClaim = respondLastClaim(func(seq *test.GameBuilderSeq) { // Must attack the root seq.Attack2(nil, 0) } else { - seq.Attack2(nil, 1) + seq.Attack2(nil, seq.MaxAttackBranch()) } }) var incorrectAttackLastClaim = respondLastClaim(func(seq *test.GameBuilderSeq) { - seq.Attack2(nil, 0, test.WithValue(common.Hash{0xaa})) + incorrectSubValues := []common.Hash{} + for i := uint64(0); i < seq.MaxAttackBranch(); i++ { + incorrectSubValues = append(incorrectSubValues, common.Hash{0xaa}) + } + seq.Attack2(incorrectSubValues, 0) }) var incorrectDefendLastClaim = respondLastClaim(func(seq *test.GameBuilderSeq) { + incorrectSubValues := []common.Hash{} + for i := uint64(0); i < seq.MaxAttackBranch(); i++ { + incorrectSubValues = append(incorrectSubValues, common.Hash{0xdd}) + } if seq.IsRoot() { // Must attack the root - seq.Attack2(nil, 0, test.WithValue(common.Hash{0xdd})) + seq.Attack2(incorrectSubValues, 0) } else { - seq.Attack2(nil, 1, test.WithValue(common.Hash{0xdd})) + seq.Attack2(incorrectSubValues, seq.MaxAttackBranch()) } }) @@ -77,29 +85,51 @@ var defendEverythingCorrect = respondAllClaims(func(seq *test.GameBuilderSeq) { // Must attack root seq.Attack2(nil, 0) } else { - seq.Attack2(nil, 1) + seq.Attack2(nil, seq.MaxAttackBranch()) } }) var attackEverythingIncorrect = respondAllClaims(func(seq *test.GameBuilderSeq) { - seq.Attack2(nil, 0, test.WithValue(common.Hash{0xaa})) + incorrectSubValues := []common.Hash{} + for i := uint64(0); i < seq.MaxAttackBranch(); i++ { + incorrectSubValues = append(incorrectSubValues, common.Hash{0xaa}) + } + seq.Attack2(incorrectSubValues, 0) }) var defendEverythingIncorrect = respondAllClaims(func(seq *test.GameBuilderSeq) { + incorrectSubValues := []common.Hash{} + for i := uint64(0); i < seq.MaxAttackBranch(); i++ { + incorrectSubValues = append(incorrectSubValues, common.Hash{0xbb}) + } if seq.IsRoot() { // Must attack root - seq.Attack2(nil, 0, test.WithValue(common.Hash{0xbb})) + seq.Attack2(incorrectSubValues, 0) } else { - seq.Attack2(nil, 1, test.WithValue(common.Hash{0xbb})) + seq.Attack2(incorrectSubValues, seq.MaxAttackBranch()) } }) var exhaustive = respondAllClaims(func(seq *test.GameBuilderSeq) { seq.Attack2(nil, 0) - seq.Attack2(nil, 0, test.WithValue(common.Hash{0xaa})) - if !seq.IsRoot() { - seq.Attack2(nil, 1) - seq.Attack2(nil, 1, test.WithValue(common.Hash{0xdd})) + incorrectSubValues := []common.Hash{} + for i := uint64(0); i < seq.MaxAttackBranch(); i++ { + incorrectSubValues = append(incorrectSubValues, common.Hash{0xaa}) + if seq.IsSplitDepth() { + // at splitDepth, there is only one subValue + break + } + } + seq.Attack2(incorrectSubValues, 0) + if !seq.IsRoot() && !seq.IsTraceRoot() { + seq.Attack2(nil, seq.MaxAttackBranch()) + for i := uint64(0); i < seq.MaxAttackBranch(); i++ { + incorrectSubValues[i] = common.Hash{0xdd} + if seq.IsSplitDepth() { + break + } + } + seq.Attack2(incorrectSubValues, seq.MaxAttackBranch()) } }) diff --git a/op-challenger2/game/fault/solver/game_rules_test.go b/op-challenger2/game/fault/solver/game_rules_test.go index af6845ccf5eb..0d60476bfb08 100644 --- a/op-challenger2/game/fault/solver/game_rules_test.go +++ b/op-challenger2/game/fault/solver/game_rules_test.go @@ -15,7 +15,6 @@ import ( func verifyGameRules(t *testing.T, game types.Game, rootClaimCorrect bool) { actualResult, claimTree, resolvedGame := gameResult(game) - verifyExpectedGameResult(t, rootClaimCorrect, actualResult) verifyNoChallengerClaimsWereSuccessfullyCountered(t, resolvedGame) diff --git a/op-challenger2/game/fault/solver/game_solver.go b/op-challenger2/game/fault/solver/game_solver.go index 5f8ad8c579c1..d3ebcea0310d 100644 --- a/op-challenger2/game/fault/solver/game_solver.go +++ b/op-challenger2/game/fault/solver/game_solver.go @@ -99,11 +99,12 @@ func (s *GameSolver) calculateStep(ctx context.Context, game types.Game, claim t func (s *GameSolver) calculateMove(ctx context.Context, game types.Game, claim types.Claim, honestClaims *honestClaimTracker) (*types.Action, error) { for branch := range *claim.SubValues { + // attack branch 0 can be attacked at root or splitDepth+nbits if claim.Position.Depth() == game.SplitDepth()+types.Depth(game.NBits()) && branch != 0 { - continue + return nil, nil } if claim.IsRoot() && branch != 0 { - continue + return nil, nil } move, err := s.claimSolver.NextMove(ctx, claim, game, honestClaims, uint64(branch)) if err != nil { diff --git a/op-challenger2/game/fault/solver/game_solver_test.go b/op-challenger2/game/fault/solver/game_solver_test.go index e36f09da7042..58de94bc544b 100644 --- a/op-challenger2/game/fault/solver/game_solver_test.go +++ b/op-challenger2/game/fault/solver/game_solver_test.go @@ -57,7 +57,7 @@ func runStep(t *testing.T, solver *GameSolver, game types.Game, correctTraceProv return postState, actions } -func TestMultipleRounds(t *testing.T) { +func TestMultipleRoundsWithNbits1(t *testing.T) { t.Parallel() tests := []struct { name string @@ -142,7 +142,7 @@ func TestMultipleRounds(t *testing.T) { done := false for !done { t.Logf("------ ROUND %v ------", roundNum) - game, _ = runStep(t, solver, game, correctTrace) + game, _ = runStep2(t, solver, game, correctTrace) verifyGameRules(t, game, rootClaimCorrect) game, done = test.actor.Apply(t, game, correctTrace) @@ -525,33 +525,80 @@ func getStepPreimage(t *testing.T, builder *faulttest.GameBuilder, game types.Ga return preimage } -func TestMultipleRounds2(t *testing.T) { +func TestMultipleRoundsWithNbits2(t *testing.T) { t.Parallel() tests := []struct { name string actor actor }{ - /* - { - name: "SingleRoot", - actor: doNothingActor, - }, - */ + { + name: "SingleRoot", + actor: doNothingActor, + }, { name: "LinearAttackCorrect", actor: correctAttackLastClaim, }, + { + name: "LinearDefendCorrect", + actor: correctDefendLastClaim, + }, + { + name: "LinearAttackIncorrect", + actor: incorrectAttackLastClaim, + }, + { + name: "LinearDefendInorrect", + actor: incorrectDefendLastClaim, + }, + { + name: "LinearDefendIncorrectDefendCorrect", + actor: combineActors(incorrectDefendLastClaim, correctDefendLastClaim), + }, + { + name: "LinearAttackIncorrectDefendCorrect", + actor: combineActors(incorrectAttackLastClaim, correctDefendLastClaim), + }, + { + name: "LinearDefendIncorrectDefendIncorrect", + actor: combineActors(incorrectDefendLastClaim, incorrectDefendLastClaim), + }, + { + name: "LinearAttackIncorrectDefendIncorrect", + actor: combineActors(incorrectAttackLastClaim, incorrectDefendLastClaim), + }, + { + name: "AttackEverythingCorrect", + actor: attackEverythingCorrect, + }, + { + name: "DefendEverythingCorrect", + actor: defendEverythingCorrect, + }, + { + name: "AttackEverythingIncorrect", + actor: attackEverythingIncorrect, + }, + { + name: "DefendEverythingIncorrect", + actor: defendEverythingIncorrect, + }, + { + name: "Exhaustive", + actor: exhaustive, + }, } for _, test := range tests { test := test - for _, rootClaimCorrect := range []bool{true, false} { + for _, rootClaimCorrect := range []bool{false} { rootClaimCorrect := rootClaimCorrect t.Run(fmt.Sprintf("%v-%v", test.name, rootClaimCorrect), func(t *testing.T) { t.Parallel() - maxDepth := types.Depth(8) + maxDepth := types.Depth(10) startingL2BlockNumber := big.NewInt(50) nbits := uint64(2) + // splitDepth can't be 4 when maxDepth=8, because we can only attack branch 0 at claim with depth of splitDepth+nbits splitDepth := types.Depth(4) claimBuilder := faulttest.NewAlphabetClaimBuilder2(t, startingL2BlockNumber, maxDepth, nbits, splitDepth) builder := claimBuilder.GameBuilder(faulttest.WithInvalidValue(!rootClaimCorrect)) diff --git a/op-challenger2/game/fault/solver/solver.go b/op-challenger2/game/fault/solver/solver.go index 26095abd5c69..e702cf25cae6 100644 --- a/op-challenger2/game/fault/solver/solver.go +++ b/op-challenger2/game/fault/solver/solver.go @@ -81,6 +81,14 @@ func (s *claimSolver) NextMove(ctx context.Context, claim types.Claim, game type return nil, err } if agree { + if claim.Depth() == game.TraceRootDepth() && branch == 0 { + // Only useful in alphabet game, because the alphabet game has a constant status byte, and is not safe from someone being dishonest in + // output bisection and then posting a correct execution trace bisection root claim. + // when root claim of output bisection is dishonest, + // root claim of execution trace bisection is made by the dishonest actor but is honest + // we should counter it. + return s.attackV2(ctx, game, claim, branch) + } if branch < game.MaxAttackBranch()-1 { return nil, nil } diff --git a/op-challenger2/game/fault/solver/solver_test.go b/op-challenger2/game/fault/solver/solver_test.go index 10d93a3f08fd..5a0e1f595cc1 100644 --- a/op-challenger2/game/fault/solver/solver_test.go +++ b/op-challenger2/game/fault/solver/solver_test.go @@ -5,6 +5,7 @@ import ( "math/big" "testing" + "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/contracts" faulttest "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/test" "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/trace" "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/trace/alphabet" @@ -534,7 +535,7 @@ func TestAttemptStepNary4(t *testing.T) { ), }, PostDA: types.DAItem{ - DataHash: claimAt(types.NewPosition(types.Depth(4), common.Big0)), + DataHash: contracts.SubValuesHash(append([]common.Hash{}, claimAt(types.NewPosition(types.Depth(4), common.Big0)), common.Hash{}, common.Hash{})), }, }, expectedLocalData: &types.DAItem{}, diff --git a/op-challenger2/game/fault/test/claim_builder.go b/op-challenger2/game/fault/test/claim_builder.go index cd2a99f6a6c1..38cbf021105b 100644 --- a/op-challenger2/game/fault/test/claim_builder.go +++ b/op-challenger2/game/fault/test/claim_builder.go @@ -161,7 +161,13 @@ func (c *ClaimBuilder) claim(pos types.Position, opts ...ClaimOpt) types.Claim { }, AttackBranch: cfg.branch, } - if cfg.subValues != nil { + if cfg.claimant != (common.Address{}) { + claim.Claimant = cfg.claimant + } + if cfg.invalidValue { + claim.Value = c.incorrectClaim(pos) + claim.SubValues = &[]common.Hash{claim.Value} + } else if cfg.subValues != nil { claim.SubValues = cfg.subValues claim.Value = contracts.SubValuesHash(*claim.SubValues) } else { diff --git a/op-challenger2/game/fault/test/game_builder.go b/op-challenger2/game/fault/test/game_builder.go index 5a0e0b18f9c3..e683620563e2 100644 --- a/op-challenger2/game/fault/test/game_builder.go +++ b/op-challenger2/game/fault/test/game_builder.go @@ -56,10 +56,22 @@ func (g *GameBuilderSeq) IsRoot() bool { return g.lastClaim.IsRoot() } +func (g *GameBuilderSeq) IsTraceRoot() bool { + return g.lastClaim.Depth() == g.gameBuilder.Game.SplitDepth()+types.Depth(g.gameBuilder.Game.NBits()) +} + +func (g *GameBuilderSeq) IsSplitDepth() bool { + return g.lastClaim.Depth() == g.gameBuilder.Game.SplitDepth() +} + func (g *GameBuilderSeq) LastClaim() types.Claim { return g.lastClaim } +func (g *GameBuilderSeq) MaxAttackBranch() uint64 { + return g.gameBuilder.builder.MaxAttackBranch() +} + // addClaimToGame replaces the game being built with a new instance that has claim as the latest claim. // The ContractIndex in claim is updated with its position in the game's claim array. // Does nothing if the claim already exists diff --git a/op-challenger2/game/fault/trace/access.go b/op-challenger2/game/fault/trace/access.go index ff610f2f12df..ed00bdbb9072 100644 --- a/op-challenger2/game/fault/trace/access.go +++ b/op-challenger2/game/fault/trace/access.go @@ -152,7 +152,7 @@ func (t *Accessor) GetStepData2(ctx context.Context, game types.Game, ref types. preTraceIdx := new(big.Int).Sub(postTraceIdx, big.NewInt(1)) preStateDaItem, err := findAncestorProofAtDepth2(ctx, provider, game, ref, preTraceIdx) if err != nil { - return nil, nil, nil, fmt.Errorf("failed to get postStateDaItem at trace index %v: %w", preTraceIdx, err) + return nil, nil, nil, fmt.Errorf("failed to get preStateDaItem at trace index %v: %w", preTraceIdx, err) } postStateDaItem, err := findAncestorProofAtDepth2(ctx, provider, game, ref, postTraceIdx) if err != nil { From 0176d08688f8b264e67150e20297d2655eac73aa Mon Sep 17 00:00:00 2001 From: Po Date: Wed, 27 Nov 2024 10:13:29 +0000 Subject: [PATCH 11/13] fix tests: responder_test and TestGetStepDataWithOutputRoot test --- .../game/fault/responder/responder_test.go | 69 ++++++++++++++----- op-challenger2/game/fault/trace/access.go | 21 +++--- 2 files changed, 63 insertions(+), 27 deletions(-) diff --git a/op-challenger2/game/fault/responder/responder_test.go b/op-challenger2/game/fault/responder/responder_test.go index 65517bc188fd..c9dc5b1f7e35 100644 --- a/op-challenger2/game/fault/responder/responder_test.go +++ b/op-challenger2/game/fault/responder/responder_test.go @@ -146,37 +146,54 @@ func TestPerformAction(t *testing.T) { require.Equal(t, ([]byte)("attack"), mockTxMgr.sent[0].TxData) }) - t.Run("defend", func(t *testing.T) { + t.Run("attackV2", func(t *testing.T) { responder, mockTxMgr, contract, _, _ := newTestFaultResponder(t) action := types.Action{ - Type: types.ActionTypeMove, - ParentClaim: types.Claim{ContractIndex: 123}, - IsAttack: false, - Value: common.Hash{0xaa}, + Type: types.ActionTypeAttackV2, + ParentClaim: types.Claim{ContractIndex: 123}, + IsAttack: false, + AttackBranch: 0, + DAType: types.CallDataType, + SubValues: &[]common.Hash{{0xaa}}, } err := responder.PerformAction(context.Background(), action) require.NoError(t, err) require.Len(t, mockTxMgr.sent, 1) - require.EqualValues(t, []interface{}{action.ParentClaim, action.Value}, contract.defendArgs) - require.Equal(t, ([]byte)("defend"), mockTxMgr.sent[0].TxData) + daTypeUint64 := (*big.Int)(action.DAType).Uint64() + subValues := make([]byte, 0, len(*action.SubValues)) + for _, subValue := range *action.SubValues { + subValues = append(subValues, subValue[:]...) + } + require.EqualValues(t, []interface{}{action.ParentClaim, action.AttackBranch, daTypeUint64, subValues}, contract.attackV2Args) + require.Equal(t, ([]byte)("attackV2"), mockTxMgr.sent[0].TxData) }) t.Run("step", func(t *testing.T) { responder, mockTxMgr, contract, _, _ := newTestFaultResponder(t) action := types.Action{ - Type: types.ActionTypeStep, - ParentClaim: types.Claim{ContractIndex: 123}, - IsAttack: true, - PreState: []byte{1, 2, 3}, - ProofData: []byte{4, 5, 6}, + Type: types.ActionTypeStep, + ParentClaim: types.Claim{ContractIndex: 123}, + IsAttack: true, + AttackBranch: 0, + PreState: []byte{1, 2, 3}, + ProofData: []byte{4, 5, 6}, + OracleData: &types.PreimageOracleData{ + VMStateDA: types.DAData{}, + OutputRootDAItem: types.DAItem{}, + }, + } + stepProof := types.StepProof{ + PreStateItem: action.OracleData.VMStateDA.PreDA, + PostStateItem: action.OracleData.VMStateDA.PostDA, + VmProof: action.ProofData, } err := responder.PerformAction(context.Background(), action) require.NoError(t, err) require.Len(t, mockTxMgr.sent, 1) - require.EqualValues(t, []interface{}{uint64(123), action.IsAttack, action.PreState, action.ProofData}, contract.stepArgs) - require.Equal(t, ([]byte)("step"), mockTxMgr.sent[0].TxData) + require.EqualValues(t, []interface{}{uint64(123), action.AttackBranch, action.PreState, stepProof}, contract.stepV2Args) + require.Equal(t, ([]byte)("stepV2"), mockTxMgr.sent[0].TxData) }) t.Run("stepWithLocalOracleData", func(t *testing.T) { @@ -188,7 +205,9 @@ func TestPerformAction(t *testing.T) { PreState: []byte{1, 2, 3}, ProofData: []byte{4, 5, 6}, OracleData: &types.PreimageOracleData{ - IsLocal: true, + IsLocal: true, + VMStateDA: types.DAData{}, + OutputRootDAItem: types.DAItem{}, }, } err := responder.PerformAction(context.Background(), action) @@ -196,7 +215,7 @@ func TestPerformAction(t *testing.T) { require.Len(t, mockTxMgr.sent, 1) require.Nil(t, contract.updateOracleArgs) // mock uploader returns nil - require.Equal(t, ([]byte)("step"), mockTxMgr.sent[0].TxData) + require.Equal(t, ([]byte)("stepV2"), mockTxMgr.sent[0].TxData) require.Equal(t, 1, uploader.updates) require.Equal(t, 0, oracle.existCalls) }) @@ -210,7 +229,9 @@ func TestPerformAction(t *testing.T) { PreState: []byte{1, 2, 3}, ProofData: []byte{4, 5, 6}, OracleData: &types.PreimageOracleData{ - IsLocal: false, + IsLocal: false, + VMStateDA: types.DAData{}, + OutputRootDAItem: types.DAItem{}, }, } err := responder.PerformAction(context.Background(), action) @@ -218,7 +239,7 @@ func TestPerformAction(t *testing.T) { require.Len(t, mockTxMgr.sent, 1) require.Nil(t, contract.updateOracleArgs) // mock uploader returns nil - require.Equal(t, ([]byte)("step"), mockTxMgr.sent[0].TxData) + require.Equal(t, ([]byte)("stepV2"), mockTxMgr.sent[0].TxData) require.Equal(t, 1, uploader.updates) require.Equal(t, 1, oracle.existCalls) }) @@ -370,8 +391,10 @@ type mockContract struct { calls int callFails bool attackArgs []interface{} + attackV2Args []interface{} defendArgs []interface{} stepArgs []interface{} + stepV2Args []interface{} challengeArgs []interface{} updateOracleClaimIdx uint64 updateOracleArgs *types.PreimageOracleData @@ -411,6 +434,11 @@ func (m *mockContract) AttackTx(_ context.Context, parent types.Claim, claim com return txmgr.TxCandidate{TxData: ([]byte)("attack")}, nil } +func (m *mockContract) AttackV2Tx(ctx context.Context, parent types.Claim, attackBranch uint64, daType uint64, claims []byte) (txmgr.TxCandidate, error) { + m.attackV2Args = []interface{}{parent, attackBranch, daType, claims} + return txmgr.TxCandidate{TxData: ([]byte)("attackV2")}, nil +} + func (m *mockContract) DefendTx(_ context.Context, parent types.Claim, claim common.Hash) (txmgr.TxCandidate, error) { m.defendArgs = []interface{}{parent, claim} return txmgr.TxCandidate{TxData: ([]byte)("defend")}, nil @@ -421,6 +449,11 @@ func (m *mockContract) StepTx(claimIdx uint64, isAttack bool, stateData []byte, return txmgr.TxCandidate{TxData: ([]byte)("step")}, nil } +func (m *mockContract) StepV2Tx(claimIdx uint64, attackBranch uint64, stateData []byte, proof types.StepProof) (txmgr.TxCandidate, error) { + m.stepV2Args = []interface{}{claimIdx, attackBranch, stateData, proof} + return txmgr.TxCandidate{TxData: ([]byte)("stepV2")}, nil +} + func (m *mockContract) UpdateOracleTx(_ context.Context, claimIdx uint64, data *types.PreimageOracleData) (txmgr.TxCandidate, error) { m.updateOracleClaimIdx = claimIdx m.updateOracleArgs = data diff --git a/op-challenger2/game/fault/trace/access.go b/op-challenger2/game/fault/trace/access.go index ed00bdbb9072..c8e7841ecc68 100644 --- a/op-challenger2/game/fault/trace/access.go +++ b/op-challenger2/game/fault/trace/access.go @@ -165,17 +165,20 @@ func (t *Accessor) GetStepData2(ctx context.Context, game types.Game, ref types. preimageData.VMStateDA = stateData - keyType := preimage.KeyType(preimageData.OracleKey[0]) - if keyType == preimage.LocalKeyType { - ident := preimageData.GetIdent() - addlocalDataDaItem := types.DAItem{} - if ident.Cmp(big.NewInt(types.LocalPreimageKeyStartingOutputRoot)) == 0 { - addlocalDataDaItem = outputRootDA.PreDA - } else if ident.Cmp(big.NewInt(types.LocalPreimageKeyDisputedOutputRoot)) == 0 { - addlocalDataDaItem = outputRootDA.PostDA + if preimageData.OracleKey != nil { + keyType := preimage.KeyType(preimageData.OracleKey[0]) + if keyType == preimage.LocalKeyType { + ident := preimageData.GetIdent() + addlocalDataDaItem := types.DAItem{} + if ident.Cmp(big.NewInt(types.LocalPreimageKeyStartingOutputRoot)) == 0 { + addlocalDataDaItem = outputRootDA.PreDA + } else if ident.Cmp(big.NewInt(types.LocalPreimageKeyDisputedOutputRoot)) == 0 { + addlocalDataDaItem = outputRootDA.PostDA + } + preimageData.OutputRootDAItem = addlocalDataDaItem } - preimageData.OutputRootDAItem = addlocalDataDaItem } + return prestate, proofData, preimageData, nil } From d7f7e6710fad3a99d0585ee3320fd1e18c167ee9 Mon Sep 17 00:00:00 2001 From: Abe Orm Date: Sat, 30 Nov 2024 00:10:04 +0800 Subject: [PATCH 12/13] fix buy --- op-challenger2/game/fault/agent.go | 2 +- .../game/fault/solver/game_solver_test.go | 50 +++++++++---------- op-challenger2/game/fault/solver/solver.go | 2 +- .../game/fault/test/claim_builder.go | 2 +- 4 files changed, 28 insertions(+), 28 deletions(-) diff --git a/op-challenger2/game/fault/agent.go b/op-challenger2/game/fault/agent.go index 42d9195e8de8..609e848eb911 100644 --- a/op-challenger2/game/fault/agent.go +++ b/op-challenger2/game/fault/agent.go @@ -133,7 +133,7 @@ func (a *Agent) performAction(ctx context.Context, wg *sync.WaitGroup, action ty if action.OracleData != nil { actionLog = actionLog.New("oracleKey", common.Bytes2Hex(action.OracleData.OracleKey)) } - } else if action.Type == types.ActionTypeMove { + } else if action.Type == types.ActionTypeAttackV2 { actionLog = actionLog.New("attackBranch", action.AttackBranch, "parent", action.ParentClaim.ContractIndex, "value", action.Value) } diff --git a/op-challenger2/game/fault/solver/game_solver_test.go b/op-challenger2/game/fault/solver/game_solver_test.go index 58de94bc544b..b63ecdeb13c8 100644 --- a/op-challenger2/game/fault/solver/game_solver_test.go +++ b/op-challenger2/game/fault/solver/game_solver_test.go @@ -250,9 +250,9 @@ func TestCalculateNextActions(t *testing.T) { honest := builder.Seq() honest.Attack2(nil, 0).ExpectAttackV2(3) honest.Attack2(values, 0).ExpectAttackV2(0) - values = claimBuilder.GetClaimsAtPosition(builder.Game.RootClaim().Position.MoveN(nbits, 0), []uint64{1}) + values = claimBuilder.GetCorrectClaimsAndInvalidClaimAtIndex(builder.Game.RootClaim().Position.MoveN(nbits, 0), []uint64{1}) honest.Attack2(values, 0).ExpectAttackV2(1) - values = claimBuilder.GetClaimsAtPosition(builder.Game.RootClaim().Position.MoveN(nbits, 0), []uint64{2}) + values = claimBuilder.GetCorrectClaimsAndInvalidClaimAtIndex(builder.Game.RootClaim().Position.MoveN(nbits, 0), []uint64{2}) honest.Attack2(values, 0).ExpectAttackV2(2) }, }, @@ -262,9 +262,9 @@ func TestCalculateNextActions(t *testing.T) { values := []common.Hash{{0x81}, {0x82}, {0x83}} dishonest := builder.Seq().ExpectAttackV2(0) dishonest.Attack2(values, 0).ExpectAttackV2(0) - values = claimBuilder.GetClaimsAtPosition(builder.Game.RootClaim().Position.MoveN(nbits, 0), []uint64{1}) + values = claimBuilder.GetCorrectClaimsAndInvalidClaimAtIndex(builder.Game.RootClaim().Position.MoveN(nbits, 0), []uint64{1}) dishonest.Attack2(values, 0).ExpectAttackV2(1) - values = claimBuilder.GetClaimsAtPosition(builder.Game.RootClaim().Position.MoveN(nbits, 0), []uint64{2}) + values = claimBuilder.GetCorrectClaimsAndInvalidClaimAtIndex(builder.Game.RootClaim().Position.MoveN(nbits, 0), []uint64{2}) dishonest.Attack2(values, 0).ExpectAttackV2(2) }, }, @@ -291,32 +291,32 @@ func TestCalculateNextActions(t *testing.T) { dishonest.Attack2(values, 2).ExpectAttackV2(0) dishonest.Attack2(values, 3).ExpectAttackV2(0) - values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 0), []uint64{0}) + values = claimBuilder.GetCorrectClaimsAndInvalidClaimAtIndex(lastPosition.MoveN(nbits, 0), []uint64{0}) dishonest.Attack2(values, 0).ExpectAttackV2(0) - values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 0), []uint64{1}) + values = claimBuilder.GetCorrectClaimsAndInvalidClaimAtIndex(lastPosition.MoveN(nbits, 0), []uint64{1}) dishonest.Attack2(values, 0).ExpectAttackV2(1) - values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 0), []uint64{2}) + values = claimBuilder.GetCorrectClaimsAndInvalidClaimAtIndex(lastPosition.MoveN(nbits, 0), []uint64{2}) dishonest.Attack2(values, 0).ExpectAttackV2(2) - values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 1), []uint64{0}) + values = claimBuilder.GetCorrectClaimsAndInvalidClaimAtIndex(lastPosition.MoveN(nbits, 1), []uint64{0}) dishonest.Attack2(values, 1).ExpectAttackV2(0) - values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 1), []uint64{1}) + values = claimBuilder.GetCorrectClaimsAndInvalidClaimAtIndex(lastPosition.MoveN(nbits, 1), []uint64{1}) dishonest.Attack2(values, 1).ExpectAttackV2(1) - values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 1), []uint64{2}) + values = claimBuilder.GetCorrectClaimsAndInvalidClaimAtIndex(lastPosition.MoveN(nbits, 1), []uint64{2}) dishonest.Attack2(values, 1).ExpectAttackV2(2) - values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 2), []uint64{0}) + values = claimBuilder.GetCorrectClaimsAndInvalidClaimAtIndex(lastPosition.MoveN(nbits, 2), []uint64{0}) dishonest.Attack2(values, 2).ExpectAttackV2(0) - values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 2), []uint64{1}) + values = claimBuilder.GetCorrectClaimsAndInvalidClaimAtIndex(lastPosition.MoveN(nbits, 2), []uint64{1}) dishonest.Attack2(values, 2).ExpectAttackV2(1) - values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 2), []uint64{2}) + values = claimBuilder.GetCorrectClaimsAndInvalidClaimAtIndex(lastPosition.MoveN(nbits, 2), []uint64{2}) dishonest.Attack2(values, 2).ExpectAttackV2(2) - values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 3), []uint64{0}) + values = claimBuilder.GetCorrectClaimsAndInvalidClaimAtIndex(lastPosition.MoveN(nbits, 3), []uint64{0}) dishonest.Attack2(values, 3).ExpectAttackV2(0) - values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 3), []uint64{1}) + values = claimBuilder.GetCorrectClaimsAndInvalidClaimAtIndex(lastPosition.MoveN(nbits, 3), []uint64{1}) dishonest.Attack2(values, 3).ExpectAttackV2(1) - values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 3), []uint64{2}) + values = claimBuilder.GetCorrectClaimsAndInvalidClaimAtIndex(lastPosition.MoveN(nbits, 3), []uint64{2}) dishonest.Attack2(values, 3).ExpectAttackV2(2) }, }, @@ -328,11 +328,11 @@ func TestCalculateNextActions(t *testing.T) { honest := builder.Seq() lastPosition := builder.Game.Claims()[len(builder.Game.Claims())-1].Position honest.Attack2(values, 0).Attack2(nil, 0) - values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 0), []uint64{1}) + values = claimBuilder.GetCorrectClaimsAndInvalidClaimAtIndex(lastPosition.MoveN(nbits, 0), []uint64{1}) honest.Attack2(values, 0).ExpectAttackV2(1).Attack2(nil, 0).ExpectAttackV2(3) - values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 0), []uint64{2}) + values = claimBuilder.GetCorrectClaimsAndInvalidClaimAtIndex(lastPosition.MoveN(nbits, 0), []uint64{2}) honest.Attack2(values, 0).ExpectAttackV2(2).Attack2(nil, 0).ExpectAttackV2(3) - values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 0), []uint64{3}) + values = claimBuilder.GetCorrectClaimsAndInvalidClaimAtIndex(lastPosition.MoveN(nbits, 0), []uint64{3}) honest.Attack2(values, 0).ExpectAttackV2(3).Attack2(nil, 0).ExpectAttackV2(3) }, }, @@ -356,7 +356,7 @@ func TestCalculateNextActions(t *testing.T) { Attack2(values, 0). // dishonest Attack2(nil, 0) // honest lastPosition := builder.Game.Claims()[len(builder.Game.Claims())-1].Position - values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 0), []uint64{1}) + values = claimBuilder.GetCorrectClaimsAndInvalidClaimAtIndex(lastPosition.MoveN(nbits, 0), []uint64{1}) lastHonestClaim.Attack2(values, 0).ExpectStepV2(1) }, }, @@ -369,7 +369,7 @@ func TestCalculateNextActions(t *testing.T) { Attack2(values, 0). // dishonest Attack2(nil, 0) // honest lastPosition := builder.Game.Claims()[len(builder.Game.Claims())-1].Position - values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 0), []uint64{2}) + values = claimBuilder.GetCorrectClaimsAndInvalidClaimAtIndex(lastPosition.MoveN(nbits, 0), []uint64{2}) lastHonestClaim.Attack2(values, 0).ExpectStepV2(2) }, }, @@ -382,7 +382,7 @@ func TestCalculateNextActions(t *testing.T) { Attack2(values, 0). // dishonest Attack2(nil, 0) // honest lastPosition := builder.Game.Claims()[len(builder.Game.Claims())-1].Position - values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 0), []uint64{}) + values = claimBuilder.GetCorrectClaimsAndInvalidClaimAtIndex(lastPosition.MoveN(nbits, 0), []uint64{}) lastHonestClaim.Attack2(values, 0).ExpectStepV2(3) }, }, @@ -406,7 +406,7 @@ func TestCalculateNextActions(t *testing.T) { Attack2(values, 1). // dishonest Attack2(nil, 0) // honest lastPosition := builder.Game.Claims()[len(builder.Game.Claims())-1].Position - values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 0), []uint64{1}) + values = claimBuilder.GetCorrectClaimsAndInvalidClaimAtIndex(lastPosition.MoveN(nbits, 0), []uint64{1}) lastHonestClaim.Attack2(values, 0).ExpectStepV2(1) }, }, @@ -419,7 +419,7 @@ func TestCalculateNextActions(t *testing.T) { Attack2(values, 1). // dishonest Attack2(nil, 0) // honest lastPosition := builder.Game.Claims()[len(builder.Game.Claims())-1].Position - values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 0), []uint64{2}) + values = claimBuilder.GetCorrectClaimsAndInvalidClaimAtIndex(lastPosition.MoveN(nbits, 0), []uint64{2}) lastHonestClaim.Attack2(values, 0).ExpectStepV2(2) }, }, @@ -432,7 +432,7 @@ func TestCalculateNextActions(t *testing.T) { Attack2(values, 1). // dishonest Attack2(nil, 0) // honest lastPosition := builder.Game.Claims()[len(builder.Game.Claims())-1].Position - values = claimBuilder.GetClaimsAtPosition(lastPosition.MoveN(nbits, 0), []uint64{}) + values = claimBuilder.GetCorrectClaimsAndInvalidClaimAtIndex(lastPosition.MoveN(nbits, 0), []uint64{}) lastHonestClaim.Attack2(values, 0).ExpectStepV2(3) }, }, diff --git a/op-challenger2/game/fault/solver/solver.go b/op-challenger2/game/fault/solver/solver.go index e702cf25cae6..b59ee357d13b 100644 --- a/op-challenger2/game/fault/solver/solver.go +++ b/op-challenger2/game/fault/solver/solver.go @@ -155,7 +155,7 @@ func (s *claimSolver) AttemptStep(ctx context.Context, game types.Game, claim ty // agreeWithClaim returns true if the claim is correct according to the internal [TraceProvider]. func (s *claimSolver) agreeWithClaimV2(ctx context.Context, game types.Game, claim types.Claim, branch uint64) (bool, error) { if branch >= uint64(len(*claim.SubValues)) { - return true, fmt.Errorf("branch must be lesser than maxAttachBranch") + return true, fmt.Errorf("branch must be less than maxAttachBranch") } ourValue, err := s.trace.Get(ctx, game, claim, claim.Position.MoveRightN(branch)) return bytes.Equal(ourValue[:], (*claim.SubValues)[branch][:]), err diff --git a/op-challenger2/game/fault/test/claim_builder.go b/op-challenger2/game/fault/test/claim_builder.go index 38cbf021105b..020c34e8c33d 100644 --- a/op-challenger2/game/fault/test/claim_builder.go +++ b/op-challenger2/game/fault/test/claim_builder.go @@ -241,7 +241,7 @@ func (c *ClaimBuilder) TraceRootDepth() types.Depth { return c.splitDepth + types.Depth(c.nbits) } -func (c *ClaimBuilder) GetClaimsAtPosition(pos types.Position, invalidBranchList []uint64) []common.Hash { +func (c *ClaimBuilder) GetCorrectClaimsAndInvalidClaimAtIndex(pos types.Position, invalidBranchList []uint64) []common.Hash { values := []common.Hash{} if pos.IsRootPosition() { if contains(invalidBranchList, uint64(0)) { From 4ba3d109f4b52209616a58d447e2fad2d8d1cbfb Mon Sep 17 00:00:00 2001 From: Abe Orm Date: Sat, 30 Nov 2024 00:28:46 +0800 Subject: [PATCH 13/13] add check,attack root, non-zero branch returns an error --- op-challenger2/game/fault/solver/game_solver.go | 2 +- op-challenger2/game/fault/test/claim_builder.go | 3 --- 2 files changed, 1 insertion(+), 4 deletions(-) diff --git a/op-challenger2/game/fault/solver/game_solver.go b/op-challenger2/game/fault/solver/game_solver.go index d3ebcea0310d..7114b1db58f0 100644 --- a/op-challenger2/game/fault/solver/game_solver.go +++ b/op-challenger2/game/fault/solver/game_solver.go @@ -104,7 +104,7 @@ func (s *GameSolver) calculateMove(ctx context.Context, game types.Game, claim t return nil, nil } if claim.IsRoot() && branch != 0 { - return nil, nil + return nil, fmt.Errorf("cannot attack root claim with branch %v", branch) } move, err := s.claimSolver.NextMove(ctx, claim, game, honestClaims, uint64(branch)) if err != nil { diff --git a/op-challenger2/game/fault/test/claim_builder.go b/op-challenger2/game/fault/test/claim_builder.go index 020c34e8c33d..5e1933ceba34 100644 --- a/op-challenger2/game/fault/test/claim_builder.go +++ b/op-challenger2/game/fault/test/claim_builder.go @@ -178,9 +178,6 @@ func (c *ClaimBuilder) claim(pos types.Position, opts ...ClaimOpt) types.Claim { } else { values = append(values, c.CorrectClaimAtPosition(pos)) } - for i := uint64(0); i < c.MaxAttackBranch()-1; i++ { - values = append(values, common.Hash{}) - } } else if pos.Depth() == c.splitDepth+types.Depth(c.nbits) { values = append(values, c.CorrectClaimAtPosition(pos)) for i := uint64(0); i < c.MaxAttackBranch()-1; i++ {