[NOD-1125] Implement the IBD flow (#800)

* [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.
This commit is contained in:
stasatdaglabs 2020-07-20 12:52:23 +03:00 committed by GitHub
parent aa5bc34280
commit 4773f87875
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 924 additions and 235 deletions

View File

@ -53,6 +53,7 @@ var (
ntarLog = BackendLog.Logger("NTAR")
dnssLog = BackendLog.Logger("DNSS")
snvrLog = BackendLog.Logger("SNVR")
ibdsLog = BackendLog.Logger("IBDS")
)
// SubsystemTags is an enum of all sub system tags
@ -81,7 +82,8 @@ var SubsystemTags = struct {
P2PS,
NTAR,
DNSS,
SNVR string
SNVR,
IBDS string
}{
ADXR: "ADXR",
AMGR: "AMGR",
@ -108,6 +110,7 @@ var SubsystemTags = struct {
NTAR: "NTAR",
DNSS: "DNSS",
SNVR: "SNVR",
IBDS: "IBDS",
}
// subsystemLoggers maps each subsystem identifier to its associated logger.
@ -137,6 +140,7 @@ var subsystemLoggers = map[string]*logs.Logger{
SubsystemTags.NTAR: ntarLog,
SubsystemTags.DNSS: dnssLog,
SubsystemTags.SNVR: snvrLog,
SubsystemTags.IBDS: ibdsLog,
}
// InitLog attaches log file and error log file to the backend log.

View File

@ -9,7 +9,7 @@ import (
)
const (
maxMessages = 100
defaultMaxMessages = 100
)
// ErrTimeout signifies that one of the router functions had a timeout.
@ -32,8 +32,12 @@ type Route struct {
// NewRoute create a new Route
func NewRoute() *Route {
return newRouteWithCapacity(defaultMaxMessages)
}
func newRouteWithCapacity(capacity int) *Route {
return &Route{
channel: make(chan wire.Message, maxMessages),
channel: make(chan wire.Message, capacity),
closed: false,
}
}
@ -46,33 +50,13 @@ func (r *Route) Enqueue(message wire.Message) (isOpen bool) {
if r.closed {
return false
}
if len(r.channel) == maxMessages {
if len(r.channel) == defaultMaxMessages {
r.onCapacityReachedHandler()
}
r.channel <- message
return true
}
// EnqueueWithTimeout attempts to enqueue a message to the Route
// and returns an error if the given timeout expires first.
func (r *Route) EnqueueWithTimeout(message wire.Message, timeout time.Duration) (isOpen bool, err error) {
r.closeLock.Lock()
defer r.closeLock.Unlock()
if r.closed {
return false, nil
}
if len(r.channel) == maxMessages {
r.onCapacityReachedHandler()
}
select {
case <-time.After(timeout):
return false, errors.Wrapf(ErrTimeout, "got timeout after %s", timeout)
case r.channel <- message:
return true, nil
}
}
// Dequeue dequeues a message from the Route
func (r *Route) Dequeue() (message wire.Message, isOpen bool) {
message, isOpen = <-r.channel

View File

@ -5,6 +5,8 @@ import (
"github.com/pkg/errors"
)
const outgoingRouteMaxMessages = wire.MaxInvPerMsg + defaultMaxMessages
// OnRouteCapacityReachedHandler is a function that is to
// be called when one of the routes reaches capacity.
type OnRouteCapacityReachedHandler func()
@ -22,7 +24,7 @@ type Router struct {
func NewRouter() *Router {
router := Router{
incomingRoutes: make(map[wire.MessageCommand]*Route),
outgoingRoute: NewRoute(),
outgoingRoute: newRouteWithCapacity(outgoingRouteMaxMessages),
}
router.outgoingRoute.setOnCapacityReachedHandler(func() {
router.onRouteCapacityReachedHandler()

View File

@ -131,7 +131,7 @@ func messageSummary(msg wire.Message) string {
case *wire.MsgGetData:
return invSummary(msg.InvList)
case *wire.MsgGetBlockInvs:
case *wire.MsgGetBlocks:
return fmt.Sprintf("low hash %s, high hash %s", msg.LowHash,
msg.HighHash)

View File

@ -138,7 +138,7 @@ type MessageListeners struct {
// OnGetBlockInvs is invoked when a peer receives a getblockinvs kaspa
// message.
OnGetBlockInvs func(p *Peer, msg *wire.MsgGetBlockInvs)
OnGetBlockInvs func(p *Peer, msg *wire.MsgGetBlocks)
// OnFeeFilter is invoked when a peer receives a feefilter kaspa message.
OnFeeFilter func(p *Peer, msg *wire.MsgFeeFilter)
@ -803,7 +803,7 @@ func (p *Peer) PushGetBlockInvsMsg(lowHash, highHash *daghash.Hash) error {
}
// Construct the getblockinvs request and queue it to be sent.
msg := wire.NewMsgGetBlockInvs(lowHash, highHash)
msg := wire.NewMsgGetBlocks(lowHash, highHash)
p.QueueMessage(msg, nil)
// Update the previous getblockinvs request information for filtering
@ -820,13 +820,7 @@ func (p *Peer) PushGetBlockInvsMsg(lowHash, highHash *daghash.Hash) error {
// This function is safe for concurrent access.
func (p *Peer) PushBlockLocatorMsg(locator blockdag.BlockLocator) error {
// Construct the locator request and queue it to be sent.
msg := wire.NewMsgBlockLocator()
for _, hash := range locator {
err := msg.AddBlockLocatorHash(hash)
if err != nil {
return err
}
}
msg := wire.NewMsgBlockLocator(locator)
p.QueueMessage(msg, nil)
return nil
}
@ -1107,7 +1101,7 @@ func (p *Peer) maybeAddDeadline(pendingResponses map[wire.MessageCommand]time.Ti
// Expects a verack message.
pendingResponses[wire.CmdVerAck] = deadline
case wire.CmdGetBlockInvs:
case wire.CmdGetBlocks:
// Expects an inv message.
pendingResponses[wire.CmdInv] = deadline
@ -1401,7 +1395,7 @@ out:
p.cfg.Listeners.OnBlockLocator(p, msg)
}
case *wire.MsgGetBlockInvs:
case *wire.MsgGetBlocks:
if p.cfg.Listeners.OnGetBlockInvs != nil {
p.cfg.Listeners.OnGetBlockInvs(p, msg)
}

View File

@ -0,0 +1,7 @@
package common
import "time"
// DefaultTimeout is the default duration to wait for enqueuing/dequeuing
// to/from routes.
const DefaultTimeout = 30 * time.Second

View File

@ -17,20 +17,13 @@ const timeout = 30 * time.Second
func ReceiveAddresses(incomingRoute *router.Route, outgoingRoute *router.Route,
peer *peerpkg.Peer, addressManager *addrmgr.AddrManager) (routeClosed bool, err error) {
subnetworkID, err := peer.SubnetworkID()
if err != nil {
panic(err)
}
if !addressManager.NeedMoreAddresses() {
return false, nil
}
subnetworkID := peer.SubnetworkID()
msgGetAddresses := wire.NewMsgGetAddresses(false, subnetworkID)
isOpen, err := outgoingRoute.EnqueueWithTimeout(msgGetAddresses, timeout)
if err != nil {
return false, err
}
isOpen := outgoingRoute.Enqueue(msgGetAddresses)
if !isOpen {
return true, nil
}

View File

@ -1,12 +1,10 @@
package addressexchange
import (
"math/rand"
"time"
"github.com/kaspanet/kaspad/addrmgr"
"github.com/kaspanet/kaspad/netadapter/router"
"github.com/kaspanet/kaspad/wire"
"math/rand"
)
// SendAddresses sends addresses to a peer that requests it.
@ -26,11 +24,7 @@ func SendAddresses(incomingRoute *router.Route, outgoingRoute *router.Route,
panic(err)
}
const timeout = 30 * time.Second
isOpen, err = outgoingRoute.EnqueueWithTimeout(msgAddresses, timeout)
if err != nil {
return false, err
}
isOpen = outgoingRoute.Enqueue(msgAddresses)
if !isOpen {
return true, nil
}

View File

@ -33,10 +33,7 @@ func HandleRelayBlockRequests(incomingRoute *router.Route, outgoingRoute *router
// If we are a full node and the peer is a partial node, we must convert
// the block to a partial block.
nodeSubnetworkID := dag.SubnetworkID()
peerSubnetworkID, err := peer.SubnetworkID()
if err != nil {
return err
}
peerSubnetworkID := peer.SubnetworkID()
isNodeFull := nodeSubnetworkID == nil
isPeerFull := peerSubnetworkID == nil

View File

@ -7,6 +7,7 @@ import (
"github.com/kaspanet/kaspad/netadapter"
"github.com/kaspanet/kaspad/netadapter/router"
"github.com/kaspanet/kaspad/protocol/blocklogger"
"github.com/kaspanet/kaspad/protocol/flows/ibd"
peerpkg "github.com/kaspanet/kaspad/protocol/peer"
"github.com/kaspanet/kaspad/protocol/protocolerrors"
"github.com/kaspanet/kaspad/util"
@ -41,6 +42,12 @@ func HandleRelayInvs(incomingRoute *router.Route, outgoingRoute *router.Route,
continue
}
ibd.StartIBDIfRequired(dag)
if ibd.IsInIBD() {
// Block relay is disabled during IBD
continue
}
requestQueue := newHashesQueueSet()
requestQueue.enqueueIfNotExists(inv.Hash)
@ -102,10 +109,7 @@ func requestBlocks(netAdapater *netadapter.NetAdapter, outgoingRoute *router.Rou
defer requestedBlocks.removeSet(pendingBlocks)
getRelayBlocksMsg := wire.NewMsgGetRelayBlocks(filteredHashesToRequest)
isOpen, err := outgoingRoute.EnqueueWithTimeout(getRelayBlocksMsg, timeout)
if err != nil {
return false, err
}
isOpen := outgoingRoute.Enqueue(getRelayBlocksMsg)
if !isOpen {
return true, nil
}
@ -224,5 +228,8 @@ func processAndRelayBlock(netAdapter *netadapter.NetAdapter, peer *peerpkg.Peer,
if err != nil {
return false, err
}
ibd.StartIBDIfRequired(dag)
return false, nil
}

View File

@ -8,6 +8,7 @@ import (
"github.com/kaspanet/kaspad/blockdag"
"github.com/kaspanet/kaspad/netadapter"
routerpkg "github.com/kaspanet/kaspad/netadapter/router"
"github.com/kaspanet/kaspad/protocol/flows/ibd"
peerpkg "github.com/kaspanet/kaspad/protocol/peer"
"github.com/kaspanet/kaspad/util/locks"
"github.com/kaspanet/kaspad/wire"
@ -16,8 +17,8 @@ import (
// HandleHandshake sets up the handshake protocol - It sends a version message and waits for an incoming
// version message, as well as a verack for the sent version
func HandleHandshake(router *routerpkg.Router, netAdapter *netadapter.NetAdapter, peer *peerpkg.Peer,
dag *blockdag.BlockDAG, addressManager *addrmgr.AddrManager) (closed bool, err error) {
func HandleHandshake(router *routerpkg.Router, netAdapter *netadapter.NetAdapter,
dag *blockdag.BlockDAG, addressManager *addrmgr.AddrManager) (peer *peerpkg.Peer, closed bool, err error) {
receiveVersionRoute, err := router.AddIncomingRoute([]wire.MessageCommand{wire.CmdVersion})
if err != nil {
@ -71,42 +72,39 @@ func HandleHandshake(router *routerpkg.Router, netAdapter *netadapter.NetAdapter
select {
case err := <-errChan:
if err != nil {
return false, err
return nil, false, err
}
return true, nil
return nil, true, nil
case <-locks.ReceiveFromChanWhenDone(func() { wg.Wait() }):
}
peer = peerpkg.New()
err = peerpkg.AddToReadyPeers(peer)
if err != nil {
if errors.Is(err, peerpkg.ErrPeerWithSameIDExists) {
return false, err
return nil, false, err
}
panic(err)
}
peerID, err := peer.ID()
if err != nil {
panic(err)
}
peerID := peer.ID()
err = netAdapter.AssociateRouterID(router, peerID)
if err != nil {
panic(err)
}
if peerAddress != nil {
subnetworkID, err := peer.SubnetworkID()
if err != nil {
panic(err)
}
subnetworkID := peer.SubnetworkID()
addressManager.AddAddress(peerAddress, peerAddress, subnetworkID)
addressManager.Good(peerAddress, subnetworkID)
}
ibd.StartIBDIfRequired(dag)
err = router.RemoveRoute([]wire.MessageCommand{wire.CmdVersion, wire.CmdVerAck})
if err != nil {
panic(err)
}
return false, nil
return peer, false, nil
}

View File

@ -77,10 +77,7 @@ func ReceiveVersion(incomingRoute *router.Route, outgoingRoute *router.Route, ne
//}
peer.UpdateFieldsFromMsgVersion(msgVersion)
isOpen, err = outgoingRoute.EnqueueWithTimeout(wire.NewMsgVerAck(), timeout)
if err != nil {
return nil, false, err
}
isOpen = outgoingRoute.Enqueue(wire.NewMsgVerAck())
if !isOpen {
return nil, true, nil
}

View File

@ -51,10 +51,7 @@ func SendVersion(incomingRoute *router.Route, outgoingRoute *router.Route, netAd
// Advertise if inv messages for transactions are desired.
msg.DisableRelayTx = config.ActiveConfig().BlocksOnly
isOpen, err := outgoingRoute.EnqueueWithTimeout(msg, timeout)
if err != nil {
return false, err
}
isOpen := outgoingRoute.Enqueue(msg)
if !isOpen {
return true, nil
}

View File

@ -0,0 +1,51 @@
package ibd
import (
"github.com/kaspanet/kaspad/blockdag"
"github.com/kaspanet/kaspad/netadapter/router"
"github.com/kaspanet/kaspad/protocol/protocolerrors"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/wire"
)
// HandleGetBlockLocator handles getBlockLocator messages
func HandleGetBlockLocator(incomingRoute *router.Route, outgoingRoute *router.Route, dag *blockdag.BlockDAG) error {
for {
lowHash, highHash, shouldStop, err := receiveGetBlockLocator(incomingRoute)
if err != nil {
return err
}
if shouldStop {
return nil
}
locator, err := dag.BlockLocatorFromHashes(highHash, lowHash)
if err != nil || len(locator) == 0 {
return protocolerrors.Errorf(true, "couldn't build a block "+
"locator between blocks %s and %s", lowHash, highHash)
}
shouldStop = sendBlockLocator(outgoingRoute, locator)
if shouldStop {
return nil
}
}
}
func receiveGetBlockLocator(incomingRoute *router.Route) (lowHash *daghash.Hash,
highHash *daghash.Hash, shouldStop bool, err error) {
message, isOpen := incomingRoute.Dequeue()
if !isOpen {
return nil, nil, true, nil
}
msgGetBlockLocator := message.(*wire.MsgGetBlockLocator)
return msgGetBlockLocator.LowHash, msgGetBlockLocator.HighHash, false, nil
}
func sendBlockLocator(outgoingRoute *router.Route, locator blockdag.BlockLocator) (shouldStop bool) {
msgBlockLocator := wire.NewMsgBlockLocator(locator)
isOpen := outgoingRoute.Enqueue(msgBlockLocator)
return !isOpen
}

View File

@ -0,0 +1,74 @@
package ibd
import (
"github.com/kaspanet/kaspad/blockdag"
"github.com/kaspanet/kaspad/netadapter/router"
"github.com/kaspanet/kaspad/util/daghash"
"github.com/kaspanet/kaspad/wire"
)
// HandleGetBlocks handles getBlocks messages
func HandleGetBlocks(incomingRoute *router.Route, outgoingRoute *router.Route, dag *blockdag.BlockDAG) error {
for {
lowHash, highHash, shouldStop, err := receiveGetBlocks(incomingRoute)
if err != nil {
return err
}
if shouldStop {
return nil
}
msgIBDBlocks, err := buildMsgIBDBlocks(lowHash, highHash, dag)
if err != nil {
return err
}
shouldStop = sendMsgIBDBlocks(outgoingRoute, msgIBDBlocks)
if shouldStop {
return nil
}
}
}
func receiveGetBlocks(incomingRoute *router.Route) (lowHash *daghash.Hash,
highHash *daghash.Hash, shouldStop bool, err error) {
message, isOpen := incomingRoute.Dequeue()
if !isOpen {
return nil, nil, true, nil
}
msgGetBlocks := message.(*wire.MsgGetBlocks)
return msgGetBlocks.LowHash, msgGetBlocks.HighHash, false, nil
}
func buildMsgIBDBlocks(lowHash *daghash.Hash, highHash *daghash.Hash,
dag *blockdag.BlockDAG) ([]*wire.MsgIBDBlock, error) {
const maxHashesInMsgIBDBlocks = wire.MaxInvPerMsg
blockHashes, err := dag.AntiPastHashesBetween(lowHash, highHash, maxHashesInMsgIBDBlocks)
if err != nil {
return nil, err
}
msgIBDBlocks := make([]*wire.MsgIBDBlock, len(blockHashes))
for i, blockHash := range blockHashes {
block, err := dag.BlockByHash(blockHash)
if err != nil {
return nil, err
}
msgIBDBlocks[i] = wire.NewMsgIBDBlock(block.MsgBlock())
}
return msgIBDBlocks, nil
}
func sendMsgIBDBlocks(outgoingRoute *router.Route, msgIBDBlocks []*wire.MsgIBDBlock) (shouldStop bool) {
for _, msgIBDBlock := range msgIBDBlocks {
isOpen := outgoingRoute.Enqueue(msgIBDBlock)
if !isOpen {
return true
}
}
return false
}

237
protocol/flows/ibd/ibd.go Normal file
View File

@ -0,0 +1,237 @@
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)
}

View File

@ -0,0 +1,9 @@
package ibd
import (
"github.com/kaspanet/kaspad/logger"
"github.com/kaspanet/kaspad/util/panics"
)
var log, _ = logger.Get(logger.SubsystemTags.IBDS)
var spawn = panics.GoroutineWrapperFunc(log)

View File

@ -0,0 +1,127 @@
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/util/daghash"
"github.com/kaspanet/kaspad/wire"
"github.com/pkg/errors"
"time"
)
const minDurationToRequestSelectedTips = time.Minute
func requestSelectedTipsIfRequired(dag *blockdag.BlockDAG) {
if isDAGTimeCurrent(dag) {
return
}
requestSelectedTips()
}
func isDAGTimeCurrent(dag *blockdag.BlockDAG) bool {
return dag.Now().Sub(dag.SelectedTipHeader().Timestamp) > minDurationToRequestSelectedTips
}
func requestSelectedTips() {
for _, peer := range peerpkg.ReadyPeers() {
peer.RequestSelectedTipIfRequired()
}
}
// RequestSelectedTip waits for selected tip requests and handles them
func RequestSelectedTip(incomingRoute *router.Route,
outgoingRoute *router.Route, peer *peerpkg.Peer, dag *blockdag.BlockDAG) error {
for {
shouldStop, err := runSelectedTipRequest(incomingRoute, outgoingRoute, peer, dag)
if err != nil {
return err
}
if shouldStop {
return nil
}
}
}
func runSelectedTipRequest(incomingRoute *router.Route, outgoingRoute *router.Route,
peer *peerpkg.Peer, dag *blockdag.BlockDAG) (shouldStop bool, err error) {
peer.WaitForSelectedTipRequests()
defer peer.FinishRequestingSelectedTip()
shouldStop = requestSelectedTip(outgoingRoute)
if shouldStop {
return true, nil
}
peerSelectedTipHash, shouldStop, err := receiveSelectedTip(incomingRoute)
if err != nil {
return false, err
}
if shouldStop {
return true, nil
}
peer.SetSelectedTipHash(peerSelectedTipHash)
StartIBDIfRequired(dag)
return false, nil
}
func requestSelectedTip(outgoingRoute *router.Route) (shouldStop bool) {
msgGetSelectedTip := wire.NewMsgGetSelectedTip()
isOpen := outgoingRoute.Enqueue(msgGetSelectedTip)
return !isOpen
}
func receiveSelectedTip(incomingRoute *router.Route) (selectedTipHash *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
}
msgSelectedTip := message.(*wire.MsgSelectedTip)
return msgSelectedTip.SelectedTipHash, false, nil
}
// HandleGetSelectedTip handles getSelectedTip messages
func HandleGetSelectedTip(incomingRoute *router.Route, outgoingRoute *router.Route, dag *blockdag.BlockDAG) error {
for {
shouldStop, err := receiveGetSelectedTip(incomingRoute)
if err != nil {
return err
}
if shouldStop {
return nil
}
selectedTipHash := dag.SelectedTipHash()
shouldStop = sendSelectedTipHash(outgoingRoute, selectedTipHash)
if shouldStop {
return nil
}
}
}
func receiveGetSelectedTip(incomingRoute *router.Route) (shouldStop bool, err error) {
message, isOpen := incomingRoute.Dequeue()
if !isOpen {
return true, nil
}
_, ok := message.(*wire.MsgGetSelectedTip)
if !ok {
panic(errors.Errorf("received unexpected message type. "+
"expected: %s, got: %s", wire.CmdGetSelectedTip, message.Command()))
}
return false, nil
}
func sendSelectedTipHash(outgoingRoute *router.Route, selectedTipHash *daghash.Hash) (shouldStop bool) {
msgSelectedTip := wire.NewMsgSelectedTip(selectedTipHash)
isOpen := outgoingRoute.Enqueue(msgSelectedTip)
return !isOpen
}

View File

@ -23,10 +23,7 @@ func ReceivePings(incomingRoute *router.Route, outgoingRoute *router.Route) erro
pingMessage := message.(*wire.MsgPing)
pongMessage := wire.NewMsgPong(pingMessage.Nonce)
isOpen, err := outgoingRoute.EnqueueWithTimeout(pongMessage, pingTimeout)
if err != nil {
return err
}
isOpen = outgoingRoute.Enqueue(pongMessage)
if !isOpen {
return nil
}
@ -49,10 +46,7 @@ func SendPings(incomingRoute *router.Route, outgoingRoute *router.Route, peer *p
peer.SetPingPending(nonce)
pingMessage := wire.NewMsgPing(nonce)
isOpen, err := outgoingRoute.EnqueueWithTimeout(pingMessage, pingTimeout)
if err != nil {
return err
}
isOpen := outgoingRoute.Enqueue(pingMessage)
if !isOpen {
return nil
}

View File

@ -8,6 +8,7 @@ import (
"github.com/kaspanet/kaspad/netadapter/id"
"github.com/kaspanet/kaspad/util/daghash"
mathUtil "github.com/kaspanet/kaspad/util/math"
"github.com/kaspanet/kaspad/util/mstime"
"github.com/kaspanet/kaspad/util/subnetworkid"
"github.com/kaspanet/kaspad/wire"
"github.com/pkg/errors"
@ -15,8 +16,6 @@ import (
// Peer holds data about a peer.
type Peer struct {
ready uint32
selectedTipHashMtx sync.RWMutex
selectedTipHash *daghash.Hash
@ -32,52 +31,45 @@ type Peer struct {
lastPingNonce uint64 // The nonce of the last ping we sent
lastPingTime time.Time // Time we sent last ping
lastPingDuration time.Duration // Time for last ping to return
isSelectedTipRequested uint32
selectedTipRequestChan chan struct{}
lastSelectedTipRequest mstime.Time
ibdStartChan chan struct{}
}
// New returns a new Peer
func New() *Peer {
return &Peer{
selectedTipRequestChan: make(chan struct{}),
ibdStartChan: make(chan struct{}),
}
}
// SelectedTipHash returns the selected tip of the peer.
func (p *Peer) SelectedTipHash() (*daghash.Hash, error) {
if atomic.LoadUint32(&p.ready) == 0 {
return nil, errors.New("peer is not ready yet")
}
func (p *Peer) SelectedTipHash() *daghash.Hash {
p.selectedTipHashMtx.RLock()
defer p.selectedTipHashMtx.RUnlock()
return p.selectedTipHash, nil
return p.selectedTipHash
}
// SetSelectedTipHash sets the selected tip of the peer.
func (p *Peer) SetSelectedTipHash(hash *daghash.Hash) error {
if atomic.LoadUint32(&p.ready) == 0 {
return errors.New("peer is not ready yet")
}
func (p *Peer) SetSelectedTipHash(hash *daghash.Hash) {
p.selectedTipHashMtx.Lock()
defer p.selectedTipHashMtx.Unlock()
p.selectedTipHash = hash
return nil
}
// SubnetworkID returns the subnetwork the peer is associated with.
// It is nil in full nodes.
func (p *Peer) SubnetworkID() (*subnetworkid.SubnetworkID, error) {
if atomic.LoadUint32(&p.ready) == 0 {
return nil, errors.New("peer is not ready yet")
}
return p.subnetworkID, nil
func (p *Peer) SubnetworkID() *subnetworkid.SubnetworkID {
return p.subnetworkID
}
// ID returns the peer ID.
func (p *Peer) ID() (*id.ID, error) {
if atomic.LoadUint32(&p.ready) == 0 {
return nil, errors.New("peer is not ready yet")
}
return p.id, nil
}
// MarkAsReady marks the peer as ready.
func (p *Peer) MarkAsReady() error {
if atomic.AddUint32(&p.ready, 1) != 1 {
return errors.New("peer is already ready")
}
return nil
func (p *Peer) ID() *id.ID {
return p.id
}
// UpdateFieldsFromMsgVersion updates the peer with the data from the version message.
@ -143,11 +135,6 @@ func AddToReadyPeers(peer *Peer) error {
return errors.Wrapf(ErrPeerWithSameIDExists, "peer with ID %s already exists", peer.id)
}
err := peer.MarkAsReady()
if err != nil {
return err
}
readyPeers[peer.id] = peer
return nil
}
@ -165,8 +152,51 @@ func GetReadyPeerIDs() []*id.ID {
return peerIDs
}
// IDExists returns whether there's a peer with the given ID.
func IDExists(peerID *id.ID) bool {
_, ok := readyPeers[peerID]
return ok
// ReadyPeers returns a copy of the currently ready peers
func ReadyPeers() []*Peer {
peers := make([]*Peer, 0, len(readyPeers))
for _, readyPeer := range readyPeers {
peers = append(peers, readyPeer)
}
return peers
}
// RequestSelectedTipIfRequired notifies the peer that requesting
// a selected tip is required. This triggers the selected tip
// request flow.
func (p *Peer) RequestSelectedTipIfRequired() {
if atomic.SwapUint32(&p.isSelectedTipRequested, 1) != 0 {
return
}
const minGetSelectedTipInterval = time.Minute
if mstime.Since(p.lastSelectedTipRequest) < minGetSelectedTipInterval {
return
}
p.lastSelectedTipRequest = mstime.Now()
p.selectedTipRequestChan <- struct{}{}
}
// WaitForSelectedTipRequests blocks the current thread until
// a selected tip is requested from this peer
func (p *Peer) WaitForSelectedTipRequests() {
<-p.selectedTipRequestChan
}
// FinishRequestingSelectedTip finishes requesting the selected
// tip from this peer
func (p *Peer) FinishRequestingSelectedTip() {
atomic.StoreUint32(&p.isSelectedTipRequested, 0)
}
// StartIBD starts the IBD process for this peer
func (p *Peer) StartIBD() {
p.ibdStartChan <- struct{}{}
}
// WaitForIBDStart blocks the current thread until
// IBD start is requested from this peer
func (p *Peer) WaitForIBDStart() {
<-p.ibdStartChan
}

View File

@ -12,6 +12,7 @@ import (
"github.com/kaspanet/kaspad/blockdag"
"github.com/kaspanet/kaspad/netadapter"
routerpkg "github.com/kaspanet/kaspad/netadapter/router"
"github.com/kaspanet/kaspad/protocol/flows/ibd"
"github.com/kaspanet/kaspad/protocol/flows/ping"
peerpkg "github.com/kaspanet/kaspad/protocol/peer"
"github.com/kaspanet/kaspad/protocol/protocolerrors"
@ -58,15 +59,13 @@ func newRouterInitializer(netAdapter *netadapter.NetAdapter,
}
}
func startFlows(netAdapter *netadapter.NetAdapter, router *routerpkg.Router, dag *blockdag.BlockDAG,
addressManager *addrmgr.AddrManager) error {
func startFlows(netAdapter *netadapter.NetAdapter, router *routerpkg.Router,
dag *blockdag.BlockDAG, addressManager *addrmgr.AddrManager) error {
stop := make(chan error)
stopped := uint32(0)
outgoingRoute := router.OutgoingRoute()
peer := new(peerpkg.Peer)
closed, err := handshake.HandleHandshake(router, netAdapter, peer, dag, addressManager)
peer, closed, err := handshake.HandleHandshake(router, netAdapter, dag, addressManager)
if err != nil {
return err
}
@ -74,44 +73,101 @@ func startFlows(netAdapter *netadapter.NetAdapter, router *routerpkg.Router, dag
return nil
}
addOneTimeFlow("SendAddresses", router, []wire.MessageCommand{wire.CmdGetAddresses}, &stopped, stop,
addAddressFlows(router, &stopped, stop, peer, addressManager)
addBlockRelayFlows(netAdapter, router, &stopped, stop, peer, dag)
addPingFlows(router, &stopped, stop, peer)
addIBDFlows(router, &stopped, stop, peer, dag)
err = <-stop
return err
}
func addAddressFlows(router *routerpkg.Router, stopped *uint32, stop chan error,
peer *peerpkg.Peer, addressManager *addrmgr.AddrManager) {
outgoingRoute := router.OutgoingRoute()
addOneTimeFlow("SendAddresses", router, []wire.MessageCommand{wire.CmdGetAddresses}, stopped, stop,
func(incomingRoute *routerpkg.Route) (routeClosed bool, err error) {
return addressexchange.SendAddresses(incomingRoute, outgoingRoute, addressManager)
},
)
addOneTimeFlow("ReceiveAddresses", router, []wire.MessageCommand{wire.CmdAddress}, &stopped, stop,
addOneTimeFlow("ReceiveAddresses", router, []wire.MessageCommand{wire.CmdAddress}, stopped, stop,
func(incomingRoute *routerpkg.Route) (routeClosed bool, err error) {
return addressexchange.ReceiveAddresses(incomingRoute, outgoingRoute, peer, addressManager)
},
)
}
addFlow("HandleRelayInvs", router, []wire.MessageCommand{wire.CmdInvRelayBlock, wire.CmdBlock}, &stopped, stop,
func addBlockRelayFlows(netAdapter *netadapter.NetAdapter, router *routerpkg.Router,
stopped *uint32, stop chan error, peer *peerpkg.Peer, dag *blockdag.BlockDAG) {
outgoingRoute := router.OutgoingRoute()
addFlow("HandleRelayInvs", router, []wire.MessageCommand{wire.CmdInvRelayBlock, wire.CmdBlock}, stopped, stop,
func(incomingRoute *routerpkg.Route) error {
return blockrelay.HandleRelayInvs(incomingRoute, outgoingRoute, peer, netAdapter, dag)
},
)
addFlow("HandleRelayBlockRequests", router, []wire.MessageCommand{wire.CmdGetRelayBlocks}, &stopped, stop,
addFlow("HandleRelayBlockRequests", router, []wire.MessageCommand{wire.CmdGetRelayBlocks}, stopped, stop,
func(incomingRoute *routerpkg.Route) error {
return blockrelay.HandleRelayBlockRequests(incomingRoute, outgoingRoute, peer, dag)
},
)
}
addFlow("ReceivePings", router, []wire.MessageCommand{wire.CmdPing}, &stopped, stop,
func addPingFlows(router *routerpkg.Router, stopped *uint32, stop chan error, peer *peerpkg.Peer) {
outgoingRoute := router.OutgoingRoute()
addFlow("ReceivePings", router, []wire.MessageCommand{wire.CmdPing}, stopped, stop,
func(incomingRoute *routerpkg.Route) error {
return ping.ReceivePings(incomingRoute, outgoingRoute)
},
)
addFlow("SendPings", router, []wire.MessageCommand{wire.CmdPong}, &stopped, stop,
addFlow("SendPings", router, []wire.MessageCommand{wire.CmdPong}, stopped, stop,
func(incomingRoute *routerpkg.Route) error {
return ping.SendPings(incomingRoute, outgoingRoute, peer)
},
)
}
err = <-stop
return err
func addIBDFlows(router *routerpkg.Router, stopped *uint32, stop chan error,
peer *peerpkg.Peer, dag *blockdag.BlockDAG) {
outgoingRoute := router.OutgoingRoute()
addFlow("HandleIBD", router, []wire.MessageCommand{wire.CmdBlockLocator, wire.CmdIBDBlock}, stopped, stop,
func(incomingRoute *routerpkg.Route) error {
return ibd.HandleIBD(incomingRoute, outgoingRoute, peer, dag)
},
)
addFlow("RequestSelectedTip", router, []wire.MessageCommand{wire.CmdSelectedTip}, stopped, stop,
func(incomingRoute *routerpkg.Route) error {
return ibd.RequestSelectedTip(incomingRoute, outgoingRoute, peer, dag)
},
)
addFlow("HandleGetSelectedTip", router, []wire.MessageCommand{wire.CmdGetSelectedTip}, stopped, stop,
func(incomingRoute *routerpkg.Route) error {
return ibd.HandleGetSelectedTip(incomingRoute, outgoingRoute, dag)
},
)
addFlow("HandleGetBlockLocator", router, []wire.MessageCommand{wire.CmdGetBlockLocator}, stopped, stop,
func(incomingRoute *routerpkg.Route) error {
return ibd.HandleGetBlockLocator(incomingRoute, outgoingRoute, dag)
},
)
addFlow("HandleGetBlocks", router, []wire.MessageCommand{wire.CmdGetBlocks}, stopped, stop,
func(incomingRoute *routerpkg.Route) error {
return ibd.HandleGetBlocks(incomingRoute, outgoingRoute, dag)
},
)
}
func addFlow(name string, router *routerpkg.Router, messageTypes []wire.MessageCommand, stopped *uint32,

View File

@ -10,7 +10,7 @@ import (
// message.
// It finds the blue future between msg.LowHash and msg.HighHash
// and send the invs to the requesting peer.
func (sp *Peer) OnGetBlockInvs(_ *peer.Peer, msg *wire.MsgGetBlockInvs) {
func (sp *Peer) OnGetBlockInvs(_ *peer.Peer, msg *wire.MsgGetBlocks) {
dag := sp.server.DAG
// We want to prevent a situation where the syncing peer needs
// to call getblocks once again, but the block we sent it
@ -24,7 +24,7 @@ func (sp *Peer) OnGetBlockInvs(_ *peer.Peer, msg *wire.MsgGetBlockInvs) {
hashList, err := dag.AntiPastHashesBetween(msg.LowHash, msg.HighHash,
wire.MaxInvPerMsg)
if err != nil {
sp.AddBanScoreAndPushRejectMsg(wire.CmdGetBlockInvs, wire.RejectInvalid, nil,
sp.AddBanScoreAndPushRejectMsg(wire.CmdGetBlocks, wire.RejectInvalid, nil,
peer.BanScoreInvalidMsgGetBlockInvs, 0,
fmt.Sprintf("error getting antiPast hashes between %s and %s: %s", msg.LowHash, msg.HighHash, err))
return

View File

@ -378,19 +378,19 @@ func BenchmarkWriteBlockHeader(b *testing.B) {
// decode a getblockinvs message.
func BenchmarkDecodeGetBlockInvs(b *testing.B) {
pver := ProtocolVersion
var m MsgGetBlockInvs
var m MsgGetBlocks
m.LowHash = &daghash.Hash{1}
m.HighHash = &daghash.Hash{1}
// Serialize it so the bytes are available to test the decode below.
var bb bytes.Buffer
if err := m.KaspaEncode(&bb, pver); err != nil {
b.Fatalf("MsgGetBlockInvs.KaspaEncode: unexpected error: %v", err)
b.Fatalf("MsgGetBlocks.KaspaEncode: unexpected error: %v", err)
}
buf := bb.Bytes()
r := bytes.NewReader(buf)
var msg MsgGetBlockInvs
var msg MsgGetBlocks
b.ResetTimer()
for i := 0; i < b.N; i++ {
r.Seek(0, 0)

View File

@ -40,7 +40,7 @@ const (
CmdVerAck MessageCommand = 1
CmdGetAddresses MessageCommand = 2
CmdAddress MessageCommand = 3
CmdGetBlockInvs MessageCommand = 4
CmdGetBlocks MessageCommand = 4
CmdInv MessageCommand = 5
CmdGetData MessageCommand = 6
CmdNotFound MessageCommand = 7
@ -61,6 +61,7 @@ const (
CmdInvRelayBlock MessageCommand = 22
CmdGetRelayBlocks MessageCommand = 23
CmdRejectMalformed MessageCommand = 24 // Used only for reject message
CmdIBDBlock MessageCommand = 25
)
var messageCommandToString = map[MessageCommand]string{
@ -68,7 +69,7 @@ var messageCommandToString = map[MessageCommand]string{
CmdVerAck: "VerAck",
CmdGetAddresses: "GetAddress",
CmdAddress: "Address",
CmdGetBlockInvs: "GetBlockInvs",
CmdGetBlocks: "GetBlocks",
CmdInv: "Inv",
CmdGetData: "GetData",
CmdNotFound: "NotFound",
@ -89,6 +90,7 @@ var messageCommandToString = map[MessageCommand]string{
CmdInvRelayBlock: "InvRelayBlock",
CmdGetRelayBlocks: "GetRelayBlocks",
CmdRejectMalformed: "RejectMalformed",
CmdIBDBlock: "IBDBlock",
}
// Message is an interface that describes a kaspa message. A type that
@ -119,8 +121,8 @@ func MakeEmptyMessage(command MessageCommand) (Message, error) {
case CmdAddress:
msg = &MsgAddresses{}
case CmdGetBlockInvs:
msg = &MsgGetBlockInvs{}
case CmdGetBlocks:
msg = &MsgGetBlocks{}
case CmdBlock:
msg = &MsgBlock{}
@ -173,6 +175,9 @@ func MakeEmptyMessage(command MessageCommand) (Message, error) {
case CmdSelectedTip:
msg = &MsgSelectedTip{}
case CmdIBDBlock:
msg = &MsgIBDBlock{}
default:
return nil, errors.Errorf("unhandled command [%s]", command)
}

View File

@ -57,7 +57,7 @@ func TestMessage(t *testing.T) {
msgVerack := NewMsgVerAck()
msgGetAddresses := NewMsgGetAddresses(false, nil)
msgAddresses := NewMsgAddresses(false, nil)
msgGetBlockInvs := NewMsgGetBlockInvs(&daghash.Hash{}, &daghash.Hash{})
msgGetBlockInvs := NewMsgGetBlocks(&daghash.Hash{}, &daghash.Hash{})
msgBlock := &blockOne
msgInv := NewMsgInv()
msgGetData := NewMsgGetData()
@ -66,7 +66,7 @@ func TestMessage(t *testing.T) {
msgPing := NewMsgPing(123123)
msgPong := NewMsgPong(123123)
msgGetBlockLocator := NewMsgGetBlockLocator(&daghash.ZeroHash, &daghash.ZeroHash)
msgBlockLocator := NewMsgBlockLocator()
msgBlockLocator := NewMsgBlockLocator([]*daghash.Hash{})
msgFeeFilter := NewMsgFeeFilter(123456)
msgFilterAdd := NewMsgFilterAdd([]byte{0x01})
msgFilterClear := NewMsgFilterClear()

View File

@ -18,18 +18,6 @@ type MsgBlockLocator struct {
BlockLocatorHashes []*daghash.Hash
}
// AddBlockLocatorHash adds a new block locator hash to the message.
func (msg *MsgBlockLocator) AddBlockLocatorHash(hash *daghash.Hash) error {
if len(msg.BlockLocatorHashes) >= MaxBlockLocatorsPerMsg {
str := fmt.Sprintf("too many block locator hashes for message [max %d]",
MaxBlockLocatorsPerMsg)
return messageError("MsgBlockLocator.AddBlockLocatorHash", str)
}
msg.BlockLocatorHashes = append(msg.BlockLocatorHashes, hash)
return nil
}
// KaspaDecode decodes r using the kaspa protocol encoding into the receiver.
// This is part of the Message interface implementation.
func (msg *MsgBlockLocator) KaspaDecode(r io.Reader, pver uint32) error {
@ -54,7 +42,8 @@ func (msg *MsgBlockLocator) KaspaDecode(r io.Reader, pver uint32) error {
if err != nil {
return err
}
err = msg.AddBlockLocatorHash(hash)
msg.BlockLocatorHashes = append(msg.BlockLocatorHashes, hash)
if err != nil {
return err
}
@ -104,9 +93,8 @@ func (msg *MsgBlockLocator) MaxPayloadLength(pver uint32) uint32 {
// NewMsgBlockLocator returns a new kaspa locator message that conforms to
// the Message interface. See MsgBlockLocator for details.
func NewMsgBlockLocator() *MsgBlockLocator {
func NewMsgBlockLocator(locatorHashes []*daghash.Hash) *MsgBlockLocator {
return &MsgBlockLocator{
BlockLocatorHashes: make([]*daghash.Hash, 0,
MaxBlockLocatorsPerMsg),
BlockLocatorHashes: locatorHashes,
}
}

View File

@ -21,7 +21,7 @@ func TestBlockLocator(t *testing.T) {
t.Errorf("NewHashFromStr: %v", err)
}
msg := NewMsgBlockLocator()
msg := NewMsgBlockLocator([]*daghash.Hash{locatorHash})
// Ensure the command is expected value.
wantCmd := MessageCommand(19)
@ -42,26 +42,12 @@ func TestBlockLocator(t *testing.T) {
}
// Ensure block locator hashes are added properly.
err = msg.AddBlockLocatorHash(locatorHash)
if err != nil {
t.Errorf("AddBlockLocatorHash: %v", err)
}
if msg.BlockLocatorHashes[0] != locatorHash {
t.Errorf("AddBlockLocatorHash: wrong block locator added - "+
"got %v, want %v",
spew.Sprint(msg.BlockLocatorHashes[0]),
spew.Sprint(locatorHash))
}
// Ensure adding more than the max allowed block locator hashes per
// message returns an error.
for i := 0; i < MaxBlockLocatorsPerMsg; i++ {
err = msg.AddBlockLocatorHash(locatorHash)
}
if err == nil {
t.Errorf("AddBlockLocatorHash: expected error on too many " +
"block locator hashes not received")
}
}
// TestBlockLocatorWire tests the MsgBlockLocator wire encode and decode for various
@ -80,15 +66,13 @@ func TestBlockLocatorWire(t *testing.T) {
}
// MsgBlockLocator message with no block locators.
noLocators := NewMsgBlockLocator()
noLocators := NewMsgBlockLocator([]*daghash.Hash{})
noLocatorsEncoded := []byte{
0x00, // Varint for number of block locator hashes
}
// MsgBlockLocator message with multiple block locators.
multiLocators := NewMsgBlockLocator()
multiLocators.AddBlockLocatorHash(hashLocator2)
multiLocators.AddBlockLocatorHash(hashLocator)
multiLocators := NewMsgBlockLocator([]*daghash.Hash{hashLocator2, hashLocator})
multiLocatorsEncoded := []byte{
0x02, // Varint for number of block locator hashes
0xe0, 0xde, 0x06, 0x44, 0x68, 0x13, 0x2c, 0x63,
@ -177,9 +161,7 @@ func TestBlockLocatorWireErrors(t *testing.T) {
}
// MsgBlockLocator message with multiple block locators and a low hash.
baseGetBlocks := NewMsgBlockLocator()
baseGetBlocks.AddBlockLocatorHash(hashLocator2)
baseGetBlocks.AddBlockLocatorHash(hashLocator)
baseGetBlocks := NewMsgBlockLocator([]*daghash.Hash{hashLocator2, hashLocator})
baseGetBlocksEncoded := []byte{
0x02, // Varint for number of block locator hashes
0xe0, 0xde, 0x06, 0x44, 0x68, 0x13, 0x2c, 0x63,
@ -194,10 +176,11 @@ func TestBlockLocatorWireErrors(t *testing.T) {
// Message that forces an error by having more than the max allowed
// block locator hashes.
maxGetBlocks := NewMsgBlockLocator()
maxLocaterHashesSlice := make([]*daghash.Hash, MaxBlockLocatorsPerMsg)
for i := 0; i < MaxBlockLocatorsPerMsg; i++ {
maxGetBlocks.AddBlockLocatorHash(mainnetGenesisHash)
maxLocaterHashesSlice[i] = mainnetGenesisHash
}
maxGetBlocks := NewMsgBlockLocator(maxLocaterHashesSlice)
maxGetBlocks.BlockLocatorHashes = append(maxGetBlocks.BlockLocatorHashes,
mainnetGenesisHash)
maxGetBlocksEncoded := []byte{

View File

@ -10,17 +10,17 @@ import (
"github.com/kaspanet/kaspad/util/daghash"
)
// MsgGetBlockInvs implements the Message interface and represents a kaspa
// getblockinvs message. It is used to request a list of blocks starting after the
// MsgGetBlocks implements the Message interface and represents a kaspa
// getblocks message. It is used to request a list of blocks starting after the
// low hash and until the high hash.
type MsgGetBlockInvs struct {
type MsgGetBlocks struct {
LowHash *daghash.Hash
HighHash *daghash.Hash
}
// KaspaDecode decodes r using the kaspa protocol encoding into the receiver.
// This is part of the Message interface implementation.
func (msg *MsgGetBlockInvs) KaspaDecode(r io.Reader, pver uint32) error {
func (msg *MsgGetBlocks) KaspaDecode(r io.Reader, pver uint32) error {
msg.LowHash = &daghash.Hash{}
err := ReadElement(r, msg.LowHash)
if err != nil {
@ -33,7 +33,7 @@ func (msg *MsgGetBlockInvs) KaspaDecode(r io.Reader, pver uint32) error {
// KaspaEncode encodes the receiver to w using the kaspa protocol encoding.
// This is part of the Message interface implementation.
func (msg *MsgGetBlockInvs) KaspaEncode(w io.Writer, pver uint32) error {
func (msg *MsgGetBlocks) KaspaEncode(w io.Writer, pver uint32) error {
err := WriteElement(w, msg.LowHash)
if err != nil {
return err
@ -44,22 +44,22 @@ func (msg *MsgGetBlockInvs) KaspaEncode(w io.Writer, pver uint32) error {
// Command returns the protocol command string for the message. This is part
// of the Message interface implementation.
func (msg *MsgGetBlockInvs) Command() MessageCommand {
return CmdGetBlockInvs
func (msg *MsgGetBlocks) Command() MessageCommand {
return CmdGetBlocks
}
// MaxPayloadLength returns the maximum length the payload can be for the
// receiver. This is part of the Message interface implementation.
func (msg *MsgGetBlockInvs) MaxPayloadLength(pver uint32) uint32 {
func (msg *MsgGetBlocks) MaxPayloadLength(pver uint32) uint32 {
// low hash + high hash.
return 2 * daghash.HashSize
}
// NewMsgGetBlockInvs returns a new kaspa getblockinvs message that conforms to the
// NewMsgGetBlocks returns a new kaspa getblocks message that conforms to the
// Message interface using the passed parameters and defaults for the remaining
// fields.
func NewMsgGetBlockInvs(lowHash, highHash *daghash.Hash) *MsgGetBlockInvs {
return &MsgGetBlockInvs{
func NewMsgGetBlocks(lowHash, highHash *daghash.Hash) *MsgGetBlocks {
return &MsgGetBlocks{
LowHash: lowHash,
HighHash: highHash,
}

View File

@ -15,8 +15,8 @@ import (
"github.com/kaspanet/kaspad/util/daghash"
)
// TestGetBlockInvs tests the MsgGetBlockInvs API.
func TestGetBlockInvs(t *testing.T) {
// TestGetBlocks tests the MsgGetBlocks API.
func TestGetBlocks(t *testing.T) {
pver := ProtocolVersion
hashStr := "000000000002e7ad7b9eef9479e4aabc65cb831269cc20d2632c13684406dee0"
@ -32,16 +32,16 @@ func TestGetBlockInvs(t *testing.T) {
}
// Ensure we get the same data back out.
msg := NewMsgGetBlockInvs(lowHash, highHash)
msg := NewMsgGetBlocks(lowHash, highHash)
if !msg.HighHash.IsEqual(highHash) {
t.Errorf("NewMsgGetBlockInvs: wrong high hash - got %v, want %v",
t.Errorf("NewMsgGetBlocks: wrong high hash - got %v, want %v",
msg.HighHash, highHash)
}
// Ensure the command is expected value.
wantCmd := MessageCommand(4)
if cmd := msg.Command(); cmd != wantCmd {
t.Errorf("NewMsgGetBlockInvs: wrong command - got %v want %v",
t.Errorf("NewMsgGetBlocks: wrong command - got %v want %v",
cmd, wantCmd)
}
@ -55,9 +55,9 @@ func TestGetBlockInvs(t *testing.T) {
}
}
// TestGetBlockInvsWire tests the MsgGetBlockInvs wire encode and decode for various
// TestGetBlocksWire tests the MsgGetBlocks wire encode and decode for various
// numbers of block locator hashes and protocol versions.
func TestGetBlockInvsWire(t *testing.T) {
func TestGetBlocksWire(t *testing.T) {
hashStr := "2710f40c87ec93d010a6fd95f42c59a2cbacc60b18cf6b7957535"
lowHash, err := daghash.NewHashFromStr(hashStr)
if err != nil {
@ -71,7 +71,7 @@ func TestGetBlockInvsWire(t *testing.T) {
}
// MsgGetBlocks message with no start or high hash.
noStartOrStop := NewMsgGetBlockInvs(&daghash.Hash{}, &daghash.Hash{})
noStartOrStop := NewMsgGetBlocks(&daghash.Hash{}, &daghash.Hash{})
noStartOrStopEncoded := []byte{
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
@ -83,8 +83,8 @@ func TestGetBlockInvsWire(t *testing.T) {
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // High hash
}
// MsgGetBlockInvs message with a low hash and a high hash.
withLowAndHighHash := NewMsgGetBlockInvs(lowHash, highHash)
// MsgGetBlocks message with a low hash and a high hash.
withLowAndHighHash := NewMsgGetBlocks(lowHash, highHash)
withLowAndHighHashEncoded := []byte{
0x35, 0x75, 0x95, 0xb7, 0xf6, 0x8c, 0xb1, 0x60,
0xcc, 0xba, 0x2c, 0x9a, 0xc5, 0x42, 0x5f, 0xd9,
@ -97,10 +97,10 @@ func TestGetBlockInvsWire(t *testing.T) {
}
tests := []struct {
in *MsgGetBlockInvs // Message to encode
out *MsgGetBlockInvs // Expected decoded message
buf []byte // Wire encoding
pver uint32 // Protocol version for wire encoding
in *MsgGetBlocks // Message to encode
out *MsgGetBlocks // Expected decoded message
buf []byte // Wire encoding
pver uint32 // Protocol version for wire encoding
}{
// Latest protocol version with no block locators.
{
@ -135,7 +135,7 @@ func TestGetBlockInvsWire(t *testing.T) {
}
// Decode the message from wire format.
var msg MsgGetBlockInvs
var msg MsgGetBlocks
rbuf := bytes.NewReader(test.buf)
err = msg.KaspaDecode(rbuf, test.pver)
if err != nil {
@ -150,9 +150,9 @@ func TestGetBlockInvsWire(t *testing.T) {
}
}
// TestGetBlockInvsWireErrors performs negative tests against wire encode and
// decode of MsgGetBlockInvs to confirm error paths work correctly.
func TestGetBlockInvsWireErrors(t *testing.T) {
// TestGetBlocksWireErrors performs negative tests against wire encode and
// decode of MsgGetBlocks to confirm error paths work correctly.
func TestGetBlocksWireErrors(t *testing.T) {
// Set protocol inside getheaders message.
pver := ProtocolVersion
@ -168,8 +168,8 @@ func TestGetBlockInvsWireErrors(t *testing.T) {
t.Errorf("NewHashFromStr: %v", err)
}
// MsgGetBlockInvs message with multiple block locators and a high hash.
baseGetBlocks := NewMsgGetBlockInvs(lowHash, highHash)
// MsgGetBlocks message with multiple block locators and a high hash.
baseGetBlocks := NewMsgGetBlocks(lowHash, highHash)
baseGetBlocksEncoded := []byte{
0x35, 0x75, 0x95, 0xb7, 0xf6, 0x8c, 0xb1, 0x60,
0xcc, 0xba, 0x2c, 0x9a, 0xc5, 0x42, 0x5f, 0xd9,
@ -182,12 +182,12 @@ func TestGetBlockInvsWireErrors(t *testing.T) {
}
tests := []struct {
in *MsgGetBlockInvs // Value to encode
buf []byte // Wire encoding
pver uint32 // Protocol version for wire encoding
max int // Max size of fixed buffer to induce errors
writeErr error // Expected write error
readErr error // Expected read error
in *MsgGetBlocks // Value to encode
buf []byte // Wire encoding
pver uint32 // Protocol version for wire encoding
max int // Max size of fixed buffer to induce errors
writeErr error // Expected write error
readErr error // Expected read error
}{
// Force error in low hash.
{baseGetBlocks, baseGetBlocksEncoded, pver, 0, io.ErrShortWrite, io.EOF},
@ -218,7 +218,7 @@ func TestGetBlockInvsWireErrors(t *testing.T) {
}
// Decode from wire format.
var msg MsgGetBlockInvs
var msg MsgGetBlocks
r := newFixedReader(test.max, test.buf)
err = msg.KaspaDecode(r, test.pver)
if reflect.TypeOf(err) != reflect.TypeOf(test.readErr) {

44
wire/msgibdblock.go Normal file
View File

@ -0,0 +1,44 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package wire
import "io"
// MsgIBDBlock implements the Message interface and represents a kaspa
// ibdblock message. It is used to deliver block and transaction information in
// response to a getblocks message (MsgGetBlocks).
type MsgIBDBlock struct {
MsgBlock
}
// KaspaDecode decodes r using the kaspa protocol encoding into the receiver.
// This is part of the Message interface implementation.
func (msg *MsgIBDBlock) KaspaDecode(r io.Reader, pver uint32) error {
return msg.MsgBlock.KaspaDecode(r, pver)
}
// KaspaEncode encodes the receiver to w using the kaspa protocol encoding.
// This is part of the Message interface implementation.
func (msg *MsgIBDBlock) KaspaEncode(w io.Writer, pver uint32) error {
return msg.MsgBlock.KaspaEncode(w, pver)
}
// Command returns the protocol command string for the message. This is part
// of the Message interface implementation.
func (msg *MsgIBDBlock) Command() MessageCommand {
return CmdIBDBlock
}
// MaxPayloadLength returns the maximum length the payload can be for the
// receiver. This is part of the Message interface implementation.
func (msg *MsgIBDBlock) MaxPayloadLength(pver uint32) uint32 {
return MaxMessagePayload
}
// NewMsgIBDBlock returns a new kaspa ibdblock message that conforms to the
// Message interface. See MsgIBDBlock for details.
func NewMsgIBDBlock(msgBlock *MsgBlock) *MsgIBDBlock {
return &MsgIBDBlock{*msgBlock}
}

117
wire/msgibdblock_test.go Normal file
View File

@ -0,0 +1,117 @@
// Copyright (c) 2013-2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package wire
import (
"bytes"
"reflect"
"testing"
"github.com/davecgh/go-spew/spew"
)
// TestIBDBlock tests the MsgIBDBlock API.
func TestIBDBlock(t *testing.T) {
pver := ProtocolVersion
// Block 1 header.
parentHashes := blockOne.Header.ParentHashes
hashMerkleRoot := blockOne.Header.HashMerkleRoot
acceptedIDMerkleRoot := blockOne.Header.AcceptedIDMerkleRoot
utxoCommitment := blockOne.Header.UTXOCommitment
bits := blockOne.Header.Bits
nonce := blockOne.Header.Nonce
bh := NewBlockHeader(1, parentHashes, hashMerkleRoot, acceptedIDMerkleRoot, utxoCommitment, bits, nonce)
// Ensure the command is expected value.
wantCmd := MessageCommand(25)
msg := NewMsgIBDBlock(NewMsgBlock(bh))
if cmd := msg.Command(); cmd != wantCmd {
t.Errorf("NewMsgIBDBlock: wrong command - got %v want %v",
cmd, wantCmd)
}
// Ensure max payload is expected value for latest protocol version.
wantPayload := uint32(1024 * 1024 * 32)
maxPayload := msg.MaxPayloadLength(pver)
if maxPayload != wantPayload {
t.Errorf("MaxPayloadLength: wrong max payload length for "+
"protocol version %d - got %v, want %v", pver,
maxPayload, wantPayload)
}
// Ensure we get the same block header data back out.
if !reflect.DeepEqual(&msg.Header, bh) {
t.Errorf("NewMsgIBDBlock: wrong block header - got %v, want %v",
spew.Sdump(&msg.Header), spew.Sdump(bh))
}
// Ensure transactions are added properly.
tx := blockOne.Transactions[0].Copy()
msg.AddTransaction(tx)
if !reflect.DeepEqual(msg.Transactions, blockOne.Transactions) {
t.Errorf("AddTransaction: wrong transactions - got %v, want %v",
spew.Sdump(msg.Transactions),
spew.Sdump(blockOne.Transactions))
}
// Ensure transactions are properly cleared.
msg.ClearTransactions()
if len(msg.Transactions) != 0 {
t.Errorf("ClearTransactions: wrong transactions - got %v, want %v",
len(msg.Transactions), 0)
}
}
// TestIBDBlockWire tests the MsgIBDBlock wire encode and decode for various numbers
// of transaction inputs and outputs and protocol versions.
func TestIBDBlockWire(t *testing.T) {
tests := []struct {
in *MsgIBDBlock // Message to encode
out *MsgIBDBlock // Expected decoded message
buf []byte // Wire encoding
txLocs []TxLoc // Expected transaction locations
pver uint32 // Protocol version for wire encoding
}{
// Latest protocol version.
{
&MsgIBDBlock{blockOne},
&MsgIBDBlock{blockOne},
blockOneBytes,
blockOneTxLocs,
ProtocolVersion,
},
}
t.Logf("Running %d tests", len(tests))
for i, test := range tests {
// Encode the message to wire format.
var buf bytes.Buffer
err := test.in.KaspaEncode(&buf, test.pver)
if err != nil {
t.Errorf("KaspaEncode #%d error %v", i, err)
continue
}
if !bytes.Equal(buf.Bytes(), test.buf) {
t.Errorf("KaspaEncode #%d\n got: %s want: %s", i,
spew.Sdump(buf.Bytes()), spew.Sdump(test.buf))
continue
}
// Decode the message from wire format.
var msg MsgIBDBlock
rbuf := bytes.NewReader(test.buf)
err = msg.KaspaDecode(rbuf, test.pver)
if err != nil {
t.Errorf("KaspaDecode #%d error %v", i, err)
continue
}
if !reflect.DeepEqual(&msg, test.out) {
t.Errorf("KaspaDecode #%d\n got: %s want: %s", i,
spew.Sdump(&msg), spew.Sdump(test.out))
continue
}
}
}