Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 54 additions & 2 deletions op-e2e/actions/interop/proofs_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,19 +8,25 @@ import (
"reflect"
"testing"

altda "github.com/ethereum-optimism/optimism/op-alt-da"
"github.com/ethereum-optimism/optimism/op-challenger/game/fault/trace/super"
challengerTypes "github.com/ethereum-optimism/optimism/op-challenger/game/fault/types"
"github.com/ethereum-optimism/optimism/op-e2e/actions/helpers"
"github.com/ethereum-optimism/optimism/op-e2e/actions/interop/dsl"
fpHelpers "github.com/ethereum-optimism/optimism/op-e2e/actions/proofs/helpers"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/node/safedb"
sync2 "github.com/ethereum-optimism/optimism/op-node/rollup/sync"
"github.com/ethereum-optimism/optimism/op-program/client/claim"
"github.com/ethereum-optimism/optimism/op-program/client/interop"
"github.com/ethereum-optimism/optimism/op-program/client/interop/types"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/depset"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)

Expand Down Expand Up @@ -1391,15 +1397,61 @@ func WithInteropEnabled(t helpers.StatefulTesting, actors *dsl.InteropActors, de
f.DependencySet = depSet

for _, chain := range []*dsl.Chain{actors.ChainA, actors.ChainB} {
verifier, canonicalOnlyEngine := createVerifierWithOnlyCanonicalBlocks(t, actors.L1Miner, chain)
f.L2Sources = append(f.L2Sources, &fpHelpers.FaultProofProgramL2Source{
Node: chain.Sequencer.L2Verifier,
Engine: chain.SequencerEngine,
Node: verifier,
Engine: canonicalOnlyEngine,
ChainConfig: chain.L2Genesis.Config,
})
}
}
}

// createVerifierWithOnlyCanonicalBlocks creates a new L2Verifier and associated L2Engine that only has the canonical
// blocks from chain in its database. Non-canonical blocks, their world state, receipts and other data are not available
func createVerifierWithOnlyCanonicalBlocks(t helpers.StatefulTesting, l1Miner *helpers.L1Miner, chain *dsl.Chain) (*helpers.L2Verifier, *helpers.L2Engine) {
jwtPath := e2eutils.WriteDefaultJWT(t)
canonicalOnlyEngine := helpers.NewL2Engine(t, testlog.Logger(t, log.LvlInfo).New("role", "canonicalOnlyEngine"), chain.L2Genesis, jwtPath)
head := chain.Sequencer.L2Unsafe()
for i := uint64(1); i <= head.Number; i++ {
block, err := chain.SequencerEngine.EthClient().BlockByNumber(t.Ctx(), new(big.Int).SetUint64(i))
require.NoErrorf(t, err, "failed to get block by number %v", i)
envelope, err := eth.BlockAsPayloadEnv(block, chain.L2Genesis.Config)
require.NoErrorf(t, err, "could not convert block %v to payload envelope")
result, err := canonicalOnlyEngine.EngineApi.NewPayloadV4(t.Ctx(), envelope.ExecutionPayload, []common.Hash{}, envelope.ParentBeaconBlockRoot, []hexutil.Bytes{})
require.NoErrorf(t, err, "could not import payload for block %v", i)
require.Equal(t, eth.ExecutionValid, result.Status)
}
fcuResult, err := canonicalOnlyEngine.EngineApi.ForkchoiceUpdatedV3(t.Ctx(), &eth.ForkchoiceState{
HeadBlockHash: head.Hash,
SafeBlockHash: head.Hash,
FinalizedBlockHash: head.Hash,
}, nil)
require.NoErrorf(t, err, "could not update fork choice for block %v", head.Hash)
require.Equal(t, eth.ExecutionValid, fcuResult.PayloadStatus.Status)

// Verify chain matches exactly
for i := uint64(0); i <= head.Number; i++ {
blockNum := new(big.Int).SetUint64(i)
expected, err := chain.SequencerEngine.EthClient().BlockByNumber(t.Ctx(), blockNum)
require.NoErrorf(t, err, "failed to get original block by number %v", i)
actual, err := canonicalOnlyEngine.EthClient().BlockByNumber(t.Ctx(), blockNum)
require.NoErrorf(t, err, "failed to get canonical-only block by number %v", i)
require.Equalf(t, expected.Hash(), actual.Hash(), "block %v does not match", i)
}

verifier := helpers.NewL2Verifier(t,
testlog.Logger(t, log.LvlInfo).New("role", "canonicalOnlyVerifier"),
l1Miner.L1Client(t, chain.RollupCfg),
l1Miner.BlobSource(),
altda.Disabled,
canonicalOnlyEngine.EngineClient(t, chain.RollupCfg),
chain.RollupCfg,
&sync2.Config{},
safedb.Disabled)
return verifier, canonicalOnlyEngine
}

