api.go 5.04 KB
Newer Older
1 2 3 4 5 6
package node

import (
	"context"
	"fmt"

7 8 9 10 11
	"github.com/ethereum/go-ethereum"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/common/hexutil"
	"github.com/ethereum/go-ethereum/log"

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

type l2EthClient interface {
19
	InfoByHash(ctx context.Context, hash common.Hash) (eth.BlockInfo, error)
20
	// GetProof returns a proof of the account, it may return a nil result without error if the address was not found.
21 22
	// Optionally keys of the account storage trie can be specified to include with corresponding values in the proof.
	GetProof(ctx context.Context, address common.Address, storage []common.Hash, blockTag string) (*eth.AccountResult, error)
23 24
}

25
type driverClient interface {
26
	SyncStatus(ctx context.Context) (*eth.SyncStatus, error)
27
	BlockRefWithStatus(ctx context.Context, num uint64) (eth.L2BlockRef, *eth.SyncStatus, error)
28
	ResetDerivationPipeline(context.Context) error
29 30
	StartSequencer(ctx context.Context, blockHash common.Hash) error
	StopSequencer(context.Context) (common.Hash, error)
31
	SequencerActive(context.Context) (bool, error)
32 33
}

34 35 36 37 38
type rpcMetrics interface {
	// RecordRPCServerRequest returns a function that records the duration of serving the given RPC method
	RecordRPCServerRequest(method string) func()
}

39 40
type adminAPI struct {
	dr driverClient
41
	m  rpcMetrics
42 43
}

44
func NewAdminAPI(dr driverClient, m rpcMetrics) *adminAPI {
45 46 47 48 49 50 51 52 53 54
	return &adminAPI{
		dr: dr,
		m:  m,
	}
}

func (n *adminAPI) ResetDerivationPipeline(ctx context.Context) error {
	recordDur := n.m.RecordRPCServerRequest("admin_resetDerivationPipeline")
	defer recordDur()
	return n.dr.ResetDerivationPipeline(ctx)
55 56
}

57
func (n *adminAPI) StartSequencer(ctx context.Context, blockHash common.Hash) error {
58 59
	recordDur := n.m.RecordRPCServerRequest("admin_startSequencer")
	defer recordDur()
60
	return n.dr.StartSequencer(ctx, blockHash)
61 62
}

63
func (n *adminAPI) StopSequencer(ctx context.Context) (common.Hash, error) {
64 65 66 67 68
	recordDur := n.m.RecordRPCServerRequest("admin_stopSequencer")
	defer recordDur()
	return n.dr.StopSequencer(ctx)
}

69 70 71 72 73 74
func (n *adminAPI) SequencerActive(ctx context.Context) (bool, error) {
	recordDur := n.m.RecordRPCServerRequest("admin_sequencerActive")
	defer recordDur()
	return n.dr.SequencerActive(ctx)
}

75 76 77
type nodeAPI struct {
	config *rollup.Config
	client l2EthClient
78
	dr     driverClient
79
	log    log.Logger
80
	m      rpcMetrics
81 82
}

83
func NewNodeAPI(config *rollup.Config, l2Client l2EthClient, dr driverClient, log log.Logger, m rpcMetrics) *nodeAPI {
84 85 86
	return &nodeAPI{
		config: config,
		client: l2Client,
87
		dr:     dr,
88
		log:    log,
89
		m:      m,
90 91 92
	}
}

93
func (n *nodeAPI) OutputAtBlock(ctx context.Context, number hexutil.Uint64) (*eth.OutputResponse, error) {
94 95
	recordDur := n.m.RecordRPCServerRequest("optimism_outputAtBlock")
	defer recordDur()
96

97
	ref, status, err := n.dr.BlockRefWithStatus(ctx, uint64(number))
98
	if err != nil {
99 100 101 102 103 104
		return nil, fmt.Errorf("failed to get L2 block ref with sync status: %w", err)
	}

	head, err := n.client.InfoByHash(ctx, ref.Hash)
	if err != nil {
		return nil, fmt.Errorf("failed to get L2 block by hash %s: %w", ref, err)
105 106 107 108 109
	}
	if head == nil {
		return nil, ethereum.NotFound
	}

110
	proof, err := n.client.GetProof(ctx, predeploys.L2ToL1MessagePasserAddr, []common.Hash{}, ref.Hash.String())
111
	if err != nil {
112
		return nil, fmt.Errorf("failed to get contract proof at block %s: %w", ref, err)
113 114
	}
	if proof == nil {
115
		return nil, fmt.Errorf("proof %w", ethereum.NotFound)
116 117
	}
	// make sure that the proof (including storage hash) that we retrieved is correct by verifying it against the state-root
118 119
	if err := proof.Verify(head.Root()); err != nil {
		n.log.Error("invalid withdrawal root detected in block", "stateRoot", head.Root(), "blocknum", number, "msg", err)
120
		return nil, fmt.Errorf("invalid withdrawal root hash, state root was %s: %w", head.Root(), err)
121 122
	}

123
	var l2OutputRootVersion eth.Bytes32 // it's zero for now
124
	l2OutputRoot, err := rollup.ComputeL2OutputRootV0(head, proof.StorageHash)
125 126 127 128
	if err != nil {
		n.log.Error("Error computing L2 output root, nil ptr passed to hashing function")
		return nil, err
	}
129

130 131 132 133 134 135 136 137
	return &eth.OutputResponse{
		Version:               l2OutputRootVersion,
		OutputRoot:            l2OutputRoot,
		BlockRef:              ref,
		WithdrawalStorageRoot: proof.StorageHash,
		StateRoot:             head.Root(),
		Status:                status,
	}, nil
138 139
}

140
func (n *nodeAPI) SyncStatus(ctx context.Context) (*eth.SyncStatus, error) {
141 142
	recordDur := n.m.RecordRPCServerRequest("optimism_syncStatus")
	defer recordDur()
143 144 145
	return n.dr.SyncStatus(ctx)
}

146 147 148 149 150 151
func (n *nodeAPI) RollupConfig(_ context.Context) (*rollup.Config, error) {
	recordDur := n.m.RecordRPCServerRequest("optimism_rollupConfig")
	defer recordDur()
	return n.config, nil
}

152
func (n *nodeAPI) Version(ctx context.Context) (string, error) {
153 154
	recordDur := n.m.RecordRPCServerRequest("optimism_version")
	defer recordDur()
155 156
	return version.Version + "-" + version.Meta, nil
}