log_processor.go 4 KB
Newer Older
1 2 3 4
package source

import (
	"context"
5
	"errors"
6 7
	"fmt"

8 9 10 11
	"github.com/ethereum/go-ethereum/common"
	ethTypes "github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/crypto"

12
	"github.com/ethereum-optimism/optimism/op-service/eth"
13
	"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/backend/source/contracts"
14
	"github.com/ethereum-optimism/optimism/op-supervisor/supervisor/types"
15 16 17
)

type LogStorage interface {
Axel Kingsley's avatar
Axel Kingsley committed
18
	SealBlock(chain types.ChainID, block eth.BlockRef) error
19 20 21 22
	AddLog(chain types.ChainID, logHash common.Hash, parentBlock eth.BlockID, logIdx uint32, execMsg *types.ExecutingMessage) error
}

type ChainsDBClientForLogProcessor interface {
Axel Kingsley's avatar
Axel Kingsley committed
23
	SealBlock(chain types.ChainID, block eth.BlockRef) error
24
	AddLog(chain types.ChainID, logHash common.Hash, parentBlock eth.BlockID, logIdx uint32, execMsg *types.ExecutingMessage) error
25 26 27
}

type EventDecoder interface {
28
	DecodeExecutingMessageLog(log *ethTypes.Log) (types.ExecutingMessage, error)
29 30 31
}

type logProcessor struct {
32
	chain        types.ChainID
33 34
	logStore     LogStorage
	eventDecoder EventDecoder
35 36
}

37
func newLogProcessor(chain types.ChainID, logStore LogStorage) *logProcessor {
38 39 40 41 42
	return &logProcessor{
		chain:        chain,
		logStore:     logStore,
		eventDecoder: contracts.NewCrossL2Inbox(),
	}
43 44
}

45 46
// ProcessLogs processes logs from a block and stores them in the log storage
// for any logs that are related to executing messages, they are decoded and stored
Axel Kingsley's avatar
Axel Kingsley committed
47
func (p *logProcessor) ProcessLogs(_ context.Context, block eth.BlockRef, rcpts ethTypes.Receipts) error {
48 49
	for _, rcpt := range rcpts {
		for _, l := range rcpt.Logs {
50 51
			// log hash represents the hash of *this* log as a potentially initiating message
			logHash := logToLogHash(l)
52
			var execMsg *types.ExecutingMessage
53 54 55 56 57 58 59 60 61
			msg, err := p.eventDecoder.DecodeExecutingMessageLog(l)
			if err != nil && !errors.Is(err, contracts.ErrEventNotFound) {
				return fmt.Errorf("failed to decode executing message log: %w", err)
			} else if err == nil {
				// if the log is an executing message, store the message
				execMsg = &msg
			}
			// executing messages have multiple entries in the database
			// they should start with the initiating message and then include the execution
62
			err = p.logStore.AddLog(p.chain, logHash, block.ParentID(), uint32(l.Index), execMsg)
63 64 65 66 67
			if err != nil {
				return fmt.Errorf("failed to add log %d from block %v: %w", l.Index, block.ID(), err)
			}
		}
	}
68
	if err := p.logStore.SealBlock(p.chain, block); err != nil {
69 70
		return fmt.Errorf("failed to seal block %s: %w", block.ID(), err)
	}
71 72 73
	return nil
}

74 75 76 77 78
// logToLogHash transforms a log into a hash that represents the log.
// it is the concatenation of the log's address and the hash of the log's payload,
// which is then hashed again. This is the hash that is stored in the log storage.
// The address is hashed into the payload hash to save space in the log storage,
// and because they represent paired data.
79
func logToLogHash(l *ethTypes.Log) common.Hash {
80 81
	payloadHash := crypto.Keccak256(logToMessagePayload(l))
	return payloadHashToLogHash(common.Hash(payloadHash), l.Address)
82 83
}

84 85 86 87
// logToMessagePayload is the data that is hashed to get the logHash
// it is the concatenation of the log's topics and data
// the implementation is based on the interop messaging spec
func logToMessagePayload(l *ethTypes.Log) []byte {
88 89 90 91 92 93 94
	msg := make([]byte, 0)
	for _, topic := range l.Topics {
		msg = append(msg, topic.Bytes()...)
	}
	msg = append(msg, l.Data...)
	return msg
}
95 96 97 98 99 100

// payloadHashToLogHash converts the payload hash to the log hash
// it is the concatenation of the log's address and the hash of the log's payload,
// which is then hashed. This is the hash that is stored in the log storage.
// The logHash can then be used to traverse from the executing message
// to the log the referenced initiating message.
101
func payloadHashToLogHash(payloadHash common.Hash, addr common.Address) common.Hash {
102 103 104
	msg := make([]byte, 0, 2*common.HashLength)
	msg = append(msg, addr.Bytes()...)
	msg = append(msg, payloadHash.Bytes()...)
105
	return crypto.Keccak256Hash(msg)
106
}