Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions mise.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ svm-rs = "0.5.19"

# Go dependencies
"go:github.com/ethereum/go-ethereum/cmd/abigen" = "1.15.10"
"go:github.com/ethereum/go-ethereum/cmd/geth" = "1.16.4" # Osaka release.
"go:gotest.tools/gotestsum" = "1.12.1"
"go:github.com/vektra/mockery/v2" = "2.46.0"
"go:github.com/golangci/golangci-lint/cmd/golangci-lint" = "1.64.8"
Expand Down
14 changes: 12 additions & 2 deletions op-acceptance-tests/tests/interop/loadtest/schedule.go
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,12 @@ type Spammer interface {
Spam(devtest.T) error
}

type SpammerFunc func(t devtest.T) error

func (s SpammerFunc) Spam(t devtest.T) error {
return s(t)
}

// Schedule schedules a Spammer. It determines how often to spam and when to stop.
type Schedule interface {
Run(devtest.T, Spammer)
Expand Down Expand Up @@ -326,12 +332,16 @@ func setupAIMD(t devtest.T, blockTime time.Duration, aimdOpts ...AIMDOption) *AI
t.Require().NoError(err)
}
aimd := NewAIMD(targetMessagePassesPerBlock, blockTime, aimdOpts...)
ctx, cancel := context.WithCancel(t.Ctx())
var wg sync.WaitGroup
t.Cleanup(wg.Wait)
t.Cleanup(func() {
cancel()
wg.Wait()
})
wg.Add(1)
go func() {
defer wg.Done()
aimd.Start(t.Ctx())
aimd.Start(ctx)
}()
return aimd
}
Expand Down
214 changes: 214 additions & 0 deletions op-acceptance-tests/tests/osaka/osaka_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package osaka

import (
"bytes"
"context"
"crypto/rand"
"fmt"
"math/big"
"os"
"os/exec"
"strings"
"sync"
"testing"
"time"

"github.com/ethereum-optimism/optimism/op-acceptance-tests/tests/interop/loadtest"
"github.com/ethereum-optimism/optimism/op-batcher/batcher"
"github.com/ethereum-optimism/optimism/op-batcher/flags"
"github.com/ethereum-optimism/optimism/op-chain-ops/devkeys"
"github.com/ethereum-optimism/optimism/op-devstack/devtest"
"github.com/ethereum-optimism/optimism/op-devstack/presets"
"github.com/ethereum-optimism/optimism/op-devstack/stack"
"github.com/ethereum-optimism/optimism/op-devstack/sysgo"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils/intentbuilder"
"github.com/ethereum-optimism/optimism/op-node/rollup/derive"
"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/txinclude"
"github.com/ethereum-optimism/optimism/op-service/txplan"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/params"
)

// configureDevstackEnvVars sets the appropriate env vars to use a mise-installed geth binary for
// the L1 EL. This is useful in Osaka acceptance tests since op-geth does not include full Osaka
// support. This is meant to run before presets.DoMain in a TestMain function. It will log to
// stdout. ResetDevstackEnvVars should be used to reset the environment variables when TestMain
// exits.
//
// Note that this is a no-op if either [sysgo.DevstackL1ELKindVar] or [sysgo.GethExecPathEnvVar]
// are set.
//
// The returned callback resets any modified environment variables.
func configureDevstackEnvVars() func() {
if _, ok := os.LookupEnv(sysgo.DevstackL1ELKindEnvVar); ok {
return func() {}
}
if _, ok := os.LookupEnv(sysgo.GethExecPathEnvVar); ok {
return func() {}
}

cmd := exec.Command("mise", "which", "geth")
buf := bytes.NewBuffer([]byte{})
cmd.Stdout = buf
if err := cmd.Run(); err != nil {
fmt.Printf("Failed to find mise-installed geth: %v\n", err)
return func() {}
}
execPath := strings.TrimSpace(buf.String())
fmt.Println("Found mise-installed geth:", execPath)
_ = os.Setenv(sysgo.GethExecPathEnvVar, execPath)
_ = os.Setenv(sysgo.DevstackL1ELKindEnvVar, "geth")
return func() {
_ = os.Unsetenv(sysgo.GethExecPathEnvVar)
_ = os.Unsetenv(sysgo.DevstackL1ELKindEnvVar)
}
}

