attributes.go 4.84 KB
Newer Older
1 2 3 4 5 6 7 8 9
package derive

import (
	"context"
	"fmt"

	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/common/hexutil"
	"github.com/ethereum/go-ethereum/core/types"
10 11 12 13

	"github.com/ethereum-optimism/optimism/op-bindings/predeploys"
	"github.com/ethereum-optimism/optimism/op-node/eth"
	"github.com/ethereum-optimism/optimism/op-node/rollup"
14 15 16 17
)

// L1ReceiptsFetcher fetches L1 header info and receipts for the payload attributes derivation (the info tx and deposits)
type L1ReceiptsFetcher interface {
18
	InfoByHash(ctx context.Context, hash common.Hash) (eth.BlockInfo, error)
19
	FetchReceipts(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Receipts, error)
20 21
}

22 23 24 25
type SystemConfigL2Fetcher interface {
	SystemConfigByL2Hash(ctx context.Context, hash common.Hash) (eth.SystemConfig, error)
}

26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
// FetchingAttributesBuilder fetches inputs for the building of L2 payload attributes on the fly.
type FetchingAttributesBuilder struct {
	cfg *rollup.Config
	l1  L1ReceiptsFetcher
	l2  SystemConfigL2Fetcher
}

func NewFetchingAttributesBuilder(cfg *rollup.Config, l1 L1ReceiptsFetcher, l2 SystemConfigL2Fetcher) *FetchingAttributesBuilder {
	return &FetchingAttributesBuilder{
		cfg: cfg,
		l1:  l1,
		l2:  l2,
	}
}

41 42 43 44 45
// PreparePayloadAttributes prepares a PayloadAttributes template that is ready to build a L2 block with deposits only, on top of the given l2Parent, with the given epoch as L1 origin.
// The template defaults to NoTxPool=true, and no sequencer transactions: the caller has to modify the template to add transactions,
// by setting NoTxPool=false as sequencer, or by appending batch transactions as verifier.
// The severity of the error is returned; a crit=false error means there was a temporary issue, like a failed RPC or time-out.
// A crit=true error means the input arguments are inconsistent or invalid.
46
func (ba *FetchingAttributesBuilder) PreparePayloadAttributes(ctx context.Context, l2Parent eth.L2BlockRef, epoch eth.BlockID) (attrs *eth.PayloadAttributes, err error) {
47
	var l1Info eth.BlockInfo
48 49 50
	var depositTxs []hexutil.Bytes
	var seqNumber uint64

51
	sysConfig, err := ba.l2.SystemConfigByL2Hash(ctx, l2Parent.Hash)
52 53 54 55
	if err != nil {
		return nil, NewTemporaryError(fmt.Errorf("failed to retrieve L2 parent block: %w", err))
	}

56 57 58 59
	// If the L1 origin changed this block, then we are in the first block of the epoch. In this
	// case we need to fetch all transaction receipts from the L1 origin block so we can scan for
	// user deposits.
	if l2Parent.L1Origin.Number != epoch.Number {
60
		info, receipts, err := ba.l1.FetchReceipts(ctx, epoch.Hash)
61
		if err != nil {
62
			return nil, NewTemporaryError(fmt.Errorf("failed to fetch L1 block info and receipts: %w", err))
63 64
		}
		if l2Parent.L1Origin.Hash != info.ParentHash() {
65
			return nil, NewResetError(
66 67
				fmt.Errorf("cannot create new block with L1 origin %s (parent %s) on top of L1 origin %s",
					epoch, info.ParentHash(), l2Parent.L1Origin))
68
		}
69

70
		deposits, err := DeriveDeposits(receipts, ba.cfg.DepositContractAddress)
71
		if err != nil {
72 73
			// deposits may never be ignored. Failing to process them is a critical error.
			return nil, NewCriticalError(fmt.Errorf("failed to derive some deposits: %w", err))
74
		}
75
		// apply sysCfg changes
76
		if err := UpdateSystemConfigWithL1Receipts(&sysConfig, receipts, ba.cfg); err != nil {
77 78 79
			return nil, NewCriticalError(fmt.Errorf("failed to apply derived L1 sysCfg updates: %w", err))
		}

80 81 82 83 84
		l1Info = info
		depositTxs = deposits
		seqNumber = 0
	} else {
		if l2Parent.L1Origin.Hash != epoch.Hash {
85
			return nil, NewResetError(fmt.Errorf("cannot create new block with L1 origin %s in conflict with L1 origin %s", epoch, l2Parent.L1Origin))
86
		}
87
		info, err := ba.l1.InfoByHash(ctx, epoch.Hash)
88
		if err != nil {
89
			return nil, NewTemporaryError(fmt.Errorf("failed to fetch L1 block info: %w", err))
90 91 92 93 94 95
		}
		l1Info = info
		depositTxs = nil
		seqNumber = l2Parent.SequenceNumber + 1
	}

96 97 98 99 100 101 102
	// Sanity check the L1 origin was correctly selected to maintain the time invariant between L1 and L2
	nextL2Time := l2Parent.Time + ba.cfg.BlockTime
	if nextL2Time < l1Info.Time() {
		return nil, NewResetError(fmt.Errorf("cannot build L2 block on top %s for time %d before L1 origin %s at time %d",
			l2Parent, nextL2Time, eth.ToBlockID(l1Info), l1Info.Time()))
	}

103
	l1InfoTx, err := L1InfoDepositBytes(seqNumber, l1Info, sysConfig, ba.cfg.IsRegolith(nextL2Time))
104
	if err != nil {
105
		return nil, NewCriticalError(fmt.Errorf("failed to create l1InfoTx: %w", err))
106 107 108 109 110 111 112
	}

	txs := make([]hexutil.Bytes, 0, 1+len(depositTxs))
	txs = append(txs, l1InfoTx)
	txs = append(txs, depositTxs...)

	return &eth.PayloadAttributes{
113
		Timestamp:             hexutil.Uint64(nextL2Time),
114
		PrevRandao:            eth.Bytes32(l1Info.MixDigest()),
115
		SuggestedFeeRecipient: predeploys.SequencerFeeVaultAddr,
116 117
		Transactions:          txs,
		NoTxPool:              true,
118
		GasLimit:              (*eth.Uint64Quantity)(&sysConfig.GasLimit),
119
	}, nil
120
}