btcd源码解析——节点P2P连接建立的过程 (1)

1. 写在前面

从这一篇博客开始,我们将介绍btcd节点之间P2P连接建立的过程。考虑到内容太长,分为上下两篇博客来讲解。这两篇博客之后,我们将继续介绍节点之间数据的同步过程。
源码解析是基于btcd仓库c26ffa870fd817666a857af1bf6498fabba1ffe3commit id 版本。

2. 从节点的“启动”说起

btcd节点的启动主要由btcd.go文件中的btcdMain函数完成,其中P2P的连接过程又是由 server.Start()代码完成,如下所示:

// btcdMain [btcd.go]
func btcdMain(serverChan chan<- *server) error {    				// L43
    ...
    server, err := newServer(...)                                   // L149
    ...
    server.Start()                                                  // L162
    ...
}

Start函数中和P2P连接相关的部分在go s.peerHandler()完成,如下代码所示:

// btcdMain [btcd.go] -> Start [server.go]
func (s *server) Start() {                                      // L2291
    ...
    go s.peerHandler()                                          // L2305
    ...
}

在比特币P2P连接的语境中,一个节点就是一个peer. peerHandler函数包含三个最关键的启动工作:

  • 启动addrManager,进行peer地址的管理
  • 启动 syncManager,进行peer之间数据的同步
  • 启动 connManager,进行peer之间连接的管理

代码如下所示:

// btcdMain [btcd.go] -> Start [server.go] -> peerHandler
func (s *server) peerHandler() {                             // L2062
    ...
    s.addrManager.Start()                                    // L2068
    s.syncManager.Start()                                    // L2069
    ...
    go s.connManager.Start()                           		 // L2093
    ...
}

其中跟P2P连接相关的主要是addrManagerconnManager的启动。具体而言:

  • addrManager负责对其他peer地址的管理,主要是一些本地的工作,不涉及直接的网络连接或传输;
  • connManager则主要负责与其他peer建立P2P连接,建立连接是需要对方peer的地址,这便依赖于addrManager中管理的地址。

3. P2P连接中peer地址的管理

3.1 AddrManager数据结构相关

P2P连接中peer地址的管理主要由addrManager完成,addrManager变量中包含了各种用于地址管理的信息,其数据结构如下所示:

// AddrManager [addrmanager.go]
type AddrManager struct {                                           // L32
    ...
    peersFile      string                                           // L34
    ...
    addrIndex    map[string]*KnownAddress                           // L38
    addrNew     [newBucketCount]map[string]*KnownAddress
    addrTried    [newBucketCount]map[string]*KnownAddress
    ...
}

其中最重要的四个字段是peersFile, addrIndex, addrNew, 和addrTried:

  • peersFile 对应于一个文件名,该文件主要保存序列化后的addrManager,用于节点重启时能快速建立连接。该文件路径名默认为$data-dir/data/mainnet/peers.json
  • addrIndex 缓存所有KnownAddressmap
  • addrNew 缓存所有新地址的map slice
  • addrTried 缓存所有已经尝试连接过的地址的list slice

准确来说,peersFile中保存的并不是直接序列化后的addrManager,因为addrManager中的一些信息是运行时信息,并不需要保存下来。因此源码中构造了专门用于序列化addrManager的数据结构,如下所示:

// serializedAddrManager [addrmanager.go]
type serializedAddrManager struct {                                               // L64
    Version      int
    Key           [32]byte
    Addresses    []*serializedKnownAddress
    NewBuckets   [newBucketCount][]string // string is NetAddressKey
    TriedBuckets [triedBucketCount][]string
}

3.2 从peersFile中反序列化填充AddrManager变量

addrManagerStart函数中的loadPeers函数用来从peersFile中反序列化出peers的信息,并填充到addrManager中.

// Start [addrmanager.go]
func (a *AddrManager) Start() {                                          // L567
    ...
    a.loadPeers()
    ...
    go a.addressHandler()                                               // L580
}

loadPeers函数中主要做事的是deserializePeers函数,如下所示:

// Start [addrmanager.go] -> loadPeers
func (a *AddrManager) loadPeers() {                                            // L423
    ...
    err := a.deserializePeers(a.peersFile)
    ...
}

deserializePeers函数中的工作主要包括两个部分:

  1. peersFile文件中的数据反序列化成serializedAddrManager变量 (sam)
  2. sam中的AddressesNewBucketsTriedBuckets字段处理赋值给AddrManager变量 (a) 的addrIndex, addrNewaddrTried字段

