-
Notifications
You must be signed in to change notification settings - Fork 185
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Parallelize header download across peers to fetch headers within a chain's checkpointed region. #282
base: master
Are you sure you want to change the base?
Parallelize header download across peers to fetch headers within a chain's checkpointed region. #282
Changes from 1 commit
40670d1
9ab1d8b
101c690
f168538
dd02e22
2285ce7
64b2787
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,7 @@ package neutrino | |
import ( | ||
"bytes" | ||
"container/list" | ||
"errors" | ||
"fmt" | ||
"math" | ||
"math/big" | ||
|
@@ -808,19 +809,39 @@ func (b *blockManager) getUncheckpointedCFHeaders( | |
// handle a query for checkpointed filter headers. | ||
type checkpointedCFHeadersQuery struct { | ||
blockMgr *blockManager | ||
msgs []wire.Message | ||
msgs []*encodedQuery | ||
checkpoints []*chainhash.Hash | ||
stopHashes map[chainhash.Hash]uint32 | ||
headerChan chan *wire.MsgCFHeaders | ||
} | ||
|
||
// encodedQuery holds all the information needed to query a message that pushes requests | ||
// using the QueryMessagingWithEncoding method. | ||
type encodedQuery struct { | ||
message wire.Message | ||
encoding wire.MessageEncoding | ||
priorityIndex uint64 | ||
} | ||
|
||
// Message returns the wire.Message of encodedQuery's struct. | ||
func (e *encodedQuery) Message() wire.Message { | ||
return e.message | ||
} | ||
|
||
// PriorityIndex returns the specified priority the caller wants | ||
// the request to take. | ||
func (e *encodedQuery) PriorityIndex() uint64 { | ||
return e.priorityIndex | ||
} | ||
|
||
// requests creates the query.Requests for this CF headers query. | ||
func (c *checkpointedCFHeadersQuery) requests() []*query.Request { | ||
reqs := make([]*query.Request, len(c.msgs)) | ||
for idx, m := range c.msgs { | ||
reqs[idx] = &query.Request{ | ||
Req: m, | ||
HandleResp: c.handleResponse, | ||
SendQuery: sendQueryMessageWithEncoding, | ||
} | ||
} | ||
return reqs | ||
|
@@ -924,6 +945,24 @@ func (c *checkpointedCFHeadersQuery) handleResponse(req, resp wire.Message, | |
} | ||
} | ||
|
||
// sendQueryMessageWithEncoding sends a message to the peer with encoding. | ||
func sendQueryMessageWithEncoding(peer query.Peer, req query.ReqMessage) error { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. why not just require that this is a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Because it follows this function signature: There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It looks like There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. That was removed in this commit: 9ab1d8b |
||
sp, ok := peer.(*ServerPeer) | ||
if !ok { | ||
err := "peer is not of type ServerPeer" | ||
log.Errorf(err) | ||
return errors.New(err) | ||
} | ||
request, ok := req.(*encodedQuery) | ||
if !ok { | ||
return errors.New("invalid request type") | ||
} | ||
|
||
sp.QueueMessageWithEncoding(request.message, nil, request.encoding) | ||
|
||
return nil | ||
} | ||
|
||
// getCheckpointedCFHeaders catches a filter header store up with the | ||
// checkpoints we got from the network. It assumes that the filter header store | ||
// matches the checkpoints up to the tip of the store. | ||
|
@@ -959,7 +998,7 @@ func (b *blockManager) getCheckpointedCFHeaders(checkpoints []*chainhash.Hash, | |
// the remaining checkpoint intervals. | ||
numCheckpts := uint32(len(checkpoints)) - startingInterval | ||
numQueries := (numCheckpts + maxCFCheckptsPerQuery - 1) / maxCFCheckptsPerQuery | ||
queryMsgs := make([]wire.Message, 0, numQueries) | ||
queryMsgs := make([]*encodedQuery, 0, numQueries) | ||
|
||
// We'll also create an additional set of maps that we'll use to | ||
// re-order the responses as we get them in. | ||
|
@@ -1004,9 +1043,12 @@ func (b *blockManager) getCheckpointedCFHeaders(checkpoints []*chainhash.Hash, | |
|
||
// Once we have the stop hash, we can construct the query | ||
// message itself. | ||
queryMsg := wire.NewMsgGetCFHeaders( | ||
fType, startHeightRange, &stopHash, | ||
) | ||
queryMsg := &encodedQuery{ | ||
message: wire.NewMsgGetCFHeaders( | ||
fType, startHeightRange, &stopHash, | ||
), | ||
encoding: wire.WitnessEncoding, | ||
} | ||
|
||
// We'll mark that the ith interval is queried by this message, | ||
// and also map the stop hash back to the index of this message. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,8 +3,6 @@ package query | |
import ( | ||
"errors" | ||
"time" | ||
|
||
"github.com/btcsuite/btcd/wire" | ||
) | ||
|
||
var ( | ||
|
@@ -27,7 +25,6 @@ type queryJob struct { | |
tries uint8 | ||
index uint64 | ||
timeout time.Duration | ||
encoding wire.MessageEncoding | ||
cancelChan <-chan struct{} | ||
*Request | ||
} | ||
|
@@ -89,6 +86,7 @@ func (w *worker) Run(results chan<- *jobResult, quit <-chan struct{}) { | |
msgChan, cancel := peer.SubscribeRecvMsg() | ||
defer cancel() | ||
|
||
nexJobLoop: | ||
for { | ||
log.Tracef("Worker %v waiting for more work", peer.Addr()) | ||
|
||
|
@@ -133,7 +131,22 @@ func (w *worker) Run(results chan<- *jobResult, quit <-chan struct{}) { | |
log.Tracef("Worker %v queuing job %T with index %v", | ||
peer.Addr(), job.Req, job.Index()) | ||
|
||
peer.QueueMessageWithEncoding(job.Req, nil, job.encoding) | ||
err := job.SendQuery(peer, job.Req) | ||
|
||
// If any error occurs while sending query, quickly send the result | ||
// containing the error to the workmanager. | ||
if err != nil { | ||
select { | ||
case results <- &jobResult{ | ||
job: job, | ||
peer: peer, | ||
err: err, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes as There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think including it would make the code more readable There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what about redundancy? |
||
}: | ||
case <-quit: | ||
return | ||
} | ||
goto nexJobLoop | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Would a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks looking at it again, I think |
||
} | ||
} | ||
|
||
// Wait for the correct response to be received from the peer, | ||
|
@@ -143,15 +156,15 @@ func (w *worker) Run(results chan<- *jobResult, quit <-chan struct{}) { | |
timeout = time.NewTimer(job.timeout) | ||
) | ||
|
||
Loop: | ||
feedbackLoop: | ||
for { | ||
select { | ||
// A message was received from the peer, use the | ||
// response handler to check whether it was answering | ||
// our request. | ||
case resp := <-msgChan: | ||
progress := job.HandleResp( | ||
job.Req, resp, peer.Addr(), | ||
job.Req.Message(), resp, peer.Addr(), | ||
) | ||
|
||
log.Tracef("Worker %v handled msg %T while "+ | ||
|
@@ -176,12 +189,12 @@ func (w *worker) Run(results chan<- *jobResult, quit <-chan struct{}) { | |
job.timeout, | ||
) | ||
} | ||
continue Loop | ||
continue feedbackLoop | ||
} | ||
|
||
// We did get a valid response, and can break | ||
// the loop. | ||
break Loop | ||
break feedbackLoop | ||
|
||
// If the timeout is reached before a valid response | ||
// has been received, we exit with an error. | ||
|
@@ -193,7 +206,7 @@ func (w *worker) Run(results chan<- *jobResult, quit <-chan struct{}) { | |
"with job index %v", peer.Addr(), | ||
job.Req, job.Index()) | ||
|
||
break Loop | ||
break feedbackLoop | ||
|
||
// If the peer disconnects before giving us a valid | ||
// answer, we'll also exit with an error. | ||
|
@@ -203,7 +216,7 @@ func (w *worker) Run(results chan<- *jobResult, quit <-chan struct{}) { | |
job.Index()) | ||
|
||
jobErr = ErrPeerDisconnected | ||
break Loop | ||
break feedbackLoop | ||
|
||
// If the job was canceled, we report this back to the | ||
// work manager. | ||
|
@@ -212,7 +225,7 @@ func (w *worker) Run(results chan<- *jobResult, quit <-chan struct{}) { | |
peer.Addr(), job.Index()) | ||
|
||
jobErr = ErrJobCanceled | ||
break Loop | ||
break feedbackLoop | ||
|
||
case <-quit: | ||
return | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What's the reasonale for the priority index? It's adding quite a bit of complexity to the code.
Would the caller asking for block headers within a moving window suffice instead of having a priority index?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The work manager assigns priority to jobs on a first come, first serve basis. There are cases where we might need to bump the priority of a job sent at a later time, that is when the priority index comes in. Example use case:
https://github.com/lightninglabs/neutrino/pull/282/files#diff-2345cea8b12f07d1f60d42b7e5563720e69e1544c641261cf147668c4599320bR2227 (I would be changing the priority to not 0 in my next push, though)
Where we might need to re-request headers after validation while fetching headers within the checkpointed region. If there was no priority index, this particular job would be handled last after all jobs, we do not want this as we would not be able to progress with block header verification and writing into the database if we do not have those headers we re-requested at that particular height (validation is done in order). This is because it is at the chain tip and therefore next in line to be processed here
Could you please explain more about what you mean by this?