func TestMain(m *testing.M) {
resetEnvVars := configureDevstackEnvVars()
defer resetEnvVars()

presets.DoMain(m, stack.MakeCommon(stack.Combine[*sysgo.Orchestrator](
sysgo.DefaultMinimalSystem(&sysgo.DefaultMinimalSystemIDs{}),
sysgo.WithDeployerOptions(func(_ devtest.P, _ devkeys.Keys, builder intentbuilder.Builder) {
_, l1Config := builder.WithL1(sysgo.DefaultL1ID)
l1Config.WithOsakaOffset(0)
l1Config.WithBPO1Offset(0)
l1Config.WithL1BlobSchedule(&params.BlobScheduleConfig{
Cancun: params.DefaultCancunBlobConfig,
Osaka: params.DefaultOsakaBlobConfig,
Prague: params.DefaultPragueBlobConfig,
BPO1: params.DefaultBPO1BlobConfig,
})
}),
sysgo.WithBatcherOption(func(_ stack.L2BatcherID, cfg *batcher.CLIConfig) {
cfg.DataAvailabilityType = flags.BlobsType
}),
)))
}

func TestBatcherUsesNewSidecarFormatAfterOsaka(gt *testing.T) {
t := devtest.SerialT(gt)
sys := presets.NewMinimal(t)
t.Log("Waiting for Osaka to activate")
t.Require().NotNil(sys.L1Network.Escape().ChainConfig().OsakaTime)
sys.L1EL.WaitForTime(*sys.L1Network.Escape().ChainConfig().OsakaTime)
t.Log("Osaka activated")

// 1. Wait for the sequencer to build a block after Osaka is activated. This avoids a race
// condition where the unsafe head has been posted as part of a blob, but has not been
// marked as "safe" yet.
sys.L2EL.WaitForBlock()

// 2. Wait for the batcher to include target in a batch and post it to L1. Because the batch is
// posted after Osaka has activated, it means the batcher must have successfully used the
// new format.
target := sys.L2EL.BlockRefByLabel(eth.Unsafe)
blockTime := time.Duration(sys.L2Chain.Escape().RollupConfig().BlockTime) * time.Second
for range time.Tick(blockTime) {
if sys.L2EL.BlockRefByLabel(eth.Safe).Number >= target.Number {
// If the safe head is ahead of the target height and the target block is part of the
// canonical chain, then the target block is safe.
_, err := sys.L2EL.Escape().EthClient().BlockRefByHash(t.Ctx(), target.Hash)
t.Require().NoError(err)
return
}
}
}

func TestBlobBaseFeeIsCorrectAfterBPOFork(gt *testing.T) {
t := devtest.SerialT(gt)
sys := presets.NewMinimal(t)
t.Log("Waiting for BPO1 to activate")
t.Require().NotNil(sys.L1Network.Escape().ChainConfig().BPO1Time)
sys.L1EL.WaitForTime(*sys.L1Network.Escape().ChainConfig().BPO1Time)
t.Log("BPO1 activated")

sys.L1EL.WaitForBlock()
l1BlockTime := sys.L1EL.EstimateBlockTime()
l1ChainConfig := sys.L1Network.Escape().ChainConfig()

spamBlobs(t, sys) // Raise the blob base fee to make blob parameter changes visible.

// Wait for the blob base fee to rise above 1 so the blob parameter changes will be visible.
for range time.Tick(l1BlockTime) {
info, _, err := sys.L1EL.EthClient().InfoAndTxsByLabel(t.Ctx(), eth.Unsafe)
t.Require().NoError(err)
if calcBlobBaseFee(l1ChainConfig, info).Cmp(big.NewInt(1)) > 0 {
break
}
t.Logf("Waiting for blob base fee to rise above 1")
}

l2UnsafeRef := sys.L2CL.SyncStatus().UnsafeL2

// Get the L1 blob base fee.
l1OriginInfo, err := sys.L1EL.EthClient().InfoByHash(t.Ctx(), l2UnsafeRef.L1Origin.Hash)
t.Require().NoError(err)
l1BlobBaseFee := calcBlobBaseFee(l1ChainConfig, l1OriginInfo)

// Get the L2 blob base fee from the system deposit tx.
info, txs, err := sys.L2EL.Escape().EthClient().InfoAndTxsByHash(t.Ctx(), l2UnsafeRef.Hash)
t.Require().NoError(err)
blockInfo, err := derive.L1BlockInfoFromBytes(sys.L2Chain.Escape().RollupConfig(), info.Time(), txs[0].Data())
t.Require().NoError(err)
l2BlobBaseFee := blockInfo.BlobBaseFee

t.Require().Equal(l1BlobBaseFee, l2BlobBaseFee)
}

