From bd144d9f1ce724816881c0319a04498b30c006cf Mon Sep 17 00:00:00 2001 From: Po Date: Sat, 9 Nov 2024 03:28:54 +0800 Subject: [PATCH] feat(MSFDG): add getStepData2 and findAncestorWithTraceIndex2 for localdata --- op-challenger2/game/fault/agent_test.go | 22 +- .../game/fault/contracts/faultdisputegame.go | 3 + .../fault/contracts/faultdisputegame_test.go | 10 + op-challenger2/game/fault/solver/actors.go | 32 +- .../game/fault/solver/game_solver_test.go | 14 +- .../game/fault/solver/honest_claims_test.go | 12 +- .../game/fault/solver/solver_test.go | 60 +-- op-challenger2/game/fault/test/alphabet.go | 37 +- .../game/fault/test/claim_builder.go | 51 ++- .../game/fault/test/game_builder.go | 26 +- op-challenger2/game/fault/test/seq_builder.go | 5 +- op-challenger2/game/fault/trace/access.go | 152 ++++++++ .../game/fault/trace/access_test.go | 360 +++++++++++++++++- .../game/fault/trace/alphabet/provider.go | 8 + .../game/fault/trace/asterisc/provider.go | 10 + .../game/fault/trace/cannon/provider.go | 10 + .../game/fault/trace/outputs/provider.go | 14 +- .../game/fault/trace/split/split.go | 113 +++--- .../game/fault/trace/split/split_test.go | 355 +++++++++++++++-- op-challenger2/game/fault/trace/translate.go | 16 +- .../game/fault/trace/translate_test.go | 4 +- .../game/fault/trace/utils/daproof.go | 116 ++++++ .../game/fault/trace/utils/daproof_test.go | 76 ++++ op-challenger2/game/fault/types/game.go | 9 +- op-challenger2/game/fault/types/position.go | 11 + op-challenger2/game/fault/types/types.go | 9 + 26 files changed, 1364 insertions(+), 171 deletions(-) create mode 100644 op-challenger2/game/fault/trace/utils/daproof.go create mode 100644 op-challenger2/game/fault/trace/utils/daproof_test.go diff --git a/op-challenger2/game/fault/agent_test.go b/op-challenger2/game/fault/agent_test.go index f3e9ccccfde22..d75b0fbc3504d 100644 --- a/op-challenger2/game/fault/agent_test.go +++ b/op-challenger2/game/fault/agent_test.go @@ -72,13 +72,15 @@ func TestDoNotMakeMovesWhenL2BlockNumberChallenged(t *testing.T) { } func createClaimsWithClaimants(t *testing.T, d types.Depth) []types.Claim { - claimBuilder := test.NewClaimBuilder(t, d, alphabet.NewTraceProvider(big.NewInt(0), d)) + nbits := uint64(1) + splitDepth := types.Depth(3) + claimBuilder := test.NewClaimBuilder2(t, d, nbits, splitDepth, alphabet.NewTraceProvider(big.NewInt(0), d)) rootClaim := claimBuilder.CreateRootClaim() claim1 := rootClaim claim1.Claimant = common.BigToAddress(big.NewInt(1)) - claim2 := claimBuilder.AttackClaim(claim1) + claim2 := claimBuilder.AttackClaim2(claim1, nil, 0) claim2.Claimant = common.BigToAddress(big.NewInt(2)) - claim3 := claimBuilder.AttackClaim(claim2) + claim3 := claimBuilder.AttackClaim2(claim2, nil, 0) claim3.Claimant = common.BigToAddress(big.NewInt(3)) return []types.Claim{claim1, claim2, claim3} } @@ -150,14 +152,16 @@ func TestSkipAttemptingToResolveClaimsWhenClockNotExpired(t *testing.T) { responder.callResolveErr = errors.New("game is not resolvable") responder.callResolveClaimErr = errors.New("claim is not resolvable") depth := types.Depth(4) - claimBuilder := test.NewClaimBuilder(t, depth, alphabet.NewTraceProvider(big.NewInt(0), depth)) + nbits := uint64(1) + splitDepth := types.Depth(3) + claimBuilder := test.NewClaimBuilder2(t, depth, nbits, splitDepth, alphabet.NewTraceProvider(big.NewInt(0), depth)) rootTime := l1Time.Add(-agent.maxClockDuration - 5*time.Minute) gameBuilder := claimBuilder.GameBuilder(test.WithClock(rootTime, 0)) gameBuilder.Seq(). - Attack(test.WithClock(rootTime.Add(5*time.Minute), 5*time.Minute)). - Defend(test.WithClock(rootTime.Add(7*time.Minute), 2*time.Minute)). - Attack(test.WithClock(rootTime.Add(11*time.Minute), 4*time.Minute)) + Attack2(nil, 0, test.WithClock(rootTime.Add(5*time.Minute), 5*time.Minute)). + Attack2(nil, 1, test.WithClock(rootTime.Add(7*time.Minute), 2*time.Minute)). + Attack2(nil, 0, test.WithClock(rootTime.Add(11*time.Minute), 4*time.Minute)) claimLoader.claims = gameBuilder.Game.Claims() require.NoError(t, agent.Act(context.Background())) @@ -173,7 +177,9 @@ func TestLoadClaimsWhenGameNotResolvable(t *testing.T) { responder.callResolveErr = errors.New("game is not resolvable") responder.callResolveClaimErr = errors.New("claim is not resolvable") depth := types.Depth(4) - claimBuilder := test.NewClaimBuilder(t, depth, alphabet.NewTraceProvider(big.NewInt(0), depth)) + nbits := uint64(1) + splitDepth := types.Depth(3) + claimBuilder := test.NewClaimBuilder2(t, depth, nbits, splitDepth, alphabet.NewTraceProvider(big.NewInt(0), depth)) claimLoader.claims = []types.Claim{ claimBuilder.CreateRootClaim(), diff --git a/op-challenger2/game/fault/contracts/faultdisputegame.go b/op-challenger2/game/fault/contracts/faultdisputegame.go index de945aad5ced4..384bb1065bf8e 100644 --- a/op-challenger2/game/fault/contracts/faultdisputegame.go +++ b/op-challenger2/game/fault/contracts/faultdisputegame.go @@ -147,6 +147,9 @@ func mustParseAbi(json []byte) *abi.ABI { } func SubValuesHash(values []common.Hash) common.Hash { + if len(values) == 0 { + return common.Hash{} + } nelem := len(values) hashes := make([]common.Hash, nelem) copy(hashes, values) diff --git a/op-challenger2/game/fault/contracts/faultdisputegame_test.go b/op-challenger2/game/fault/contracts/faultdisputegame_test.go index db9581b2a43f5..ced0eb91ee1f9 100644 --- a/op-challenger2/game/fault/contracts/faultdisputegame_test.go +++ b/op-challenger2/game/fault/contracts/faultdisputegame_test.go @@ -956,6 +956,8 @@ func TestSubValuesHash(t *testing.T) { allClaims := [][]common.Hash{ {{0x01}, {0x02}, {0x03}}, {{0x01}, {0x02}, {0x03}, {0x04}}, + {}, + nil, } Hash := crypto.Keccak256Hash tests := []struct { @@ -970,6 +972,14 @@ func TestSubValuesHash(t *testing.T) { claims: allClaims[1], expectedHash: Hash(Hash(allClaims[1][0][:], allClaims[1][1][:]).Bytes(), Hash(allClaims[1][2][:], allClaims[1][3][:]).Bytes()), }, + { + claims: allClaims[2], + expectedHash: common.Hash{}, + }, + { + claims: allClaims[3], + expectedHash: common.Hash{}, + }, } for _, test := range tests { diff --git a/op-challenger2/game/fault/solver/actors.go b/op-challenger2/game/fault/solver/actors.go index 9ba26732544e7..38ddbbf39b393 100644 --- a/op-challenger2/game/fault/solver/actors.go +++ b/op-challenger2/game/fault/solver/actors.go @@ -43,63 +43,63 @@ var doNothingActor builderFn = func(builder *test.GameBuilder) bool { } var correctAttackLastClaim = respondLastClaim(func(seq *test.GameBuilderSeq) { - seq.Attack() + seq.Attack2(nil, 0) }) var correctDefendLastClaim = respondLastClaim(func(seq *test.GameBuilderSeq) { if seq.IsRoot() { // Must attack the root - seq.Attack() + seq.Attack2(nil, 0) } else { - seq.Defend() + seq.Attack2(nil, 1) } }) var incorrectAttackLastClaim = respondLastClaim(func(seq *test.GameBuilderSeq) { - seq.Attack(test.WithValue(common.Hash{0xaa})) + seq.Attack2(nil, 0, test.WithValue(common.Hash{0xaa})) }) var incorrectDefendLastClaim = respondLastClaim(func(seq *test.GameBuilderSeq) { if seq.IsRoot() { // Must attack the root - seq.Attack(test.WithValue(common.Hash{0xdd})) + seq.Attack2(nil, 0, test.WithValue(common.Hash{0xdd})) } else { - seq.Defend(test.WithValue(common.Hash{0xdd})) + seq.Attack2(nil, 1, test.WithValue(common.Hash{0xdd})) } }) var attackEverythingCorrect = respondAllClaims(func(seq *test.GameBuilderSeq) { - seq.Attack() + seq.Attack2(nil, 0) }) var defendEverythingCorrect = respondAllClaims(func(seq *test.GameBuilderSeq) { if seq.IsRoot() { // Must attack root - seq.Attack() + seq.Attack2(nil, 0) } else { - seq.Defend() + seq.Attack2(nil, 1) } }) var attackEverythingIncorrect = respondAllClaims(func(seq *test.GameBuilderSeq) { - seq.Attack(test.WithValue(common.Hash{0xaa})) + seq.Attack2(nil, 0, test.WithValue(common.Hash{0xaa})) }) var defendEverythingIncorrect = respondAllClaims(func(seq *test.GameBuilderSeq) { if seq.IsRoot() { // Must attack root - seq.Attack(test.WithValue(common.Hash{0xbb})) + seq.Attack2(nil, 0, test.WithValue(common.Hash{0xbb})) } else { - seq.Defend(test.WithValue(common.Hash{0xbb})) + seq.Attack2(nil, 1, test.WithValue(common.Hash{0xbb})) } }) var exhaustive = respondAllClaims(func(seq *test.GameBuilderSeq) { - seq.Attack() - seq.Attack(test.WithValue(common.Hash{0xaa})) + seq.Attack2(nil, 0) + seq.Attack2(nil, 0, test.WithValue(common.Hash{0xaa})) if !seq.IsRoot() { - seq.Defend() - seq.Defend(test.WithValue(common.Hash{0xdd})) + seq.Attack2(nil, 1) + seq.Attack2(nil, 1, test.WithValue(common.Hash{0xdd})) } }) diff --git a/op-challenger2/game/fault/solver/game_solver_test.go b/op-challenger2/game/fault/solver/game_solver_test.go index 42282a1ac839c..9eb9a1a41e504 100644 --- a/op-challenger2/game/fault/solver/game_solver_test.go +++ b/op-challenger2/game/fault/solver/game_solver_test.go @@ -21,8 +21,10 @@ func TestCalculateNextActions_ChallengeL2BlockNumber(t *testing.T) { challenge := &types.InvalidL2BlockNumberChallenge{ Output: ð.OutputResponse{OutputRoot: eth.Bytes32{0xbb}}, } - claimBuilder := faulttest.NewAlphabetClaimBuilder(t, startingBlock, maxDepth) - traceProvider := faulttest.NewAlphabetWithProofProvider(t, startingBlock, maxDepth, nil) + nbits := uint64(1) + splitDepth := types.Depth(3) + 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) // Do not challenge when provider returns error indicating l2 block is valid @@ -43,7 +45,9 @@ func TestCalculateNextActions_ChallengeL2BlockNumber(t *testing.T) { func TestCalculateNextActions(t *testing.T) { maxDepth := types.Depth(6) startingL2BlockNumber := big.NewInt(0) - claimBuilder := faulttest.NewAlphabetClaimBuilder(t, startingL2BlockNumber, maxDepth) + nbits := uint64(1) + splitDepth := types.Depth(3) + claimBuilder := faulttest.NewAlphabetClaimBuilder2(t, startingL2BlockNumber, maxDepth, nbits, splitDepth) tests := []struct { name string @@ -306,7 +310,9 @@ func TestMultipleRounds(t *testing.T) { maxDepth := types.Depth(6) startingL2BlockNumber := big.NewInt(50) - claimBuilder := faulttest.NewAlphabetClaimBuilder(t, startingL2BlockNumber, maxDepth) + nbits := uint64(1) + splitDepth := types.Depth(3) + claimBuilder := faulttest.NewAlphabetClaimBuilder2(t, startingL2BlockNumber, maxDepth, nbits, splitDepth) builder := claimBuilder.GameBuilder(faulttest.WithInvalidValue(!rootClaimCorrect)) game := builder.Game diff --git a/op-challenger2/game/fault/solver/honest_claims_test.go b/op-challenger2/game/fault/solver/honest_claims_test.go index 878f64cc2f90b..6f3617350cba7 100644 --- a/op-challenger2/game/fault/solver/honest_claims_test.go +++ b/op-challenger2/game/fault/solver/honest_claims_test.go @@ -11,7 +11,9 @@ import ( func TestHonestClaimTracker_RootClaim(t *testing.T) { tracker := newHonestClaimTracker() - builder := test.NewAlphabetClaimBuilder(t, big.NewInt(3), 4) + nBits := uint64(1) + splitDepth := types.Depth(3) + builder := test.NewAlphabetClaimBuilder2(t, big.NewInt(3), 4, nBits, splitDepth) claim := builder.Seq().Get() require.False(t, tracker.IsHonest(claim)) @@ -22,11 +24,13 @@ func TestHonestClaimTracker_RootClaim(t *testing.T) { func TestHonestClaimTracker_ChildClaim(t *testing.T) { tracker := newHonestClaimTracker() - builder := test.NewAlphabetClaimBuilder(t, big.NewInt(3), 4) + nBits := uint64(1) + splitDepth := types.Depth(3) + builder := test.NewAlphabetClaimBuilder2(t, big.NewInt(3), 4, nBits, splitDepth) - seq := builder.Seq().Attack().Defend() + seq := builder.Seq().Attack2(nil, 0).Attack2(nil, 1) parent := seq.Get() - child := seq.Attack().Get() + child := seq.Attack2(nil, 0).Get() require.Zero(t, child.ContractIndex, "should work for claims that are not in the game state yet") tracker.AddHonestClaim(parent, child) diff --git a/op-challenger2/game/fault/solver/solver_test.go b/op-challenger2/game/fault/solver/solver_test.go index 09ae0f5d402f1..4fb5723fde8eb 100644 --- a/op-challenger2/game/fault/solver/solver_test.go +++ b/op-challenger2/game/fault/solver/solver_test.go @@ -15,7 +15,9 @@ import ( func TestAttemptStep(t *testing.T) { maxDepth := types.Depth(3) startingL2BlockNumber := big.NewInt(0) - claimBuilder := faulttest.NewAlphabetClaimBuilder(t, startingL2BlockNumber, maxDepth) + nbits := uint64(1) + splitDepth := types.Depth(3) + claimBuilder := faulttest.NewAlphabetClaimBuilder2(t, startingL2BlockNumber, maxDepth, nbits, splitDepth) // Last accessible leaf is the second last trace index // The root node is used for the last trace index and can only be attacked. @@ -42,9 +44,9 @@ func TestAttemptStep(t *testing.T) { expectedOracleData: claimBuilder.CorrectOracleData(common.Big0), setupGame: func(builder *faulttest.GameBuilder) { builder.Seq(). - Attack(faulttest.WithValue(common.Hash{0xaa})). - Attack(). - Attack(faulttest.WithValue(common.Hash{0xbb})) + Attack2(nil, 0, faulttest.WithValue(common.Hash{0xaa})). + Attack2(nil, 0). + Attack2(nil, 0, faulttest.WithValue(common.Hash{0xbb})) }, }, { @@ -55,9 +57,9 @@ func TestAttemptStep(t *testing.T) { expectedOracleData: claimBuilder.CorrectOracleData(big.NewInt(1)), setupGame: func(builder *faulttest.GameBuilder) { builder.Seq(). - Attack(faulttest.WithValue(common.Hash{0xaa})). - Attack(). - Attack() + Attack2(nil, 0, faulttest.WithValue(common.Hash{0xaa})). + Attack2(nil, 0). + Attack2(nil, 0) }, }, { @@ -68,9 +70,9 @@ func TestAttemptStep(t *testing.T) { expectedOracleData: claimBuilder.CorrectOracleData(big.NewInt(4)), setupGame: func(builder *faulttest.GameBuilder) { builder.Seq(). - Attack(). - Defend(). - Attack(faulttest.WithValue(common.Hash{0xaa})) + Attack2(nil, 0). + Attack2(nil, 1). + Attack2(nil, 0, faulttest.WithValue(common.Hash{0xaa})) }, }, { @@ -81,9 +83,9 @@ func TestAttemptStep(t *testing.T) { expectedOracleData: claimBuilder.CorrectOracleData(big.NewInt(5)), setupGame: func(builder *faulttest.GameBuilder) { builder.Seq(). - Attack(). - Defend(). - Attack() + Attack2(nil, 0). + Attack2(nil, 1). + Attack2(nil, 0) }, }, { @@ -94,9 +96,9 @@ func TestAttemptStep(t *testing.T) { expectedOracleData: claimBuilder.CorrectOracleData(lastLeafTraceIndex), setupGame: func(builder *faulttest.GameBuilder) { builder.Seq(). - Attack(). - Defend(). - Defend(faulttest.WithValue(common.Hash{0xaa})) + Attack2(nil, 0). + Attack2(nil, 1). + Attack2(nil, 1, faulttest.WithValue(common.Hash{0xaa})) }, }, { @@ -107,15 +109,15 @@ func TestAttemptStep(t *testing.T) { expectedOracleData: claimBuilder.CorrectOracleData(lastLeafTraceIndexPlusOne), setupGame: func(builder *faulttest.GameBuilder) { builder.Seq(). - Attack(). - Defend(). - Defend() + Attack2(nil, 0). + Attack2(nil, 1). + Attack2(nil, 1) }, }, { name: "CannotStepNonLeaf", setupGame: func(builder *faulttest.GameBuilder) { - builder.Seq().Attack().Attack() + builder.Seq().Attack2(nil, 0).Attack2(nil, 0) }, expectedErr: ErrStepNonLeafNode, agreeWithOutputRoot: true, @@ -124,9 +126,9 @@ func TestAttemptStep(t *testing.T) { name: "CannotStepAgreedNode", setupGame: func(builder *faulttest.GameBuilder) { builder.Seq(). - Attack(). - Attack(faulttest.WithValue(common.Hash{0xaa})). - Attack() + Attack2(nil, 0). + Attack2(nil, 0, faulttest.WithValue(common.Hash{0xaa})). + Attack2(nil, 0) }, expectNoStep: true, agreeWithOutputRoot: true, @@ -135,9 +137,9 @@ func TestAttemptStep(t *testing.T) { name: "CannotStepInvalidPath", setupGame: func(builder *faulttest.GameBuilder) { builder.Seq(). - Attack(faulttest.WithValue(common.Hash{0xaa})). - Attack(faulttest.WithValue(common.Hash{0xbb})). - Attack(faulttest.WithValue(common.Hash{0xcc})) + Attack2(nil, 0, faulttest.WithValue(common.Hash{0xaa})). + Attack2(nil, 0, faulttest.WithValue(common.Hash{0xbb})). + Attack2(nil, 0, faulttest.WithValue(common.Hash{0xcc})) }, expectNoStep: true, agreeWithOutputRoot: true, @@ -150,9 +152,9 @@ func TestAttemptStep(t *testing.T) { expectedOracleData: claimBuilder.CorrectOracleData(big.NewInt(4)), setupGame: func(builder *faulttest.GameBuilder) { builder.Seq(). - Attack(). - Defend(). - Defend() + Attack2(nil, 0). + Attack2(nil, 1). + Attack2(nil, 1) }, expectNoStep: true, agreeWithOutputRoot: true, diff --git a/op-challenger2/game/fault/test/alphabet.go b/op-challenger2/game/fault/test/alphabet.go index b8a4485624e6c..1bcfdaf25388e 100644 --- a/op-challenger2/game/fault/test/alphabet.go +++ b/op-challenger2/game/fault/test/alphabet.go @@ -2,25 +2,36 @@ package test import ( "context" + "fmt" "math/big" "testing" "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/trace/alphabet" "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/types" + oppreimage "github.com/ethereum-optimism/optimism/op-preimage" ) -func NewAlphabetWithProofProvider(t *testing.T, startingL2BlockNumber *big.Int, maxDepth types.Depth, oracleError error) *AlphabetWithProofProvider { +type TestKeyType int + +const ( + OraclePreKey TestKeyType = iota + OraclPostKey TestKeyType = iota + OracleDefaultKey TestKeyType = iota +) + +func NewAlphabetWithProofProvider(t *testing.T, startingL2BlockNumber *big.Int, maxDepth types.Depth, oracleError error, rootDepth types.Depth, oracleKey TestKeyType) *AlphabetWithProofProvider { return &AlphabetWithProofProvider{ alphabet.NewTraceProvider(startingL2BlockNumber, maxDepth), maxDepth, oracleError, nil, + oracleKey, } } -func NewAlphabetClaimBuilder(t *testing.T, startingL2BlockNumber *big.Int, maxDepth types.Depth) *ClaimBuilder { - alphabetProvider := NewAlphabetWithProofProvider(t, startingL2BlockNumber, maxDepth, nil) - return NewClaimBuilder(t, maxDepth, alphabetProvider) +func NewAlphabetClaimBuilder2(t *testing.T, startingL2BlockNumber *big.Int, maxDepth types.Depth, nbits uint64, splitDepth types.Depth) *ClaimBuilder { + alphabetProvider := NewAlphabetWithProofProvider(t, startingL2BlockNumber, maxDepth, nil, splitDepth+types.Depth(nbits), OracleDefaultKey) + return NewClaimBuilder2(t, maxDepth, nbits, splitDepth, alphabetProvider) } type AlphabetWithProofProvider struct { @@ -28,6 +39,7 @@ type AlphabetWithProofProvider struct { depth types.Depth OracleError error L2BlockChallenge *types.InvalidL2BlockNumberChallenge + pre TestKeyType } func (a *AlphabetWithProofProvider) GetStepData(ctx context.Context, i types.Position) ([]byte, []byte, *types.PreimageOracleData, error) { @@ -36,10 +48,25 @@ func (a *AlphabetWithProofProvider) GetStepData(ctx context.Context, i types.Pos return nil, nil, nil, err } traceIndex := i.TraceIndex(a.depth).Uint64() - data := types.NewPreimageOracleData([]byte{byte(traceIndex)}, []byte{byte(traceIndex - 1)}, uint32(traceIndex-1)) + var key []byte + switch a.pre { + case OraclePreKey: + // localPreimageKey(1) + STARTING_OUTPUT_ROOT(2) + key = []byte{byte(oppreimage.LocalKeyType), types.LocalPreimageKeyStartingOutputRoot} + case OraclPostKey: + // localPreimageKey(1) + DISPUTED_OUTPUT_ROOT(3) + key = []byte{byte(oppreimage.LocalKeyType), types.LocalPreimageKeyDisputedOutputRoot} + case OracleDefaultKey: + key = []byte{byte(traceIndex)} + } + data := types.NewPreimageOracleData(key, []byte{byte(traceIndex - 1)}, uint32(traceIndex-1)) return preimage, []byte{byte(traceIndex - 1)}, data, nil } +func (ap *AlphabetWithProofProvider) GetStepData2(ctx context.Context, pos types.Position) ([]byte, []byte, *types.PreimageOracleData, error) { + return nil, nil, nil, fmt.Errorf("alphabetWithProofProvider GetStepData2 is not supported, use GetStepData instead") +} + func (c *AlphabetWithProofProvider) GetL2BlockNumberChallenge(_ context.Context) (*types.InvalidL2BlockNumberChallenge, error) { if c.L2BlockChallenge != nil { return c.L2BlockChallenge, nil diff --git a/op-challenger2/game/fault/test/claim_builder.go b/op-challenger2/game/fault/test/claim_builder.go index 8bdd6fc39b99b..91d07d6d1c0d4 100644 --- a/op-challenger2/game/fault/test/claim_builder.go +++ b/op-challenger2/game/fault/test/claim_builder.go @@ -7,6 +7,7 @@ import ( "testing" "time" + "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/contracts" "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/types" "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/require" @@ -21,6 +22,8 @@ type claimCfg struct { parentIdx int clockTimestamp time.Time clockDuration time.Duration + branch uint64 + subValues *[]common.Hash } func newClaimCfg(opts ...ClaimOpt) *claimCfg { @@ -74,19 +77,35 @@ func WithClock(timestamp time.Time, duration time.Duration) ClaimOpt { }) } +func WithBranch(branch uint64) ClaimOpt { + return claimOptFn(func(cfg *claimCfg) { + cfg.branch = branch + }) +} + +func WithSubValues(subValues *[]common.Hash) ClaimOpt { + return claimOptFn(func(cfg *claimCfg) { + cfg.subValues = subValues + }) +} + // ClaimBuilder is a test utility to enable creating claims in a wide range of situations type ClaimBuilder struct { - require *require.Assertions - maxDepth types.Depth - correct types.TraceProvider + require *require.Assertions + maxDepth types.Depth + nbits uint64 + splitDepth types.Depth + correct types.TraceProvider } // NewClaimBuilder creates a new [ClaimBuilder]. -func NewClaimBuilder(t *testing.T, maxDepth types.Depth, provider types.TraceProvider) *ClaimBuilder { +func NewClaimBuilder2(t *testing.T, maxDepth types.Depth, nbits uint64, splitDepth types.Depth, provider types.TraceProvider) *ClaimBuilder { return &ClaimBuilder{ - require: require.New(t), - maxDepth: maxDepth, - correct: provider, + require: require.New(t), + maxDepth: maxDepth, + nbits: nbits, + splitDepth: splitDepth, + correct: provider, } } @@ -140,6 +159,7 @@ func (c *ClaimBuilder) claim(pos types.Position, opts ...ClaimOpt) types.Claim { Duration: cfg.clockDuration, Timestamp: cfg.clockTimestamp, }, + AttackBranch: cfg.branch, } if cfg.claimant != (common.Address{}) { claim.Claimant = cfg.claimant @@ -150,6 +170,11 @@ func (c *ClaimBuilder) claim(pos types.Position, opts ...ClaimOpt) types.Claim { 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.ParentContractIndex = cfg.parentIdx return claim @@ -165,9 +190,15 @@ func (c *ClaimBuilder) CreateLeafClaim(traceIndex *big.Int, opts ...ClaimOpt) ty return c.claim(pos, opts...) } -func (c *ClaimBuilder) AttackClaim(claim types.Claim, opts ...ClaimOpt) types.Claim { - pos := claim.Position.Attack() - return c.claim(pos, append([]ClaimOpt{WithParent(claim)}, opts...)...) +func (c *ClaimBuilder) AttackClaim2(claim types.Claim, subValues []common.Hash, branch uint64, opts ...ClaimOpt) types.Claim { + pos := claim.Position.MoveN(c.nbits, branch) + if subValues == nil { + // aggCLaim will be auto generated in this function + return c.claim(pos, append([]ClaimOpt{WithParent(claim), WithBranch(branch)}, opts...)...) + } else { + aggClaim := contracts.SubValuesHash(subValues) + return c.claim(pos, append([]ClaimOpt{WithParent(claim), WithValue(aggClaim), WithBranch(branch), WithSubValues(&subValues)}, opts...)...) + } } func (c *ClaimBuilder) DefendClaim(claim types.Claim, opts ...ClaimOpt) types.Claim { diff --git a/op-challenger2/game/fault/test/game_builder.go b/op-challenger2/game/fault/test/game_builder.go index e9ddb6f809793..823a1935fb38f 100644 --- a/op-challenger2/game/fault/test/game_builder.go +++ b/op-challenger2/game/fault/test/game_builder.go @@ -15,17 +15,17 @@ type GameBuilder struct { } func NewGameBuilderFromGame(t *testing.T, provider types.TraceProvider, game types.Game) *GameBuilder { - claimBuilder := NewClaimBuilder(t, game.MaxDepth(), provider) + claimBuilder := NewClaimBuilder2(t, game.MaxDepth(), game.NBits(), game.SplitDepth(), provider) return &GameBuilder{ builder: claimBuilder, - Game: types.NewGameState(game.Claims(), game.MaxDepth()), + Game: types.NewGameState2(game.Claims(), game.MaxDepth(), game.NBits(), game.SplitDepth()), } } func (c *ClaimBuilder) GameBuilder(rootOpts ...ClaimOpt) *GameBuilder { return &GameBuilder{ builder: c, - Game: types.NewGameState([]types.Claim{c.CreateRootClaim(rootOpts...)}, c.maxDepth), + Game: types.NewGameState2([]types.Claim{c.CreateRootClaim(rootOpts...)}, c.maxDepth, c.nbits, c.splitDepth), } } @@ -55,6 +55,10 @@ func (g *GameBuilderSeq) IsRoot() bool { return g.lastClaim.IsRoot() } +func (g *GameBuilderSeq) LastClaim() types.Claim { + return g.lastClaim +} + // 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 @@ -64,11 +68,11 @@ func (s *GameBuilderSeq) addClaimToGame(claim *types.Claim) { } claim.ContractIndex = len(s.gameBuilder.Game.Claims()) claims := append(s.gameBuilder.Game.Claims(), *claim) - s.gameBuilder.Game = types.NewGameState(claims, s.builder.maxDepth) + s.gameBuilder.Game = types.NewGameState2(claims, s.builder.maxDepth, s.builder.nbits, s.builder.splitDepth) } func (s *GameBuilderSeq) Attack(opts ...ClaimOpt) *GameBuilderSeq { - claim := s.builder.AttackClaim(s.lastClaim, opts...) + claim := s.builder.AttackClaim2(s.lastClaim, nil, 0, opts...) s.addClaimToGame(&claim) return &GameBuilderSeq{ gameBuilder: s.gameBuilder, @@ -87,6 +91,16 @@ func (s *GameBuilderSeq) Defend(opts ...ClaimOpt) *GameBuilderSeq { } } +func (s *GameBuilderSeq) Attack2(subValues []common.Hash, branch uint64, opts ...ClaimOpt) *GameBuilderSeq { + claim := s.builder.AttackClaim2(s.lastClaim, subValues, branch, opts...) + s.addClaimToGame(&claim) + return &GameBuilderSeq{ + gameBuilder: s.gameBuilder, + builder: s.builder, + lastClaim: claim, + } +} + func (s *GameBuilderSeq) Step(opts ...ClaimOpt) { cfg := newClaimCfg(opts...) claimant := DefaultClaimant @@ -95,7 +109,7 @@ func (s *GameBuilderSeq) Step(opts ...ClaimOpt) { } claims := s.gameBuilder.Game.Claims() claims[len(claims)-1].CounteredBy = claimant - s.gameBuilder.Game = types.NewGameState(claims, s.builder.maxDepth) + s.gameBuilder.Game = types.NewGameState2(claims, s.builder.maxDepth, s.builder.nbits, s.builder.splitDepth) } func (s *GameBuilderSeq) ExpectAttack() *GameBuilderSeq { diff --git a/op-challenger2/game/fault/test/seq_builder.go b/op-challenger2/game/fault/test/seq_builder.go index ce5da3c60099f..d2e96f15b3c43 100644 --- a/op-challenger2/game/fault/test/seq_builder.go +++ b/op-challenger2/game/fault/test/seq_builder.go @@ -2,6 +2,7 @@ package test import ( "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/types" + "github.com/ethereum/go-ethereum/common" ) type SequenceBuilder struct { @@ -20,8 +21,8 @@ func (c *ClaimBuilder) Seq(rootOpts ...ClaimOpt) *SequenceBuilder { } } -func (s *SequenceBuilder) Attack(opts ...ClaimOpt) *SequenceBuilder { - claim := s.builder.AttackClaim(s.lastClaim, opts...) +func (s *SequenceBuilder) Attack2(subValues []common.Hash, branch uint64, opts ...ClaimOpt) *SequenceBuilder { + claim := s.builder.AttackClaim2(s.lastClaim, subValues, branch, opts...) return &SequenceBuilder{ builder: s.builder, lastClaim: claim, diff --git a/op-challenger2/game/fault/trace/access.go b/op-challenger2/game/fault/trace/access.go index 1fb5281b082ce..869740fe56bdb 100644 --- a/op-challenger2/game/fault/trace/access.go +++ b/op-challenger2/game/fault/trace/access.go @@ -2,8 +2,12 @@ package trace import ( "context" + "fmt" + "math/big" + "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/trace/utils" "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/types" + preimage "github.com/ethereum-optimism/optimism/op-preimage" "github.com/ethereum/go-ethereum/common" ) @@ -16,6 +20,102 @@ func NewSimpleTraceAccessor(trace types.TraceProvider) *Accessor { type ProviderSelector func(ctx context.Context, game types.Game, ref types.Claim, pos types.Position) (types.TraceProvider, error) +func FindAncestorAtDepth(game types.Game, claim types.Claim, depth types.Depth) (types.Claim, error) { + for claim.Depth() > depth { + parent, err := game.GetParent(claim) + if err != nil { + return types.Claim{}, fmt.Errorf("failed to find ancestor at depth %v: %w", depth, err) + } + claim = parent + } + return claim, nil +} + +// Get the traceIdx's accestor claims hash and its merkel proof in subValues. traceIdx must be ref's traceIdx ±1. +// Params: +// - relativeTraceIdx: the trace index relative to splitDepth +func findAncestorProofAtDepth2(ctx context.Context, provider types.TraceProvider, game types.Game, ref types.Claim, relativeTraceIdx *big.Int) (types.DAItem, error) { + maxTraceDepth := game.MaxDepth() - game.SplitDepth() + // Find the ancestor claim at the (splitDepth + nbits) level. + splitLeaf, err := FindAncestorAtDepth(game, ref, types.Depth(game.TraceRootDepth())) + if err != nil { + return types.DAItem{}, err + } + + ancestor := ref + relativeAncestorPos, err := ancestor.RelativeToAncestorAtDepth(game.SplitDepth()) + if err != nil { + return types.DAItem{}, err + } + minTraceIndex := relativeAncestorPos.TraceIndex(maxTraceDepth) + offset := new(big.Int).Lsh(big.NewInt(int64(game.MaxAttackBranch())-1), uint(maxTraceDepth-relativeAncestorPos.Depth())) + maxTraceIndex := new(big.Int).Add(minTraceIndex, offset) + minPrePostTraceIdx := new(big.Int).Sub(minTraceIndex, big.NewInt(1)) + maxPrePostTraceIdx := new(big.Int).Add(maxTraceIndex, big.NewInt(1)) + if relativeTraceIdx.Cmp(minPrePostTraceIdx) == -1 || relativeTraceIdx.Cmp(maxPrePostTraceIdx) == 1 { + return types.DAItem{}, fmt.Errorf("relativeTraceIdx %v is beyond ref claims's pre/post trace range [%v, %v]", relativeTraceIdx, minPrePostTraceIdx, maxPrePostTraceIdx) + } + + // If traceIdx equals -1, the absolute prestate is returned. + if relativeTraceIdx.Cmp(big.NewInt(-1)) == 0 { + absolutePresate, err := provider.AbsolutePreStateCommitment(ctx) + if err != nil { + return types.DAItem{}, fmt.Errorf("failed to get absolutePrestate: %w", err) + } + return types.DAItem{ + DaType: types.CallDataType, + DataHash: absolutePresate, + Proof: []byte{}, + }, nil + } + + // If traceIdx is the right most branch, the root Claim is returned. + if new(big.Int).Add(relativeTraceIdx, big.NewInt(1)).Cmp(new(big.Int).Lsh(big.NewInt(1), uint(maxTraceDepth-types.Depth(game.NBits())))) == 0 { + return types.DAItem{ + DaType: types.CallDataType, + DataHash: splitLeaf.Value, + Proof: []byte{}, + }, nil + } + + // If Ancestor's left most branch traceIdx <= traceId <= Ancestor's rightmost branch traceIdx, we do nothing; + // otherwise, we should trace the correct ancestor + for minTraceIndex.Cmp(relativeTraceIdx) == 1 || maxTraceIndex.Cmp(relativeTraceIdx) == -1 { + parent, err := game.GetParent(ancestor) + if err != nil { + return types.DAItem{}, fmt.Errorf("failed to get ancestor of claim %v: %w", ancestor.ContractIndex, err) + } + + ancestor = parent + relativeAncestorPos, err = ancestor.RelativeToAncestorAtDepth(game.SplitDepth()) + if err != nil { + return types.DAItem{}, err + } + minTraceIndex = relativeAncestorPos.TraceIndex(maxTraceDepth) + offset = new(big.Int).Lsh(big.NewInt(int64(game.MaxAttackBranch())-1), uint(maxTraceDepth-relativeAncestorPos.Depth())) + maxTraceIndex = new(big.Int).Add(minTraceIndex, offset) + } + + branch := new(big.Int).Div(new(big.Int).Sub(relativeTraceIdx, minTraceIndex), new(big.Int).Lsh(big.NewInt(1), uint(maxTraceDepth-relativeAncestorPos.Depth()))).Int64() + subValues := *ancestor.SubValues + ancestorClaim := subValues[branch] + merkleProof := utils.GenerateProofForSubValues(subValues, uint32(branch)) + // merkleProof := make([]byte, 0) + // for i := int64(0); i < branch; i++ { + // merkleProof = append(merkleProof, subValues[i].Bytes()...) + // } + + // for i := branch + 1; i < int64(game.MaxAttackBranch()); i++ { + // merkleProof = append(merkleProof, subValues[i].Bytes()...) + // } + + return types.DAItem{ + DaType: types.CallDataType, + DataHash: ancestorClaim, + Proof: merkleProof, + }, nil +} + func NewAccessor(selector ProviderSelector) *Accessor { return &Accessor{selector} } @@ -40,6 +140,58 @@ func (t *Accessor) GetStepData(ctx context.Context, game types.Game, ref types.C return provider.GetStepData(ctx, pos) } +func (t *Accessor) GetStepData2(ctx context.Context, game types.Game, ref types.Claim, pos types.Position) (prestate []byte, proofData []byte, preimageData *types.PreimageOracleData, err error) { + // Get oracle data + provider, err := t.selector(ctx, game, ref, pos) + if err != nil { + return nil, nil, nil, err + } + // Get output root oracle for addLocaldata + outputRootDA, err := provider.GetLocalData(ctx) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to get local data: %w", err) + } + + prestate, proofData, preimageData, err = provider.GetStepData(ctx, pos) + if err != nil { + return nil, nil, nil, err + } + // Get stepProof for stepV2 + relativePos, err := pos.RelativeToAncestorAtDepth(game.TraceRootDepth()) + if err != nil { + return nil, nil, nil, err + } + postTraceIdx := relativePos.TraceIndex(game.MaxDepth() - game.TraceRootDepth()) + 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) + } + postStateDaItem, err := findAncestorProofAtDepth2(ctx, provider, game, ref, postTraceIdx) + if err != nil { + return nil, nil, nil, fmt.Errorf("failed to get postStateDaItem at trace index %v: %w", postTraceIdx, err) + } + stateData := types.DAData{ + PreDA: preStateDaItem, + PostDA: postStateDaItem, + } + + 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 + } + preimageData.OutputRootDAItem = addlocalDataDaItem + } + return prestate, proofData, preimageData, nil +} + func (t *Accessor) GetL2BlockNumberChallenge(ctx context.Context, game types.Game) (*types.InvalidL2BlockNumberChallenge, error) { provider, err := t.selector(ctx, game, game.Claims()[0], types.RootPosition) if err != nil { diff --git a/op-challenger2/game/fault/trace/access_test.go b/op-challenger2/game/fault/trace/access_test.go index 4c16969caf6e9..c2263b20bd124 100644 --- a/op-challenger2/game/fault/trace/access_test.go +++ b/op-challenger2/game/fault/trace/access_test.go @@ -10,13 +10,15 @@ import ( "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/trace/alphabet" "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/types" "github.com/ethereum-optimism/optimism/op-service/eth" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" ) func TestAccessor_UsesSelector(t *testing.T) { ctx := context.Background() depth := types.Depth(4) - provider1 := test.NewAlphabetWithProofProvider(t, big.NewInt(0), depth, nil) + provider1 := test.NewAlphabetWithProofProvider(t, big.NewInt(0), depth, nil, 0, test.OracleDefaultKey) provider2 := alphabet.NewTraceProvider(big.NewInt(0), depth) claim := types.Claim{} game := types.NewGameState([]types.Claim{claim}, depth) @@ -97,3 +99,359 @@ func (c *ChallengeTraceProvider) GetL2BlockNumberChallenge(_ context.Context) (* Output: ð.OutputResponse{OutputRoot: eth.Bytes32{0xaa, 0xbb}}, }, nil } + +func TestGetStepData2(t *testing.T) { + ctx := context.Background() + traceDepth := types.Depth(4) + splitDepth := types.Depth(2) + nbits := uint64(2) + nary := int64(1) << nbits + maxDepth := traceDepth + splitDepth + types.Depth(nbits) + provider := test.NewAlphabetWithProofProvider(t, big.NewInt(0), traceDepth, nil, splitDepth, test.OracleDefaultKey) + translatedTracerovider := Translate(provider, splitDepth+types.Depth(nary), types.DAData{}) + + claimBuilder := test.NewAlphabetClaimBuilder2(t, big.NewInt(0), maxDepth, nbits, splitDepth) + gameBuilder := claimBuilder.GameBuilder() + seq := gameBuilder.Seq() + subClaimsDep2 := []common.Hash{{0x01}, {0x02}, {0x03}} // claim at splitDepth + seq = seq.Attack2(subClaimsDep2[:], 0) + // traces + subClaimsDep4 := []common.Hash{{0x04}} + seq = seq.Attack2(subClaimsDep4[:], 0) + subClaimsDep6 := []common.Hash{{0x61}, {0x62}, {0x63}} + seq = seq.Attack2(subClaimsDep6[:], 0) + subClaimsDep8 := []common.Hash{{0x81}, {0x82}, {0x83}} + seq.Attack2(subClaimsDep8[:], 1) + + game := gameBuilder.Game + claim := game.Claims()[len(game.Claims())-1] + + pos := claim.Position.MoveRight() + + accessor := &Accessor{ + selector: func(ctx context.Context, actualGame types.Game, ref types.Claim, pos types.Position) (types.TraceProvider, error) { + require.Equal(t, game, actualGame) + require.Equal(t, claim, ref) + return translatedTracerovider, nil + }, + } + + expectedStateDA := types.DAData{ + PreDA: types.DAItem{ + DaType: types.CallDataType, + DataHash: subClaimsDep8[0], + Proof: append(subClaimsDep8[1][:], subClaimsDep8[2][:]...), + }, + PostDA: types.DAItem{ + DaType: types.CallDataType, + DataHash: subClaimsDep8[1], + Proof: append(subClaimsDep8[0][:], subClaimsDep8[2][:]...), + }, + } + + expectedPrestate, expectedProofData, expectedPreimageData, err := translatedTracerovider.GetStepData(ctx, pos) + expectedPreimageData.VMStateDA = expectedStateDA + require.NoError(t, err) + + actualPrestate, actualProofData, actualPreimageData, err := accessor.GetStepData2(ctx, game, claim, pos) + require.NoError(t, err) + + require.Equal(t, expectedPrestate, actualPrestate) + require.Equal(t, expectedProofData, actualProofData) + require.Equal(t, expectedPreimageData, actualPreimageData) +} + +func TestGetStepDataWithOutputRoot(t *testing.T) { + ctx := context.Background() + traceDepth := types.Depth(4) + splitDepth := types.Depth(2) + nbits := uint64(2) + nary := int64(1) << nbits + maxDepth := traceDepth + splitDepth + types.Depth(nbits) + + claimBuilder := test.NewAlphabetClaimBuilder2(t, big.NewInt(0), maxDepth, nbits, splitDepth) + gameBuilder := claimBuilder.GameBuilder() + seq := gameBuilder.Seq() + subClaimsDep2 := []common.Hash{{0x01}, {0x02}, {0x03}} // claim at splitDepth + seq = seq.Attack2(subClaimsDep2[:], 0) + // traces + subClaimsDep4 := []common.Hash{{0x04}} + seq = seq.Attack2(subClaimsDep4[:], 0) + subClaimsDep6 := []common.Hash{{0x61}, {0x62}, {0x63}} + seq = seq.Attack2(subClaimsDep6[:], 0) + subClaimsDep8 := []common.Hash{{0x81}, {0x82}, {0x83}} + seq.Attack2(subClaimsDep8[:], 1) + + game := gameBuilder.Game + claim := game.Claims()[len(game.Claims())-1] + + pos := claim.Position.MoveRight() + + outputRootDA := types.DAData{ + PreDA: types.DAItem{}, + PostDA: types.DAItem{ + DaType: types.CallDataType, + DataHash: subClaimsDep2[0], + Proof: append(subClaimsDep2[1][:], subClaimsDep2[2][:]...), + }, + } + + tests := []struct { + oracalKeyType test.TestKeyType + expectedDAItem types.DAItem + }{ + {test.OraclePreKey, outputRootDA.PreDA}, + {test.OraclPostKey, outputRootDA.PostDA}, + } + + for _, tCase := range tests { + provider := test.NewAlphabetWithProofProvider(t, big.NewInt(0), traceDepth, nil, splitDepth, tCase.oracalKeyType) + translatedTracerovider := Translate(provider, splitDepth+types.Depth(nary), outputRootDA) + accessor := &Accessor{ + selector: func(ctx context.Context, actualGame types.Game, ref types.Claim, pos types.Position) (types.TraceProvider, error) { + require.Equal(t, game, actualGame) + require.Equal(t, claim, ref) + return translatedTracerovider, nil + }, + } + + _, _, actualPreimageData, err := accessor.GetStepData2(ctx, game, claim, pos) + require.NoError(t, err) + require.Equal(t, tCase.expectedDAItem, actualPreimageData.OutputRootDAItem) + } +} + +func TestFindAncestorProofAtDepth2(t *testing.T) { + nbits := uint64(2) + maxGameDepth := types.Depth(8) + splitDepth := types.Depth(2) + traceDepth := maxGameDepth - splitDepth - types.Depth(nbits) + ctx := context.Background() + provider := test.NewAlphabetWithProofProvider(t, big.NewInt(0), traceDepth, nil, splitDepth+types.Depth(nbits), test.OracleDefaultKey) + + tests := []struct { + name string + setup func(t *testing.T, ctx context.Context, gameBuilder *test.GameBuilder, provider *test.AlphabetWithProofProvider) (game types.Game, ref types.Claim, preTraceIdx *big.Int, postTraceIdx *big.Int, expectPreDA types.DAItem, expectPostDA types.DAItem) + }{ + // attack every claims's first branch (index at 0) + {"attackLeftMost", attackLeftMost}, + // attack one claim's branch between [1,maxBranch-1] and leaf claims's first branch + {"attackFirstBranch", attackFirstBranch}, + // attack one claim's branch between [1,maxBranch-1] and leaf claim's branch between [1,maxBranch-1] + {"attackMidBranch", attackMidBranch}, + // attack one claim's branch between [1,maxBranch-1] and leaf claim's last branch (index at maxBranch) + {"attackMaxBranch", attackMaxBranch}, + // attack every claims's last branch + {"attackRightMost", attackRightMost}, + } + + for _, tCase := range tests { + t.Run(tCase.name, func(t *testing.T) { + claimBuilder := test.NewAlphabetClaimBuilder2(t, big.NewInt(0), maxGameDepth, nbits, splitDepth) + game, ref, preTraceIdx, postTraceIdx, expectPreDA, expectPostDA := tCase.setup(t, ctx, claimBuilder.GameBuilder(), provider) + preStateDaItem, err := findAncestorProofAtDepth2(ctx, provider, game, ref, preTraceIdx) + require.NoError(t, err) + require.Equal(t, expectPreDA, preStateDaItem) + postStateDaItem, err := findAncestorProofAtDepth2(ctx, provider, game, ref, postTraceIdx) + require.NoError(t, err) + require.Equal(t, expectPostDA, postStateDaItem) + }) + } +} + +func attackLeftMost(t *testing.T, ctx context.Context, gameBuilder *test.GameBuilder, provider *test.AlphabetWithProofProvider) (game types.Game, ref types.Claim, preTraceIdx *big.Int, postTraceIdx *big.Int, expectPreDA types.DAItem, expectPostDA types.DAItem) { + depth8Branch := uint64(0) + stepBranch := int64(0) + // output roots + seq := gameBuilder.Seq() + subClaimsDep2 := []common.Hash{{0x01}, {0x02}, {0x03}} // claim at splitDepth + seq = seq.Attack2(subClaimsDep2[:], 0) + // traces + subClaimsDep4 := []common.Hash{{0x04}} + seq = seq.Attack2(subClaimsDep4[:], 0) + subClaimsDep6 := []common.Hash{{0x61}, {0x62}, {0x63}} + seq = seq.Attack2(subClaimsDep6[:], 0) + subClaimsDep8 := []common.Hash{{0x81}, {0x82}, {0x83}} + seq.Attack2(subClaimsDep8[:], depth8Branch) + + game = gameBuilder.Game + ref = game.Claims()[4] + pos := types.NewPositionFromGIndex(big.NewInt(ref.ToGIndex().Int64() + stepBranch)) + traceRootDepth := game.TraceRootDepth() + relativePos, err := pos.RelativeToAncestorAtDepth(traceRootDepth) + require.NoError(t, err) + traceDepth := game.MaxDepth() - game.TraceRootDepth() + postTraceIdx = relativePos.TraceIndex(traceDepth) + preTraceIdx = new(big.Int).Sub(postTraceIdx, big.NewInt(1)) + + absolutePre, err := provider.AbsolutePreStateCommitment(ctx) + require.NoError(t, err) + expectPreDA = types.DAItem{ + DaType: types.CallDataType, + DataHash: absolutePre, + Proof: []byte{}, + } + + expectPostDA = types.DAItem{ + DaType: types.CallDataType, + DataHash: subClaimsDep8[0], + Proof: append(subClaimsDep8[1][:], subClaimsDep8[2][:]...), + } + return +} + +func attackFirstBranch(t *testing.T, ctx context.Context, gameBuilder *test.GameBuilder, provider *test.AlphabetWithProofProvider) (game types.Game, ref types.Claim, preTraceIdx *big.Int, postTraceIdx *big.Int, expectPreDA types.DAItem, expectPostDA types.DAItem) { + depth8Branch := uint64(1) + stepBranch := int64(0) + // output roots + seq := gameBuilder.Seq() + subClaimsDep2 := []common.Hash{{0x01}, {0x02}, {0x03}} // claim at splitDepth + seq = seq.Attack2(subClaimsDep2[:], 0) + // traces + subClaimsDep4 := []common.Hash{{0x04}} + seq = seq.Attack2(subClaimsDep4[:], 0) + subClaimsDep6 := []common.Hash{{0x61}, {0x62}, {0x63}} + seq = seq.Attack2(subClaimsDep6[:], 0) + subClaimsDep8 := []common.Hash{{0x81}, {0x82}, {0x83}} + seq.Attack2(subClaimsDep8[:], depth8Branch) + + game = gameBuilder.Game + ref = game.Claims()[4] + pos := types.NewPositionFromGIndex(big.NewInt(ref.ToGIndex().Int64() + stepBranch)) + traceRootDepth := game.TraceRootDepth() + relativePos, err := pos.RelativeToAncestorAtDepth(traceRootDepth) + require.NoError(t, err) + + traceDepth := game.MaxDepth() - game.TraceRootDepth() + postTraceIdx = relativePos.TraceIndex(traceDepth) + preTraceIdx = new(big.Int).Sub(postTraceIdx, big.NewInt(1)) + + expectPreDA = types.DAItem{ + DaType: types.CallDataType, + DataHash: subClaimsDep6[0], + Proof: append(subClaimsDep6[1][:], subClaimsDep6[2][:]...), + } + + expectPostDA = types.DAItem{ + DaType: types.CallDataType, + DataHash: subClaimsDep8[0], + Proof: append(subClaimsDep8[1][:], subClaimsDep8[2][:]...), + } + return +} +func attackMidBranch(t *testing.T, ctx context.Context, gameBuilder *test.GameBuilder, provider *test.AlphabetWithProofProvider) (game types.Game, ref types.Claim, preTraceIdx *big.Int, postTraceIdx *big.Int, expectPreDA types.DAItem, expectPostDA types.DAItem) { + depth8Branch := uint64(1) + stepBranch := int64(1) + // output roots + seq := gameBuilder.Seq() + subClaimsDep2 := []common.Hash{{0x01}, {0x02}, {0x03}} // claim at splitDepth + seq = seq.Attack2(subClaimsDep2[:], 0) + // traces + subClaimsDep4 := []common.Hash{{0x04}} + seq = seq.Attack2(subClaimsDep4[:], 0) + subClaimsDep6 := []common.Hash{{0x61}, {0x62}, {0x63}} + seq = seq.Attack2(subClaimsDep6[:], 0) + subClaimsDep8 := []common.Hash{{0x81}, {0x82}, {0x83}} + seq.Attack2(subClaimsDep8[:], depth8Branch) + + game = gameBuilder.Game + ref = game.Claims()[4] + pos := types.NewPositionFromGIndex(big.NewInt(ref.ToGIndex().Int64() + stepBranch)) + traceRootDepth := game.TraceRootDepth() + relativePos, err := pos.RelativeToAncestorAtDepth(traceRootDepth) + require.NoError(t, err) + traceDepth := game.MaxDepth() - game.TraceRootDepth() + postTraceIdx = relativePos.TraceIndex(traceDepth) + preTraceIdx = new(big.Int).Sub(postTraceIdx, big.NewInt(1)) + + expectPreDA = types.DAItem{ + DaType: types.CallDataType, + DataHash: subClaimsDep8[0], + Proof: append(subClaimsDep8[1][:], subClaimsDep8[2][:]...), + } + + expectPostDA = types.DAItem{ + DaType: types.CallDataType, + DataHash: subClaimsDep8[1], + Proof: append(subClaimsDep8[0][:], subClaimsDep8[2][:]...), + } + return +} + +func attackMaxBranch(t *testing.T, ctx context.Context, gameBuilder *test.GameBuilder, provider *test.AlphabetWithProofProvider) (game types.Game, ref types.Claim, preTraceIdx *big.Int, postTraceIdx *big.Int, expectPreDA types.DAItem, expectPostDA types.DAItem) { + depth8Branch := uint64(1) + stepBranch := int64(3) + // output roots + seq := gameBuilder.Seq() + subClaimsDep2 := []common.Hash{{0x01}, {0x02}, {0x03}} // claim at splitDepth + seq = seq.Attack2(subClaimsDep2[:], 0) + // traces + subClaimsDep4 := []common.Hash{{0x04}} + seq = seq.Attack2(subClaimsDep4[:], 0) + subClaimsDep6 := []common.Hash{{0x61}, {0x62}, {0x63}} + seq = seq.Attack2(subClaimsDep6[:], 0) + subClaimsDep8 := []common.Hash{{0x81}, {0x82}, {0x83}} + seq.Attack2(subClaimsDep8[:], depth8Branch) + + game = gameBuilder.Game + ref = game.Claims()[4] + pos := types.NewPositionFromGIndex(big.NewInt(ref.ToGIndex().Int64() + stepBranch)) + traceRootDepth := game.TraceRootDepth() + relativePos, err := pos.RelativeToAncestorAtDepth(traceRootDepth) + require.NoError(t, err) + traceDepth := game.MaxDepth() - game.TraceRootDepth() + postTraceIdx = relativePos.TraceIndex(traceDepth) + preTraceIdx = new(big.Int).Sub(postTraceIdx, big.NewInt(1)) + + expectPreDA = types.DAItem{ + DaType: types.CallDataType, + DataHash: subClaimsDep8[2], + Proof: crypto.Keccak256(subClaimsDep8[0][:], subClaimsDep8[1][:]), + } + + expectPostDA = types.DAItem{ + DaType: types.CallDataType, + DataHash: subClaimsDep6[1], + Proof: append(subClaimsDep6[0][:], subClaimsDep6[2][:]...), + } + return +} +func attackRightMost(t *testing.T, ctx context.Context, gameBuilder *test.GameBuilder, provider *test.AlphabetWithProofProvider) (game types.Game, ref types.Claim, preTraceIdx *big.Int, postTraceIdx *big.Int, expectPreDA types.DAItem, expectPostDA types.DAItem) { + depth8Branch := uint64(3) + stepBranch := int64(3) + // output roots + seq := gameBuilder.Seq() + subClaimsDep2 := []common.Hash{{0x01}, {0x02}, {0x03}} // claim at splitDepth + seq = seq.Attack2(subClaimsDep2[:], 0) + // traces + subClaimsDep4 := []common.Hash{{0x04}} + seq = seq.Attack2(subClaimsDep4[:], 0) + subClaimsDep6 := []common.Hash{{0x61}, {0x62}, {0x63}} + seq = seq.Attack2(subClaimsDep6[:], 0) + subClaimsDep8 := []common.Hash{{0x81}, {0x82}, {0x83}} + seq.Attack2(subClaimsDep8[:], depth8Branch) + + game = gameBuilder.Game + ref = game.Claims()[4] + pos := types.NewPositionFromGIndex(big.NewInt(ref.ToGIndex().Int64() + stepBranch)) + traceRootDepth := game.TraceRootDepth() + relativePos, err := pos.RelativeToAncestorAtDepth(traceRootDepth) + require.NoError(t, err) + traceDepth := game.MaxDepth() - game.TraceRootDepth() + postTraceIdx = relativePos.TraceIndex(traceDepth) + preTraceIdx = new(big.Int).Sub(postTraceIdx, big.NewInt(1)) + + expectPreDA = types.DAItem{ + DaType: types.CallDataType, + DataHash: subClaimsDep8[2], + Proof: crypto.Keccak256(subClaimsDep8[0][:], subClaimsDep8[1][:]), + } + + splitLeaf := game.Claims()[2] + expectPostDA = types.DAItem{ + DaType: types.CallDataType, + DataHash: splitLeaf.Value, + Proof: []byte{}, + } + return +} diff --git a/op-challenger2/game/fault/trace/alphabet/provider.go b/op-challenger2/game/fault/trace/alphabet/provider.go index 0257c125023bf..bd8b9cb0231be 100644 --- a/op-challenger2/game/fault/trace/alphabet/provider.go +++ b/op-challenger2/game/fault/trace/alphabet/provider.go @@ -60,6 +60,10 @@ func (ap *AlphabetTraceProvider) GetStepData(ctx context.Context, pos types.Posi return BuildAlphabetPreimage(newTraceIndex, newClaim), []byte{}, preimageData, nil } +func (p *AlphabetTraceProvider) GetStepData2(ctx context.Context, pos types.Position) (prestate []byte, proofData []byte, preimageData *types.PreimageOracleData, err error) { + return nil, nil, nil, fmt.Errorf("alphabet GetStepData2 is not supported, use GetStepData instead") +} + // Get returns the claim value at the given index in the trace. func (ap *AlphabetTraceProvider) Get(ctx context.Context, i types.Position) (common.Hash, error) { if i.Depth() > ap.depth { @@ -79,6 +83,10 @@ func (ap *AlphabetTraceProvider) GetL2BlockNumberChallenge(_ context.Context) (* return nil, types.ErrL2BlockNumberValid } +func (ap *AlphabetTraceProvider) GetLocalData(_ context.Context) (types.DAData, error) { + return types.DAData{}, nil +} + // BuildAlphabetPreimage constructs the claim bytes for the index and claim. func BuildAlphabetPreimage(traceIndex *big.Int, claim *big.Int) []byte { return append(traceIndex.FillBytes(make([]byte, 32)), claim.FillBytes(make([]byte, 32))...) diff --git a/op-challenger2/game/fault/trace/asterisc/provider.go b/op-challenger2/game/fault/trace/asterisc/provider.go index 2fa77821b2d9a..8a84b3d039d28 100644 --- a/op-challenger2/game/fault/trace/asterisc/provider.go +++ b/op-challenger2/game/fault/trace/asterisc/provider.go @@ -43,6 +43,8 @@ type AsteriscTraceProvider struct { lastStep uint64 } +var _ types.TraceProvider = (*AsteriscTraceProvider)(nil) + func NewTraceProvider(logger log.Logger, m AsteriscMetricer, cfg *config.Config, prestateProvider types.PrestateProvider, asteriscPrestate string, localInputs utils.LocalGameInputs, dir string, gameDepth types.Depth) *AsteriscTraceProvider { return &AsteriscTraceProvider{ logger: logger, @@ -96,10 +98,18 @@ func (p *AsteriscTraceProvider) GetStepData(ctx context.Context, pos types.Posit return value, data, oracleData, nil } +func (p *AsteriscTraceProvider) GetStepData2(ctx context.Context, pos types.Position) (prestate []byte, proofData []byte, preimageData *types.PreimageOracleData, err error) { + return nil, nil, nil, fmt.Errorf("asterisc GetStepData2 is not supported, use GetStepData instead") +} + func (p *AsteriscTraceProvider) GetL2BlockNumberChallenge(_ context.Context) (*types.InvalidL2BlockNumberChallenge, error) { return nil, types.ErrL2BlockNumberValid } +func (ap *AsteriscTraceProvider) GetLocalData(_ context.Context) (types.DAData, error) { + return types.DAData{}, nil +} + // loadProof will attempt to load or generate the proof data at the specified index // If the requested index is beyond the end of the actual trace it is extended with no-op instructions. func (p *AsteriscTraceProvider) loadProof(ctx context.Context, i uint64) (*utils.ProofData, error) { diff --git a/op-challenger2/game/fault/trace/cannon/provider.go b/op-challenger2/game/fault/trace/cannon/provider.go index 8de4510196ee9..ced63185119dc 100644 --- a/op-challenger2/game/fault/trace/cannon/provider.go +++ b/op-challenger2/game/fault/trace/cannon/provider.go @@ -41,6 +41,8 @@ type CannonTraceProvider struct { lastStep uint64 } +var _ types.TraceProvider = (*CannonTraceProvider)(nil) + func NewTraceProvider(logger log.Logger, m CannonMetricer, cfg *config.Config, prestateProvider types.PrestateProvider, prestate string, localInputs utils.LocalGameInputs, dir string, gameDepth types.Depth) *CannonTraceProvider { return &CannonTraceProvider{ logger: logger, @@ -94,10 +96,18 @@ func (p *CannonTraceProvider) GetStepData(ctx context.Context, pos types.Positio return value, data, oracleData, nil } +func (p *CannonTraceProvider) GetStepData2(ctx context.Context, pos types.Position) (prestate []byte, proofData []byte, preimageData *types.PreimageOracleData, err error) { + return nil, nil, nil, fmt.Errorf("cannon GetStepData2 is not supported, use GetStepData instead") +} + func (p *CannonTraceProvider) GetL2BlockNumberChallenge(_ context.Context) (*types.InvalidL2BlockNumberChallenge, error) { return nil, types.ErrL2BlockNumberValid } +func (ap *CannonTraceProvider) GetLocalData(_ context.Context) (types.DAData, error) { + return types.DAData{}, nil +} + // loadProof will attempt to load or generate the proof data at the specified index // If the requested index is beyond the end of the actual trace it is extended with no-op instructions. func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*utils.ProofData, error) { diff --git a/op-challenger2/game/fault/trace/outputs/provider.go b/op-challenger2/game/fault/trace/outputs/provider.go index a65d19d23a7d7..1c8df23890d70 100644 --- a/op-challenger2/game/fault/trace/outputs/provider.go +++ b/op-challenger2/game/fault/trace/outputs/provider.go @@ -14,8 +14,10 @@ import ( ) var ( - ErrGetStepData = errors.New("GetStepData not supported") - ErrIndexTooBig = errors.New("trace index is greater than max uint64") + ErrGetStepData = errors.New("GetStepData not supported") + ErrGetStepData2 = errors.New("GetStepData2 not supported") + ErrGetLocalData = errors.New("GetLocalData not supported") + ErrIndexTooBig = errors.New("trace index is greater than max uint64") ) var _ types.TraceProvider = (*OutputTraceProvider)(nil) @@ -98,6 +100,14 @@ func (o *OutputTraceProvider) GetStepData(_ context.Context, _ types.Position) ( return nil, nil, nil, ErrGetStepData } +func (o *OutputTraceProvider) GetStepData2(_ context.Context, _ types.Position) (prestate []byte, proofData []byte, preimageData *types.PreimageOracleData, err error) { + return nil, nil, nil, ErrGetStepData2 +} + +func (o *OutputTraceProvider) GetLocalData(_ context.Context) (localDA types.DAData, err error) { + return types.DAData{}, ErrGetLocalData +} + func (o *OutputTraceProvider) GetL2BlockNumberChallenge(ctx context.Context) (*types.InvalidL2BlockNumberChallenge, error) { outputBlock, err := o.HonestBlockNumber(ctx, types.RootPosition) if err != nil { diff --git a/op-challenger2/game/fault/trace/split/split.go b/op-challenger2/game/fault/trace/split/split.go index a0ca97337babf..a662f47de2cab 100644 --- a/op-challenger2/game/fault/trace/split/split.go +++ b/op-challenger2/game/fault/trace/split/split.go @@ -7,6 +7,7 @@ import ( "math/big" "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/trace" + "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/trace/utils" "github.com/ethereum-optimism/optimism/op-challenger2/game/fault/types" ) @@ -17,6 +18,7 @@ var ( type ProviderCreator func(ctx context.Context, depth types.Depth, pre types.Claim, post types.Claim) (types.TraceProvider, error) func NewSplitProviderSelector(topProvider types.TraceProvider, topDepth types.Depth, bottomProviderCreator ProviderCreator) trace.ProviderSelector { + // pos is next move position of any claim whose ancestor is ref return func(ctx context.Context, game types.Game, ref types.Claim, pos types.Position) (types.TraceProvider, error) { if pos.Depth() <= topDepth { return topProvider, nil @@ -25,68 +27,91 @@ func NewSplitProviderSelector(topProvider types.TraceProvider, topDepth types.De return nil, fmt.Errorf("%w, claim depth: %v, depth required: %v", errRefClaimNotDeepEnough, ref.Position.Depth(), topDepth) } - // Find the ancestor claim at the leaf level for the top game. - topLeaf, err := findAncestorAtDepth(game, ref, topDepth) + // Find the ancestor claim at the (splitDepth + nbits) level for the top game. + topLeaf, err := trace.FindAncestorAtDepth(game, ref, topDepth) if err != nil { return nil, err } - var pre, post types.Claim - // If pos is to the right of the leaf from the top game, we must be defending that output root - // otherwise, we're attacking it. - if pos.TraceIndex(pos.Depth()).Cmp(topLeaf.TraceIndex(pos.Depth())) > 0 { - // Defending the top leaf claim, so use it as the pre-claim and find the post - pre = topLeaf - postTraceIdx := new(big.Int).Add(pre.TraceIndex(topDepth), big.NewInt(1)) - post, err = findAncestorWithTraceIndex(game, topLeaf, topDepth, postTraceIdx) - if err != nil { - return nil, fmt.Errorf("failed to find post claim: %w", err) - } - } else { - // Attacking the top leaf claim, so use it as the post-claim and find the pre - post = topLeaf - postTraceIdx := post.TraceIndex(topDepth) - if postTraceIdx.Cmp(big.NewInt(0)) == 0 { - pre = types.Claim{} - } else { - preTraceIdx := new(big.Int).Sub(postTraceIdx, big.NewInt(1)) - pre, err = findAncestorWithTraceIndex(game, topLeaf, topDepth, preTraceIdx) - if err != nil { - return nil, fmt.Errorf("failed to find pre claim: %w", err) - } - } + ancestorPos := pos + for ancestorPos.Depth() > topLeaf.Depth() { + ancestorPos = ancestorPos.ParentN(game.NBits()) } + + attackBranch := ancestorPos.IndexAtDepth().Int64() % (int64(game.MaxAttackBranch()) + 1) + postTraceIdx := new(big.Int).Add(topLeaf.TraceIndex(topDepth), big.NewInt(attackBranch)) + preTraceIdx := new(big.Int).Sub(postTraceIdx, big.NewInt(1)) + + var outputRootDA = types.DAData{} + pre, preDA, err := findAncestorWithTraceIndex2(game, topLeaf, topDepth, preTraceIdx) + if err != nil { + return nil, fmt.Errorf("failed to find pre claim: %w", err) + } + post, postDA, err := findAncestorWithTraceIndex2(game, topLeaf, topDepth, postTraceIdx) + if err != nil { + return nil, fmt.Errorf("failed to find post claim: %w", err) + } + + outputRootDA.PreDA = preDA + outputRootDA.PostDA = postDA + // The top game runs from depth 0 to split depth *inclusive*. // The - 1 here accounts for the fact that the split depth is included in the top game. - bottomDepth := game.MaxDepth() - topDepth - 1 + bottomDepth := game.MaxDepth() - topDepth - types.Depth(game.NBits()) provider, err := bottomProviderCreator(ctx, bottomDepth, pre, post) if err != nil { return nil, err } + // Translate such that the root of the bottom game is the level below the top game leaf - return trace.Translate(provider, topDepth+1), nil + return trace.Translate(provider, topDepth+types.Depth(game.NBits()), outputRootDA), nil } } -func findAncestorAtDepth(game types.Game, claim types.Claim, depth types.Depth) (types.Claim, error) { - for claim.Depth() > depth { - parent, err := game.GetParent(claim) - if err != nil { - return types.Claim{}, fmt.Errorf("failed to find ancestor at depth %v: %w", depth, err) - } - claim = parent +// Get the traceIdx's accestor claim and pre/post outputroot proof for MSFDG. traceIdx must be ref's traceIdx ±1. +func findAncestorWithTraceIndex2(game types.Game, ref types.Claim, depth types.Depth, traceIdx *big.Int) (types.Claim, types.DAItem, error) { + // If traceIdx equals -1, the absolute prestate is returned. + if traceIdx.Cmp(big.NewInt(-1)) == 0 { + return types.Claim{}, types.DAItem{}, nil } - return claim, nil -} -func findAncestorWithTraceIndex(game types.Game, ref types.Claim, depth types.Depth, traceIdx *big.Int) (types.Claim, error) { - candidate := ref - for candidate.TraceIndex(depth).Cmp(traceIdx) != 0 { - parent, err := game.GetParent(candidate) + // If traceIdx is the right most branch, the root Claim is returned. + if new(big.Int).Add(traceIdx, big.NewInt(1)).Cmp(new(big.Int).Lsh(big.NewInt(1), uint(depth))) == 0 { + return game.RootClaim(), types.DAItem{}, nil + } + + ancestor := ref + minTraceIndex := ancestor.TraceIndex(depth) + offset := new(big.Int).Lsh(big.NewInt(int64(game.MaxAttackBranch())-1), uint(depth-ancestor.Depth())) + maxTraceIndex := new(big.Int).Add(minTraceIndex, offset) + // If Ancestor's left most branch traceIdx <= traceId <= Ancestor's rightmost branch traceIdx, we do nothing; + // otherwise, we should trace the correct ancestor + for minTraceIndex.Cmp(traceIdx) == 1 || maxTraceIndex.Cmp(traceIdx) == -1 { + parent, err := game.GetParent(ancestor) if err != nil { - return types.Claim{}, fmt.Errorf("failed to get parent of claim %v: %w", candidate.ContractIndex, err) + return types.Claim{}, types.DAItem{}, fmt.Errorf("failed to get ancestor of claim %v: %w", ancestor.ContractIndex, err) } - candidate = parent + + ancestor = parent + minTraceIndex = ancestor.TraceIndex(depth) + offset = new(big.Int).Lsh(big.NewInt(int64(game.MaxAttackBranch())-1), uint(depth-ancestor.Depth())) + maxTraceIndex = new(big.Int).Add(minTraceIndex, offset) } - return candidate, nil + + branch := new(big.Int).Div(new(big.Int).Sub(traceIdx, minTraceIndex), new(big.Int).Lsh(big.NewInt(1), uint(depth-ancestor.Depth()))).Int64() + if ancestor.SubValues == nil { + return types.Claim{}, types.DAItem{}, fmt.Errorf("ancestor's subvalues are nil %v", ancestor.ContractIndex) + } + subValues := *ancestor.SubValues + claim := ancestor + claim.Value = subValues[branch] + merkleProof := utils.GenerateProofForSubValues(subValues, uint32(branch)) + + outputRootDAItem := types.DAItem{ + DaType: types.CallDataType, + DataHash: claim.Value, + Proof: merkleProof, + } + + return claim, outputRootDAItem, nil } diff --git a/op-challenger2/game/fault/trace/split/split_test.go b/op-challenger2/game/fault/trace/split/split_test.go index 4a0c5969fb5f8..1559cfd016e96 100644 --- a/op-challenger2/game/fault/trace/split/split_test.go +++ b/op-challenger2/game/fault/trace/split/split_test.go @@ -10,12 +10,15 @@ import ( "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/types" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" "github.com/stretchr/testify/require" ) const ( gameDepth = 7 splitDepth = 3 + nbits = 1 ) func TestUseTopProvider(t *testing.T) { @@ -147,12 +150,12 @@ func TestBottomProviderAttackingTopLeaf(t *testing.T) { // If the ref is the leaf of the top claim, ensure we respect whether the test is setup // to attack or defend the top leaf claim. if ref.Depth() != splitDepth || !pos.RightOf(ref.Position) { - gameBuilder.SeqFrom(ref).Attack() + gameBuilder.SeqFrom(ref).Attack2(nil, 0) attackRef := latestClaim(gameBuilder) testDescendantClaims(attackRef, attackRef.Position) } if ref.Depth() != splitDepth || pos.RightOf(ref.Position) { - gameBuilder.SeqFrom(ref).Defend() + gameBuilder.SeqFrom(ref).Attack2(nil, 1) defendRef := latestClaim(gameBuilder) testDescendantClaims(defendRef, defendRef.Position) } @@ -164,10 +167,10 @@ func TestBottomProviderAttackingTopLeaf(t *testing.T) { func attackTopLeafGIndex8(_ *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim) { // Generate claims down to the top provider's leaf - seq := gameBuilder.Seq() // gindex 1, trace 7 - seq = seq.Attack() // gindex 2, trace 3 - seq = seq.Attack() // gindex 4, trace 1 - seq.Attack() // gindex 8, trace 0 + seq := gameBuilder.Seq() // gindex 1, trace 7 + seq = seq.Attack2(nil, 0) // gindex 2, trace 3 + seq = seq.Attack2(nil, 0) // gindex 4, trace 1 + seq.Attack2(nil, 0) // gindex 8, trace 0 expectPost = latestClaim(gameBuilder) // No pre-claim as the first output root is being challenged. @@ -180,11 +183,11 @@ func attackTopLeafGIndex8(_ *testing.T, gameBuilder *test.GameBuilder) (ref type func defendTopLeafGIndex8(_ *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim) { // Generate claims down to the top provider's leaf - seq := gameBuilder.Seq() // gindex 1, trace 7 - seq = seq.Attack() // gindex 2, trace 3 - seq = seq.Attack() // gindex 4, trace 1 + seq := gameBuilder.Seq() // gindex 1, trace 7 + seq = seq.Attack2(nil, 0) // gindex 2, trace 3 + seq = seq.Attack2(nil, 0) // gindex 4, trace 1 expectPost = latestClaim(gameBuilder) - seq.Attack() // gindex 8, trace 0 + seq.Attack2(nil, 0) // gindex 8, trace 0 expectPre = latestClaim(gameBuilder) ref = latestClaim(gameBuilder) @@ -193,11 +196,11 @@ func defendTopLeafGIndex8(_ *testing.T, gameBuilder *test.GameBuilder) (ref type } func attackTopLeafGIndex10(_ *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim) { - seq := gameBuilder.Seq() // gindex 1, trace 7 - seq = seq.Attack() // gindex 2, trace 3 - seq = seq.Attack() // gindex 4, trace 1 + seq := gameBuilder.Seq() // gindex 1, trace 7 + seq = seq.Attack2(nil, 0) // gindex 2, trace 3 + seq = seq.Attack2(nil, 0) // gindex 4, trace 1 expectPre = latestClaim(gameBuilder) - seq.Defend() // gindex 10, trace 2 + seq.Attack2(nil, 1) // gindex 10, trace 2 expectPost = latestClaim(gameBuilder) ref = latestClaim(gameBuilder) @@ -206,11 +209,11 @@ func attackTopLeafGIndex10(_ *testing.T, gameBuilder *test.GameBuilder) (ref typ } func defendTopLeafGIndex10(_ *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim) { - seq := gameBuilder.Seq() // gindex 1, trace 7 - seq = seq.Attack() // gindex 2, trace 3 + seq := gameBuilder.Seq() // gindex 1, trace 7 + seq = seq.Attack2(nil, 0) // gindex 2, trace 3 expectPost = latestClaim(gameBuilder) - seq = seq.Attack() // gindex 4, trace 1 - seq.Defend() // gindex 10, trace 2 + seq = seq.Attack2(nil, 0) // gindex 4, trace 1 + seq.Attack2(nil, 1) // gindex 10, trace 2 expectPre = latestClaim(gameBuilder) ref = latestClaim(gameBuilder) @@ -219,11 +222,11 @@ func defendTopLeafGIndex10(_ *testing.T, gameBuilder *test.GameBuilder) (ref typ } func attackTopLeafGIndex12(_ *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim) { - seq := gameBuilder.Seq() // gindex 1, trace 7 - seq = seq.Attack() // gindex 2, trace 3 + seq := gameBuilder.Seq() // gindex 1, trace 7 + seq = seq.Attack2(nil, 0) // gindex 2, trace 3 expectPre = latestClaim(gameBuilder) - seq = seq.Defend() // gindex 6, trace 5 - seq.Attack() // gindex 12, trace 4 + seq = seq.Attack2(nil, 1) // gindex 6, trace 5 + seq.Attack2(nil, 0) // gindex 12, trace 4 expectPost = latestClaim(gameBuilder) ref = latestClaim(gameBuilder) @@ -232,11 +235,11 @@ func attackTopLeafGIndex12(_ *testing.T, gameBuilder *test.GameBuilder) (ref typ } func defendTopLeafGIndex12(_ *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim) { - seq := gameBuilder.Seq() // gindex 1, trace 7 - seq = seq.Attack() // gindex 2, trace 3 - seq = seq.Defend() // gindex 6, trace 5 + seq := gameBuilder.Seq() // gindex 1, trace 7 + seq = seq.Attack2(nil, 0) // gindex 2, trace 3 + seq = seq.Attack2(nil, 1) // gindex 6, trace 5 expectPost = latestClaim(gameBuilder) - seq.Attack() // gindex 12, trace 4 + seq.Attack2(nil, 0) // gindex 12, trace 4 expectPre = latestClaim(gameBuilder) ref = latestClaim(gameBuilder) @@ -245,11 +248,11 @@ func defendTopLeafGIndex12(_ *testing.T, gameBuilder *test.GameBuilder) (ref typ } func attackTopLeafGIndex14(_ *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim) { - seq := gameBuilder.Seq() // gindex 1, trace 7 - seq = seq.Attack() // gindex 2, trace 3 - seq = seq.Defend() // gindex 6, trace 5 + seq := gameBuilder.Seq() // gindex 1, trace 7 + seq = seq.Attack2(nil, 0) // gindex 2, trace 3 + seq = seq.Attack2(nil, 1) // gindex 6, trace 5 expectPre = latestClaim(gameBuilder) - seq.Defend() // gindex 14, trace 6 + seq.Attack2(nil, 1) // gindex 14, trace 6 expectPost = latestClaim(gameBuilder) ref = latestClaim(gameBuilder) @@ -260,9 +263,9 @@ func attackTopLeafGIndex14(_ *testing.T, gameBuilder *test.GameBuilder) (ref typ func defendTopLeafGIndex14(_ *testing.T, gameBuilder *test.GameBuilder) (ref types.Claim, pos types.Position, expectPre types.Claim, expectPost types.Claim) { seq := gameBuilder.Seq() // gindex 1, trace 7 expectPost = latestClaim(gameBuilder) - seq = seq.Attack() // gindex 2, trace 3 - seq = seq.Defend() // gindex 6, trace 5 - seq.Defend() // gindex 14, trace 6 + seq = seq.Attack2(nil, 0) // gindex 2, trace 3 + seq = seq.Attack2(nil, 1) // gindex 6, trace 5 + seq.Attack2(nil, 1) // gindex 14, trace 6 expectPre = latestClaim(gameBuilder) ref = latestClaim(gameBuilder) @@ -277,7 +280,7 @@ func latestClaim(gameBuilder *test.GameBuilder) types.Claim { func createClaimsToDepth(gameBuilder *test.GameBuilder, depth int) { seq := gameBuilder.Seq() for i := 0; i < depth; i++ { - seq = seq.Attack() + seq = seq.Attack2(nil, 0) } } @@ -313,7 +316,7 @@ func setupAlphabetSplitSelector(t *testing.T) (*alphabet.AlphabetTraceProvider, } selector := NewSplitProviderSelector(top, splitDepth, bottomCreator) - claimBuilder := test.NewAlphabetClaimBuilder(t, big.NewInt(0), gameDepth) + claimBuilder := test.NewAlphabetClaimBuilder2(t, big.NewInt(0), gameDepth, nbits, splitDepth) gameBuilder := claimBuilder.GameBuilder() return top, selector, gameBuilder } @@ -323,3 +326,285 @@ type bottomTraceProvider struct { post types.Claim *alphabet.AlphabetTraceProvider } + +func TestFindAncestorProofAtDepth2(t *testing.T) { + var nbits2 uint64 = 2 + maxGameDepth := types.Depth(8) + splitDepth2 := types.Depth(4) + + tests := []struct { + name string + setup func(t *testing.T, gameBuilder *test.GameBuilder) (game types.Game, topLeaf types.Claim, splitDepth types.Depth, preTraceIdx *big.Int, postTraceIdx *big.Int, expectPre types.Claim, expectedPreDA types.DAItem, expectPost types.Claim, expectedPostDA types.DAItem) + }{ + // attack every claims's first branch (index at 0) + {"attackLeftMost", attackLeftMost}, + // attack one claim's branch between [1,maxBranch-1] and leaf claims's first branch + {"attackFirstBranch", attackFirstBranch}, + // attack one claim's branch between [1,maxBranch-1] and leaf claim's branch between [1,maxBranch-1] + {"attackMidBranch", attackMidBranch}, + // attack one claim's branch between [1,maxBranch-1] and leaf claim's last branch (index at maxBranch) + {"attackMaxBranch", attackMaxBranch}, + // attack every claims's last branch + {"attackRightMost", attackRightMost}, + } + + for _, tCase := range tests { + t.Run(tCase.name, func(t *testing.T) { + claimBuilder := test.NewAlphabetClaimBuilder2(t, big.NewInt(0), maxGameDepth, nbits2, splitDepth2) + game, topLeaf, splitDepth2, preTraceIdx, postTraceIdx, expectPre, expectPreDA, expectPost, expectPostDA := tCase.setup(t, claimBuilder.GameBuilder()) + pre, preDA, err := findAncestorWithTraceIndex2(game, topLeaf, splitDepth2, preTraceIdx) + require.NoError(t, err) + require.Equal(t, expectPre, pre) + require.Equal(t, expectPreDA, preDA) + + post, postDA, err := findAncestorWithTraceIndex2(game, topLeaf, splitDepth2, postTraceIdx) + require.NoError(t, err) + require.Equal(t, expectPost, post) + require.Equal(t, expectPostDA, postDA) + }) + } +} + +func attackLeftMost(t *testing.T, gameBuilder *test.GameBuilder) (game types.Game, topLeaf types.Claim, splitDepth2 types.Depth, preTraceIdx *big.Int, postTraceIdx *big.Int, expectPre types.Claim, expectPreDA types.DAItem, expectPost types.Claim, expectPostDA types.DAItem) { + splitDepth2 = gameBuilder.Game.SplitDepth() + nbits2 := types.Depth(gameBuilder.Game.NBits()) + + expectedTopLeafBranch := uint64(0) + seq := gameBuilder.Seq() + subClaimsDep2 := []common.Hash{{0x01}, {0x02}, {0x03}} + seq = seq.Attack2(subClaimsDep2[:], 0) + + subClaimsDep4 := []common.Hash{{0x04}, {0x05}, {0x06}} // claim at splitDepth + seq = seq.Attack2(subClaimsDep4[:], expectedTopLeafBranch) + depth4Claim := seq.LastClaim() + + subClaimsDep6 := []common.Hash{{0x07}} // claim at splitDepth + 1 + seq = seq.Attack2(subClaimsDep6[:], 0) + + subClaimsDep8 := []common.Hash{{0x11, 0x12, 0x13}} + seq.Attack2(subClaimsDep8[:], 0) + depth8Claim := seq.LastClaim() + + game = gameBuilder.Game + splitLeaf, err := trace.FindAncestorAtDepth(game, depth8Claim, splitDepth2+nbits2) + require.NoError(t, err) + // Find the ancestor claim at the leaf level splitDepth for the top game. + topLeaf, err = game.GetParent(splitLeaf) + require.NoError(t, err) + require.Equal(t, expectedTopLeafBranch, topLeaf.AttackBranch) + + attackBranch := int64(splitLeaf.AttackBranch) + postTraceIdx = new(big.Int).Add(depth4Claim.TraceIndex(splitDepth2), big.NewInt(attackBranch)) + preTraceIdx = new(big.Int).Sub(postTraceIdx, big.NewInt(1)) + expectPre = types.Claim{} + expectPreDA = types.DAItem{} + + expectPost = depth4Claim + expectPost.Value = subClaimsDep4[0] + expectPostDA = types.DAItem{ + DaType: types.CallDataType, + DataHash: subClaimsDep4[0], + Proof: append(subClaimsDep4[1][:], subClaimsDep4[2][:]...), + } + return +} + +func attackFirstBranch(t *testing.T, gameBuilder *test.GameBuilder) (game types.Game, topLeaf types.Claim, splitDepth2 types.Depth, preTraceIdx *big.Int, postTraceIdx *big.Int, expectPre types.Claim, expectPreDA types.DAItem, expectPost types.Claim, expectPostDA types.DAItem) { + splitDepth2 = gameBuilder.Game.SplitDepth() + nbits2 := types.Depth(gameBuilder.Game.NBits()) + + expectedTopLeafBranch := uint64(1) + seq := gameBuilder.Seq() + subClaimsDep2 := []common.Hash{{0x01}, {0x02}, {0x03}} + seq = seq.Attack2(subClaimsDep2[:], 0) + depth2Claim := seq.LastClaim() + + subClaimsDep4 := []common.Hash{{0x04}, {0x05}, {0x06}} // claim at splitDepth + seq = seq.Attack2(subClaimsDep4[:], expectedTopLeafBranch) + depth4Claim := seq.LastClaim() + + subClaimsDep6 := []common.Hash{{0x07}} // claim at splitDepth + 1 + seq = seq.Attack2(subClaimsDep6[:], 0) + + subClaimsDep8 := []common.Hash{{0x11, 0x12, 0x13}} + seq.Attack2(subClaimsDep8[:], 0) + depth8Claim := seq.LastClaim() + + game = gameBuilder.Game + splitLeaf, err := trace.FindAncestorAtDepth(game, depth8Claim, splitDepth2+nbits2) + require.NoError(t, err) + // Find the ancestor claim at the leaf level splitDepth for the top game. + topLeaf, err = game.GetParent(splitLeaf) + require.NoError(t, err) + require.Equal(t, expectedTopLeafBranch, topLeaf.AttackBranch) + + attackBranch := int64(splitLeaf.AttackBranch) + postTraceIdx = new(big.Int).Add(depth4Claim.TraceIndex(splitDepth2), big.NewInt(attackBranch)) + preTraceIdx = new(big.Int).Sub(postTraceIdx, big.NewInt(1)) + expectPre = depth2Claim + expectPre.Value = subClaimsDep2[0] + expectPreDA = types.DAItem{ + DaType: types.CallDataType, + DataHash: subClaimsDep2[0], + Proof: append(subClaimsDep2[1][:], subClaimsDep2[2][:]...), + } + + expectPost = depth4Claim + expectPost.Value = subClaimsDep4[0] + expectPostDA = types.DAItem{ + DaType: types.CallDataType, + DataHash: subClaimsDep4[0], + Proof: append(subClaimsDep4[1][:], subClaimsDep4[2][:]...), + } + + return +} + +func attackMidBranch(t *testing.T, gameBuilder *test.GameBuilder) (game types.Game, topLeaf types.Claim, splitDepth2 types.Depth, preTraceIdx *big.Int, postTraceIdx *big.Int, expectPre types.Claim, expectPreDA types.DAItem, expectPost types.Claim, expectPostDA types.DAItem) { + splitDepth2 = gameBuilder.Game.SplitDepth() + nbits2 := types.Depth(gameBuilder.Game.NBits()) + expectedTopLeafBranch := uint64(1) + expectedSplitBranch := uint64(1) + seq := gameBuilder.Seq() + subClaimsDep2 := []common.Hash{{0x01}, {0x02}, {0x03}} + seq = seq.Attack2(subClaimsDep2[:], 0) + + subClaimsDep4 := []common.Hash{{0x04}, {0x05}, {0x06}} // claim at splitDepth + seq = seq.Attack2(subClaimsDep4[:], expectedTopLeafBranch) + depth4Claim := gameBuilder.Game.Claims()[2] + + subClaimsDep6 := []common.Hash{{0x07}} // claim at splitDepth + 1 + seq = seq.Attack2(subClaimsDep6[:], expectedSplitBranch) + + subClaimsDep8 := []common.Hash{{0x11, 0x12, 0x13}} + seq.Attack2(subClaimsDep8[:], 0) + depth8Claim := gameBuilder.Game.Claims()[4] + + game = gameBuilder.Game + splitLeaf, err := trace.FindAncestorAtDepth(game, depth8Claim, splitDepth2+nbits2) + require.NoError(t, err) + // Find the ancestor claim at the leaf level splitDepth for the top game. + topLeaf, err = game.GetParent(splitLeaf) + require.NoError(t, err) + require.Equal(t, expectedTopLeafBranch, topLeaf.AttackBranch) + require.Equal(t, expectedSplitBranch, splitLeaf.AttackBranch) + + attackBranch := int64(splitLeaf.AttackBranch) + postTraceIdx = new(big.Int).Add(depth4Claim.TraceIndex(splitDepth2), big.NewInt(attackBranch)) + preTraceIdx = new(big.Int).Sub(postTraceIdx, big.NewInt(1)) + expectPre = depth4Claim + expectPre.Value = subClaimsDep4[0] + expectPreDA = types.DAItem{ + DaType: types.CallDataType, + DataHash: subClaimsDep4[0], + Proof: append(subClaimsDep4[1][:], subClaimsDep4[2][:]...), + } + + expectPost = depth4Claim + expectPost.Value = subClaimsDep4[1] + expectPostDA = types.DAItem{ + DaType: types.CallDataType, + DataHash: subClaimsDep4[1], + Proof: append(subClaimsDep4[0][:], subClaimsDep4[2][:]...), + } + + return +} + +func attackMaxBranch(t *testing.T, gameBuilder *test.GameBuilder) (game types.Game, topLeaf types.Claim, splitDepth2 types.Depth, preTraceIdx *big.Int, postTraceIdx *big.Int, expectPre types.Claim, expectPreDA types.DAItem, expectPost types.Claim, expectPostDA types.DAItem) { + splitDepth2 = gameBuilder.Game.SplitDepth() + nbits2 := types.Depth(gameBuilder.Game.NBits()) + expectedTopLeafBranch := uint64(1) + expectedSplitBranch := uint64(3) + seq := gameBuilder.Seq() + subClaimsDep2 := []common.Hash{{0x01}, {0x02}, {0x03}} + seq = seq.Attack2(subClaimsDep2[:], 0) + depth2Claim := seq.LastClaim() + + subClaimsDep4 := []common.Hash{{0x04}, {0x05}, {0x06}} // claim at splitDepth + seq = seq.Attack2(subClaimsDep4[:], expectedTopLeafBranch) + depth4Claim := gameBuilder.Game.Claims()[2] + + subClaimsDep6 := []common.Hash{{0x07}} // claim at splitDepth + 1 + seq = seq.Attack2(subClaimsDep6[:], expectedSplitBranch) + + subClaimsDep8 := []common.Hash{{0x11, 0x12, 0x13}} + seq.Attack2(subClaimsDep8[:], 0) + depth8Claim := gameBuilder.Game.Claims()[4] + + game = gameBuilder.Game + splitLeaf, err := trace.FindAncestorAtDepth(game, depth8Claim, splitDepth2+nbits2) + require.NoError(t, err) + // Find the ancestor claim at the leaf level splitDepth for the top game. + topLeaf, err = game.GetParent(splitLeaf) + require.NoError(t, err) + require.Equal(t, expectedTopLeafBranch, topLeaf.AttackBranch) + require.Equal(t, expectedSplitBranch, splitLeaf.AttackBranch) + + attackBranch := int64(splitLeaf.AttackBranch) + postTraceIdx = new(big.Int).Add(depth4Claim.TraceIndex(splitDepth2), big.NewInt(attackBranch)) + preTraceIdx = new(big.Int).Sub(postTraceIdx, big.NewInt(1)) + expectPre = depth4Claim + expectPre.Value = subClaimsDep4[expectedSplitBranch-1] + expectPreDA = types.DAItem{ + DaType: types.CallDataType, + DataHash: subClaimsDep4[expectedSplitBranch-1], + Proof: crypto.Keccak256(subClaimsDep4[expectedSplitBranch-3][:], subClaimsDep4[expectedSplitBranch-2][:]), + } + + expectPost = depth2Claim + expectPost.Value = subClaimsDep2[expectedTopLeafBranch] + expectPostDA = types.DAItem{ + DaType: types.CallDataType, + DataHash: subClaimsDep2[expectedTopLeafBranch], + Proof: append(subClaimsDep2[expectedTopLeafBranch-1][:], subClaimsDep2[expectedTopLeafBranch+1][:]...), + } + + return +} + +func attackRightMost(t *testing.T, gameBuilder *test.GameBuilder) (game types.Game, topLeaf types.Claim, splitDepth2 types.Depth, preTraceIdx *big.Int, postTraceIdx *big.Int, expectPre types.Claim, expectPreDA types.DAItem, expectPost types.Claim, expectPostDA types.DAItem) { + splitDepth2 = gameBuilder.Game.SplitDepth() + nbits2 := types.Depth(gameBuilder.Game.NBits()) + expectedTopLeafBranch := uint64(3) + expectedSplitBranch := uint64(3) + + seq := gameBuilder.Seq() + subClaimsDep2 := []common.Hash{{0x01}, {0x02}, {0x03}} + seq = seq.Attack2(subClaimsDep2[:], 0) + + subClaimsDep4 := []common.Hash{{0x04}, {0x05}, {0x06}} // claim at splitDepth + seq = seq.Attack2(subClaimsDep4[:], expectedTopLeafBranch) + depth4Claim := gameBuilder.Game.Claims()[2] + + subClaimsDep6 := []common.Hash{{0x07}} // claim at splitDepth + 1 + seq = seq.Attack2(subClaimsDep6[:], expectedSplitBranch) + + subClaimsDep8 := []common.Hash{{0x11, 0x12, 0x13}} + seq.Attack2(subClaimsDep8[:], 0) + depth8Claim := gameBuilder.Game.Claims()[4] + + game = gameBuilder.Game + splitLeaf, err := trace.FindAncestorAtDepth(game, depth8Claim, splitDepth2+nbits2) + require.NoError(t, err) + // Find the ancestor claim at the leaf level splitDepth for the top game. + topLeaf, err = game.GetParent(splitLeaf) + require.NoError(t, err) + require.Equal(t, expectedTopLeafBranch, topLeaf.AttackBranch) + require.Equal(t, expectedSplitBranch, splitLeaf.AttackBranch) + + attackBranch := int64(splitLeaf.AttackBranch) + postTraceIdx = new(big.Int).Add(depth4Claim.TraceIndex(splitDepth2), big.NewInt(attackBranch)) + preTraceIdx = new(big.Int).Sub(postTraceIdx, big.NewInt(1)) + expectPre = depth4Claim + expectPre.Value = subClaimsDep4[expectedSplitBranch-1] + expectPreDA = types.DAItem{ + DaType: types.CallDataType, + DataHash: subClaimsDep4[expectedSplitBranch-1], + Proof: crypto.Keccak256(subClaimsDep4[expectedSplitBranch-3][:], subClaimsDep4[expectedSplitBranch-2][:]), + } + + expectPost = game.RootClaim() + expectPostDA = types.DAItem{} + return +} diff --git a/op-challenger2/game/fault/trace/translate.go b/op-challenger2/game/fault/trace/translate.go index 32d146a78b370..ed282e1899bf6 100644 --- a/op-challenger2/game/fault/trace/translate.go +++ b/op-challenger2/game/fault/trace/translate.go @@ -10,15 +10,17 @@ import ( type TranslatingProvider struct { rootDepth types.Depth provider types.TraceProvider + localData types.DAData } // Translate returns a new TraceProvider that translates any requested positions before passing them on to the // specified provider. // The translation is done such that the root node for provider is at rootDepth. -func Translate(provider types.TraceProvider, rootDepth types.Depth) types.TraceProvider { +func Translate(provider types.TraceProvider, rootDepth types.Depth, outputRootDA types.DAData) types.TraceProvider { return &TranslatingProvider{ rootDepth: rootDepth, provider: provider, + localData: outputRootDA, } } @@ -42,6 +44,14 @@ func (p *TranslatingProvider) GetStepData(ctx context.Context, pos types.Positio return p.provider.GetStepData(ctx, relativePos) } +func (p *TranslatingProvider) GetStepData2(ctx context.Context, pos types.Position) (prestate []byte, proofData []byte, preimageData *types.PreimageOracleData, err error) { + relativePos, err := pos.RelativeToAncestorAtDepth(p.rootDepth) + if err != nil { + return nil, nil, nil, err + } + return p.provider.GetStepData(ctx, relativePos) +} + func (p *TranslatingProvider) AbsolutePreStateCommitment(ctx context.Context) (hash common.Hash, err error) { return p.provider.AbsolutePreStateCommitment(ctx) } @@ -50,4 +60,8 @@ func (p *TranslatingProvider) GetL2BlockNumberChallenge(ctx context.Context) (*t return p.provider.GetL2BlockNumberChallenge(ctx) } +func (p *TranslatingProvider) GetLocalData(ctx context.Context) (types.DAData, error) { + return p.localData, nil +} + var _ types.TraceProvider = (*TranslatingProvider)(nil) diff --git a/op-challenger2/game/fault/trace/translate_test.go b/op-challenger2/game/fault/trace/translate_test.go index c161c6978c7ae..ece7e0595ccc8 100644 --- a/op-challenger2/game/fault/trace/translate_test.go +++ b/op-challenger2/game/fault/trace/translate_test.go @@ -12,7 +12,7 @@ import ( func TestTranslate(t *testing.T) { orig := alphabet.NewTraceProvider(big.NewInt(0), 4) - translated := Translate(orig, 3) + translated := Translate(orig, 3, types.DAData{}) // All nodes on the first translated layer, map to GIndex 1 for i := int64(8); i <= 15; i++ { requireSameValue(t, orig, 1, translated, i) @@ -51,7 +51,7 @@ func requireSameValue(t *testing.T, a types.TraceProvider, aGIdx int64, b types. func TestTranslate_AbsolutePreStateCommitment(t *testing.T) { orig := alphabet.NewTraceProvider(big.NewInt(0), 4) - translated := Translate(orig, 3) + translated := Translate(orig, 3, types.DAData{}) origValue, err := orig.AbsolutePreStateCommitment(context.Background()) require.NoError(t, err) translatedValue, err := translated.AbsolutePreStateCommitment(context.Background()) diff --git a/op-challenger2/game/fault/trace/utils/daproof.go b/op-challenger2/game/fault/trace/utils/daproof.go new file mode 100644 index 0000000000000..122ef1effe10d --- /dev/null +++ b/op-challenger2/game/fault/trace/utils/daproof.go @@ -0,0 +1,116 @@ +package utils + +import ( + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" +) + +// MerkleNode represents a node in the Merkle tree +type MerkleNode struct { + Left, Right *MerkleNode + Hash []byte +} + +// MerkleTree represents a non-2^n Merkle tree +type MerkleTree struct { + Root *MerkleNode + Leaves []*MerkleNode +} + +// NewMerkleNode creates a new Merkle node, hashing the left and right children if both are present +func NewMerkleNode(left, right *MerkleNode, data common.Hash) *MerkleNode { + node := &MerkleNode{} + if left == nil && right == nil { + // Leaf node + node.Hash = data[:] + } else { + // Internal node + var combined []byte + if right != nil { + combined = append(left.Hash[:], right.Hash[:]...) + } else { + combined = left.Hash[:] // Only one child, carry the hash up + } + node.Hash = crypto.Keccak256(combined) + } + node.Left = left + node.Right = right + return node +} + +// NewMerkleTree constructs a Merkle tree from given leaf data without padding +func NewMerkleTree(data []common.Hash) *MerkleTree { + var leaves []*MerkleNode + for _, datum := range data { + leaves = append(leaves, NewMerkleNode(nil, nil, datum)) + } + tree := &MerkleTree{Leaves: leaves} + tree.Root = buildTree(leaves) + return tree +} + +// buildTree recursively builds the Merkle tree without padding +func buildTree(nodes []*MerkleNode) *MerkleNode { + if len(nodes) == 1 { + return nodes[0] + } + + var nextLevel []*MerkleNode + for i := 0; i < len(nodes); i += 2 { + if i+1 < len(nodes) { + // Pair of nodes + parent := NewMerkleNode(nodes[i], nodes[i+1], common.Hash{}) + nextLevel = append(nextLevel, parent) + } else { + // Unpaired node, carry it up to the next level + nextLevel = append(nextLevel, nodes[i]) + } + } + return buildTree(nextLevel) +} + +// GenerateProof generates a Merkle proof for a leaf node at a given index +func (mt *MerkleTree) GenerateProof(index uint32) []byte { + var proof []byte + if index < 0 || index >= uint32(len(mt.Leaves)) { + return proof // Invalid index + } + + node := mt.Leaves[index] + for node != mt.Root { + parent := findParent(mt.Root, node) + sibling := getSibling(parent, node) + if sibling != nil { + proof = append(proof, sibling.Hash...) + } + node = parent + } + return proof +} + +// findParent finds the parent node of a given child in the Merkle tree +func findParent(root, child *MerkleNode) *MerkleNode { + if root == nil || (root.Left == child || root.Right == child) { + return root + } + if node := findParent(root.Left, child); node != nil { + return node + } + return findParent(root.Right, child) +} + +// getSibling returns the sibling of a given node in the tree +func getSibling(parent, node *MerkleNode) *MerkleNode { + if parent == nil || (parent.Left != node && parent.Right != node) { + return nil + } + if parent.Left == node { + return parent.Right + } + return parent.Left +} + +func GenerateProofForSubValues(subValues []common.Hash, index uint32) []byte { + tree := NewMerkleTree(subValues) + return tree.GenerateProof(index) +} diff --git a/op-challenger2/game/fault/trace/utils/daproof_test.go b/op-challenger2/game/fault/trace/utils/daproof_test.go new file mode 100644 index 0000000000000..5620102a0eb03 --- /dev/null +++ b/op-challenger2/game/fault/trace/utils/daproof_test.go @@ -0,0 +1,76 @@ +package utils + +import ( + "testing" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/crypto" + "github.com/stretchr/testify/require" +) + +func TestUnpaddingMerkelProof(t *testing.T) { + twoLeaves := []common.Hash{{0xaa}, {0xbb}} + fourLeaves := []common.Hash{{0xaa}, {0xbb}, {0xcc}, {0xdd}} + fiveLeaves := []common.Hash{{0xaa}, {0xbb}, {0xcc}, {0xdd}, {0xee}} + tests := []struct { + data []common.Hash + index uint32 + expectProof []byte + }{ + // 2 leaves + { + data: twoLeaves, + index: 0, + expectProof: twoLeaves[1][:], + }, + { + data: twoLeaves, + index: 1, + expectProof: twoLeaves[0][:], + }, + // 4 leaves + { + data: fourLeaves, + index: 0, + expectProof: append(fourLeaves[1][:], crypto.Keccak256(fourLeaves[2][:], fourLeaves[3][:])...), + }, + { + data: fourLeaves, + index: 3, + expectProof: append(fourLeaves[2][:], crypto.Keccak256(fourLeaves[0][:], fourLeaves[1][:])...), + }, + // 5 leaves + { + data: fiveLeaves, + index: 0, + expectProof: append(append(fiveLeaves[1][:], crypto.Keccak256(fiveLeaves[2][:], fiveLeaves[3][:])...), fiveLeaves[4][:]...), + }, + { + data: fiveLeaves, + index: 1, + expectProof: append(append(fiveLeaves[0][:], crypto.Keccak256(fiveLeaves[2][:], fiveLeaves[3][:])...), fiveLeaves[4][:]...), + }, + { + data: fiveLeaves, + index: 2, + expectProof: append(append(fiveLeaves[3][:], crypto.Keccak256(fiveLeaves[0][:], fiveLeaves[1][:])...), fiveLeaves[4][:]...), + }, + { + data: fiveLeaves, + index: 3, + expectProof: append(append(fiveLeaves[2][:], crypto.Keccak256(fiveLeaves[0][:], fiveLeaves[1][:])...), fiveLeaves[4][:]...), + }, + { + data: fiveLeaves, + index: 4, + expectProof: crypto.Keccak256(crypto.Keccak256(fiveLeaves[0][:], fiveLeaves[1][:]), crypto.Keccak256(fiveLeaves[2][:], fiveLeaves[3][:])), + }, + } + + for _, tCase := range tests { + // Build the Merkle tree without padding + tree := NewMerkleTree(tCase.data) + proof := tree.GenerateProof(tCase.index) + require.Equal(t, tCase.expectProof, proof) + } +} diff --git a/op-challenger2/game/fault/types/game.go b/op-challenger2/game/fault/types/game.go index 12f41abf1d5ff..8f3eea2bb6f5a 100644 --- a/op-challenger2/game/fault/types/game.go +++ b/op-challenger2/game/fault/types/game.go @@ -46,6 +46,7 @@ type Game interface { SplitDepth() Depth // TraceRootDepth = MaxDepth - SplitDepth TraceRootDepth() Depth + RootClaim() Claim } // gameState is a struct that represents the state of a dispute game. @@ -57,7 +58,6 @@ type gameState struct { depth Depth nBits uint64 splitDepth Depth - daType DAType } // NewGameState returns a new game state. @@ -109,6 +109,11 @@ func (g *gameState) Claims() []Claim { return append([]Claim(nil), g.claims...) } +func (g *gameState) RootClaim() Claim { + // Defensively copy to avoid modifications to the underlying array. + return g.claims[0] +} + func (g *gameState) MaxDepth() Depth { return g.depth } @@ -190,5 +195,5 @@ func (g *gameState) SplitDepth() Depth { } func (g *gameState) TraceRootDepth() Depth { - return g.depth - g.splitDepth + return g.splitDepth + Depth(g.nBits) } diff --git a/op-challenger2/game/fault/types/position.go b/op-challenger2/game/fault/types/position.go index 318da52dea8b0..86dd518eed329 100644 --- a/op-challenger2/game/fault/types/position.go +++ b/op-challenger2/game/fault/types/position.go @@ -117,6 +117,10 @@ func (p Position) parentIndexAtDepth() *big.Int { return new(big.Int).Div(p.IndexAtDepth(), big.NewInt(2)) } +func (p Position) parentIndexAtDepthN(nbits uint64) *big.Int { + return new(big.Int).Div(p.IndexAtDepth(), big.NewInt(1<