https://github.com/Bytom/bytom s& {8 O/ v0 O8 K8 b
本章介绍bytom代码P2P网络中addrbook地址簿" [! x, r7 j6 T0 T. g; @
作者使用MacOS操作系统,其他平台也大同小异! v. C# J/ p# q' R% C
Golang Version: 1.86 @: h L! l+ K; ]
addrbook介绍4 i! u! l: e" D- w' L
addrbook用于存储P2P网络中保留最近的对端节点地址( Z" g2 G$ B' |# C5 [
在MacOS下,默认的地址簿路径存储在~/Library/Bytom/addrbook.json( X- [5 J) F7 P' r2 |' p* \& @. t
地址簿格式
- ** ~/Library/Bytom/addrbook.json **
- {
- "Key": "359be6d08bc0c6e21c84bbb2",
- "Addrs": [- t/ V: x: J/ N3 K5 l
- {
- "Addr": {% p# p/ c# \2 H& _6 S$ f5 _6 |
- "IP": "122.224.11.144",/ N' }0 ]% H7 K: @" c M/ i$ i3 h
- "Port": 466574 A Q* ? o& M) a" J3 F" `" d
- },
- "Src": {5 a( ]2 @# T) W- Y& Y, I
- "IP": "198.74.61.131",0 Y/ q/ z7 O4 O' p5 Q- O( X
- "Port": 46657
- },0 |* ^$ S T" v) v
- "Attempts": 0,3 o4 }! g* H! s1 m _, q
- "LastAttempt": "2018-05-04T12:58:23.894057702+08:00",8 y* X* ]* L0 N: c
- "LastSuccess": "0001-01-01T00:00:00Z",
- "BucketType": 1,2 M8 V' Y" k8 E2 s
- "Buckets": [
- 181,
- 10
- ]3 ~! b' u, Z, B" Q
- }
- ]0 q% y# I6 E7 [% T5 ]
- }
地址类型+ _ \3 I t2 a) ]5 y
在addrbook中存储的地址有两种:
- ** p2p/addrbook.go **
- const (, I% u2 `! r) F( ~
- bucketTypeNew = 0x01 // 标识新地址,不可靠地址(未成功连接过)。只存储在一个bucket中' Q W, x, B' A0 ]
- bucketTypeOld = 0x02 // 标识旧地址,可靠地址(已成功连接过)。可以存储在多个bucket中,最多为maxNewBucketsPerAddress个' N( C, d+ b) q* k6 D p: ^6 B+ D( G
- )
注意: 一个地址的类型变更不在此文章中做介绍,后期的文章会讨论该问题
- ]) H9 ?" E7 {( u; W% `/ l; ]
地址簿相关结构体
地址簿& I8 [ S: Z" U" `* I0 `& d. T
- type AddrBook struct {; s' L4 L3 r2 U0 Z; x
- cmn.BaseService
- mtx sync.Mutex8 m7 H' Z" R, N" M& y3 i2 H
- filePath string // 地址簿路径
- routabilityStrict bool // 是否可路由,默认为true8 A$ O- W! L/ ~0 ^3 z& T/ ^
- rand *rand.Rand
- key string // 地址簿标识,用于计算addrNew和addrOld的索引! v1 B8 K" `- z$ z% L' m
- ourAddrs map[string]*NetAddress // 存储本地网络地址,用于添加p2p地址时做排除使用
- addrLookup map[string]*knownAddress // 存储新、旧地址集,用于查询) i- M: l$ n+ W( X. i
- addrNew []map[string]*knownAddress // 存储新地址" I! U9 O/ S! T& F
- addrOld []map[string]*knownAddress // 存储旧地址 V3 L' u& H. y
- wg sync.WaitGroup
- nOld int // 旧地址数量
- nNew int // 新地址数量
- }
已知地址
- type knownAddress struct {
- Addr *NetAddress // 已知peer的addr
- Src *NetAddress // 已知peer的addr的来源addr
- Attempts int32 // 连接peer的重试次数
- LastAttempt time.Time // 最近一次尝试连接的时间0 \( D; W0 N+ k9 V) j
- LastSuccess time.Time // 最近一次尝试成功连接的时间, |% j1 e/ A% | x+ Z3 U
- BucketType byte // 地址的类型(表示可靠地址或不可靠地址)
- Buckets []int // 当前addr所属的buckets
- }
routabilityStrict参数表示地址簿是否存储的ip是否可路由。可路由是根据RFC划分,具体参考资料:RFC标准5 S- m; _% B2 s v% J6 t
初始化地址簿
- // NewAddrBook creates a new address book.* W& p* p* D+ w. P. C" \
- // Use Start to begin processing asynchronous address updates.) o( e! {& ~0 }7 Q5 B0 I; C/ h
- func NewAddrBook(filePath string, routabilityStrict bool) *AddrBook {
- am := &AddrBook{7 o$ C% N4 {6 @5 w& V2 H/ O" j
- rand: rand.New(rand.NewSource(time.Now().UnixNano())),
- ourAddrs: make(map[string]*NetAddress),
- addrLookup: make(map[string]*knownAddress),6 k& z3 ]% C% k: n+ D: t
- filePath: filePath,
- routabilityStrict: routabilityStrict,
- }1 j3 n3 H3 s! z8 z2 x
- am.init(); V* J: A% A( s* V( ]/ ~0 w" J" T
- am.BaseService = *cmn.NewBaseService(nil, "AddrBook", am)) ^) q7 N" u, H! W3 I, D8 P9 c; w
- return am5 J3 q. t* h8 G4 F
- }
- // When modifying this, don't forget to update loadFromFile()
- func (a *AddrBook) init() {) I L# O& H4 @1 u Q
- // 地址簿唯一标识
- a.key = crypto.CRandHex(24) // 24/2 * 8 = 96 bits6 q2 S; u1 K# }* w: N% w# T
- // New addr buckets, 默认为256个大小
- a.addrNew = make([]map[string]*knownAddress, newBucketCount)5 @, f7 g5 V/ q9 s t
- for i := range a.addrNew {7 H5 O/ @9 \3 D* |: Y' S
- a.addrNew<i> = make(map[string]*knownAddress)
- }
- // Old addr buckets,默认为64个大小
- a.addrOld = make([]map[string]*knownAddress, oldBucketCount)$ g$ U+ D1 z* t. k2 _+ y/ n
- for i := range a.addrOld {6 i8 Q7 j1 \7 U1 y# u# v5 p
- a.addrOld<i> = make(map[string]*knownAddress)# I* S) c y$ g& k
- }5 ~9 }6 P0 h) |( [* W+ T
- }</i></i>
bytomd启动时加载本地地址簿1 M' d' A( n, R8 a4 o, W, X
loadFromFile在bytomd启动时,首先会加载本地的地址簿0 B8 c; ~1 W0 U: W
- // OnStart implements Service.( ~/ o" Y, y8 e. F) q8 ^, D- W
- func (a *AddrBook) OnStart() error {$ p% g$ |- Q2 w+ h
- a.BaseService.OnStart()( A* R( o) h: [/ R* c
- a.loadFromFile(a.filePath)+ r. O% A; i8 E- a
- a.wg.Add(1)
- go a.saveRoutine()1 y' ]: i( m5 J6 u& a2 s
- return nil$ B, {- Q" r0 F) F" g8 l# s5 G! V
- }
- // Returns false if file does not exist.* V3 {: [8 a9 y: ~) W1 F
- // cmn.Panics if file is corrupt.: a9 D! C7 m: P3 ] Z
- func (a *AddrBook) loadFromFile(filePath string) bool {
- // If doesn't exist, do nothing.4 r, c% \; k9 w
- // 如果本地地址簿不存在则直接返回! T3 k. i! q* E9 W: ^; C, d3 J
- _, err := os.Stat(filePath)
- if os.IsNotExist(err) {# z# H* Q& T' I0 R) M w7 u1 Y" F0 {
- return false+ U! k# J/ i9 O2 o( I
- }
- // 加载地址簿json内容% |9 _6 W& D, H) I& X& h
- // Load addrBookJSON{}
- r, err := os.Open(filePath)! h- z3 u: O) u9 x5 f8 q* ]0 f
- if err != nil {+ f9 B* {" o" C8 O* T5 r
- cmn.PanicCrisis(cmn.Fmt("Error opening file %s: %v", filePath, err))
- }& d: ^3 D' }5 | [! V
- defer r.Close()
- aJSON := &addrBookJSON{}) `# Z9 i4 T1 K1 [$ _5 ^
- dec := json.NewDecoder(r)0 Q! d; u! t$ b9 W: y0 Z- {
- err = dec.Decode(aJSON)1 F& Y1 K: c5 K' Q. p) A; y; r- n) o
- if err != nil {
- cmn.PanicCrisis(cmn.Fmt("Error reading file %s: %v", filePath, err))6 G# b) ~% e& X( x1 J4 p
- }
- // 填充addrNew、addrOld等4 K7 [5 c. v/ i: ?/ \2 R7 ?7 \$ F
- // Restore all the fields...
- // Restore the key; s4 G: m9 ?" B( d8 s# X8 `* j) z
- a.key = aJSON.Key
- // Restore .addrNew & .addrOld
- for _, ka := range aJSON.Addrs {: u4 a7 v; y& Y: B7 I
- for _, bucketIndex := range ka.Buckets {
- bucket := a.getBucket(ka.BucketType, bucketIndex)
- bucket[ka.Addr.String()] = ka
- }
- a.addrLookup[ka.Addr.String()] = ka+ u: @1 g, g6 N+ j& f n8 I
- if ka.BucketType == bucketTypeNew {
- a.nNew++; t/ q4 t6 s" a: `/ Q
- } else {" r# J4 b* f: q- q
- a.nOld++: q- k# N' G- N, u, Y
- } ?7 d* e2 ]; p x
- }
- return true9 A; Q) }& G' }2 z6 v/ i1 W
- }
定时更新地址簿
bytomd会定时更新本地地址簿,默认2分钟一次) y9 X0 j! {6 ]* W( P- h
- func (a *AddrBook) saveRoutine() {" a/ q# J7 |: Z/ I
- dumpAddressTicker := time.NewTicker(dumpAddressInterval)
- out:
- for {
- select {
- case
添加新地址: E' K- i* R1 ?8 v N4 F
当peer之间交换addr时,节点会收到对端节点已知的地址信息,这些信息会被当前节点添加到地址簿中
- func (a *AddrBook) AddAddress(addr *NetAddress, src *NetAddress) {
- a.mtx.Lock()% W7 A, p9 A3 b |9 [5 S
- defer a.mtx.Unlock()
- log.WithFields(log.Fields{
- "addr": addr,
- "src": src,6 [9 P4 `# W3 M& t/ X
- }).Debug("Add address to book")9 w" g& r$ y* R/ f. Q
- a.addAddress(addr, src)# e3 m' P/ ]: H
- }9 t( ~* z% ^4 \" |3 {; X1 h2 X2 m
- func (a *AddrBook) addAddress(addr, src *NetAddress) {1 S0 c( C8 m$ F) O3 [, s+ b2 @
- // 验证地址是否为可路由地址
- if a.routabilityStrict && !addr.Routable() {. D q, C- m3 x6 ^6 A* Z
- log.Error(cmn.Fmt("Cannot add non-routable address %v", addr))6 Y# {1 g3 i. L: ], X
- return
- }
- // 验证地址是否为本地节点地址" C: e1 A, H: _8 w* u
- if _, ok := a.ourAddrs[addr.String()]; ok {
- // Ignore our own listener address.
- return: g+ _) ]; I" ]9 D! @; ^
- }
- // 验证地址是否存在地址集中! s% @% n: C" H0 p9 ~4 T) R" l
- // 如果存在:则判断该地址是否为old可靠地址、是否超过了最大buckets中。否则根据该地址已经被ka.Buckets引用的个数来随机决定是否添加到地址集中) K( l/ v8 N% g$ }6 k. r# c
- // 如果不存在:则添加到地址集中。并标识为bucketTypeNew地址类型$ X6 u$ v2 i0 @. H6 C
- ka := a.addrLookup[addr.String()]; t1 f3 Z- M3 `: w0 Q# S2 e6 ^' u
- if ka != nil {+ _* z# x( z+ S; j& g7 T+ s) c4 I
- // Already old.) Z* L7 l" i2 a7 U* W6 T1 r
- if ka.isOld() {9 D q4 x* N3 ?9 L8 P; y+ y
- return [! V1 b: d& P( u* }# b
- }
- // Already in max new buckets.
- if len(ka.Buckets) == maxNewBucketsPerAddress {4 q9 y$ W* Y- G! ]- Z
- return+ x' ]: a- R( S1 ~1 Y
- }' L+ s! M( F8 f, I( c2 G
- // 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 {9 _+ F3 G! y- u% y }% Z
- return
- }! ]0 p% a! I& L) i8 B
- } else {
- ka = newKnownAddress(addr, src), _7 c. ^6 L$ ^2 U, [
- }# D' r% J x1 c8 g. H4 `% y; J4 C
- // 找到该地址在地址集的索引位置并添加. }& _- Z+ u- s5 ]( P* a$ N& z
- bucket := a.calcNewBucket(addr, src)
- a.addToNewBucket(ka, bucket)' j) E `; T+ ?; q* c
- log.Info("Added new address ", "address:", addr, " total:", a.size())
- }
选择最优节点0 l" m) m& j! _& ?
地址簿中存储众多地址,在p2p网络中需选择最优的地址去连接8 J# T: C1 L) y% L! g7 s
PickAddress(newBias int)函数中newBias是由pex_reactor产生的地址评分。如何计算地址分数在其他章节中再讲/ h8 D0 j+ X" G4 c, f( `
根据地址评分随机选择地址可增加区块链安全性- O- G+ `/ @ R7 @6 C6 g
- // Pick an address to connect to with new/old bias.6 R; F8 v! ]+ o# |$ p5 ^
- func (a *AddrBook) PickAddress(newBias int) *NetAddress {$ K3 J( [' V) T; O
- a.mtx.Lock()) r7 c! Y/ \. s p
- defer a.mtx.Unlock()+ I" o9 N6 u# X) p) u% ` {
- if a.size() == 0 {! N# y/ H# y* D: e! S
- return nil
- }4 K& ~+ V( d; p+ P4 j4 v& F
- // newBias地址分数限制在0-100分数之间+ V3 ]1 d1 \/ {+ S; w
- if newBias > 100 {$ B6 `. \8 ?7 K8 s7 B$ \- M( t
- newBias = 100
- }
- if newBias
移除一个地址
当一个地址被标记为Bad时则从地址集中移除。目前bytomd的代码版本并未调用过' ~8 P; K+ Q& R# K8 r
- func (a *AddrBook) MarkBad(addr *NetAddress) {1 s5 |. x( v* ? [6 W
- a.RemoveAddress(addr), u5 k- d/ F9 Z: H( y; }5 ^( k- w
- }
- // RemoveAddress removes the address from the book.) I8 r- r" s. D6 l$ ~8 R
- func (a *AddrBook) RemoveAddress(addr *NetAddress) {- a. Q7 D& h; g) M% P& B" i
- a.mtx.Lock()/ S/ |6 b5 [ P; _/ C
- defer a.mtx.Unlock()
- ka := a.addrLookup[addr.String()]
- if ka == nil {' X& P- u8 i1 Q `' Q9 T$ g' e& L
- return
- }$ F% A/ v! |2 u4 P5 a/ C# [7 D
- log.WithField("addr", addr).Info("Remove address from book"); B$ j C6 Y- J
- a.removeFromAllBuckets(ka)7 x' i, w9 z, n% A
- }
- func (a *AddrBook) removeFromAllBuckets(ka *knownAddress) {8 }7 ]* i: ~& ^0 J2 h
- for _, bucketIdx := range ka.Buckets {
- bucket := a.getBucket(ka.BucketType, bucketIdx)
- delete(bucket, ka.Addr.String())
- }
- ka.Buckets = nil
- if ka.BucketType == bucketTypeNew {
- a.nNew--. ~( X5 ^. s! T+ i7 x5 N
- } else {- [" k- |- G6 k. p
- a.nOld--
- }
- delete(a.addrLookup, ka.Addr.String())6 x2 x! L2 O" c* @- B2 b
- }