https://github.com/Bytom/bytom
本章介绍bytom代码P2P网络中addrbook地址簿
作者使用MacOS操作系统,其他平台也大同小异 @) ?: E! m6 T3 N0 n
- ]+ P5 a6 m, F+ M' I
Golang Version: 1.8* t. ]% Y3 h4 j8 q
addrbook介绍
addrbook用于存储P2P网络中保留最近的对端节点地址
在MacOS下,默认的地址簿路径存储在~/Library/Bytom/addrbook.json: }( n% [5 f0 G5 P3 k2 c
地址簿格式" P0 E" V. f: K1 B7 S
- ** ~/Library/Bytom/addrbook.json **
- {' R m+ S* X! Z4 A0 g
- "Key": "359be6d08bc0c6e21c84bbb2",1 A, k p* Y% D7 Y: q6 J
- "Addrs": [
- {
- "Addr": { R! T7 |: f9 A; P
- "IP": "122.224.11.144",9 E" {9 i2 M' o! O( U; g) ?
- "Port": 46657$ i" C( s% B, h7 D8 i9 x% F
- },
- "Src": {
- "IP": "198.74.61.131",
- "Port": 466577 W' L" N* u/ A9 l5 s
- },
- "Attempts": 0,
- "LastAttempt": "2018-05-04T12:58:23.894057702+08:00",! y" W3 }) @2 x( y7 X: X
- "LastSuccess": "0001-01-01T00:00:00Z",
- "BucketType": 1,
- "Buckets": [! y; n1 w2 a; Q) o# x
- 181,
- 10
- ]
- }9 C2 ]$ M) x6 L3 b
- ]7 L% J( q' I, ]" |
- }
地址类型0 ]; o& \9 @# T5 E8 F
在addrbook中存储的地址有两种:
- ** p2p/addrbook.go **; N/ B! f. p7 m0 J/ E' k3 F f
- const (
- bucketTypeNew = 0x01 // 标识新地址,不可靠地址(未成功连接过)。只存储在一个bucket中
- bucketTypeOld = 0x02 // 标识旧地址,可靠地址(已成功连接过)。可以存储在多个bucket中,最多为maxNewBucketsPerAddress个- A' O8 ?" k8 q7 h) `, b
- )
% H& `) n* e$ B0 o E
注意: 一个地址的类型变更不在此文章中做介绍,后期的文章会讨论该问题: ]5 o' x! ], o) Y. n1 U; t
地址簿相关结构体3 R! p, b `1 U6 M0 D% W
地址簿
- type AddrBook struct {; e5 H, d7 g$ P- A
- cmn.BaseService! C6 g7 m$ J; p, `7 m$ W; Q
- mtx sync.Mutex
- filePath string // 地址簿路径1 }' I. o5 {9 B* E3 Y6 P
- routabilityStrict bool // 是否可路由,默认为true' W) C& o0 Q z8 f, I9 a) x
- rand *rand.Rand
- key string // 地址簿标识,用于计算addrNew和addrOld的索引
- ourAddrs map[string]*NetAddress // 存储本地网络地址,用于添加p2p地址时做排除使用
- addrLookup map[string]*knownAddress // 存储新、旧地址集,用于查询
- addrNew []map[string]*knownAddress // 存储新地址
- addrOld []map[string]*knownAddress // 存储旧地址! Q$ o" T& H! B# j- p
- wg sync.WaitGroup
- nOld int // 旧地址数量
- nNew int // 新地址数量2 t% X. ^3 d( C
- }
已知地址( O v, D7 X X8 w$ h
- type knownAddress struct {7 t: p3 o- x# {1 t4 S* K
- Addr *NetAddress // 已知peer的addr$ I3 ^4 [% @ r% T: K7 ~- l# `' n6 ^" Q
- Src *NetAddress // 已知peer的addr的来源addr
- Attempts int32 // 连接peer的重试次数6 y' Y* K. |7 D. i
- LastAttempt time.Time // 最近一次尝试连接的时间% P/ [ J; P- ]1 M
- LastSuccess time.Time // 最近一次尝试成功连接的时间
- BucketType byte // 地址的类型(表示可靠地址或不可靠地址)
- Buckets []int // 当前addr所属的buckets
- }
routabilityStrict参数表示地址簿是否存储的ip是否可路由。可路由是根据RFC划分,具体参考资料:RFC标准 l% a2 Y1 F0 C3 V4 ^2 N8 j
初始化地址簿
- // NewAddrBook creates a new address book.# o; Q) q, n/ P0 M" f' G, z
- // Use Start to begin processing asynchronous address updates.0 n6 _% A' z% x/ }6 q$ S! K
- func NewAddrBook(filePath string, routabilityStrict bool) *AddrBook {
- am := &AddrBook{4 `& u- t* q- ^2 S% [
- rand: rand.New(rand.NewSource(time.Now().UnixNano())),
- ourAddrs: make(map[string]*NetAddress), S8 G. B5 F- c; k2 q
- addrLookup: make(map[string]*knownAddress),
- filePath: filePath,4 v [8 y3 r- e1 [' X; g c
- routabilityStrict: routabilityStrict,, i; ?5 Q8 |/ K- J8 R9 E
- }* `' N1 [8 T0 p
- am.init()+ @$ {1 E# U$ X
- am.BaseService = *cmn.NewBaseService(nil, "AddrBook", am)+ l( x4 B2 H. r$ o* e }5 L0 a" E
- return am) H0 ]" e$ C9 c2 i: g
- }
- // When modifying this, don't forget to update loadFromFile()
- func (a *AddrBook) init() {/ S5 Z' q# D7 ^- U
- // 地址簿唯一标识
- a.key = crypto.CRandHex(24) // 24/2 * 8 = 96 bits
- // New addr buckets, 默认为256个大小
- a.addrNew = make([]map[string]*knownAddress, newBucketCount)
- for i := range a.addrNew {6 z2 O& G+ Z e' @$ _
- a.addrNew<i> = make(map[string]*knownAddress)
- }
- // Old addr buckets,默认为64个大小+ p4 D) x Z4 ^" U7 ?! @
- a.addrOld = make([]map[string]*knownAddress, oldBucketCount)
- for i := range a.addrOld {. j8 r9 |- B, f2 b7 B4 s) M
- a.addrOld<i> = make(map[string]*knownAddress)# S3 Y9 R# ?* ]8 _' D
- }( d8 Q. ~) g* B) f5 L* m5 j
- }</i></i>
bytomd启动时加载本地地址簿& q) F; U f7 S, p1 i
loadFromFile在bytomd启动时,首先会加载本地的地址簿
- // OnStart implements Service.
- func (a *AddrBook) OnStart() error {3 e& n. U3 o% p" }
- a.BaseService.OnStart()- ` z# {2 M6 h+ u2 C8 R0 C
- a.loadFromFile(a.filePath)" ]& G I: U, H9 v" V, l& H
- a.wg.Add(1); f- x, E8 ?4 |, _/ ?
- go a.saveRoutine()# E- H: @$ V$ _, [& i( ]
- return nil6 u, J1 ], p1 p0 J- C; e3 Q
- }
- // Returns false if file does not exist." u& ^" n( Y! ~
- // cmn.Panics if file is corrupt.5 d# T$ P: y7 K) R# U/ `* l
- func (a *AddrBook) loadFromFile(filePath string) bool {
- // If doesn't exist, do nothing.8 Q* t, w; A6 S8 E! ?. n" { w, B1 }$ r
- // 如果本地地址簿不存在则直接返回
- _, err := os.Stat(filePath)( X1 |7 d/ l% Z0 L% @
- if os.IsNotExist(err) {, q( D k! j0 h5 h8 C& c, ]
- return false# x4 F. S) |3 B, s( j5 p0 g
- }' ] O5 m. n8 B- H
- // 加载地址簿json内容
- // Load addrBookJSON{}
- r, err := os.Open(filePath)# V% k& u) r/ g# u& F$ a& ]) e5 y
- if err != nil {
- cmn.PanicCrisis(cmn.Fmt("Error opening file %s: %v", filePath, err))
- }# B4 ?% F: ~: U! n& \) V# r6 N3 ^
- defer r.Close()3 [: s$ X% F& p; x) d& U6 P5 D
- aJSON := &addrBookJSON{}
- dec := json.NewDecoder(r)4 q( \; S6 ~& A
- err = dec.Decode(aJSON)5 ?0 y" Y- Y# A/ M- t# E4 q
- if err != nil {
- cmn.PanicCrisis(cmn.Fmt("Error reading file %s: %v", filePath, err))
- }
- // 填充addrNew、addrOld等/ I3 m$ M( W: n3 t# @! b" f3 x' p
- // Restore all the fields...4 A+ i& h$ {0 c' K. t
- // Restore the key
- a.key = aJSON.Key
- // Restore .addrNew & .addrOld% d/ W0 r* l% s* z1 S3 q
- for _, ka := range aJSON.Addrs {
- for _, bucketIndex := range ka.Buckets {- Y) T' u4 U1 p% _
- bucket := a.getBucket(ka.BucketType, bucketIndex)9 {- `( b; N# K. k4 b
- bucket[ka.Addr.String()] = ka
- }
- a.addrLookup[ka.Addr.String()] = ka
- if ka.BucketType == bucketTypeNew { N- z# Z7 x7 r6 [! I+ }. a. l9 e. r/ N
- a.nNew++3 [: R U# _( L' f; j% i
- } else {
- a.nOld++ h' A9 ~/ [5 \! C* U& U
- }
- }* V# e& ]; Q4 X% s
- return true% }3 X8 q" r' G: A) l# W. ]% _
- }
定时更新地址簿- v9 X: V6 s |. g
bytomd会定时更新本地地址簿,默认2分钟一次
- func (a *AddrBook) saveRoutine() {' X P2 G% P9 V2 @) F5 F
- dumpAddressTicker := time.NewTicker(dumpAddressInterval)1 U5 c3 X S$ e! s/ p# B1 j
- out:( m; X5 h [8 C- a, ~% A
- for {! u% K& `7 ^# S+ k
- select {$ C: y% \ i' E, V5 S/ g( ?! K+ j
- case
添加新地址
当peer之间交换addr时,节点会收到对端节点已知的地址信息,这些信息会被当前节点添加到地址簿中
- func (a *AddrBook) AddAddress(addr *NetAddress, src *NetAddress) {: ~- e4 R- f: l: }! m% W) X- O
- a.mtx.Lock()
- defer a.mtx.Unlock()
- log.WithFields(log.Fields{
- "addr": addr,2 y @. j7 W2 j6 V' [/ X
- "src": src,
- }).Debug("Add address to book")1 p' I+ b- H. @% i
- a.addAddress(addr, src)
- }" K1 z0 F8 b8 C( N6 @" C
- func (a *AddrBook) addAddress(addr, src *NetAddress) {6 q: O% X' p! d6 |
- // 验证地址是否为可路由地址5 C4 |" Z4 u' J' o9 e9 l
- if a.routabilityStrict && !addr.Routable() {$ e& r' o$ l6 P' `, F# }
- log.Error(cmn.Fmt("Cannot add non-routable address %v", addr)): L, @* Y% ]1 n* v1 Y" u/ f# N
- return; K: A& m3 Q* g: d4 D% I7 ^$ E
- }/ U% h/ d+ q! [3 e* G
- // 验证地址是否为本地节点地址
- if _, ok := a.ourAddrs[addr.String()]; ok {
- // Ignore our own listener address.
- return: n7 P$ k$ n9 r" v; `- p
- }
- // 验证地址是否存在地址集中
- // 如果存在:则判断该地址是否为old可靠地址、是否超过了最大buckets中。否则根据该地址已经被ka.Buckets引用的个数来随机决定是否添加到地址集中
- // 如果不存在:则添加到地址集中。并标识为bucketTypeNew地址类型
- ka := a.addrLookup[addr.String()]/ v/ m. t! [: k
- if ka != nil {
- // Already old.4 @# t0 W$ i; G) t& X
- if ka.isOld() {& g6 H! s6 n7 W
- return3 p/ C1 M7 ?% c. e) l8 _
- }
- // Already in max new buckets.. z# {; r: N, @' }3 z' }+ D
- if len(ka.Buckets) == maxNewBucketsPerAddress {( l1 F: F. K0 J
- return
- } r9 L) y1 c: G; ]- V& C
- // 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 {
- return
- }
- } else {' \1 e- p( X. C2 A+ T
- ka = newKnownAddress(addr, src)
- }" z" ^1 A: [; o4 k* `
- // 找到该地址在地址集的索引位置并添加! }0 s3 D* ]4 X* Q$ A
- bucket := a.calcNewBucket(addr, src)
- a.addToNewBucket(ka, bucket)- J1 {( e& V( K. Q8 S& W5 S
- log.Info("Added new address ", "address:", addr, " total:", a.size())
- }
选择最优节点
地址簿中存储众多地址,在p2p网络中需选择最优的地址去连接
PickAddress(newBias int)函数中newBias是由pex_reactor产生的地址评分。如何计算地址分数在其他章节中再讲, F% q/ {) ~7 Y# Z F" }
根据地址评分随机选择地址可增加区块链安全性
- // Pick an address to connect to with new/old bias.
- func (a *AddrBook) PickAddress(newBias int) *NetAddress {
- a.mtx.Lock()
- defer a.mtx.Unlock()* w, Z7 f$ R4 R0 l
- if a.size() == 0 {
- return nil. R6 O& T) M/ i" f
- }
- // newBias地址分数限制在0-100分数之间
- if newBias > 100 {' |0 {$ k5 |& _; p; E6 C: L
- newBias = 100 r3 V# g4 O! x, V e
- }# X3 P2 d1 i* f: w P2 Q8 N& D
- if newBias
移除一个地址" N" M# W6 b a3 F/ t( m8 n
当一个地址被标记为Bad时则从地址集中移除。目前bytomd的代码版本并未调用过
- func (a *AddrBook) MarkBad(addr *NetAddress) {
- a.RemoveAddress(addr)+ {2 i2 v+ o" l; r l6 }% b
- }
- // RemoveAddress removes the address from the book.
- func (a *AddrBook) RemoveAddress(addr *NetAddress) {
- a.mtx.Lock()
- defer a.mtx.Unlock()
- ka := a.addrLookup[addr.String()]( J9 R0 K4 Z% F! W" N P3 c* \
- if ka == nil {) F, k: ?7 I% v/ F
- return9 a# n9 s" B$ D: D% _' `
- }
- log.WithField("addr", addr).Info("Remove address from book")) V6 q9 J' E8 `
- a.removeFromAllBuckets(ka)( \9 o6 N" N7 |7 V c% L: q# z
- }/ y) Z% ^$ V+ a. L
- func (a *AddrBook) removeFromAllBuckets(ka *knownAddress) {" V5 ^$ J& n( \* o
- for _, bucketIdx := range ka.Buckets {! q0 c" Y" C9 w3 \
- bucket := a.getBucket(ka.BucketType, bucketIdx)
- delete(bucket, ka.Addr.String())
- }
- ka.Buckets = nil
- if ka.BucketType == bucketTypeNew {" u2 A5 i2 V( X) ^, b" @
- a.nNew--
- } else {
- a.nOld--
- }# ^7 C! x# [* X
- delete(a.addrLookup, ka.Addr.String())! ~, W9 T6 m% w$ q8 B
- }