Skip to content

Commit

Permalink
Action test for genesis isthmus activation and activation after genesis
Browse files Browse the repository at this point in the history
* test all combinations - with/without withdrawal transaction before, at and after isthmus
  • Loading branch information
Vinod Damle committed Nov 13, 2024
1 parent 6b3c027 commit 79609ed
Show file tree
Hide file tree
Showing 10 changed files with 169 additions and 95 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,7 @@ require (
rsc.io/tmplfunc v0.0.3 // indirect
)

replace github.com/ethereum/go-ethereum v1.14.11 => github.com/ethereum-optimism/op-geth v1.101411.1-rc.6
replace github.com/ethereum/go-ethereum v1.14.11 => ../op-geth

//replace github.com/ethereum/go-ethereum => ../go-ethereum

Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -187,8 +187,6 @@ github.com/elastic/gosigar v0.14.3 h1:xwkKwPia+hSfg9GqrCUKYdId102m9qTJIIr7egmK/u
github.com/elastic/gosigar v0.14.3/go.mod h1:iXRIGg2tLnu7LBdpqzyQfGDEidKCfWcCMS0WKyPWoMs=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3 h1:RWHKLhCrQThMfch+QJ1Z8veEq5ZO3DfIhZ7xgRP9WTc=
github.com/ethereum-optimism/go-ethereum-hdwallet v0.1.3/go.mod h1:QziizLAiF0KqyLdNJYD7O5cpDlaFMNZzlxYNcWsJUxs=
github.com/ethereum-optimism/op-geth v1.101411.1-rc.6 h1:VvUBIVFbnU9486CWHa9Js5XYY3o6OsdQcI8gE3XjCDE=
github.com/ethereum-optimism/op-geth v1.101411.1-rc.6/go.mod h1:7S4pp8KHBmEmKkRjL1BPOc6jY9hW+64YeMUjR3RVLw4=
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240910145426-b3905c89e8ac h1:hCIrLuOPV3FJfMDvXeOhCC3uQNvFoMIIlkT2mN2cfeg=
github.com/ethereum-optimism/superchain-registry/superchain v0.0.0-20240910145426-b3905c89e8ac/go.mod h1:XaVXL9jg8BcyOeugECgIUGa9Y3DjYJj71RHmb5qon6M=
github.com/ethereum/c-kzg-4844 v1.0.0 h1:0X1LBXxaEtYD9xsyj9B9ctQEZIpnvVDeoBx8aHEwTNA=
Expand Down
2 changes: 1 addition & 1 deletion op-e2e/actions/helpers/l1_miner.go
Original file line number Diff line number Diff line change
Expand Up @@ -218,7 +218,7 @@ func (s *L1Miner) ActL1EndBlock(t Testing) *types.Block {
withdrawals = make([]*types.Withdrawal, 0)
}

