Commit 9f93d78b authored by Adrian Sutton's avatar Adrian Sutton

op-challenger: Fix provision of pre-state, proof and oracle data from cannon

Combines GetPreimage and GetOracleData into GetStepData. GetStepData returns the data needed to execute the step at the specified trace index, so the state is the pre-state rather than matching the claim at the specified index. The proof data contains proofs for data accessed while executing the step and oracle data the pre-images required to execute the step. This matches up correctly with the data in the cannon proof files and what is required when calling the step function.
parent 5e7efe5a
......@@ -134,23 +134,19 @@ func (a *Agent) step(ctx context.Context, claim types.Claim, game types.Game) er
return nil
}
oracleData, err := a.solver.GetOracleData(ctx, claim)
if err != nil {
a.log.Debug("Failed to get oracle data", "err", err)
return nil
}
a.log.Info("Updating oracle data", "oracleKey", oracleData.OracleKey, "oracleData", oracleData.OracleData)
if err := a.updater.UpdateOracle(ctx, 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)
}
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("Performing step", "is_attack", step.IsAttack,
"depth", step.LeafClaim.Depth(), "index_at_depth", step.LeafClaim.IndexAtDepth(), "value", step.LeafClaim.Value)
callData := types.StepCallData{
......
......@@ -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]
......
......@@ -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,36 +147,62 @@ func TestChallengerCompleteDisputeGame(t *testing.T) {
func TestCannonDisputeGame(t *testing.T) {
InitParallel(t)
ctx := context.Background()
sys, l1Client := startFaultDisputeSystem(t)
t.Cleanup(sys.Close)
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys.cfg.L1Deployments, l1Client)
game := disputeGameFactory.StartCannonGame(ctx, common.Hash{0xaa})
require.NotNil(t, game)
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)
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
c.TxMgrConfig.PrivateKey = e2eutils.EncodePrivKeyToString(sys.cfg.Secrets.Alice)
})
ctx := context.Background()
sys, l1Client := startFaultDisputeSystem(t)
t.Cleanup(sys.Close)
maxDepth := game.MaxDepth(ctx)
for claimCount := int64(1); claimCount < maxDepth; {
claimCount++
// Wait for the challenger to counter
game.WaitForClaimCount(ctx, claimCount)
disputeGameFactory := disputegame.NewFactoryHelper(t, ctx, sys.cfg.L1Deployments, l1Client)
game := disputeGameFactory.StartCannonGame(ctx, common.Hash{0xaa})
require.NotNil(t, game)
game.LogGameData(ctx)
// Post our own counter to the latest challenger claim
game.Attack(ctx, claimCount-1, common.Hash{byte(claimCount)})
claimCount++
game.WaitForClaimCount(ctx, claimCount)
}
game.WaitForClaimAtMaxDepth(ctx, false)
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
c.TxMgrConfig.PrivateKey = e2eutils.EncodePrivKeyToString(sys.cfg.Secrets.Alice)
})
sys.TimeTravelClock.AdvanceTime(game.GameDuration(ctx))
require.NoError(t, utils.WaitNextBlock(ctx, l1Client))
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.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.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