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 d69d9ed261..55a6e08aea 100644 --- a/core/genesis.go +++ b/core/genesis.go @@ -485,6 +485,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 +}