func assertTime(t helpers.Testing, chain *dsl.Chain, unsafe, crossUnsafe, localSafe, safe uint64) {
start := chain.L2Genesis.Timestamp
status := chain.Sequencer.SyncStatus()
Expand Down
25 changes: 13 additions & 12 deletions op-program/client/interop/consolidate.go
Original file line number Diff line number Diff line change
Expand Up @@ -164,18 +164,7 @@ func singleRoundConsolidation(
continue
}

agreedOutput := l2PreimageOracle.OutputByRoot(common.Hash(chain.Output), chain.ChainID)
agreedOutputV0, ok := agreedOutput.(*eth.OutputV0)
if !ok {
return fmt.Errorf("%w: version: %d", l2.ErrUnsupportedL2Output, agreedOutput.Version())
}
agreedBlockHash := common.Hash(agreedOutputV0.BlockHash)

progress := consolidateState.PendingProgress[i]
// It's possible that the optimistic block is not canonical.
// So we use the blockDataByHash hint to trigger a block rebuild to ensure that the block data, including receipts, are available.
_ = l2PreimageOracle.BlockDataByHash(agreedBlockHash, progress.BlockHash, chain.ChainID)

optimisticBlock, _ := l2PreimageOracle.ReceiptsByBlockHash(progress.BlockHash, chain.ChainID)

candidate := supervisortypes.BlockSeal{
Expand Down Expand Up @@ -273,7 +262,10 @@ func newConsolidateCheckDeps(
progress := transitionState.PendingProgress[i]
// This is the optimistic head. It's OK if it's replaced by a deposits-only block.
// Because by then the replacement block won't be used for hazard checks.
head := oracle.BlockByHash(progress.BlockHash, chain.ChainID)
head, err := fetchOptimisticBlock(oracle, progress.BlockHash, chain)
if err != nil {
return nil, fmt.Errorf("failed to fetch optimistic block for chain %v: %w", chain.ChainID, err)
}
blockByHash := func(hash common.Hash) *ethtypes.Block {
return oracle.BlockByHash(hash, chain.ChainID)
}
Expand All @@ -293,6 +285,15 @@ func newConsolidateCheckDeps(
}, nil
}

func fetchOptimisticBlock(oracle l2.Oracle, blockHash common.Hash, chain eth.ChainIDAndOutput) (*ethtypes.Block, error) {
agreedOutput := oracle.OutputByRoot(common.Hash(chain.Output), chain.ChainID)
agreedOutputV0, ok := agreedOutput.(*eth.OutputV0)
if !ok {
return nil, fmt.Errorf("%w: version: %d", l2.ErrUnsupportedL2Output, agreedOutput.Version())
}
return oracle.BlockDataByHash(agreedOutputV0.BlockHash, blockHash, chain.ChainID), nil
}

func (d *consolidateCheckDeps) Contains(chain eth.ChainID, query supervisortypes.ContainsQuery) (includedIn supervisortypes.BlockSeal, err error) {
// We can assume the oracle has the block the executing message is in
block, err := d.CanonBlockByNumber(d.oracle, query.BlockNum, chain)
Expand Down
8 changes: 3 additions & 5 deletions op-program/client/interop/interop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -474,10 +474,6 @@ func runConsolidationTestCase(t *testing.T, testCase consolidationTestCase) {

l2PreimageOracle.Outputs[common.Hash(agreedSuperRoot.Chains[0].Output)] = createOutput(block1A.Hash())
l2PreimageOracle.Outputs[common.Hash(agreedSuperRoot.Chains[1].Output)] = createOutput(block1B.Hash())
l2PreimageOracle.BlockData = map[common.Hash]*gethTypes.Block{
block2A.Hash(): block2A,
block2B.Hash(): block2B,
}
l2PreimageOracle.Blocks[block1A.Hash()] = block1A
l2PreimageOracle.Blocks[block2A.Hash()] = block2A
l2PreimageOracle.Blocks[block2B.Hash()] = block2B
Expand All @@ -503,7 +499,6 @@ func runConsolidationTestCase(t *testing.T, testCase consolidationTestCase) {
finalRoots[chainIndexToReplace] = depositsOnlyOutputRoot
// stub the preimages in the replacement block
l2PreimageOracle.Blocks[depositsOnlyBlock.Hash()] = depositsOnlyBlock
l2PreimageOracle.BlockData[depositsOnlyBlock.Hash()] = depositsOnlyBlock
l2PreimageOracle.Outputs[common.Hash(depositsOnlyOutputRoot)] = depositsOnlyOutput
l2PreimageOracle.Receipts[depositsOnlyBlock.Hash()] = depositsOnlyBlockReceipts
}
Expand Down Expand Up @@ -645,6 +640,9 @@ func TestHazardSet_ExpiredMessageShortCircuitsInclusionCheck(t *testing.T) {
l2PreimageOracle.Blocks[block2B.Hash()] = block2B
l2PreimageOracle.Receipts[block2A.Hash()] = block2AReceipts
l2PreimageOracle.Receipts[block2B.Hash()] = block2BReceipts
for _, chain := range agreedSuperRoot.Chains {
l2PreimageOracle.Outputs[common.Hash(chain.Output)] = &eth.OutputV0{}
}

consolidateState := newConsolidateState(transitionState)
consolidateDeps, err := newConsolidateCheckDeps(configSource.depset, configSource, transitionState, agreedSuperRoot.Chains, l2PreimageOracle, consolidateState)
Expand Down
4 changes: 1 addition & 3 deletions op-program/client/l2/test/stub_oracle.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ type stateOracle interface {
type StubBlockOracle struct {
t *testing.T
Blocks map[common.Hash]*gethTypes.Block
BlockData map[common.Hash]*gethTypes.Block
Receipts map[common.Hash]gethTypes.Receipts
Outputs map[common.Hash]eth.Output
TransitionStates map[common.Hash]*interopTypes.TransitionState
Expand All @@ -35,7 +34,6 @@ func NewStubOracle(t *testing.T) (*StubBlockOracle, *StubStateOracle) {
blockOracle := StubBlockOracle{
t: t,
Blocks: make(map[common.Hash]*gethTypes.Block),
BlockData: make(map[common.Hash]*gethTypes.Block),
Outputs: make(map[common.Hash]eth.Output),
TransitionStates: make(map[common.Hash]*interopTypes.TransitionState),
Receipts: make(map[common.Hash]gethTypes.Receipts),
Expand Down Expand Up @@ -89,7 +87,7 @@ func (o StubBlockOracle) Hinter() l2Types.OracleHinter {
}

func (o StubBlockOracle) BlockDataByHash(agreedBlockHash, blockHash common.Hash, chainID eth.ChainID) *gethTypes.Block {
block, ok := o.BlockData[blockHash]
block, ok := o.Blocks[blockHash]
if !ok {
o.t.Fatalf("requested unknown block %s", blockHash)
}
Expand Down
4 changes: 4 additions & 0 deletions op-program/client/preinterop.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,5 +34,9 @@ func RunPreInteropProgram(
if err != nil {
return err
}
if opts.SkipValidation {
logger.Info("Validation skipped", "blockHash", result.BlockHash, "outputRoot", result.OutputRoot)
return nil
}
return claim.ValidateClaim(logger, eth.Bytes32(bootInfo.L2Claim), result.OutputRoot)
}
13 changes: 7 additions & 6 deletions op-program/client/program.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ import (
var errInvalidConfig = errors.New("invalid config")

type Config struct {
SkipValidation bool
InteropEnabled bool
DB l2.KeyValueStore
StoreBlockData bool
SkipValidation bool
InteropEnabled bool
ForceHintChainID bool
DB l2.KeyValueStore
StoreBlockData bool
}

// Main executes the client program in a detached context and exits the current process.
Expand Down Expand Up @@ -63,7 +64,7 @@ func RunProgram(logger log.Logger, preimageOracle io.ReadWriter, preimageHinter
pClient := preimage.NewOracleClient(preimageOracle)
hClient := preimage.NewHintWriter(preimageHinter)
l1PreimageOracle := l1.NewCachingOracle(l1.NewPreimageOracle(pClient, hClient))
l2PreimageOracle := l2.NewCachingOracle(l2.NewPreimageOracle(pClient, hClient, cfg.InteropEnabled))
l2PreimageOracle := l2.NewCachingOracle(l2.NewPreimageOracle(pClient, hClient, cfg.InteropEnabled || cfg.ForceHintChainID))

if cfg.InteropEnabled {
bootInfo := boot.BootstrapInterop(pClient)
Expand All @@ -73,6 +74,6 @@ func RunProgram(logger log.Logger, preimageOracle io.ReadWriter, preimageHinter
return fmt.Errorf("%w: db config is required", errInvalidConfig)
}
bootInfo := boot.NewBootstrapClient(pClient).BootInfo()
derivationOptions := tasks.DerivationOptions{StoreBlockData: cfg.StoreBlockData}
derivationOptions := tasks.DerivationOptions{StoreBlockData: cfg.StoreBlockData, SkipValidation: cfg.SkipValidation}
return RunPreInteropProgram(logger, bootInfo, l1PreimageOracle, l2PreimageOracle, cfg.DB, derivationOptions)
}
3 changes: 3 additions & 0 deletions op-program/client/tasks/derive.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ type DerivationOptions struct {
// StoreBlockData controls whether block data, including intermediate trie nodes from transactions and receipts
// of the derived block should be stored in the l2.KeyValueStore.
StoreBlockData bool

// SkipValidation controls whether the claim is validated after the block is derived.
SkipValidation bool
}

// RunDerivation executes the L2 state transition, given a minimal interface to retrieve data.
Expand Down
20 changes: 20 additions & 0 deletions op-program/compatibility-test/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Compatiblity Test Baseline

These baselines are used as part of the `analyze-op-program-client` task to check op-program for any unsupported
opcodes or syscalls.

## Simplifying `vm-compat` Output

When the analysis job fails it prints JSON output for all new findings. To format these nicely and remove the `line`,
`file` and `absPath` fields to match the existing baseline, use `jq`:

```shell
pbpaste | jq 'walk(if type == "object" and has("line") then del(.line) else . end | if type == "object" and has("absPath") then del(.absPath) else . end | if type == "object" and has("file") then del(.file) else . end)' | pbcopy
```

`pbpaste` and `pbcopy` are MacOS specific commands. They make it easy to copy the output from the CI results, run that
command and the formatted result is left on the clipboard ready to be pasted in. The `jq` command itself will work fine
on Linux.

Since these fields are ignored by `vm-compat` (to reduce false positives when line numbers change), it simplifies the
diff substantially to exclude them from the committed baseline file.
Loading