diff --git a/op-node/rollup/derive/attributes_test.go b/op-node/rollup/derive/attributes_test.go index 68c7c71aa1e..e428d952e5b 100644 --- a/op-node/rollup/derive/attributes_test.go +++ b/op-node/rollup/derive/attributes_test.go @@ -195,6 +195,54 @@ func TestPreparePayloadAttributes(t *testing.T) { require.Equal(t, l1InfoTx, []byte(attrs.Transactions[0])) require.True(t, attrs.NoTxPool) }) + t.Run("same origin with deposits on post-Isthmus", func(t *testing.T) { + rng := rand.New(rand.NewSource(1234)) + l1Fetcher := &testutils.MockL1Source{} + defer l1Fetcher.AssertExpectations(t) + l2Parent := testutils.RandomL2BlockRef(rng) + l1CfgFetcher := &testutils.MockL2Client{} + l1CfgFetcher.ExpectSystemConfigByL2Hash(l2Parent.Hash, testSysCfg, nil) + defer l1CfgFetcher.AssertExpectations(t) + l1Info := testutils.RandomBlockInfo(rng) + l1Info.InfoParentHash = l2Parent.L1Origin.Hash + l1Info.InfoNum = l2Parent.L1Origin.Number + 1 + + receipts, depositTxs, err := makeReceipts(rng, l1Info.InfoHash, cfg.DepositContractAddress, []receiptData{ + {goodReceipt: true, DepositLogs: []bool{true, false}}, + {goodReceipt: true, DepositLogs: []bool{true}}, + {goodReceipt: false, DepositLogs: []bool{true}}, + {goodReceipt: false, DepositLogs: []bool{false}}, + }) + require.NoError(t, err) + usedDepositTxs, err := encodeDeposits(depositTxs) + require.NoError(t, err) + + // sets config to post-interop + cfg.AfterHardfork(rollup.Interop, 2) + + epoch := l1Info.ID() + l1InfoTx, err := L1InfoDepositBytes(cfg, testSysCfg, 0, l1Info, 0) + require.NoError(t, err) + + l2Txs := append(append(make([]eth.Data, 0), l1InfoTx), usedDepositTxs...) + + l1Fetcher.ExpectFetchReceipts(epoch.Hash, l1Info, receipts, nil) + attrBuilder := NewFetchingAttributesBuilder(cfg, l1Fetcher, l1CfgFetcher) + attrs, err := attrBuilder.PreparePayloadAttributes(context.Background(), l2Parent, epoch) + require.NoError(t, err) + require.NotNil(t, attrs) + require.Equal(t, l2Parent.Time+cfg.BlockTime, uint64(attrs.Timestamp)) + require.Equal(t, eth.Bytes32(l1Info.InfoMixDigest), attrs.PrevRandao) + require.Equal(t, predeploys.SequencerFeeVaultAddr, attrs.SuggestedFeeRecipient) + require.Equal(t, len(l2Txs), len(attrs.Transactions), "Expected txs to equal l1 info tx + user deposit txs + DepositsComplete") + require.Equal(t, l2Txs, attrs.Transactions) + require.Equal(t, DepositsCompleteBytes4, attrs.Transactions[1+len(depositTxs)][:4]) + require.True(t, attrs.NoTxPool) + depositsCompleteTx, err := DepositsCompleteBytes(l2Parent.SequenceNumber, l1Info) + require.NoError(t, err) + require.Equal(t, l2Txs, append(attrs.Transactions, depositsCompleteTx)) + }) + // Test that the payload attributes builder changes the deposit format based on L2-time-based regolith activation t.Run("regolith", func(t *testing.T) { testCases := []struct { diff --git a/op-node/rollup/derive/deposit_source_test.go b/op-node/rollup/derive/deposit_source_test.go index 10fb7048a2a..d851325e370 100644 --- a/op-node/rollup/derive/deposit_source_test.go +++ b/op-node/rollup/derive/deposit_source_test.go @@ -3,6 +3,7 @@ package derive import ( "testing" + "github.com/ethereum/go-ethereum/common" "github.com/stretchr/testify/assert" ) @@ -34,3 +35,17 @@ func TestEcotone4788ContractSourceHash(t *testing.T) { assert.Equal(t, expected, actual.Hex()) } + +// TestAfterForceIncludeSourceHash +// cast keccak $(cast concat-hex 0x0000000000000000000000000000000000000000000000000000000000000003 $(cast keccak 0x01)) +// # 0x8afb1c4a581d0e71ab65334e3365ba5511fb15c13fa212776f9d4dafc6287845 +func TestAfterForceIncludeSource(t *testing.T) { + source := AfterForceIncludeSource{ + L1BlockHash: common.Hash{0x01}, + } + + actual := source.SourceHash() + expected := "0x8afb1c4a581d0e71ab65334e3365ba5511fb15c13fa212776f9d4dafc6287845" + + assert.Equal(t, expected, actual.Hex()) +} diff --git a/op-node/rollup/derive/fuzz_parsers_test.go b/op-node/rollup/derive/fuzz_parsers_test.go index 95ce94bc7cc..4f76c4ac742 100644 --- a/op-node/rollup/derive/fuzz_parsers_test.go +++ b/op-node/rollup/derive/fuzz_parsers_test.go @@ -83,15 +83,26 @@ func FuzzL1InfoEcotoneRoundTrip(f *testing.F) { } enc, err := in.marshalBinaryEcotone() if err != nil { - t.Fatalf("Failed to marshal binary: %v", err) + t.Fatalf("Failed to marshal Ecotone binary: %v", err) } var out L1BlockInfo err = out.unmarshalBinaryEcotone(enc) if err != nil { - t.Fatalf("Failed to unmarshal binary: %v", err) + t.Fatalf("Failed to unmarshal Ecotone binary: %v", err) } if !cmp.Equal(in, out, cmp.Comparer(testutils.BigEqual)) { - t.Fatalf("The data did not round trip correctly. in: %v. out: %v", in, out) + t.Fatalf("The Ecotone data did not round trip correctly. in: %v. out: %v", in, out) + } + enc, err = in.marshalBinaryIsthmus() + if err != nil { + t.Fatalf("Failed to marshal Isthmus binary: %v", err) + } + err = out.unmarshalBinaryIsthmus(enc) + if err != nil { + t.Fatalf("Failed to unmarshal Isthmus binary: %v", err) + } + if !cmp.Equal(in, out, cmp.Comparer(testutils.BigEqual)) { + t.Fatalf("The Isthmus data did not round trip correctly. in: %v. out: %v", in, out) } }) diff --git a/op-node/rollup/derive/l1_block_info.go b/op-node/rollup/derive/l1_block_info.go index 8c8132f5eff..7631494e42c 100644 --- a/op-node/rollup/derive/l1_block_info.go +++ b/op-node/rollup/derive/l1_block_info.go @@ -26,6 +26,11 @@ const ( L1InfoBedrockLen = 4 + 32*L1InfoArguments L1InfoEcotoneLen = 4 + 32*5 // after Ecotone upgrade, args are packed into 5 32-byte slots DepositsCompleteLen = 4 // only the selector + // We set the gas limit to 15k to ensure that the DepositsComplete Transaction does not run out of gas. + // GasBenchMark_L1BlockIsthmus_DepositsComplete:test_depositsComplete_benchmark() (gas: 7768) + // GasBenchMark_L1BlockIsthmus_DepositsComplete_Warm:test_depositsComplete_benchmark() (gas: 5768) + // see `test_depositsComplete_benchmark` at: `/packages/contracts-bedrock/test/BenchmarkTest.t.sol` + DepositsCompleteGas = uint64(15_000) ) var ( @@ -391,16 +396,12 @@ func DepositsCompleteDeposit(seqNumber uint64, block eth.BlockInfo) (*types.Depo L1BlockHash: block.Hash(), } out := &types.DepositTx{ - SourceHash: source.SourceHash(), - From: L1InfoDepositerAddress, - To: &L1BlockAddress, - Mint: nil, - Value: big.NewInt(0), - // We set the gas limit to 15k to ensure that the DepositsComplete Transaction does not run out of gas. - // GasBenchMark_L1BlockIsthmus_DepositsComplete:test_depositsComplete_benchmark() (gas: 7768) - // GasBenchMark_L1BlockIsthmus_DepositsComplete_Warm:test_depositsComplete_benchmark() (gas: 5768) - // see `test_depositsComplete_benchmark` at: `/packages/contracts-bedrock/test/BenchmarkTest.t.sol` - Gas: 15_000, + SourceHash: source.SourceHash(), + From: L1InfoDepositerAddress, + To: &L1BlockAddress, + Mint: nil, + Value: big.NewInt(0), + Gas: DepositsCompleteGas, IsSystemTransaction: false, Data: DepositsCompleteBytes4, } diff --git a/op-node/rollup/derive/l1_block_info_test.go b/op-node/rollup/derive/l1_block_info_test.go index e5c9253ce1c..0f847309995 100644 --- a/op-node/rollup/derive/l1_block_info_test.go +++ b/op-node/rollup/derive/l1_block_info_test.go @@ -41,6 +41,7 @@ func TestParseL1InfoDepositTxData(t *testing.T) { randomSeqNr := func(rng *rand.Rand) uint64 { return rng.Uint64() } + // Go 1.18 will have native fuzzing for us to use, until then, we cover just the below cases cases := []infoTest{ {"random", testutils.MakeBlockInfo(nil), randomL1Cfg, randomSeqNr}, @@ -109,10 +110,8 @@ func TestParseL1InfoDepositTxData(t *testing.T) { t.Run("regolith", func(t *testing.T) { rng := rand.New(rand.NewSource(1234)) info := testutils.MakeBlockInfo(nil)(rng) - zero := uint64(0) - rollupCfg := rollup.Config{ - RegolithTime: &zero, - } + rollupCfg := rollup.Config{} + rollupCfg.AtHardfork(rollup.Regolith) depTx, err := L1InfoDeposit(&rollupCfg, randomL1Cfg(rng, info), randomSeqNr(rng), info, 0) require.NoError(t, err) require.False(t, depTx.IsSystemTransaction) @@ -121,11 +120,8 @@ func TestParseL1InfoDepositTxData(t *testing.T) { t.Run("ecotone", func(t *testing.T) { rng := rand.New(rand.NewSource(1234)) info := testutils.MakeBlockInfo(nil)(rng) - zero := uint64(0) - rollupCfg := rollup.Config{ - RegolithTime: &zero, - EcotoneTime: &zero, - } + rollupCfg := rollup.Config{} + rollupCfg.AtHardfork(rollup.Ecotone) depTx, err := L1InfoDeposit(&rollupCfg, randomL1Cfg(rng, info), randomSeqNr(rng), info, 1) require.NoError(t, err) require.False(t, depTx.IsSystemTransaction) @@ -135,12 +131,8 @@ func TestParseL1InfoDepositTxData(t *testing.T) { t.Run("first-block ecotone", func(t *testing.T) { rng := rand.New(rand.NewSource(1234)) info := testutils.MakeBlockInfo(nil)(rng) - zero := uint64(2) - rollupCfg := rollup.Config{ - RegolithTime: &zero, - EcotoneTime: &zero, - BlockTime: 2, - } + rollupCfg := rollup.Config{} + rollupCfg.AfterHardfork(rollup.Ecotone, 2) depTx, err := L1InfoDeposit(&rollupCfg, randomL1Cfg(rng, info), randomSeqNr(rng), info, 2) require.NoError(t, err) require.False(t, depTx.IsSystemTransaction) @@ -150,16 +142,75 @@ func TestParseL1InfoDepositTxData(t *testing.T) { t.Run("genesis-block ecotone", func(t *testing.T) { rng := rand.New(rand.NewSource(1234)) info := testutils.MakeBlockInfo(nil)(rng) - zero := uint64(0) - rollupCfg := rollup.Config{ - RegolithTime: &zero, - EcotoneTime: &zero, - BlockTime: 2, - } + rollupCfg := rollup.Config{} + rollupCfg.AfterHardfork(rollup.Ecotone, 2) + depTx, err := L1InfoDeposit(&rollupCfg, randomL1Cfg(rng, info), randomSeqNr(rng), info, 0) + require.NoError(t, err) + require.False(t, depTx.IsSystemTransaction) + require.Equal(t, depTx.Gas, uint64(RegolithSystemTxGas)) + require.Equal(t, L1InfoEcotoneLen, len(depTx.Data)) + }) + t.Run("Isthmus", func(t *testing.T) { + rng := rand.New(rand.NewSource(1234)) + info := testutils.MakeBlockInfo(nil)(rng) + rollupCfg := rollup.Config{} + rollupCfg.AtHardfork(rollup.Interop) + depTx, err := L1InfoDeposit(&rollupCfg, randomL1Cfg(rng, info), randomSeqNr(rng), info, 1) + require.NoError(t, err) + require.False(t, depTx.IsSystemTransaction) + require.Equal(t, depTx.Gas, uint64(RegolithSystemTxGas)) + require.Equal(t, L1InfoEcotoneLen, len(depTx.Data)) + require.Equal(t, L1InfoFuncEcotoneBytes4, depTx.Data[:4]) + // should still send Ecotone signature since upgrade happens after deposits + }) + t.Run("first-block isthmus", func(t *testing.T) { + rng := rand.New(rand.NewSource(1234)) + info := testutils.MakeBlockInfo(nil)(rng) + rollupCfg := rollup.Config{} + rollupCfg.AfterHardfork(rollup.Interop, 2) + depTx, err := L1InfoDeposit(&rollupCfg, randomL1Cfg(rng, info), randomSeqNr(rng), info, 2) + require.NoError(t, err) + require.False(t, depTx.IsSystemTransaction) + require.Equal(t, depTx.Gas, uint64(RegolithSystemTxGas)) + require.Equal(t, L1InfoBedrockLen, len(depTx.Data)) + require.Equal(t, L1InfoFuncIsthmusBytes4, depTx.Data[:4]) + }) + t.Run("genesis-block isthmus", func(t *testing.T) { + rng := rand.New(rand.NewSource(1234)) + info := testutils.MakeBlockInfo(nil)(rng) + rollupCfg := rollup.Config{} + rollupCfg.AfterHardfork(rollup.Interop, 2) depTx, err := L1InfoDeposit(&rollupCfg, randomL1Cfg(rng, info), randomSeqNr(rng), info, 0) require.NoError(t, err) require.False(t, depTx.IsSystemTransaction) require.Equal(t, depTx.Gas, uint64(RegolithSystemTxGas)) require.Equal(t, L1InfoEcotoneLen, len(depTx.Data)) + require.Equal(t, L1InfoBedrockLen, len(depTx.Data)) + }) +} + +func TestDepositsCompleteBytes(t *testing.T) { + randomSeqNr := func(rng *rand.Rand) uint64 { + return rng.Uint64() + } + t.Run("valid return bytes", func(t *testing.T) { + rng := rand.New(rand.NewSource(1234)) + info := testutils.MakeBlockInfo(nil)(rng) + depTxByes, err := DepositsCompleteBytes(randomSeqNr(rng), info) + require.NoError(t, err) + require.Equal(t, depTxByes, DepositsCompleteBytes4) + require.Equal(t, DepositsCompleteLen, len(depTxByes)) + }) + t.Run("valid return Transaction", func(t *testing.T) { + rng := rand.New(rand.NewSource(1234)) + info := testutils.MakeBlockInfo(nil)(rng) + depTx, err := DepositsCompleteDeposit(randomSeqNr(rng), info) + require.NoError(t, err) + require.Equal(t, depTx.Data, DepositsCompleteBytes4) + require.Equal(t, DepositsCompleteLen, len(depTx.Data)) + require.Equal(t, DepositsCompleteGas, depTx.Gas) + require.False(t, depTx.IsSystemTransaction) + require.Equal(t, depTx.Value, big.NewInt(0)) + require.Equal(t, depTx.From, L1InfoDepositerAddress) }) } diff --git a/op-node/rollup/types.go b/op-node/rollup/types.go index 72c3d849625..56cdcedfde2 100644 --- a/op-node/rollup/types.go +++ b/op-node/rollup/types.go @@ -474,6 +474,44 @@ func (c *Config) IsInteropActivationBlock(l2BlockTime uint64) bool { !c.IsInterop(l2BlockTime-c.BlockTime) } +func (c *Config) AtHardfork(hardfork ForkName) { + zero := uint64(0) + // IMPORTANT! ordered from newest to oldest + switch hardfork { + case Interop: + c.InteropTime = &zero + fallthrough + case Holocene: + c.HoloceneTime = &zero + fallthrough + case Granite: + c.GraniteTime = &zero + fallthrough + case Fjord: + c.FjordTime = &zero + fallthrough + case Ecotone: + c.EcotoneTime = &zero + fallthrough + case Delta: + c.DeltaTime = &zero + fallthrough + case Canyon: + c.CanyonTime = &zero + fallthrough + case Regolith: + c.RegolithTime = &zero + fallthrough + case None: + break + } +} +func (c *Config) AfterHardfork(hardfork ForkName, timestamp uint64) { + c.AtHardfork(hardfork) + // required after hardfork time bump + c.BlockTime = timestamp +} + // ForkchoiceUpdatedVersion returns the EngineAPIMethod suitable for the chain hard fork version. func (c *Config) ForkchoiceUpdatedVersion(attr *eth.PayloadAttributes) eth.EngineAPIMethod { if attr == nil {