deserializePeers函数代码如下所示, 其中:

  1. L444 - L456行代码将peersFile文件中的数据反序列化成sam变量,
  2. L471 - L502行代码将sam中的Addresses字段处理赋值给的addrIndex,
  3. L504 - L518将sam中的NewBuckets字段处理赋值给的addrNew,
  4. L519 - L531将sam中的TriedBuckets字段处理赋值给的addrTried
// Start [addrmanager.go] -> loadPeers -> deserializePeers
func (a *AddrManager) deserializePeers(filePath string) error {         // L442
    ...
    r, err := os.Open(filePath)                                                    // L444
    ...
    var sam serializedAddrManager
    dec := json.NewDecoder(r)
    err = dec.Decode(&sam)                                                      // L456
    ...
    for _, v := range sam.Addresses {                                           // L471
        ka := new(KnownAddress)
        ...
        ka.na, err = a.DeserializeNetAddress(v.Addr, v.Services)
        ...
        ka.srcAddr, err = a.DeserializeNetAddress(v.Src, v.SrcServices)
        ...
        a.addrIndex[NetAddressKey(ka.na)] = ka
    }                                                                                   // L502
    ...
    for i := range sam.NewBuckets {                                     // L504
        for _, val := range sam.NewBuckets[i] {
            ka, ok := a.addrIndex[val]
            ...
            a.addrNew[i][val] = ka
        }
    }                                                                                   // L518
    for i := range sam.TriedBuckets {                                   // L519
        for _, val := range sam.TriedBuckets[i] {
            ka, ok := a.addrIndex[val]
            ...
            a.addrTried[i].PushBack(ka)
        }
    }                                                                                   // L531
    ...
}

3.3 将AddrManager变量序列化存储到peersFile中

3.2小节中Start()函数的L580行启动了一个addressHandler函数的协程,该函数每隔10分钟调用一次savePeers函数,将peers信息 (sam变量) 序列化并保存到peersFile文件中。addressHandler函数和savePeers函数的代码分别如下所示:

// Start [addrmanager.go] -> addressHandler 
func (a *AddrManager) addressHandler() {                                 // L567
    ...
    for {
        select {
        case <- dumpAddressTicker.C:
            a.savePeers()
        ...
        }
    }
    ...
}
// Start [addrmanager.go] -> addressHandler -> savePeers
func (a *AddrManager) savePeers() {                                 // L361
    ...
    w, err := os.Create(a.peersFile)                                // L408
    ...
    enc := json.NewEncoder(w)                                       // L413
    ...
    enc.Encode(&sam)                                                // L415
    ...
}

4. 与其他peer建立P2P连接

第2小节的peerHandler函数中的L2093启动了一个新协程,该协程运行s.connManager.Start()代码启动了ConnManager管理器,代码如下所示:

// Start [connmanager.go]
func (cm *ConnManager) Start() {
    ...
    go cm.connHandler()                                                             // L518
    ...
    if cm.cfg.OnAccept != nil {
        for _, listener := range cm.cfg.Listeners {
            cm.wg.Add(1)
            go cm.listenerHandler(listener)                                        // L525
        }
    }
    
    for i := atomic.LoadUnit64(&cm.connReqCount); i < uint64(cm.cfg.TargetOutbound); i++ {   
        go cm.NewConnReq()                                                       // L530
    }
}

Start函数中最重要的代码包括三部分:

  1. L530行主动发起与其他peer节点的连接
  2. L518行对所有的主动连接进行管理
  3. L525行被动接受其他peer节点的连接

需要进一步说明的是,1主要是完成主动连接的建立过程,2主要完成主动连接建立之后的管理。1每建立一次连接后,都需要由2完成后续的管理工作,如登记到conns变量中。

4.1 主动发起连接

节点主动发起连接的行为由NewConnReq函数完成,代码如下所示:

// Start [connmanager.go] -> NewConnReq [server.go]
func (cm *ConnManager) NewConnReq() {
    ...
    done := make(chan struct{})                                                         // L376
    select {
    case cm.requests <- registerPending{c, done}:
    case <-cm.quit:       
        return
    }
    ...
    select {
    case <-done:
    case <-cm.quit:       
    return
    }                                                                                           // L398
    
    addr, err := cm.cfg.GetNewAddress()                                             // L400
    ...
    c.Addr = addr
    
    cm.Connect(c)                                                                           // L402
}

其中需要解释的代码包括三个部分:

  1. L376-L398: 将当前连接请求登记到pending变量中,方便对该连接进行后续管理。登记过程是通过requests管道完成的,管道的另一端连接connHandler函数,后续在讲解connHandler函数的再做详细介绍;
  2. L400: 获取将要连接的peer地址,将在4.1.1节讲述
  3. L402: 完成实际的连接过程,将在4.1.2节讲述
