types.go 11.1 KB
Newer Older
1
package sources
2 3 4 5

import (
	"fmt"
	"math/big"
6
	"strings"
7

8
	"github.com/ethereum/go-ethereum/rlp"
9 10
	"github.com/holiman/uint256"

11
	"github.com/ethereum/go-ethereum/common"
12
	"github.com/ethereum/go-ethereum/common/hexutil"
13
	"github.com/ethereum/go-ethereum/consensus/misc/eip4844"
14
	"github.com/ethereum/go-ethereum/core/types"
15
	"github.com/ethereum/go-ethereum/rpc"
16
	"github.com/ethereum/go-ethereum/trie"
17

18
	"github.com/ethereum-optimism/optimism/op-service/eth"
19 20
)

21
// Note: these types are used, instead of the geth types, to enable:
22 23 24 25
// - batched calls of many block requests (standard bindings do extra uncle-header fetches, cannot be batched nicely)
// - ignore uncle data (does not even exist anymore post-Merge)
// - use cached block hash, if we trust the RPC.
// - verify transactions list matches tx-root, to ensure consistency with block-hash, if we do not trust the RPC
26
// - verify block contents are compatible with Post-Merge ExecutionPayload format
27 28 29 30 31 32
//
// Transaction-sender data from the RPC is not cached, since ethclient.setSenderFromServer is private,
// and we only need to compute the sender for transactions into the inbox.
//
// This way we minimize RPC calls, enable batching, and can choose to verify what the RPC gives us.

33 34 35 36 37
// headerInfo is a conversion type of types.Header turning it into a
// BlockInfo, but using a cached hash value.
type headerInfo struct {
	hash common.Hash
	*types.Header
38 39
}

40
var _ eth.BlockInfo = (*headerInfo)(nil)
41

42 43
func (h headerInfo) Hash() common.Hash {
	return h.hash
44 45
}

46 47
func (h headerInfo) ParentHash() common.Hash {
	return h.Header.ParentHash
48 49
}

50 51
func (h headerInfo) Coinbase() common.Address {
	return h.Header.Coinbase
52 53
}

54 55
func (h headerInfo) Root() common.Hash {
	return h.Header.Root
56 57
}

58 59
func (h headerInfo) NumberU64() uint64 {
	return h.Header.Number.Uint64()
60 61
}

62 63
func (h headerInfo) Time() uint64 {
	return h.Header.Time
64 65
}

66 67
func (h headerInfo) MixDigest() common.Hash {
	return h.Header.MixDigest
68 69
}

70 71
func (h headerInfo) BaseFee() *big.Int {
	return h.Header.BaseFee
72 73
}

74 75 76 77 78 79 80
func (h headerInfo) BlobBaseFee() *big.Int {
	if h.Header.ExcessBlobGas == nil {
		return nil
	}
	return eip4844.CalcBlobFee(*h.Header.ExcessBlobGas)
}

81 82
func (h headerInfo) ReceiptHash() common.Hash {
	return h.Header.ReceiptHash
83 84
}

85 86
func (h headerInfo) GasUsed() uint64 {
	return h.Header.GasUsed
87 88
}

89 90 91 92
func (h headerInfo) GasLimit() uint64 {
	return h.Header.GasLimit
}

93 94 95 96
func (h headerInfo) ParentBeaconRoot() *common.Hash {
	return h.Header.ParentBeaconRoot
}

97 98
func (h headerInfo) HeaderRLP() ([]byte, error) {
	return rlp.EncodeToBytes(h.Header)
99 100
}

101
type RPCHeader struct {
102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
	ParentHash  common.Hash      `json:"parentHash"`
	UncleHash   common.Hash      `json:"sha3Uncles"`
	Coinbase    common.Address   `json:"miner"`
	Root        common.Hash      `json:"stateRoot"`
	TxHash      common.Hash      `json:"transactionsRoot"`
	ReceiptHash common.Hash      `json:"receiptsRoot"`
	Bloom       eth.Bytes256     `json:"logsBloom"`
	Difficulty  hexutil.Big      `json:"difficulty"`
	Number      hexutil.Uint64   `json:"number"`
	GasLimit    hexutil.Uint64   `json:"gasLimit"`
	GasUsed     hexutil.Uint64   `json:"gasUsed"`
	Time        hexutil.Uint64   `json:"timestamp"`
	Extra       hexutil.Bytes    `json:"extraData"`
	MixDigest   common.Hash      `json:"mixHash"`
	Nonce       types.BlockNonce `json:"nonce"`

	// BaseFee was added by EIP-1559 and is ignored in legacy headers.
119 120 121
	BaseFee *hexutil.Big `json:"baseFeePerGas"`

	// WithdrawalsRoot was added by EIP-4895 and is ignored in legacy headers.
122 123 124 125 126 127 128 129 130 131
	WithdrawalsRoot *common.Hash `json:"withdrawalsRoot,omitempty"`

	// BlobGasUsed was added by EIP-4844 and is ignored in legacy headers.
	BlobGasUsed *hexutil.Uint64 `json:"blobGasUsed,omitempty"`

	// ExcessBlobGas was added by EIP-4844 and is ignored in legacy headers.
	ExcessBlobGas *hexutil.Uint64 `json:"excessBlobGas,omitempty"`

	// ParentBeaconRoot was added by EIP-4788 and is ignored in legacy headers.
	ParentBeaconRoot *common.Hash `json:"parentBeaconBlockRoot,omitempty"`
132 133

	// untrusted info included by RPC, may have to be checked
134 135 136
	Hash common.Hash `json:"hash"`
}

