https://github.com/Bytom/bytom. r' l) w6 q3 X0 ~& [
本章介绍bytom代码P2P网络中addrbook地址簿( K( R" p+ m b$ [: y$ S, {
作者使用MacOS操作系统,其他平台也大同小异
Golang Version: 1.8+ Q% W, n: x8 W# s$ B" X! E
addrbook介绍" f' J5 j1 R1 P: h0 x, x9 X) }
addrbook用于存储P2P网络中保留最近的对端节点地址
在MacOS下,默认的地址簿路径存储在~/Library/Bytom/addrbook.json$ @9 [1 N' ^! g; D6 w" ?" ^# T$ A
地址簿格式
- ** ~/Library/Bytom/addrbook.json **) b$ e2 _$ k8 p9 J b, k: @
- {; {; T4 i3 S6 ]8 M6 [
- "Key": "359be6d08bc0c6e21c84bbb2",
- "Addrs": [/ w, B- l" J$ E! j# }2 d Q
- {. o- S( c6 |7 t7 [8 F: S
- "Addr": {
- "IP": "122.224.11.144",
- "Port": 466574 C* I8 y$ V3 Z$ ~" b
- },$ Q% y! U6 A0 t3 @. n% i
- "Src": {
- "IP": "198.74.61.131",/ C9 U0 T- h3 L0 C
- "Port": 46657" m6 M- P- [- }1 S" \
- },: |$ p/ m- g- q2 N6 H& P+ Z; f
- "Attempts": 0,
- "LastAttempt": "2018-05-04T12:58:23.894057702+08:00"," ]% T/ n& A7 @3 `
- "LastSuccess": "0001-01-01T00:00:00Z",2 ]& ?& J# J4 o2 T
- "BucketType": 1,' b6 `& s' D7 {9 i
- "Buckets": [
- 181,' u8 M) G% E( z( v# a
- 10; x) H1 F0 q! M# l- H+ F q" T
- ]+ b1 y% @; Q0 O! B6 l
- }
- ]
- }
地址类型
在addrbook中存储的地址有两种:
- ** p2p/addrbook.go **1 {4 ]* t4 U$ X% P2 q2 ]0 w/ z& [
- const (
- bucketTypeNew = 0x01 // 标识新地址,不可靠地址(未成功连接过)。只存储在一个bucket中/ ?3 X# n; M {+ }' ^; U
- bucketTypeOld = 0x02 // 标识旧地址,可靠地址(已成功连接过)。可以存储在多个bucket中,最多为maxNewBucketsPerAddress个
- )
1 L E7 z: ^! j6 x! N5 F
注意: 一个地址的类型变更不在此文章中做介绍,后期的文章会讨论该问题
地址簿相关结构体
地址簿! E K$ b2 L+ h; l( A8 ~) s
- type AddrBook struct {
- cmn.BaseService- h/ G0 m6 B) J8 H2 z8 M7 B
- mtx sync.Mutex# C N! j y% r/ I7 j0 A. W' k4 e
- filePath string // 地址簿路径# l4 z T5 M t% b
- routabilityStrict bool // 是否可路由,默认为true
- rand *rand.Rand , |6 y2 K& I0 k
- key string // 地址簿标识,用于计算addrNew和addrOld的索引
- ourAddrs map[string]*NetAddress // 存储本地网络地址,用于添加p2p地址时做排除使用" n4 }/ e& j# N! j+ o. g( f
- addrLookup map[string]*knownAddress // 存储新、旧地址集,用于查询5 A% g$ U2 @- i2 o- x
- addrNew []map[string]*knownAddress // 存储新地址7 m4 [0 a4 V; U2 V7 t8 t5 o# u2 F* p
- addrOld []map[string]*knownAddress // 存储旧地址 C5 W% u6 R0 r
- wg sync.WaitGroup( ]; ]/ a4 e& M: G7 H
- nOld int // 旧地址数量; v: \$ @# Z6 W8 ~( ~7 Q
- nNew int // 新地址数量( y1 B' j8 I4 y, D
- }
已知地址3 F- @) |* o7 Q9 }9 }8 g
- type knownAddress struct {
- Addr *NetAddress // 已知peer的addr
- Src *NetAddress // 已知peer的addr的来源addr
- Attempts int32 // 连接peer的重试次数7 V# m) m* j8 m; r% }2 j/ B
- LastAttempt time.Time // 最近一次尝试连接的时间! r& ]; d u' t7 C
- LastSuccess time.Time // 最近一次尝试成功连接的时间0 _5 i! ?# p9 X
- BucketType byte // 地址的类型(表示可靠地址或不可靠地址)
- Buckets []int // 当前addr所属的buckets7 T7 F L5 B9 e6 q4 U3 U7 O4 {% L
- }
routabilityStrict参数表示地址簿是否存储的ip是否可路由。可路由是根据RFC划分,具体参考资料:RFC标准/ ~) E% @) h; X0 ^+ ]1 L
初始化地址簿, r. L, g7 F- ^/ I" ~5 B# j
- // NewAddrBook creates a new address book.2 e$ @3 S( g8 ~7 {
- // 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())),
- ourAddrs: make(map[string]*NetAddress)," e+ h0 U! `3 D: M9 ?/ I
- addrLookup: make(map[string]*knownAddress),( r; y% _7 M+ H* B! y7 F4 O
- filePath: filePath,
- routabilityStrict: routabilityStrict,, G- j; U. v; {' N( W/ z
- }
- am.init()& O& \2 _7 v5 W; ?0 H2 Z7 K
- am.BaseService = *cmn.NewBaseService(nil, "AddrBook", am)
- return am
- }7 q4 R5 O9 x6 j6 W$ k
- // When modifying this, don't forget to update loadFromFile()8 j* [ \; B4 B- W; o8 Z7 W- V
- func (a *AddrBook) init() {
- // 地址簿唯一标识
- a.key = crypto.CRandHex(24) // 24/2 * 8 = 96 bits
- // New addr buckets, 默认为256个大小. I; Q7 z! I; @& B. G& Y: a, ?0 B V
- a.addrNew = make([]map[string]*knownAddress, newBucketCount)
- for i := range a.addrNew {; V9 u; u$ t( U6 \4 Z
- a.addrNew<i> = make(map[string]*knownAddress)
- } n8 D% K, u: m+ _1 B
- // Old addr buckets,默认为64个大小6 G. Y8 V; B% C: b6 i
- a.addrOld = make([]map[string]*knownAddress, oldBucketCount)
- for i := range a.addrOld {1 W; n. a& C: T4 g
- a.addrOld<i> = make(map[string]*knownAddress)9 o, L L" f' w, g5 V' b
- }
- }</i></i>
bytomd启动时加载本地地址簿5 }+ f+ [ P& r& H/ Z" J* M' ~
loadFromFile在bytomd启动时,首先会加载本地的地址簿$ l k5 q5 F0 F! z; D; ^
- // OnStart implements Service.
- func (a *AddrBook) OnStart() error {( l9 u- A1 F" z! X0 h' F3 d
- a.BaseService.OnStart() A {* \+ ?- x. j, G( v- f
- a.loadFromFile(a.filePath)" n8 o* W( d4 Q. [' P9 G
- a.wg.Add(1); J- F) Y/ B; U
- go a.saveRoutine()4 e# q; w) [2 N! [
- return nil' @$ t/ R$ L$ e, W5 [, g5 P
- }
- // Returns false if file does not exist./ T0 d' t( \! Z, X* o L
- // cmn.Panics if file is corrupt.) Y- Q3 s1 K1 X* U& L# R
- func (a *AddrBook) loadFromFile(filePath string) bool {* p8 I3 A, ^5 q
- // If doesn't exist, do nothing.6 ?" `, B) ?5 B' m3 E" W
- // 如果本地地址簿不存在则直接返回
- _, err := os.Stat(filePath)
- if os.IsNotExist(err) {0 _: j! C% s0 J3 i+ j1 P9 U
- return false
- }
- // 加载地址簿json内容
- // Load addrBookJSON{}* m! u$ \7 n0 N
- r, err := os.Open(filePath)3 f& ~4 e6 k1 F# Y
- if err != nil {
- cmn.PanicCrisis(cmn.Fmt("Error opening file %s: %v", filePath, err)), v9 A+ y3 i: |0 i2 b! e+ {: H
- }
- defer r.Close()2 v' r0 ] u& i% N
- aJSON := &addrBookJSON{}3 l! M& L7 k( r7 f9 T5 y
- dec := json.NewDecoder(r)0 c1 G; s4 r6 [2 l9 a0 F
- err = dec.Decode(aJSON)
- if err != nil {1 l; Y+ b( Z& o2 b4 G' H
- cmn.PanicCrisis(cmn.Fmt("Error reading file %s: %v", filePath, err))- e4 @1 M/ _: K% ]" t9 c" U
- }
- // 填充addrNew、addrOld等
- // Restore all the fields...
- // Restore the key) P. S5 V7 [8 X
- a.key = aJSON.Key
- // Restore .addrNew & .addrOld; l6 I4 m: V' k7 e
- for _, ka := range aJSON.Addrs {( Y7 @, o* R; b- ?+ l( H7 m
- for _, bucketIndex := range ka.Buckets {* q/ \4 f6 m/ A- O1 d h9 C
- bucket := a.getBucket(ka.BucketType, bucketIndex)
- bucket[ka.Addr.String()] = ka7 F; ` W' ?7 B( t, X
- }4 G0 t' E; V$ T+ W' f' Z+ K2 E7 G
- a.addrLookup[ka.Addr.String()] = ka) u b" s0 B5 w, B- B4 A
- if ka.BucketType == bucketTypeNew {) ]1 v, J5 i7 s8 c+ x
- a.nNew++
- } else {
- a.nOld++
- }5 h$ C$ ~+ m2 s! a
- }
- return true
- }
定时更新地址簿; {) j3 `& p$ K! _' A
bytomd会定时更新本地地址簿,默认2分钟一次
- func (a *AddrBook) saveRoutine() {
- dumpAddressTicker := time.NewTicker(dumpAddressInterval)
- out:
- for {/ h) H, N5 x" _" ?
- select {
- case
添加新地址
当peer之间交换addr时,节点会收到对端节点已知的地址信息,这些信息会被当前节点添加到地址簿中
- func (a *AddrBook) AddAddress(addr *NetAddress, src *NetAddress) {
- a.mtx.Lock()
- defer a.mtx.Unlock()- C$ H6 T% K, b% U* D
- log.WithFields(log.Fields{
- "addr": addr," h& _, U% H( {) N9 X
- "src": src,* b) P! |0 S2 n& `& q" I# _
- }).Debug("Add address to book"), u+ c( C5 }+ z2 J7 ~
- a.addAddress(addr, src)# r3 U+ y! c4 p# A* Z$ }' `: x- V
- }) ^% l s" B1 {
- func (a *AddrBook) addAddress(addr, src *NetAddress) {% w9 f) W+ M" Q- p# W
- // 验证地址是否为可路由地址
- if a.routabilityStrict && !addr.Routable() {
- log.Error(cmn.Fmt("Cannot add non-routable address %v", addr))3 a. q% u3 {1 U
- return
- }- c! E8 x! k4 `; s8 E" B. z
- // 验证地址是否为本地节点地址, v7 ^$ F2 I; {" ^( e( n, X
- if _, ok := a.ourAddrs[addr.String()]; ok {
- // Ignore our own listener address.
- return: j0 Z) H" s, y( W: @
- }
- // 验证地址是否存在地址集中
- // 如果存在:则判断该地址是否为old可靠地址、是否超过了最大buckets中。否则根据该地址已经被ka.Buckets引用的个数来随机决定是否添加到地址集中
- // 如果不存在:则添加到地址集中。并标识为bucketTypeNew地址类型/ B( Q0 @! M, A4 X
- ka := a.addrLookup[addr.String()]
- if ka != nil {) V2 ]7 }( l! u: D# O0 ]1 P; @4 `
- // Already old.) N7 ]# R W* V6 p4 F. X- A
- if ka.isOld() {3 Y# a# N: _4 h
- return, s1 s) l v! f( Z& V* x
- }+ M2 w3 S6 @; i0 H/ c; V6 v
- // Already in max new buckets.' b2 \2 d+ t) f! m
- if len(ka.Buckets) == maxNewBucketsPerAddress {) C" O; |- Q1 Q3 Q: {. ~ A {
- return
- }
- // The more entries we have, the less likely we are to add more.' A. A8 E, j" H5 ^2 U L3 t5 ~/ g- U
- factor := int32(2 * len(ka.Buckets)); W4 ^/ i# W8 y) e+ i8 u
- if a.rand.Int31n(factor) != 0 {& D; ]* J4 T0 t4 c( N
- return0 u6 C: g0 P$ r4 i0 r. C
- }, R8 [& @; m# Q! {+ f' P9 k
- } else {# t' H* N( z0 F3 H
- ka = newKnownAddress(addr, src)
- }2 M4 e: s. ]" f' {5 u6 k
- // 找到该地址在地址集的索引位置并添加
- bucket := a.calcNewBucket(addr, src)3 {/ f' t8 c7 D9 `1 |
- a.addToNewBucket(ka, bucket)
- log.Info("Added new address ", "address:", addr, " total:", a.size())
- }
选择最优节点
地址簿中存储众多地址,在p2p网络中需选择最优的地址去连接- _( ?5 ?' L% ^% E+ v) V
PickAddress(newBias int)函数中newBias是由pex_reactor产生的地址评分。如何计算地址分数在其他章节中再讲1 T" M# Y' u5 j. v" A
根据地址评分随机选择地址可增加区块链安全性
- // Pick an address to connect to with new/old bias.: [' M* n2 z5 Z3 O# ~+ c: [( Q9 N8 |
- func (a *AddrBook) PickAddress(newBias int) *NetAddress {: ^( I( f! B% G( U% K5 }9 _
- a.mtx.Lock()" u6 L( O4 L% y! ~- [. H" p
- defer a.mtx.Unlock()8 S4 {4 q; u1 b" g( h0 R
- if a.size() == 0 {: E0 n# g& u- Y6 k
- return nil
- }# N$ D% N4 M8 s8 H: ]2 a
- // newBias地址分数限制在0-100分数之间
- if newBias > 100 {- _ H- P. ]" D+ ]
- newBias = 1006 Q5 B: Z/ G/ ~
- }. d9 `: @9 h D# K
- if newBias
移除一个地址
当一个地址被标记为Bad时则从地址集中移除。目前bytomd的代码版本并未调用过& x4 f; N. ^7 Q* u/ r z9 D' F
- func (a *AddrBook) MarkBad(addr *NetAddress) {
- a.RemoveAddress(addr)8 n! i( s- f, a( ?& M
- }
- // RemoveAddress removes the address from the book.
- func (a *AddrBook) RemoveAddress(addr *NetAddress) {
- a.mtx.Lock()
- defer a.mtx.Unlock(); B1 I" U/ u/ ]9 @8 s' L$ W1 u4 \
- ka := a.addrLookup[addr.String()]- k) @9 m$ i& E: t
- if ka == nil {
- return
- }; B5 I/ E( B0 ^6 [& X" S
- log.WithField("addr", addr).Info("Remove address from book")" d9 ]( k7 r% o4 ~& A, D
- a.removeFromAllBuckets(ka)
- }5 i/ x* B) {8 r3 y+ A' k
- func (a *AddrBook) removeFromAllBuckets(ka *knownAddress) {1 q( p% y7 _& U- |$ x6 ^6 p
- for _, bucketIdx := range ka.Buckets {( U: g( @0 D* W0 ]( e5 w
- bucket := a.getBucket(ka.BucketType, bucketIdx)" w! J3 U! A* ~5 t1 ]+ d) v
- delete(bucket, ka.Addr.String())9 w3 P2 M0 I6 g6 W& ^
- }( a) l A. z# K! E2 |6 V
- ka.Buckets = nil; i& [% h0 { a; H* @
- if ka.BucketType == bucketTypeNew {
- a.nNew--
- } else {; D; b& q+ e+ J1 f" ?1 S7 c! ~
- a.nOld--3 e8 D1 \3 ^) g0 V* G+ K" P
- }; _8 Y# Z! y3 p+ e7 [: z$ A, b
- delete(a.addrLookup, ka.Addr.String())2 F" n% O+ }% F r
- }