mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-06-18 03:56:40 +00:00

* [NOD-1125] Write a skeleton for starting IBD. * [NOD-1125] Add WaitForIBDStart to Peer. * [NOD-1125] Move functions around. * [NOD-1125] Fix merge errors. * [NOD-1125] Fix a comment. * [NOD-1125] Implement sendGetBlockLocator. * [NOD-1125] Begin implementing findIBDLowHash. * [NOD-1125] Finish implementing findIBDLowHash. * [NOD-1125] Rename findIBDLowHash to findHighestSharedBlockHash. * [NOD-1125] Implement downloadBlocks. * [NOD-1125] Implement msgIBDBlock. * [NOD-1125] Implement msgIBDBlock. * [NOD-1125] Fix message types for HandleIBD. * [NOD-1125] Write a skeleton for requesting selected tip hashes. * [NOD-1125] Write a skeleton for the rest of the IBD requests. * [NOD-1125] Implement HandleGetBlockLocator. * [NOD-1125] Fix wrong timeout. * [NOD-1125] Fix compilation error. * [NOD-1125] Implement HandleGetBlocks. * [NOD-1125] Fix compilation errors. * [NOD-1125] Fix merge errors. * [NOD-1125] Implement selectPeerForIBD. * [NOD-1125] Implement RequestSelectedTip. * [NOD-1125] Implement HandleGetSelectedTip. * [NOD-1125] Make go lint happy. * [NOD-1125] Add minGetSelectedTipInterval. * [NOD-1125] Call StartIBDIfRequired where needed. * [NOD-1125] Fix merge errors. * [NOD-1125] Remove a redundant line. * [NOD-1125] Rename shouldContinue to shouldStop. * [NOD-1125] Lowercasify an error message. * [NOD-1125] Shuffle statements around in findHighestSharedBlockHash. * [NOD-1125] Rename hasRecentlyReceivedBlock to isDAGTimeCurrent. * [NOD-1125] Scope minGetSelectedTipInterval. * [NOD-1125] Handle an unhandled error. * [NOD-1125] Use AddUint32 instead of LoadUint32 + StoreUint32. * [NOD-1125] Use AddUint32 instead of LoadUint32 + StoreUint32. * [NOD-1125] Use SwapUint32 instead of AddUint32. * [NOD-1125] Remove error from requestSelectedTips. * [NOD-1125] Actually stop IBD when it should stop. * [NOD-1125] Actually stop RequestSelectedTip when it should stop. * [NOD-1125] Don't ban peers that send us delayed blocks during IBD. * [NOD-1125] Make unexpected message type messages nicer. * [NOD-1125] Remove Peer.ready and make HandleHandshake return it to guarantee we never operate on a non-initialized peer. * [NOD-1125] Remove errors associated with Peer.ready. * [NOD-1125] Extract maxHashesInMsgIBDBlocks to a const. * [NOD-1125] Move the ibd package into flows. * [NOD-1125] Start IBD if required after getting an unknown block inv. * [NOD-1125] Don't request blocks during relay if we're in the middle of IBD. * [NOD-1125] Remove AddBlockLocatorHash. * [NOD-1125] Extract runIBD to a seperate function. * [NOD-1125] Extract runSelectedTipRequest to a seperate function. * [NOD-1125] Remove EnqueueWithTimeout. * [NOD-1125] Increase the capacity of the outgoingRoute. * [NOD-1125] Fix some bad names. * [NOD-1125] Fix a comment. * [NOD-1125] Simplify a comment. * [NOD-1125] Move WaitFor... functions into their respective run... functions. * [NOD-1125] Return default values in case of error. * [NOD-1125] Use CmdXXX in error messages. * [NOD-1125] Use MaxInvPerMsg in outgoingRouteMaxMessages instead of MaxBlockLocatorsPerMsg. * [NOD-1125] Fix a comment. * [NOD-1125] Disconnect a peer that sends us a delayed block during IBD. * [NOD-1125] Use StoreUint32 instead of SwapUint32. * [NOD-1125] Add a comment. * [NOD-1125] Don't ban peers that send us delayed blocks.
238 lines
6.4 KiB
Go
238 lines
6.4 KiB
Go
package ibd
|
|
|
|
import (
|
|
"github.com/kaspanet/kaspad/blockdag"
|
|
"github.com/kaspanet/kaspad/netadapter/router"
|
|
"github.com/kaspanet/kaspad/protocol/common"
|
|
peerpkg "github.com/kaspanet/kaspad/protocol/peer"
|
|
"github.com/kaspanet/kaspad/protocol/protocolerrors"
|
|
"github.com/kaspanet/kaspad/util"
|
|
"github.com/kaspanet/kaspad/util/daghash"
|
|
"github.com/kaspanet/kaspad/wire"
|
|
"sync"
|
|
"sync/atomic"
|
|
)
|
|
|
|
var (
|
|
isIBDRunning uint32
|
|
startIBDMutex sync.Mutex
|
|
)
|
|
|
|
// StartIBDIfRequired selects a peer and starts IBD against it
|
|
// if required
|
|
func StartIBDIfRequired(dag *blockdag.BlockDAG) {
|
|
startIBDMutex.Lock()
|
|
defer startIBDMutex.Unlock()
|
|
|
|
if IsInIBD() {
|
|
return
|
|
}
|
|
|
|
peer := selectPeerForIBD(dag)
|
|
if peer == nil {
|
|
requestSelectedTipsIfRequired(dag)
|
|
return
|
|
}
|
|
|
|
atomic.StoreUint32(&isIBDRunning, 1)
|
|
peer.StartIBD()
|
|
}
|
|
|
|
// IsInIBD is true if IBD is currently running
|
|
func IsInIBD() bool {
|
|
return atomic.LoadUint32(&isIBDRunning) != 0
|
|
}
|
|
|
|
// selectPeerForIBD returns the first peer whose selected tip
|
|
// hash is not in our DAG
|
|
func selectPeerForIBD(dag *blockdag.BlockDAG) *peerpkg.Peer {
|
|
for _, peer := range peerpkg.ReadyPeers() {
|
|
peerSelectedTipHash := peer.SelectedTipHash()
|
|
if !dag.IsInDAG(peerSelectedTipHash) {
|
|
return peer
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// HandleIBD waits for IBD start and handles it when IBD is triggered for this peer
|
|
func HandleIBD(incomingRoute *router.Route, outgoingRoute *router.Route,
|
|
peer *peerpkg.Peer, dag *blockdag.BlockDAG) error {
|
|
|
|
for {
|
|
shouldStop, err := runIBD(incomingRoute, outgoingRoute, peer, dag)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if shouldStop {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func runIBD(incomingRoute *router.Route, outgoingRoute *router.Route,
|
|
peer *peerpkg.Peer, dag *blockdag.BlockDAG) (shouldStop bool, err error) {
|
|
|
|
peer.WaitForIBDStart()
|
|
defer finishIBD(dag)
|
|
|
|
peerSelectedTipHash := peer.SelectedTipHash()
|
|
highestSharedBlockHash, shouldStop, err := findHighestSharedBlockHash(incomingRoute, outgoingRoute, dag, peerSelectedTipHash)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if shouldStop {
|
|
return true, nil
|
|
}
|
|
if dag.IsKnownFinalizedBlock(highestSharedBlockHash) {
|
|
return false, protocolerrors.Errorf(false, "cannot initiate "+
|
|
"IBD with peer %s because the highest shared chain block (%s) is "+
|
|
"below the finality point", peer, highestSharedBlockHash)
|
|
}
|
|
|
|
shouldStop, err = downloadBlocks(incomingRoute, outgoingRoute, dag, highestSharedBlockHash, peerSelectedTipHash)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return shouldStop, nil
|
|
}
|
|
|
|
func findHighestSharedBlockHash(incomingRoute *router.Route, outgoingRoute *router.Route, dag *blockdag.BlockDAG,
|
|
peerSelectedTipHash *daghash.Hash) (lowHash *daghash.Hash, shouldStop bool, err error) {
|
|
|
|
lowHash = dag.Params.GenesisHash
|
|
highHash := peerSelectedTipHash
|
|
|
|
for {
|
|
shouldStop = sendGetBlockLocator(outgoingRoute, lowHash, highHash)
|
|
if shouldStop {
|
|
return nil, true, nil
|
|
}
|
|
|
|
blockLocatorHashes, shouldStop, err := receiveBlockLocator(incomingRoute)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
if shouldStop {
|
|
return nil, true, nil
|
|
}
|
|
|
|
// We check whether the locator's highest hash is in the local DAG.
|
|
// If it is, return it. If it isn't, we need to narrow our
|
|
// getBlockLocator request and try again.
|
|
locatorHighHash := blockLocatorHashes[0]
|
|
if dag.IsInDAG(locatorHighHash) {
|
|
return locatorHighHash, false, nil
|
|
}
|
|
|
|
highHash, lowHash = dag.FindNextLocatorBoundaries(blockLocatorHashes)
|
|
}
|
|
}
|
|
|
|
func sendGetBlockLocator(outgoingRoute *router.Route, lowHash *daghash.Hash,
|
|
highHash *daghash.Hash) (shouldStop bool) {
|
|
|
|
msgGetBlockLocator := wire.NewMsgGetBlockLocator(highHash, lowHash)
|
|
isOpen := outgoingRoute.Enqueue(msgGetBlockLocator)
|
|
return !isOpen
|
|
}
|
|
|
|
func receiveBlockLocator(incomingRoute *router.Route) (blockLocatorHashes []*daghash.Hash,
|
|
shouldStop bool, err error) {
|
|
|
|
message, isOpen, err := incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
if !isOpen {
|
|
return nil, true, nil
|
|
}
|
|
msgBlockLocator, ok := message.(*wire.MsgBlockLocator)
|
|
if !ok {
|
|
return nil, false,
|
|
protocolerrors.Errorf(true, "received unexpected message type. "+
|
|
"expected: %s, got: %s", wire.CmdBlockLocator, message.Command())
|
|
}
|
|
return msgBlockLocator.BlockLocatorHashes, false, nil
|
|
}
|
|
|
|
func downloadBlocks(incomingRoute *router.Route, outgoingRoute *router.Route,
|
|
dag *blockdag.BlockDAG, highestSharedBlockHash *daghash.Hash, peerSelectedTipHash *daghash.Hash) (shouldStop bool, err error) {
|
|
|
|
shouldStop = sendGetBlocks(outgoingRoute, highestSharedBlockHash, peerSelectedTipHash)
|
|
if shouldStop {
|
|
return true, nil
|
|
}
|
|
|
|
for {
|
|
msgIBDBlock, shouldStop, err := receiveIBDBlock(incomingRoute)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if shouldStop {
|
|
return true, nil
|
|
}
|
|
shouldStop, err = processIBDBlock(dag, msgIBDBlock)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if shouldStop {
|
|
return true, nil
|
|
}
|
|
if msgIBDBlock.BlockHash().IsEqual(peerSelectedTipHash) {
|
|
return true, nil
|
|
}
|
|
}
|
|
}
|
|
|
|
func sendGetBlocks(outgoingRoute *router.Route, highestSharedBlockHash *daghash.Hash,
|
|
peerSelectedTipHash *daghash.Hash) (shouldStop bool) {
|
|
|
|
msgGetBlockInvs := wire.NewMsgGetBlocks(highestSharedBlockHash, peerSelectedTipHash)
|
|
isOpen := outgoingRoute.Enqueue(msgGetBlockInvs)
|
|
return !isOpen
|
|
}
|
|
|
|
func receiveIBDBlock(incomingRoute *router.Route) (msgIBDBlock *wire.MsgIBDBlock, shouldStop bool, err error) {
|
|
message, isOpen, err := incomingRoute.DequeueWithTimeout(common.DefaultTimeout)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
if !isOpen {
|
|
return nil, true, nil
|
|
}
|
|
msgIBDBlock, ok := message.(*wire.MsgIBDBlock)
|
|
if !ok {
|
|
return nil, false,
|
|
protocolerrors.Errorf(true, "received unexpected message type. "+
|
|
"expected: %s, got: %s", wire.CmdIBDBlock, message.Command())
|
|
}
|
|
return msgIBDBlock, false, nil
|
|
}
|
|
|
|
func processIBDBlock(dag *blockdag.BlockDAG, msgIBDBlock *wire.MsgIBDBlock) (shouldStop bool, err error) {
|
|
block := util.NewBlock(&msgIBDBlock.MsgBlock)
|
|
if dag.IsInDAG(block.Hash()) {
|
|
return false, nil
|
|
}
|
|
isOrphan, isDelayed, err := dag.ProcessBlock(block, blockdag.BFNone)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if isOrphan {
|
|
return false, protocolerrors.Errorf(true, "received orphan block %s "+
|
|
"during IBD", block.Hash())
|
|
}
|
|
if isDelayed {
|
|
return false, protocolerrors.Errorf(false, "received delayed block %s "+
|
|
"during IBD", block.Hash())
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
func finishIBD(dag *blockdag.BlockDAG) {
|
|
atomic.StoreUint32(&isIBDRunning, 0)
|
|
|
|
StartIBDIfRequired(dag)
|
|
}
|