mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-05-25 09:19:28 +00:00
In this PR, we add support for protocol version eth/70, defined by EIP-7975. Overall changes: - Each response is buffered in the peer’s receipt buffer when the `lastBlockIncomplete` field is true. - Continued request uses the same request id of its original request(`RequestPartialReceipts`). - Partial responses are verified in `validateLastBlockReceipt`. - Even if all receipts for partial blocks of the request are collected, those partial results are not sinked to the downloader, to avoid complexity. This assumes that partial response and buffering occur only in exceptional cases. --------- Co-authored-by: Gary Rong <garyrong0905@gmail.com> Co-authored-by: Felix Lange <fjl@twurst.com>
276 lines
8.7 KiB
Go
276 lines
8.7 KiB
Go
// Copyright 2021 The go-ethereum Authors
|
|
// This file is part of the go-ethereum library.
|
|
//
|
|
// The go-ethereum library is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Lesser General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// The go-ethereum library is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Lesser General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Lesser General Public License
|
|
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
package eth
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/ethereum/go-ethereum/p2p"
|
|
"github.com/ethereum/go-ethereum/p2p/tracker"
|
|
)
|
|
|
|
var (
|
|
// errDisconnected is returned if a request is attempted to be made to a peer
|
|
// that was already closed.
|
|
errDisconnected = errors.New("disconnected")
|
|
|
|
// errDanglingResponse is returned if a response arrives with a request id
|
|
// which does not match to any existing pending requests.
|
|
errDanglingResponse = errors.New("response to non-existent request")
|
|
|
|
// errMismatchingResponseType is returned if the remote peer sent a different
|
|
// packet type as a response to a request than what the local node expected.
|
|
errMismatchingResponseType = errors.New("mismatching response type")
|
|
)
|
|
|
|
// Request is a pending request to allow tracking it and delivering a response
|
|
// back to the requester on their chosen channel.
|
|
type Request struct {
|
|
peer *Peer // Peer to which this request belongs for untracking
|
|
id uint64 // Request ID to match up replies to
|
|
|
|
sink chan *Response // Channel to deliver the response on
|
|
cancel chan struct{} // Channel to cancel requests ahead of time
|
|
|
|
code uint64 // Message code of the request packet
|
|
want uint64 // Message code of the response packet
|
|
numItems int // Number of requested items
|
|
data interface{} // Data content of the request packet
|
|
|
|
Peer string // Demultiplexer if cross-peer requests are batched together
|
|
Sent time.Time // Timestamp when the request was sent
|
|
}
|
|
|
|
// Close aborts an in-flight request. Although there's no way to notify the
|
|
// remote peer about the cancellation, this method notifies the dispatcher to
|
|
// discard any late responses.
|
|
func (r *Request) Close() error {
|
|
if r.peer == nil { // Tests mock out the dispatcher, skip internal cancellation
|
|
return nil
|
|
}
|
|
cancelOp := &cancel{
|
|
id: r.id,
|
|
fail: make(chan error),
|
|
}
|
|
select {
|
|
case r.peer.reqCancel <- cancelOp:
|
|
if err := <-cancelOp.fail; err != nil {
|
|
return err
|
|
}
|
|
close(r.cancel)
|
|
return nil
|
|
case <-r.peer.term:
|
|
return errDisconnected
|
|
}
|
|
}
|
|
|
|
// request is a wrapper around a client Request that has an error channel to
|
|
// signal on if sending the request already failed on a network level.
|
|
type request struct {
|
|
req *Request
|
|
fail chan error
|
|
}
|
|
|
|
// cancel is a maintenance type on the dispatcher to stop tracking a pending
|
|
// request.
|
|
type cancel struct {
|
|
id uint64 // Request ID to stop tracking
|
|
fail chan error
|
|
}
|
|
|
|
// Response is a reply packet to a previously created request. It is delivered
|
|
// on the channel assigned by the requester subsystem and contains the original
|
|
// request embedded to allow uniquely matching it caller side.
|
|
type Response struct {
|
|
id uint64 // Request ID to match up this reply to
|
|
recv time.Time // Timestamp when the request was received
|
|
code uint64 // Response packet type to cross validate with request
|
|
|
|
Req *Request // Original request to cross-reference with
|
|
Res interface{} // Remote response for the request query
|
|
Meta interface{} // Metadata generated locally on the receiver thread
|
|
Time time.Duration // Time it took for the request to be served
|
|
Done chan error // Channel to signal message handling to the reader
|
|
}
|
|
|
|
// response is a wrapper around a remote Response that has an error channel to
|
|
// signal on if processing the response failed.
|
|
type response struct {
|
|
res *Response
|
|
fail chan error
|
|
}
|
|
|
|
// dispatchRequest schedules the request to the dispatcher for tracking and
|
|
// network serialization, blocking until it's successfully sent.
|
|
//
|
|
// The returned Request must either be closed before discarding it, or the reply
|
|
// must be waited for and the Response's Done channel signalled.
|
|
func (p *Peer) dispatchRequest(req *Request) error {
|
|
reqOp := &request{
|
|
req: req,
|
|
fail: make(chan error),
|
|
}
|
|
req.cancel = make(chan struct{})
|
|
req.peer = p
|
|
req.Peer = p.id
|
|
|
|
select {
|
|
case p.reqDispatch <- reqOp:
|
|
return <-reqOp.fail
|
|
case <-p.term:
|
|
return errDisconnected
|
|
}
|
|
}
|
|
|
|
// dispatchResponse fulfils a pending request and delivers it to the requested
|
|
// sink.
|
|
func (p *Peer) dispatchResponse(res *Response, metadata func() interface{}) error {
|
|
resOp := &response{
|
|
res: res,
|
|
fail: make(chan error),
|
|
}
|
|
res.recv = time.Now()
|
|
res.Done = make(chan error)
|
|
|
|
select {
|
|
case p.resDispatch <- resOp:
|
|
// Ensure the response is accepted by the dispatcher
|
|
if err := <-resOp.fail; err != nil {
|
|
return nil
|
|
}
|
|
// Request was accepted, run any postprocessing step to generate metadata
|
|
// on the receiver thread, not the sink thread
|
|
if metadata != nil {
|
|
res.Meta = metadata()
|
|
}
|
|
// Deliver the filled out response and wait until it's handled. This
|
|
// path is a bit funky as Go's select has no order, so if a response
|
|
// arrives to an already cancelled request, there's a 50-50% changes
|
|
// of picking on channel or the other. To avoid such cases delivering
|
|
// the packet upstream, check for cancellation first and only after
|
|
// block on delivery.
|
|
select {
|
|
case <-res.Req.cancel:
|
|
return nil // Request cancelled, silently discard response
|
|
default:
|
|
// Request not yet cancelled, attempt to deliver it, but do watch
|
|
// for fresh cancellations too
|
|
select {
|
|
case res.Req.sink <- res:
|
|
return <-res.Done // Response delivered, return any errors
|
|
case <-res.Req.cancel:
|
|
return nil // Request cancelled, silently discard response
|
|
case <-p.term:
|
|
return errDisconnected
|
|
}
|
|
}
|
|
|
|
case <-p.term:
|
|
return errDisconnected
|
|
}
|
|
}
|
|
|
|
// dispatcher is a loop that accepts requests from higher layer packages, pushes
|
|
// it to the network and tracks and dispatches the responses back to the original
|
|
// requester.
|
|
func (p *Peer) dispatcher() {
|
|
pending := make(map[uint64]*Request)
|
|
|
|
loop:
|
|
for {
|
|
select {
|
|
case reqOp := <-p.reqDispatch:
|
|
req := reqOp.req
|
|
req.Sent = time.Now()
|
|
|
|
treq := tracker.Request{
|
|
ID: req.id,
|
|
ReqCode: req.code,
|
|
RespCode: req.want,
|
|
Size: req.numItems,
|
|
}
|
|
if err := p.tracker.Track(treq); err != nil {
|
|
reqOp.fail <- err
|
|
continue loop
|
|
}
|
|
if err := p2p.Send(p.rw, req.code, req.data); err != nil {
|
|
reqOp.fail <- err
|
|
continue loop
|
|
}
|
|
|
|
// do not overwrite if it is re-request
|
|
if _, ok := pending[req.id]; !ok {
|
|
pending[req.id] = req
|
|
}
|
|
reqOp.fail <- nil
|
|
|
|
case cancelOp := <-p.reqCancel:
|
|
// Retrieve the pending request to cancel and short circuit if it
|
|
// has already been serviced and is not available anymore
|
|
req := pending[cancelOp.id]
|
|
if req == nil {
|
|
cancelOp.fail <- nil
|
|
continue
|
|
}
|
|
// Stop tracking the request
|
|
delete(pending, cancelOp.id)
|
|
|
|
// Not sure if the request is about the receipt, but remove it anyway.
|
|
// TODO(rjl493456442, bosul): investigate whether we can avoid leaking peer fields here.
|
|
p.receiptBufferLock.Lock()
|
|
delete(p.receiptBuffer, cancelOp.id)
|
|
p.receiptBufferLock.Unlock()
|
|
|
|
cancelOp.fail <- nil
|
|
|
|
case resOp := <-p.resDispatch:
|
|
res := resOp.res
|
|
res.Req = pending[res.id]
|
|
|
|
switch {
|
|
case res.Req == nil:
|
|
// Response arrived with an untracked ID. Since even cancelled
|
|
// requests are tracked until fulfillment, a dangling response
|
|
// means the remote peer implements the protocol badly.
|
|
resOp.fail <- errDanglingResponse
|
|
|
|
case res.Req.want != res.code:
|
|
// Response arrived, but it's a different packet type than the
|
|
// one expected by the requester. Either the local code is bad,
|
|
// or the remote peer send junk. In neither cases can we handle
|
|
// the packet.
|
|
resOp.fail <- fmt.Errorf("%w: have %d, want %d", errMismatchingResponseType, res.code, res.Req.want)
|
|
|
|
default:
|
|
// All dispatcher checks passed and the response was initialized
|
|
// with the matching request. Signal to the delivery routine that
|
|
// it can wait for a handler response and dispatch the data.
|
|
res.Time = res.recv.Sub(res.Req.Sent)
|
|
resOp.fail <- nil
|
|
|
|
// Stop tracking the request, the response dispatcher will deliver
|
|
delete(pending, res.id)
|
|
}
|
|
|
|
case <-p.term:
|
|
p.tracker.Stop()
|
|
return
|
|
}
|
|
}
|
|
}
|