https://github.com/Bytom/bytom a ^, `: @' W/ J
本章介绍bytom代码P2P网络中addrbook地址簿" H J3 o: R4 ~6 ` \' P
作者使用MacOS操作系统,其他平台也大同小异
) S; A7 m8 m" a
Golang Version: 1.8- N9 O$ g- a9 \; V+ o0 h* e7 `
addrbook介绍
addrbook用于存储P2P网络中保留最近的对端节点地址
在MacOS下,默认的地址簿路径存储在~/Library/Bytom/addrbook.json! H; n) n; G, A
地址簿格式4 D/ u! ^: O% _+ z1 ~! K
- ** ~/Library/Bytom/addrbook.json **
- {- \1 ?- G, k* }
- "Key": "359be6d08bc0c6e21c84bbb2",
- "Addrs": [
- {7 I# E- X5 ^2 [8 n4 O' g- M
- "Addr": {
- "IP": "122.224.11.144"," l9 L* d! V+ |8 h5 @# z$ e7 c2 |2 O0 N
- "Port": 466573 ~ C1 F+ G" {" T1 Q
- },, r) n' R1 \% \# ^6 ?. T1 @
- "Src": {
- "IP": "198.74.61.131",
- "Port": 46657
- },% @ _) K9 |! [2 L
- "Attempts": 0,
- "LastAttempt": "2018-05-04T12:58:23.894057702+08:00",3 |8 h" w3 v0 A" C" S
- "LastSuccess": "0001-01-01T00:00:00Z",! V( Z; P5 ]$ {2 l2 ?
- "BucketType": 1,4 _1 Y: ]; z: N# A/ Z; D9 w
- "Buckets": [2 O. ]# V5 U: Q
- 181,: d/ M. M+ {& E8 j
- 10
- ]5 `9 p; L3 Y8 ?4 G& `
- }
- ]- Y: ~# ~0 O1 E* R1 g8 x
- }
地址类型
在addrbook中存储的地址有两种:
- ** p2p/addrbook.go **
- const (* g2 a# O: P# H/ k0 \
- bucketTypeNew = 0x01 // 标识新地址,不可靠地址(未成功连接过)。只存储在一个bucket中/ L* K7 O6 M+ |% Q2 y5 c
- bucketTypeOld = 0x02 // 标识旧地址,可靠地址(已成功连接过)。可以存储在多个bucket中,最多为maxNewBucketsPerAddress个
- )
注意: 一个地址的类型变更不在此文章中做介绍,后期的文章会讨论该问题5 s0 s6 r1 o8 K; |+ f
; P4 L. D4 w) j( k0 f
地址簿相关结构体; L& e' k7 `" V8 j) S3 E
地址簿
- type AddrBook struct {5 X7 F; m' \, k% V. l
- cmn.BaseService
- mtx sync.Mutex
- filePath string // 地址簿路径
- routabilityStrict bool // 是否可路由,默认为true) |# s' V+ ~) ~4 |% ^0 j \( c4 v! J$ d
- rand *rand.Rand
- key string // 地址簿标识,用于计算addrNew和addrOld的索引- Y4 M8 D5 R9 Q# Z0 s
- ourAddrs map[string]*NetAddress // 存储本地网络地址,用于添加p2p地址时做排除使用: g- |6 _* w2 c6 J' I# B
- addrLookup map[string]*knownAddress // 存储新、旧地址集,用于查询
- addrNew []map[string]*knownAddress // 存储新地址
- addrOld []map[string]*knownAddress // 存储旧地址7 {/ A; `4 q" X. Y0 i
- wg sync.WaitGroup
- nOld int // 旧地址数量2 }- r. \5 O: w) V; n Y9 J. L
- nNew int // 新地址数量
- }
已知地址
- type knownAddress struct {
- Addr *NetAddress // 已知peer的addr
- Src *NetAddress // 已知peer的addr的来源addr
- Attempts int32 // 连接peer的重试次数
- LastAttempt time.Time // 最近一次尝试连接的时间8 _' ~3 [: n$ P' ~
- LastSuccess time.Time // 最近一次尝试成功连接的时间2 O, y2 `9 O* y; M, ^7 I% s- ]
- BucketType byte // 地址的类型(表示可靠地址或不可靠地址); Z) H- g" X/ k0 U! E
- Buckets []int // 当前addr所属的buckets+ e1 t! O1 M, W/ d
- }
routabilityStrict参数表示地址簿是否存储的ip是否可路由。可路由是根据RFC划分,具体参考资料:RFC标准
初始化地址簿5 d6 B, ?& G9 P1 V& B0 P8 Y
- // NewAddrBook creates a new address book.
- // Use Start to begin processing asynchronous address updates.
- func NewAddrBook(filePath string, routabilityStrict bool) *AddrBook {
- am := &AddrBook{
- rand: rand.New(rand.NewSource(time.Now().UnixNano())), m! J& Q3 e4 U) r% ^+ ~0 B0 x
- ourAddrs: make(map[string]*NetAddress),2 b& \7 c- i4 Y L$ T
- addrLookup: make(map[string]*knownAddress),- r9 q5 l- D% e5 R% t, ?
- filePath: filePath,# W9 H! G# m; t
- routabilityStrict: routabilityStrict,1 @9 q P5 d# l+ P
- }+ A5 O8 j: ^ H2 I9 b w7 B1 X
- am.init()4 ?8 O, x7 l- O, L. d3 `" R4 z
- am.BaseService = *cmn.NewBaseService(nil, "AddrBook", am)8 ^/ ?5 k. a6 h+ @0 }7 U" ]
- return am3 t7 V; J# l: C% m0 \0 e% k E' w" A
- }6 p/ `1 P; W3 x7 M
- // When modifying this, don't forget to update loadFromFile()+ ?7 {: ? k& B; c: h) l* V! K) b6 d
- func (a *AddrBook) init() {9 U2 }* j' X. d, G
- // 地址簿唯一标识
- a.key = crypto.CRandHex(24) // 24/2 * 8 = 96 bits# A+ m q7 s, i m n* c9 p6 y
- // New addr buckets, 默认为256个大小0 P% _) Y7 ~) d" j0 |
- a.addrNew = make([]map[string]*knownAddress, newBucketCount)- C5 Y/ ?) h6 t( F
- for i := range a.addrNew {
- a.addrNew<i> = make(map[string]*knownAddress)6 z% a! S2 g! z1 G% ?0 H' U
- }: { v* [6 ^) c8 e0 a0 t
- // Old addr buckets,默认为64个大小
- a.addrOld = make([]map[string]*knownAddress, oldBucketCount)9 f" H) `, |2 y5 w" ?' K. N
- for i := range a.addrOld {6 p. T0 m f5 W+ G
- a.addrOld<i> = make(map[string]*knownAddress): e( a. G, T s& N6 Z
- }
- }</i></i>
bytomd启动时加载本地地址簿
loadFromFile在bytomd启动时,首先会加载本地的地址簿
- // OnStart implements Service.
- func (a *AddrBook) OnStart() error {
- a.BaseService.OnStart()9 Q: {* U; S) b+ Z
- a.loadFromFile(a.filePath)0 {4 t6 n+ Q' i* }8 y% U
- a.wg.Add(1)4 y3 T* U. n/ n7 j- J3 g
- go a.saveRoutine()9 M e6 v' s+ x3 [+ t6 d
- return nil5 @3 p3 U/ j- k2 G& r0 f
- }
- // Returns false if file does not exist.
- // cmn.Panics if file is corrupt.3 q# M7 s0 s# s" ~: U/ o b
- func (a *AddrBook) loadFromFile(filePath string) bool {( X3 ?8 `1 D) M$ ?
- // If doesn't exist, do nothing.: N* o2 P3 T5 q5 j! h% g
- // 如果本地地址簿不存在则直接返回 {9 m) r( b( }1 W) W: T
- _, err := os.Stat(filePath) K P, b2 j2 Q! V1 i( d: o6 I
- if os.IsNotExist(err) {
- return false
- }8 I5 R: s! V9 ~& J' w, B; w
- // 加载地址簿json内容. p( q' ^6 O' f
- // Load addrBookJSON{}1 Z- g! w6 H2 y1 k& N/ F# Y, g
- r, err := os.Open(filePath)
- if err != nil {( a: `+ q8 `, P! ~- [- d1 ?- U
- cmn.PanicCrisis(cmn.Fmt("Error opening file %s: %v", filePath, err)), \; y) j3 K8 l7 b2 q+ z& g L
- }
- defer r.Close()% _8 c( O. m& m% L. \$ W
- aJSON := &addrBookJSON{}
- dec := json.NewDecoder(r)$ N5 d4 w( w! b" G6 t# [3 k0 u
- err = dec.Decode(aJSON)
- if err != nil {
- cmn.PanicCrisis(cmn.Fmt("Error reading file %s: %v", filePath, err))
- }/ T! X+ L! n, t( F; d6 y. k: r
- // 填充addrNew、addrOld等! x1 E" T v$ Y) s% p/ M
- // Restore all the fields...
- // Restore the key5 p/ t: B1 k+ h+ G0 T. H1 X U
- a.key = aJSON.Key Z |5 o# ^# b2 |
- // Restore .addrNew & .addrOld. u- b+ v" k, K
- for _, ka := range aJSON.Addrs {
- for _, bucketIndex := range ka.Buckets {
- bucket := a.getBucket(ka.BucketType, bucketIndex)2 l+ P/ f* m& B0 m9 B. Z
- bucket[ka.Addr.String()] = ka
- }
- a.addrLookup[ka.Addr.String()] = ka* s# O% ?0 n2 K8 M* d8 Z
- if ka.BucketType == bucketTypeNew {3 {$ W, N' t0 ]+ f+ _- }* N& u% @
- a.nNew++1 O! R( P4 n: f# k; I
- } else {
- a.nOld++1 K. |$ X4 ?# b- ^2 }
- }" t6 P& t- z, a K1 j
- }
- return true
- }
定时更新地址簿2 v5 F8 A# J; \( a& U5 `# I
bytomd会定时更新本地地址簿,默认2分钟一次
- func (a *AddrBook) saveRoutine() {: x0 c# h6 `; q1 h1 h
- dumpAddressTicker := time.NewTicker(dumpAddressInterval); y; P! A& j9 {
- out:
- for {
- select {# M# y% A7 l5 l7 ~" F) L9 V. x
- case
添加新地址% O' n5 s/ }2 D8 x2 R" p- R$ E" K: z8 z
当peer之间交换addr时,节点会收到对端节点已知的地址信息,这些信息会被当前节点添加到地址簿中- \4 j! H: E) b; W$ i
- func (a *AddrBook) AddAddress(addr *NetAddress, src *NetAddress) {/ q$ _$ e$ m% Y2 ], \1 e9 l+ ?
- a.mtx.Lock()
- defer a.mtx.Unlock(), ^+ Y! Q7 } a! `: z: p8 U1 H
- log.WithFields(log.Fields{
- "addr": addr,
- "src": src,1 ?) N8 ^2 m1 R0 ]
- }).Debug("Add address to book")5 u4 B; t; s S7 X+ O3 V, }% W
- a.addAddress(addr, src)! f- z$ m- _- m, I* n$ `* ~
- }
- func (a *AddrBook) addAddress(addr, src *NetAddress) {
- // 验证地址是否为可路由地址' Z. L* D+ M/ O7 ~
- if a.routabilityStrict && !addr.Routable() {
- log.Error(cmn.Fmt("Cannot add non-routable address %v", addr))
- return
- }
- // 验证地址是否为本地节点地址
- if _, ok := a.ourAddrs[addr.String()]; ok {
- // Ignore our own listener address.
- return
- }% E3 L- g# W1 N# T
- // 验证地址是否存在地址集中' W' ]6 o& G. B* @' q
- // 如果存在:则判断该地址是否为old可靠地址、是否超过了最大buckets中。否则根据该地址已经被ka.Buckets引用的个数来随机决定是否添加到地址集中* O7 `9 [/ u3 y' |
- // 如果不存在:则添加到地址集中。并标识为bucketTypeNew地址类型
- ka := a.addrLookup[addr.String()]+ U* L/ d/ e! A5 U! Z+ V4 a1 G* f9 ?
- if ka != nil {% I9 K/ J) N, p; Y6 v
- // Already old." d3 T' }" e# B! a* ?! C# o! L
- if ka.isOld() {
- return
- }# b8 F0 C0 W- U& |1 ?* W
- // Already in max new buckets.
- if len(ka.Buckets) == maxNewBucketsPerAddress {
- return( W1 b. d- Z0 i7 v; s2 G; F4 v* H; r* y+ i
- }( z1 F4 k- p3 Y" q
- // The more entries we have, the less likely we are to add more.
- factor := int32(2 * len(ka.Buckets))$ T& m8 l1 z/ P L/ m
- if a.rand.Int31n(factor) != 0 {
- return
- }+ x, }8 |/ e+ u3 [0 x
- } else {
- ka = newKnownAddress(addr, src). u8 p3 @5 k* m- n8 ~
- }
- // 找到该地址在地址集的索引位置并添加1 t# H/ h+ G9 [& Y. o' {6 k% Q
- bucket := a.calcNewBucket(addr, src)
- a.addToNewBucket(ka, bucket)/ N, @$ s/ ?3 q
- log.Info("Added new address ", "address:", addr, " total:", a.size()). ~8 L3 H3 O" K. h
- }
选择最优节点6 |+ e8 @& S$ ~: d1 C
地址簿中存储众多地址,在p2p网络中需选择最优的地址去连接
PickAddress(newBias int)函数中newBias是由pex_reactor产生的地址评分。如何计算地址分数在其他章节中再讲/ O. c; Y% Y4 `& w. N
根据地址评分随机选择地址可增加区块链安全性
- // Pick an address to connect to with new/old bias.( p1 X. P- {4 h; T, R% B ?4 a7 Y: U
- func (a *AddrBook) PickAddress(newBias int) *NetAddress {
- a.mtx.Lock()4 ]( R' O3 ~5 o6 M
- defer a.mtx.Unlock()
- if a.size() == 0 {
- return nil! e5 [9 B* I+ \
- }
- // newBias地址分数限制在0-100分数之间
- if newBias > 100 {
- newBias = 100- u" Q/ d( f! T# o3 k! R* @! V
- }$ Z* N3 k& h3 @! h
- if newBias
移除一个地址- R3 z; T7 J! C0 d
当一个地址被标记为Bad时则从地址集中移除。目前bytomd的代码版本并未调用过
- func (a *AddrBook) MarkBad(addr *NetAddress) {3 a- W$ {1 N$ D, C1 @7 _# o
- a.RemoveAddress(addr)
- }
- // RemoveAddress removes the address from the book.
- func (a *AddrBook) RemoveAddress(addr *NetAddress) {
- a.mtx.Lock()
- defer a.mtx.Unlock()2 P ]2 `2 t8 B* L4 l) i
- ka := a.addrLookup[addr.String()]/ v2 x! X2 `) Q
- if ka == nil {+ x3 ` ?% N6 q C1 W0 T9 m
- return% F; |9 x$ Z/ x1 {
- }9 X6 ]6 W, d+ i0 ~( ?
- log.WithField("addr", addr).Info("Remove address from book")
- a.removeFromAllBuckets(ka)
- }
- func (a *AddrBook) removeFromAllBuckets(ka *knownAddress) {4 ?) E$ L# }) _$ N5 M# |+ Y
- for _, bucketIdx := range ka.Buckets {
- bucket := a.getBucket(ka.BucketType, bucketIdx)" `. F1 r2 C+ E, r. l3 n3 e# a7 b
- delete(bucket, ka.Addr.String())" L$ F& a9 b P7 c) }0 ~
- }1 y1 P1 H( U$ L7 l, d. t
- ka.Buckets = nil
- if ka.BucketType == bucketTypeNew {
- a.nNew--# J2 y# E4 P4 r( Y& ]: ?! [! P
- } else {& e/ u/ h3 O- O/ I3 C# q
- a.nOld--. j6 l9 B+ L6 X! z6 Q, s0 r. X
- }8 `3 j. {; n* T
- delete(a.addrLookup, ka.Addr.String())
- }