https://github.com/Bytom/bytom
本章介绍bytom代码P2P网络中addrbook地址簿
1 |' ?4 m( r% }3 _" @( \4 ` B& W
作者使用MacOS操作系统,其他平台也大同小异) v) _. W6 `2 [/ r8 M& G
; n5 J/ ~0 j7 j" b; C
2 B, m U( y' c* i( W) Z, }
Golang Version: 1.82 \4 K; g6 z9 I& y
addrbook介绍# r! B j5 c. B7 b9 J: m7 m p
addrbook用于存储P2P网络中保留最近的对端节点地址
在MacOS下,默认的地址簿路径存储在~/Library/Bytom/addrbook.json# \( ?' c2 l# k% R8 S ]) T, r/ ?
地址簿格式* b' h3 J9 M% W
- ** ~/Library/Bytom/addrbook.json **- c* f- i# B3 n8 ?% l9 b! k5 z/ b% ~* G, W
- {, c( O6 @. A' i( D" i7 S0 R
- "Key": "359be6d08bc0c6e21c84bbb2",: O- I: I& q! _' Z
- "Addrs": [
- {
- "Addr": { \2 N! N' J. b& Z* I
- "IP": "122.224.11.144",3 K* _1 }6 t8 l( G6 W. M8 r
- "Port": 46657
- },2 c$ ~( N% C$ g8 H) c
- "Src": {6 x5 Q. h$ ` o4 A" h5 k5 K
- "IP": "198.74.61.131",
- "Port": 46657
- },1 i5 |; k9 |& _# ?0 T! D& `6 y
- "Attempts": 0,1 \ K, W# V: W! N! j/ T6 w
- "LastAttempt": "2018-05-04T12:58:23.894057702+08:00",
- "LastSuccess": "0001-01-01T00:00:00Z",
- "BucketType": 1,
- "Buckets": [
- 181,
- 10
- ]
- }
- ]
- }
地址类型9 `) Y3 s7 \& t8 Q
在addrbook中存储的地址有两种:
- ** p2p/addrbook.go **7 w) T' q- P! G7 D9 Y
- const ($ ? L( e7 X, H* o# w0 G
- bucketTypeNew = 0x01 // 标识新地址,不可靠地址(未成功连接过)。只存储在一个bucket中+ y G) C. a3 }; \- S4 r
- bucketTypeOld = 0x02 // 标识旧地址,可靠地址(已成功连接过)。可以存储在多个bucket中,最多为maxNewBucketsPerAddress个5 z4 w/ @) D d$ }* u
- )
注意: 一个地址的类型变更不在此文章中做介绍,后期的文章会讨论该问题
地址簿相关结构体, Z* i6 Q5 M5 {. f) V4 m+ P3 C
地址簿
- type AddrBook struct {/ ?4 N: ^0 F* \' K
- cmn.BaseService
- mtx sync.Mutex
- filePath string // 地址簿路径
- routabilityStrict bool // 是否可路由,默认为true7 r# C N0 ~6 k0 G- k& m, q
- rand *rand.Rand
- key string // 地址簿标识,用于计算addrNew和addrOld的索引
- ourAddrs map[string]*NetAddress // 存储本地网络地址,用于添加p2p地址时做排除使用
- addrLookup map[string]*knownAddress // 存储新、旧地址集,用于查询
- addrNew []map[string]*knownAddress // 存储新地址
- addrOld []map[string]*knownAddress // 存储旧地址
- wg sync.WaitGroup; T# B% y' W& t, f
- nOld int // 旧地址数量
- nNew int // 新地址数量 m) p* X' k# y) @9 r& A* T
- }
已知地址( I# O# p, _8 [/ I$ w
- type knownAddress struct {- ]* d! D) V6 Q
- Addr *NetAddress // 已知peer的addr M" ~9 ?! H# A" b
- Src *NetAddress // 已知peer的addr的来源addr
- Attempts int32 // 连接peer的重试次数
- LastAttempt time.Time // 最近一次尝试连接的时间
- LastSuccess time.Time // 最近一次尝试成功连接的时间
- BucketType byte // 地址的类型(表示可靠地址或不可靠地址)
- Buckets []int // 当前addr所属的buckets
- }
routabilityStrict参数表示地址簿是否存储的ip是否可路由。可路由是根据RFC划分,具体参考资料:RFC标准2 W& x7 d0 G# Z( P
初始化地址簿: z# D! A! H# X7 X% I4 Y) p" u, ]4 V
- // 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())),
- ourAddrs: make(map[string]*NetAddress),) |: C4 x/ }/ @) F! n
- addrLookup: make(map[string]*knownAddress),' D, {8 [% r) _5 Q
- filePath: filePath,- |% `# L9 S# d/ Q: t& D
- routabilityStrict: routabilityStrict,6 d" F# l' a+ r
- }
- am.init()
- am.BaseService = *cmn.NewBaseService(nil, "AddrBook", am)
- return am
- }& h+ n$ N6 w; S7 ?' {
- // When modifying this, don't forget to update loadFromFile()# ? f/ u2 K r/ ]) M; `# e
- func (a *AddrBook) init() {
- // 地址簿唯一标识
- a.key = crypto.CRandHex(24) // 24/2 * 8 = 96 bits
- // New addr buckets, 默认为256个大小) ^* N; R& t6 {; F" y& q
- a.addrNew = make([]map[string]*knownAddress, newBucketCount)2 o! L# S1 U) l; d* y
- for i := range a.addrNew {' r( D; X. J% q
- a.addrNew<i> = make(map[string]*knownAddress)* M/ I7 m, O; c- F4 R' R( x
- }; ~% g j6 Q' s5 l
- // Old addr buckets,默认为64个大小
- a.addrOld = make([]map[string]*knownAddress, oldBucketCount)
- for i := range a.addrOld {, [1 [7 m& j8 N) t3 {
- a.addrOld<i> = make(map[string]*knownAddress)
- }
- }</i></i>
bytomd启动时加载本地地址簿1 m$ v4 k5 S$ s6 ~2 r5 q1 r
loadFromFile在bytomd启动时,首先会加载本地的地址簿+ k4 q& z& K) q
- // OnStart implements Service.7 N! I) Z7 T) X2 ~( [" j. Q7 m# u# Q
- func (a *AddrBook) OnStart() error {/ {/ ^5 q$ I' o4 A
- a.BaseService.OnStart()
- a.loadFromFile(a.filePath), g% @0 A( e* L
- a.wg.Add(1)7 ^: ~7 `# ] a* g* G0 d
- go a.saveRoutine()
- return nil5 y: Y0 y! D! k! p% o
- }3 h! h7 I) H6 n
- // Returns false if file does not exist.8 P" h+ K3 J+ }. J! j1 g, q
- // cmn.Panics if file is corrupt.( L% p* Q' r( c! D
- func (a *AddrBook) loadFromFile(filePath string) bool {
- // If doesn't exist, do nothing.) u+ ^# t5 W- D9 F# B; I7 @8 E
- // 如果本地地址簿不存在则直接返回$ L6 V- d- o! m
- _, err := os.Stat(filePath)
- if os.IsNotExist(err) {
- return false
- }
- // 加载地址簿json内容
- // Load addrBookJSON{}! t; f$ u" }1 D
- r, err := os.Open(filePath)! C- G) z5 Z7 `3 A; M) d
- if err != nil {# x# h% f# S* O, H: C# j9 J
- cmn.PanicCrisis(cmn.Fmt("Error opening file %s: %v", filePath, err))
- }) ^4 K9 K8 U! u+ G
- defer r.Close()
- aJSON := &addrBookJSON{}. }( w& }7 f6 D' A/ o
- dec := json.NewDecoder(r)- S! \% u0 E/ M" Y8 F- f2 F2 }; C
- err = dec.Decode(aJSON)
- if err != nil {6 A8 t% \1 X7 l
- cmn.PanicCrisis(cmn.Fmt("Error reading file %s: %v", filePath, err)) I% z6 l0 [7 C$ |) ]
- }
- // 填充addrNew、addrOld等
- // Restore all the fields...1 X, p* X: ~* B/ c# G
- // Restore the key
- a.key = aJSON.Key" V+ c0 E7 B' T
- // Restore .addrNew & .addrOld
- for _, ka := range aJSON.Addrs {
- for _, bucketIndex := range ka.Buckets {
- bucket := a.getBucket(ka.BucketType, bucketIndex)$ W( I" j& f) \' N3 y0 X J" }+ }
- bucket[ka.Addr.String()] = ka. d6 u; ]5 E7 K" T6 g; b& p( x
- }$ X9 z% ~/ }5 W0 ^+ s$ L+ I
- a.addrLookup[ka.Addr.String()] = ka4 D+ i* V) n6 m* o( }! R
- if ka.BucketType == bucketTypeNew {
- a.nNew++
- } else {
- a.nOld++
- }7 O& N* I6 m: B
- }( B. x: V% ]. Z* a% Z
- return true
- }
定时更新地址簿
bytomd会定时更新本地地址簿,默认2分钟一次
- func (a *AddrBook) saveRoutine() {+ S. ~! }) V, W6 ^2 d/ Q
- dumpAddressTicker := time.NewTicker(dumpAddressInterval)8 `$ o( }+ u7 ^; r$ M/ K& a% N
- out:# ]1 n1 ~( `9 o* r% W" p
- for { X5 y* \" G0 Z/ p0 X
- select {" _* [# @# s' g. j7 Q8 k
- case
添加新地址
当peer之间交换addr时,节点会收到对端节点已知的地址信息,这些信息会被当前节点添加到地址簿中. e5 O$ ^: n" T0 ]8 \
- func (a *AddrBook) AddAddress(addr *NetAddress, src *NetAddress) {
- a.mtx.Lock()- F9 B/ v- d/ k; K2 N* C
- defer a.mtx.Unlock()
- log.WithFields(log.Fields{7 M0 j3 k# b0 o+ D8 P. H
- "addr": addr,- k! }& E4 w3 ~% ~, e: E/ l
- "src": src,8 M% S: T; C; Q$ ~; ^
- }).Debug("Add address to book")
- a.addAddress(addr, src)0 O1 E* H4 D8 E; i' o
- }
- func (a *AddrBook) addAddress(addr, src *NetAddress) {- s" K5 ?8 I$ N. V3 I1 v( I
- // 验证地址是否为可路由地址! ^7 K8 a4 U$ H$ {# L! R& u
- if a.routabilityStrict && !addr.Routable() {2 y# Z" c. A" _, O {& {7 V
- log.Error(cmn.Fmt("Cannot add non-routable address %v", addr))4 T* \/ Q2 ]$ C* ~" W$ h
- return; o% l4 u# u+ e0 q( W. f& S7 f
- }$ r; x9 Z% j! ]8 n: ^. x+ E
- // 验证地址是否为本地节点地址) W3 P$ Y- x0 c
- if _, ok := a.ourAddrs[addr.String()]; ok {3 a9 h- k) K+ E' d
- // Ignore our own listener address.
- return
- }
- // 验证地址是否存在地址集中
- // 如果存在:则判断该地址是否为old可靠地址、是否超过了最大buckets中。否则根据该地址已经被ka.Buckets引用的个数来随机决定是否添加到地址集中; \1 ?9 \0 X/ ]3 D! s; p
- // 如果不存在:则添加到地址集中。并标识为bucketTypeNew地址类型: ~! L( A; }2 g1 y4 a
- ka := a.addrLookup[addr.String()]/ h8 E: w/ c( b# c* Q6 T
- if ka != nil {" c; t4 K( r) @ r
- // Already old., n- \7 O2 F' ~! I5 s9 a* I% v
- if ka.isOld() { i1 e$ L2 u- J" ^) s
- return
- }
- // Already in max new buckets.
- if len(ka.Buckets) == maxNewBucketsPerAddress {& _ e$ w4 [; l" B8 i" y
- return
- }
- // The more entries we have, the less likely we are to add more.9 \; ?" ^, U4 {" u- U
- factor := int32(2 * len(ka.Buckets))
- if a.rand.Int31n(factor) != 0 {5 r' r/ v8 o5 ~" S6 h
- return8 R% F; m: f V
- }
- } else {
- ka = newKnownAddress(addr, src)
- }
- // 找到该地址在地址集的索引位置并添加. Z# Z% Z, t) P' Z! G4 y
- bucket := a.calcNewBucket(addr, src)% o# m- v% n1 c: q k; u9 b
- a.addToNewBucket(ka, bucket)9 c% r q# ?6 B# l$ `! Z. c" U
- log.Info("Added new address ", "address:", addr, " total:", a.size())
- }
选择最优节点' J' ]: \% J0 L5 @( h$ a
地址簿中存储众多地址,在p2p网络中需选择最优的地址去连接" n) x; S7 r1 h, S# ?
PickAddress(newBias int)函数中newBias是由pex_reactor产生的地址评分。如何计算地址分数在其他章节中再讲
根据地址评分随机选择地址可增加区块链安全性! M' v! Y) ?* O# {4 h0 M8 V3 v
- // Pick an address to connect to with new/old bias.0 p; u' h {& g: G- e% _
- func (a *AddrBook) PickAddress(newBias int) *NetAddress {
- a.mtx.Lock()2 ?6 A) q5 y+ S6 n b$ }9 O$ C
- defer a.mtx.Unlock()
- if a.size() == 0 {8 z) d; F$ d4 i8 U, g I' I
- return nil7 s1 o% V( p# h6 |# X+ \) y
- }
- // newBias地址分数限制在0-100分数之间
- if newBias > 100 {! F x# m8 A5 j7 W/ O3 p9 V6 z
- newBias = 100
- }$ A7 u a7 B9 T4 M* g+ T! E
- if newBias
移除一个地址5 W9 T% D- A* _ T
当一个地址被标记为Bad时则从地址集中移除。目前bytomd的代码版本并未调用过6 K- ?$ k, }$ P' K$ A( o
- func (a *AddrBook) MarkBad(addr *NetAddress) {
- a.RemoveAddress(addr)% Q' E8 X2 w5 ]- @8 L' }7 D3 J
- }
- // RemoveAddress removes the address from the book.
- func (a *AddrBook) RemoveAddress(addr *NetAddress) {) a5 ?: w- C* W9 {- r
- a.mtx.Lock()9 P. K! m4 ^$ b
- defer a.mtx.Unlock()
- ka := a.addrLookup[addr.String()]
- if ka == nil {
- return' @" M3 i9 n6 c3 K7 ?
- }" r3 r6 p. E N% H1 ^
- log.WithField("addr", addr).Info("Remove address from book")& r+ ~- i; l; j% f3 m) U
- a.removeFromAllBuckets(ka)
- }
- func (a *AddrBook) removeFromAllBuckets(ka *knownAddress) {+ ] q# H3 a- `% y3 ^5 `# d
- for _, bucketIdx := range ka.Buckets {
- bucket := a.getBucket(ka.BucketType, bucketIdx)& W$ A- \3 I8 I4 c: F
- delete(bucket, ka.Addr.String())0 T5 I/ Z) l* ]' y: P; e5 J. Y
- }
- ka.Buckets = nil- {* x+ K! `; m, O% `$ r
- if ka.BucketType == bucketTypeNew {
- a.nNew--
- } else {
- a.nOld--
- }
- delete(a.addrLookup, ka.Addr.String())
- }