137 138
// checkPostMerge checks that the block header meets all criteria to be a valid ExecutionPayloadHeader,
// see EIP-3675 (block header changes) and EIP-4399 (mixHash usage for prev-randao)
139
func (hdr *RPCHeader) checkPostMerge() error {
140 141 142 143 144 145 146 147 148
	// TODO: the genesis block has a non-zero difficulty number value.
	// Either this block needs to change, or we special case it. This is not valid w.r.t. EIP-3675.
	if hdr.Number != 0 && (*big.Int)(&hdr.Difficulty).Cmp(common.Big0) != 0 {
		return fmt.Errorf("post-merge block header requires zeroed difficulty field, but got: %s", &hdr.Difficulty)
	}
	if hdr.Nonce != (types.BlockNonce{}) {
		return fmt.Errorf("post-merge block header requires zeroed block nonce field, but got: %s", hdr.Nonce)
	}
	if hdr.BaseFee == nil {
149
		return fmt.Errorf("post-merge block header requires EIP-1559 base fee field, but got %s", hdr.BaseFee)
150 151 152 153 154 155 156 157
	}
	if len(hdr.Extra) > 32 {
		return fmt.Errorf("post-merge block header requires 32 or less bytes of extra data, but got %d", len(hdr.Extra))
	}
	if hdr.UncleHash != types.EmptyUncleHash {
		return fmt.Errorf("post-merge block header requires uncle hash to be of empty uncle list, but got %s", hdr.UncleHash)
	}
	return nil
158 159
}

160
func (hdr *RPCHeader) computeBlockHash() common.Hash {
161 162 163 164
	gethHeader := hdr.createGethHeader()
	return gethHeader.Hash()
}

165
func (hdr *RPCHeader) createGethHeader() *types.Header {
166
	return &types.Header{
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
		ParentHash:      hdr.ParentHash,
		UncleHash:       hdr.UncleHash,
		Coinbase:        hdr.Coinbase,
		Root:            hdr.Root,
		TxHash:          hdr.TxHash,
		ReceiptHash:     hdr.ReceiptHash,
		Bloom:           types.Bloom(hdr.Bloom),
		Difficulty:      (*big.Int)(&hdr.Difficulty),
		Number:          new(big.Int).SetUint64(uint64(hdr.Number)),
		GasLimit:        uint64(hdr.GasLimit),
		GasUsed:         uint64(hdr.GasUsed),
		Time:            uint64(hdr.Time),
		Extra:           hdr.Extra,
		MixDigest:       hdr.MixDigest,
		Nonce:           hdr.Nonce,
		BaseFee:         (*big.Int)(hdr.BaseFee),
		WithdrawalsHash: hdr.WithdrawalsRoot,
184 185 186 187
		// Cancun
		BlobGasUsed:      (*uint64)(hdr.BlobGasUsed),
		ExcessBlobGas:    (*uint64)(hdr.ExcessBlobGas),
		ParentBeaconRoot: hdr.ParentBeaconRoot,
188 189 190
	}
}

191
func (hdr *RPCHeader) Info(trustCache bool, mustBePostMerge bool) (eth.BlockInfo, error) {
192 193 194 195
	if mustBePostMerge {
		if err := hdr.checkPostMerge(); err != nil {
			return nil, err
		}
196 197
	}
	if !trustCache {
198 199
		if computed := hdr.computeBlockHash(); computed != hdr.Hash {
			return nil, fmt.Errorf("failed to verify block hash: computed %s but RPC said %s", computed, hdr.Hash)
200 201
		}
	}
202
	return &headerInfo{hdr.Hash, hdr.createGethHeader()}, nil
203 204
}

205
func (hdr *RPCHeader) BlockID() eth.BlockID {
206 207 208 209 210 211
	return eth.BlockID{
		Hash:   hdr.Hash,
		Number: uint64(hdr.Number),
	}
}

212 213
type RPCBlock struct {
	RPCHeader
214
	Transactions []*types.Transaction `json:"transactions"`
215
	Withdrawals  *types.Withdrawals   `json:"withdrawals,omitempty"`
216 217
}