4.1.1 获取将要连接的peer地址

由上可知,获取将要连接的peer地址是由GetNewAddress函数实现的,该函数是connmanager.go文件中config数据结构的字段. 在初始化cmgr变量时,该字段被赋值为newAddressFunc,代码如下所示:

// newServer [server.go]
func newServer(...) (*server, error) {
    ...
    cmgr, err := connmgr.New(&connmgr.Config{                                         // L2818
        ...     
        GetNewAddress:  newAddressFunc,
    })
    ...
    ...
}

进一步查看newAddressFunc函数的定义,其也定义在newServer函数中,代码如下所示:

// newServer [server.go]
func newServer(...) (*server, error) {
    ...
var newAddressFunc func() (net.Addr, error)                                             // L2773
    if !cfg.SimNet && len(cfg.ConnectPeers) == 0 {       
        newAddressFunc = func() (net.Addr, error) {              
            for tries := 0; tries < 100; tries++ {
                addr := s.addrManager.GetAddress()                                      // L2777
                ...
                addrString := addrmgr.NetAddressKey(addr.NetAddress())
                return addrStringToNetAddr(addrString)
            }
            ...
        }
    }
    ...
}

其中最重要的代码在L2777行,利用addrManagerGetAddress函数获取可用的连接地址。GetAddress函数主要在addrTriedaddrNew两个列表中随机地挑选可用的地址,这就与第3节的内容联系起来了。

4.1.2 完成实际的连接过程

实际的连接过程是由cm.Connect(c)完成的,其代码如下所示:

// Start [connmanager.go] -> NewConnReq [server.go] -> Connect [connmanager.go]
func (cm *ConnManager) Connect(c *ConnReq) {
    ...
    conn, err := cm.cfg.Dial(c.Addr)                                                    // L444
    if err != nil {       
        select {       
        case cm.requests <- handleFailed{c, err}:                                   // L447
        case <-cm.quit:      
        }       
        return
    }
    select {
    case cm.requests <- handleConnected{c, conn}:                               // L454
    case <-cm.quit:
    }
}

L444行通过调用Dial函数进行连接,该函数主要对golangnet包的Dial方法进行了一些包装。
L447L454行分别用来处理连接失败和成功的情况,具体处理过程也是通过向requests通道传递数据来完成。

