diff --git a/eth/protocols/eth/handlers.go b/eth/protocols/eth/handlers.go index 32c55bdafa..10bf20875e 100644 --- a/eth/protocols/eth/handlers.go +++ b/eth/protocols/eth/handlers.go @@ -269,33 +269,28 @@ func handleGetReceipts70(backend Backend, msg Decoder, peer *Peer) error { // It does not send the bloom filters for the receipts. It is exposed // to allow external packages to test protocol behavior. func ServiceGetReceiptsQuery69(chain *core.BlockChain, query GetReceiptsRequest) rlp.RawList[*ReceiptList] { - // Gather state data until the fetch or network limits is reached var ( bytes int receipts rlp.RawList[*ReceiptList] ) for lookups, hash := range query { - if bytes >= softResponseLimit || receipts.Len() >= maxReceiptsServe || - lookups >= 2*maxReceiptsServe { + if bytes >= softResponseLimit || receipts.Len() >= maxReceiptsServe || lookups >= 2*maxReceiptsServe { break } + // Retrieve the requested block's receipts results := chain.GetReceiptsRLP(hash) if results == nil { - if header := chain.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash { - continue - } - } else { - body := chain.GetBodyRLP(hash) - if body == nil { - continue - } - var err error - results, err = blockReceiptsToNetwork(results, body) - if err != nil { - log.Error("Error in block receipts conversion", "hash", hash, "err", err) - continue - } + continue // Can't retrieve the receipts, so we just skip this block. + } + body := chain.GetBodyRLP(hash) + if body == nil { + continue // The block body is missing, we also have to skip. + } + results, _, err := blockReceiptsToNetwork(results, body, receiptQueryParams{}) + if err != nil { + log.Error("Error in block receipts conversion", "hash", hash, "err", err) + continue } receipts.AppendRaw(results) bytes += len(results) @@ -309,9 +304,8 @@ func ServiceGetReceiptsQuery69(chain *core.BlockChain, query GetReceiptsRequest) // are omitted from the first block receipt list. func serviceGetReceiptsQuery70(chain *core.BlockChain, query GetReceiptsRequest, firstBlockReceiptIndex uint64) (rlp.RawList[*ReceiptList], bool) { var ( - bytes int - receipts rlp.RawList[*ReceiptList] - lastBlockIncomplete bool + bytes int + receipts rlp.RawList[*ReceiptList] ) for i, hash := range query { if bytes >= softResponseLimit || receipts.Len() >= maxReceiptsServe { @@ -319,65 +313,28 @@ func serviceGetReceiptsQuery70(chain *core.BlockChain, query GetReceiptsRequest, } results := chain.GetReceiptsRLP(hash) if results == nil { - if header := chain.GetHeaderByHash(hash); header == nil || header.ReceiptHash != types.EmptyRootHash { - continue - } - } else { - body := chain.GetBodyRLP(hash) - if body == nil { - continue - } - var err error - results, err = blockReceiptsToNetwork(results, body) - if err != nil { - log.Error("Error in block receipts conversion", "hash", hash, "err", err) - continue - } + continue // Can't retrieve the receipts, so we just skip this block. } - - if firstBlockReceiptIndex > 0 && i == 0 { - results, lastBlockIncomplete = trimReceiptsRLP(results, int(firstBlockReceiptIndex), maxPacketSize) - } else if bytes+len(results) > maxPacketSize { - results, lastBlockIncomplete = trimReceiptsRLP(results, 0, maxPacketSize-bytes) + body := chain.GetBodyRLP(hash) + if body == nil { + continue // The block body is missing, we also have to skip. } - - receipts.AppendRaw(results) - bytes += len(results) - } - - return receipts, lastBlockIncomplete -} - -// trimReceiptsRLP trims raw value from `from` index until it exceeds limit -func trimReceiptsRLP(receiptsRLP rlp.RawValue, from int, limit int) (rlp.RawValue, bool) { - var ( - out bytes.Buffer - buffer = rlp.NewEncoderBuffer(&out) - iter, _ = rlp.NewListIterator(receiptsRLP) - index int - bytes int - overflow bool - ) - - list := buffer.List() - for iter.Next() { - if index < from { - index++ + q := receiptQueryParams{sizeLimit: uint64(maxPacketSize - bytes)} + if i == 0 { + q.firstIndex = firstBlockReceiptIndex + } + results, incomplete, err := blockReceiptsToNetwork(results, body, q) + if err != nil { + log.Error("Error in block receipts conversion", "hash", hash, "err", err) continue } - receipt := iter.Value() - if bytes+len(receipt) > limit { - overflow = true - break + receipts.AppendRaw(results) + bytes += len(results) + if incomplete { + return receipts, true } - buffer.Write(receipt) - bytes += len(receipt) - index++ } - buffer.ListEnd(list) - buffer.Flush() - - return out.Bytes(), overflow + return receipts, false } func handleBlockHeaders(backend Backend, msg Decoder, peer *Peer) error { diff --git a/eth/protocols/eth/receipt.go b/eth/protocols/eth/receipt.go index 4452880965..d8b64b0cbb 100644 --- a/eth/protocols/eth/receipt.go +++ b/eth/protocols/eth/receipt.go @@ -240,12 +240,17 @@ func (rl *ReceiptList) LogsSize() (uint64, error) { return size, nil } -// blockReceiptsToNetwork takes a slice of rlp-encoded receipts, and transactions, -// and re-encodes them for the network protocol. -func blockReceiptsToNetwork(blockReceipts, blockBody rlp.RawValue) ([]byte, error) { +type receiptQueryParams struct { + firstIndex uint64 + sizeLimit uint64 +} + +// blockReceiptsToNetwork takes a slice of rlp-encoded receipts (in the 'storage' encoding), +// and an encoded block body, and re-encodes the receipts for the network protocol. +func blockReceiptsToNetwork(blockReceipts, blockBody rlp.RawValue, q receiptQueryParams) (output []byte, incomplete bool, err error) { txTypesIter, err := txTypesInBody(blockBody) if err != nil { - return nil, fmt.Errorf("invalid block body: %v", err) + return nil, false, fmt.Errorf("invalid block body: %v", err) } nextTxType, stopTxTypes := iter.Pull(txTypesIter) defer stopTxTypes() @@ -256,9 +261,24 @@ func blockReceiptsToNetwork(blockReceipts, blockBody rlp.RawValue) ([]byte, erro it, _ = rlp.NewListIterator(blockReceipts) ) outer := enc.List() - for i := 0; it.Next(); i++ { - txType, _ := nextTxType() + for i := 0; it.Next() && !incomplete; i++ { + txType, end := nextTxType() + if end { + return nil, false, fmt.Errorf("block has less txs than receipts (%d)", i) + } + // Skip receipts before the requested index. + if uint64(i) < q.firstIndex { + continue + } content, _, _ := rlp.SplitList(it.Value()) + // Stop appending receipts when they would go over the size limit. + // Note we rely on the assumption that the txType is encoded as a single byte, + // which is always true because EIP-2718 does not allow tx types > 0x7f. + size := rlp.ListSize(1 + uint64(len(content))) + if q.sizeLimit > 0 && (uint64(enc.Size())+size) > q.sizeLimit { + break + } + receiptList := enc.List() enc.WriteUint64(uint64(txType)) enc.Write(content) @@ -266,7 +286,7 @@ func blockReceiptsToNetwork(blockReceipts, blockBody rlp.RawValue) ([]byte, erro } enc.ListEnd(outer) enc.Flush() - return out.Bytes(), nil + return out.Bytes(), incomplete, nil } // txTypesInBody parses the transactions list of an encoded block body, returning just the types. diff --git a/eth/protocols/eth/receipt_test.go b/eth/protocols/eth/receipt_test.go index 693ccd6918..237f7b9420 100644 --- a/eth/protocols/eth/receipt_test.go +++ b/eth/protocols/eth/receipt_test.go @@ -105,10 +105,13 @@ func TestReceiptList(t *testing.T) { canonBody, _ := rlp.EncodeToBytes(blockBody) // convert from storage encoding to network encoding - network, err := blockReceiptsToNetwork(canonDB, canonBody) + network, incomplete, err := blockReceiptsToNetwork(canonDB, canonBody, receiptQueryParams{}) if err != nil { t.Fatalf("test[%d]: blockReceiptsToNetwork error: %v", i, err) } + if incomplete { + t.Fatalf("test[%d]: blockReceiptsToNetwork returned incomplete == true", i) + } // parse as Receipts response list from network encoding var rl ReceiptList