attributes_test.go 10.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
package derive

import (
	"context"
	"errors"
	"fmt"
	"math/big"
	"math/rand"
	"testing"

11 12
	"github.com/stretchr/testify/require"

13 14 15
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"

16
	"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
17
	"github.com/ethereum-optimism/optimism/op-node/rollup"
18
	"github.com/ethereum-optimism/optimism/op-service/eth"
Sabnock01's avatar
Sabnock01 committed
19
	"github.com/ethereum-optimism/optimism/op-service/testutils"
20 21 22
)

func TestPreparePayloadAttributes(t *testing.T) {
23
	// test sysCfg, only init the necessary fields
24 25 26 27 28
	cfg := &rollup.Config{
		BlockTime:              2,
		L1ChainID:              big.NewInt(101),
		L2ChainID:              big.NewInt(102),
		DepositContractAddress: common.Address{0xbb},
29 30 31 32 33 34 35
		L1SystemConfigAddress:  common.Address{0xcc},
	}

	testSysCfg := eth.SystemConfig{
		BatcherAddr: common.Address{42},
		Overhead:    [32]byte{},
		Scalar:      [32]byte{},
36 37 38 39 40 41 42
	}

	t.Run("inconsistent next height origin", func(t *testing.T) {
		rng := rand.New(rand.NewSource(1234))
		l1Fetcher := &testutils.MockL1Source{}
		defer l1Fetcher.AssertExpectations(t)
		l2Parent := testutils.RandomL2BlockRef(rng)
43 44 45
		l1CfgFetcher := &testutils.MockL2Client{}
		l1CfgFetcher.ExpectSystemConfigByL2Hash(l2Parent.Hash, testSysCfg, nil)
		defer l1CfgFetcher.AssertExpectations(t)
46
		l1Info := testutils.RandomBlockInfo(rng)
47 48
		l1Info.InfoNum = l2Parent.L1Origin.Number + 1
		epoch := l1Info.ID()
49
		l1Fetcher.ExpectFetchReceipts(epoch.Hash, l1Info, nil, nil)
50 51
		attrBuilder := NewFetchingAttributesBuilder(cfg, l1Fetcher, l1CfgFetcher)
		_, err := attrBuilder.PreparePayloadAttributes(context.Background(), l2Parent, epoch)
52
		require.NotNil(t, err, "inconsistent L1 origin error expected")
53
		require.ErrorIs(t, err, ErrReset, "inconsistent L1 origin transition must be handled like a critical error with reorg")
54 55 56 57 58 59
	})
	t.Run("inconsistent equal height origin", func(t *testing.T) {
		rng := rand.New(rand.NewSource(1234))
		l1Fetcher := &testutils.MockL1Source{}
		defer l1Fetcher.AssertExpectations(t)
		l2Parent := testutils.RandomL2BlockRef(rng)
60 61 62
		l1CfgFetcher := &testutils.MockL2Client{}
		l1CfgFetcher.ExpectSystemConfigByL2Hash(l2Parent.Hash, testSysCfg, nil)
		defer l1CfgFetcher.AssertExpectations(t)
63
		l1Info := testutils.RandomBlockInfo(rng)
64 65
		l1Info.InfoNum = l2Parent.L1Origin.Number
		epoch := l1Info.ID()
66 67
		attrBuilder := NewFetchingAttributesBuilder(cfg, l1Fetcher, l1CfgFetcher)
		_, err := attrBuilder.PreparePayloadAttributes(context.Background(), l2Parent, epoch)
68
		require.NotNil(t, err, "inconsistent L1 origin error expected")
69
		require.ErrorIs(t, err, ErrReset, "inconsistent L1 origin transition must be handled like a critical error with reorg")
70 71 72 73 74 75
	})
	t.Run("rpc fail Fetch", func(t *testing.T) {
		rng := rand.New(rand.NewSource(1234))
		l1Fetcher := &testutils.MockL1Source{}
		defer l1Fetcher.AssertExpectations(t)
		l2Parent := testutils.RandomL2BlockRef(rng)
76 77 78
		l1CfgFetcher := &testutils.MockL2Client{}
		l1CfgFetcher.ExpectSystemConfigByL2Hash(l2Parent.Hash, testSysCfg, nil)
		defer l1CfgFetcher.AssertExpectations(t)
79 80 81
		epoch := l2Parent.L1Origin
		epoch.Number += 1
		mockRPCErr := errors.New("mock rpc error")
82
		l1Fetcher.ExpectFetchReceipts(epoch.Hash, nil, nil, mockRPCErr)
83 84
		attrBuilder := NewFetchingAttributesBuilder(cfg, l1Fetcher, l1CfgFetcher)
		_, err := attrBuilder.PreparePayloadAttributes(context.Background(), l2Parent, epoch)
85
		require.ErrorIs(t, err, mockRPCErr, "mock rpc error expected")
86
		require.ErrorIs(t, err, ErrTemporary, "rpc errors should not be critical, it is not necessary to reorg")
87 88 89 90 91 92
	})
	t.Run("rpc fail InfoByHash", func(t *testing.T) {
		rng := rand.New(rand.NewSource(1234))
		l1Fetcher := &testutils.MockL1Source{}
		defer l1Fetcher.AssertExpectations(t)
		l2Parent := testutils.RandomL2BlockRef(rng)
93 94 95
		l1CfgFetcher := &testutils.MockL2Client{}
		l1CfgFetcher.ExpectSystemConfigByL2Hash(l2Parent.Hash, testSysCfg, nil)
		defer l1CfgFetcher.AssertExpectations(t)
96 97 98
		epoch := l2Parent.L1Origin
		mockRPCErr := errors.New("mock rpc error")
		l1Fetcher.ExpectInfoByHash(epoch.Hash, nil, mockRPCErr)
99 100
		attrBuilder := NewFetchingAttributesBuilder(cfg, l1Fetcher, l1CfgFetcher)
		_, err := attrBuilder.PreparePayloadAttributes(context.Background(), l2Parent, epoch)
101
		require.ErrorIs(t, err, mockRPCErr, "mock rpc error expected")
102
		require.ErrorIs(t, err, ErrTemporary, "rpc errors should not be critical, it is not necessary to reorg")
103 104 105 106 107 108
	})
	t.Run("next origin without deposits", func(t *testing.T) {
		rng := rand.New(rand.NewSource(1234))
		l1Fetcher := &testutils.MockL1Source{}
		defer l1Fetcher.AssertExpectations(t)
		l2Parent := testutils.RandomL2BlockRef(rng)
109 110 111
		l1CfgFetcher := &testutils.MockL2Client{}
		l1CfgFetcher.ExpectSystemConfigByL2Hash(l2Parent.Hash, testSysCfg, nil)
		defer l1CfgFetcher.AssertExpectations(t)
112
		l1Info := testutils.RandomBlockInfo(rng)
113 114 115
		l1Info.InfoParentHash = l2Parent.L1Origin.Hash
		l1Info.InfoNum = l2Parent.L1Origin.Number + 1
		epoch := l1Info.ID()
116
		l1InfoTx, err := L1InfoDepositBytes(0, l1Info, testSysCfg, false)
117
		require.NoError(t, err)
118
		l1Fetcher.ExpectFetchReceipts(epoch.Hash, l1Info, nil, nil)
119 120
		attrBuilder := NewFetchingAttributesBuilder(cfg, l1Fetcher, l1CfgFetcher)
		attrs, err := attrBuilder.PreparePayloadAttributes(context.Background(), l2Parent, epoch)
121 122 123 124
		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)
125
		require.Equal(t, predeploys.SequencerFeeVaultAddr, attrs.SuggestedFeeRecipient)
126 127 128 129 130 131 132 133 134
		require.Equal(t, 1, len(attrs.Transactions))
		require.Equal(t, l1InfoTx, []byte(attrs.Transactions[0]))
		require.True(t, attrs.NoTxPool)
	})
	t.Run("next origin with deposits", func(t *testing.T) {
		rng := rand.New(rand.NewSource(1234))
		l1Fetcher := &testutils.MockL1Source{}
		defer l1Fetcher.AssertExpectations(t)
		l2Parent := testutils.RandomL2BlockRef(rng)
135 136 137
		l1CfgFetcher := &testutils.MockL2Client{}
		l1CfgFetcher.ExpectSystemConfigByL2Hash(l2Parent.Hash, testSysCfg, nil)
		defer l1CfgFetcher.AssertExpectations(t)
138
		l1Info := testutils.RandomBlockInfo(rng)
139 140 141
		l1Info.InfoParentHash = l2Parent.L1Origin.Hash
		l1Info.InfoNum = l2Parent.L1Origin.Number + 1

142
		receipts, depositTxs, err := makeReceipts(rng, l1Info.InfoHash, cfg.DepositContractAddress, []receiptData{
143 144 145 146 147
			{goodReceipt: true, DepositLogs: []bool{true, false}},
			{goodReceipt: true, DepositLogs: []bool{true}},
			{goodReceipt: false, DepositLogs: []bool{true}},
			{goodReceipt: false, DepositLogs: []bool{false}},
		})
148
		require.NoError(t, err)
149 150 151 152
		usedDepositTxs, err := encodeDeposits(depositTxs)
		require.NoError(t, err)

		epoch := l1Info.ID()
153
		l1InfoTx, err := L1InfoDepositBytes(0, l1Info, testSysCfg, false)
154 155 156 157
		require.NoError(t, err)

		l2Txs := append(append(make([]eth.Data, 0), l1InfoTx), usedDepositTxs...)

158
		l1Fetcher.ExpectFetchReceipts(epoch.Hash, l1Info, receipts, nil)
159 160
		attrBuilder := NewFetchingAttributesBuilder(cfg, l1Fetcher, l1CfgFetcher)
		attrs, err := attrBuilder.PreparePayloadAttributes(context.Background(), l2Parent, epoch)
161 162 163 164
		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)
165
		require.Equal(t, predeploys.SequencerFeeVaultAddr, attrs.SuggestedFeeRecipient)
166 167 168 169 170 171 172 173 174
		require.Equal(t, len(l2Txs), len(attrs.Transactions), "Expected txs to equal l1 info tx + user deposit txs")
		require.Equal(t, l2Txs, attrs.Transactions)
		require.True(t, attrs.NoTxPool)
	})
	t.Run("same origin again", func(t *testing.T) {
		rng := rand.New(rand.NewSource(1234))
		l1Fetcher := &testutils.MockL1Source{}
		defer l1Fetcher.AssertExpectations(t)
		l2Parent := testutils.RandomL2BlockRef(rng)
175 176 177
		l1CfgFetcher := &testutils.MockL2Client{}
		l1CfgFetcher.ExpectSystemConfigByL2Hash(l2Parent.Hash, testSysCfg, nil)
		defer l1CfgFetcher.AssertExpectations(t)
178
		l1Info := testutils.RandomBlockInfo(rng)
179 180 181 182
		l1Info.InfoHash = l2Parent.L1Origin.Hash
		l1Info.InfoNum = l2Parent.L1Origin.Number

		epoch := l1Info.ID()
183
		l1InfoTx, err := L1InfoDepositBytes(l2Parent.SequenceNumber+1, l1Info, testSysCfg, false)
184 185 186
		require.NoError(t, err)

		l1Fetcher.ExpectInfoByHash(epoch.Hash, l1Info, nil)
187 188
		attrBuilder := NewFetchingAttributesBuilder(cfg, l1Fetcher, l1CfgFetcher)
		attrs, err := attrBuilder.PreparePayloadAttributes(context.Background(), l2Parent, epoch)
189 190 191 192
		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)
193
		require.Equal(t, predeploys.SequencerFeeVaultAddr, attrs.SuggestedFeeRecipient)
194 195 196 197
		require.Equal(t, 1, len(attrs.Transactions))
		require.Equal(t, l1InfoTx, []byte(attrs.Transactions[0]))
		require.True(t, attrs.NoTxPool)
	})