218
func (block *RPCBlock) verify() error {
219 220 221
	if computed := block.computeBlockHash(); computed != block.Hash {
		return fmt.Errorf("failed to verify block hash: computed %s but RPC said %s", computed, block.Hash)
	}
222 223
	for i, tx := range block.Transactions {
		if tx == nil {
224
			return fmt.Errorf("block tx %d is nil", i)
225 226
		}
	}
227 228 229
	if computed := types.DeriveSha(types.Transactions(block.Transactions), trie.NewStackTrie(nil)); block.TxHash != computed {
		return fmt.Errorf("failed to verify transactions list: computed %s but RPC said %s", computed, block.TxHash)
	}
230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246
	if block.WithdrawalsRoot != nil {
		if block.Withdrawals == nil {
			return fmt.Errorf("expected withdrawals")
		}
		for i, w := range *block.Withdrawals {
			if w == nil {
				return fmt.Errorf("block withdrawal %d is null", i)
			}
		}
		if computed := types.DeriveSha(*block.Withdrawals, trie.NewStackTrie(nil)); *block.WithdrawalsRoot != computed {
			return fmt.Errorf("failed to verify withdrawals list: computed %s but RPC said %s", computed, block.WithdrawalsRoot)
		}
	} else {
		if block.Withdrawals != nil {
			return fmt.Errorf("expected no withdrawals due to missing withdrawals-root, but got %d", len(*block.Withdrawals))
		}
	}
247
	return nil
248 249
}

250
func (block *RPCBlock) Info(trustCache bool, mustBePostMerge bool) (eth.BlockInfo, types.Transactions, error) {
251 252 253 254 255 256 257 258 259
	if mustBePostMerge {
		if err := block.checkPostMerge(); err != nil {
			return nil, nil, err
		}
	}
	if !trustCache {
		if err := block.verify(); err != nil {
			return nil, nil, err
		}
260 261 262
	}

	// verify the header data
263
	info, err := block.RPCHeader.Info(trustCache, mustBePostMerge)
264
	if err != nil {
265
		return nil, nil, fmt.Errorf("failed to verify block from RPC: %w", err)
266 267
	}

268 269 270
	return info, block.Transactions, nil
}

271
func (block *RPCBlock) ExecutionPayloadEnvelope(trustCache bool) (*eth.ExecutionPayloadEnvelope, error) {
272 273 274 275 276 277
	if err := block.checkPostMerge(); err != nil {
		return nil, err
	}
	if !trustCache {
		if err := block.verify(); err != nil {
			return nil, err
278 279
		}
	}
280 281 282 283 284 285 286 287 288 289 290 291 292 293
	var baseFee uint256.Int
	baseFee.SetFromBig((*big.Int)(block.BaseFee))

	// Unfortunately eth_getBlockByNumber either returns full transactions, or only tx-hashes.
	// There is no option for encoded transactions.
	opaqueTxs := make([]hexutil.Bytes, len(block.Transactions))
	for i, tx := range block.Transactions {
		data, err := tx.MarshalBinary()
		if err != nil {
			return nil, fmt.Errorf("failed to encode tx %d from RPC: %w", i, err)
		}
		opaqueTxs[i] = data
	}

294
	payload := &eth.ExecutionPayload{
295 296 297 298 299 300 301 302 303 304 305
		ParentHash:    block.ParentHash,
		FeeRecipient:  block.Coinbase,
		StateRoot:     eth.Bytes32(block.Root),
		ReceiptsRoot:  eth.Bytes32(block.ReceiptHash),
		LogsBloom:     block.Bloom,
		PrevRandao:    eth.Bytes32(block.MixDigest), // mix-digest field is used for prevRandao post-merge
		BlockNumber:   block.Number,
		GasLimit:      block.GasLimit,
		GasUsed:       block.GasUsed,
		Timestamp:     block.Time,
		ExtraData:     eth.BytesMax32(block.Extra),
306
		BaseFeePerGas: eth.Uint256Quantity(baseFee),
307 308
		BlockHash:     block.Hash,
		Transactions:  opaqueTxs,
Danyal Prout's avatar
Danyal Prout committed
309
		Withdrawals:   block.Withdrawals,
310 311
		BlobGasUsed:   block.BlobGasUsed,
		ExcessBlobGas: block.ExcessBlobGas,
312 313 314 315 316
	}

	return &eth.ExecutionPayloadEnvelope{
		ParentBeaconBlockRoot: block.ParentBeaconRoot,
		ExecutionPayload:      payload,
317
	}, nil
318
}
319 320 321 322 323 324 325 326 327 328 329 330

// blockHashParameter is used as "block parameter":
// Some Nethermind and Alchemy RPC endpoints require an object to identify a block, instead of a string.
type blockHashParameter struct {
	BlockHash common.Hash `json:"blockHash"`
}

// unusableMethod identifies if an error indicates that the RPC method cannot be used as expected:
// if it's an unknown method, or if parameters were invalid.
func unusableMethod(err error) bool {
	if rpcErr, ok := err.(rpc.Error); ok {
		code := rpcErr.ErrorCode()
331 332
		// invalid request, method not found, or invalid params
		if code == -32600 || code == -32601 || code == -32602 {
333 334 335
			return true
		}
	}
336 337 338 339
	errText := strings.ToLower(err.Error())
	return strings.Contains(errText, "unsupported method") || // alchemy -32600 message
		strings.Contains(errText, "unknown method") ||
		strings.Contains(errText, "invalid param") ||
340 341
		strings.Contains(errText, "is not available") ||
		strings.Contains(errText, "rpc method is not whitelisted") // proxyd -32001 error code
342
}