func spamBlobs(t devtest.T, sys *presets.Minimal) {
l1BlockTime := sys.L1EL.EstimateBlockTime()
l1ChainConfig := sys.L1Network.Escape().ChainConfig()

eoa := sys.FunderL1.NewFundedEOA(eth.OneEther.Mul(5))
signer := txinclude.NewPkSigner(eoa.Key().Priv(), sys.L1Network.ChainID().ToBig())
l1ETHClient := sys.L1EL.EthClient()
syncEOA := loadtest.NewSyncEOA(txinclude.NewPersistent(signer, struct {
*txinclude.Monitor
*txinclude.Resubmitter
}{
txinclude.NewMonitor(l1ETHClient, l1BlockTime),
txinclude.NewResubmitter(l1ETHClient, l1BlockTime),
}), eoa.Plan())

var blob eth.Blob
_, err := rand.Read(blob[:])
t.Require().NoError(err)
// get the field-elements into a valid range
for i := range 4096 {
blob[32*i] &= 0b0011_1111
}

const maxBlobTxsPerAccountInMempool = 16 // Private policy param in geth.
spammer := loadtest.SpammerFunc(func(t devtest.T) error {
_, err := syncEOA.Include(t, txplan.WithBlobs([]*eth.Blob{&blob}, l1ChainConfig), txplan.WithTo(&common.Address{}))
return err
})
txsPerSlot := min(l1ChainConfig.BlobScheduleConfig.BPO1.Max*3/4, maxBlobTxsPerAccountInMempool)
schedule := loadtest.NewConstant(l1BlockTime, loadtest.WithBaseRPS(uint64(txsPerSlot)))

ctx, cancel := context.WithCancel(t.Ctx())
var wg sync.WaitGroup
t.Cleanup(func() {
cancel()
wg.Wait()
})
wg.Add(1)
go func() {
defer wg.Done()
schedule.Run(t.WithCtx(ctx), spammer)
}()
}

