l2_source.go 6.41 KB
Newer Older
1
package common
2 3 4

import (
	"context"
5
	"errors"
6

7
	"github.com/ethereum-optimism/optimism/op-node/rollup"
8
	hosttypes "github.com/ethereum-optimism/optimism/op-program/host/types"
9 10 11 12 13 14 15 16
	"github.com/ethereum-optimism/optimism/op-service/client"
	"github.com/ethereum-optimism/optimism/op-service/eth"
	"github.com/ethereum-optimism/optimism/op-service/sources"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/types"
	"github.com/ethereum/go-ethereum/log"
)

17 18 19 20 21
var (
	ErrExperimentalPrefetchFailed   = errors.New("experimental prefetch failed")
	ErrExperimentalPrefetchDisabled = errors.New("experimental prefetch disabled")
)

22 23 24 25 26 27 28 29 30 31 32 33 34 35
// L2Source is a source of L2 data, it abstracts away the details of how to fetch L2 data between canonical and experimental sources.
// It also tracks metrics for each of the apis. Once experimental sources are stable, this will only route to the "experimental" source.
type L2Source struct {
	logger log.Logger

	// canonical source, used as a fallback if experimental source is enabled but fails
	// the underlying node should be a geth hash scheme archival node.
	canonicalEthClient   *L2Client
	canonicalDebugClient *sources.DebugClient

	// experimental source, used as the primary source if enabled
	experimentalClient *L2Client
}

36
var _ hosttypes.L2Source = &L2Source{}
37 38 39 40 41 42 43 44 45 46 47 48 49

// NewL2SourceWithClient creates a new L2 source with the given client as the canonical client.
// This doesn't configure the experimental source, but is useful for testing.
func NewL2SourceWithClient(logger log.Logger, canonicalL2Client *L2Client, canonicalDebugClient *sources.DebugClient) *L2Source {
	source := &L2Source{
		logger:               logger,
		canonicalEthClient:   canonicalL2Client,
		canonicalDebugClient: canonicalDebugClient,
	}

	return source
}

50
func NewL2SourceFromRPC(logger log.Logger, rollupCfg *rollup.Config, canonicalL2RPC client.RPC, experimentalRPC client.RPC) (*L2Source, error) {
51 52
	canonicalDebugClient := sources.NewDebugClient(canonicalL2RPC.CallContext)

53
	canonicalL2ClientCfg := sources.L2ClientDefaultConfig(rollupCfg, true)
54
	canonicalL2Client, err := NewL2Client(canonicalL2RPC, logger, nil, &L2ClientConfig{L2ClientConfig: canonicalL2ClientCfg})
55 56 57 58 59 60
	if err != nil {
		return nil, err
	}

	source := NewL2SourceWithClient(logger, canonicalL2Client, canonicalDebugClient)

61
	if experimentalRPC == nil {
62 63 64
		return source, nil
	}

65
	experimentalL2ClientCfg := sources.L2ClientDefaultConfig(rollupCfg, true)
66
	experimentalL2Client, err := NewL2Client(experimentalRPC, logger, nil, &L2ClientConfig{L2ClientConfig: experimentalL2ClientCfg})
67 68 69 70 71 72 73 74 75
	if err != nil {
		return nil, err
	}

	source.experimentalClient = experimentalL2Client

	return source, nil
}

76 77 78 79
func (s *L2Source) RollupConfig() *rollup.Config {
	return s.canonicalEthClient.RollupConfig()
}

80 81 82 83 84 85 86 87 88 89 90 91 92 93
func (l *L2Source) ExperimentalEnabled() bool {
	return l.experimentalClient != nil
}

// CodeByHash implements prefetcher.L2Source.
func (l *L2Source) CodeByHash(ctx context.Context, hash common.Hash) ([]byte, error) {
	if l.ExperimentalEnabled() {
		// This means experimental source was not able to retrieve relevant information from eth_getProof or debug_executionWitness
		// We should fall back to the canonical source, and log a warning, and record a metric
		l.logger.Warn("Experimental source failed to retrieve code by hash, falling back to canonical source", "hash", hash)
	}
	return l.canonicalDebugClient.CodeByHash(ctx, hash)
}