198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244
	// 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 {
			name         string
			l1Time       uint64
			l2ParentTime uint64
			regolithTime uint64
			regolith     bool
		}{
			{"exactly", 900, 1000 - cfg.BlockTime, 1000, true},
			{"almost", 900, 1000 - cfg.BlockTime - 1, 1000, false},
			{"inactive", 700, 700, 1000, false},
			{"l1 time before regolith", 1000, 1001, 1001, true},
			{"l1 time way before regolith", 1000, 2000, 2000, true},
			{"l1 time before regoltih and l2 after", 1000, 3000, 2000, true},
		}
		for _, tc := range testCases {
			t.Run(tc.name, func(t *testing.T) {
				cfgCopy := *cfg // copy, we are making regolith config modifications
				cfg := &cfgCopy
				rng := rand.New(rand.NewSource(1234))
				l1Fetcher := &testutils.MockL1Source{}
				defer l1Fetcher.AssertExpectations(t)
				l2Parent := testutils.RandomL2BlockRef(rng)
				cfg.RegolithTime = &tc.regolithTime
				l2Parent.Time = tc.l2ParentTime

				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
				l1Info.InfoTime = tc.l1Time

				epoch := l1Info.ID()
				l1InfoTx, err := L1InfoDepositBytes(0, l1Info, testSysCfg, tc.regolith)
				require.NoError(t, err)
				l1Fetcher.ExpectFetchReceipts(epoch.Hash, l1Info, nil, nil)
				attrBuilder := NewFetchingAttributesBuilder(cfg, l1Fetcher, l1CfgFetcher)
				attrs, err := attrBuilder.PreparePayloadAttributes(context.Background(), l2Parent, epoch)
				require.NoError(t, err)
				require.Equal(t, l1InfoTx, []byte(attrs.Transactions[0]))
			})
		}
	})
245 246 247 248 249 250
}

func encodeDeposits(deposits []*types.DepositTx) (out []eth.Data, err error) {
	for i, tx := range deposits {
		opaqueTx, err := types.NewTx(tx).MarshalBinary()
		if err != nil {
251
			return nil, fmt.Errorf("bad deposit %d: %w", i, err)
252 253 254 255 256
		}
		out = append(out, opaqueTx)
	}
	return
}