Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Isthmus: Updates for L2 withdrawals root in header #12767

Closed
wants to merge 11 commits into from
2 changes: 1 addition & 1 deletion bedrock-devnet/devnet/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@
log = logging.getLogger()

# Global constants
FORKS = ["delta", "ecotone", "fjord", "granite", "holocene"]
FORKS = ["delta", "ecotone", "fjord", "granite", "holocene", "isthmus"]

# Global environment variables
DEVNET_NO_BUILD = os.getenv('DEVNET_NO_BUILD') == "true"
Expand Down
3 changes: 2 additions & 1 deletion op-chain-ops/cmd/check-derivation/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ func newClientsFromContext(cliCtx *cli.Context) (*ethclient.Client, *sources.Eth
MethodResetDuration: time.Minute,
}
cl := ethclient.NewClient(clients.L2RpcClient)
ethCl, err := sources.NewEthClient(client.NewBaseRPCClient(clients.L2RpcClient), log.Root(), nil, &ethClCfg)
l2RpcChecker := sources.NewL2RPCChecker()
ethCl, err := sources.NewEthClient(client.NewBaseRPCClient(clients.L2RpcClient), log.Root(), nil, &ethClCfg, l2RpcChecker)
if err != nil {
return nil, nil, err
}
Expand Down
13 changes: 13 additions & 0 deletions op-chain-ops/genesis/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,9 @@ type UpgradeScheduleDeployConfig struct {
// L2GenesisHoloceneTimeOffset is the number of seconds after genesis block that the Holocene hard fork activates.
// Set it to 0 to activate at genesis. Nil to disable Holocene.
L2GenesisHoloceneTimeOffset *hexutil.Uint64 `json:"l2GenesisHoloceneTimeOffset,omitempty"`
// L2GenesisIsthmusTimeOffset is the number of seconds after genesis block that the Isthmus hard fork activates.
// Set it to 0 to activate at genesis. Nil to disable Isthmus.
L2GenesisIsthmusTimeOffset *hexutil.Uint64 `json:"l2GenesisIsthmusTimeOffset,omitempty"`
// L2GenesisInteropTimeOffset is the number of seconds after genesis block that the Interop hard fork activates.
// Set it to 0 to activate at genesis. Nil to disable Interop.
L2GenesisInteropTimeOffset *hexutil.Uint64 `json:"l2GenesisInteropTimeOffset,omitempty"`
Expand Down Expand Up @@ -385,6 +388,8 @@ func (d *UpgradeScheduleDeployConfig) ForkTimeOffset(fork rollup.ForkName) *uint
return (*uint64)(d.L2GenesisGraniteTimeOffset)
case rollup.Holocene:
return (*uint64)(d.L2GenesisHoloceneTimeOffset)
case rollup.Isthmus:
return (*uint64)(d.L2GenesisIsthmusTimeOffset)
case rollup.Interop:
return (*uint64)(d.L2GenesisInteropTimeOffset)
default:
Expand All @@ -408,6 +413,8 @@ func (d *UpgradeScheduleDeployConfig) SetForkTimeOffset(fork rollup.ForkName, of
d.L2GenesisGraniteTimeOffset = (*hexutil.Uint64)(offset)
case rollup.Holocene:
d.L2GenesisHoloceneTimeOffset = (*hexutil.Uint64)(offset)
case rollup.Isthmus:
d.L2GenesisIsthmusTimeOffset = (*hexutil.Uint64)(offset)
case rollup.Interop:
d.L2GenesisInteropTimeOffset = (*hexutil.Uint64)(offset)
default:
Expand Down Expand Up @@ -472,6 +479,10 @@ func (d *UpgradeScheduleDeployConfig) HoloceneTime(genesisTime uint64) *uint64 {
return offsetToUpgradeTime(d.L2GenesisHoloceneTimeOffset, genesisTime)
}

func (d *UpgradeScheduleDeployConfig) IsthmusTime(genesisTime uint64) *uint64 {
return offsetToUpgradeTime(d.L2GenesisIsthmusTimeOffset, genesisTime)
}

func (d *UpgradeScheduleDeployConfig) InteropTime(genesisTime uint64) *uint64 {
return offsetToUpgradeTime(d.L2GenesisInteropTimeOffset, genesisTime)
}
Expand Down Expand Up @@ -504,6 +515,7 @@ func (d *UpgradeScheduleDeployConfig) forks() []Fork {
{L2GenesisTimeOffset: d.L2GenesisFjordTimeOffset, Name: string(L2AllocsFjord)},
{L2GenesisTimeOffset: d.L2GenesisGraniteTimeOffset, Name: string(L2AllocsGranite)},
{L2GenesisTimeOffset: d.L2GenesisHoloceneTimeOffset, Name: string(L2AllocsHolocene)},
{L2GenesisTimeOffset: d.L2GenesisIsthmusTimeOffset, Name: string(L2AllocsIsthmus)},
}
}

Expand Down Expand Up @@ -1014,6 +1026,7 @@ func (d *DeployConfig) RollupConfig(l1StartBlock *types.Header, l2GenesisBlockHa
FjordTime: d.FjordTime(l1StartTime),
GraniteTime: d.GraniteTime(l1StartTime),
HoloceneTime: d.HoloceneTime(l1StartTime),
IsthmusTime: d.IsthmusTime(l1StartTime),
InteropTime: d.InteropTime(l1StartTime),
ProtocolVersionsAddress: d.ProtocolVersionsProxy,
AltDAConfig: altDA,
Expand Down
1 change: 1 addition & 0 deletions op-chain-ops/genesis/layer_two.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const (
L2AllocsFjord L2AllocsMode = "fjord"
L2AllocsGranite L2AllocsMode = "granite"
L2AllocsHolocene L2AllocsMode = "holocene"
L2AllocsIsthmus L2AllocsMode = "isthmus"
)

var (
Expand Down
3 changes: 2 additions & 1 deletion op-conductor/conductor/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,8 +129,9 @@ func (c *OpConductor) initSequencerControl(ctx context.Context) error {
return errors.Wrap(err, "failed to create geth rpc client")
}
execCfg := sources.L2ClientDefaultConfig(&c.cfg.RollupCfg, true)
l2RpcChecker := sources.NewL2RPCChecker()
// TODO: Add metrics tracer here. tracked by https://github.com/ethereum-optimism/protocol-quest/issues/45
exec, err := sources.NewEthClient(ec, c.log, nil, &execCfg.EthClientConfig)
exec, err := sources.NewEthClient(ec, c.log, nil, &execCfg.EthClientConfig, l2RpcChecker)
if err != nil {
return errors.Wrap(err, "failed to create geth client")
}
Expand Down
2 changes: 1 addition & 1 deletion op-e2e/actions/helpers/env.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ func WithActiveGenesisFork(fork rollup.ForkName) EnvOpt {
// DefaultFork specifies the default fork to use when setting up the action test environment.
// Currently manually set to Holocene.
// Replace with `var DefaultFork = func() rollup.ForkName { return rollup.AllForks[len(rollup.AllForks)-1] }()` after Interop launch.
const DefaultFork = rollup.Holocene
const DefaultFork = rollup.Isthmus

// SetupEnv sets up a default action test environment. If no fork is specified, the default fork as
// specified by the package variable [defaultFork] is used.
Expand Down
5 changes: 5 additions & 0 deletions op-e2e/actions/helpers/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ type hardforkScheduledTest struct {
fjordTime *hexutil.Uint64
graniteTime *hexutil.Uint64
holoceneTime *hexutil.Uint64
isthmusTime *hexutil.Uint64
runToFork string
allocType config.AllocType
}
Expand All @@ -41,6 +42,8 @@ func (tc *hardforkScheduledTest) GetFork(fork string) *uint64 {

func (tc *hardforkScheduledTest) fork(fork string) **hexutil.Uint64 {
switch fork {
case "isthmus":
return &tc.isthmusTime
case "holocene":
return &tc.holoceneTime
case "granite":
Expand Down Expand Up @@ -88,6 +91,7 @@ func testCrossLayerUser(t *testing.T, allocType config.AllocType) {
"fjord",
"granite",
"holocene",
"isthmus",
}
for i, fork := range forks {
i := i
Expand Down Expand Up @@ -146,6 +150,7 @@ func runCrossLayerUserTest(gt *testing.T, test hardforkScheduledTest) {
dp.DeployConfig.L2GenesisFjordTimeOffset = test.fjordTime
dp.DeployConfig.L2GenesisGraniteTimeOffset = test.graniteTime
dp.DeployConfig.L2GenesisHoloceneTimeOffset = test.holoceneTime
dp.DeployConfig.L2GenesisIsthmusTimeOffset = test.isthmusTime

if test.canyonTime != nil {
require.Zero(t, uint64(*test.canyonTime)%uint64(dp.DeployConfig.L2BlockTime), "canyon fork must be aligned")
Expand Down
9 changes: 9 additions & 0 deletions op-e2e/actions/upgrades/helpers/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,4 +43,13 @@ func ApplyDeltaTimeOffset(dp *e2eutils.DeployParams, deltaTimeOffset *hexutil.Ui
dp.DeployConfig.L2GenesisHoloceneTimeOffset = deltaTimeOffset
}
}

// configure Isthmus to not be before Delta accidentally
if dp.DeployConfig.L2GenesisIsthmusTimeOffset != nil {
if deltaTimeOffset == nil {
dp.DeployConfig.L2GenesisIsthmusTimeOffset = nil
} else if *dp.DeployConfig.L2GenesisIsthmusTimeOffset < *deltaTimeOffset {
dp.DeployConfig.L2GenesisIsthmusTimeOffset = deltaTimeOffset
}
}
}
78 changes: 78 additions & 0 deletions op-e2e/actions/upgrades/isthmus_fork_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package upgrades

import (
"testing"

"github.com/ethereum-optimism/optimism/op-e2e/actions/helpers"
"github.com/ethereum-optimism/optimism/op-e2e/e2eutils"
"github.com/ethereum-optimism/optimism/op-node/rollup"
"github.com/ethereum-optimism/optimism/op-service/testlog"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/stretchr/testify/require"
)

func TestIsthmusActivationAtGenesis(gt *testing.T) {
t := helpers.NewDefaultTesting(gt)
env := helpers.SetupEnv(t, helpers.WithActiveGenesisFork(rollup.Isthmus))

// Start op-nodes
env.Seq.ActL2PipelineFull(t)
env.Verifier.ActL2PipelineFull(t)

// Verify Isthmus is active at genesis
l2Head := env.Seq.L2Unsafe()
require.NotZero(t, l2Head.Hash)
require.True(t, env.SetupData.RollupCfg.IsIsthmus(l2Head.Time), "Isthmus should be active at genesis")

// build empty L1 block
env.Miner.ActEmptyBlock(t)

// Build L2 chain and advance safe head
env.Seq.ActL1HeadSignal(t)
env.Seq.ActBuildToL1Head(t)

block := env.VerifEngine.L2Chain().CurrentBlock()
verifyIsthmusBlock(gt, block)
}

func TestWithdrawlsRootPreCanyon(gt *testing.T) {
t := helpers.NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, helpers.DefaultRollupTestParams())
genesisBlock := hexutil.Uint64(0)
canyonOffset := hexutil.Uint64(2)

log := testlog.Logger(t, log.LvlDebug)

dp.DeployConfig.L1CancunTimeOffset = &canyonOffset

// Activate pre-canyon forks at genesis, and schedule Canyon the block after
dp.DeployConfig.L2GenesisRegolithTimeOffset = &genesisBlock
dp.DeployConfig.L2GenesisCanyonTimeOffset = &canyonOffset
dp.DeployConfig.L2GenesisDeltaTimeOffset = nil
dp.DeployConfig.L2GenesisEcotoneTimeOffset = nil
dp.DeployConfig.L2GenesisFjordTimeOffset = nil
dp.DeployConfig.L2GenesisGraniteTimeOffset = nil
dp.DeployConfig.L2GenesisHoloceneTimeOffset = nil
dp.DeployConfig.L2GenesisIsthmusTimeOffset = nil
require.NoError(t, dp.DeployConfig.Check(log), "must have valid config")

sd := e2eutils.Setup(t, dp, helpers.DefaultAlloc)
_, _, _, sequencer, engine, verifier, _, _ := helpers.SetupReorgTestActors(t, dp, sd, log)
// ethCl := engine.EthClient()

// start op-nodes
sequencer.ActL2PipelineFull(t)
verifier.ActL2PipelineFull(t)

verifyPreIsthmusBlock(gt, engine.L2Chain().CurrentBlock())
}

func verifyPreIsthmusBlock(gt *testing.T, header *types.Header) {
require.Nil(gt, header.WithdrawalsHash)
}

func verifyIsthmusBlock(gt *testing.T, header *types.Header) {
require.Equal(gt, types.EmptyWithdrawalsHash, *header.WithdrawalsHash)
}
1 change: 1 addition & 0 deletions op-e2e/config/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,7 @@ func initAllocType(root string, allocType AllocType) {
}
l2Alloc[mode] = allocs
}
mustL2Allocs(genesis.L2AllocsIsthmus)
mustL2Allocs(genesis.L2AllocsHolocene)
mustL2Allocs(genesis.L2AllocsGranite)
mustL2Allocs(genesis.L2AllocsFjord)
Expand Down
8 changes: 8 additions & 0 deletions op-e2e/e2eutils/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,9 @@ func Ether(v uint64) *big.Int {
}

func GetL2AllocsMode(dc *genesis.DeployConfig, t uint64) genesis.L2AllocsMode {
if fork := dc.IsthmusTime(t); fork != nil && *fork <= 0 {
return genesis.L2AllocsIsthmus
}
if fork := dc.HoloceneTime(t); fork != nil && *fork <= 0 {
return genesis.L2AllocsHolocene
}
Expand Down Expand Up @@ -205,6 +208,7 @@ func Setup(t require.TestingT, deployParams *DeployParams, alloc *AllocParams) *
FjordTime: deployConf.FjordTime(uint64(deployConf.L1GenesisBlockTimestamp)),
GraniteTime: deployConf.GraniteTime(uint64(deployConf.L1GenesisBlockTimestamp)),
HoloceneTime: deployConf.HoloceneTime(uint64(deployConf.L1GenesisBlockTimestamp)),
IsthmusTime: deployConf.IsthmusTime(uint64(deployConf.L1GenesisBlockTimestamp)),
InteropTime: deployConf.InteropTime(uint64(deployConf.L1GenesisBlockTimestamp)),
AltDAConfig: pcfg,
}
Expand Down Expand Up @@ -235,6 +239,7 @@ func SystemConfigFromDeployConfig(deployConfig *genesis.DeployConfig) eth.System
}

func ApplyDeployConfigForks(deployConfig *genesis.DeployConfig) {
isIsthmus := os.Getenv("OP_E2E_USE_ISTHMUS") == "true"
isHolocene := os.Getenv("OP_E2E_USE_HOLOCENE") == "true"
isGranite := isHolocene || os.Getenv("OP_E2E_USE_GRANITE") == "true"
isFjord := isGranite || os.Getenv("OP_E2E_USE_FJORD") == "true"
Expand All @@ -255,6 +260,9 @@ func ApplyDeployConfigForks(deployConfig *genesis.DeployConfig) {
if isHolocene {
deployConfig.L2GenesisHoloceneTimeOffset = new(hexutil.Uint64)
}
if isIsthmus {
deployConfig.L2GenesisIsthmusTimeOffset = new(hexutil.Uint64)
}
// Canyon and lower is activated by default
deployConfig.L2GenesisCanyonTimeOffset = new(hexutil.Uint64)
deployConfig.L2GenesisRegolithTimeOffset = new(hexutil.Uint64)
Expand Down
2 changes: 2 additions & 0 deletions op-node/node/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ func (n *nodeAPI) OutputAtBlock(ctx context.Context, number hexutil.Uint64) (*et
return nil, fmt.Errorf("failed to get L2 block ref with sync status: %w", err)
}

// OutputV0AtBlock uses the WithdrawalsRoot in the block header as the value for the
// output MessagePasserStorageRoot, if Isthmus hard fork has activated.
output, err := n.client.OutputV0AtBlock(ctx, ref.Hash)
if err != nil {
return nil, fmt.Errorf("failed to get L2 output at block %s: %w", ref, err)
Expand Down
41 changes: 38 additions & 3 deletions op-node/p2p/gossip.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,19 @@ func blocksTopicV3(cfg *rollup.Config) string {
return fmt.Sprintf("/optimism/%s/2/blocks", cfg.L2ChainID.String())
}

func blocksTopicV4(cfg *rollup.Config) string {
return fmt.Sprintf("/optimism/%s/3/blocks", cfg.L2ChainID.String())
}

// BuildSubscriptionFilter builds a simple subscription filter,
// to help protect against peers spamming useless subscriptions.
func BuildSubscriptionFilter(cfg *rollup.Config) pubsub.SubscriptionFilter {
return pubsub.NewAllowlistSubscriptionFilter(blocksTopicV1(cfg), blocksTopicV2(cfg), blocksTopicV3(cfg)) // add more topics here in the future, if any.
return pubsub.NewAllowlistSubscriptionFilter(
blocksTopicV1(cfg),
blocksTopicV2(cfg),
blocksTopicV3(cfg),
blocksTopicV4(cfg), // add more topics here in the future, if any.
)
}

var msgBufPool = sync.Pool{New: func() any {
Expand Down Expand Up @@ -386,6 +395,10 @@ func BuildBlocksValidator(log log.Logger, cfg *rollup.Config, runCfg GossipRunti
return pubsub.ValidationReject
}

if blockVersion.HasWithdrawalsRoot() && payload.WithdrawalsRoot == nil {
log.Warn("payload is on v4 topic, but has nil withdrawals root", "bad_hash", payload.BlockHash.String())
}

seen, ok := blockHeightLRU.Get(uint64(payload.BlockNumber))
if !ok {
seen = new(seenBlocks)
Expand Down Expand Up @@ -450,6 +463,7 @@ type GossipTopicInfo interface {
BlocksTopicV1Peers() []peer.ID
BlocksTopicV2Peers() []peer.ID
BlocksTopicV3Peers() []peer.ID
BlocksTopicV4Peers() []peer.ID
}

type GossipOut interface {
Expand Down Expand Up @@ -485,6 +499,7 @@ type publisher struct {
blocksV1 *blockTopic
blocksV2 *blockTopic
blocksV3 *blockTopic
blocksV4 *blockTopic

runCfg GossipRuntimeConfig
}
Expand All @@ -507,7 +522,12 @@ func combinePeers(allPeers ...[]peer.ID) []peer.ID {
}

func (p *publisher) AllBlockTopicsPeers() []peer.ID {
return combinePeers(p.BlocksTopicV1Peers(), p.BlocksTopicV2Peers(), p.BlocksTopicV3Peers())
return combinePeers(
p.BlocksTopicV1Peers(),
p.BlocksTopicV2Peers(),
p.BlocksTopicV3Peers(),
p.BlocksTopicV4Peers(),
)
}

func (p *publisher) BlocksTopicV1Peers() []peer.ID {
Expand All @@ -522,6 +542,10 @@ func (p *publisher) BlocksTopicV3Peers() []peer.ID {
return p.blocksV3.topic.ListPeers()
}

func (p *publisher) BlocksTopicV4Peers() []peer.ID {
return p.blocksV4.topic.ListPeers()
}

func (p *publisher) PublishL2Payload(ctx context.Context, envelope *eth.ExecutionPayloadEnvelope, signer Signer) error {
res := msgBufPool.Get().(*[]byte)
buf := bytes.NewBuffer((*res)[:0])
Expand Down Expand Up @@ -554,7 +578,9 @@ func (p *publisher) PublishL2Payload(ctx context.Context, envelope *eth.Executio
// This also copies the data, freeing up the original buffer to go back into the pool
out := snappy.Encode(nil, data)

if p.cfg.IsEcotone(uint64(envelope.ExecutionPayload.Timestamp)) {
if p.cfg.IsIsthmus(uint64(envelope.ExecutionPayload.Timestamp)) {
return p.blocksV4.topic.Publish(ctx, out)
} else if p.cfg.IsEcotone(uint64(envelope.ExecutionPayload.Timestamp)) {
return p.blocksV3.topic.Publish(ctx, out)
} else if p.cfg.IsCanyon(uint64(envelope.ExecutionPayload.Timestamp)) {
return p.blocksV2.topic.Publish(ctx, out)
Expand Down Expand Up @@ -597,13 +623,22 @@ func JoinGossip(self peer.ID, ps *pubsub.PubSub, log log.Logger, cfg *rollup.Con
return nil, fmt.Errorf("failed to setup blocks v3 p2p: %w", err)
}

v4Logger := log.New("topic", "blocksV4")
blocksV4Validator := guardGossipValidator(log, logValidationResult(self, "validated blockv4", v4Logger, BuildBlocksValidator(v4Logger, cfg, runCfg, eth.BlockV4)))
blocksV4, err := newBlockTopic(p2pCtx, blocksTopicV4(cfg), ps, v4Logger, gossipIn, blocksV4Validator)
if err != nil {
p2pCancel()
return nil, fmt.Errorf("failed to setup blocks v4 p2p: %w", err)
}

return &publisher{
log: log,
cfg: cfg,
p2pCancel: p2pCancel,
blocksV1: blocksV1,
blocksV2: blocksV2,
blocksV3: blocksV3,
blocksV4: blocksV4,
runCfg: runCfg,
}, nil
}
Expand Down
Loading