log_processor_test.go 6.71 KB
Newer Older
1 2 3 4
package source

import (
	"context"
5
	"fmt"
6 7 8
	"testing"

	"github.com/ethereum-optimism/optimism/op-service/eth"
9
	"github.com/ethereum-optimism/optimism/op-service/predeploys"
10
	"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
11
	"github.com/ethereum/go-ethereum/common"
12
	ethTypes "github.com/ethereum/go-ethereum/core/types"
13 14 15
	"github.com/stretchr/testify/require"
)

16
var logProcessorChainID = types.ChainIDFromUInt64(4)
17

18 19
func TestLogProcessor(t *testing.T) {
	ctx := context.Background()
20
	block1 := eth.L2BlockRef{
21 22 23 24 25
		ParentHash: common.Hash{0x42},
		Number:     100,
		Hash:       common.Hash{0x11},
		Time:       1111,
	}
26 27
	t.Run("NoOutputWhenLogsAreEmpty", func(t *testing.T) {
		store := &stubLogStorage{}
28
		processor := newLogProcessor(logProcessorChainID, store)
29

30
		err := processor.ProcessLogs(ctx, block1, ethTypes.Receipts{})
31 32 33 34 35
		require.NoError(t, err)
		require.Empty(t, store.logs)
	})

	t.Run("OutputLogs", func(t *testing.T) {
36
		rcpts := ethTypes.Receipts{
37
			{
38
				Logs: []*ethTypes.Log{
39 40 41 42 43 44 45 46 47 48 49 50 51
					{
						Address: common.Address{0x11},
						Topics:  []common.Hash{{0xaa}},
						Data:    []byte{0xbb},
					},
					{
						Address: common.Address{0x22},
						Topics:  []common.Hash{{0xcc}},
						Data:    []byte{0xdd},
					},
				},
			},
			{
52
				Logs: []*ethTypes.Log{
53 54 55 56 57 58 59 60 61
					{
						Address: common.Address{0x33},
						Topics:  []common.Hash{{0xee}},
						Data:    []byte{0xff},
					},
				},
			},
		}
		store := &stubLogStorage{}
62
		processor := newLogProcessor(logProcessorChainID, store)
63 64 65

		err := processor.ProcessLogs(ctx, block1, rcpts)
		require.NoError(t, err)
66
		expectedLogs := []storedLog{
67
			{
68 69 70 71
				parent:  block1.ParentID(),
				logIdx:  0,
				logHash: logToLogHash(rcpts[0].Logs[0]),
				execMsg: nil,
72 73
			},
			{
74 75 76 77
				parent:  block1.ParentID(),
				logIdx:  0,
				logHash: logToLogHash(rcpts[0].Logs[1]),
				execMsg: nil,
78 79
			},
			{
80 81 82 83 84 85 86 87 88 89 90
				parent:  block1.ParentID(),
				logIdx:  0,
				logHash: logToLogHash(rcpts[1].Logs[0]),
				execMsg: nil,
			},
		}
		require.Equal(t, expectedLogs, store.logs)

		expectedBlocks := []storedSeal{
			{
				parent:    block1.ParentHash,
91 92 93 94
				block:     block1.ID(),
				timestamp: block1.Time,
			},
		}
95
		require.Equal(t, expectedBlocks, store.seals)
96
	})
97 98 99 100 101 102 103 104 105 106 107 108 109

	t.Run("IncludeExecutingMessage", func(t *testing.T) {
		rcpts := ethTypes.Receipts{
			{
				Logs: []*ethTypes.Log{
					{
						Address: predeploys.CrossL2InboxAddr,
						Topics:  []common.Hash{},
						Data:    []byte{0xff},
					},
				},
			},
		}
110
		execMsg := types.ExecutingMessage{
111 112 113 114
			Chain:     4,
			BlockNum:  6,
			LogIdx:    8,
			Timestamp: 10,
115
			Hash:      common.Hash{0xaa},
116 117
		}
		store := &stubLogStorage{}
118 119
		processor := newLogProcessor(types.ChainID{4}, store)
		processor.eventDecoder = EventDecoderFn(func(l *ethTypes.Log) (types.ExecutingMessage, error) {
120 121 122 123 124 125 126 127
			require.Equal(t, rcpts[0].Logs[0], l)
			return execMsg, nil
		})

		err := processor.ProcessLogs(ctx, block1, rcpts)
		require.NoError(t, err)
		expected := []storedLog{
			{
128 129 130 131 132 133 134 135 136 137 138
				parent:  block1.ParentID(),
				logIdx:  0,
				logHash: logToLogHash(rcpts[0].Logs[0]),
				execMsg: &execMsg,
			},
		}
		require.Equal(t, expected, store.logs)

		expectedBlocks := []storedSeal{
			{
				parent:    block1.ParentHash,
139 140 141 142
				block:     block1.ID(),
				timestamp: block1.Time,
			},
		}
143
		require.Equal(t, expectedBlocks, store.seals)
144
	})
145 146 147
}