func calcBlobBaseFee(cfg *params.ChainConfig, info eth.BlockInfo) *big.Int {
return eip4844.CalcBlobFee(cfg, &types.Header{
// It's unfortunate that we can't build a proper header from a BlockInfo.
// We do our best to work around deficiencies in the BlockInfo implementation here.
Time: info.Time(),
ExcessBlobGas: info.ExcessBlobGas(),
})
}
3 changes: 3 additions & 0 deletions op-deployer/pkg/deployer/pipeline/seal_l1_dev_genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ func SealL1DevGenesis(env *Env, intent *state.Intent, st *state.State) error {
},
L1ChainID: eth.ChainIDFromUInt64(intent.L1ChainID),
L1PragueTimeOffset: l1DevParams.PragueTimeOffset,
L1OsakaTimeOffset: l1DevParams.OsakaTimeOffset,
L1BPO1TimeOffset: l1DevParams.BPO1TimeOffset,
BlobScheduleConfig: l1DevParams.BlobSchedule,
})
if err != nil {
return fmt.Errorf("failed to create dev L1 genesis template: %w", err)
Expand Down
11 changes: 11 additions & 0 deletions op-deployer/pkg/deployer/state/intent.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (

"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/params"

"github.com/ethereum-optimism/optimism/op-chain-ops/addresses"
"github.com/ethereum-optimism/optimism/op-deployer/pkg/deployer/artifacts"
Expand Down Expand Up @@ -55,6 +56,16 @@ type L1DevGenesisParams struct {
// PragueTimeOffset configures Prague (aka Pectra) to be activated at the given time after L1 dev genesis time.
PragueTimeOffset *uint64 `json:"pragueTimeOffset" toml:"pragueTimeOffset"`

// OsakaTimeOffset configures Osaka (the EL changes in the Fusaka Ethereum fork) to be
// activated at the given time after L1 dev genesis time.
OsakaTimeOffset *uint64 `json:"osakaTimeOffset" toml:"osakaTimeOffset"`

// BPO1TimeOffset configures the BPO1 fork to be activated at the given time after L1 dev
// genesis time.
BPO1TimeOffset *uint64 `json:"bpo1TimeOffset" toml:"bpo1TimeOffset"`

BlobSchedule *params.BlobScheduleConfig `json:"blobSchedule"`

// Prefund is a map of addresses to balances (in wei), to prefund in the L1 dev genesis state.
// This is independent of the "Prefund" functionality that may fund a default 20 test accounts.
Prefund map[common.Address]*hexutil.U256 `json:"prefund" toml:"prefund"`
Expand Down
12 changes: 12 additions & 0 deletions op-devstack/dsl/el.go
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,18 @@ func (el *elNode) waitForNextBlock(blocksFromNow uint64) eth.BlockRef {
return newRef
}

// WaitForTime waits until the chain has reached or surpassed the given timestamp.
func (el *elNode) WaitForTime(timestamp uint64) eth.BlockRef {
for range time.Tick(500 * time.Millisecond) {
ref, err := el.inner.EthClient().BlockRefByLabel(el.ctx, eth.Unsafe)
el.require.NoError(err)
if ref.Time >= timestamp {
return ref
}
}
return eth.BlockRef{} // Should never be reached.
}

func (el *elNode) stackEL() stack.ELNode {
return el.inner
}
Expand Down
4 changes: 4 additions & 0 deletions op-devstack/sysgo/engine_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ func (e *engineClient) GetPayloadV4(id engine.PayloadID) (*engine.ExecutionPaylo
return e.getPayload(id, "engine_getPayloadV4")
}

func (e *engineClient) GetPayloadV5(id engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error) {
return e.getPayload(id, "engine_getPayloadV5")
}

func (e *engineClient) NewPayloadV2(data engine.ExecutableData) (engine.PayloadStatusV1, error) {
var result engine.PayloadStatusV1
if err := e.inner.CallContext(context.Background(), &result, "engine_newPayloadV2", data); err != nil {
Expand Down
7 changes: 6 additions & 1 deletion op-devstack/sysgo/l1_nodes_subprocess.go
Original file line number Diff line number Diff line change
Expand Up @@ -182,8 +182,13 @@ func WithL1NodesSubprocess(id stack.L1ELNodeID, clID stack.L1CLNodeID) stack.Opt
args := []string{
"--log.format", "json",
"--datadir", dataDirPath,
"--ws", "--ws.addr", "127.0.0.1", "--ws.port", "0",
"--ws", "--ws.addr", "127.0.0.1", "--ws.port", "0", "--ws.origins", "*", "--ws.api", "admin,debug,eth,net,txpool",
"--authrpc.addr", "127.0.0.1", "--authrpc.port", "0", "--authrpc.jwtsecret", jwtPath,
"--ipcdisable",
"--nodiscover",
"--verbosity", "5",
"--miner.recommit", "2s",
"--gcmode", "archive",
}

l1EL := &ExternalL1Geth{
Expand Down
11 changes: 8 additions & 3 deletions op-e2e/e2eutils/geth/fakepos.go
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ type EngineAPI interface {
ForkchoiceUpdatedV3(engine.ForkchoiceStateV1, *engine.PayloadAttributes) (engine.ForkChoiceResponse, error)
ForkchoiceUpdatedV2(engine.ForkchoiceStateV1, *engine.PayloadAttributes) (engine.ForkChoiceResponse, error)

GetPayloadV5(engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error)
GetPayloadV4(engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error)
GetPayloadV3(engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error)
GetPayloadV2(engine.PayloadID) (*engine.ExecutionPayloadEnvelope, error)
Expand Down Expand Up @@ -157,8 +158,10 @@ func (f *FakePoS) Start() error {
Withdrawals: withdrawals,
}
parentBeaconBlockRoot := f.FakeBeaconBlockRoot(head.Time) // parent beacon block root
isCancun := f.config.IsCancun(new(big.Int).SetUint64(head.Number.Uint64()+1), newBlockTime)
isPrague := f.config.IsPrague(new(big.Int).SetUint64(head.Number.Uint64()+1), newBlockTime)
nextHeight := new(big.Int).SetUint64(head.Number.Uint64() + 1)
isCancun := f.config.IsCancun(nextHeight, newBlockTime)
isPrague := f.config.IsPrague(nextHeight, newBlockTime)
isOsaka := f.config.IsOsaka(nextHeight, newBlockTime)
if isCancun {
attrs.BeaconRoot = &parentBeaconBlockRoot
}
Expand Down Expand Up @@ -192,7 +195,9 @@ func (f *FakePoS) Start() error {
return nil
}
var envelope *engine.ExecutionPayloadEnvelope
if isPrague {
if isOsaka {
envelope, err = f.engineAPI.GetPayloadV5(*res.PayloadID)
} else if isPrague {
envelope, err = f.engineAPI.GetPayloadV4(*res.PayloadID)
} else if isCancun {
envelope, err = f.engineAPI.GetPayloadV3(*res.PayloadID)
Expand Down
Loading