Published: 2019-08-22

BTCD源码阅读笔记-netsync

btcd 是一个golang实现的比特币全节点。

本文是阅读其netsync包代码的笔记(有些潦草),分析其主要结构。

基于git代码版本: 16327141da8ce4b46b5bac57ba01352943465d9e

Table of Contents

1 netsync模块

下面摘选自包的doc文件

Package netsync implements a concurrency safe block syncing protocol. The SyncManager communicates with connected peers to perform an initial block download, keep the chain and unconfirmed transaction pool in sync, and announce new blocks connected to the chain. Currently the sync manager selects a single sync peer that it downloads all blocks from until it is up to date with the longest chain the sync peer is aware of.

netsync包用来同步peer间消息。核心是定义了一个SyncManager,用来管理不同的peer节点的同步状态。


SyncManager的Start方法,以goroutine的方式启动了blockHandler,来处理block和inv相关的消息。


blockHandler核心是通过for/select来监听不同事件:
1. SyncManager有消息传来(<-sm.msgChan)
   根据不同的消息类型作不同的处理:
   1. 消息类型是newPeerMsg,表示发现新的peer,用handleNewPeerMsg处理;该消息由NewPeer方法发送
   2. 消息类型是txMsg, 表示接收到tx消息,用handleTxMsg处理;该消息由QueueTx发送
   3. 消息类型是blockMsg,表示接收到peer发送的block消息,用handleBlockMsg处理;该消息有QueueBlock发送
   4. 消息类型是invMsg, 表示接收到inv消息,用handleInvMsg处理,该消息由QueueInv发送
   5. 消息类型是headerMsg, 表示接收到headers消息,用handleHeadersMsg处理,该消息由QueueHeaders发送
   6. 消息类型是donePeerMsg, 表示有peer断开连接,用handleDonePeerMsg处理,由DonePeer触发
   7. 消息类型是getSyncPeerMsg,表示获取目前的同步节点, 由SyncManager.SyncPeerID方法调用,处理该消息时直接取出同步节点的id即可
   8. 消息类型是processBlockMsg,表示处理新的区块, 由SyncManager.ProcessBlock方法调用,处理该消息时直接调用chain.ProcessBlock处理
   9. 消息类型是isCurrentMsg, 表示查询是否和连接的peer同步, 由SyncManager.IsCurrent方法调用,处理该消息时直接调用SyncManager.current处理
   10.消息类型是pauseMsg, 表示暂停SyncManager, 由SyncManager.Pause方法调用,处理该消息时直接等待SyncManager的unpause channel

2. 本身30s的ticker
3. SyncManager退出(<-sm.quit)

1.1 处理发现新peer(newPeerMsg)

1.1.1 handleNewPeerMsg

// 该方法以map的方式将peer信息存储到SyncManager的peerStates里,
// 存储的内容包括该peer是否是合适的候选者,初始化该peer请求过的txns和blocks.

sm.peerStates[peer] = &peerSyncState{
        syncCandidate:   isSyncCandidate,
        requestedTxns:   make(map[chainhash.Hash]struct{}),
        requestedBlocks: make(map[chainhash.Hash]struct{}),
}


// 最后如果SyncManager的同步peer还没有,就开启同步:

if isSyncCandidate && sm.syncPeer == nil {
        sm.startSync()
}



// startSync方法从管理的peers里筛选block height高于目前已知最长链高度的peer
// 随机挑选一个发送getblock消息,并更新同步节点相关信息
// 如果存在nextcheckpoint,那么采用headersFirst模式,先发送getheaders消息,获取headers。


1.1.2 NewPeer

由btcd/server.go 下的 serverPeer struct的OnVersion方法调用,也就是当有version信息交换时。

1.2 处理tx消息 (txMsg)

1.2.1 handleTxMsg

// 该方法核心是通过SyncManger绑定内存池 (mempool.TxPool)的ProcessTransaction方法来处理收到的tx
acceptedTxs, err := sm.txMemPool.ProcessTransaction(tmsg.tx, true, true, mempool.Tag(peer.ID()))

// 如果处理出错,则向相应的peer发送reject消息
peer.PushRejectMsg(wire.CmdTx, code, reason, txHash, false)

// 如果成功处理,那么通过SyncManager绑定的peerNotifier接口来处理相应的新的tx加到内存池后应该做的事
sm.peerNotifier.AnnounceNewTransactions(acceptedTxs)

// 在btcd/server.go 下的 server struct的实现了AnnounceNewTransactions方法
// 该方法核心是转发交易消息
s.relayTransactions(txns)



1.2.2 QueueTx

由btcd/server.go 下的 serverPeer struct的OnTx方法调用,也就是当peer接收到tx消息时触发。

1.3 处理block消息(blockMsg)

1.3.1 TODO handleBlockMsg

// 该方法核心是是调用chain.ProcessBlock来处理block. 如果失败了,返回reject消息
_, isOrphan, err := sm.chain.ProcessBlock(bmsg.block, behaviorFlags)



// 如果该block是Orphan区块,那么向该peer请求orphan区块的之前区块 (直到自己已知的)
orphanRoot := sm.chain.GetOrphanRoot(blockHash)
locator, err := sm.chain.LatestBlockLocator()
if err != nil {
        log.Warnf("Failed to get block locator for the "+
                "latest block: %v", err)
} else {
        peer.PushGetBlocksMsg(locator, orphanRoot)
}


// 除此之外,如果SyncManger处在headers-first mode下,有相应的处理方式 (TODO)

1.3.2 QueueBlock

由btcd/server.go 下的 serverPeer struct的OnBlock方法调用,也就是当peer接收到block消息时触发。

1.4 处理inv消息(invMsg)

1.4.1 handleInvMsg

该方法的主要处理逻辑:
1. 如果inv里有block信息,则先更新该peer最新的block相关消息
2. 处理inv消息里的每个Inventory Vector:
   1.加到该节点请求的队列requestQueue
   2.如果是MSG_BLOCK,则检查是不是我们已知的orphan, 如果是,则向节点请求orphan相关的父区块;
   3.当处理完InvVect里的最后一个MSG_BLOCK,则请求更多的Block,直到peer已知最新的
3. 对于处在请求队列requestQueue上的每个InvVect进行处理,构造getdata请求

1.4.2 QueueInv

由btcd/server.go 下的 serverPeer struct的OnInv方法调用,也就是当peer接收到inv消息时触发。

1.5 处理headers消息(headerMsg)

1.5.1 TODO handleHeadersMsg

该方法定义了接收到返回的headers消息时如何处理.

主要逻辑如下:
首先检查返回的headers确保每个都能连上之前的header(检查逻辑TODO).

如果headers里有checkpoint header,那么去获取上次checkpoint之后的所有block
如果没有,那么继续发送getheaders请求来获取接下来的checkpoint之前的新的headers

// TODO 这里的header first mode的处理逻辑,和checkpoint等逻辑还需整理才能理清


1.5.2 QueueHeaders

由btcd/server.go 下的 serverPeer struct的OnHeaders方法调用,也就是当peer接收到headers消息时触发。

1.6 处理peer断开消息(donePeerMsg)

1.6.1 handleDonePeerMsg

该方法做一些清理工作,包括删除存储的节点状态。
如果该节点是同步节点,那么更新同步的节点(重新找寻同步的节点)。

1.6.2 DonePeer

由btcd/server.go 下的 server struct的peerDoneHandler方法调用,用来处理节点断开

2 End

Author: Nisen

Email: imnisen@163.com