https://github.com/Bytom/bytom( r' t; b" B, q. L7 Z
本章介绍bytom代码P2P网络中addrbook地址簿
作者使用MacOS操作系统,其他平台也大同小异" L9 Q! z% a0 q8 ]
* K$ V( ^+ {& u8 q
# |1 c5 S. E4 v1 J1 y7 M, u
Golang Version: 1.8# e" F p) m/ l! @, a- i
addrbook介绍3 k# t2 H2 c: _3 O: Y. t- o
addrbook用于存储P2P网络中保留最近的对端节点地址
在MacOS下,默认的地址簿路径存储在~/Library/Bytom/addrbook.json
地址簿格式: `. ~0 {1 y+ j
- ** ~/Library/Bytom/addrbook.json **6 {8 ]' s: K9 L
- {5 M) Y P4 U+ k- D- T5 W; r
- "Key": "359be6d08bc0c6e21c84bbb2",# V. u5 m1 L0 z, P9 b/ z
- "Addrs": [
- {
- "Addr": {
- "IP": "122.224.11.144",) j$ K( P6 A& Y4 ]" ?% I* j
- "Port": 46657+ A; M9 a: Y9 ]
- },9 g7 |, Y6 l3 H4 u( g
- "Src": {) C) V/ m% ^. E7 Q4 B2 Z
- "IP": "198.74.61.131",. c! r' ^1 c9 Q6 B' @2 i: J7 h
- "Port": 46657
- },) d+ `0 k e9 e( M' p5 Y
- "Attempts": 0,9 m: V+ G" Y% M- c3 R2 l
- "LastAttempt": "2018-05-04T12:58:23.894057702+08:00",: d9 X f3 ^( J& Y; x9 C
- "LastSuccess": "0001-01-01T00:00:00Z",7 w/ F! s4 h+ I' W- _9 B! R0 a
- "BucketType": 1,
- "Buckets": [3 b {0 l ]' Q4 o
- 181,
- 10" |( }3 S. O9 P
- ]: X6 h9 J9 `3 `% v5 j! D
- }
- ]
- }
地址类型
在addrbook中存储的地址有两种:( F, d+ ^9 d( E$ S+ l- M
- ** p2p/addrbook.go **! a/ h6 B, x2 W' U; I
- const (
- bucketTypeNew = 0x01 // 标识新地址,不可靠地址(未成功连接过)。只存储在一个bucket中, s" u l3 b" ?3 V) }: ~
- bucketTypeOld = 0x02 // 标识旧地址,可靠地址(已成功连接过)。可以存储在多个bucket中,最多为maxNewBucketsPerAddress个
- )
注意: 一个地址的类型变更不在此文章中做介绍,后期的文章会讨论该问题
地址簿相关结构体
地址簿
- type AddrBook struct {
- cmn.BaseService
- mtx sync.Mutex
- filePath string // 地址簿路径7 ]/ u& P! I+ ]7 \2 Q( j
- routabilityStrict bool // 是否可路由,默认为true
- rand *rand.Rand 6 b- u+ y. c& a3 ~# ~1 a+ ?
- key string // 地址簿标识,用于计算addrNew和addrOld的索引
- ourAddrs map[string]*NetAddress // 存储本地网络地址,用于添加p2p地址时做排除使用3 H/ ]+ ^5 G7 m4 b3 O$ h
- addrLookup map[string]*knownAddress // 存储新、旧地址集,用于查询3 S/ V2 ]- S, D6 V2 S
- addrNew []map[string]*knownAddress // 存储新地址
- addrOld []map[string]*knownAddress // 存储旧地址
- wg sync.WaitGroup) [5 V5 Z. y% p0 r! w
- nOld int // 旧地址数量9 \, _7 k6 }% c/ `6 C
- nNew int // 新地址数量) `' |; R; }4 A# Q) r( g. D+ e
- }
已知地址+ G6 t5 j0 \' q, Z _7 @4 m! _
- type knownAddress struct {
- Addr *NetAddress // 已知peer的addr
- Src *NetAddress // 已知peer的addr的来源addr
- Attempts int32 // 连接peer的重试次数
- LastAttempt time.Time // 最近一次尝试连接的时间9 b6 l. b' l& z2 K& c
- LastSuccess time.Time // 最近一次尝试成功连接的时间
- BucketType byte // 地址的类型(表示可靠地址或不可靠地址)
- Buckets []int // 当前addr所属的buckets
- }
routabilityStrict参数表示地址簿是否存储的ip是否可路由。可路由是根据RFC划分,具体参考资料:RFC标准
初始化地址簿
- // NewAddrBook creates a new address book.
- // Use Start to begin processing asynchronous address updates./ c0 Y4 ?6 u1 K: Z& F
- func NewAddrBook(filePath string, routabilityStrict bool) *AddrBook {/ m/ k* J# `9 w% E- Z
- am := &AddrBook{+ s: T. k: F4 ~9 t
- rand: rand.New(rand.NewSource(time.Now().UnixNano())),- Z5 p7 |* \) B% b5 L' r; }" t
- ourAddrs: make(map[string]*NetAddress),
- addrLookup: make(map[string]*knownAddress),* n" q( |8 {/ F
- filePath: filePath,
- routabilityStrict: routabilityStrict,0 q" J) {4 v: W/ S7 N0 u
- }9 y, R" D* m# h, {& [: ]5 w
- am.init()! u ?" s4 Y' Y
- am.BaseService = *cmn.NewBaseService(nil, "AddrBook", am)
- return am
- }
- // When modifying this, don't forget to update loadFromFile()+ q5 g* w1 ]8 T8 I7 u
- func (a *AddrBook) init() {: c6 a, A3 p' ^: r l! I3 R
- // 地址簿唯一标识
- a.key = crypto.CRandHex(24) // 24/2 * 8 = 96 bits
- // New addr buckets, 默认为256个大小7 o& N% _+ ~6 j6 T5 X
- a.addrNew = make([]map[string]*knownAddress, newBucketCount) k/ D6 B! X& V( ^% B, v5 x
- for i := range a.addrNew {6 `) `" t# ~- x5 B9 E& v
- a.addrNew<i> = make(map[string]*knownAddress)
- }$ \. A+ b0 c7 q$ X @% k0 }
- // Old addr buckets,默认为64个大小- H, N4 x0 ^7 T ?1 m
- a.addrOld = make([]map[string]*knownAddress, oldBucketCount)
- for i := range a.addrOld {
- a.addrOld<i> = make(map[string]*knownAddress)/ m, L+ y, y+ ?/ g
- }
- }</i></i>
bytomd启动时加载本地地址簿! Q4 a2 x7 R" G" t' x$ _% g
loadFromFile在bytomd启动时,首先会加载本地的地址簿, v0 O+ m: A. c' g) H6 J; w
- // OnStart implements Service.
- func (a *AddrBook) OnStart() error {9 n- {( y1 [) E9 b
- a.BaseService.OnStart()* o$ Q+ ?- J8 V C8 _+ i
- a.loadFromFile(a.filePath)
- a.wg.Add(1)) Z' }- X% Q1 ]% R- M
- go a.saveRoutine()3 P! Y. q! x! W
- return nil
- }& ?8 R8 S1 A ^7 M+ A4 P! ^8 B
- // Returns false if file does not exist.
- // cmn.Panics if file is corrupt.: d( e& X+ a( e) p! V
- func (a *AddrBook) loadFromFile(filePath string) bool {" ]" ^9 G7 _* `* q( D2 S
- // If doesn't exist, do nothing.* T5 o. d' D9 _( ~; ~3 n3 h
- // 如果本地地址簿不存在则直接返回
- _, err := os.Stat(filePath)
- if os.IsNotExist(err) {. M5 T8 |& b# h( K# _5 w1 T
- return false! Y9 J/ I: x: C" U8 \# E
- }8 L' Y4 D' L. @- w' A1 f
- // 加载地址簿json内容 k; B; Y4 p: v4 e2 l1 q, n Y
- // Load addrBookJSON{}& m) K6 }! D# o! u7 c* q
- r, err := os.Open(filePath)
- if err != nil {
- cmn.PanicCrisis(cmn.Fmt("Error opening file %s: %v", filePath, err))
- }
- defer r.Close()
- aJSON := &addrBookJSON{}+ ~& C# x4 D+ g9 {+ a% {
- dec := json.NewDecoder(r)- { k# D8 j; {, b$ S1 T4 m4 u
- err = dec.Decode(aJSON)0 R1 r) C; \; [9 b2 v+ e# `. J
- if err != nil {
- cmn.PanicCrisis(cmn.Fmt("Error reading file %s: %v", filePath, err))
- }4 a" b: x e% X' K' T3 `2 I
- // 填充addrNew、addrOld等: H+ O6 ?" N) ?* \9 v) [8 t
- // Restore all the fields...6 ~0 E- u# L6 u$ V* M% i5 D/ W
- // Restore the key
- a.key = aJSON.Key7 P' }: z, \0 x5 @6 g# b
- // Restore .addrNew & .addrOld
- for _, ka := range aJSON.Addrs {1 W% M- B: _" C9 |4 j j8 Q
- for _, bucketIndex := range ka.Buckets {, {. e& ?! `3 [4 z0 J2 b; s
- bucket := a.getBucket(ka.BucketType, bucketIndex)9 f& I) H1 S/ ]. M
- bucket[ka.Addr.String()] = ka2 Y# t. y/ n. G% Z* ^
- }* A7 n1 j3 L+ }4 q
- a.addrLookup[ka.Addr.String()] = ka
- if ka.BucketType == bucketTypeNew {; W( e5 ^7 m9 w+ P* R3 N5 C- b
- a.nNew++
- } else {
- a.nOld++& Q- R7 @' |7 I
- }
- }
- return true/ z; d' m1 K( A* G/ P. o
- }
定时更新地址簿
bytomd会定时更新本地地址簿,默认2分钟一次
- func (a *AddrBook) saveRoutine() {
- dumpAddressTicker := time.NewTicker(dumpAddressInterval)
- out:
- for {7 @ R1 c$ F( m- E) U) N* B
- select {4 N" N; h) R& N2 P: e
- case
添加新地址4 D# }3 N3 a6 ~
当peer之间交换addr时,节点会收到对端节点已知的地址信息,这些信息会被当前节点添加到地址簿中8 s- E7 |8 \0 d! r* \1 c: A. a
- func (a *AddrBook) AddAddress(addr *NetAddress, src *NetAddress) {- B6 `$ n6 Q! b7 z p0 x+ W
- a.mtx.Lock(): M5 i" ]: M! \ j- V0 D
- defer a.mtx.Unlock()
- log.WithFields(log.Fields{
- "addr": addr,! r( B. d: v. x1 \
- "src": src,
- }).Debug("Add address to book"). Y! Y; j4 \6 K! y9 e* x
- a.addAddress(addr, src)
- }
- func (a *AddrBook) addAddress(addr, src *NetAddress) {
- // 验证地址是否为可路由地址5 j& E' d. U& @8 \4 q
- if a.routabilityStrict && !addr.Routable() {
- log.Error(cmn.Fmt("Cannot add non-routable address %v", addr))3 v( P* p P" s/ n% g2 |2 ~
- return
- }
- // 验证地址是否为本地节点地址- ]! _8 @1 P" d; l5 P
- if _, ok := a.ourAddrs[addr.String()]; ok {
- // Ignore our own listener address.
- return% m8 N) {+ R8 N d' x
- }$ J: O7 m: y; z; C9 e7 i3 [' N
- // 验证地址是否存在地址集中
- // 如果存在:则判断该地址是否为old可靠地址、是否超过了最大buckets中。否则根据该地址已经被ka.Buckets引用的个数来随机决定是否添加到地址集中
- // 如果不存在:则添加到地址集中。并标识为bucketTypeNew地址类型
- ka := a.addrLookup[addr.String()]
- if ka != nil {8 D; p; |! p4 {, Z d
- // Already old.
- if ka.isOld() {
- return- `# Q* c$ ^; G, X8 p+ c
- }* n3 J M6 V: o" M( C% g2 m8 J4 l5 [# ~* E
- // Already in max new buckets.- p0 m/ V" k; J1 C) j- [
- if len(ka.Buckets) == maxNewBucketsPerAddress {, j: C. R* _4 e' G0 v
- return
- }& N" o5 ]2 M* k/ I* z) l1 V* @
- // The more entries we have, the less likely we are to add more.# h2 ~. N: M7 r6 o+ p* E$ @
- factor := int32(2 * len(ka.Buckets))
- if a.rand.Int31n(factor) != 0 {
- return- I$ b6 i J) D; i* c+ C* y
- }
- } else {
- ka = newKnownAddress(addr, src)) j3 o. i. G- ]8 @8 H
- }& y4 M% \& K- l0 W, h
- // 找到该地址在地址集的索引位置并添加
- bucket := a.calcNewBucket(addr, src)
- a.addToNewBucket(ka, bucket)
- log.Info("Added new address ", "address:", addr, " total:", a.size())
- }
选择最优节点
地址簿中存储众多地址,在p2p网络中需选择最优的地址去连接 h9 F I' d$ Z$ {5 }
PickAddress(newBias int)函数中newBias是由pex_reactor产生的地址评分。如何计算地址分数在其他章节中再讲
根据地址评分随机选择地址可增加区块链安全性
- // Pick an address to connect to with new/old bias.( y' T$ l9 z( B/ \0 l4 i0 Y) F
- func (a *AddrBook) PickAddress(newBias int) *NetAddress {$ r8 N1 \9 c3 B
- a.mtx.Lock()7 B( m5 E7 E6 }; c6 Y+ I
- defer a.mtx.Unlock()
- if a.size() == 0 {$ h) Q4 F9 ] `$ Z2 s( r
- return nil
- }" x d! Q- r% \, P
- // newBias地址分数限制在0-100分数之间
- if newBias > 100 {
- newBias = 100- \5 E" U- b% ^! ?" k0 q5 {
- }
- if newBias
移除一个地址; T& y t) r( p8 E* z+ S/ W
当一个地址被标记为Bad时则从地址集中移除。目前bytomd的代码版本并未调用过9 F& ^# i8 m; a- k, J
- func (a *AddrBook) MarkBad(addr *NetAddress) {# @! m& d2 }- U9 V9 B- T F
- a.RemoveAddress(addr)
- }
- // RemoveAddress removes the address from the book.
- func (a *AddrBook) RemoveAddress(addr *NetAddress) {
- a.mtx.Lock()
- defer a.mtx.Unlock()0 Z2 Q: U- }8 J/ T
- ka := a.addrLookup[addr.String()]6 x) B3 I2 I( D6 @
- if ka == nil {
- return, S' [( Y5 l# p5 k: M0 C
- }) X' y# `& [' L# }
- log.WithField("addr", addr).Info("Remove address from book")
- a.removeFromAllBuckets(ka)
- }
- func (a *AddrBook) removeFromAllBuckets(ka *knownAddress) {4 v% f2 k' f2 W) A0 O3 I9 [
- for _, bucketIdx := range ka.Buckets {
- bucket := a.getBucket(ka.BucketType, bucketIdx)+ {, X- m* \7 L. b# i- @
- delete(bucket, ka.Addr.String())
- }4 j% X4 q9 m7 h
- ka.Buckets = nil0 G9 j) |, K, r- \* _9 |
- if ka.BucketType == bucketTypeNew {- o) K) N, [# }# D* n( E
- a.nNew--3 W$ F( v3 J% g* h4 H [. w
- } else {
- a.nOld--
- } p) M3 X; a& `
- delete(a.addrLookup, ka.Addr.String())) H. N2 P& [. c) k! v$ v
- }