From aa17e2d008a2ff09c77b234f01e0d5c9d408457e Mon Sep 17 00:00:00 2001 From: pcw109550 Date: Sat, 4 Nov 2023 04:23:20 +0900 Subject: [PATCH 01/10] post-Canyon receipt-root deposit tx hashing fix --- cmd/rpcdaemon/commands/erigon_block.go | 4 +- cmd/rpcdaemon/commands/eth_api.go | 12 ++- cmd/rpcdaemon/commands/eth_block.go | 8 +- cmd/rpcdaemon/commands/eth_receipts.go | 9 +- cmd/rpcdaemon/commands/eth_txs.go | 27 +++--- cmd/rpcdaemon/commands/eth_uncles.go | 8 +- cmd/rpcdaemon/commands/graphql_api.go | 4 +- cmd/rpcdaemon/commands/otterscan_api.go | 19 ++--- .../commands/otterscan_search_trace.go | 13 +-- core/state_processor.go | 9 +- core/types/receipt.go | 85 ++++++++++++++----- turbo/adapter/ethapi/api.go | 30 ++++--- turbo/adapter/ethapi/internal.go | 8 +- 13 files changed, 145 insertions(+), 91 deletions(-) diff --git a/cmd/rpcdaemon/commands/erigon_block.go b/cmd/rpcdaemon/commands/erigon_block.go index f1ada108f22..efd38d90f46 100644 --- a/cmd/rpcdaemon/commands/erigon_block.go +++ b/cmd/rpcdaemon/commands/erigon_block.go @@ -190,8 +190,8 @@ func buildBlockResponse(br services.FullBlockReader, db kv.Tx, blockNum uint64, additionalFields["totalDifficulty"] = (*hexutil.Big)(td) } - depositNonces := rawdb.ReadDepositNonces(db, blockNum) - response, err := ethapi.RPCMarshalBlockEx(block, true, fullTx, nil, common.Hash{}, additionalFields, depositNonces) + receipts := rawdb.ReadRawReceipts(db, blockNum) + response, err := ethapi.RPCMarshalBlockEx(block, true, fullTx, nil, common.Hash{}, additionalFields, receipts) if err == nil && rpc.BlockNumber(block.NumberU64()) == rpc.PendingBlockNumber { // Pending blocks need to nil out a few fields diff --git a/cmd/rpcdaemon/commands/eth_api.go b/cmd/rpcdaemon/commands/eth_api.go index e29d9ebde41..1dd16ac62e0 100644 --- a/cmd/rpcdaemon/commands/eth_api.go +++ b/cmd/rpcdaemon/commands/eth_api.go @@ -403,12 +403,14 @@ type RPCTransaction struct { SourceHash *common.Hash `json:"sourceHash,omitempty"` Mint *hexutil.Big `json:"mint,omitempty"` IsSystemTx *bool `json:"isSystemTx,omitempty"` + // deposit-tx post-Canyon only + DepositReceiptVersion *hexutil.Uint64 `json:"depositReceiptVersion,omitempty"` } // newRPCTransaction returns a transaction that will serialize to the RPC // representation, with the given location metadata set (if available). func newRPCTransaction(tx types.Transaction, blockHash common.Hash, blockNumber uint64, index uint64, baseFee *big.Int, - depositNonce *uint64) *RPCTransaction { + receipt *types.Receipt) *RPCTransaction { // Determine the signer. For replay-protected transactions, use the most permissive // signer, because we assume that signers are backwards-compatible with old // transactions. For non-protected transactions, the homestead signer signer is used @@ -464,8 +466,12 @@ func newRPCTransaction(tx types.Transaction, blockHash common.Hash, blockNumber if t.IsSystemTransaction { result.IsSystemTx = &t.IsSystemTransaction } - if depositNonce != nil { - result.Nonce = hexutil.Uint64(*depositNonce) + if receipt != nil && receipt.DepositNonce != nil { + result.Nonce = hexutil.Uint64(*receipt.DepositNonce) + if receipt.DepositReceiptVersion != nil { + result.DepositReceiptVersion = new(hexutil.Uint64) + *result.DepositReceiptVersion = hexutil.Uint64(*receipt.DepositReceiptVersion) + } } result.GasPrice = (*hexutil.Big)(common.Big0) // must contain v, r, s values for backwards compatibility. diff --git a/cmd/rpcdaemon/commands/eth_block.go b/cmd/rpcdaemon/commands/eth_block.go index 0b8a4f393d3..a4cb78fb63b 100644 --- a/cmd/rpcdaemon/commands/eth_block.go +++ b/cmd/rpcdaemon/commands/eth_block.go @@ -231,8 +231,8 @@ func (api *APIImpl) GetBlockByNumber(ctx context.Context, number rpc.BlockNumber } } - depositNonces := rawdb.ReadDepositNonces(tx, b.NumberU64()) - response, err := ethapi.RPCMarshalBlockEx(b, true, fullTx, borTx, borTxHash, additionalFields, depositNonces) + receipts := rawdb.ReadRawReceipts(tx, b.NumberU64()) + response, err := ethapi.RPCMarshalBlockEx(b, true, fullTx, borTx, borTxHash, additionalFields, receipts) if err == nil && number == rpc.PendingBlockNumber { // Pending blocks need to nil out a few fields for _, field := range []string{"hash", "nonce", "miner"} { @@ -291,8 +291,8 @@ func (api *APIImpl) GetBlockByHash(ctx context.Context, numberOrHash rpc.BlockNu } } - depositNonces := rawdb.ReadDepositNonces(tx, number) - response, err := ethapi.RPCMarshalBlockEx(block, true, fullTx, borTx, borTxHash, additionalFields, depositNonces) + receipts := rawdb.ReadRawReceipts(tx, number) + response, err := ethapi.RPCMarshalBlockEx(block, true, fullTx, borTx, borTxHash, additionalFields, receipts) if chainConfig.Bor != nil { response["miner"], _ = ecrecover(block.Header(), chainConfig.Bor) diff --git a/cmd/rpcdaemon/commands/eth_receipts.go b/cmd/rpcdaemon/commands/eth_receipts.go index 3ec0efa0013..af90a6ba8b6 100644 --- a/cmd/rpcdaemon/commands/eth_receipts.go +++ b/cmd/rpcdaemon/commands/eth_receipts.go @@ -773,8 +773,13 @@ func marshalReceipt(receipt *types.Receipt, txn types.Transaction, chainConfig * fields["l1GasUsed"] = hexutil.Big(*receipt.L1GasUsed) fields["l1Fee"] = hexutil.Big(*receipt.L1Fee) fields["l1FeeScalar"] = receipt.FeeScalar - } else if receipt.DepositNonce != nil { - fields["depositNonce"] = hexutil.Uint64(*receipt.DepositNonce) + } else { + if receipt.DepositNonce != nil { + fields["depositNonce"] = hexutil.Uint64(*receipt.DepositNonce) + } + if receipt.DepositReceiptVersion != nil { + fields["depositReceiptVersion"] = hexutil.Uint64(*receipt.DepositReceiptVersion) + } } } diff --git a/cmd/rpcdaemon/commands/eth_txs.go b/cmd/rpcdaemon/commands/eth_txs.go index defafccbaf5..c4c6b888554 100644 --- a/cmd/rpcdaemon/commands/eth_txs.go +++ b/cmd/rpcdaemon/commands/eth_txs.go @@ -86,12 +86,11 @@ func (api *APIImpl) GetTransactionByHash(ctx context.Context, txnHash common.Has } if chainConfig.IsOptimism() { - depositNonces := rawdb.ReadDepositNonces(tx, block.NumberU64()) - if txnIndex >= uint64(len(depositNonces)) { - return nil, fmt.Errorf("depositNonce for tx %x not found", txnHash) - } else { - return newRPCTransaction(txn, blockHash, blockNum, txnIndex, baseFee, depositNonces[txnIndex]), nil + receipts := rawdb.ReadRawReceipts(tx, block.NumberU64()) + if len(receipts) <= int(txnIndex) { + return nil, fmt.Errorf("block has less receipts than expected: %d <= %d, block: %d", len(receipts), int(txnIndex), blockNum) } + return newRPCTransaction(txn, blockHash, blockNum, txnIndex, baseFee, receipts[txnIndex]), nil } return newRPCTransaction(txn, blockHash, blockNum, txnIndex, baseFee, nil), nil } @@ -209,12 +208,11 @@ func (api *APIImpl) GetTransactionByBlockHashAndIndex(ctx context.Context, block } if chainConfig.IsOptimism() { - depositNonces := rawdb.ReadDepositNonces(tx, block.NumberU64()) - if uint64(txIndex) >= uint64(len(depositNonces)) { - return nil, fmt.Errorf("depositNonce for tx %x not found", txs[txIndex].Hash()) - } else { - return newRPCTransaction(txs[txIndex], block.Hash(), block.NumberU64(), uint64(txIndex), block.BaseFee(), depositNonces[txIndex]), nil + receipts := rawdb.ReadRawReceipts(tx, block.NumberU64()) + if len(receipts) <= int(txIndex) { + return nil, fmt.Errorf("block has less receipts than expected: %d <= %d, block: %d", len(receipts), int(txIndex), block.NumberU64()) } + return newRPCTransaction(txs[txIndex], block.Hash(), block.NumberU64(), uint64(txIndex), block.BaseFee(), receipts[txIndex]), nil } return newRPCTransaction(txs[txIndex], block.Hash(), block.NumberU64(), uint64(txIndex), block.BaseFee(), nil), nil } @@ -280,12 +278,11 @@ func (api *APIImpl) GetTransactionByBlockNumberAndIndex(ctx context.Context, blo return newRPCBorTransaction(borTx, derivedBorTxHash, block.Hash(), block.NumberU64(), uint64(txIndex), block.BaseFee(), chainConfig.ChainID), nil } if chainConfig.IsOptimism() { - depositNonces := rawdb.ReadDepositNonces(tx, blockNum) - if uint64(txIndex) >= uint64(len(depositNonces)) { - return nil, fmt.Errorf("depositNonce for tx %x not found", txs[txIndex].Hash()) - } else { - return newRPCTransaction(txs[txIndex], block.Hash(), blockNum, uint64(txIndex), block.BaseFee(), depositNonces[txIndex]), nil + receipts := rawdb.ReadRawReceipts(tx, block.NumberU64()) + if len(receipts) <= int(txIndex) { + return nil, fmt.Errorf("block has less receipts than expected: %d <= %d, block: %d", len(receipts), int(txIndex), block.NumberU64()) } + return newRPCTransaction(txs[txIndex], block.Hash(), block.NumberU64(), uint64(txIndex), block.BaseFee(), receipts[txIndex]), nil } return newRPCTransaction(txs[txIndex], block.Hash(), block.NumberU64(), uint64(txIndex), block.BaseFee(), nil), nil } diff --git a/cmd/rpcdaemon/commands/eth_uncles.go b/cmd/rpcdaemon/commands/eth_uncles.go index 952ebe0324f..cfb58fde3df 100644 --- a/cmd/rpcdaemon/commands/eth_uncles.go +++ b/cmd/rpcdaemon/commands/eth_uncles.go @@ -46,8 +46,8 @@ func (api *APIImpl) GetUncleByBlockNumberAndIndex(ctx context.Context, number rp return nil, nil } uncle := types.NewBlockWithHeader(uncles[index]) - depositNonces := rawdb.ReadDepositNonces(tx, blockNum) - return ethapi.RPCMarshalBlock(uncle, false, false, additionalFields, depositNonces) + receipts := rawdb.ReadRawReceipts(tx, blockNum) + return ethapi.RPCMarshalBlock(uncle, false, false, additionalFields, receipts) } // GetUncleByBlockHashAndIndex implements eth_getUncleByBlockHashAndIndex. Returns information about an uncle given a block's hash and the index of the uncle. @@ -79,8 +79,8 @@ func (api *APIImpl) GetUncleByBlockHashAndIndex(ctx context.Context, hash common return nil, nil } uncle := types.NewBlockWithHeader(uncles[index]) - depositNonces := rawdb.ReadDepositNonces(tx, number) - return ethapi.RPCMarshalBlock(uncle, false, false, additionalFields, depositNonces) + receipts := rawdb.ReadRawReceipts(tx, number) + return ethapi.RPCMarshalBlock(uncle, false, false, additionalFields, receipts) } // GetUncleCountByBlockNumber implements eth_getUncleCountByBlockNumber. Returns the number of uncles in the block, if any. diff --git a/cmd/rpcdaemon/commands/graphql_api.go b/cmd/rpcdaemon/commands/graphql_api.go index 7861b777a33..5d818eb826f 100644 --- a/cmd/rpcdaemon/commands/graphql_api.go +++ b/cmd/rpcdaemon/commands/graphql_api.go @@ -116,8 +116,8 @@ func (api *GraphQLAPIImpl) delegateGetBlockByNumber(tx kv.Tx, b *types.Block, nu return nil, err } additionalFields := make(map[string]interface{}) - depositNonces := rawdb.ReadDepositNonces(tx, uint64(number.Int64())) - response, err := ethapi.RPCMarshalBlock(b, inclTx, inclTx, additionalFields, depositNonces) + receipts := rawdb.ReadRawReceipts(tx, uint64(number.Int64())) + response, err := ethapi.RPCMarshalBlock(b, inclTx, inclTx, additionalFields, receipts) if !inclTx { delete(response, "transactions") // workaround for https://github.com/ledgerwatch/erigon/issues/4989#issuecomment-1218415666 } diff --git a/cmd/rpcdaemon/commands/otterscan_api.go b/cmd/rpcdaemon/commands/otterscan_api.go index 981f87698b6..8a3d4b8f3aa 100644 --- a/cmd/rpcdaemon/commands/otterscan_api.go +++ b/cmd/rpcdaemon/commands/otterscan_api.go @@ -520,18 +520,17 @@ func (api *OtterscanAPIImpl) searchTransactionsBeforeV3(tx kv.TemporalTx, ctx co return nil, err } var rpcTx *RPCTransaction + var receipt *types.Receipt if chainConfig.IsOptimism() { - depositNonces := rawdb.ReadDepositNonces(tx, blockNum) - if txIndex >= len(depositNonces) { - return nil, fmt.Errorf("depositNonce for tx %x not found", txn.Hash()) - } else { - rpcTx = newRPCTransaction(txn, blockHash, blockNum, uint64(txIndex), header.BaseFee, depositNonces[txIndex]) + receipts := rawdb.ReadRawReceipts(tx, blockNum) + if len(receipts) <= int(txIndex) { + return nil, fmt.Errorf("block has less receipts than expected: %d <= %d, block: %d", len(receipts), int(txIndex), blockNum) } - } else { - rpcTx = newRPCTransaction(txn, blockHash, blockNum, uint64(txIndex), header.BaseFee, nil) + receipt = receipts[txIndex] } + rpcTx = newRPCTransaction(txn, blockHash, blockNum, uint64(txIndex), header.BaseFee, receipt) txs = append(txs, rpcTx) - receipt := &types.Receipt{ + receipt = &types.Receipt{ Type: txn.Type(), CumulativeGasUsed: res.UsedGas, TransactionIndex: uint(txIndex), BlockNumber: header.Number, BlockHash: blockHash, Logs: rawLogs, @@ -677,8 +676,8 @@ func (api *OtterscanAPIImpl) delegateGetBlockByNumber(tx kv.Tx, b *types.Block, return nil, err } additionalFields := make(map[string]interface{}) - depositNonces := rawdb.ReadDepositNonces(tx, uint64(number.Int64())) - response, err := ethapi.RPCMarshalBlock(b, inclTx, inclTx, additionalFields, depositNonces) + receipts := rawdb.ReadRawReceipts(tx, uint64(number.Int64())) + response, err := ethapi.RPCMarshalBlock(b, inclTx, inclTx, additionalFields, receipts) if !inclTx { delete(response, "transactions") // workaround for https://github.com/ledgerwatch/erigon/issues/4989#issuecomment-1218415666 } diff --git a/cmd/rpcdaemon/commands/otterscan_search_trace.go b/cmd/rpcdaemon/commands/otterscan_search_trace.go index 7ecf1b11f90..d3033b08c9c 100644 --- a/cmd/rpcdaemon/commands/otterscan_search_trace.go +++ b/cmd/rpcdaemon/commands/otterscan_search_trace.go @@ -94,11 +94,6 @@ func (api *OtterscanAPIImpl) traceBlock(dbtx kv.Tx, ctx context.Context, blockNu header := block.Header() rules := chainConfig.Rules(block.NumberU64(), header.Time) found := false - - var depositNonces []*uint64 - if chainConfig.IsOptimism() { - depositNonces = rawdb.ReadDepositNonces(dbtx, blockNum) - } for idx, tx := range block.Transactions() { ibs.SetTxContext(tx.Hash(), block.Hash(), idx) @@ -116,11 +111,11 @@ func (api *OtterscanAPIImpl) traceBlock(dbtx kv.Tx, ctx context.Context, blockNu _ = ibs.FinalizeTx(rules, cachedWriter) if tracer.Found { - var depositNonce *uint64 - if chainConfig.IsOptimism() && idx < len(depositNonces) { - depositNonce = depositNonces[idx] + var receipt *types.Receipt + if chainConfig.IsOptimism() && idx < len(block.Transactions()) { + receipt = blockReceipts[idx] } - rpcTx := newRPCTransaction(tx, block.Hash(), blockNum, uint64(idx), block.BaseFee(), depositNonce) + rpcTx := newRPCTransaction(tx, block.Hash(), blockNum, uint64(idx), block.BaseFee(), receipt) mReceipt := marshalReceipt(blockReceipts[idx], tx, chainConfig, block.HeaderNoCopy(), tx.Hash(), true) mReceipt["timestamp"] = block.Time() rpcTxs = append(rpcTxs, rpcTx) diff --git a/core/state_processor.go b/core/state_processor.go index e4ac5a2f71e..1cd0b9cc058 100644 --- a/core/state_processor.go +++ b/core/state_processor.go @@ -91,9 +91,14 @@ func applyTransaction(config *chain.Config, engine consensus.EngineReader, gp *G receipt.GasUsed = result.UsedGas if msg.IsDepositTx() && config.IsOptimismRegolith(evm.Context().Time) { - // The actual nonce for deposit transactions is only recorded from Regolith onwards. - // Before the Regolith fork the DepositNonce must remain nil + // The actual nonce for deposit transactions is only recorded from Regolith onwards and + // otherwise must be nil. receipt.DepositNonce = &nonce + + if config.IsOptimismCanyon(evm.Context().Time) { + receipt.DepositReceiptVersion = new(uint64) + *receipt.DepositReceiptVersion = types.CanyonDepositReceiptVersion + } } // if the transaction created a contract, store the creation address in the receipt. diff --git a/core/types/receipt.go b/core/types/receipt.go index 699528356b0..662d0659349 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -48,6 +48,9 @@ const ( // ReceiptStatusSuccessful is the status code of a transaction if execution succeeded. ReceiptStatusSuccessful = uint64(1) + + // The version number for post-canyon deposit receipts. + CanyonDepositReceiptVersion = uint64(1) ) // Receipt represents the results of a transaction. @@ -86,6 +89,11 @@ type Receipt struct { // for the data following previous struct and leading to decoding error(triggering backward imcompatibility). // Further fields when added must be appended after the last variable. Watch out for cbor. + + // DepositReceiptVersion was introduced in Canyon to indicate an update to how receipt hashes + // should be computed when set. The state transition process ensures this is only set for + // post-Canyon deposit transactions. + DepositReceiptVersion *uint64 `json:"depositReceiptVersion,omitempty"` } type receiptMarshaling struct { @@ -120,6 +128,10 @@ type depositReceiptRlp struct { // DepositNonce was introduced in Regolith to store the actual nonce used by deposit transactions. // Must be nil for any transactions prior to Regolith or that aren't deposit transactions. DepositNonce *uint64 `rlp:"optional"` + // Receipt hash post-Regolith but pre-Canyon inadvertently did not include the above + // DepositNonce. Post Canyon, receipts will have a non-empty DepositReceiptVersion indicating + // which post-Canyon receipt hash function to invoke. + DepositReceiptVersion *uint64 `rlp:"optional"` } // storedReceiptRLP is the storage encoding of a receipt. @@ -130,6 +142,10 @@ type storedReceiptRLP struct { // DepositNonce was introduced in Regolith to store the actual nonce used by deposit transactions. // Must be nil for any transactions prior to Regolith or that aren't deposit transactions. DepositNonce *uint64 `rlp:"optional"` + // Receipt hash post-Regolith but pre-Canyon inadvertently did not include the above + // DepositNonce. Post Canyon, receipts will have a non-empty DepositReceiptVersion indicating + // which post-Canyon receipt hash function to invoke. + DepositReceiptVersion *uint64 `rlp:"optional"` } // v4StoredReceiptRLP is the storage encoding of a receipt used in database version 4. @@ -182,8 +198,8 @@ func (r Receipt) EncodeRLP(w io.Writer) error { buf := new(bytes.Buffer) buf.WriteByte(r.Type) if r.Type == DepositTxType { - withNonce := &depositReceiptRlp{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs, r.DepositNonce} - if err := rlp.Encode(buf, withNonce); err != nil { + withNonceAndReceiptVersion := &depositReceiptRlp{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs, r.DepositNonce, r.DepositReceiptVersion} + if err := rlp.Encode(buf, withNonceAndReceiptVersion); err != nil { return err } } else { @@ -272,6 +288,15 @@ func (r *Receipt) decodePayload(s *rlp.Stream) error { } else { r.DepositNonce = &depositNonce } + depositReceiptVersion, err := s.Uint() + if err != nil { + if !errors.Is(err, rlp.EOL) { + return fmt.Errorf("read DepositReceiptVersion: %w", err) + } + return nil + } else { + r.DepositReceiptVersion = &depositReceiptVersion + } } if err := s.ListEnd(); err != nil { return fmt.Errorf("close receipt payload: %w", err) @@ -363,18 +388,20 @@ func (r *Receipt) Copy() *Receipt { blockNumber := big.NewInt(0).Set(r.BlockNumber) return &Receipt{ - Type: r.Type, - PostState: postState, - Status: r.Status, - CumulativeGasUsed: r.CumulativeGasUsed, - Bloom: bloom, - Logs: logs, - TxHash: txHash, - ContractAddress: contractAddress, - GasUsed: r.GasUsed, - BlockHash: blockHash, - BlockNumber: blockNumber, - TransactionIndex: r.TransactionIndex, + Type: r.Type, + PostState: postState, + Status: r.Status, + CumulativeGasUsed: r.CumulativeGasUsed, + Bloom: bloom, + Logs: logs, + TxHash: txHash, + ContractAddress: contractAddress, + GasUsed: r.GasUsed, + BlockHash: blockHash, + BlockNumber: blockNumber, + TransactionIndex: r.TransactionIndex, + DepositNonce: r.DepositNonce, + DepositReceiptVersion: r.DepositReceiptVersion, } } @@ -388,10 +415,11 @@ type ReceiptForStorage Receipt // into an RLP stream. func (r *ReceiptForStorage) EncodeRLP(w io.Writer) error { enc := &storedReceiptRLP{ - PostStateOrStatus: (*Receipt)(r).statusEncoding(), - CumulativeGasUsed: r.CumulativeGasUsed, - Logs: make([]*LogForStorage, len(r.Logs)), - DepositNonce: r.DepositNonce, + PostStateOrStatus: (*Receipt)(r).statusEncoding(), + CumulativeGasUsed: r.CumulativeGasUsed, + Logs: make([]*LogForStorage, len(r.Logs)), + DepositNonce: r.DepositNonce, + DepositReceiptVersion: r.DepositReceiptVersion, } for i, log := range r.Logs { enc.Logs[i] = (*LogForStorage)(log) @@ -436,7 +464,9 @@ func decodeStoredReceiptRLP(r *ReceiptForStorage, blob []byte) error { if stored.DepositNonce != nil { r.DepositNonce = stored.DepositNonce } - + if stored.DepositReceiptVersion != nil { + r.DepositReceiptVersion = stored.DepositReceiptVersion + } return nil } @@ -485,7 +515,10 @@ type Receipts []*Receipt // Len returns the number of receipts in this list. func (rs Receipts) Len() int { return len(rs) } -// EncodeIndex encodes the i'th receipt to w. +// EncodeIndex encodes the i'th receipt to w. For DepositTxType receipts with non-nil DepositNonce +// but nil DepositReceiptVersion, the output will differ than calling r.MarshalBinary(); this +// behavior difference should not be changed to preserve backwards compatibility of receipt-root +// hash computation. func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) { r := rs[i] data := &receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs} @@ -507,8 +540,16 @@ func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) { } case DepositTxType: w.WriteByte(DepositTxType) - if err := rlp.Encode(w, data); err != nil { - panic(err) + if r.DepositReceiptVersion != nil { + // post-canyon receipt hash computation update + depositData := &depositReceiptRlp{data.PostStateOrStatus, data.CumulativeGasUsed, r.Bloom, r.Logs, r.DepositNonce, r.DepositReceiptVersion} + if err := rlp.Encode(w, depositData); err != nil { + panic(err) + } + } else { + if err := rlp.Encode(w, data); err != nil { + panic(err) + } } case BlobTxType: w.WriteByte(BlobTxType) diff --git a/turbo/adapter/ethapi/api.go b/turbo/adapter/ethapi/api.go index 4879aa988a7..7ed9efdd663 100644 --- a/turbo/adapter/ethapi/api.go +++ b/turbo/adapter/ethapi/api.go @@ -298,12 +298,12 @@ func RPCMarshalHeader(head *types.Header) map[string]interface{} { // RPCMarshalBlock converts the given block to the RPC output which depends on fullTx. If inclTx is true transactions are // returned. When fullTx is true the returned block contains full transaction details, otherwise it will only contain // transaction hashes. -func RPCMarshalBlockDeprecated(block *types.Block, inclTx bool, fullTx bool, depositNonces []*uint64) (map[string]interface{}, error) { - return RPCMarshalBlockExDeprecated(block, inclTx, fullTx, nil, libcommon.Hash{}, depositNonces) +func RPCMarshalBlockDeprecated(block *types.Block, inclTx bool, fullTx bool, receipts types.Receipts) (map[string]interface{}, error) { + return RPCMarshalBlockExDeprecated(block, inclTx, fullTx, nil, libcommon.Hash{}, receipts) } func RPCMarshalBlockExDeprecated(block *types.Block, inclTx bool, fullTx bool, borTx types.Transaction, borTxHash libcommon.Hash, - depositNonces []*uint64) (map[string]interface{}, error) { + receipts types.Receipts) (map[string]interface{}, error) { fields := RPCMarshalHeader(block.Header()) fields["size"] = hexutil.Uint64(block.Size()) if _, ok := fields["transactions"]; !ok { @@ -316,14 +316,14 @@ func RPCMarshalBlockExDeprecated(block *types.Block, inclTx bool, fullTx bool, b } if fullTx { formatTx = func(tx types.Transaction, index int) (interface{}, error) { - return newRPCTransactionFromBlockAndTxGivenIndex(block, tx, uint64(index), depositNonces[index]), nil + return newRPCTransactionFromBlockAndTxGivenIndex(block, tx, uint64(index), receipts[index]), nil } } txs := block.Transactions() transactions := make([]interface{}, len(txs), len(txs)+1) - if depositNonces == nil { - // ensure that depositNonces is always initialized for formatTx - depositNonces = make([]*uint64, len(txs)) + if receipts == nil { + // ensure that receipts is always initialized for formatTx + receipts = make([]*types.Receipt, len(txs)) } var err error for i, tx := range txs { @@ -409,12 +409,14 @@ type RPCTransaction struct { SourceHash *libcommon.Hash `json:"sourceHash,omitempty"` Mint *hexutil.Big `json:"mint,omitempty"` IsSystemTx *bool `json:"isSystemTx,omitempty"` + // deposit-tx post-Canyon only + DepositReceiptVersion *hexutil.Uint64 `json:"depositReceiptVersion,omitempty"` } // newRPCTransaction returns a transaction that will serialize to the RPC // representation, with the given location metadata set (if available). func newRPCTransaction(tx types.Transaction, blockHash libcommon.Hash, blockNumber uint64, index uint64, baseFee *big.Int, - depositNonce *uint64) *RPCTransaction { + receipt *types.Receipt) *RPCTransaction { // Determine the signer. For replay-protected transactions, use the most permissive // signer, because we assume that signers are backwards-compatible with old // transactions. For non-protected transactions, the homestead signer signer is used @@ -471,8 +473,12 @@ func newRPCTransaction(tx types.Transaction, blockHash libcommon.Hash, blockNumb if t.IsSystemTransaction { result.IsSystemTx = &t.IsSystemTransaction } - if depositNonce != nil { - result.Nonce = hexutil.Uint64(*depositNonce) + if receipt != nil && receipt.DepositNonce != nil { + result.Nonce = hexutil.Uint64(*receipt.DepositNonce) + if receipt.DepositReceiptVersion != nil { + result.DepositReceiptVersion = new(hexutil.Uint64) + *result.DepositReceiptVersion = hexutil.Uint64(*receipt.DepositReceiptVersion) + } } result.GasPrice = (*hexutil.Big)(libcommon.Big0) // must contain v, r, s values for backwards compatibility. @@ -562,8 +568,8 @@ func newRPCTransactionFromBlockIndex(b *types.Block, index uint64) *RPCTransacti */ // newRPCTransactionFromBlockAndTxGivenIndex returns a transaction that will serialize to the RPC representation. -func newRPCTransactionFromBlockAndTxGivenIndex(b *types.Block, tx types.Transaction, index uint64, depositNonce *uint64) *RPCTransaction { - return newRPCTransaction(tx, b.Hash(), b.NumberU64(), index, b.BaseFee(), depositNonce) +func newRPCTransactionFromBlockAndTxGivenIndex(b *types.Block, tx types.Transaction, index uint64, receipt *types.Receipt) *RPCTransaction { + return newRPCTransaction(tx, b.Hash(), b.NumberU64(), index, b.BaseFee(), receipt) } /* diff --git a/turbo/adapter/ethapi/internal.go b/turbo/adapter/ethapi/internal.go index e19236329f3..47000be7526 100644 --- a/turbo/adapter/ethapi/internal.go +++ b/turbo/adapter/ethapi/internal.go @@ -8,8 +8,8 @@ import ( ) // nolint -func RPCMarshalBlock(b *types.Block, inclTx bool, fullTx bool, additional map[string]interface{}, depositNonces []*uint64) (map[string]interface{}, error) { - fields, err := RPCMarshalBlockDeprecated(b, inclTx, fullTx, depositNonces) +func RPCMarshalBlock(b *types.Block, inclTx bool, fullTx bool, additional map[string]interface{}, receipts types.Receipts) (map[string]interface{}, error) { + fields, err := RPCMarshalBlockDeprecated(b, inclTx, fullTx, receipts) if err != nil { return nil, err } @@ -23,8 +23,8 @@ func RPCMarshalBlock(b *types.Block, inclTx bool, fullTx bool, additional map[st // nolint func RPCMarshalBlockEx(b *types.Block, inclTx bool, fullTx bool, borTx types.Transaction, borTxHash libcommon.Hash, - additional map[string]interface{}, depositNonces []*uint64) (map[string]interface{}, error) { - fields, err := RPCMarshalBlockExDeprecated(b, inclTx, fullTx, borTx, borTxHash, depositNonces) + additional map[string]interface{}, receipts types.Receipts) (map[string]interface{}, error) { + fields, err := RPCMarshalBlockExDeprecated(b, inclTx, fullTx, borTx, borTxHash, receipts) if err != nil { return nil, err } From cb15a8114804cfa725996368e86bfb2d9c6be88a Mon Sep 17 00:00:00 2001 From: pcw109550 Date: Sat, 4 Nov 2023 04:23:35 +0900 Subject: [PATCH 02/10] Add tests for post-Canyon receipt-root deposit tx hashing fix --- core/types/receipt_test.go | 143 ++++++++++++++++++++++++++++--- turbo/adapter/ethapi/api_test.go | 49 ++++++++++- 2 files changed, 180 insertions(+), 12 deletions(-) diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go index 9caa53a164a..ef6bf3b69da 100644 --- a/core/types/receipt_test.go +++ b/core/types/receipt_test.go @@ -20,12 +20,13 @@ import ( "bytes" "errors" "fmt" - "github.com/stretchr/testify/require" "math" "math/big" "reflect" "testing" + "github.com/stretchr/testify/require" + "github.com/holiman/uint256" libcommon "github.com/ledgerwatch/erigon-lib/common" @@ -106,9 +107,30 @@ var ( } nonce = uint64(1234) depositReceiptWithNonce = &Receipt{ - Status: ReceiptStatusFailed, - CumulativeGasUsed: 1, - DepositNonce: &nonce, + Status: ReceiptStatusFailed, + CumulativeGasUsed: 1, + DepositNonce: &nonce, + DepositReceiptVersion: nil, + Logs: []*Log{ + { + Address: libcommon.BytesToAddress([]byte{0x11}), + Topics: []libcommon.Hash{libcommon.HexToHash("dead"), libcommon.HexToHash("beef")}, + Data: []byte{0x01, 0x00, 0xff}, + }, + { + Address: libcommon.BytesToAddress([]byte{0x01, 0x11}), + Topics: []libcommon.Hash{libcommon.HexToHash("dead"), libcommon.HexToHash("beef")}, + Data: []byte{0x01, 0x00, 0xff}, + }, + }, + Type: DepositTxType, + } + version = CanyonDepositReceiptVersion + depositReceiptWithNonceAndVersion = &Receipt{ + Status: ReceiptStatusFailed, + CumulativeGasUsed: 1, + DepositNonce: &nonce, + DepositReceiptVersion: &version, Logs: []*Log{ { Address: libcommon.BytesToAddress([]byte{0x11}), @@ -253,8 +275,15 @@ func TestDeriveFields(t *testing.T) { Value: uint256.NewInt(3), Gas: 4, }, + &DepositTx{ + To: nil, // contract creation + Value: uint256.NewInt(6), + Gas: 5, + }, } depNonce := uint64(7) + depNonce2 := uint64(8) + canyonDepositReceiptVersion := CanyonDepositReceiptVersion // Create the corresponding receipts receipts := Receipts{ &Receipt{ @@ -299,10 +328,49 @@ func TestDeriveFields(t *testing.T) { {Address: libcommon.BytesToAddress([]byte{0x33})}, {Address: libcommon.BytesToAddress([]byte{0x03, 0x33})}, }, - TxHash: txs[3].Hash(), - ContractAddress: libcommon.BytesToAddress([]byte{0x03, 0x33, 0x33}), - GasUsed: 4, - DepositNonce: &depNonce, + TxHash: txs[3].Hash(), + ContractAddress: libcommon.BytesToAddress([]byte{0x03, 0x33, 0x33}), + GasUsed: 4, + BlockHash: libcommon.BytesToHash([]byte{0x03, 0x14}), + BlockNumber: big.NewInt(1), + TransactionIndex: 7, + DepositNonce: &depNonce, + DepositReceiptVersion: nil, + }, + &Receipt{ + Type: DepositTxType, + PostState: libcommon.Hash{5}.Bytes(), + CumulativeGasUsed: 15, + Logs: []*Log{ + { + Address: libcommon.BytesToAddress([]byte{0x33}), + Topics: []libcommon.Hash{libcommon.HexToHash("dead"), libcommon.HexToHash("beef")}, + // derived fields: + BlockNumber: big.NewInt(1).Uint64(), + TxHash: txs[4].Hash(), + TxIndex: 4, + BlockHash: libcommon.BytesToHash([]byte{0x03, 0x14}), + Index: 4, + }, + { + Address: libcommon.BytesToAddress([]byte{0x03, 0x33}), + Topics: []libcommon.Hash{libcommon.HexToHash("dead"), libcommon.HexToHash("beef")}, + // derived fields: + BlockNumber: big.NewInt(1).Uint64(), + TxHash: txs[4].Hash(), + TxIndex: 4, + BlockHash: libcommon.BytesToHash([]byte{0x03, 0x14}), + Index: 5, + }, + }, + TxHash: txs[4].Hash(), + ContractAddress: libcommon.HexToAddress("0x3bb898b4bbe24f68a4e9be46cfe72d1787fd74f4"), + GasUsed: 5, + BlockHash: libcommon.BytesToHash([]byte{0x03, 0x14}), + BlockNumber: big.NewInt(1), + TransactionIndex: 4, + DepositNonce: &depNonce2, + DepositReceiptVersion: &canyonDepositReceiptVersion, }, } @@ -310,7 +378,9 @@ func TestDeriveFields(t *testing.T) { txs[0].GetNonce(), txs[1].GetNonce(), txs[2].GetNonce(), - *receipts[3].DepositNonce, // Deposit tx should use deposit nonce + // Deposit tx should use deposit nonce + *receipts[3].DepositNonce, + *receipts[4].DepositNonce, } // Clear all the computed fields and re-derive them number := big.NewInt(1) @@ -318,7 +388,7 @@ func TestDeriveFields(t *testing.T) { time := uint64(0) clearComputedFieldsOnReceipts(t, receipts) - if err := receipts.DeriveFields(params.TestChainConfig, hash, number.Uint64(), time, txs, []libcommon.Address{libcommon.BytesToAddress([]byte{0x0}), libcommon.BytesToAddress([]byte{0x0}), libcommon.BytesToAddress([]byte{0x0}), libcommon.BytesToAddress([]byte{0x0})}); err != nil { + if err := receipts.DeriveFields(params.TestChainConfig, hash, number.Uint64(), time, txs, []libcommon.Address{libcommon.BytesToAddress([]byte{0x0}), libcommon.BytesToAddress([]byte{0x0}), libcommon.BytesToAddress([]byte{0x0}), libcommon.BytesToAddress([]byte{0x0}), libcommon.BytesToAddress([]byte{0x0})}); err != nil { t.Fatalf("DeriveFields(...) = %v, want ", err) } // Iterate over all the computed fields and check that they're correct @@ -478,6 +548,54 @@ func TestBedrockDepositReceiptUnchanged(t *testing.T) { } // And still shouldn't have a nonce require.Nil(t, parsed.DepositNonce) + // ..or a deposit nonce + require.Nil(t, parsed.DepositReceiptVersion) +} + +// Regolith did not include deposit nonce during receipt root construction. +// TestReceiptEncodeIndexBugIsEnshrined makes sure this difference is preserved for backwards +// compatibility purposes, but also that there is no discrepancy for the post-Canyon encoding. +func TestReceiptEncodeIndexBugIsEnshrined(t *testing.T) { + // Check that a post-Regolith, pre-Canyon receipt produces no difference between + // receipts having different depositNonce + buf := new(bytes.Buffer) + receipts := Receipts{depositReceiptWithNonce} + receipts.EncodeIndex(0, buf) + indexBytesBefore := buf.Bytes() + + buf2 := new(bytes.Buffer) + newDepositNonce := *receipts[0].DepositNonce + 1 + receipts[0].DepositNonce = &newDepositNonce + receipts.EncodeIndex(0, buf2) + indexBytesAfter := buf2.Bytes() + + require.Equal(t, indexBytesBefore, indexBytesAfter) + + // Confirm the buggy encoding is as expected, which means it should encode as if it had no + // nonce specified (like that of a non-deposit receipt, whose encoding would differ only in the + // type byte). + buf3 := new(bytes.Buffer) + receipts[0].Type = eip1559Receipt.Type + receipts.EncodeIndex(0, buf3) + indexBytesNonDeposit := buf3.Bytes() + + require.NotEqual(t, indexBytesBefore[0], indexBytesNonDeposit[0]) + require.Equal(t, indexBytesBefore[1:], indexBytesNonDeposit[1:]) + + // Check that post-canyon changes the hash compared to pre-Canyon + buf4 := new(bytes.Buffer) + receipts = Receipts{depositReceiptWithNonceAndVersion} + receipts.EncodeIndex(0, buf4) + indexBytesCanyon := buf4.Bytes() + require.NotEqual(t, indexBytesBefore[1:], indexBytesCanyon[1:]) + + // Check that bumping the nonce post-canyon changes the hash + buf5 := new(bytes.Buffer) + bumpedNonce := *depositReceiptWithNonceAndVersion.DepositNonce + 1 + receipts[0].DepositNonce = &bumpedNonce + receipts.EncodeIndex(0, buf5) + indexBytesCanyonBump := buf5.Bytes() + require.NotEqual(t, indexBytesCanyon[1:], indexBytesCanyonBump[1:]) } func TestRoundTripReceipt(t *testing.T) { @@ -490,6 +608,7 @@ func TestRoundTripReceipt(t *testing.T) { {name: "EIP1559", rcpt: eip1559Receipt}, {name: "DepositNoNonce", rcpt: depositReceiptNoNonce}, {name: "DepositWithNonce", rcpt: depositReceiptWithNonce}, + {name: "DepositWithNonceAndVersion", rcpt: depositReceiptWithNonceAndVersion}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -500,6 +619,8 @@ func TestRoundTripReceipt(t *testing.T) { err = rlp.DecodeBytes(data, d) require.NoError(t, err) require.Equal(t, test.rcpt, d) + require.Equal(t, test.rcpt.DepositNonce, d.DepositNonce) + require.Equal(t, test.rcpt.DepositReceiptVersion, d.DepositReceiptVersion) }) t.Run(fmt.Sprintf("%sRejectExtraData", test.name), func(t *testing.T) { @@ -523,6 +644,7 @@ func TestRoundTripReceiptForStorage(t *testing.T) { {name: "EIP1559", rcpt: eip1559Receipt}, {name: "DepositNoNonce", rcpt: depositReceiptNoNonce}, {name: "DepositWithNonce", rcpt: depositReceiptWithNonce}, + {name: "DepositWithNonceAndVersion", rcpt: depositReceiptWithNonceAndVersion}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { @@ -537,6 +659,7 @@ func TestRoundTripReceiptForStorage(t *testing.T) { require.Equal(t, test.rcpt.CumulativeGasUsed, d.CumulativeGasUsed) require.Equal(t, test.rcpt.Logs, d.Logs) require.Equal(t, test.rcpt.DepositNonce, d.DepositNonce) + require.Equal(t, test.rcpt.DepositReceiptVersion, d.DepositReceiptVersion) }) } } diff --git a/turbo/adapter/ethapi/api_test.go b/turbo/adapter/ethapi/api_test.go index 9874853cbe3..dd0dd61cf9e 100644 --- a/turbo/adapter/ethapi/api_test.go +++ b/turbo/adapter/ethapi/api_test.go @@ -21,7 +21,8 @@ func TestNewRPCTransactionDepositTx(t *testing.T) { } nonce := uint64(7) depositNonce := &nonce - got := newRPCTransaction(tx, libcommon.Hash{}, uint64(12), uint64(1), big.NewInt(0), depositNonce) + receipt := &types.Receipt{DepositNonce: depositNonce} + got := newRPCTransaction(tx, libcommon.Hash{}, uint64(12), uint64(1), big.NewInt(0), receipt) // Should provide zero values for unused fields that are required in other transactions require.Equal(t, got.GasPrice, (*hexutil.Big)(big.NewInt(0)), "newRPCTransaction().GasPrice = %v, want 0x0", got.GasPrice) require.Equal(t, got.V, (*hexutil.Big)(big.NewInt(0)), "newRPCTransaction().V = %v, want 0x0", got.V) @@ -32,7 +33,43 @@ func TestNewRPCTransactionDepositTx(t *testing.T) { require.Equal(t, *got.SourceHash, tx.SourceHash, "newRPCTransaction().SourceHash = %v, want %v", got.SourceHash, tx.SourceHash) require.Equal(t, *got.IsSystemTx, tx.IsSystemTransaction, "newRPCTransaction().IsSystemTransaction = %v, want %v", got.IsSystemTx, tx.IsSystemTransaction) require.Equal(t, got.Mint, (*hexutil.Big)(tx.Mint.ToBig()), "newRPCTransaction().Mint = %v, want %v", got.Mint, tx.Mint.ToBig()) - require.Equal(t, got.Nonce, (hexutil.Uint64)(nonce), "newRPCTransaction().Mint = %v, want %v", got.Nonce, nonce) + require.Equal(t, got.Nonce, (hexutil.Uint64)(nonce), "newRPCTransaction().Nonce = %v, want %v", got.Nonce, nonce) +} + +func TestNewRPCTransactionDepositTxWithVersion(t *testing.T) { + tx := &types.DepositTx{ + SourceHash: libcommon.HexToHash("0x1234"), + IsSystemTransaction: true, + Mint: uint256.NewInt(34), + Value: uint256.NewInt(1337), + } + nonce := uint64(7) + version := types.CanyonDepositReceiptVersion + receipt := &types.Receipt{ + DepositNonce: &nonce, + DepositReceiptVersion: &version, + } + got := newRPCTransaction(tx, libcommon.Hash{}, uint64(12), uint64(1), big.NewInt(0), receipt) + // Should provide zero values for unused fields that are required in other transactions + require.Equal(t, got.GasPrice, (*hexutil.Big)(big.NewInt(0)), "newRPCTransaction().GasPrice = %v, want 0x0", got.GasPrice) + require.Equal(t, got.V, (*hexutil.Big)(big.NewInt(0)), "newRPCTransaction().V = %v, want 0x0", got.V) + require.Equal(t, got.R, (*hexutil.Big)(big.NewInt(0)), "newRPCTransaction().R = %v, want 0x0", got.R) + require.Equal(t, got.S, (*hexutil.Big)(big.NewInt(0)), "newRPCTransaction().S = %v, want 0x0", got.S) + + // Should include versioned deposit tx specific fields + require.Equal(t, *got.SourceHash, tx.SourceHash, "newRPCTransaction().SourceHash = %v, want %v", got.SourceHash, tx.SourceHash) + require.Equal(t, *got.IsSystemTx, tx.IsSystemTransaction, "newRPCTransaction().IsSystemTx = %v, want %v", got.IsSystemTx, tx.IsSystemTransaction) + require.Equal(t, got.Mint, (*hexutil.Big)(tx.Mint.ToBig()), "newRPCTransaction().Mint = %v, want %v", got.Mint, tx.Mint.ToBig()) + require.Equal(t, got.Nonce, (hexutil.Uint64)(nonce), "newRPCTransaction().Nonce = %v, want %v", got.Nonce, nonce) + require.Equal(t, *got.DepositReceiptVersion, (hexutil.Uint64(version)), "newRPCTransaction().DepositReceiptVersion = %v, want %v", *got.DepositReceiptVersion, version) + + // Make sure json marshal/unmarshal of the rpc tx preserves the receipt version + b, err := json.Marshal(got) + require.NoError(t, err, "marshalling failed: %w", err) + parsed := make(map[string]interface{}) + err = json.Unmarshal(b, &parsed) + require.NoError(t, err, "unmarshalling failed: %w", err) + require.Equal(t, "0x1", parsed["depositReceiptVersion"]) } func TestNewRPCTransactionOmitIsSystemTxFalse(t *testing.T) { @@ -46,6 +83,7 @@ func TestNewRPCTransactionOmitIsSystemTxFalse(t *testing.T) { } func TestUnmarshalRpcDepositTx(t *testing.T) { + version := hexutil.Uint64(types.CanyonDepositReceiptVersion) tests := []struct { name string modifier func(tx *RPCTransaction) @@ -104,6 +142,13 @@ func TestUnmarshalRpcDepositTx(t *testing.T) { }, valid: false, }, + { + name: "Non-nil deposit receipt version", + modifier: func(tx *RPCTransaction) { + tx.DepositReceiptVersion = &version + }, + valid: true, + }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { From f7c07d3e69f9f55abddc18ac03ef99fbb1f6a184 Mon Sep 17 00:00:00 2001 From: pcw109550 Date: Sat, 4 Nov 2023 04:26:19 +0900 Subject: [PATCH 03/10] Remove unused method --- core/rawdb/accessors_chain.go | 15 ++------------- 1 file changed, 2 insertions(+), 13 deletions(-) diff --git a/core/rawdb/accessors_chain.go b/core/rawdb/accessors_chain.go index f0ac56ed6be..b34f7037330 100644 --- a/core/rawdb/accessors_chain.go +++ b/core/rawdb/accessors_chain.go @@ -22,11 +22,12 @@ import ( "encoding/binary" "encoding/json" "fmt" - "github.com/ledgerwatch/erigon-lib/chain" "math" "math/big" "time" + "github.com/ledgerwatch/erigon-lib/chain" + "github.com/gballet/go-verkle" common2 "github.com/ledgerwatch/erigon-lib/common" libcommon "github.com/ledgerwatch/erigon-lib/common" @@ -851,18 +852,6 @@ func ReadReceipts(config *chain.Config, db kv.Tx, block *types.Block, senders [] return receipts } -func ReadDepositNonces(db kv.Tx, blockNumber uint64) []*uint64 { - receipts := ReadRawReceipts(db, blockNumber) - if receipts == nil { - return nil - } - depositNonces := make([]*uint64, len(receipts)) - for i, r := range receipts { - depositNonces[i] = r.DepositNonce - } - return depositNonces -} - // WriteReceipts stores all the transaction receipts belonging to a block. func WriteReceipts(tx kv.Putter, number uint64, receipts types.Receipts) error { buf := bytes.NewBuffer(make([]byte, 0, 1024)) From dc4a7b6c859a581267e3453311e1d38e3826c685 Mon Sep 17 00:00:00 2001 From: pcw109550 Date: Sat, 4 Nov 2023 12:29:13 +0900 Subject: [PATCH 04/10] Fix comment --- core/types/receipt.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/core/types/receipt.go b/core/types/receipt.go index 662d0659349..0de1503fd5b 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -515,10 +515,11 @@ type Receipts []*Receipt // Len returns the number of receipts in this list. func (rs Receipts) Len() int { return len(rs) } -// EncodeIndex encodes the i'th receipt to w. For DepositTxType receipts with non-nil DepositNonce -// but nil DepositReceiptVersion, the output will differ than calling r.MarshalBinary(); this -// behavior difference should not be changed to preserve backwards compatibility of receipt-root -// hash computation. + +// EncodeIndex encodes the i'th receipt to w. +// During post-regolith and pre-Canyon, DepositNonce was not included when encoding for hashing. +// Canyon adds DepositReceiptVersion to preserve backwards compatibility for pre-Canyon, and +// for correct receipt-root hash computation. func (rs Receipts) EncodeIndex(i int, w *bytes.Buffer) { r := rs[i] data := &receiptRLP{r.statusEncoding(), r.CumulativeGasUsed, r.Bloom, r.Logs} From 87d1c83e16eb96ea590f8751785cbff7a425fb51 Mon Sep 17 00:00:00 2001 From: pcw109550 Date: Sat, 4 Nov 2023 19:31:20 +0900 Subject: [PATCH 05/10] Avoid null access for blockNumber while receipt copy --- core/types/receipt.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/types/receipt.go b/core/types/receipt.go index 0de1503fd5b..fcd78d1403e 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -385,7 +385,10 @@ func (r *Receipt) Copy() *Receipt { txHash := libcommon.BytesToHash(r.TxHash.Bytes()) contractAddress := libcommon.BytesToAddress(r.ContractAddress.Bytes()) blockHash := libcommon.BytesToHash(r.BlockHash.Bytes()) - blockNumber := big.NewInt(0).Set(r.BlockNumber) + var blockNumber *big.Int + if r.BlockNumber != nil { + blockNumber = big.NewInt(0).Set(r.BlockNumber) + } return &Receipt{ Type: r.Type, @@ -515,7 +518,6 @@ type Receipts []*Receipt // Len returns the number of receipts in this list. func (rs Receipts) Len() int { return len(rs) } - // EncodeIndex encodes the i'th receipt to w. // During post-regolith and pre-Canyon, DepositNonce was not included when encoding for hashing. // Canyon adds DepositReceiptVersion to preserve backwards compatibility for pre-Canyon, and From a183507c304c1a303e1ba019097228385f4f7307 Mon Sep 17 00:00:00 2001 From: pcw109550 Date: Sat, 4 Nov 2023 19:31:38 +0900 Subject: [PATCH 06/10] Deepcopy receipts while testing --- core/types/receipt_test.go | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go index ef6bf3b69da..17f6d783659 100644 --- a/core/types/receipt_test.go +++ b/core/types/receipt_test.go @@ -559,7 +559,7 @@ func TestReceiptEncodeIndexBugIsEnshrined(t *testing.T) { // Check that a post-Regolith, pre-Canyon receipt produces no difference between // receipts having different depositNonce buf := new(bytes.Buffer) - receipts := Receipts{depositReceiptWithNonce} + receipts := Receipts{depositReceiptWithNonce.Copy()} receipts.EncodeIndex(0, buf) indexBytesBefore := buf.Bytes() @@ -577,14 +577,14 @@ func TestReceiptEncodeIndexBugIsEnshrined(t *testing.T) { buf3 := new(bytes.Buffer) receipts[0].Type = eip1559Receipt.Type receipts.EncodeIndex(0, buf3) - indexBytesNonDeposit := buf3.Bytes() + indexBytesNoDeposit := buf3.Bytes() - require.NotEqual(t, indexBytesBefore[0], indexBytesNonDeposit[0]) - require.Equal(t, indexBytesBefore[1:], indexBytesNonDeposit[1:]) + require.NotEqual(t, indexBytesBefore[0], indexBytesNoDeposit[0]) + require.Equal(t, indexBytesBefore[1:], indexBytesNoDeposit[1:]) // Check that post-canyon changes the hash compared to pre-Canyon buf4 := new(bytes.Buffer) - receipts = Receipts{depositReceiptWithNonceAndVersion} + receipts = Receipts{depositReceiptWithNonceAndVersion.Copy()} receipts.EncodeIndex(0, buf4) indexBytesCanyon := buf4.Bytes() require.NotEqual(t, indexBytesBefore[1:], indexBytesCanyon[1:]) From 2d70c291c8a82ff211f35e4be1e19f157b353bd5 Mon Sep 17 00:00:00 2001 From: pcw109550 Date: Sat, 4 Nov 2023 20:11:56 +0900 Subject: [PATCH 07/10] Add deposit related field when receipt json marshal --- core/types/receipt.go | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/core/types/receipt.go b/core/types/receipt.go index fcd78d1403e..d2ed85af3eb 100644 --- a/core/types/receipt.go +++ b/core/types/receipt.go @@ -105,11 +105,13 @@ type receiptMarshaling struct { BlockNumber *hexutil.Big TransactionIndex hexutil.Uint - // Optimism: extend receipts with their L1 price (if a rollup tx) - L1GasPrice *hexutil.Big - L1GasUsed *hexutil.Big - L1Fee *hexutil.Big - FeeScalar *big.Float + // Optimism + L1GasPrice *hexutil.Big + L1GasUsed *hexutil.Big + L1Fee *hexutil.Big + FeeScalar *big.Float + DepositNonce *hexutil.Uint64 + DepositReceiptVersion *hexutil.Uint64 } // receiptRLP is the consensus encoding of a receipt. From a92dcc0699001d367156d47aeec81acbd28131aa Mon Sep 17 00:00:00 2001 From: pcw109550 Date: Sat, 4 Nov 2023 20:12:18 +0900 Subject: [PATCH 08/10] Run codecgen and gencodec for receipt --- core/types/gen_receipt_json.go | 89 ++++++++++++----- core/types/receipt_codecgen_gen.go | 155 ++++++++++++++++++----------- 2 files changed, 160 insertions(+), 84 deletions(-) diff --git a/core/types/gen_receipt_json.go b/core/types/gen_receipt_json.go index 8a898648386..a9c886aedac 100644 --- a/core/types/gen_receipt_json.go +++ b/core/types/gen_receipt_json.go @@ -7,9 +7,8 @@ import ( "errors" "math/big" - libcommon "github.com/ledgerwatch/erigon-lib/common" + "github.com/ledgerwatch/erigon-lib/common" "github.com/ledgerwatch/erigon-lib/common/hexutility" - "github.com/ledgerwatch/erigon/common/hexutil" ) @@ -18,18 +17,24 @@ var _ = (*receiptMarshaling)(nil) // MarshalJSON marshals as JSON. func (r Receipt) MarshalJSON() ([]byte, error) { type Receipt struct { - Type hexutil.Uint64 `json:"type,omitempty"` - PostState hexutility.Bytes `json:"root"` - Status hexutil.Uint64 `json:"status"` - CumulativeGasUsed hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"` - Bloom Bloom `json:"logsBloom" gencodec:"required"` - Logs []*Log `json:"logs" gencodec:"required"` - TxHash libcommon.Hash `json:"transactionHash" gencodec:"required"` - ContractAddress libcommon.Address `json:"contractAddress"` - GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required"` - BlockHash libcommon.Hash `json:"blockHash,omitempty"` - BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` - TransactionIndex hexutil.Uint `json:"transactionIndex"` + Type hexutil.Uint64 `json:"type,omitempty"` + PostState hexutility.Bytes `json:"root" codec:"1"` + Status hexutil.Uint64 `json:"status" codec:"2"` + CumulativeGasUsed hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required" codec:"3"` + Bloom Bloom `json:"logsBloom" gencodec:"required" codec:"-"` + Logs Logs `json:"logs" gencodec:"required" codec:"-"` + TxHash common.Hash `json:"transactionHash" gencodec:"required" codec:"-"` + ContractAddress common.Address `json:"contractAddress" codec:"-"` + GasUsed hexutil.Uint64 `json:"gasUsed" gencodec:"required" codec:"-"` + BlockHash common.Hash `json:"blockHash,omitempty" codec:"-"` + BlockNumber *hexutil.Big `json:"blockNumber,omitempty" codec:"-"` + TransactionIndex hexutil.Uint `json:"transactionIndex" codec:"-"` + L1GasPrice *hexutil.Big `json:"l1GasPrice,omitempty"` + L1GasUsed *hexutil.Big `json:"l1GasUsed,omitempty"` + L1Fee *hexutil.Big `json:"l1Fee,omitempty"` + FeeScalar *big.Float `json:"l1FeeScalar,omitempty"` + DepositNonce *hexutil.Uint64 `json:"depositNonce,omitempty"` + DepositReceiptVersion *hexutil.Uint64 `json:"depositReceiptVersion,omitempty"` } var enc Receipt enc.Type = hexutil.Uint64(r.Type) @@ -44,24 +49,36 @@ func (r Receipt) MarshalJSON() ([]byte, error) { enc.BlockHash = r.BlockHash enc.BlockNumber = (*hexutil.Big)(r.BlockNumber) enc.TransactionIndex = hexutil.Uint(r.TransactionIndex) + enc.L1GasPrice = (*hexutil.Big)(r.L1GasPrice) + enc.L1GasUsed = (*hexutil.Big)(r.L1GasUsed) + enc.L1Fee = (*hexutil.Big)(r.L1Fee) + enc.FeeScalar = r.FeeScalar + enc.DepositNonce = (*hexutil.Uint64)(r.DepositNonce) + enc.DepositReceiptVersion = (*hexutil.Uint64)(r.DepositReceiptVersion) return json.Marshal(&enc) } // UnmarshalJSON unmarshals from JSON. func (r *Receipt) UnmarshalJSON(input []byte) error { type Receipt struct { - Type *hexutil.Uint64 `json:"type,omitempty"` - PostState *hexutility.Bytes `json:"root"` - Status *hexutil.Uint64 `json:"status"` - CumulativeGasUsed *hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required"` - Bloom *Bloom `json:"logsBloom" gencodec:"required"` - Logs []*Log `json:"logs" gencodec:"required"` - TxHash *libcommon.Hash `json:"transactionHash" gencodec:"required"` - ContractAddress *libcommon.Address `json:"contractAddress"` - GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required"` - BlockHash *libcommon.Hash `json:"blockHash,omitempty"` - BlockNumber *hexutil.Big `json:"blockNumber,omitempty"` - TransactionIndex *hexutil.Uint `json:"transactionIndex"` + Type *hexutil.Uint64 `json:"type,omitempty"` + PostState *hexutility.Bytes `json:"root" codec:"1"` + Status *hexutil.Uint64 `json:"status" codec:"2"` + CumulativeGasUsed *hexutil.Uint64 `json:"cumulativeGasUsed" gencodec:"required" codec:"3"` + Bloom *Bloom `json:"logsBloom" gencodec:"required" codec:"-"` + Logs *Logs `json:"logs" gencodec:"required" codec:"-"` + TxHash *common.Hash `json:"transactionHash" gencodec:"required" codec:"-"` + ContractAddress *common.Address `json:"contractAddress" codec:"-"` + GasUsed *hexutil.Uint64 `json:"gasUsed" gencodec:"required" codec:"-"` + BlockHash *common.Hash `json:"blockHash,omitempty" codec:"-"` + BlockNumber *hexutil.Big `json:"blockNumber,omitempty" codec:"-"` + TransactionIndex *hexutil.Uint `json:"transactionIndex" codec:"-"` + L1GasPrice *hexutil.Big `json:"l1GasPrice,omitempty"` + L1GasUsed *hexutil.Big `json:"l1GasUsed,omitempty"` + L1Fee *hexutil.Big `json:"l1Fee,omitempty"` + FeeScalar *big.Float `json:"l1FeeScalar,omitempty"` + DepositNonce *hexutil.Uint64 `json:"depositNonce,omitempty"` + DepositReceiptVersion *hexutil.Uint64 `json:"depositReceiptVersion,omitempty"` } var dec Receipt if err := json.Unmarshal(input, &dec); err != nil { @@ -87,7 +104,7 @@ func (r *Receipt) UnmarshalJSON(input []byte) error { if dec.Logs == nil { return errors.New("missing required field 'logs' for Receipt") } - r.Logs = dec.Logs + r.Logs = *dec.Logs if dec.TxHash == nil { return errors.New("missing required field 'transactionHash' for Receipt") } @@ -108,5 +125,23 @@ func (r *Receipt) UnmarshalJSON(input []byte) error { if dec.TransactionIndex != nil { r.TransactionIndex = uint(*dec.TransactionIndex) } + if dec.L1GasPrice != nil { + r.L1GasPrice = (*big.Int)(dec.L1GasPrice) + } + if dec.L1GasUsed != nil { + r.L1GasUsed = (*big.Int)(dec.L1GasUsed) + } + if dec.L1Fee != nil { + r.L1Fee = (*big.Int)(dec.L1Fee) + } + if dec.FeeScalar != nil { + r.FeeScalar = dec.FeeScalar + } + if dec.DepositNonce != nil { + r.DepositNonce = (*uint64)(dec.DepositNonce) + } + if dec.DepositReceiptVersion != nil { + r.DepositReceiptVersion = (*uint64)(dec.DepositReceiptVersion) + } return nil } diff --git a/core/types/receipt_codecgen_gen.go b/core/types/receipt_codecgen_gen.go index 35a3d6b5ec3..de6c7b1b103 100644 --- a/core/types/receipt_codecgen_gen.go +++ b/core/types/receipt_codecgen_gen.go @@ -69,7 +69,8 @@ func (x *Receipt) CodecEncodeSelf(e *codec1978.Encoder) { var yyn9 bool = x.L1Fee == nil var yyn10 bool = x.FeeScalar == nil var yyn11 bool = x.DepositNonce == nil - z.EncWriteArrayStart(9) + var yyn12 bool = x.DepositReceiptVersion == nil + z.EncWriteArrayStart(10) z.EncWriteArrayElem() r.EncodeUint(uint64(x.Type)) z.EncWriteArrayElem() @@ -131,8 +132,16 @@ func (x *Receipt) CodecEncodeSelf(e *codec1978.Encoder) { r.EncodeNil() } else { z.EncWriteArrayElem() - yy20 := *x.DepositNonce - r.EncodeUint(uint64(yy20)) + yy21 := *x.DepositNonce + r.EncodeUint(uint64(yy21)) + } + if yyn12 { + z.EncWriteArrayElem() + r.EncodeNil() + } else { + z.EncWriteArrayElem() + yy23 := *x.DepositReceiptVersion + r.EncodeUint(uint64(yy23)) } z.EncWriteArrayEnd() } @@ -266,6 +275,17 @@ func (x *Receipt) codecDecodeSelfFromMap(l int, d *codec1978.Decoder) { } *x.DepositNonce = (uint64)(r.DecodeUint64()) } + case "DepositReceiptVersion": + if r.TryNil() { + if x.DepositReceiptVersion != nil { // remove the if-true + x.DepositReceiptVersion = nil + } + } else { + if x.DepositReceiptVersion == nil { + x.DepositReceiptVersion = new(uint64) + } + *x.DepositReceiptVersion = (uint64)(r.DecodeUint64()) + } default: z.DecStructFieldNotFound(-1, yys3) } // end switch yys3 @@ -276,64 +296,64 @@ func (x *Receipt) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { var h codecSelfer2 z, r := codec1978.GenHelperDecoder(d) _, _, _ = h, z, r - var yyj19 int - var yyb19 bool - var yyhl19 bool = l >= 0 - yyj19++ - if yyhl19 { - yyb19 = yyj19 > l + var yyj21 int + var yyb21 bool + var yyhl21 bool = l >= 0 + yyj21++ + if yyhl21 { + yyb21 = yyj21 > l } else { - yyb19 = z.DecCheckBreak() + yyb21 = z.DecCheckBreak() } - if yyb19 { + if yyb21 { z.DecReadArrayEnd() return } z.DecReadArrayElem() x.Type = (uint8)(z.C.UintV(r.DecodeUint64(), 8)) - yyj19++ - if yyhl19 { - yyb19 = yyj19 > l + yyj21++ + if yyhl21 { + yyb21 = yyj21 > l } else { - yyb19 = z.DecCheckBreak() + yyb21 = z.DecCheckBreak() } - if yyb19 { + if yyb21 { z.DecReadArrayEnd() return } z.DecReadArrayElem() x.PostState = r.DecodeBytes(([]byte)(x.PostState), false) - yyj19++ - if yyhl19 { - yyb19 = yyj19 > l + yyj21++ + if yyhl21 { + yyb21 = yyj21 > l } else { - yyb19 = z.DecCheckBreak() + yyb21 = z.DecCheckBreak() } - if yyb19 { + if yyb21 { z.DecReadArrayEnd() return } z.DecReadArrayElem() x.Status = (uint64)(r.DecodeUint64()) - yyj19++ - if yyhl19 { - yyb19 = yyj19 > l + yyj21++ + if yyhl21 { + yyb21 = yyj21 > l } else { - yyb19 = z.DecCheckBreak() + yyb21 = z.DecCheckBreak() } - if yyb19 { + if yyb21 { z.DecReadArrayEnd() return } z.DecReadArrayElem() x.CumulativeGasUsed = (uint64)(r.DecodeUint64()) - yyj19++ - if yyhl19 { - yyb19 = yyj19 > l + yyj21++ + if yyhl21 { + yyb21 = yyj21 > l } else { - yyb19 = z.DecCheckBreak() + yyb21 = z.DecCheckBreak() } - if yyb19 { + if yyb21 { z.DecReadArrayEnd() return } @@ -352,13 +372,13 @@ func (x *Receipt) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { z.DecFallback(x.L1GasPrice, false) } } - yyj19++ - if yyhl19 { - yyb19 = yyj19 > l + yyj21++ + if yyhl21 { + yyb21 = yyj21 > l } else { - yyb19 = z.DecCheckBreak() + yyb21 = z.DecCheckBreak() } - if yyb19 { + if yyb21 { z.DecReadArrayEnd() return } @@ -377,13 +397,13 @@ func (x *Receipt) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { z.DecFallback(x.L1GasUsed, false) } } - yyj19++ - if yyhl19 { - yyb19 = yyj19 > l + yyj21++ + if yyhl21 { + yyb21 = yyj21 > l } else { - yyb19 = z.DecCheckBreak() + yyb21 = z.DecCheckBreak() } - if yyb19 { + if yyb21 { z.DecReadArrayEnd() return } @@ -402,13 +422,13 @@ func (x *Receipt) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { z.DecFallback(x.L1Fee, false) } } - yyj19++ - if yyhl19 { - yyb19 = yyj19 > l + yyj21++ + if yyhl21 { + yyb21 = yyj21 > l } else { - yyb19 = z.DecCheckBreak() + yyb21 = z.DecCheckBreak() } - if yyb19 { + if yyb21 { z.DecReadArrayEnd() return } @@ -427,13 +447,13 @@ func (x *Receipt) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { z.DecFallback(x.FeeScalar, false) } } - yyj19++ - if yyhl19 { - yyb19 = yyj19 > l + yyj21++ + if yyhl21 { + yyb21 = yyj21 > l } else { - yyb19 = z.DecCheckBreak() + yyb21 = z.DecCheckBreak() } - if yyb19 { + if yyb21 { z.DecReadArrayEnd() return } @@ -448,18 +468,39 @@ func (x *Receipt) codecDecodeSelfFromArray(l int, d *codec1978.Decoder) { } *x.DepositNonce = (uint64)(r.DecodeUint64()) } + yyj21++ + if yyhl21 { + yyb21 = yyj21 > l + } else { + yyb21 = z.DecCheckBreak() + } + if yyb21 { + z.DecReadArrayEnd() + return + } + z.DecReadArrayElem() + if r.TryNil() { + if x.DepositReceiptVersion != nil { // remove the if-true + x.DepositReceiptVersion = nil + } + } else { + if x.DepositReceiptVersion == nil { + x.DepositReceiptVersion = new(uint64) + } + *x.DepositReceiptVersion = (uint64)(r.DecodeUint64()) + } for { - yyj19++ - if yyhl19 { - yyb19 = yyj19 > l + yyj21++ + if yyhl21 { + yyb21 = yyj21 > l } else { - yyb19 = z.DecCheckBreak() + yyb21 = z.DecCheckBreak() } - if yyb19 { + if yyb21 { break } z.DecReadArrayElem() - z.DecStructFieldNotFound(yyj19-1, "") + z.DecStructFieldNotFound(yyj21-1, "") } } From 699ca44efa3198e68d2ec0985b5fdf3671fbbfef Mon Sep 17 00:00:00 2001 From: pcw109550 Date: Sat, 4 Nov 2023 20:12:33 +0900 Subject: [PATCH 09/10] Add receipt test for json sedes --- core/types/receipt_test.go | 39 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/core/types/receipt_test.go b/core/types/receipt_test.go index 17f6d783659..cb18e7ae2de 100644 --- a/core/types/receipt_test.go +++ b/core/types/receipt_test.go @@ -663,3 +663,42 @@ func TestRoundTripReceiptForStorage(t *testing.T) { }) } } + +func TestReceiptJSON(t *testing.T) { + tests := []struct { + name string + rcpt *Receipt + }{ + {name: "Legacy", rcpt: legacyReceipt}, + {name: "AccessList", rcpt: accessListReceipt}, + {name: "EIP1559", rcpt: eip1559Receipt}, + {name: "DepositNoNonce", rcpt: depositReceiptNoNonce}, + {name: "DepositWithNonce", rcpt: depositReceiptWithNonce}, + {name: "DepositWithNonceAndVersion", rcpt: depositReceiptWithNonceAndVersion}, + } + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + b, err := test.rcpt.MarshalJSON() + if err != nil { + t.Fatal("error marshaling receipt to json:", err) + } + r := Receipt{} + err = r.UnmarshalJSON(b) + if err != nil { + t.Fatal("error unmarshaling receipt from json:", err) + } + + // Make sure marshal/unmarshal doesn't affect receipt hash root computation by comparing + // the output of EncodeIndex + rsBefore := Receipts([]*Receipt{test.rcpt}) + rsAfter := Receipts([]*Receipt{&r}) + + encBefore, encAfter := bytes.Buffer{}, bytes.Buffer{} + rsBefore.EncodeIndex(0, &encBefore) + rsAfter.EncodeIndex(0, &encAfter) + if !bytes.Equal(encBefore.Bytes(), encAfter.Bytes()) { + t.Errorf("%v: EncodeIndex differs after JSON marshal/unmarshal", test.name) + } + }) + } +} From 3e319d2f63d7bdae176263451208644ee7b7e559 Mon Sep 17 00:00:00 2001 From: pcw109550 Date: Mon, 6 Nov 2023 17:21:29 +0900 Subject: [PATCH 10/10] Remove unnecessary conversion --- cmd/rpcdaemon/commands/otterscan_api.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/rpcdaemon/commands/otterscan_api.go b/cmd/rpcdaemon/commands/otterscan_api.go index 8a3d4b8f3aa..64b0acb2a99 100644 --- a/cmd/rpcdaemon/commands/otterscan_api.go +++ b/cmd/rpcdaemon/commands/otterscan_api.go @@ -523,8 +523,8 @@ func (api *OtterscanAPIImpl) searchTransactionsBeforeV3(tx kv.TemporalTx, ctx co var receipt *types.Receipt if chainConfig.IsOptimism() { receipts := rawdb.ReadRawReceipts(tx, blockNum) - if len(receipts) <= int(txIndex) { - return nil, fmt.Errorf("block has less receipts than expected: %d <= %d, block: %d", len(receipts), int(txIndex), blockNum) + if len(receipts) <= txIndex { + return nil, fmt.Errorf("block has less receipts than expected: %d <= %d, block: %d", len(receipts), txIndex, blockNum) } receipt = receipts[txIndex] }