func TestToLogHash(t *testing.T) {
148 149
	mkLog := func() *ethTypes.Log {
		return &ethTypes.Log{
150 151 152 153 154 155 156 157 158 159 160 161 162 163
			Address: common.Address{0xaa, 0xbb},
			Topics: []common.Hash{
				{0xcc},
				{0xdd},
			},
			Data:        []byte{0xee, 0xff, 0x00},
			BlockNumber: 12345,
			TxHash:      common.Hash{0x11, 0x22, 0x33},
			TxIndex:     4,
			BlockHash:   common.Hash{0x44, 0x55},
			Index:       8,
			Removed:     false,
		}
	}
164 165 166 167 168 169 170 171
	relevantMods := []func(l *ethTypes.Log){
		func(l *ethTypes.Log) { l.Address = common.Address{0xab, 0xcd} },
		func(l *ethTypes.Log) { l.Topics = append(l.Topics, common.Hash{0x12, 0x34}) },
		func(l *ethTypes.Log) { l.Topics = l.Topics[:len(l.Topics)-1] },
		func(l *ethTypes.Log) { l.Topics[0] = common.Hash{0x12, 0x34} },
		func(l *ethTypes.Log) { l.Data = append(l.Data, 0x56) },
		func(l *ethTypes.Log) { l.Data = l.Data[:len(l.Data)-1] },
		func(l *ethTypes.Log) { l.Data[0] = 0x45 },
172
	}
173 174 175 176 177 178 179
	irrelevantMods := []func(l *ethTypes.Log){
		func(l *ethTypes.Log) { l.BlockNumber = 987 },
		func(l *ethTypes.Log) { l.TxHash = common.Hash{0xab, 0xcd} },
		func(l *ethTypes.Log) { l.TxIndex = 99 },
		func(l *ethTypes.Log) { l.BlockHash = common.Hash{0xab, 0xcd} },
		func(l *ethTypes.Log) { l.Index = 98 },
		func(l *ethTypes.Log) { l.Removed = true },
180
	}
181
	refHash := logToLogHash(mkLog())
182
	// The log hash is stored in the database so test that it matches the actual value.
183
	// If this changes, compatibility with existing databases may be affected
184
	expectedRefHash := common.HexToHash("0x4e1dc08fddeb273275f787762cdfe945cf47bb4e80a1fabbc7a825801e81b73f")
185 186 187 188 189 190
	require.Equal(t, expectedRefHash, refHash, "reference hash changed, check that database compatibility is not broken")

	// Check that the hash is changed when any data it should include changes
	for i, mod := range relevantMods {
		l := mkLog()
		mod(l)
191
		hash := logToLogHash(l)
192 193 194 195 196 197
		require.NotEqualf(t, refHash, hash, "expected relevant modification %v to affect the hash but it did not", i)
	}
	// Check that the hash is not changed when any data it should not include changes
	for i, mod := range irrelevantMods {
		l := mkLog()
		mod(l)
198
		hash := logToLogHash(l)
199 200 201 202 203
		require.Equal(t, refHash, hash, "expected irrelevant modification %v to not affect the hash but it did", i)
	}
}

type stubLogStorage struct {
204 205
	logs  []storedLog
	seals []storedSeal
206 207
}

208
func (s *stubLogStorage) SealBlock(chainID types.ChainID, block eth.L2BlockRef) error {
209 210 211
	if logProcessorChainID != chainID {
		return fmt.Errorf("chain id mismatch, expected %v but got %v", logProcessorChainID, chainID)
	}
212
	s.seals = append(s.seals, storedSeal{
213 214 215
		parent:    block.ParentHash,
		block:     block.ID(),
		timestamp: block.Time,
216 217 218 219
	})
	return nil
}

220
func (s *stubLogStorage) AddLog(chainID types.ChainID, logHash common.Hash, parentBlock eth.BlockID, logIdx uint32, execMsg *types.ExecutingMessage) error {
221 222 223 224 225 226 227 228 229 230 231 232 233 234
	if logProcessorChainID != chainID {
		return fmt.Errorf("chain id mismatch, expected %v but got %v", logProcessorChainID, chainID)
	}
	s.logs = append(s.logs, storedLog{
		parent:  parentBlock,
		logIdx:  logIdx,
		logHash: logHash,
		execMsg: execMsg,
	})
	return nil
}

type storedSeal struct {
	parent    common.Hash
235 236
	block     eth.BlockID
	timestamp uint64
237 238 239 240 241
}

type storedLog struct {
	parent  eth.BlockID
	logIdx  uint32
242 243
	logHash common.Hash
	execMsg *types.ExecutingMessage
244 245
}

246
type EventDecoderFn func(*ethTypes.Log) (types.ExecutingMessage, error)
247

248
func (f EventDecoderFn) DecodeExecutingMessageLog(log *ethTypes.Log) (types.ExecutingMessage, error) {
249
	return f(log)
250
}