Skip to content

Commit

Permalink
Modifications to support snap sync (#152)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
piersy authored and karlb committed Dec 16, 2024
1 parent c36c300 commit 15e9e4b
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 14 deletions.
6 changes: 6 additions & 0 deletions consensus/misc/eip1559/eip1559.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,12 @@ func ValidateHoloceneExtraData(extra []byte) error {
// CalcBaseFee calculates the basefee of the header.
// The time belongs to the new block to check which upgrades are active.
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)
Expand Down
3 changes: 3 additions & 0 deletions core/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,9 @@ func (g *Genesis) toBlockWithRoot(root common.Hash) *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 {
Expand Down
21 changes: 14 additions & 7 deletions core/types/celo_block.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{}
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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
}
21 changes: 19 additions & 2 deletions core/types/receipt.go
Original file line number Diff line number Diff line change
Expand Up @@ -556,13 +556,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)
Expand Down Expand Up @@ -611,6 +614,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 {
Expand Down
3 changes: 2 additions & 1 deletion eth/downloader/queue.go
Original file line number Diff line number Diff line change
Expand Up @@ -793,7 +793,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 {
Expand Down
8 changes: 7 additions & 1 deletion eth/filters/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -307,7 +307,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
Expand Down
10 changes: 7 additions & 3 deletions internal/ethapi/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -1033,7 +1033,8 @@ func (api *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rp
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))
}

Expand All @@ -1042,9 +1043,12 @@ func (api *BlockChainAPI) GetBlockReceipts(ctx context.Context, blockNrOrHash rp

result := make([]map[string]interface{}, len(receipts))
for i, receipt := range receipts {
result[i] = marshalReceipt(receipt, block.Hash(), block.NumberU64(), signer, txs[i], i, api.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, api.b.ChainConfig())
}
}

return result, nil
}

Expand Down
60 changes: 60 additions & 0 deletions internal/ethapi/celo_block_receipt.go
Original file line number Diff line number Diff line change
@@ -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
}

0 comments on commit 15e9e4b

Please sign in to comment.