94 95 96 97 98 99 100 101
// FetchReceipts implements prefetcher.L2Source.
func (l *L2Source) FetchReceipts(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Receipts, error) {
	if l.ExperimentalEnabled() {
		return l.experimentalClient.FetchReceipts(ctx, blockHash)
	}
	return l.canonicalEthClient.FetchReceipts(ctx, blockHash)
}

102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
// NodeByHash implements prefetcher.L2Source.
func (l *L2Source) NodeByHash(ctx context.Context, hash common.Hash) ([]byte, error) {
	if l.ExperimentalEnabled() {
		// This means experimental source was not able to retrieve relevant information from eth_getProof or debug_executionWitness
		// We should fall back to the canonical source, and log a warning, and record a metric
		l.logger.Warn("Experimental source failed to retrieve node by hash, falling back to canonical source", "hash", hash)
	}
	return l.canonicalDebugClient.NodeByHash(ctx, hash)
}

// InfoAndTxsByHash implements prefetcher.L2Source.
func (l *L2Source) InfoAndTxsByHash(ctx context.Context, blockHash common.Hash) (eth.BlockInfo, types.Transactions, error) {
	if l.ExperimentalEnabled() {
		return l.experimentalClient.InfoAndTxsByHash(ctx, blockHash)
	}
	return l.canonicalEthClient.InfoAndTxsByHash(ctx, blockHash)
}

// OutputByRoot implements prefetcher.L2Source.
121
func (l *L2Source) OutputByRoot(ctx context.Context, blockRoot common.Hash) (eth.Output, error) {
122
	if l.ExperimentalEnabled() {
123
		return l.experimentalClient.OutputByRoot(ctx, blockRoot)
124
	}
125
	return l.canonicalEthClient.OutputByRoot(ctx, blockRoot)
126 127
}

128 129 130 131 132 133 134 135
// OutputByBlockNumber implements prefetcher.L2Source.
func (l *L2Source) OutputByNumber(ctx context.Context, blockNum uint64) (eth.Output, error) {
	if l.ExperimentalEnabled() {
		return l.experimentalClient.OutputByNumber(ctx, blockNum)
	}
	return l.canonicalEthClient.OutputByNumber(ctx, blockNum)
}

136 137 138 139
// ExecutionWitness implements prefetcher.L2Source.
func (l *L2Source) ExecutionWitness(ctx context.Context, blockNum uint64) (*eth.ExecutionWitness, error) {
	if !l.ExperimentalEnabled() {
		l.logger.Error("Experimental source is not enabled, cannot fetch execution witness", "blockNum", blockNum)
140
		return nil, ErrExperimentalPrefetchDisabled
141 142 143 144 145 146
	}

	// log errors, but return standard error so we know to retry with legacy source
	witness, err := l.experimentalClient.ExecutionWitness(ctx, blockNum)
	if err != nil {
		l.logger.Error("Failed to fetch execution witness from experimental source", "blockNum", blockNum, "err", err)
147
		return nil, ErrExperimentalPrefetchFailed
148 149 150 151 152 153 154 155 156 157 158 159
	}
	return witness, nil
}

// GetProof implements prefetcher.L2Source.
func (l *L2Source) GetProof(ctx context.Context, address common.Address, storage []common.Hash, blockTag string) (*eth.AccountResult, error) {
	if l.ExperimentalEnabled() {
		return l.experimentalClient.GetProof(ctx, address, storage, blockTag)
	}
	proof, err := l.canonicalEthClient.GetProof(ctx, address, storage, blockTag)
	if err != nil {
		l.logger.Error("Failed to fetch proof from canonical source", "address", address, "storage", storage, "blockTag", blockTag, "err", err)
160
		return nil, ErrExperimentalPrefetchFailed
161 162 163
	}
	return proof, nil
}