From 811e35d9470f913ab622f50c9e8774990220e956 Mon Sep 17 00:00:00 2001 From: piersy Date: Tue, 25 Jun 2024 14:16:27 +0100 Subject: [PATCH] Modifications to support snap sync (#152) * Add code to handle block receipts Add code to handle the extra block receipt added by celo to the list of receipts in a block and also adds the GetBlockReceipt rpc api which was an additional api added by celo to allow retrieveal of the block receipt. * Ensure non nil difficulty in pre-gingerbread blocks Nil difficulty in headers causes problems because it is assumed by the go-ethereum codebase that difficulty is non nil. The specific problem requiring this fix was a NPE in Downloader.processHeaders where the total difficulty (td) is calculated by adding individual header difficulty values. Rather than try to fix this specific case it seems safer to ensure blocks have a non nil Difficulty which should help future proof our code since it upholds the assumptions of the go-ethereum codebase. * Skip uncle hash check for pre gingerbread headers Pre gingerbread headers do not have a valid uncle hash because they have no notion of uncles. * Handle choosing the cel2 transition block base fee If the parent block has a base fee then we use that, otherwise we use the initial base fee. --- consensus/misc/eip1559/eip1559.go | 6 +++ core/genesis.go | 3 ++ core/types/celo_block.go | 21 ++++++---- core/types/receipt.go | 21 +++++++++- eth/downloader/queue.go | 3 +- eth/filters/filter.go | 8 +++- internal/ethapi/api.go | 10 +++-- internal/ethapi/celo_block_receipt.go | 60 +++++++++++++++++++++++++++ 8 files changed, 118 insertions(+), 14 deletions(-) create mode 100644 internal/ethapi/celo_block_receipt.go diff --git a/consensus/misc/eip1559/eip1559.go b/consensus/misc/eip1559/eip1559.go index a66298af69..d21e41654d 100644 --- a/consensus/misc/eip1559/eip1559.go +++ b/consensus/misc/eip1559/eip1559.go @@ -58,6 +58,12 @@ func VerifyEIP1559Header(config *params.ChainConfig, parent, header *types.Heade // CalcBaseFee calculates the basefee of the header. // The time belongs to the new block to check if Canyon is activted or not func CalcBaseFee(config *params.ChainConfig, parent *types.Header, time uint64) *big.Int { + // If this is the cel2 transition block and the parent block has a base fee + // then use that. + if config.Cel2Time != nil && *config.Cel2Time == time && parent.BaseFee != nil { + return parent.BaseFee + } + // If the current block is the first EIP-1559 block, return the InitialBaseFee. if !config.IsLondon(parent.Number) { return new(big.Int).SetUint64(params.InitialBaseFee) diff --git a/core/genesis.go b/core/genesis.go index cd844d483f..177c05d4e4 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -490,6 +490,9 @@ func (g *Genesis) ToBlock() *types.Block { if g.Difficulty == nil && g.Mixhash == (common.Hash{}) { head.Difficulty = params.GenesisDifficulty } + } else if g.Difficulty == nil { + // In the case of migrated chains we ensure a zero rather than nil difficulty. + head.Difficulty = new(big.Int) } if g.Config != nil && g.Config.IsLondon(common.Big0) { if g.BaseFee != nil { diff --git a/core/types/celo_block.go b/core/types/celo_block.go index 07aebf7139..075c856087 100644 --- a/core/types/celo_block.go +++ b/core/types/celo_block.go @@ -53,6 +53,7 @@ func (h *Header) DecodeRLP(s *rlp.Stream) error { h.GasUsed = decodedHeader.GasUsed h.Time = decodedHeader.Time h.Extra = decodedHeader.Extra + h.Difficulty = new(big.Int) } else { // After gingerbread decodedHeader := afterGingerbreadHeader{} @@ -85,13 +86,7 @@ func (h *Header) DecodeRLP(s *rlp.Stream) error { // EncodeRLP implements encodes the Header to an RLP data stream. func (h *Header) EncodeRLP(w io.Writer) error { - // We check for a pre gingerbread header by looking for (GasLimit == 0) - // here. We don't use Difficulty because CopyHeader can end up setting a - // nil Difficulty to a zero difficulty, so testing for nil difficulty is - // not reliable, and post gingerbread difficulty is hardcoded to zero. Also - // testing for base fee is not reliable because some older eth blocks had - // no base fee and they are used in some tests. - if h.GasLimit == 0 { + if h.IsPreGingerbread() { // Encode the header encodedHeader := beforeGingerbreadHeader{ ParentHash: h.ParentHash, @@ -152,3 +147,15 @@ func isPreGingerbreadHeader(buf []byte) (bool, error) { return contentSize == common.AddressLength, nil } + +// Returns if the header is a gingerbread header by looking at the gas limit. +func (h *Header) IsPreGingerbread() bool { + // We check for a pre gingerbread header by looking for (GasLimit == 0) + // here. We don't use Difficulty because we ensure that headers have a zero + // difficulty, even if it's not set in the rlp encoded form (we do this + // because the go ethereum codebase assumed non nil difficulties) and post + // gingerbread difficulty is hardcoded to zero. Also testing for base fee + // is not reliable because some older eth blocks had no base fee and they + // are used in some tests. + return h.GasLimit == 0 +} diff --git a/core/types/receipt.go b/core/types/receipt.go index 5ce8302dd3..58984c056f 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -600,13 +600,16 @@ func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, nu signer := MakeSigner(config, new(big.Int).SetUint64(number), time) logIndex := uint(0) - if len(txs) != len(rs) { + + // If rs are one longer than txs it indicates the presence of a celo block receipt. + if len(txs) != len(rs) && len(txs)+1 != len(rs) { return errors.New("transaction and receipt count mismatch") } - for i := 0; i < len(rs); i++ { + for i := 0; i < len(txs); i++ { // The transaction type and hash can be retrieved from the transaction itself rs[i].Type = txs[i].Type() rs[i].TxHash = txs[i].Hash() + // The CeloDynamicFeeTxs set the baseFee in the receipt if txs[i].Type() != CeloDynamicFeeTxV2Type { rs[i].EffectiveGasPrice = txs[i].inner.effectiveGasPrice(new(big.Int), baseFee) @@ -655,6 +658,20 @@ func (rs Receipts) DeriveFields(config *params.ChainConfig, hash common.Hash, nu logIndex++ } } + + // This is a celo block receipt, which uses the block hash in place of the tx hash. + if len(txs)+1 == len(rs) { + j := len(txs) + for k := 0; k < len(rs[j].Logs); k++ { + rs[j].Logs[k].BlockNumber = number + rs[j].Logs[k].BlockHash = hash + rs[j].Logs[k].TxHash = hash + rs[j].Logs[k].TxIndex = uint(j) + rs[j].Logs[k].Index = logIndex + logIndex++ + } + } + if config.Optimism != nil && len(txs) >= 2 && config.IsBedrock(new(big.Int).SetUint64(number)) { // need at least an info tx and a non-info tx gasParams, err := extractL1GasParams(config, time, txs[0].Data()) if err != nil { diff --git a/eth/downloader/queue.go b/eth/downloader/queue.go index 6ff858d755..06005d96e6 100644 --- a/eth/downloader/queue.go +++ b/eth/downloader/queue.go @@ -782,7 +782,8 @@ func (q *queue) DeliverBodies(id string, txLists [][]*types.Transaction, txListH if txListHashes[index] != header.TxHash { return errInvalidBody } - if uncleListHashes[index] != header.UncleHash { + // Pre gingerbread headers do not have a valid uncle hash. + if !header.IsPreGingerbread() && uncleListHashes[index] != header.UncleHash { return errInvalidBody } if header.WithdrawalsHash == nil { diff --git a/eth/filters/filter.go b/eth/filters/filter.go index 83e3284a2b..664df4cf03 100644 --- a/eth/filters/filter.go +++ b/eth/filters/filter.go @@ -325,7 +325,13 @@ func (f *Filter) checkMatches(ctx context.Context, header *types.Header) ([]*typ for i, log := range logs { // Copy log not to modify cache elements logcopy := *log - logcopy.TxHash = body.Transactions[logcopy.TxIndex].Hash() + // Block receipts reference a non existent transaction ocurring after the last transaction. + // We use the block hash in place of the transaction hash for the block receipt. + if logcopy.TxIndex == uint(len(body.Transactions)) { + logcopy.TxHash = logcopy.BlockHash + } else { + logcopy.TxHash = body.Transactions[logcopy.TxIndex].Hash() + } logs[i] = &logcopy } return logs, nil diff --git a/internal/ethapi/api.go b/internal/ethapi/api.go index 3594bccc10..725e097dd9 100644 --- a/internal/ethapi/api.go +++ b/internal/ethapi/api.go @@ -1015,7 +1015,8 @@ func (s *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc. return nil, err } txs := block.Transactions() - if len(txs) != len(receipts) { + // Legacy Celo blocks sometimes include an extra block receipt. See https://docs.celo.org/developer/migrate/from-ethereum#core-contract-calls + if len(txs) != len(receipts) && len(txs)+1 != len(receipts) { return nil, fmt.Errorf("receipts length mismatch: %d vs %d", len(txs), len(receipts)) } @@ -1024,9 +1025,12 @@ func (s *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rpc. result := make([]map[string]interface{}, len(receipts)) for i, receipt := range receipts { - result[i] = marshalReceipt(receipt, block.Hash(), block.NumberU64(), signer, txs[i], i, s.b.ChainConfig()) + if i == len(txs) { + result[i] = marshalBlockReceipt(receipt, block.Hash(), block.NumberU64(), i) + } else { + result[i] = marshalReceipt(receipt, block.Hash(), block.NumberU64(), signer, txs[i], i, s.b.ChainConfig()) + } } - return result, nil } diff --git a/internal/ethapi/celo_block_receipt.go b/internal/ethapi/celo_block_receipt.go new file mode 100644 index 0000000000..e89b3a684a --- /dev/null +++ b/internal/ethapi/celo_block_receipt.go @@ -0,0 +1,60 @@ +package ethapi + +import ( + "context" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/common/hexutil" + "github.com/ethereum/go-ethereum/core/types" +) + +// GetBlockReceipt returns "system calls" receipt for the block with the given block hash. +func (s *BlockChainAPI) GetBlockReceipt(ctx context.Context, hash common.Hash) (map[string]interface{}, error) { + block, err := s.b.BlockByHash(ctx, hash) + if block == nil || err != nil { + // If no header with that hash is found, err gives "header for hash not found". + // But we return nil with no error, to match the behavior of eth_getBlockByHash and eth_getTransactionReceipt in these cases. + return nil, nil + } + index := block.Transactions().Len() + blockNumber := block.NumberU64() + receipts, err := s.b.GetReceipts(ctx, block.Hash()) + // GetReceipts() doesn't return an error if things go wrong, so we also check len(receipts) + if err != nil || len(receipts) < index { + return nil, err + } + + var receipt *types.Receipt + if len(receipts) == index { + // The block didn't have any logs from system calls and no receipt was created. + // So we create an empty receipt to return, similarly to how system receipts are created. + receipt = types.NewReceipt(nil, false, 0) + receipt.Bloom = types.CreateBloom(types.Receipts{receipt}) + } else { + receipt = receipts[index] + } + return marshalBlockReceipt(receipt, hash, blockNumber, index), nil +} + +// marshalBlockReceipt marshals a Celo block receipt into a JSON object. See https://docs.celo.org/developer/migrate/from-ethereum#core-contract-calls +func marshalBlockReceipt(receipt *types.Receipt, blockHash common.Hash, blockNumber uint64, index int) map[string]interface{} { + fields := map[string]interface{}{ + "blockHash": blockHash, + "blockNumber": hexutil.Uint64(blockNumber), + "transactionHash": blockHash, + "transactionIndex": hexutil.Uint64(index), + "from": common.Address{}, + "to": nil, + "gasUsed": hexutil.Uint64(0), + "cumulativeGasUsed": hexutil.Uint64(0), + "contractAddress": nil, + "logs": receipt.Logs, + "logsBloom": receipt.Bloom, + "type": hexutil.Uint(0), + "status": hexutil.Uint(types.ReceiptStatusSuccessful), + } + if receipt.Logs == nil { + fields["logs"] = []*types.Log{} + } + return fields +}