https://github.com/Bytom/bytom' I0 `5 N0 K4 e/ B
本章介绍bytom代码P2P网络中addrbook地址簿
作者使用MacOS操作系统,其他平台也大同小异
Golang Version: 1.8% }+ j9 C7 w. @9 X, v7 F
e4 l1 X" _/ M! S: c2 i9 |, e
addrbook介绍
addrbook用于存储P2P网络中保留最近的对端节点地址
在MacOS下,默认的地址簿路径存储在~/Library/Bytom/addrbook.json
地址簿格式
- ** ~/Library/Bytom/addrbook.json **
- {% M0 e0 V, s3 H* c# H8 r
- "Key": "359be6d08bc0c6e21c84bbb2",+ _7 t3 f3 D& e- X; X5 ?- r/ c
- "Addrs": [& {0 a% C) A2 p1 U& o1 i4 w
- {
- "Addr": {1 ], v3 z* s4 q5 B' m/ B; D
- "IP": "122.224.11.144",! B/ d. {' {) R! f
- "Port": 46657$ |! x' n4 G5 q1 W _, ]# n
- },9 a- n- W! ?( @/ Q1 V/ K, D- p; {
- "Src": {
- "IP": "198.74.61.131",
- "Port": 46657
- },' v T- h1 u# D, _2 G- s
- "Attempts": 0,% ]2 l+ n4 a7 x5 |
- "LastAttempt": "2018-05-04T12:58:23.894057702+08:00",
- "LastSuccess": "0001-01-01T00:00:00Z",& N0 c5 x0 N9 G8 I
- "BucketType": 1,. @; @% g$ e, {) ~
- "Buckets": [
- 181,8 S' g- `: | a' u6 Z
- 10* I% \# C" X6 @
- ]: U2 d* }0 J; ]7 i3 ~! f, ^3 D
- }
- ]3 C) N9 d% N( y, z D2 f
- }
地址类型
在addrbook中存储的地址有两种:
- ** p2p/addrbook.go **& ~- [2 O R$ c4 D( ?
- const (
- bucketTypeNew = 0x01 // 标识新地址,不可靠地址(未成功连接过)。只存储在一个bucket中2 C: A* w# [- B& m! v
- bucketTypeOld = 0x02 // 标识旧地址,可靠地址(已成功连接过)。可以存储在多个bucket中,最多为maxNewBucketsPerAddress个 X( D, o7 ]. U3 w
- )
6 J. {( t0 `7 v! T
注意: 一个地址的类型变更不在此文章中做介绍,后期的文章会讨论该问题. B6 s# Z* U' j/ ~: p' a
地址簿相关结构体
地址簿
- type AddrBook struct {
- cmn.BaseService4 J; ]5 k$ N* b% p# N* t; |; O
- mtx sync.Mutex
- filePath string // 地址簿路径5 E! u) _7 f' e W" O& |
- routabilityStrict bool // 是否可路由,默认为true
- rand *rand.Rand
- key string // 地址簿标识,用于计算addrNew和addrOld的索引
- ourAddrs map[string]*NetAddress // 存储本地网络地址,用于添加p2p地址时做排除使用
- addrLookup map[string]*knownAddress // 存储新、旧地址集,用于查询" d' T3 ^/ ~+ W, _9 @! U1 M' P# q
- addrNew []map[string]*knownAddress // 存储新地址8 F& z8 y0 L; f, a; E$ a3 }
- addrOld []map[string]*knownAddress // 存储旧地址
- wg sync.WaitGroup) {" Q# [, l" @ ^
- nOld int // 旧地址数量
- nNew int // 新地址数量1 i1 k+ f6 F) ]8 ?5 d+ U# I% I
- }
已知地址
- type knownAddress struct {
- Addr *NetAddress // 已知peer的addr/ q; X, k- ]: s, w
- Src *NetAddress // 已知peer的addr的来源addr4 `9 T e; ^( f! M
- Attempts int32 // 连接peer的重试次数3 a' M( }+ Q5 P0 ^, N* ? _5 a. Z
- LastAttempt time.Time // 最近一次尝试连接的时间
- LastSuccess time.Time // 最近一次尝试成功连接的时间, G0 [$ t" x/ Q
- BucketType byte // 地址的类型(表示可靠地址或不可靠地址)' Q" H F; B/ r5 s; |
- Buckets []int // 当前addr所属的buckets; d* K3 d2 f7 J6 { F
- }
routabilityStrict参数表示地址簿是否存储的ip是否可路由。可路由是根据RFC划分,具体参考资料:RFC标准
初始化地址簿
- // NewAddrBook creates a new address book.$ X {' _' |' }1 |' I3 p' x
- // Use Start to begin processing asynchronous address updates." F& c, I/ t' D) y6 C0 J
- func NewAddrBook(filePath string, routabilityStrict bool) *AddrBook {
- am := &AddrBook{
- rand: rand.New(rand.NewSource(time.Now().UnixNano())),
- ourAddrs: make(map[string]*NetAddress),' v. @8 p7 {- Y; _9 o$ a+ {+ p5 M9 ?
- addrLookup: make(map[string]*knownAddress)," g2 R: O/ b3 d+ y4 |: b
- filePath: filePath,2 S+ f+ A8 u2 Q- L6 {. X
- routabilityStrict: routabilityStrict,# H" x1 Z' F3 x
- }) a5 C! j2 T; d) `
- am.init()
- am.BaseService = *cmn.NewBaseService(nil, "AddrBook", am)/ E' S1 {% A$ z) J
- return am
- }& M! P9 G: `( h3 x9 V
- // When modifying this, don't forget to update loadFromFile()
- func (a *AddrBook) init() {
- // 地址簿唯一标识
- a.key = crypto.CRandHex(24) // 24/2 * 8 = 96 bits
- // New addr buckets, 默认为256个大小
- a.addrNew = make([]map[string]*knownAddress, newBucketCount)& p' L; U5 r% V- i: z' m
- for i := range a.addrNew {
- a.addrNew<i> = make(map[string]*knownAddress)$ J! z! P* }8 K7 v! B
- }
- // Old addr buckets,默认为64个大小- ~3 ?# u( t. o& ?( O( g0 c$ d" R0 q
- a.addrOld = make([]map[string]*knownAddress, oldBucketCount)4 V; D l: i, S' Q p6 f
- for i := range a.addrOld {! `% q* I9 j: M
- a.addrOld<i> = make(map[string]*knownAddress)# w9 D4 S- c) q A+ a
- }% X' {1 w3 V$ g& O4 {% A/ _7 N! d
- }</i></i>
bytomd启动时加载本地地址簿% F- ], @, G( O, w* B
loadFromFile在bytomd启动时,首先会加载本地的地址簿
- // OnStart implements Service./ c$ B2 }( K1 J! @- B/ W Y
- func (a *AddrBook) OnStart() error {8 d5 Y% K+ L3 q. Q& X
- a.BaseService.OnStart()5 x# a. h" l( b# q* K5 Z& C
- a.loadFromFile(a.filePath)' l# A0 S: l* F$ K+ {$ ~
- a.wg.Add(1)
- go a.saveRoutine(). z0 d& `7 E7 K7 A
- return nil
- }
- // Returns false if file does not exist." Q9 C; J; ]1 _7 D& u
- // cmn.Panics if file is corrupt.
- func (a *AddrBook) loadFromFile(filePath string) bool {
- // If doesn't exist, do nothing.8 J+ g! [7 g- Z* ~8 ?+ h; o' y8 p
- // 如果本地地址簿不存在则直接返回1 Z# w" s0 L2 q$ T2 r3 e
- _, err := os.Stat(filePath)5 O$ z/ E5 l. j! y3 @5 S
- if os.IsNotExist(err) {
- return false
- }
- // 加载地址簿json内容2 I" {* q: _$ L1 E
- // Load addrBookJSON{}
- r, err := os.Open(filePath)
- if err != nil {5 K/ w4 j& D; K4 e: K& I
- cmn.PanicCrisis(cmn.Fmt("Error opening file %s: %v", filePath, err))
- }: Y+ V1 Z- J$ W( J6 F) V4 ]/ n. P
- defer r.Close()5 ^& f O3 r# }
- aJSON := &addrBookJSON{}/ Y! B" d6 D2 A# F& `: ]7 `
- dec := json.NewDecoder(r)
- err = dec.Decode(aJSON)* r6 z5 T* E; ^$ y- B
- if err != nil {
- cmn.PanicCrisis(cmn.Fmt("Error reading file %s: %v", filePath, err)): K( `9 E6 P7 F6 c, [- l+ Q2 R+ T' A
- }
- // 填充addrNew、addrOld等
- // Restore all the fields...- S: q$ Z1 j" J" [) [, Y. u
- // Restore the key
- a.key = aJSON.Key1 M4 d# L. m' o5 a
- // Restore .addrNew & .addrOld% r+ r: Y: e2 \' U- y
- for _, ka := range aJSON.Addrs {, |: [ Q, \7 T6 _5 S
- for _, bucketIndex := range ka.Buckets {
- bucket := a.getBucket(ka.BucketType, bucketIndex)
- bucket[ka.Addr.String()] = ka4 D6 M' L( q% }5 g' c0 s- O
- }
- a.addrLookup[ka.Addr.String()] = ka
- if ka.BucketType == bucketTypeNew {( K5 ^3 c o+ s
- a.nNew++
- } else {
- a.nOld++3 O3 W" e$ e8 P& W1 F/ Y: g0 y
- }, ]/ k7 j7 @6 l; F _* K
- }
- return true
- }
定时更新地址簿$ i/ i2 ]% ^( _6 D- Z
bytomd会定时更新本地地址簿,默认2分钟一次
- func (a *AddrBook) saveRoutine() {
- dumpAddressTicker := time.NewTicker(dumpAddressInterval)
- out:
- for {
- select {8 r' i; s/ y9 R' H% y2 ^0 ?
- case
添加新地址
当peer之间交换addr时,节点会收到对端节点已知的地址信息,这些信息会被当前节点添加到地址簿中7 l( U* v- E+ X. L" N6 R
- func (a *AddrBook) AddAddress(addr *NetAddress, src *NetAddress) {1 ~# B2 [: N- p" b$ j7 q) u
- a.mtx.Lock()
- defer a.mtx.Unlock()
- log.WithFields(log.Fields{
- "addr": addr,0 j: f/ y {# D: B) b
- "src": src,
- }).Debug("Add address to book"), i& \1 u6 J) A* t
- a.addAddress(addr, src)* P" \9 S8 a( @1 G7 G' d
- }; ~1 f' ~) l: g0 ^5 _$ z
- func (a *AddrBook) addAddress(addr, src *NetAddress) {/ r' G$ _! ?: v: w9 O4 ~9 ^4 v! I9 _
- // 验证地址是否为可路由地址
- if a.routabilityStrict && !addr.Routable() {/ `1 i8 s& t* ^: p
- log.Error(cmn.Fmt("Cannot add non-routable address %v", addr))
- return
- }& D6 _4 k+ _, ^$ E
- // 验证地址是否为本地节点地址
- if _, ok := a.ourAddrs[addr.String()]; ok {: p8 o3 n4 S8 d* o0 d% W& d
- // Ignore our own listener address.1 m" `* \/ A6 G' u# V
- return0 J. g" { N9 N- p5 L6 M0 L
- }6 L `# z1 M' U' Q' K2 L* a Q
- // 验证地址是否存在地址集中
- // 如果存在:则判断该地址是否为old可靠地址、是否超过了最大buckets中。否则根据该地址已经被ka.Buckets引用的个数来随机决定是否添加到地址集中
- // 如果不存在:则添加到地址集中。并标识为bucketTypeNew地址类型6 ?# k9 g6 z( ^2 r* J
- ka := a.addrLookup[addr.String()]
- if ka != nil {
- // Already old.
- if ka.isOld() {
- return
- }1 L, i1 g1 p8 F3 |6 l, f6 [6 b
- // Already in max new buckets.: ]0 h- _3 ?$ r
- if len(ka.Buckets) == maxNewBucketsPerAddress {( W/ t7 J% J5 @: }( `: K1 A0 z
- return+ e p s( a7 k9 J
- }
- // The more entries we have, the less likely we are to add more.
- factor := int32(2 * len(ka.Buckets))
- if a.rand.Int31n(factor) != 0 {
- return0 n3 Y6 p; e( z. X
- }( i) w& \9 A5 C; L
- } else {. A' R! }! E' ]( l$ |1 t
- ka = newKnownAddress(addr, src)0 |* v- F3 [! A0 T# q7 E+ A
- }4 b3 k" t- H) l" g/ z$ ~% r* N
- // 找到该地址在地址集的索引位置并添加- E- {- b! U& |9 ~$ N. m$ J% r A
- bucket := a.calcNewBucket(addr, src)
- a.addToNewBucket(ka, bucket)
- log.Info("Added new address ", "address:", addr, " total:", a.size())
- }
选择最优节点
地址簿中存储众多地址,在p2p网络中需选择最优的地址去连接$ c8 \( x# X; G1 G$ b- a0 d. U
PickAddress(newBias int)函数中newBias是由pex_reactor产生的地址评分。如何计算地址分数在其他章节中再讲
根据地址评分随机选择地址可增加区块链安全性/ c) N) @ `" ]8 {( T
- // Pick an address to connect to with new/old bias.
- func (a *AddrBook) PickAddress(newBias int) *NetAddress {! c! f6 S( w" h; K. U
- a.mtx.Lock()- B. {2 _5 ^% U, t6 G1 U: J
- defer a.mtx.Unlock()
- if a.size() == 0 {- o" Z0 ~9 @: D" t; g
- return nil
- }
- // newBias地址分数限制在0-100分数之间
- if newBias > 100 {
- newBias = 100
- }! K: m& i. n4 z( S0 y
- if newBias
移除一个地址: G; \& g$ {! N, z K* f1 Q
当一个地址被标记为Bad时则从地址集中移除。目前bytomd的代码版本并未调用过
- func (a *AddrBook) MarkBad(addr *NetAddress) {$ M6 d+ p& Z7 c0 \
- a.RemoveAddress(addr)6 f6 V& [9 R& n( G- i' C
- }
- // RemoveAddress removes the address from the book.
- func (a *AddrBook) RemoveAddress(addr *NetAddress) {
- a.mtx.Lock(): f8 a# ~! I) Q0 R* \# @6 v
- defer a.mtx.Unlock()" T: r. s/ L9 f" k: P
- ka := a.addrLookup[addr.String()]
- if ka == nil {* M% N2 @( G3 s* W
- return, z7 E, H5 O+ F, Z
- }
- log.WithField("addr", addr).Info("Remove address from book")" o7 z4 c6 R1 k3 D
- a.removeFromAllBuckets(ka)
- }/ h6 Y, L( L' n
- func (a *AddrBook) removeFromAllBuckets(ka *knownAddress) {2 K+ X8 U) Z) L0 S5 ^8 e
- for _, bucketIdx := range ka.Buckets {" G0 `- m5 g/ J
- bucket := a.getBucket(ka.BucketType, bucketIdx)9 l' c* ]. H) V% {8 R' `2 f+ u
- delete(bucket, ka.Addr.String())
- }
- ka.Buckets = nil4 s4 ^/ b: D. U" W: K7 v8 Y# k
- if ka.BucketType == bucketTypeNew {
- a.nNew--2 k s7 b, Z9 g) U* T3 D$ m8 r
- } else {
- a.nOld--9 q- K' T% d9 ]
- }
- delete(a.addrLookup, ka.Addr.String())
- }