block := types.NewBlock(s.l1BuildingHeader, &types.Body{Transactions: s.L1Transactions, Withdrawals: withdrawals}, s.l1Receipts, trie.NewStackTrie(nil))
block := types.NewBlock(s.l1BuildingHeader, &types.Body{Transactions: s.L1Transactions, Withdrawals: withdrawals}, s.l1Receipts, trie.NewStackTrie(nil), s.l1Cfg.Config)
if s.l1Cfg.Config.IsCancun(s.l1BuildingHeader.Number, s.l1BuildingHeader.Time) {
parent := s.l1Chain.GetHeaderByHash(s.l1BuildingHeader.ParentHash)
var (
Expand Down
158 changes: 109 additions & 49 deletions op-e2e/actions/upgrades/isthmus_fork_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,13 +45,13 @@ func TestIsthmusActivationAtGenesis(gt *testing.T) {
env.Seq.ActBuildToL1Head(t)

block := env.VerifEngine.L2Chain().CurrentBlock()
verifyIsthmusHeaderWithdrawalsRoot(gt, block, false)
verifyIsthmusHeaderWithdrawalsRoot(gt, env.SeqEngine.RPCClient(), block, true)
}

// There are 2 stages pre-Isthmus that we need to test:
// 1. Pre-Canyon: withdrawals root should be nil
// 2. Post-Canyon: withdrawals root should be EmptyWithdrawalsHash
func TestWithdrawlsRootPreIsthmus(gt *testing.T) {
func TestWithdrawlsRootPreCanyonAndIsthmus(gt *testing.T) {
t := helpers.NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, helpers.DefaultRollupTestParams())
genesisBlock := hexutil.Uint64(0)
Expand Down Expand Up @@ -104,25 +104,41 @@ func TestWithdrawlsRootPreIsthmus(gt *testing.T) {
verifyPreIsthmusHeaderWithdrawalsRoot(gt, engine.L2Chain().CurrentBlock())
}

func TestWithdrawlsRootAtIsthmus(gt *testing.T) {
// In this section, we will test the following combinations
// 1. Withdrawals root before isthmus w/ and w/o L2toL1 withdrawal
// 2. Withdrawals root at isthmus w/ and w/o L2toL1 withdrawal
// 3. Withdrawals root after isthmus w/ and w/o L2toL1 withdrawal
func TestWithdrawalsRootBeforeAtAndAfterIsthmus(t *testing.T) {
tests := []struct {
name string
f func(gt *testing.T, withdrawalTx bool, withdrawalTxBlock, totalBlocks int)
withdrawalTx bool
withdrawalTxBlock int
totalBlocks int
}{
{"BeforeIsthmusWithoutWithdrawalTx", testWithdrawlsRootAtIsthmus, false, 0, 1},
{"BeforeIsthmusWithWithdrawalTx", testWithdrawlsRootAtIsthmus, true, 1, 1},
{"AtIsthmusWithoutWithdrawalTx", testWithdrawlsRootAtIsthmus, false, 0, 2},
{"AtIsthmusWithWithdrawalTx", testWithdrawlsRootAtIsthmus, true, 2, 2},
{"AfterIsthmusWithoutWithdrawalTx", testWithdrawlsRootAtIsthmus, false, 0, 3},
{"AfterIsthmusWithWithdrawalTx", testWithdrawlsRootAtIsthmus, true, 3, 3},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
test.f(t, test.withdrawalTx, test.withdrawalTxBlock, test.totalBlocks)
})
}
}

func testWithdrawlsRootAtIsthmus(gt *testing.T, withdrawalTx bool, withdrawalTxBlock, totalBlocks int) {
t := helpers.NewDefaultTesting(gt)
dp := e2eutils.MakeDeployParams(t, helpers.DefaultRollupTestParams())
genesisBlock := hexutil.Uint64(0)
isthmusOffset := hexutil.Uint64(2)

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

// dp.DeployConfig.L1CancunTimeOffset = &genesisBlock

// Activate pre-canyon forks at genesis, and schedule Canyon the block after
// dummyAddr := common.Address{19: 0x01}
// dp.DeployConfig.OutputOracleDeployConfig = genesis.OutputOracleDeployConfig{
// L2OutputOracleSubmissionInterval: 1,
// L2OutputOracleStartingTimestamp: 1,
// L2OutputOracleStartingBlockNumber: 3,
// L2OutputOracleProposer: dummyAddr,
// L2OutputOracleChallenger: dummyAddr,
// }
dp.DeployConfig.L2GenesisRegolithTimeOffset = &genesisBlock
dp.DeployConfig.L2GenesisCanyonTimeOffset = &genesisBlock
dp.DeployConfig.L2GenesisIsthmusTimeOffset = &isthmusOffset
Expand All @@ -142,20 +158,73 @@ func TestWithdrawlsRootAtIsthmus(gt *testing.T) {

verifyPreIsthmusHeaderWithdrawalsRoot(gt, engine.L2Chain().CurrentBlock())

getStorageRoot := func(rpcCl client.RPC, ctx context.Context, address common.Address, blockTag string) common.Hash {
var getProofResponse *eth.AccountResult
err := rpcCl.CallContext(ctx, &getProofResponse, "eth_getProof", address, []common.Hash{}, blockTag)
assert.Nil(t, err)
assert.NotNil(t, getProofResponse)
return getProofResponse.StorageHash
ethCl := engine.EthClient()
for i := 1; i <= totalBlocks; i++ {
var tx *types.Transaction

sequencer.ActL2StartBlock(t)

if withdrawalTx && withdrawalTxBlock == i {
l2withdrawer, err := bindings.NewL2ToL1MessagePasser(predeploys.L2ToL1MessagePasserAddr, ethCl)
require.Nil(t, err, "binding withdrawer on L2")

// Initiate Withdrawal
// Bind L2 Withdrawer Contract and invoke the Receive function
l2opts, err := bind.NewKeyedTransactorWithChainID(dp.Secrets.Alice, new(big.Int).SetUint64(dp.DeployConfig.L2ChainID))
require.Nil(t, err)
l2opts.Value = big.NewInt(500)
tx, err = l2withdrawer.Receive(l2opts)
require.Nil(t, err)

// include the transaction
engine.ActL2IncludeTx(dp.Addresses.Alice)(t)
}
sequencer.ActL2EndBlock(t)

if withdrawalTx && withdrawalTxBlock == i {
// wait for withdrawal to be included in a block
receipt, err := geth.WaitForTransaction(tx.Hash(), ethCl, 10*time.Duration(dp.DeployConfig.L2BlockTime)*time.Second)
require.Nil(t, err, "withdrawal initiated on L2 sequencer")
require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status, "transaction had incorrect status")
}
}
rpcCl := engine.RPCClient()

// we set withdrawals root only at or after isthmus
if totalBlocks >= 2 {
verifyIsthmusHeaderWithdrawalsRoot(gt, rpcCl, engine.L2Chain().CurrentBlock(), true)
}
}

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

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

dp.DeployConfig.L2GenesisRegolithTimeOffset = &genesisBlock
dp.DeployConfig.L2GenesisCanyonTimeOffset = &genesisBlock
dp.DeployConfig.L2GenesisIsthmusTimeOffset = &isthmusOffset
dp.DeployConfig.L2GenesisDeltaTimeOffset = &genesisBlock
dp.DeployConfig.L2GenesisEcotoneTimeOffset = &genesisBlock
dp.DeployConfig.L2GenesisFjordTimeOffset = &genesisBlock
dp.DeployConfig.L2GenesisGraniteTimeOffset = &genesisBlock
dp.DeployConfig.L2GenesisHoloceneTimeOffset = &genesisBlock
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)

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

verifyPreIsthmusHeaderWithdrawalsRoot(gt, engine.L2Chain().CurrentBlock())

rpcCl := engine.RPCClient()
currentBlock := engine.L2Chain().CurrentBlock()
t.Log("Current block number: ", currentBlock.Number)
storageHash := getStorageRoot(rpcCl, context.Background(), predeploys.L2ToL1MessagePasserAddr, "latest")
t.Log("Storage hash: ", storageHash)
// require.Equal(t, *currentBlock.WithdrawalsHash, storageHash)
verifyIsthmusHeaderWithdrawalsRoot(gt, rpcCl, engine.L2Chain().CurrentBlock(), false)

// Send withdrawal transaction
// Bind L2 Withdrawer Contract
Expand All @@ -175,21 +244,17 @@ func TestWithdrawlsRootAtIsthmus(gt *testing.T) {
sequencer.ActL2StartBlock(t)
sequencer.ActL2EndBlock(t)
sequencer.ActL2StartBlock(t)
sequencer.ActL2EndBlock(t)
sequencer.ActL2StartBlock(t)
engine.ActL2IncludeTx(dp.Addresses.Alice)(t)
sequencer.ActL2EndBlock(t)

// wait for withdrawal to be included in a block
receipt, err := geth.WaitForTransaction(tx.Hash(), ethCl, 10*time.Duration(dp.DeployConfig.L2BlockTime)*time.Second)
require.Nil(t, err, "withdrawal initiated on L2 sequencer")
require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status, "transaction had incorrect status")

sequencer.ActL2StartBlock(t)
sequencer.ActL2EndBlock(t)

currentBlock = engine.L2Chain().CurrentBlock()
t.Log("Current block number: ", currentBlock.Number)
storageHash = getStorageRoot(rpcCl, context.Background(), predeploys.L2ToL1MessagePasserAddr, "latest")
require.Equal(t, *currentBlock.WithdrawalsHash, storageHash)
// verifyIsthmusHeaderWithdrawalsRoot(gt, engine.L2Chain().CurrentBlock(), true)
verifyIsthmusHeaderWithdrawalsRoot(gt, rpcCl, engine.L2Chain().CurrentBlock(), true)
}

// Pre-Canyon, the withdrawals root field in the header should be nil
Expand All @@ -202,24 +267,19 @@ func verifyPreIsthmusHeaderWithdrawalsRoot(gt *testing.T, header *types.Header)
require.Equal(gt, types.EmptyWithdrawalsHash, *header.WithdrawalsHash)
}

