[NOD-640] Revamp blueBlocksBetween to return up to maxEntries from lowNode's antiPast to highNode's antiFuture (#593)

* [NOD-640] Revamp blueBlocksBetween to return up to maxEntries from highNode's antiFuture.

* [NOD-640] Fix bad traversal.

* [NOD-640] Use more accurate len.

* [NOD-640] Use more appropriate len in another place.

* [NOD-640] Remove the whole business with highNode's anticone.

* [NOD-640] Rename highNodeAntiFuture to candidateNodes.

* [NOD-640] Explain the highNode.blueScore-lowNode.blueScore+1 approximation.

* [NOD-640] UpHeap -> upHeap.

* [NOD-640] Fix off-by-one error.

* [NOD-640] Rename blueBlocksBetween to antiPastBetween,

* [NOD-640] upHeap -> up-heap.

* [NOD-640] Use a classic for to populate nodes.

* [NOD-640] Reworded a comment.

* [NOD-640] Clarify a comment.

* [NOD-640] Fix nodes declaration.
This commit is contained in:
stasatdaglabs 2020-01-22 12:13:55 +02:00 committed by Svarog
parent 49418f4222
commit 9d434de4a5
5 changed files with 79 additions and 61 deletions

View File

@ -1593,13 +1593,13 @@ func (dag *BlockDAG) SelectedParentHash(blockHash *daghash.Hash) (*daghash.Hash,
return node.selectedParent.hash, nil
}
// blueBlockHashesBetween returns the hashes of the blocks after the provided
// low hash until the provided high hash is reached, or up to the
// provided max number of block hashes.
// antiPastHashesBetween returns the hashes of the blocks between the
// lowHash's antiPast and highHash's antiPast, or up to the provided
// max number of block hashes.
//
// This function MUST be called with the DAG state lock held (for reads).
func (dag *BlockDAG) blueBlockHashesBetween(lowHash, highHash *daghash.Hash, maxHashes uint64) ([]*daghash.Hash, error) {
nodes, err := dag.blueBlocksBetween(lowHash, highHash, maxHashes)
func (dag *BlockDAG) antiPastHashesBetween(lowHash, highHash *daghash.Hash, maxHashes uint64) ([]*daghash.Hash, error) {
nodes, err := dag.antiPastBetween(lowHash, highHash, maxHashes)
if err != nil {
return nil, err
}
@ -1610,7 +1610,11 @@ func (dag *BlockDAG) blueBlockHashesBetween(lowHash, highHash *daghash.Hash, max
return hashes, nil
}
func (dag *BlockDAG) blueBlocksBetween(lowHash, highHash *daghash.Hash, maxEntries uint64) ([]*blockNode, error) {
// antiPastBetween returns the blockNodes between the lowHash's antiPast
// and highHash's antiPast, or up to the provided max number of blocks.
//
// This function MUST be called with the DAG state lock held (for reads).
func (dag *BlockDAG) antiPastBetween(lowHash, highHash *daghash.Hash, maxEntries uint64) ([]*blockNode, error) {
lowNode := dag.index.LookupNode(lowHash)
if lowNode == nil {
return nil, errors.Errorf("Couldn't find low hash %s", lowHash)
@ -1620,50 +1624,65 @@ func (dag *BlockDAG) blueBlocksBetween(lowHash, highHash *daghash.Hash, maxEntri
return nil, errors.Errorf("Couldn't find high hash %s", highHash)
}
// In order to get no more then maxEntries of blue blocks from
// the future of the lowNode (including itself), we iterate
// the selected parent chain of the highNode and add the blues
// each node (including the highNode itself). This is why the
// number of returned blocks will be
// highNode.blueScore-lowNode.blueScore+1.
// If highNode.blueScore-lowNode.blueScore+1 > maxEntries, we
// first iterate on the selected parent chain of the highNode
// until we find a new highNode
// where highNode.blueScore-lowNode.blueScore+1 <= maxEntries
// In order to get no more then maxEntries blocks from the
// future of the lowNode (including itself), we iterate the
// selected parent chain of the highNode and stop once we reach
// highNode.blueScore-lowNode.blueScore+1 <= maxEntries. That
// stop point becomes the new highNode.
// Using blueScore as an approximation is considered to be
// fairly accurate because we presume that most DAG blocks are
// blue.
for highNode.blueScore-lowNode.blueScore+1 > maxEntries {
highNode = highNode.selectedParent
}
// Populate and return the found nodes.
nodes := make([]*blockNode, 0, highNode.blueScore-lowNode.blueScore+1)
nodes = append(nodes, highNode)
current := highNode
for current.blueScore > lowNode.blueScore {
for _, blue := range current.blues {
nodes = append(nodes, blue)
// Collect every node in highNode's past (including itself) but
// NOT in the lowNode's past (excluding itself) into an up-heap
// (a heap sorted by blueScore from lowest to greatest).
visited := newSet()
candidateNodes := newUpHeap()
queue := newDownHeap()
queue.Push(highNode)
for queue.Len() > 0 {
current := queue.pop()
if visited.contains(current) {
continue
}
visited.add(current)
isCurrentAncestorOfLowNode, err := dag.isAncestorOf(current, lowNode)
if err != nil {
return nil, err
}
if isCurrentAncestorOfLowNode {
continue
}
candidateNodes.Push(current)
for _, parent := range current.parents {
queue.Push(parent)
}
current = current.selectedParent
}
if current != lowNode {
return nil, errors.Errorf("the low hash is not found in the " +
"selected parent chain of the high hash")
// Pop candidateNodes into a slice. Since candidateNodes is
// an up-heap, it's guaranteed to be ordered from low to high
nodesLen := int(maxEntries)
if candidateNodes.Len() < nodesLen {
nodesLen = candidateNodes.Len()
}
reversedNodes := make([]*blockNode, len(nodes))
for i, node := range nodes {
reversedNodes[len(reversedNodes)-i-1] = node
nodes := make([]*blockNode, nodesLen)
for i := 0; i < nodesLen; i++ {
nodes[i] = candidateNodes.pop()
}
return reversedNodes, nil
return nodes, nil
}
// BlueBlockHashesBetween returns the hashes of the blue blocks after the
// provided low hash until the provided high hash is reached, or up to the
// provided max number of block hashes.
// AntiPastHashesBetween returns the hashes of the blocks between the
// lowHash's antiPast and highHash's antiPast, or up to the provided
// max number of block hashes.
//
// This function is safe for concurrent access.
func (dag *BlockDAG) BlueBlockHashesBetween(lowHash, highHash *daghash.Hash, maxHashes uint64) ([]*daghash.Hash, error) {
func (dag *BlockDAG) AntiPastHashesBetween(lowHash, highHash *daghash.Hash, maxHashes uint64) ([]*daghash.Hash, error) {
dag.dagLock.RLock()
hashes, err := dag.blueBlockHashesBetween(lowHash, highHash, maxHashes)
hashes, err := dag.antiPastHashesBetween(lowHash, highHash, maxHashes)
if err != nil {
return nil, err
}
@ -1671,13 +1690,13 @@ func (dag *BlockDAG) BlueBlockHashesBetween(lowHash, highHash *daghash.Hash, max
return hashes, nil
}
// blueBlockHeadersBetween returns the headers of the blue blocks after the
// provided low hash until the provided high hash is reached, or up to the
// provided max number of block headers.
// antiPastHeadersBetween returns the headers of the blocks between the
// lowHash's antiPast and highHash's antiPast, or up to the provided
// max number of block headers.
//
// This function MUST be called with the DAG state lock held (for reads).
func (dag *BlockDAG) blueBlockHeadersBetween(lowHash, highHash *daghash.Hash, maxHeaders uint64) ([]*wire.BlockHeader, error) {
nodes, err := dag.blueBlocksBetween(lowHash, highHash, maxHeaders)
func (dag *BlockDAG) antiPastHeadersBetween(lowHash, highHash *daghash.Hash, maxHeaders uint64) ([]*wire.BlockHeader, error) {
nodes, err := dag.antiPastBetween(lowHash, highHash, maxHeaders)
if err != nil {
return nil, err
}
@ -1688,7 +1707,7 @@ func (dag *BlockDAG) blueBlockHeadersBetween(lowHash, highHash *daghash.Hash, ma
return headers, nil
}
// GetTopHeaders returns the top wire.MaxBlockHeadersPerMsg block headers ordered by height.
// GetTopHeaders returns the top wire.MaxBlockHeadersPerMsg block headers ordered by blue score.
func (dag *BlockDAG) GetTopHeaders(highHash *daghash.Hash) ([]*wire.BlockHeader, error) {
highNode := &dag.virtual.blockNode
if highHash != nil {
@ -1734,14 +1753,14 @@ func (dag *BlockDAG) RUnlock() {
dag.dagLock.RUnlock()
}
// BlueBlockHeadersBetween returns the headers of the blocks after the provided
// low hash until the provided high hash is reached, or up to the
// provided max number of block headers.
// AntiPastHeadersBetween returns the headers of the blocks between the
// lowHash's antiPast and highHash's antiPast, or up to
// wire.MaxBlockHeadersPerMsg block headers.
//
// This function is safe for concurrent access.
func (dag *BlockDAG) BlueBlockHeadersBetween(lowHash, highHash *daghash.Hash) ([]*wire.BlockHeader, error) {
func (dag *BlockDAG) AntiPastHeadersBetween(lowHash, highHash *daghash.Hash) ([]*wire.BlockHeader, error) {
dag.dagLock.RLock()
headers, err := dag.blueBlockHeadersBetween(lowHash, highHash, wire.MaxBlockHeadersPerMsg)
headers, err := dag.antiPastHeadersBetween(lowHash, highHash, wire.MaxBlockHeadersPerMsg)
if err != nil {
return nil, err
}

View File

@ -20,10 +20,10 @@ func (sp *Peer) OnGetBlockInvs(_ *peer.Peer, msg *wire.MsgGetBlockInvs) {
// This way, if one getblocks is not enough to get the peer
// synced, we can know for sure that its selected chain will
// change, so we'll have higher shared chain block.
hashList, err := dag.BlueBlockHashesBetween(msg.LowHash, msg.HighHash,
hashList, err := dag.AntiPastHashesBetween(msg.LowHash, msg.HighHash,
wire.MaxInvPerMsg)
if err != nil {
peerLog.Warnf("Error getting blue blocks between %s and %s: %s", msg.LowHash, msg.HighHash, err)
peerLog.Warnf("Error getting antiPast hashes between %s and %s: %s", msg.LowHash, msg.HighHash, err)
sp.Disconnect()
return
}

View File

@ -25,7 +25,7 @@ func handleGetHeaders(s *Server, cmd interface{}, closeChan <-chan struct{}) (in
return nil, rpcDecodeHexError(c.HighHash)
}
}
headers, err := s.cfg.SyncMgr.BlueBlockHeadersBetween(lowHash, highHash)
headers, err := s.cfg.SyncMgr.AntiPastHeadersBetween(lowHash, highHash)
if err != nil {
return nil, &rpcmodel.RPCError{
Code: rpcmodel.ErrRPCMisc,

View File

@ -269,12 +269,12 @@ func (b *rpcSyncMgr) SyncPeerID() int32 {
return b.syncMgr.SyncPeerID()
}
// BlueBlockHeadersBetween returns the headers of the blocks after the provided
// low hash until the provided high hash is reached, or up to the
// provided max number of block headers.
// AntiPastHeadersBetween returns the headers of the blocks between the
// lowHash's antiPast and highHash's antiPast, or up to
// wire.MaxBlockHeadersPerMsg block headers.
//
// This function is safe for concurrent access and is part of the
// rpcserverSyncManager interface implementation.
func (b *rpcSyncMgr) BlueBlockHeadersBetween(lowHash, highHash *daghash.Hash) ([]*wire.BlockHeader, error) {
return b.server.DAG.BlueBlockHeadersBetween(lowHash, highHash)
func (b *rpcSyncMgr) AntiPastHeadersBetween(lowHash, highHash *daghash.Hash) ([]*wire.BlockHeader, error) {
return b.server.DAG.AntiPastHeadersBetween(lowHash, highHash)
}

View File

@ -735,11 +735,10 @@ type rpcserverSyncManager interface {
// used to sync from or 0 if there is none.
SyncPeerID() int32
// BlueBlockHeadersBetween returns the headers of the blocks after the first known
// block in the provided locators until the provided high hash or the
// current tip is reached, up to a max of wire.MaxBlockHeadersPerMsg
// hashes.
BlueBlockHeadersBetween(lowHash, highHash *daghash.Hash) ([]*wire.BlockHeader, error)
// AntiPastHeadersBetween returns the headers of the blocks between the
// lowHash's antiPast and highHash's antiPast, or up to
// wire.MaxBlockHeadersPerMsg block headers.
AntiPastHeadersBetween(lowHash, highHash *daghash.Hash) ([]*wire.BlockHeader, error)
}
// rpcserverConfig is a descriptor containing the RPC server configuration.