2008年爆发全球金融危机,同年111日,一个自称中本聪(Satoshi Nakamoto)的人在P2P foundation网站上发布了比特币白皮书《比特币:一种点对点的电子现金系统》 [6]  ,陈述了他对电子货币的新设想——比特币就此面世。2009年1月3日,比特币创世区块诞生。 和法定货币相比,比特币没有一个集中的发行方,而是由网络节点的计算生成,谁都有可能参与制造比特币,而且可以全世界流通,可以在任意一台接入互联网的电脑上买卖,不管身处何方,任何人都可以挖掘、购买、出售或收取比特币,并且在交易过程中外人无法辨认用户身份信息。2009年1月5日,不受央行和任何金融机构控制的比特币诞生。比特币是一种数字货币,由计算机生成的一串串复杂代码组成,新比特币通过预设的程序制造。 每当比特币进入主流媒体的视野时,主流媒体总会请一些主流经济学家分析一下比特币。早先,这些分析总是集中在比特币是不是骗局。而现如今的分析总是集中在比特币能否成为未来的主流货币。而这其中争论的焦点又往往集中在比特币的通缩特性上。 [7]  不少比特币玩家是被比特币的不能随意增发所吸引的。和比特币玩家的态度截然相反,经济学家们对比特币2100万固定总量的态度两极分化。 凯恩斯学派的经济学家们认为政府应该积极调控货币总量,用货币政策的松紧来为经济适时的加油或者刹车。因此,他们认为比特币固定总量货币牺牲了可调控性,而且更糟糕的是将不可避免地导致通货紧缩,进而伤害整体经济。奥地利学派经济学家们的观点却截然相反,他们认为政府对货币的干预越少越好,货币总量的固定导致的通缩并没什么大不了的,甚至是社会进步的标志。 比特币网络通过“挖矿”来生成新的比特币。所谓“挖矿”实质上是用计算机解决一项复杂的数学问题,来保证比特币网络分布式记账系统的一致性。比特币网络会自动调整数学问题的难度,让整个网络约每10分钟得到一个合格答案。随后比特币网络会新生成一定量的比特币作为区块奖励,奖励获得答案的人。 [6]  2009年,比特币诞生的时候,区块奖励是50个比特币。诞生10分钟后,第一批50个比特币生成了,而此时的货币总量就是50。随后比特币就以约每10分钟50个的速度增长。当总量达到1050万时(2100万的50%),区块奖励减半为25个。当总量达到1575万(新产出525万,即1050的50%)时,区块奖励再减半为12.5个。该货币系统曾在4年内只有不超过1050万个,之后的总数量将被永久限制在约2100万个。 [3]  [8]  比特币是一种虚拟货币,数量有限,但是可以用来套现:可以兑换成大多数国家的货币。你可以使用比特币购买一些虚拟的物品,比如网络游戏当中的衣服、帽子、装备等,只要有人接受,你也可以使用比特币购买现实生活当中的物品。 2014年2月25日,“比特币中国”的比特币开盘价格为3562.41元,截至下午4点40分,价格已下跌至3185元,跌幅逾10%。根据该平台的历史行情数据显示,在2014年1月27日,1比特币还能兑换5032元人民币。这意味着,该平台上不到一个月,比特币价格已下跌了36.7%。 同年9月9日,美国电商巨头eBay宣布,该公司旗下支付处理子公司Braintree将开始接受比特币支付。该公司已与比特币交易平台Coinbase达成合作,开始接受这种相对较新的支付手段。 虽然eBay市场交易平台和PayPal业务还不接受比特币支付,但旅行房屋租赁社区Airbnb和租车服务Uber等Braintree客户将可开始接受这种虚拟货币。Braintree的主要业务是面向企业提供支付处理软件,该公司在2013年被eBay以大约8亿美元的价格收购。 2017年1月22日晚间,火币网、比特币中国与OKCoin币行相继在各自官网发布公告称,为进一步抑制投机,防止价格剧烈波动,各平台将于1月24日中午12:00起开始收取交易服务费,服务费按成交金额的0.2%固定费率收取,且主动成交和被动成交费率一致。 [9]  5月5日,OKCoin币行网的新数据显示,比特币的价格刚刚再度刷新历史,截止发稿前高触及9222元人民币高位。1月24日中午12:00起,中国三大比特币平台正式开始收取交易费。9月4日,央行等七部委发公告称中国禁止虚拟货币交易。同年12月17日,比特币达到历史高价19850美元。 2018年11月25日,比特币跌破4000美元大关,后稳定在3000多美元。 [10]  1119日,加密货币恢复跌势,比特币自2017年10月以来首次下探5000美元大关,原因是之前BCH出现硬分叉,且监管部门对首次代币发行(ICO)加强了审查。 [10]  11月21日凌晨4点半,coinbase平台比特币报价跌破4100美元,创下了13个月以来的新低。 2019年4月,比特币再次突破5000美元大关,创年内新高。 [11]  5月12日,比特币近八个月来首次突破7000美元。 [12]  5月14日,据coinmarketcap报价显示,比特币站上8000美元,24小时内上涨14.68%。 [13]  6月22日 ,比特币价格突破10000美元大关。比特币价格在10200左右震荡,24小时涨幅近7%。 [14]  6月26日,比特币价格一举突破12000美元,创下自去年1月来近17个月高点。 [15]  6月27日早间,比特币价格一度接近14000美元,再创年内新高。 [16]  2020年2月10日,比特币突破了一万美元。据交易数据,比特币的价格涨幅突破3% [17]  。3月12日,据加密货币交易平台Bitstamp数据显示,19点44分,比特币低价格已跌至5731美元 [18]  。5月8日,比特币突破10000美元关口,创下2月份以来的新高 [19]  。5月10日早上8点开始,比特币单价在半小时内从9500美元价位瞬间下跌了上千美元,低价格跌破8200美元,高价差超1400美元 [20]  。7月26日下午6点,比特币短时极速拉升,高触及10150.15USDT,日内大涨幅超过4%,这是2020年6月2日以来首次突破1万美元关口 [21]  。11月4日,比特币价格正式突破14000美元 [22]  。1112日晚,比特币价格突破16000美元,刷新2018年1月以来新高,一周涨超8.6%。比特币总市值突破2915亿美元 [23]  。1118日,比特币价格突破17000美元 [24]  。12月1日,比特币价格报19455.31美元,24小时涨幅为5.05%。 [25]  12月17日,比特币价格突破23000美元整数关口,刷新历史新高,日内涨幅超7.5%。 [26]  截至12月27日19时20分,比特币报价28273.06美元。 [27]  20211月8日,比特币涨至4万美元关口上方,高至40402美元
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值