Commit 3b76cfb5 authored by OptimismBot's avatar OptimismBot Committed by GitHub

Merge pull request #6687 from ethereum-optimism/aj/cannon-first-step

op-challenger: Fix provision of pre-state, proof and oracle data from cannon proofs
parents b8652203 279fabaa
......@@ -134,21 +134,17 @@ func (a *Agent) step(ctx context.Context, claim types.Claim, game types.Game) er
return nil
}
oracleData, err := a.solver.GetOracleData(ctx, claim)
a.log.Info("Attempting step", "claim_depth", claim.Depth(), "maxDepth", a.maxDepth)
step, err := a.solver.AttemptStep(ctx, claim, agreeWithClaimLevel)
if err != nil {
a.log.Debug("Failed to get oracle data", "err", err)
return nil
return fmt.Errorf("attempt step: %w", err)
}
a.log.Info("Updating oracle data", "oracleKey", oracleData.OracleKey, "oracleData", oracleData.OracleData)
if err := a.updater.UpdateOracle(ctx, oracleData); err != nil {
if step.OracleData != nil {
a.log.Info("Updating oracle data", "oracleKey", step.OracleData.OracleKey, "oracleData", step.OracleData.OracleData)
if err := a.updater.UpdateOracle(ctx, step.OracleData); err != nil {
return fmt.Errorf("failed to load oracle data: %w", err)
}
a.log.Info("Attempting step", "claim_depth", claim.Depth(), "maxDepth", a.maxDepth)
step, err := a.solver.AttemptStep(ctx, claim, agreeWithClaimLevel)
if err != nil {
return fmt.Errorf("attempt step: %w", err)
}
a.log.Info("Performing step", "is_attack", step.IsAttack,
......
......@@ -30,27 +30,31 @@ func NewTraceProvider(state string, depth uint64) *AlphabetTraceProvider {
}
}
// GetOracleData should not return any preimage oracle data for the alphabet provider.
func (p *AlphabetTraceProvider) GetOracleData(ctx context.Context, i uint64) (*types.PreimageOracleData, error) {
return &types.PreimageOracleData{}, nil
}
// GetPreimage returns the preimage for the given hash.
func (ap *AlphabetTraceProvider) GetPreimage(ctx context.Context, i uint64) ([]byte, []byte, error) {
func (ap *AlphabetTraceProvider) GetStepData(ctx context.Context, i uint64) ([]byte, []byte, *types.PreimageOracleData, error) {
if i == 0 {
prestate, err := ap.AbsolutePreState(ctx)
if err != nil {
return nil, nil, nil, err
}
return prestate, []byte{}, nil, nil
}
// We want the pre-state which is the value prior to the one requested
i--
// The index cannot be larger than the maximum index as computed by the depth.
if i >= ap.maxLen {
return nil, nil, ErrIndexTooLarge
return nil, nil, nil, ErrIndexTooLarge
}
// We extend the deepest hash to the maximum depth if the trace is not expansive.
if i >= uint64(len(ap.state)) {
return ap.GetPreimage(ctx, uint64(len(ap.state))-1)
return ap.GetStepData(ctx, uint64(len(ap.state)))
}
return BuildAlphabetPreimage(i, ap.state[i]), []byte{}, nil
return BuildAlphabetPreimage(i, ap.state[i]), []byte{}, nil, nil
}
// Get returns the claim value at the given index in the trace.
func (ap *AlphabetTraceProvider) Get(ctx context.Context, i uint64) (common.Hash, error) {
claimBytes, _, err := ap.GetPreimage(ctx, i)
// Step data returns the pre-state, so add 1 to get the state for index i
claimBytes, _, _, err := ap.GetStepData(ctx, i+1)
if err != nil {
return common.Hash{}, err
}
......
......@@ -58,20 +58,21 @@ func FuzzIndexToBytes(f *testing.F) {
// TestGetPreimage_Succeeds tests the GetPreimage function
// returns the correct pre-image for a index.
func TestGetPreimage_Succeeds(t *testing.T) {
func TestGetStepData_Succeeds(t *testing.T) {
ap := NewTraceProvider("abc", 2)
expected := BuildAlphabetPreimage(0, "a'")
retrieved, proof, err := ap.GetPreimage(context.Background(), uint64(0))
retrieved, proof, data, err := ap.GetStepData(context.Background(), uint64(1))
require.NoError(t, err)
require.Equal(t, expected, retrieved)
require.Empty(t, proof)
require.Nil(t, data)
}
// TestGetPreimage_TooLargeIndex_Fails tests the GetPreimage
// function errors if the index is too large.
func TestGetPreimage_TooLargeIndex_Fails(t *testing.T) {
func TestGetStepData_TooLargeIndex_Fails(t *testing.T) {
ap := NewTraceProvider("abc", 2)
_, _, err := ap.GetPreimage(context.Background(), 4)
_, _, _, err := ap.GetStepData(context.Background(), 5)
require.ErrorIs(t, err, ErrIndexTooLarge)
}
......
......@@ -8,7 +8,6 @@ import (
"os"
"path/filepath"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-bindings/bindings"
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types"
......@@ -47,6 +46,8 @@ type CannonTraceProvider struct {
// lastStep stores the last step in the actual trace if known. 0 indicates unknown.
// Cached as an optimisation to avoid repeatedly attempting to execute beyond the end of the trace.
lastStep uint64
// lastProof stores the proof data to use for all steps extended beyond lastStep
lastProof *proofData
}
func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config, l1Client bind.ContractCaller) (*CannonTraceProvider, error) {
......@@ -71,24 +72,11 @@ func NewTraceProvider(ctx context.Context, logger log.Logger, cfg *config.Config
}, nil
}
func (p *CannonTraceProvider) GetOracleData(ctx context.Context, i uint64) (*types.PreimageOracleData, error) {
proof, err := p.loadProofData(ctx, i)
if err != nil {
return nil, err
}
data := types.NewPreimageOracleData(proof.OracleKey, proof.OracleValue, proof.OracleOffset)
return &data, nil
}
func (p *CannonTraceProvider) Get(ctx context.Context, i uint64) (common.Hash, error) {
proof, state, err := p.loadProof(ctx, i)
proof, err := p.loadProof(ctx, i)
if err != nil {
return common.Hash{}, err
}
if proof == nil && state != nil {
// Use the hash from the final state
return crypto.Keccak256Hash(state.EncodeWitness()), nil
}
value := common.BytesToHash(proof.ClaimValue)
if value == (common.Hash{}) {
......@@ -97,66 +85,46 @@ func (p *CannonTraceProvider) Get(ctx context.Context, i uint64) (common.Hash, e
return value, nil
}
func (p *CannonTraceProvider) GetPreimage(ctx context.Context, i uint64) ([]byte, []byte, error) {
proof, err := p.loadProofData(ctx, i)
func (p *CannonTraceProvider) GetStepData(ctx context.Context, i uint64) ([]byte, []byte, *types.PreimageOracleData, error) {
proof, err := p.loadProof(ctx, i)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
value := ([]byte)(proof.StateData)
if len(value) == 0 {
return nil, nil, errors.New("proof missing state data")
return nil, nil, nil, errors.New("proof missing state data")
}
data := ([]byte)(proof.ProofData)
if len(data) == 0 {
return nil, nil, errors.New("proof missing proof data")
if data == nil {
return nil, nil, nil, errors.New("proof missing proof data")
}
var oracleData *types.PreimageOracleData
if len(proof.OracleKey) > 0 {
oracleData = types.NewPreimageOracleData(proof.OracleKey, proof.OracleValue, proof.OracleOffset)
}
return value, data, nil
return value, data, oracleData, nil
}
func (p *CannonTraceProvider) AbsolutePreState(ctx context.Context) ([]byte, error) {
state, err := parseState(p.prestate)
if err != nil {
return []byte{}, fmt.Errorf("cannot load absolute pre-state: %w", err)
return nil, fmt.Errorf("cannot load absolute pre-state: %w", err)
}
return state.EncodeWitness(), nil
}
// loadProofData loads the proof data for the specified step.
// If the requested index is beyond the end of the actual trace, the proof data from the last step is returned.
// Cannon will be executed a second time if required to generate the full proof data.
func (p *CannonTraceProvider) loadProofData(ctx context.Context, i uint64) (*proofData, error) {
proof, state, err := p.loadProof(ctx, i)
if err != nil {
return nil, err
} else if proof == nil && state != nil {
p.logger.Info("Re-executing to generate proof for last step", "step", state.Step)
proof, _, err = p.loadProof(ctx, state.Step)
if err != nil {
return nil, err
}
if proof == nil {
return nil, fmt.Errorf("proof at step %v was not generated", i)
}
return proof, nil
}
return proof, 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:
// - When the actual trace length is known, the proof data from the last step is returned with nil state
// - When the actual trace length is not yet know, the state from after the last step is returned with nil proofData
// and the actual trace length is cached for future runs
func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofData, *mipsevm.State, error) {
if p.lastStep != 0 && i > p.lastStep {
// If the requested index is after the last step in the actual trace, use the last step
i = p.lastStep
// 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) (*proofData, error) {
if p.lastProof != nil && i > p.lastStep {
// If the requested index is after the last step in the actual trace, extend the final no-op step
return p.lastProof, nil
}
path := filepath.Join(p.dir, proofsDir, fmt.Sprintf("%d.json", i))
file, err := os.Open(path)
if errors.Is(err, os.ErrNotExist) {
if err := p.generator.GenerateProof(ctx, p.dir, i); err != nil {
return nil, nil, fmt.Errorf("generate cannon trace with proof at %v: %w", i, err)
return nil, fmt.Errorf("generate cannon trace with proof at %v: %w", i, err)
}
// Try opening the file again now and it should exist.
file, err = os.Open(path)
......@@ -164,27 +132,39 @@ func (p *CannonTraceProvider) loadProof(ctx context.Context, i uint64) (*proofDa
// Expected proof wasn't generated, check if we reached the end of execution
state, err := parseState(filepath.Join(p.dir, finalState))
if err != nil {
return nil, nil, fmt.Errorf("cannot read final state: %w", err)
return nil, fmt.Errorf("cannot read final state: %w", err)
}
if state.Exited && state.Step < i {
if state.Exited && state.Step <= i {
p.logger.Warn("Requested proof was after the program exited", "proof", i, "last", state.Step)
// The final instruction has already been applied to this state, so the last step we can execute
// is one before its Step value.
p.lastStep = state.Step - 1
return nil, state, nil
// Extend the trace out to the full length using a no-op instruction that doesn't change any state
// No execution is done, so no proof-data or oracle values are required.
witness := state.EncodeWitness()
proof := &proofData{
ClaimValue: crypto.Keccak256(witness),
StateData: witness,
ProofData: []byte{},
OracleKey: nil,
OracleValue: nil,
OracleOffset: 0,
}
p.lastProof = proof
return proof, nil
} else {
return nil, nil, fmt.Errorf("expected proof not generated but final state was not exited, requested step %v, final state at step %v", i, state.Step)
return nil, fmt.Errorf("expected proof not generated but final state was not exited, requested step %v, final state at step %v", i, state.Step)
}
}
}
if err != nil {
return nil, nil, fmt.Errorf("cannot open proof file (%v): %w", path, err)
return nil, fmt.Errorf("cannot open proof file (%v): %w", path, err)
}
defer file.Close()
var proof proofData
err = json.NewDecoder(file).Decode(&proof)
if err != nil {
return nil, nil, fmt.Errorf("failed to read proof (%v): %w", path, err)
return nil, fmt.Errorf("failed to read proof (%v): %w", path, err)
}
return &proof, nil, nil
return &proof, nil
}
......@@ -11,6 +11,7 @@ import (
"testing"
"github.com/ethereum-optimism/optimism/cannon/mipsevm"
"github.com/ethereum-optimism/optimism/op-challenger/fault/types"
"github.com/ethereum-optimism/optimism/op-node/testlog"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
......@@ -61,21 +62,22 @@ func TestGet(t *testing.T) {
})
}
func TestGetOracleData(t *testing.T) {
func TestGetStepData(t *testing.T) {
dataDir, prestate := setupTestData(t)
t.Run("ExistingProof", func(t *testing.T) {
provider, generator := setupWithTestData(t, dataDir, prestate)
oracleData, err := provider.GetOracleData(context.Background(), 420)
value, proof, data, err := provider.GetStepData(context.Background(), 0)
require.NoError(t, err)
require.False(t, oracleData.IsLocal)
expectedKey := common.Hex2Bytes("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee")
require.Equal(t, expectedKey, oracleData.OracleKey)
expectedData := common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
require.Equal(t, expectedData, oracleData.OracleData)
expected := common.Hex2Bytes("b8f068de604c85ea0e2acd437cdb47add074a2d70b81d018390c504b71fe26f400000000000000000000000000000000000000000000000000000000000000000000000000")
require.Equal(t, expected, value)
expectedProof := common.Hex2Bytes("08028e3c0000000000000000000000003c01000a24210b7c00200008000000008fa40004")
require.Equal(t, expectedProof, proof)
// TODO: Need to add some oracle data
require.Nil(t, data)
require.Empty(t, generator.generated)
})
t.Run("ProofAfterEndOfTrace", func(t *testing.T) {
t.Run("GenerateProof", func(t *testing.T) {
provider, generator := setupWithTestData(t, dataDir, prestate)
generator.finalState = &mipsevm.State{
Memory: &mipsevm.Memory{},
......@@ -90,39 +92,14 @@ func TestGetOracleData(t *testing.T) {
OracleValue: []byte{0xdd},
OracleOffset: 10,
}
oracleData, err := provider.GetOracleData(context.Background(), 7000)
require.NoError(t, err)
require.Contains(t, generator.generated, 7000, "should have tried to generate the proof")
require.Contains(t, generator.generated, 9, "should have regenerated proof from last step")
require.False(t, oracleData.IsLocal)
require.EqualValues(t, generator.proof.OracleKey, oracleData.OracleKey)
require.EqualValues(t, generator.proof.OracleValue, oracleData.OracleData)
})
t.Run("IgnoreUnknownFields", func(t *testing.T) {
provider, generator := setupWithTestData(t, dataDir, prestate)
oracleData, err := provider.GetOracleData(context.Background(), 421)
preimage, proof, data, err := provider.GetStepData(context.Background(), 4)
require.NoError(t, err)
require.False(t, oracleData.IsLocal)
expectedKey := common.Hex2Bytes("eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee")
require.Equal(t, expectedKey, oracleData.OracleKey)
expectedData := common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")
require.Equal(t, expectedData, oracleData.OracleData)
require.Empty(t, generator.generated)
})
}
require.Contains(t, generator.generated, 4, "should have tried to generate the proof")
func TestGetPreimage(t *testing.T) {
dataDir, prestate := setupTestData(t)
t.Run("ExistingProof", func(t *testing.T) {
provider, generator := setupWithTestData(t, dataDir, prestate)
value, proof, err := provider.GetPreimage(context.Background(), 0)
require.NoError(t, err)
expected := common.Hex2Bytes("b8f068de604c85ea0e2acd437cdb47add074a2d70b81d018390c504b71fe26f400000000000000000000000000000000000000000000000000000000000000000000000000")
require.Equal(t, expected, value)
expectedProof := common.Hex2Bytes("08028e3c0000000000000000000000003c01000a24210b7c00200008000000008fa40004")
require.Equal(t, expectedProof, proof)
require.Empty(t, generator.generated)
require.EqualValues(t, generator.proof.StateData, preimage)
require.EqualValues(t, generator.proof.ProofData, proof)
expectedData := types.NewPreimageOracleData(generator.proof.OracleKey, generator.proof.OracleValue, generator.proof.OracleOffset)
require.EqualValues(t, expectedData, data)
})
t.Run("ProofAfterEndOfTrace", func(t *testing.T) {
......@@ -140,30 +117,33 @@ func TestGetPreimage(t *testing.T) {
OracleValue: []byte{0xdd},
OracleOffset: 10,
}
preimage, proof, err := provider.GetPreimage(context.Background(), 7000)
preimage, proof, data, err := provider.GetStepData(context.Background(), 7000)
require.NoError(t, err)
require.Contains(t, generator.generated, 7000, "should have tried to generate the proof")
require.Contains(t, generator.generated, 9, "should have regenerated proof from last step")
require.EqualValues(t, generator.proof.StateData, preimage)
require.EqualValues(t, generator.proof.ProofData, proof)
witness := generator.finalState.EncodeWitness()
require.EqualValues(t, witness, preimage)
require.Equal(t, []byte{}, proof)
require.Nil(t, data)
})
t.Run("MissingStateData", func(t *testing.T) {
provider, generator := setupWithTestData(t, dataDir, prestate)
_, _, err := provider.GetPreimage(context.Background(), 1)
_, _, _, err := provider.GetStepData(context.Background(), 1)
require.ErrorContains(t, err, "missing state data")
require.Empty(t, generator.generated)
})
t.Run("IgnoreUnknownFields", func(t *testing.T) {
provider, generator := setupWithTestData(t, dataDir, prestate)
value, proof, err := provider.GetPreimage(context.Background(), 2)
value, proof, data, err := provider.GetStepData(context.Background(), 2)
require.NoError(t, err)
expected := common.Hex2Bytes("cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc")
require.Equal(t, expected, value)
expectedProof := common.Hex2Bytes("dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd")
require.Equal(t, expectedProof, proof)
require.Empty(t, generator.generated)
require.Nil(t, data)
})
}
......
......@@ -87,9 +87,6 @@ func NewOracleUpdaterWithOracle(
// UpdateOracle updates the oracle with the given data.
func (u *cannonUpdater) UpdateOracle(ctx context.Context, data *types.PreimageOracleData) error {
if len(data.OracleKey) == 0 {
return nil
}
if data.IsLocal {
return u.sendLocalOracleData(ctx, data)
}
......
......@@ -89,12 +89,6 @@ func TestCannonUpdater_UpdateOracle(t *testing.T) {
}))
require.Equal(t, 1, mockTxMgr.failedSends)
})
t.Run("skip empty data", func(t *testing.T) {
updater, mockTxMgr := newTestCannonUpdater(t, true)
require.NoError(t, updater.UpdateOracle(context.Background(), &types.PreimageOracleData{}))
require.Equal(t, 0, mockTxMgr.sends)
})
}
// TestCannonUpdater_BuildLocalOracleData tests the [cannonUpdater]
......
......@@ -67,10 +67,7 @@ func newMockTraceProvider(prestateErrors bool, prestate []byte) *mockTraceProvid
func (m *mockTraceProvider) Get(ctx context.Context, i uint64) (common.Hash, error) {
panic("not implemented")
}
func (m *mockTraceProvider) GetOracleData(ctx context.Context, i uint64) (*types.PreimageOracleData, error) {
panic("not implemented")
}
func (m *mockTraceProvider) GetPreimage(ctx context.Context, i uint64) (preimage []byte, proofData []byte, err error) {
func (m *mockTraceProvider) GetStepData(ctx context.Context, i uint64) (prestate []byte, proofData []byte, preimageData *types.PreimageOracleData, err error) {
panic("not implemented")
}
func (m *mockTraceProvider) AbsolutePreState(ctx context.Context) ([]byte, error) {
......
......@@ -28,12 +28,6 @@ func NewSolver(gameDepth int, traceProvider types.TraceProvider) *Solver {
}
}
// GetOracleData returns the oracle data for the provided claim.
// It passes through to the [TraceProvider] by finding the trace index for the claim.
func (s *Solver) GetOracleData(ctx context.Context, claim types.Claim) (*types.PreimageOracleData, error) {
return s.trace.GetOracleData(ctx, claim.TraceIndex(s.gameDepth))
}
// NextMove returns the next move to make given the current state of the game.
func (s *Solver) NextMove(ctx context.Context, claim types.Claim, agreeWithClaimLevel bool) (*types.Claim, error) {
if agreeWithClaimLevel {
......@@ -58,7 +52,7 @@ type StepData struct {
IsAttack bool
PreState []byte
ProofData []byte
OracleData types.PreimageOracleData
OracleData *types.PreimageOracleData
}
// AttemptStep determines what step should occur for a given leaf claim.
......@@ -77,35 +71,30 @@ func (s *Solver) AttemptStep(ctx context.Context, claim types.Claim, agreeWithCl
index := claim.TraceIndex(s.gameDepth)
var preState []byte
var proofData []byte
// If we are attacking index 0, we provide the absolute pre-state, not an intermediate state
if index == 0 && !claimCorrect {
state, err := s.trace.AbsolutePreState(ctx)
var oracleData *types.PreimageOracleData
if !claimCorrect {
// Attack the claim by executing step index, so we need to get the pre-state of that index
preState, proofData, oracleData, err = s.trace.GetStepData(ctx, index)
if err != nil {
return StepData{}, err
}
preState = state
} else {
// If attacking, get the state just before, other get the state after
if !claimCorrect {
index = index - 1
}
preState, proofData, err = s.trace.GetPreimage(ctx, index)
// We agree with the claim so Defend and use this claim as the starting point to execute the step after
// Thus we need the pre-state of the next step
// Note: This makes our maximum depth 63 because we need to add 1 without overflowing.
preState, proofData, oracleData, err = s.trace.GetStepData(ctx, index+1)
if err != nil {
return StepData{}, err
}
}
oracleData, err := s.trace.GetOracleData(ctx, index)
if err != nil {
return StepData{}, err
}
return StepData{
LeafClaim: claim,
IsAttack: !claimCorrect,
PreState: preState,
ProofData: proofData,
OracleData: *oracleData,
OracleData: oracleData,
}, nil
}
......
......@@ -113,9 +113,6 @@ func TestAttemptStep(t *testing.T) {
ctx := context.Background()
preState, err := builder.CorrectTraceProvider().AbsolutePreState(ctx)
require.NoError(t, err)
tests := []struct {
name string
claim types.Claim
......@@ -124,63 +121,55 @@ func TestAttemptStep(t *testing.T) {
expectAttack bool
expectPreState []byte
expectProofData []byte
expectedLocal bool
expectedOracleKey []byte
expectedOracleData []byte
expectedOracleData *types.PreimageOracleData
}{
{
name: "AttackFirstTraceIndex",
claim: builder.CreateLeafClaim(0, false),
expectAttack: true,
expectPreState: preState,
expectProofData: nil,
expectedOracleKey: []byte{byte(0)},
expectedOracleData: []byte{byte(0)},
expectPreState: builder.CorrectPreState(0),
expectProofData: builder.CorrectProofData(0),
expectedOracleData: builder.CorrectOracleData(0),
},
{
name: "DefendFirstTraceIndex",
claim: builder.CreateLeafClaim(0, true),
expectAttack: false,
expectPreState: builder.CorrectPreState(0),
expectProofData: builder.CorrectProofData(0),
expectedOracleKey: []byte{byte(0)},
expectedOracleData: []byte{byte(0)},
expectPreState: builder.CorrectPreState(1),
expectProofData: builder.CorrectProofData(1),
expectedOracleData: builder.CorrectOracleData(1),
},
{
name: "AttackMiddleTraceIndex",
claim: builder.CreateLeafClaim(4, false),
expectAttack: true,
expectPreState: builder.CorrectPreState(3),
expectProofData: builder.CorrectProofData(3),
expectedOracleKey: []byte{byte(3)},
expectedOracleData: []byte{byte(3)},
expectPreState: builder.CorrectPreState(4),
expectProofData: builder.CorrectProofData(4),
expectedOracleData: builder.CorrectOracleData(4),
},
{
name: "DefendMiddleTraceIndex",
claim: builder.CreateLeafClaim(4, true),
expectAttack: false,
expectPreState: builder.CorrectPreState(4),
expectProofData: builder.CorrectProofData(4),
expectedOracleKey: []byte{byte(4)},
expectedOracleData: []byte{byte(4)},
expectPreState: builder.CorrectPreState(5),
expectProofData: builder.CorrectProofData(5),
expectedOracleData: builder.CorrectOracleData(5),
},
{
name: "AttackLastTraceIndex",
claim: builder.CreateLeafClaim(lastLeafTraceIndex, false),
expectAttack: true,
expectPreState: builder.CorrectPreState(lastLeafTraceIndex - 1),
expectProofData: builder.CorrectProofData(lastLeafTraceIndex - 1),
expectedOracleKey: []byte{byte(5)},
expectedOracleData: []byte{byte(5)},
expectPreState: builder.CorrectPreState(lastLeafTraceIndex),
expectProofData: builder.CorrectProofData(lastLeafTraceIndex),
expectedOracleData: builder.CorrectOracleData(lastLeafTraceIndex),
},
{
name: "DefendLastTraceIndex",
claim: builder.CreateLeafClaim(lastLeafTraceIndex, true),
expectAttack: false,
expectPreState: builder.CorrectPreState(lastLeafTraceIndex),
expectProofData: builder.CorrectProofData(lastLeafTraceIndex),
expectedOracleKey: []byte{byte(6)},
expectedOracleData: []byte{byte(6)},
expectPreState: builder.CorrectPreState(lastLeafTraceIndex + 1),
expectProofData: builder.CorrectProofData(lastLeafTraceIndex + 1),
expectedOracleData: builder.CorrectOracleData(lastLeafTraceIndex + 1),
},
{
name: "CannotStepNonLeaf",
......@@ -199,24 +188,6 @@ func TestAttemptStep(t *testing.T) {
agreeWithLevel: true,
expectedErr: solver.ErrStepNonLeafNode,
},
{
name: "AttackLocalOracleData",
claim: builder.Seq(false).Attack(false).Attack(true).Defend(false).Get(),
expectAttack: true,
agreeWithLevel: false,
expectPreState: builder.CorrectPreState(1),
expectProofData: builder.CorrectProofData(1),
expectedLocal: true,
expectedOracleKey: []byte{0x01},
expectedOracleData: []byte{0x01},
expectedErr: nil,
},
{
name: "AttackStepOracleError",
claim: builder.Seq(false).Attack(false).Attack(false).Attack(false).Get(),
agreeWithLevel: false,
expectedErr: errProvider,
},
}
for _, tableTest := range tests {
......@@ -235,9 +206,10 @@ func TestAttemptStep(t *testing.T) {
require.Equal(t, tableTest.expectAttack, step.IsAttack)
require.Equal(t, tableTest.expectPreState, step.PreState)
require.Equal(t, tableTest.expectProofData, step.ProofData)
require.Equal(t, tableTest.expectedLocal, step.OracleData.IsLocal)
require.Equal(t, tableTest.expectedOracleKey, step.OracleData.OracleKey)
require.Equal(t, tableTest.expectedOracleData, step.OracleData.OracleData)
require.Equal(t, tableTest.expectedOracleData.IsLocal, step.OracleData.IsLocal)
require.Equal(t, tableTest.expectedOracleData.OracleKey, step.OracleData.OracleKey)
require.Equal(t, tableTest.expectedOracleData.OracleData, step.OracleData.OracleData)
require.Equal(t, tableTest.expectedOracleData.OracleOffset, step.OracleData.OracleOffset)
} else {
require.ErrorIs(t, err, tableTest.expectedErr)
require.Equal(t, solver.StepData{}, step)
......
......@@ -25,18 +25,11 @@ type alphabetWithProofProvider struct {
OracleError error
}
func (a *alphabetWithProofProvider) GetPreimage(ctx context.Context, i uint64) ([]byte, []byte, error) {
preimage, _, err := a.AlphabetTraceProvider.GetPreimage(ctx, i)
func (a *alphabetWithProofProvider) GetStepData(ctx context.Context, i uint64) ([]byte, []byte, *types.PreimageOracleData, error) {
preimage, _, _, err := a.AlphabetTraceProvider.GetStepData(ctx, i)
if err != nil {
return nil, nil, err
return nil, nil, nil, err
}
return preimage, []byte{byte(i)}, nil
}
func (a *alphabetWithProofProvider) GetOracleData(ctx context.Context, i uint64) (*types.PreimageOracleData, error) {
if a.OracleError != nil {
return &types.PreimageOracleData{}, a.OracleError
}
data := types.NewPreimageOracleData([]byte{byte(i)}, []byte{byte(i)}, uint32(i))
return &data, nil
data := types.NewPreimageOracleData([]byte{byte(i)}, []byte{byte(i - 1)}, uint32(i-1))
return preimage, []byte{byte(i - 1)}, data, nil
}
......@@ -38,20 +38,26 @@ func (c *ClaimBuilder) CorrectClaim(idx uint64) common.Hash {
return value
}
// CorrectPreState returns the pre-image of the canonical claim at the specified trace index
// CorrectPreState returns the pre-state (not hashed) required to execute the valid step at the specified trace index
func (c *ClaimBuilder) CorrectPreState(idx uint64) []byte {
preimage, _, err := c.correct.GetPreimage(context.Background(), idx)
preimage, _, _, err := c.correct.GetStepData(context.Background(), idx)
c.require.NoError(err)
return preimage
}
// CorrectProofData returns the proof-data for the canonical claim at the specified trace index
// CorrectProofData returns the proof-data required to execute the valid step at the specified trace index
func (c *ClaimBuilder) CorrectProofData(idx uint64) []byte {
_, proof, err := c.correct.GetPreimage(context.Background(), idx)
_, proof, _, err := c.correct.GetStepData(context.Background(), idx)
c.require.NoError(err)
return proof
}
func (c *ClaimBuilder) CorrectOracleData(idx uint64) *types.PreimageOracleData {
_, _, data, err := c.correct.GetStepData(context.Background(), idx)
c.require.NoError(err)
return data
}
func (c *ClaimBuilder) incorrectClaim(idx uint64) common.Hash {
return common.BigToHash(new(big.Int).SetUint64(idx))
}
......
......@@ -45,8 +45,8 @@ func (p *PreimageOracleData) GetPreimageWithoutSize() []byte {
}
// NewPreimageOracleData creates a new [PreimageOracleData] instance.
func NewPreimageOracleData(key []byte, data []byte, offset uint32) PreimageOracleData {
return PreimageOracleData{
func NewPreimageOracleData(key []byte, data []byte, offset uint32) *PreimageOracleData {
return &PreimageOracleData{
IsLocal: len(key) > 0 && key[0] == byte(1),
OracleKey: key,
OracleData: data,
......@@ -74,17 +74,14 @@ type TraceProvider interface {
// Get(i) = Keccak256(GetPreimage(i))
Get(ctx context.Context, i uint64) (common.Hash, error)
// GetOracleData returns preimage oracle data that can be submitted to the pre-image
// oracle and the dispute game contract. This function accepts a trace index for
// which the provider returns needed preimage data.
GetOracleData(ctx context.Context, i uint64) (*PreimageOracleData, error)
// GetPreimage returns the pre-image for a claim at the specified trace index, along
// with any associated proof data to assist in its verification.
GetPreimage(ctx context.Context, i uint64) (preimage []byte, proofData []byte, err error)
// GetStepData returns the data required to execute the step at the specified trace index.
// This includes the pre-state of the step (not hashed), the proof data required during step execution
// and any pre-image data that needs to be loaded into the oracle prior to execution (may be nil)
// The prestate returned from GetStepData for trace 10 should be the pre-image of the claim from trace 9
GetStepData(ctx context.Context, i uint64) (prestate []byte, proofData []byte, preimageData *PreimageOracleData, err error)
// AbsolutePreState is the pre-image value of the trace that transitions to the trace value at index 0
AbsolutePreState(ctx context.Context) ([]byte, error)
AbsolutePreState(ctx context.Context) (preimage []byte, err error)
}
// ClaimData is the core of a claim. It must be unique inside a specific game.
......
File mode changed from 100644 to 100755
......@@ -9,6 +9,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/config"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/challenger"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
)
......@@ -49,3 +50,7 @@ func (g *CannonGameHelper) StartChallenger(ctx context.Context, rollupCfg *rollu
})
return c
}
func (g *FaultGameHelper) Address() common.Address {
return g.addr
}
......@@ -122,3 +122,29 @@ func (g *FaultGameHelper) Attack(ctx context.Context, claimIdx int64, claim comm
_, err = utils.WaitReceiptOK(ctx, g.client, tx.Hash())
g.require.NoError(err, "Attack transaction was not OK")
}
func (g *FaultGameHelper) Defend(ctx context.Context, claimIdx int64, claim common.Hash) {
tx, err := g.game.Defend(g.opts, big.NewInt(claimIdx), claim)
g.require.NoError(err, "Defend transaction did not send")
_, err = utils.WaitReceiptOK(ctx, g.client, tx.Hash())
g.require.NoError(err, "Defend transaction was not OK")
}
func (g *FaultGameHelper) LogGameData(ctx context.Context) {
opts := &bind.CallOpts{Context: ctx}
maxDepth := int(g.MaxDepth(ctx))
claimCount, err := g.game.ClaimDataLen(opts)
info := fmt.Sprintf("Claim count: %v\n", claimCount)
g.require.NoError(err, "Fetching claim count")
for i := int64(0); i < claimCount.Int64(); i++ {
claim, err := g.game.ClaimData(opts, big.NewInt(i))
g.require.NoErrorf(err, "Fetch claim %v", i)
pos := types.NewPositionFromGIndex(claim.Position.Uint64())
info = info + fmt.Sprintf("%v - Position: %v, Depth: %v, IndexAtDepth: %v Trace Index: %v, Value: %v, Countered: %v\n",
i, claim.Position.Int64(), pos.Depth(), pos.IndexAtDepth(), pos.TraceIndex(maxDepth), common.Hash(claim.Claim).Hex(), claim.Countered)
}
status, err := g.game.Status(opts)
g.require.NoError(err, "Load game status")
g.t.Logf("Game %v:\n%v\nCurrent status: %v\n", g.addr, info, Status(status))
}
......@@ -147,6 +147,19 @@ func TestChallengerCompleteDisputeGame(t *testing.T) {
func TestCannonDisputeGame(t *testing.T) {
InitParallel(t)
tests := []struct {
name string
defendAtClaim int64
}{
{"StepFirst", 0},
{"StepMiddle", 28},
{"StepInExtension", 2},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
InitParallel(t)
ctx := context.Background()
sys, l1Client := startFaultDisputeSystem(t)
t.Cleanup(sys.Close)
......@@ -154,6 +167,7 @@ func TestCannonDisputeGame(t *testing.T) {
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys.cfg.L1Deployments, l1Client)
game := disputeGameFactory.StartCannonGame(ctx, common.Hash{0xaa})
require.NotNil(t, game)
game.LogGameData(ctx)
game.StartChallenger(ctx, sys.RollupConfig, sys.L2GenesisCfg, sys.NodeEndpoint("l1"), sys.NodeEndpoint("sequencer"), "Challenger", func(c *config.Config) {
c.AgreeWithProposedOutput = true // Agree with the proposed output, so disagree with the root claim
......@@ -162,21 +176,33 @@ func TestCannonDisputeGame(t *testing.T) {
maxDepth := game.MaxDepth(ctx)
for claimCount := int64(1); claimCount < maxDepth; {
game.LogGameData(ctx)
claimCount++
// Wait for the challenger to counter
game.WaitForClaimCount(ctx, claimCount)
// Post our own counter to the latest challenger claim
if claimCount == test.defendAtClaim {
// Defend one claim so we don't wind up executing from the absolute pre-state
game.Defend(ctx, claimCount-1, common.Hash{byte(claimCount)})
} else {
game.Attack(ctx, claimCount-1, common.Hash{byte(claimCount)})
}
claimCount++
game.WaitForClaimCount(ctx, claimCount)
}
game.WaitForClaimAtMaxDepth(ctx, false)
game.LogGameData(ctx)
// Wait for the challenger to call step and counter our invalid claim
game.WaitForClaimAtMaxDepth(ctx, true)
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
require.NoError(t, utils.WaitNextBlock(ctx, l1Client))
game.WaitForGameStatus(ctx, disputegame.StatusChallengerWins)
game.LogGameData(ctx)
})
}
}
func startFaultDisputeSystem(t *testing.T) (*System, *ethclient.Client) {
......
......@@ -44,7 +44,7 @@
"l1GenesisBlockTimestamp": "0x64c811bf",
"l2GenesisRegolithTimeOffset": "0x0",
"faultGameAbsolutePrestate": "0x41c7ae758795765c6664a5d39bf63841c71ff191e9189522bad8ebff5d4eca98",
"faultGameMaxDepth": 31,
"faultGameMaxDepth": 30,
"faultGameMaxDuration": 300,
"systemConfigStartBlock": 0
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment