https://github.com/Bytom/bytom' g9 m' n4 A8 ^9 d) N4 L
本章介绍bytom代码P2P网络中addrbook地址簿4 B @ ?+ R, A) h' \' k: I# V" F3 e
+ {; b) ?& G- C" X: f/ x; g
作者使用MacOS操作系统,其他平台也大同小异
0 f: a6 I5 G! A+ K; t# i
Golang Version: 1.8: `% E4 c2 J: C& x
0 @8 M+ z( P0 P4 `% F& L- f% j/ ~
addrbook介绍% I* S1 c/ u/ [- } ], g
addrbook用于存储P2P网络中保留最近的对端节点地址
在MacOS下,默认的地址簿路径存储在~/Library/Bytom/addrbook.json
地址簿格式
- ** ~/Library/Bytom/addrbook.json **" X/ T; X; N. Z- { L5 ]! [' h
- {1 Z9 _$ ^' z( ~. g# i
- "Key": "359be6d08bc0c6e21c84bbb2",: P$ e. t! P! n+ N; q6 d# E n
- "Addrs": [' [7 g4 m7 m; C6 E! ~% t D( M
- {
- "Addr": {# D' e& d) p3 I! G/ x
- "IP": "122.224.11.144",
- "Port": 466573 H2 B9 B8 [# ~3 A/ p
- },
- "Src": {8 w+ o1 |/ C0 a1 Z; y
- "IP": "198.74.61.131",
- "Port": 466576 C- j6 M9 v0 h" O; N: c* O; w: x
- },
- "Attempts": 0,
- "LastAttempt": "2018-05-04T12:58:23.894057702+08:00",
- "LastSuccess": "0001-01-01T00:00:00Z",% Y! V5 Z8 l% D$ f5 T2 L
- "BucketType": 1,
- "Buckets": [
- 181,: K5 y& t) z# J9 m, z9 U
- 106 N' F7 L: n {' d3 b
- ]
- }
- ]
- }
地址类型 d& S" k3 G. S) \$ O" j: `
在addrbook中存储的地址有两种:
- ** p2p/addrbook.go **
- const (6 d3 Z% N, ^) h: V% \, B; p; i
- bucketTypeNew = 0x01 // 标识新地址,不可靠地址(未成功连接过)。只存储在一个bucket中
- bucketTypeOld = 0x02 // 标识旧地址,可靠地址(已成功连接过)。可以存储在多个bucket中,最多为maxNewBucketsPerAddress个
- )
1 ~% `8 \1 K1 Q9 Y; d o5 j: P
注意: 一个地址的类型变更不在此文章中做介绍,后期的文章会讨论该问题
地址簿相关结构体
地址簿$ I$ i4 n. K) c" h1 S- L
- type AddrBook struct {+ [# ?% ^, S% L% e
- cmn.BaseService
- mtx sync.Mutex
- filePath string // 地址簿路径
- routabilityStrict bool // 是否可路由,默认为true
- rand *rand.Rand % G& _" p7 z' m# Q1 V/ @/ j
- key string // 地址簿标识,用于计算addrNew和addrOld的索引
- ourAddrs map[string]*NetAddress // 存储本地网络地址,用于添加p2p地址时做排除使用 {+ l1 H3 j4 _* C. e" f2 o& h
- addrLookup map[string]*knownAddress // 存储新、旧地址集,用于查询
- addrNew []map[string]*knownAddress // 存储新地址# t: H% ]: a0 ~, V+ m
- addrOld []map[string]*knownAddress // 存储旧地址; {- I3 v' n' X
- wg sync.WaitGroup
- nOld int // 旧地址数量
- nNew int // 新地址数量$ s ]( S% L( f% z+ y, q8 s, z# p
- }
已知地址
- type knownAddress struct {
- Addr *NetAddress // 已知peer的addr3 g. q3 @6 k1 O1 V1 a
- Src *NetAddress // 已知peer的addr的来源addr
- Attempts int32 // 连接peer的重试次数
- LastAttempt time.Time // 最近一次尝试连接的时间
- LastSuccess time.Time // 最近一次尝试成功连接的时间9 d' k1 f+ F* F; w, @, J
- BucketType byte // 地址的类型(表示可靠地址或不可靠地址)) b$ p: }% P6 X& X5 P
- Buckets []int // 当前addr所属的buckets- r/ Y; E/ w. g
- }
routabilityStrict参数表示地址簿是否存储的ip是否可路由。可路由是根据RFC划分,具体参考资料:RFC标准
初始化地址簿
- // NewAddrBook creates a new address book.
- // Use Start to begin processing asynchronous address updates.
- func NewAddrBook(filePath string, routabilityStrict bool) *AddrBook {5 s6 R- D) ?9 G( ?3 |/ P
- am := &AddrBook{
- rand: rand.New(rand.NewSource(time.Now().UnixNano())),' T5 v% f$ d" W' Q7 u, H; |4 E. H
- ourAddrs: make(map[string]*NetAddress),6 C0 l! K# r! l3 r) h4 b3 C% t
- addrLookup: make(map[string]*knownAddress),
- filePath: filePath,' q0 ~7 s* x! e) B# ^
- routabilityStrict: routabilityStrict," x- S5 Y, g0 @4 {7 x! b& T0 A
- }
- am.init()
- am.BaseService = *cmn.NewBaseService(nil, "AddrBook", am)
- return am
- }
- // When modifying this, don't forget to update loadFromFile()& t$ H3 ~/ O5 D! \ T b7 Y
- func (a *AddrBook) init() {
- // 地址簿唯一标识
- a.key = crypto.CRandHex(24) // 24/2 * 8 = 96 bits4 B8 f' B0 A3 B* G' T/ X& ]
- // New addr buckets, 默认为256个大小
- a.addrNew = make([]map[string]*knownAddress, newBucketCount)9 P! y, n$ u* p2 P
- for i := range a.addrNew {
- a.addrNew<i> = make(map[string]*knownAddress)! p# w4 Y0 L( I3 ^: g
- }* ~+ s2 Y9 u: l: f z- y) J- e
- // Old addr buckets,默认为64个大小
- a.addrOld = make([]map[string]*knownAddress, oldBucketCount) u: r6 g1 C8 U" ]2 [! }
- for i := range a.addrOld {
- a.addrOld<i> = make(map[string]*knownAddress)
- }; l( k! _ V! E( C; T5 F. r8 s7 g' R
- }</i></i>
bytomd启动时加载本地地址簿
loadFromFile在bytomd启动时,首先会加载本地的地址簿
- // OnStart implements Service.- Q2 p- B7 ^8 V2 G; u+ e
- func (a *AddrBook) OnStart() error {
- a.BaseService.OnStart()
- a.loadFromFile(a.filePath)
- a.wg.Add(1)
- go a.saveRoutine()! O. z9 h% H1 E* D
- return nil
- }7 r* z' D S& s! t j! p
- // Returns false if file does not exist.
- // cmn.Panics if file is corrupt.: o" O- E! r+ a& x1 n0 t
- func (a *AddrBook) loadFromFile(filePath string) bool {
- // If doesn't exist, do nothing.
- // 如果本地地址簿不存在则直接返回
- _, err := os.Stat(filePath)
- if os.IsNotExist(err) {7 h) c( k. K7 s: k! Z. A
- return false
- }
- // 加载地址簿json内容7 e! ~" n& I7 w; ^1 S. r
- // Load addrBookJSON{}* W: W* {/ S! G$ o$ ]
- r, err := os.Open(filePath)
- if err != nil {' o7 s' [( h) I! r6 ~- e( W1 Y/ w+ N
- cmn.PanicCrisis(cmn.Fmt("Error opening file %s: %v", filePath, err))' s+ y. f& h, j d0 H( ^" E( _
- }
- defer r.Close()4 q$ G! E$ l. Z5 }* a+ t+ a
- aJSON := &addrBookJSON{}3 \5 v( I# v# p) J7 K. ^# h+ `
- dec := json.NewDecoder(r)4 k+ K0 G) D/ F1 j4 l
- err = dec.Decode(aJSON)
- if err != nil {9 n( A( E* `- r9 @
- cmn.PanicCrisis(cmn.Fmt("Error reading file %s: %v", filePath, err))
- }% F# |$ Y" L7 H
- // 填充addrNew、addrOld等) ^2 ?( K- T3 T0 U0 [
- // Restore all the fields...
- // Restore the key7 e) X5 i8 X3 v: V) ]
- a.key = aJSON.Key2 x' d- m( ]! S2 t. a" Q6 `; P
- // Restore .addrNew & .addrOld
- for _, ka := range aJSON.Addrs {
- for _, bucketIndex := range ka.Buckets {$ F1 j- w5 Q- a: @
- bucket := a.getBucket(ka.BucketType, bucketIndex); f3 k# z! R# b. g( I) `4 J
- bucket[ka.Addr.String()] = ka
- }
- a.addrLookup[ka.Addr.String()] = ka5 A7 i, E2 }# n- w7 u
- if ka.BucketType == bucketTypeNew {
- a.nNew++9 ^( X! {( s% v9 W2 \; |9 v3 J
- } else {. E9 z2 I# m& F0 P$ P5 z
- a.nOld++8 V* X! ?! Q. G
- }* p4 `! K: v+ q6 q$ _- ?
- }
- return true# T* X b. ?% n& b
- }
定时更新地址簿+ _6 H5 L/ D9 B' |4 S( }0 h
bytomd会定时更新本地地址簿,默认2分钟一次
- func (a *AddrBook) saveRoutine() {
- dumpAddressTicker := time.NewTicker(dumpAddressInterval)
- out:, v* G8 F. w/ Y8 ?) u$ E
- for {
- select {$ f# V6 C/ g m1 y1 z" o
- case
添加新地址3 c* K4 O& ^/ Z$ {# E U' l6 \
当peer之间交换addr时,节点会收到对端节点已知的地址信息,这些信息会被当前节点添加到地址簿中0 ]: }. O. m1 }. R% H' O
- func (a *AddrBook) AddAddress(addr *NetAddress, src *NetAddress) {: |4 x/ A4 I! W# c3 T6 C
- a.mtx.Lock()5 L5 h: J K5 G6 \% m9 `
- defer a.mtx.Unlock()# [) x0 ?) U l X. e
- log.WithFields(log.Fields{
- "addr": addr,
- "src": src,
- }).Debug("Add address to book")
- a.addAddress(addr, src)
- }& w% l$ Z- V- f1 T4 h
- func (a *AddrBook) addAddress(addr, src *NetAddress) {# H% V) N' l7 w. U6 Y' \
- // 验证地址是否为可路由地址
- if a.routabilityStrict && !addr.Routable() {
- log.Error(cmn.Fmt("Cannot add non-routable address %v", addr))
- return2 S. ]' ` y) ^$ z, @: t
- }
- // 验证地址是否为本地节点地址
- if _, ok := a.ourAddrs[addr.String()]; ok {
- // Ignore our own listener address.
- return( N+ m/ P8 P. r. U. [1 {1 |; x
- }- Q/ S& e* h: t/ @3 |
- // 验证地址是否存在地址集中
- // 如果存在:则判断该地址是否为old可靠地址、是否超过了最大buckets中。否则根据该地址已经被ka.Buckets引用的个数来随机决定是否添加到地址集中% S7 W. a7 z1 m/ c2 `
- // 如果不存在:则添加到地址集中。并标识为bucketTypeNew地址类型: j0 f6 V E$ z0 N# f: n% U! ^( O
- ka := a.addrLookup[addr.String()]2 R8 y$ @5 T, d$ @, ?) k
- if ka != nil {- {; ~+ U9 R0 @
- // Already old.
- if ka.isOld() {
- return' r# t7 w: V8 Z' k
- }" f3 ?: W6 x, u3 z1 ?: n
- // Already in max new buckets.
- if len(ka.Buckets) == maxNewBucketsPerAddress {
- return
- }% T+ {6 ?& n% _; d2 B( _
- // The more entries we have, the less likely we are to add more.4 ?' | ]+ L% a1 S( E2 O
- factor := int32(2 * len(ka.Buckets))
- if a.rand.Int31n(factor) != 0 {- c3 W( {- @, ~$ R
- return2 M( T* i7 a4 T4 c' b, a. P
- }# S( H+ t1 E7 G7 r/ }; K. U
- } else {
- ka = newKnownAddress(addr, src). \3 ` ~2 G9 F2 ^. H
- }' [: O W; [% X, `$ F6 ?: M5 c! F
- // 找到该地址在地址集的索引位置并添加
- bucket := a.calcNewBucket(addr, src)
- a.addToNewBucket(ka, bucket)
- log.Info("Added new address ", "address:", addr, " total:", a.size())
- }
选择最优节点8 }7 j' x- r, ^ k! V/ I) \
地址簿中存储众多地址,在p2p网络中需选择最优的地址去连接
PickAddress(newBias int)函数中newBias是由pex_reactor产生的地址评分。如何计算地址分数在其他章节中再讲
根据地址评分随机选择地址可增加区块链安全性
- // Pick an address to connect to with new/old bias.
- func (a *AddrBook) PickAddress(newBias int) *NetAddress {6 }3 K9 b6 R: A# p, F8 B$ c- \0 M
- a.mtx.Lock()
- defer a.mtx.Unlock()( W: o, p+ v6 J0 X
- if a.size() == 0 {
- return nil
- }
- // newBias地址分数限制在0-100分数之间7 R( J! Q# j% h2 @ M* ] i+ K3 ^6 L1 r
- if newBias > 100 {) i* c y0 t5 H' M7 i
- newBias = 100
- }. W0 p! N0 J# f1 ^/ @7 }
- if newBias
移除一个地址
当一个地址被标记为Bad时则从地址集中移除。目前bytomd的代码版本并未调用过3 t" o% I' k4 v, v1 A. e
- func (a *AddrBook) MarkBad(addr *NetAddress) {1 p4 _- U1 t# K5 a+ M' C$ G
- a.RemoveAddress(addr)
- }
- // RemoveAddress removes the address from the book.
- func (a *AddrBook) RemoveAddress(addr *NetAddress) {' z( L5 X, u4 f5 q e' t' P- r4 @
- a.mtx.Lock()
- defer a.mtx.Unlock()
- ka := a.addrLookup[addr.String()]
- if ka == nil {
- return
- }- E9 G7 v5 Z1 v0 @+ J, E
- log.WithField("addr", addr).Info("Remove address from book")( |4 @$ y+ e( r1 Q, M% E0 k4 t
- a.removeFromAllBuckets(ka)& [3 o- W* a/ j X9 k
- } P8 U# K* N5 j9 P) N5 }! s
- func (a *AddrBook) removeFromAllBuckets(ka *knownAddress) {
- for _, bucketIdx := range ka.Buckets {4 l9 C6 l* V( E, K- Y# I: M
- bucket := a.getBucket(ka.BucketType, bucketIdx)1 w- g1 k) k; J9 T
- delete(bucket, ka.Addr.String())6 e. E- k1 N8 X1 |
- }
- ka.Buckets = nil; ]& S& ?& [3 [# C* N
- if ka.BucketType == bucketTypeNew {' ~$ Z% l& n; J: z* u4 f1 K
- a.nNew--" D: q- D( e) |2 T" ^8 @% n
- } else {
- a.nOld--" ~1 x# ~' M+ H1 M6 k
- }" T3 i# H8 _% b; Z, _+ t6 f; o
- delete(a.addrLookup, ka.Addr.String())6 M; A5 {" f; R0 Z
- }