Commit 22444383 authored by Adrian Sutton's avatar Adrian Sutton Committed by GitHub

op-e2e: Add interop fault proofs actions test (#13672)

* op-service: Define the SuperRoot type

* op-program: Add chain ID to super chain output roots

* Rename

* op-e2e: Add interop fault proofs actions test

* op-program: Update to include chain ID
parent 604fd605
...@@ -5,13 +5,10 @@ import ( ...@@ -5,13 +5,10 @@ import (
"os" "os"
"time" "time"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/log" "github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/params"
altda "github.com/ethereum-optimism/optimism/op-alt-da" altda "github.com/ethereum-optimism/optimism/op-alt-da"
batcherFlags "github.com/ethereum-optimism/optimism/op-batcher/flags" batcherFlags "github.com/ethereum-optimism/optimism/op-batcher/flags"
"github.com/ethereum-optimism/optimism/op-chain-ops/devkeys" "github.com/ethereum-optimism/optimism/op-chain-ops/devkeys"
...@@ -30,6 +27,8 @@ import ( ...@@ -30,6 +27,8 @@ import (
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/syncnode" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/syncnode"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/frontend" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/frontend"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types" "github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
) )
const ( const (
...@@ -42,7 +41,7 @@ type Chain struct { ...@@ -42,7 +41,7 @@ type Chain struct {
ChainID types.ChainID ChainID types.ChainID
RollupCfg *rollup.Config RollupCfg *rollup.Config
ChainCfg *params.ChainConfig L2Genesis *core.Genesis
BatcherAddr common.Address BatcherAddr common.Address
Sequencer *helpers.L2Sequencer Sequencer *helpers.L2Sequencer
...@@ -242,7 +241,7 @@ func createL2Services( ...@@ -242,7 +241,7 @@ func createL2Services(
return &Chain{ return &Chain{
ChainID: types.ChainIDFromBig(output.Genesis.Config.ChainID), ChainID: types.ChainIDFromBig(output.Genesis.Config.ChainID),
RollupCfg: output.RollupCfg, RollupCfg: output.RollupCfg,
ChainCfg: output.Genesis.Config, L2Genesis: output.Genesis,
BatcherAddr: crypto.PubkeyToAddress(batcherKey.PublicKey), BatcherAddr: crypto.PubkeyToAddress(batcherKey.PublicKey),
Sequencer: seq, Sequencer: seq,
SequencerEngine: eng, SequencerEngine: eng,
......
package interop package interop
import ( import (
"context"
"log/slog"
"testing" "testing"
fpHelpers "github.com/ethereum-optimism/optimism/op-e2e/actions/proofs/helpers"
"github.com/ethereum-optimism/optimism/op-program/client/claim"
"github.com/ethereum-optimism/optimism/op-program/client/interop/types"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/rlp"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
"github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers"
...@@ -127,3 +134,243 @@ func TestFullInterop(gt *testing.T) { ...@@ -127,3 +134,243 @@ func TestFullInterop(gt *testing.T) {
require.Equal(t, head, status.SafeL2.ID()) require.Equal(t, head, status.SafeL2.ID())
require.Equal(t, head, status.FinalizedL2.ID()) require.Equal(t, head, status.FinalizedL2.ID())
} }
func TestInteropFaultProofs(gt *testing.T) {
t := helpers.NewDefaultTesting(gt)
is := SetupInterop(t)
actors := is.CreateActors()
// get both sequencers set up
actors.ChainA.Sequencer.ActL2PipelineFull(t)
actors.ChainB.Sequencer.ActL2PipelineFull(t)
// sync the supervisor, handle initial events emitted by the nodes
actors.ChainA.Sequencer.SyncSupervisor(t)
actors.ChainB.Sequencer.SyncSupervisor(t)
// No blocks yet
status := actors.ChainA.Sequencer.SyncStatus()
require.Equal(t, uint64(0), status.UnsafeL2.Number)
// sync chain A
actors.Supervisor.SyncEvents(t, actors.ChainA.ChainID)
actors.Supervisor.SyncCrossUnsafe(t, actors.ChainA.ChainID)
actors.Supervisor.SyncCrossSafe(t, actors.ChainA.ChainID)
// sync chain B
actors.Supervisor.SyncEvents(t, actors.ChainB.ChainID)
actors.Supervisor.SyncCrossUnsafe(t, actors.ChainB.ChainID)
actors.Supervisor.SyncCrossSafe(t, actors.ChainB.ChainID)
// Build L2 block on chain A
actors.ChainA.Sequencer.ActL2StartBlock(t)
actors.ChainA.Sequencer.ActL2EndBlock(t)
require.Equal(t, uint64(1), actors.ChainA.Sequencer.L2Unsafe().Number)
// Build L2 block on chain B
actors.ChainB.Sequencer.ActL2StartBlock(t)
actors.ChainB.Sequencer.ActL2EndBlock(t)
require.Equal(t, uint64(1), actors.ChainB.Sequencer.L2Unsafe().Number)
// Ingest the new unsafe-block events
actors.ChainA.Sequencer.SyncSupervisor(t)
actors.ChainB.Sequencer.SyncSupervisor(t)
// Verify as cross-unsafe with supervisor
actors.Supervisor.SyncEvents(t, actors.ChainA.ChainID)
actors.Supervisor.SyncEvents(t, actors.ChainB.ChainID)
actors.Supervisor.SyncCrossUnsafe(t, actors.ChainA.ChainID)
actors.Supervisor.SyncCrossUnsafe(t, actors.ChainB.ChainID)
actors.ChainA.Sequencer.AwaitSentCrossUnsafeUpdate(t, 1)
actors.ChainA.Sequencer.ActL2PipelineFull(t)
status = actors.ChainA.Sequencer.SyncStatus()
require.Equal(gt, uint64(1), status.UnsafeL2.Number)
require.Equal(gt, uint64(1), status.CrossUnsafeL2.Number)
actors.ChainB.Sequencer.AwaitSentCrossUnsafeUpdate(t, 1)
actors.ChainB.Sequencer.ActL2PipelineFull(t)
status = actors.ChainB.Sequencer.SyncStatus()
require.Equal(gt, uint64(1), status.UnsafeL2.Number)
require.Equal(gt, uint64(1), status.CrossUnsafeL2.Number)
// Submit the L2 blocks, sync the local-safe data
actors.ChainA.Batcher.ActSubmitAll(t)
actors.ChainB.Batcher.ActSubmitAll(t)
actors.L1Miner.ActL1StartBlock(12)(t)
actors.L1Miner.ActL1IncludeTx(actors.ChainA.BatcherAddr)(t)
actors.L1Miner.ActL1IncludeTx(actors.ChainB.BatcherAddr)(t)
actors.L1Miner.ActL1EndBlock(t)
// The node will exhaust L1 data,
// it needs the supervisor to see the L1 block first, and provide it to the node.
actors.ChainA.Sequencer.ActL2EventsUntil(t, event.Is[derive.ExhaustedL1Event], 100, false)
actors.ChainB.Sequencer.ActL2EventsUntil(t, event.Is[derive.ExhaustedL1Event], 100, false)
actors.ChainA.Sequencer.SyncSupervisor(t) // supervisor to react to exhaust-L1
actors.ChainB.Sequencer.SyncSupervisor(t) // supervisor to react to exhaust-L1
actors.ChainA.Sequencer.ActL2PipelineFull(t) // node to complete syncing to L1 head.
actors.ChainB.Sequencer.ActL2PipelineFull(t) // node to complete syncing to L1 head.
actors.ChainA.Sequencer.ActL1HeadSignal(t)
status = actors.ChainA.Sequencer.SyncStatus()
require.Equal(gt, uint64(1), status.LocalSafeL2.Number)
actors.ChainB.Sequencer.ActL1HeadSignal(t)
status = actors.ChainB.Sequencer.SyncStatus()
require.Equal(gt, uint64(1), status.LocalSafeL2.Number)
// Ingest the new local-safe event
actors.ChainA.Sequencer.SyncSupervisor(t)
actors.ChainB.Sequencer.SyncSupervisor(t)
// Cross-safe verify it
actors.Supervisor.SyncCrossSafe(t, actors.ChainA.ChainID)
actors.Supervisor.SyncCrossSafe(t, actors.ChainB.ChainID)
actors.ChainA.Sequencer.AwaitSentCrossSafeUpdate(t, 1)
actors.ChainA.Sequencer.ActL2PipelineFull(t)
status = actors.ChainA.Sequencer.SyncStatus()
require.Equal(gt, uint64(1), status.SafeL2.Number)
actors.ChainB.Sequencer.AwaitSentCrossSafeUpdate(t, 1)
actors.ChainB.Sequencer.ActL2PipelineFull(t)
status = actors.ChainB.Sequencer.SyncStatus()
require.Equal(gt, uint64(1), status.SafeL2.Number)
require.Equal(gt, uint64(1), actors.ChainA.Sequencer.L2Safe().Number)
require.Equal(gt, uint64(1), actors.ChainB.Sequencer.L2Safe().Number)
chainAClient := actors.ChainA.Sequencer.RollupClient()
chainBClient := actors.ChainB.Sequencer.RollupClient()
ctx := context.Background()
startTimestamp := actors.ChainA.RollupCfg.Genesis.L2Time
endTimestamp := startTimestamp + actors.ChainA.RollupCfg.BlockTime
source, err := NewSuperRootSource(ctx, chainAClient, chainBClient)
require.NoError(t, err)
start, err := source.CreateSuperRoot(ctx, startTimestamp)
require.NoError(t, err)
end, err := source.CreateSuperRoot(ctx, endTimestamp)
require.NoError(t, err)
serializeIntermediateRoot := func(root *types.TransitionState) []byte {
data, err := rlp.EncodeToBytes(root)
require.NoError(t, err)
return data
}
num, err := actors.ChainA.RollupCfg.TargetBlockNumber(endTimestamp)
require.NoError(t, err)
chain1End, err := chainAClient.OutputAtBlock(ctx, num)
require.NoError(t, err)
num, err = actors.ChainB.RollupCfg.TargetBlockNumber(endTimestamp)
require.NoError(t, err)
chain2End, err := chainBClient.OutputAtBlock(ctx, num)
require.NoError(t, err)
step1Expected := serializeIntermediateRoot(&types.TransitionState{
SuperRoot: start.Marshal(),
PendingProgress: []types.OptimisticBlock{
{BlockHash: chain1End.BlockRef.Hash, OutputRoot: chain1End.OutputRoot},
},
Step: 1,
})
step2Expected := serializeIntermediateRoot(&types.TransitionState{
SuperRoot: start.Marshal(),
PendingProgress: []types.OptimisticBlock{
{BlockHash: chain1End.BlockRef.Hash, OutputRoot: chain1End.OutputRoot},
{BlockHash: chain2End.BlockRef.Hash, OutputRoot: chain2End.OutputRoot},
},
Step: 2,
})
step3Expected := serializeIntermediateRoot(&types.TransitionState{
SuperRoot: start.Marshal(),
PendingProgress: []types.OptimisticBlock{
{BlockHash: chain1End.BlockRef.Hash, OutputRoot: chain1End.OutputRoot},
{BlockHash: chain2End.BlockRef.Hash, OutputRoot: chain2End.OutputRoot},
},
Step: 3,
})
tests := []*transitionTest{
{
name: "ClaimNoChange",
startTimestamp: startTimestamp,
agreedClaim: start.Marshal(),
disputedClaim: start.Marshal(),
expectValid: false,
},
{
name: "ClaimDirectToNextTimestamp",
startTimestamp: startTimestamp,
agreedClaim: start.Marshal(),
disputedClaim: end.Marshal(),
expectValid: false,
},
{
name: "FirstChainOptimisticBlock",
startTimestamp: startTimestamp,
agreedClaim: start.Marshal(),
disputedClaim: step1Expected,
expectValid: true,
skip: true,
},
{
name: "SecondChainOptimisticBlock",
startTimestamp: startTimestamp,
agreedClaim: step1Expected,
disputedClaim: step2Expected,
expectValid: true,
skip: true,
},
{
name: "PaddingStep",
startTimestamp: startTimestamp,
agreedClaim: step2Expected,
disputedClaim: step3Expected,
expectValid: true,
skip: true,
},
{
name: "Consolidate",
startTimestamp: startTimestamp,
agreedClaim: step3Expected,
disputedClaim: end.Marshal(),
expectValid: true,
skip: true,
},
}
for _, test := range tests {
test := test
gt.Run(test.name, func(gt *testing.T) {
t := helpers.NewDefaultTesting(gt)
if test.skip {
t.Skip("Not yet implemented")
return
}
logger := testlog.Logger(t, slog.LevelInfo)
checkResult := fpHelpers.ExpectNoError()
if !test.expectValid {
checkResult = fpHelpers.ExpectError(claim.ErrClaimNotValid)
}
fpHelpers.RunFaultProofProgram(
t,
logger,
actors.L1Miner,
actors.ChainA.Sequencer.L2Verifier,
actors.ChainA.SequencerEngine,
actors.ChainA.L2Genesis,
chain1End.BlockRef.Number,
checkResult,
)
})
}
}
type transitionTest struct {
name string
startTimestamp uint64
agreedClaim []byte
disputedClaim []byte
expectValid bool
skip bool
}
package interop
import (
"context"
"fmt"
"math/big"
"slices"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/eth"
)
type OutputRootSource interface {
OutputAtBlock(ctx context.Context, blockNum uint64) (*eth.OutputResponse, error)
RollupConfig(ctx context.Context) (*rollup.Config, error)
}
type chainInfo struct {
chainID *big.Int
source OutputRootSource
config *rollup.Config
}
// SuperRootSource is a testing helper to create a Super Root from a set of rollup clients
type SuperRootSource struct {
chains []*chainInfo
}
func NewSuperRootSource(ctx context.Context, sources ...OutputRootSource) (*SuperRootSource, error) {
chains := make([]*chainInfo, 0, len(sources))
for _, source := range sources {
config, err := source.RollupConfig(ctx)
if err != nil {
return nil, fmt.Errorf("failed to load rollup config: %w", err)
}
chainID := config.L2ChainID
chains = append(chains, &chainInfo{
chainID: chainID,
source: source,
config: config,
})
}
slices.SortFunc(chains, func(a, b *chainInfo) int {
return a.chainID.Cmp(b.chainID)
})
return &SuperRootSource{chains: chains}, nil
}
func (s *SuperRootSource) CreateSuperRoot(ctx context.Context, timestamp uint64) (*eth.SuperV1, error) {
chains := make([]eth.ChainIDAndOutput, len(s.chains))
for i, chain := range s.chains {
blockNum, err := chain.config.TargetBlockNumber(timestamp)
if err != nil {
return nil, err
}
output, err := chain.source.OutputAtBlock(ctx, blockNum)
if err != nil {
return nil, fmt.Errorf("failed to load output root for chain %v at block %v: %w", chain.chainID, blockNum, err)
}
chains[i] = eth.ChainIDAndOutput{ChainID: chain.chainID.Uint64(), Output: output.OutputRoot}
}
output := eth.SuperV1{
Timestamp: timestamp,
Chains: chains,
}
return &output, nil
}
package helpers package helpers
import ( import (
"context"
"math/rand" "math/rand"
"github.com/ethereum-optimism/optimism/op-chain-ops/genesis" "github.com/ethereum-optimism/optimism/op-chain-ops/genesis"
e2ecfg "github.com/ethereum-optimism/optimism/op-e2e/config" e2ecfg "github.com/ethereum-optimism/optimism/op-e2e/config"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum/go-ethereum/params"
altda "github.com/ethereum-optimism/optimism/op-alt-da" altda "github.com/ethereum-optimism/optimism/op-alt-da"
batcherFlags "github.com/ethereum-optimism/optimism/op-batcher/flags" batcherFlags "github.com/ethereum-optimism/optimism/op-batcher/flags"
"github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils" "github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/fakebeacon"
"github.com/ethereum-optimism/optimism/op-program/host"
hostcommon "github.com/ethereum-optimism/optimism/op-program/host/common"
"github.com/ethereum-optimism/optimism/op-program/host/config" "github.com/ethereum-optimism/optimism/op-program/host/config"
"github.com/ethereum-optimism/optimism/op-program/host/kvstore"
"github.com/ethereum-optimism/optimism/op-program/host/prefetcher"
hostTypes "github.com/ethereum-optimism/optimism/op-program/host/types" hostTypes "github.com/ethereum-optimism/optimism/op-program/host/types"
"github.com/ethereum-optimism/optimism/op-service/sources" "github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum-optimism/optimism/op-service/testlog" "github.com/ethereum-optimism/optimism/op-service/testlog"
...@@ -162,69 +158,7 @@ func WithL2BlockNumber(num uint64) FixtureInputParam { ...@@ -162,69 +158,7 @@ func WithL2BlockNumber(num uint64) FixtureInputParam {
} }
func (env *L2FaultProofEnv) RunFaultProofProgram(t helpers.Testing, l2ClaimBlockNum uint64, checkResult CheckResult, fixtureInputParams ...FixtureInputParam) { func (env *L2FaultProofEnv) RunFaultProofProgram(t helpers.Testing, l2ClaimBlockNum uint64, checkResult CheckResult, fixtureInputParams ...FixtureInputParam) {
// Fetch the pre and post output roots for the fault proof. RunFaultProofProgram(t, env.log, env.Miner, env.Sequencer.L2Verifier, env.Engine, env.Sd.L2Cfg, l2ClaimBlockNum, checkResult, fixtureInputParams...)
l2PreBlockNum := l2ClaimBlockNum - 1
if l2ClaimBlockNum == 0 {
// If we are at genesis, we assert that we don't move the chain at all.
l2PreBlockNum = 0
}
preRoot, err := env.Sequencer.RollupClient().OutputAtBlock(t.Ctx(), l2PreBlockNum)
require.NoError(t, err)
claimRoot, err := env.Sequencer.RollupClient().OutputAtBlock(t.Ctx(), l2ClaimBlockNum)
require.NoError(t, err)
l1Head := env.Miner.L1Chain().CurrentBlock()
fixtureInputs := &FixtureInputs{
L2BlockNumber: l2ClaimBlockNum,
L2Claim: common.Hash(claimRoot.OutputRoot),
L2Head: preRoot.BlockRef.Hash,
L2OutputRoot: common.Hash(preRoot.OutputRoot),
L2ChainID: env.Sd.RollupCfg.L2ChainID.Uint64(),
L1Head: l1Head.Hash(),
}
for _, apply := range fixtureInputParams {
apply(fixtureInputs)
}
// Run the fault proof program from the state transition from L2 block l2ClaimBlockNum - 1 -> l2ClaimBlockNum.
workDir := t.TempDir()
if IsKonaConfigured() {
fakeBeacon := fakebeacon.NewBeacon(
env.log,
env.Miner.BlobStore(),
env.Sd.L1Cfg.Timestamp,
12,
)
require.NoError(t, fakeBeacon.Start("127.0.0.1:0"))
defer fakeBeacon.Close()
err = RunKonaNative(t, workDir, env, env.Miner.HTTPEndpoint(), fakeBeacon.BeaconAddr(), env.Engine.HTTPEndpoint(), *fixtureInputs)
checkResult(t, err)
} else {
programCfg := NewOpProgramCfg(
t,
env,
fixtureInputs,
)
withInProcessPrefetcher := hostcommon.WithPrefetcher(func(ctx context.Context, logger log.Logger, kv kvstore.KV, cfg *config.Config) (hostcommon.Prefetcher, error) {
// Set up in-process L1 sources
l1Cl := env.Miner.L1Client(t, env.Sd.RollupCfg)
l1BlobFetcher := env.Miner.BlobSource()
// Set up in-process L2 source
l2ClCfg := sources.L2ClientDefaultConfig(env.Sd.RollupCfg, true)
l2RPC := env.Engine.RPCClient()
l2Client, err := hostcommon.NewL2Client(l2RPC, env.log, nil, &hostcommon.L2ClientConfig{L2ClientConfig: l2ClCfg, L2Head: cfg.L2Head})
require.NoError(t, err, "failed to create L2 client")
l2DebugCl := hostcommon.NewL2SourceWithClient(logger, l2Client, sources.NewDebugClient(l2RPC.CallContext))
executor := host.MakeProgramExecutor(env.log, programCfg)
return prefetcher.NewPrefetcher(logger, l1Cl, l1BlobFetcher, l2DebugCl, kv, env.Sd.L2Cfg.Config, executor), nil
})
err = hostcommon.FaultProofProgram(t.Ctx(), env.log, programCfg, withInProcessPrefetcher)
checkResult(t, err)
}
tryDumpTestFixture(t, err, t.Name(), env, *fixtureInputs, workDir)
} }
type TestParam func(p *e2eutils.TestParams) type TestParam func(p *e2eutils.TestParams)
...@@ -265,11 +199,12 @@ type OpProgramCfgParam func(p *config.Config) ...@@ -265,11 +199,12 @@ type OpProgramCfgParam func(p *config.Config)
func NewOpProgramCfg( func NewOpProgramCfg(
t helpers.Testing, t helpers.Testing,
env *L2FaultProofEnv, rollupCfg *rollup.Config,
l2Genesis *params.ChainConfig,
fi *FixtureInputs, fi *FixtureInputs,
params ...OpProgramCfgParam, params ...OpProgramCfgParam,
) *config.Config { ) *config.Config {
dfault := config.NewConfig(env.Sd.RollupCfg, env.Sd.L2Cfg.Config, fi.L1Head, fi.L2Head, fi.L2OutputRoot, fi.L2Claim, fi.L2BlockNumber) dfault := config.NewConfig(rollupCfg, l2Genesis, fi.L1Head, fi.L2Head, fi.L2OutputRoot, fi.L2Claim, fi.L2BlockNumber)
if dumpFixtures { if dumpFixtures {
dfault.DataDir = t.TempDir() dfault.DataDir = t.TempDir()
......
...@@ -11,8 +11,10 @@ import ( ...@@ -11,8 +11,10 @@ import (
"strings" "strings"
"github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-program/client/claim" "github.com/ethereum-optimism/optimism/op-program/client/claim"
"github.com/ethereum/go-ethereum/common" "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/naoina/toml" "github.com/naoina/toml"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -51,7 +53,8 @@ func tryDumpTestFixture( ...@@ -51,7 +53,8 @@ func tryDumpTestFixture(
t helpers.Testing, t helpers.Testing,
result error, result error,
name string, name string,
env *L2FaultProofEnv, rollupCfg *rollup.Config,
l2Genesis *core.Genesis,
inputs FixtureInputs, inputs FixtureInputs,
workDir string, workDir string,
) { ) {
...@@ -60,8 +63,6 @@ func tryDumpTestFixture( ...@@ -60,8 +63,6 @@ func tryDumpTestFixture(
} }
name = convertToKebabCase(name) name = convertToKebabCase(name)
rollupCfg := env.Sd.RollupCfg
l2Genesis := env.Sd.L2Cfg
var expectedStatus uint8 var expectedStatus uint8
if result == nil { if result == nil {
......
...@@ -12,6 +12,7 @@ import ( ...@@ -12,6 +12,7 @@ import (
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/utils"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm" "github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/vm"
"github.com/ethereum-optimism/optimism/op-e2e/actions/helpers" "github.com/ethereum-optimism/optimism/op-e2e/actions/helpers"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-program/client/claim" "github.com/ethereum-optimism/optimism/op-program/client/claim"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
...@@ -29,7 +30,7 @@ func IsKonaConfigured() bool { ...@@ -29,7 +30,7 @@ func IsKonaConfigured() bool {
func RunKonaNative( func RunKonaNative(
t helpers.Testing, t helpers.Testing,
workDir string, workDir string,
env *L2FaultProofEnv, rollupCfg *rollup.Config,
l1Rpc string, l1Rpc string,
l1BeaconRpc string, l1BeaconRpc string,
l2Rpc string, l2Rpc string,
...@@ -37,7 +38,7 @@ func RunKonaNative( ...@@ -37,7 +38,7 @@ func RunKonaNative(
) error { ) error {
// Write rollup config to tempdir. // Write rollup config to tempdir.
rollupConfigPath := filepath.Join(workDir, "rollup.json") rollupConfigPath := filepath.Join(workDir, "rollup.json")
ser, err := json.Marshal(env.Sd.RollupCfg) ser, err := json.Marshal(rollupCfg)
require.NoError(t, err) require.NoError(t, err)
require.NoError(t, os.WriteFile(rollupConfigPath, ser, fs.ModePerm)) require.NoError(t, os.WriteFile(rollupConfigPath, ser, fs.ModePerm))
......
package helpers
import (
"context"
"github.com/ethereum-optimism/optimism/op-e2e/actions/helpers"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/fakebeacon"
"github.com/ethereum-optimism/optimism/op-program/host"
hostcommon "github.com/ethereum-optimism/optimism/op-program/host/common"
"github.com/ethereum-optimism/optimism/op-program/host/config"
"github.com/ethereum-optimism/optimism/op-program/host/kvstore"
"github.com/ethereum-optimism/optimism/op-program/host/prefetcher"
"github.com/ethereum-optimism/optimism/op-service/sources"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)
type L1 interface {
}
type L2 interface {
RollupClient() *sources.RollupClient
}
func RunFaultProofProgram(t helpers.Testing, logger log.Logger, l1 *helpers.L1Miner, l2 *helpers.L2Verifier, l2Eng *helpers.L2Engine, l2ChainConfig *core.Genesis, l2ClaimBlockNum uint64, checkResult CheckResult, fixtureInputParams ...FixtureInputParam) {
// Fetch the pre and post output roots for the fault proof.
l2PreBlockNum := l2ClaimBlockNum - 1
if l2ClaimBlockNum == 0 {
// If we are at genesis, we assert that we don't move the chain at all.
l2PreBlockNum = 0
}
preRoot, err := l2.RollupClient().OutputAtBlock(t.Ctx(), l2PreBlockNum)
require.NoError(t, err)
claimRoot, err := l2.RollupClient().OutputAtBlock(t.Ctx(), l2ClaimBlockNum)
require.NoError(t, err)
l1Head := l1.L1Chain().CurrentBlock()
fixtureInputs := &FixtureInputs{
L2BlockNumber: l2ClaimBlockNum,
L2Claim: common.Hash(claimRoot.OutputRoot),
L2Head: preRoot.BlockRef.Hash,
L2OutputRoot: common.Hash(preRoot.OutputRoot),
L2ChainID: l2.RollupCfg.L2ChainID.Uint64(),
L1Head: l1Head.Hash(),
}
for _, apply := range fixtureInputParams {
apply(fixtureInputs)
}
// Run the fault proof program from the state transition from L2 block l2ClaimBlockNum - 1 -> l2ClaimBlockNum.
workDir := t.TempDir()
if IsKonaConfigured() {
fakeBeacon := fakebeacon.NewBeacon(
logger,
l1.BlobStore(),
l1.L1Chain().Genesis().Time(),
12,
)
require.NoError(t, fakeBeacon.Start("127.0.0.1:0"))
defer fakeBeacon.Close()
err := RunKonaNative(t, workDir, l2.RollupCfg, l1.HTTPEndpoint(), fakeBeacon.BeaconAddr(), l2Eng.HTTPEndpoint(), *fixtureInputs)
checkResult(t, err)
} else {
programCfg := NewOpProgramCfg(
t,
l2.RollupCfg,
l2ChainConfig.Config,
fixtureInputs,
)
withInProcessPrefetcher := hostcommon.WithPrefetcher(func(ctx context.Context, logger log.Logger, kv kvstore.KV, cfg *config.Config) (hostcommon.Prefetcher, error) {
// Set up in-process L1 sources
l1Cl := l1.L1Client(t, l2.RollupCfg)
l1BlobFetcher := l1.BlobSource()
// Set up in-process L2 source
l2ClCfg := sources.L2ClientDefaultConfig(l2.RollupCfg, true)
l2RPC := l2Eng.RPCClient()
l2Client, err := hostcommon.NewL2Client(l2RPC, logger, nil, &hostcommon.L2ClientConfig{L2ClientConfig: l2ClCfg, L2Head: cfg.L2Head})
require.NoError(t, err, "failed to create L2 client")
l2DebugCl := hostcommon.NewL2SourceWithClient(logger, l2Client, sources.NewDebugClient(l2RPC.CallContext))
executor := host.MakeProgramExecutor(logger, programCfg)
return prefetcher.NewPrefetcher(logger, l1Cl, l1BlobFetcher, l2DebugCl, kv, l2ChainConfig.Config, executor), nil
})
err = hostcommon.FaultProofProgram(t.Ctx(), logger, programCfg, withInProcessPrefetcher)
checkResult(t, err)
}
tryDumpTestFixture(t, err, t.Name(), l2.RollupCfg, l2ChainConfig, *fixtureInputs, workDir)
}
package types
import (
"fmt"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/rlp"
)
var (
IntermediateTransitionVersion = byte(255)
)
type OptimisticBlock struct {
BlockHash common.Hash
OutputRoot eth.Bytes32
}
type TransitionState struct {
SuperRoot []byte
PendingProgress []OptimisticBlock
Step uint64
}
func (t *TransitionState) String() string {
return fmt.Sprintf("{SuperRoot: %x, PendingProgress: %v, Step: %d}", t.SuperRoot, t.PendingProgress, t.Step)
}
func (i *TransitionState) Version() byte {
return IntermediateTransitionVersion
}
func (i *TransitionState) Marshal() ([]byte, error) {
rlpData, err := rlp.EncodeToBytes(i)
if err != nil {
panic(err)
}
return append([]byte{IntermediateTransitionVersion}, rlpData...), nil
}
func (i *TransitionState) Hash() (common.Hash, error) {
data, err := i.Marshal()
if err != nil {
return common.Hash{}, err
}
return crypto.Keccak256Hash(data), nil
}
func UnmarshalProofsState(data []byte) (*TransitionState, error) {
if len(data) == 0 {
return nil, eth.ErrInvalidSuperRoot
}
switch data[0] {
case IntermediateTransitionVersion:
return unmarshalTransitionSate(data)
case eth.SuperRootVersionV1:
return &TransitionState{SuperRoot: data}, nil
default:
return nil, eth.ErrInvalidSuperRootVersion
}
}
func unmarshalTransitionSate(data []byte) (*TransitionState, error) {
if len(data) == 0 {
return nil, eth.ErrInvalidSuperRoot
}
var state TransitionState
if err := rlp.DecodeBytes(data[1:], &state); err != nil {
return nil, err
}
return &state, nil
}
package types
import (
"testing"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum/go-ethereum/common"
"github.com/stretchr/testify/require"
)
func TestTransitionStateCodec(t *testing.T) {
t.Run("TransitionState", func(t *testing.T) {
superRoot := &eth.SuperV1{
Timestamp: 9842494,
Chains: []eth.ChainIDAndOutput{
{ChainID: 34, Output: eth.Bytes32{0x01}},
{ChainID: 35, Output: eth.Bytes32{0x02}},
},
}
state := &TransitionState{
SuperRoot: superRoot.Marshal(),
PendingProgress: []OptimisticBlock{
{BlockHash: common.Hash{0x05}, OutputRoot: eth.Bytes32{0x03}},
{BlockHash: common.Hash{0x06}, OutputRoot: eth.Bytes32{0x04}},
},
Step: 2,
}
data, err := state.Marshal()
require.NoError(t, err)
actual, err := UnmarshalProofsState(data)
require.NoError(t, err)
require.Equal(t, state, actual)
})
t.Run("SuperRoot", func(t *testing.T) {
superRoot := &eth.SuperV1{
Timestamp: 9842494,
Chains: []eth.ChainIDAndOutput{
{ChainID: 34, Output: eth.Bytes32{0x01}},
{ChainID: 35, Output: eth.Bytes32{0x02}},
},
}
expected := &TransitionState{
SuperRoot: superRoot.Marshal(),
}
data := superRoot.Marshal()
actual, err := UnmarshalProofsState(data)
require.NoError(t, err)
require.Equal(t, expected, actual)
})
}
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