https://github.com/Bytom/bytom8 N2 |9 y% k1 D! N( [/ v6 r" q: h
本章介绍bytom代码P2P网络中addrbook地址簿 K' _, R( s' A7 J# l, I% t4 G" f
作者使用MacOS操作系统,其他平台也大同小异
; B5 e) V0 D- A
Golang Version: 1.8' _8 Z' K0 ^" w* Q
addrbook介绍! K, y+ o# o7 U# T% i" n* Q
addrbook用于存储P2P网络中保留最近的对端节点地址- {; X1 c& t g9 [, H
在MacOS下,默认的地址簿路径存储在~/Library/Bytom/addrbook.json- ^* y; C$ Y: b( J8 n
地址簿格式
- ** ~/Library/Bytom/addrbook.json **
- {
- "Key": "359be6d08bc0c6e21c84bbb2",
- "Addrs": [
- {
- "Addr": {% J1 b+ V1 ~& V$ R G
- "IP": "122.224.11.144",
- "Port": 46657
- },8 U* W8 p5 d9 ~' j8 N. b
- "Src": {
- "IP": "198.74.61.131",: N' J1 q8 t% x7 g0 h# B
- "Port": 46657
- },
- "Attempts": 0,
- "LastAttempt": "2018-05-04T12:58:23.894057702+08:00",5 i+ I" O6 Z8 L2 C& H
- "LastSuccess": "0001-01-01T00:00:00Z",
- "BucketType": 1,
- "Buckets": [
- 181,
- 10
- ]! n1 W8 ]6 Y3 g0 P
- } z8 t5 N$ V w& A, n
- ]7 O( [. b T" A
- }
地址类型
在addrbook中存储的地址有两种:5 g6 m1 i2 t! Y$ E
- ** p2p/addrbook.go **6 [7 e. I* \' ]. ?5 ~' [
- const (" t* \- |. Q) T' c1 r* z+ v
- bucketTypeNew = 0x01 // 标识新地址,不可靠地址(未成功连接过)。只存储在一个bucket中
- bucketTypeOld = 0x02 // 标识旧地址,可靠地址(已成功连接过)。可以存储在多个bucket中,最多为maxNewBucketsPerAddress个6 z2 f& O7 x" B9 t4 D/ K9 Y1 t
- )
# N$ K3 l8 U: t8 d! v2 Z5 I
注意: 一个地址的类型变更不在此文章中做介绍,后期的文章会讨论该问题$ X& T( W6 a% ` S/ t& H
地址簿相关结构体! ?! A7 {; |& P8 ^; d
地址簿7 c. ]2 h w; F2 }5 w, R
- type AddrBook struct {
- cmn.BaseService4 x' W, u$ ^) V, ?" j% ~, V
- mtx sync.Mutex
- filePath string // 地址簿路径; X, M1 s: |; M4 |
- routabilityStrict bool // 是否可路由,默认为true/ |8 z& P0 z% |4 A
- rand *rand.Rand
- key string // 地址簿标识,用于计算addrNew和addrOld的索引
- ourAddrs map[string]*NetAddress // 存储本地网络地址,用于添加p2p地址时做排除使用
- addrLookup map[string]*knownAddress // 存储新、旧地址集,用于查询) {& C7 A; t) e/ T& }; a
- addrNew []map[string]*knownAddress // 存储新地址
- addrOld []map[string]*knownAddress // 存储旧地址
- wg sync.WaitGroup9 @3 r) }1 V* C# J! i6 o; K3 i$ M
- nOld int // 旧地址数量
- nNew int // 新地址数量7 f2 p$ R) T4 U1 z5 W
- }
已知地址/ f( x. M- \1 B# T
- type knownAddress struct {' j6 R* I) [0 y* Y; i1 x5 t
- Addr *NetAddress // 已知peer的addr. |* a# n- V+ ?; Y: `, x
- Src *NetAddress // 已知peer的addr的来源addr) Y( e+ }% r. A o7 v
- Attempts int32 // 连接peer的重试次数
- LastAttempt time.Time // 最近一次尝试连接的时间- t; v& s# Z* n9 R
- LastSuccess time.Time // 最近一次尝试成功连接的时间
- BucketType byte // 地址的类型(表示可靠地址或不可靠地址)
- Buckets []int // 当前addr所属的buckets( i Y9 E! `& W" b5 n t# f; _
- }
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 {
- am := &AddrBook{% [7 E \3 I$ M! K9 r& _
- rand: rand.New(rand.NewSource(time.Now().UnixNano())),/ w6 t7 O6 a( @) U" s6 R @
- ourAddrs: make(map[string]*NetAddress),
- addrLookup: make(map[string]*knownAddress),- f5 p: y% c4 a0 Z+ l4 O
- filePath: filePath,) @" l; {/ S7 a3 Z0 X
- routabilityStrict: routabilityStrict,
- }
- am.init()) i9 e7 R- y5 n8 a
- am.BaseService = *cmn.NewBaseService(nil, "AddrBook", am)
- return am; S+ C! b f8 W/ v
- }
- // When modifying this, don't forget to update loadFromFile()
- func (a *AddrBook) init() {8 A7 k. u' k+ C9 s8 e( c* ~! g
- // 地址簿唯一标识0 n8 X! }1 b) m
- a.key = crypto.CRandHex(24) // 24/2 * 8 = 96 bits' k7 m/ a! s/ j7 {, ^: q8 @
- // New addr buckets, 默认为256个大小
- a.addrNew = make([]map[string]*knownAddress, newBucketCount)+ f- S# ~' c9 T. A7 f5 O `
- for i := range a.addrNew {
- a.addrNew<i> = make(map[string]*knownAddress)& Y, {- `. I* i& J6 V7 z
- }& J/ X, s& d% {
- // Old addr buckets,默认为64个大小
- a.addrOld = make([]map[string]*knownAddress, oldBucketCount)
- for i := range a.addrOld {: u& X. `6 {9 o5 x
- a.addrOld<i> = make(map[string]*knownAddress)6 }- w1 d, W; d. |- X! x7 M) S0 V
- }2 X& x$ h( Y0 V- |) E' C7 y8 \
- }</i></i>
bytomd启动时加载本地地址簿
loadFromFile在bytomd启动时,首先会加载本地的地址簿
- // OnStart implements Service.2 B6 u5 a! e/ L* c% k1 E% E j" |
- func (a *AddrBook) OnStart() error {
- a.BaseService.OnStart()/ r* D, v: Q( k* G
- a.loadFromFile(a.filePath)
- a.wg.Add(1)5 l8 i6 D- }. X3 ~5 ?
- go a.saveRoutine()# b/ A8 A# `8 p+ h9 i9 s
- return nil
- }# ~/ d2 c! U; M) Z* Z
- // Returns false if file does not exist.
- // cmn.Panics if file is corrupt.
- func (a *AddrBook) loadFromFile(filePath string) bool {! f6 `( q# t$ v$ @- `
- // If doesn't exist, do nothing.
- // 如果本地地址簿不存在则直接返回
- _, err := os.Stat(filePath)
- if os.IsNotExist(err) {
- return false4 p# b! ?8 g! B- ]. J
- }' b! D7 K! r' ^( Q, E t$ Q+ E
- // 加载地址簿json内容+ b; r$ W! W k1 I1 e8 [, d
- // Load addrBookJSON{}
- r, err := os.Open(filePath)
- if err != nil {4 `7 c$ S% x1 O, B4 M/ }/ T# \5 D8 V
- cmn.PanicCrisis(cmn.Fmt("Error opening file %s: %v", filePath, err))9 Q& P" {( L+ y/ q
- }
- defer r.Close(). e' u) x) }$ Y
- aJSON := &addrBookJSON{}
- dec := json.NewDecoder(r) W0 \1 T; b$ k. C6 k
- err = dec.Decode(aJSON)
- if err != nil {
- cmn.PanicCrisis(cmn.Fmt("Error reading file %s: %v", filePath, err)) e5 V0 D! o. {8 ~3 [4 O' `2 @1 J' F$ O
- }1 f* m6 ]. q7 h1 o! Q3 |( g
- // 填充addrNew、addrOld等
- // Restore all the fields...6 G" @) ] S5 Z( @5 ~1 o6 e" G5 `' E
- // Restore the key
- a.key = aJSON.Key
- // Restore .addrNew & .addrOld5 [% C# s. D: w* _1 t# f! p
- for _, ka := range aJSON.Addrs {
- for _, bucketIndex := range ka.Buckets {
- bucket := a.getBucket(ka.BucketType, bucketIndex)# d" j+ i v" S+ X0 W: Z
- bucket[ka.Addr.String()] = ka
- }
- a.addrLookup[ka.Addr.String()] = ka
- if ka.BucketType == bucketTypeNew {0 l9 @8 c1 d2 K( ?% L' \
- a.nNew++
- } else {- }4 J- u9 \- X1 M# D
- a.nOld++7 z+ W8 y4 z* F1 b
- }, u; N) ?1 l# w2 s! s1 \
- }5 q, }3 e+ ^8 I
- return true% n; B" h6 o) q$ ^1 M$ ~3 x1 }7 i6 c
- }
定时更新地址簿8 P7 I! y. v! S3 P. F8 `
bytomd会定时更新本地地址簿,默认2分钟一次0 g" A5 H, |2 Y1 C
- func (a *AddrBook) saveRoutine() {
- dumpAddressTicker := time.NewTicker(dumpAddressInterval)
- out:
- for {
- select {
- case
添加新地址1 g0 F+ B7 i& j( K
当peer之间交换addr时,节点会收到对端节点已知的地址信息,这些信息会被当前节点添加到地址簿中
- func (a *AddrBook) AddAddress(addr *NetAddress, src *NetAddress) {
- a.mtx.Lock()
- defer a.mtx.Unlock()+ x6 @+ t1 n& |3 I9 O2 b- A+ w
- log.WithFields(log.Fields{
- "addr": addr,& ]2 J/ u4 H2 ^$ M- r$ Z% f7 J
- "src": src,1 V" z% U6 }% p+ z2 D& n1 B0 `- O6 |
- }).Debug("Add address to book")! t8 w. k. F! U' Y
- a.addAddress(addr, src)
- }
- func (a *AddrBook) addAddress(addr, src *NetAddress) {
- // 验证地址是否为可路由地址
- if a.routabilityStrict && !addr.Routable() {) I% n* d; e- q% h' Y
- log.Error(cmn.Fmt("Cannot add non-routable address %v", addr))
- return+ u9 \& E3 [ x( l6 d
- }
- // 验证地址是否为本地节点地址. S1 F! W" Z' s1 D; [$ s. q3 q% r, ^6 X
- if _, ok := a.ourAddrs[addr.String()]; ok {
- // Ignore our own listener address.
- return0 p0 ]3 X9 N+ n, M
- }
- // 验证地址是否存在地址集中
- // 如果存在:则判断该地址是否为old可靠地址、是否超过了最大buckets中。否则根据该地址已经被ka.Buckets引用的个数来随机决定是否添加到地址集中
- // 如果不存在:则添加到地址集中。并标识为bucketTypeNew地址类型
- ka := a.addrLookup[addr.String()]1 T8 z P% c, ~
- if ka != nil {4 r d! V/ p; I0 l
- // Already old.' z0 d1 o$ E% b! e& k4 j
- if ka.isOld() {
- return9 L2 [( w6 }7 v ?* ^( ?% S9 R
- }
- // Already in max new buckets.
- if len(ka.Buckets) == maxNewBucketsPerAddress {% w# `* q+ k5 Q$ Z
- return8 ?6 w! P+ I3 Z" |# o# F+ A' G
- }/ J, z" S y, w$ ]$ U# h2 a" U: u
- // The more entries we have, the less likely we are to add more.: a1 k \/ [% t- n
- factor := int32(2 * len(ka.Buckets))4 r) H ~( f& |6 i3 k2 i. R) I& o/ f+ R0 x
- if a.rand.Int31n(factor) != 0 {
- return' O% f, M0 @$ l b# _4 S
- }
- } else {
- ka = newKnownAddress(addr, src)
- }
- // 找到该地址在地址集的索引位置并添加
- bucket := a.calcNewBucket(addr, src)
- a.addToNewBucket(ka, bucket)# j# s* o1 M6 D5 g2 U
- log.Info("Added new address ", "address:", addr, " total:", a.size())
- }
选择最优节点
地址簿中存储众多地址,在p2p网络中需选择最优的地址去连接! i7 j" f& l% C. j
PickAddress(newBias int)函数中newBias是由pex_reactor产生的地址评分。如何计算地址分数在其他章节中再讲. e: r7 Y: `1 _) e9 m7 w
根据地址评分随机选择地址可增加区块链安全性
- // Pick an address to connect to with new/old bias.; n# R n5 A) K6 |7 w0 k
- func (a *AddrBook) PickAddress(newBias int) *NetAddress {
- a.mtx.Lock()
- defer a.mtx.Unlock()$ d* X9 J- v! s$ `' A! V; F
- if a.size() == 0 {; F. e: [+ w# @7 I
- return nil
- }* \3 Y# }3 f1 M; q" @* s
- // newBias地址分数限制在0-100分数之间
- if newBias > 100 {4 `( u/ Q2 B2 k5 E- J
- newBias = 100+ g1 o+ @: I* S4 S0 W
- }
- if newBias
移除一个地址
当一个地址被标记为Bad时则从地址集中移除。目前bytomd的代码版本并未调用过$ y. Z- s2 P; c! a. B
- func (a *AddrBook) MarkBad(addr *NetAddress) {
- a.RemoveAddress(addr)
- }
- // RemoveAddress removes the address from the book.
- func (a *AddrBook) RemoveAddress(addr *NetAddress) {! R+ C8 n2 z0 c- J- a! n; `( u6 i% @
- a.mtx.Lock(): ~4 N7 e5 O4 i% L& x$ U
- defer a.mtx.Unlock()
- ka := a.addrLookup[addr.String()]! u, d' h" d! T- C D( o# B, i
- if ka == nil {
- return* i: A; @0 U; r% [, d8 O/ y# e
- }
- log.WithField("addr", addr).Info("Remove address from book")$ U; H% {0 Z+ J* W" h7 o9 y6 k. R9 m9 x
- a.removeFromAllBuckets(ka)+ }7 V3 a( p, p" `
- }# E2 }# ], A: U$ E, M
- func (a *AddrBook) removeFromAllBuckets(ka *knownAddress) {
- for _, bucketIdx := range ka.Buckets {
- bucket := a.getBucket(ka.BucketType, bucketIdx)5 H/ Z' m: [3 [. A
- delete(bucket, ka.Addr.String())
- }) \, M% L/ i# l- y7 g7 F
- ka.Buckets = nil( @$ q3 p) I' g4 w
- if ka.BucketType == bucketTypeNew { ^& f( _5 Z3 i1 }
- a.nNew--0 @! u w9 v, n' B% `. w
- } else {' o" w' B( S: G- F. M
- a.nOld--
- }6 A( \6 @, O% l# F/ Y- ^6 j) e3 U
- delete(a.addrLookup, ka.Addr.String())
- }