func verifyIsthmusHeaderWithdrawalsRoot(gt *testing.T, header *types.Header, l2toL1MPPresent bool) {
func verifyIsthmusHeaderWithdrawalsRoot(gt *testing.T, rpcCl client.RPC, header *types.Header, l2toL1MPPresent bool) {
getStorageRoot := func(rpcCl client.RPC, ctx context.Context, address common.Address, blockTag string) common.Hash {
var getProofResponse *eth.AccountResult
err := rpcCl.CallContext(ctx, &getProofResponse, "eth_getProof", address, []common.Hash{}, blockTag)
assert.Nil(gt, err)
assert.NotNil(gt, getProofResponse)
return getProofResponse.StorageHash
}

if !l2toL1MPPresent {
require.Equal(gt, types.EmptyWithdrawalsHash, *header.WithdrawalsHash)
} else {
require.NotEqual(gt, types.EmptyWithdrawalsHash, *header.WithdrawalsHash)
storageHash := getStorageRoot(rpcCl, context.Background(), predeploys.L2ToL1MessagePasserAddr, "latest")
require.Equal(gt, *header.WithdrawalsHash, storageHash)
}
}

// func getL2ToL1MP(t *testing.T, engine *e2eutils.Engine) *types.Transaction {
// // Get the L2 to L1 message passing tx
// l2ToL1MP, err := engine.L2Chain().CurrentBlock().GetL2ToL1MessagePassingTx()
// require.NoError(t, err)
// return l2ToL1MP
// }

// func getL2ToL1MPStorageRoot(t *testing.T, ethCl *ethclient.Client) string {
// proof, err := ethCl.GetProof(ctx, predeploys.L2ToL1MessagePasserAddr, []common.Hash{}, blockHash.String())
// rootValue, err := ethCl.StorageAt(context.Background(), predeploys.L2ToL1MessagePasserAddr, rootIdx, nil)
// require.NoError(t, err)
// require.Equal(t, expectedHash, common.BytesToHash(rootValue), msg)
// }
2 changes: 1 addition & 1 deletion op-program/client/l2/engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ func createL2Block(t *testing.T, number int) *types.Block {
body := &types.Body{
Transactions: []*types.Transaction{types.NewTx(tx)},
}
return types.NewBlock(header, body, nil, trie.NewStackTrie(nil))
return types.NewBlock(header, body, nil, trie.NewStackTrie(nil), &params.ChainConfig{})
}

type stubEngineBackend struct {
Expand Down
7 changes: 7 additions & 0 deletions op-program/client/l2/engineapi/block_processor.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"math/big"

"github.com/ethereum-optimism/optimism/op-service/eth"
"github.com/ethereum-optimism/optimism/op-service/predeploys"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/consensus"
"github.com/ethereum/go-ethereum/consensus/misc/eip1559"
Expand Down Expand Up @@ -107,6 +108,12 @@ func NewBlockProcessorFromHeader(provider BlockDataProvider, h *types.Header) (*
vmenv := mkEVM()
core.ProcessParentBlockHash(header.ParentHash, vmenv, statedb)
}
if provider.Config().IsIsthmus(header.Time) {
// set the header withdrawals root for Isthmus blocks
mpHash := statedb.GetStorageRoot(predeploys.L2ToL1MessagePasserAddr)
header.WithdrawalsHash = &mpHash
}

return &BlockProcessor{
header: header,
state: statedb,
Expand Down
47 changes: 28 additions & 19 deletions op-program/client/l2/engineapi/l2_engine_api.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,7 @@ func (ea *L2EngineAPI) startBlock(parent common.Hash, attrs *eth.PayloadAttribut
return fmt.Errorf("failed to apply deposit transaction to L2 block (tx %d): %w", i, err)
}
}

return nil
}

Expand Down Expand Up @@ -338,7 +339,14 @@ func (ea *L2EngineAPI) NewPayloadV3(ctx context.Context, params *eth.ExecutionPa
// Payload must have eip-1559 params in ExtraData after Holocene
if ea.config().IsHolocene(uint64(params.Timestamp)) {
if err := eip1559.ValidateHoloceneExtraData(params.ExtraData); err != nil {
return &eth.PayloadStatusV1{Status: eth.ExecutionInvalid}, engine.UnsupportedFork.With(errors.New("invalid holocene extraData post-holoocene"))
return &eth.PayloadStatusV1{Status: eth.ExecutionInvalid}, engine.UnsupportedFork.With(errors.New("invalid holocene extraData post-holocene"))
}
}

// Payload must have WithdrawalsRoot after Isthmus
if ea.config().IsIsthmus(uint64(params.Timestamp)) {
if params.WithdrawalsRoot == nil {
return &eth.PayloadStatusV1{Status: eth.ExecutionInvalid}, engine.UnsupportedFork.With(errors.New("nil withdrawalsRoot post-isthmus"))
}
}

Expand Down Expand Up @@ -486,24 +494,25 @@ func (ea *L2EngineAPI) newPayload(_ context.Context, payload *eth.ExecutionPaylo
txs[i] = tx
}
block, err := engine.ExecutableDataToBlock(engine.ExecutableData{
ParentHash: payload.ParentHash,
FeeRecipient: payload.FeeRecipient,
StateRoot: common.Hash(payload.StateRoot),
ReceiptsRoot: common.Hash(payload.ReceiptsRoot),
LogsBloom: payload.LogsBloom[:],
Random: common.Hash(payload.PrevRandao),
Number: uint64(payload.BlockNumber),
GasLimit: uint64(payload.GasLimit),
GasUsed: uint64(payload.GasUsed),
Timestamp: uint64(payload.Timestamp),
ExtraData: payload.ExtraData,
BaseFeePerGas: (*uint256.Int)(&payload.BaseFeePerGas).ToBig(),
BlockHash: payload.BlockHash,
Transactions: txs,
Withdrawals: toGethWithdrawals(payload),
ExcessBlobGas: (*uint64)(payload.ExcessBlobGas),
BlobGasUsed: (*uint64)(payload.BlobGasUsed),
}, hashes, root)
ParentHash: payload.ParentHash,
FeeRecipient: payload.FeeRecipient,
StateRoot: common.Hash(payload.StateRoot),
ReceiptsRoot: common.Hash(payload.ReceiptsRoot),
LogsBloom: payload.LogsBloom[:],
Random: common.Hash(payload.PrevRandao),
Number: uint64(payload.BlockNumber),
GasLimit: uint64(payload.GasLimit),
GasUsed: uint64(payload.GasUsed),
Timestamp: uint64(payload.Timestamp),
ExtraData: payload.ExtraData,
BaseFeePerGas: (*uint256.Int)(&payload.BaseFeePerGas).ToBig(),
BlockHash: payload.BlockHash,
Transactions: txs,
Withdrawals: toGethWithdrawals(payload),
ExcessBlobGas: (*uint64)(payload.ExcessBlobGas),
BlobGasUsed: (*uint64)(payload.BlobGasUsed),
WithdrawalsRoot: payload.WithdrawalsRoot,
}, hashes, root, config)
if err != nil {
log.Debug("Invalid NewPayload params", "params", payload, "error", err)
return &eth.PayloadStatusV1{Status: eth.ExecutionInvalidBlockHash}, nil
Expand Down
35 changes: 17 additions & 18 deletions op-service/eth/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -315,22 +315,23 @@ func BlockAsPayload(bl *types.Block, shanghaiTime *uint64) (*ExecutionPayload, e
}

payload := &ExecutionPayload{
ParentHash: bl.ParentHash(),
FeeRecipient: bl.Coinbase(),
StateRoot: Bytes32(bl.Root()),
ReceiptsRoot: Bytes32(bl.ReceiptHash()),
LogsBloom: Bytes256(bl.Bloom()),
PrevRandao: Bytes32(bl.MixDigest()),
BlockNumber: Uint64Quantity(bl.NumberU64()),
GasLimit: Uint64Quantity(bl.GasLimit()),
GasUsed: Uint64Quantity(bl.GasUsed()),
Timestamp: Uint64Quantity(bl.Time()),
ExtraData: bl.Extra(),
BaseFeePerGas: Uint256Quantity(*baseFee),
BlockHash: bl.Hash(),
Transactions: opaqueTxs,
ExcessBlobGas: (*Uint64Quantity)(bl.ExcessBlobGas()),
BlobGasUsed: (*Uint64Quantity)(bl.BlobGasUsed()),
ParentHash: bl.ParentHash(),
FeeRecipient: bl.Coinbase(),
StateRoot: Bytes32(bl.Root()),
ReceiptsRoot: Bytes32(bl.ReceiptHash()),
LogsBloom: Bytes256(bl.Bloom()),
PrevRandao: Bytes32(bl.MixDigest()),
BlockNumber: Uint64Quantity(bl.NumberU64()),
GasLimit: Uint64Quantity(bl.GasLimit()),
GasUsed: Uint64Quantity(bl.GasUsed()),
Timestamp: Uint64Quantity(bl.Time()),
ExtraData: bl.Extra(),
BaseFeePerGas: Uint256Quantity(*baseFee),
BlockHash: bl.Hash(),
Transactions: opaqueTxs,
ExcessBlobGas: (*Uint64Quantity)(bl.ExcessBlobGas()),
BlobGasUsed: (*Uint64Quantity)(bl.BlobGasUsed()),
WithdrawalsRoot: bl.Header().WithdrawalsHash,
}

if shanghaiTime != nil && uint64(payload.Timestamp) >= *shanghaiTime {
Expand Down Expand Up @@ -373,8 +374,6 @@ type PayloadAttributes struct {
GasLimit *Uint64Quantity `json:"gasLimit,omitempty"`
// EIP-1559 parameters, to be specified only post-Holocene
EIP1559Params *Bytes8 `json:"eip1559Params,omitempty"`
// WithdrawalsRoot, to be specified post Isthmus as the storage root of the L2toL1MessagePasser contract
WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"`
}

// IsDepositsOnly returns whether all transactions of the PayloadAttributes are of Deposit
Expand Down
Loading

0 comments on commit 79609ed

Please sign in to comment.