https://github.com/Bytom/bytom! {$ T% R' u s1 C: L) i1 }: w$ l
本章介绍bytom代码P2P网络中addrbook地址簿( i4 V/ d: x% K
' u+ l) [; {7 o/ @
作者使用MacOS操作系统,其他平台也大同小异
Golang Version: 1.82 v" }% N! R$ u1 u
* G; e! e5 {) c. n# n
addrbook介绍; h& u. ^ g A% j0 o( |
addrbook用于存储P2P网络中保留最近的对端节点地址
在MacOS下,默认的地址簿路径存储在~/Library/Bytom/addrbook.json
地址簿格式" K7 \ ^/ c! y
- ** ~/Library/Bytom/addrbook.json **& v7 g9 [. R5 d# U( |
- {1 ^6 V* \- M9 X
- "Key": "359be6d08bc0c6e21c84bbb2",& ?1 k0 m0 M) ]0 k) \4 a
- "Addrs": [
- {: h( x0 c4 W5 M, @: i
- "Addr": {
- "IP": "122.224.11.144",
- "Port": 46657! ~1 Y+ f7 U+ y2 p @
- },
- "Src": {. D _/ m8 x1 n8 c4 P8 S3 o% I4 F
- "IP": "198.74.61.131",$ P9 l; M% u0 Y3 U8 C# n
- "Port": 466575 a" T' I' T5 \1 b# Y8 G
- },' A4 V# j. {! X3 I: _; |
- "Attempts": 0,0 f5 T2 g; n {! T4 u
- "LastAttempt": "2018-05-04T12:58:23.894057702+08:00",
- "LastSuccess": "0001-01-01T00:00:00Z",
- "BucketType": 1,
- "Buckets": [6 d0 U( C- q0 L' v% t# i
- 181,
- 10- ~' `+ v: U& Y' ~/ k3 O+ g
- ]& w" e! Y$ o. Q( _
- }+ L, n& b# T+ ^! H; f5 d8 z$ O
- ]/ ]0 B+ M G0 {* n
- }
地址类型
在addrbook中存储的地址有两种:
- ** p2p/addrbook.go **6 J# `1 j9 V! ~6 c. T
- const (
- bucketTypeNew = 0x01 // 标识新地址,不可靠地址(未成功连接过)。只存储在一个bucket中
- bucketTypeOld = 0x02 // 标识旧地址,可靠地址(已成功连接过)。可以存储在多个bucket中,最多为maxNewBucketsPerAddress个* N; i5 z p, L* m
- )
. q# `4 Z: I" J; {+ Q
注意: 一个地址的类型变更不在此文章中做介绍,后期的文章会讨论该问题2 n: r6 o9 G# k, q4 ^+ u
4 e6 z+ W8 U6 w% `& A" z1 H
地址簿相关结构体0 D1 _) D6 a0 e/ t; v1 i
地址簿1 g5 T) H2 x! ?$ K8 }
- type AddrBook struct {
- cmn.BaseService
- mtx sync.Mutex7 m- B8 Q5 s0 T) J5 x5 H
- filePath string // 地址簿路径' W! A/ A, G8 e( U
- routabilityStrict bool // 是否可路由,默认为true
- rand *rand.Rand 4 K3 d" ?( [# J- y! }: b) }
- key string // 地址簿标识,用于计算addrNew和addrOld的索引
- ourAddrs map[string]*NetAddress // 存储本地网络地址,用于添加p2p地址时做排除使用
- addrLookup map[string]*knownAddress // 存储新、旧地址集,用于查询
- addrNew []map[string]*knownAddress // 存储新地址
- addrOld []map[string]*knownAddress // 存储旧地址- B# b4 Q p$ w$ n
- wg sync.WaitGroup* s: u' l! I& }+ w
- nOld int // 旧地址数量
- nNew int // 新地址数量! _' j9 h4 Z( ~1 k8 U
- }
已知地址" O5 c3 D/ I3 v7 o/ Q) [, B
- type knownAddress struct {2 W) S. A9 T/ A% d' r
- Addr *NetAddress // 已知peer的addr
- Src *NetAddress // 已知peer的addr的来源addr
- Attempts int32 // 连接peer的重试次数
- LastAttempt time.Time // 最近一次尝试连接的时间 I" E3 d/ X5 O: S3 o: l6 C! D( y
- LastSuccess time.Time // 最近一次尝试成功连接的时间
- BucketType byte // 地址的类型(表示可靠地址或不可靠地址)
- Buckets []int // 当前addr所属的buckets
- }
routabilityStrict参数表示地址簿是否存储的ip是否可路由。可路由是根据RFC划分,具体参考资料:RFC标准
初始化地址簿( M$ R+ J( Q1 q" G. `: b( n
- // NewAddrBook creates a new address book.
- // Use Start to begin processing asynchronous address updates.0 b0 N+ x0 g* w% e; w" f) k2 K
- func NewAddrBook(filePath string, routabilityStrict bool) *AddrBook {
- am := &AddrBook{
- rand: rand.New(rand.NewSource(time.Now().UnixNano())),/ L" e' ?. H* T. p
- ourAddrs: make(map[string]*NetAddress),; h9 n/ u( H7 |5 K) O4 x6 a
- addrLookup: make(map[string]*knownAddress),3 R1 }" ?, v6 T# J) W
- filePath: filePath,4 ]! x5 X& F5 F8 q; Q
- routabilityStrict: routabilityStrict,
- }
- am.init(); y/ u. o3 t, V w4 ]$ g, T. c# V
- am.BaseService = *cmn.NewBaseService(nil, "AddrBook", am)
- return am
- }) n& {; p. w8 |$ P7 O8 P: S/ Y2 G
- // When modifying this, don't forget to update loadFromFile()' Y( Q9 ]& j) A# L( i3 k6 Q& D+ p; p
- func (a *AddrBook) init() {
- // 地址簿唯一标识
- a.key = crypto.CRandHex(24) // 24/2 * 8 = 96 bits4 c% K& N" n- x+ N7 P
- // New addr buckets, 默认为256个大小
- a.addrNew = make([]map[string]*knownAddress, newBucketCount)
- for i := range a.addrNew {
- a.addrNew<i> = make(map[string]*knownAddress)
- }
- // Old addr buckets,默认为64个大小
- a.addrOld = make([]map[string]*knownAddress, oldBucketCount)5 B- _, g5 W! B
- for i := range a.addrOld {/ @% E# S; g2 N# z6 o6 F& V
- a.addrOld<i> = make(map[string]*knownAddress)
- }
- }</i></i>
bytomd启动时加载本地地址簿; {. b$ I% w! c& M0 A, ^
loadFromFile在bytomd启动时,首先会加载本地的地址簿# E( n2 c& R( D2 g
- // OnStart implements Service.4 N% V5 W! S5 F1 _
- func (a *AddrBook) OnStart() error {4 g2 q/ |* v) i4 C2 t
- a.BaseService.OnStart()9 _9 J( g" T6 p8 `) |6 |) o
- a.loadFromFile(a.filePath)
- a.wg.Add(1). F& `$ H% j: Z- j1 Q" Y
- go a.saveRoutine()+ \ G: v. o a! M' V e
- return nil& f" S$ N! v( f$ W
- }' a6 l% C" L/ i1 L" T* g
- // Returns false if file does not exist.7 m% i5 @4 b/ }' ]3 q# R o+ {# u
- // cmn.Panics if file is corrupt.
- func (a *AddrBook) loadFromFile(filePath string) bool {
- // If doesn't exist, do nothing.
- // 如果本地地址簿不存在则直接返回1 B% z2 _) E8 e7 ]: V, h6 o# k
- _, err := os.Stat(filePath)2 u+ P4 q* ~: R) v4 e, g
- if os.IsNotExist(err) {! s m0 b4 ?8 k9 W( g; S
- return false1 x4 f8 i) [: @
- }% R1 r5 E% F! i# h
- // 加载地址簿json内容* u6 L. o& u- K& k# ^! @2 V. ]
- // Load addrBookJSON{}9 \& _, r) f8 [& P6 g
- r, err := os.Open(filePath)
- if err != nil {, ] A9 ~# X) ^+ Y- _: Q
- cmn.PanicCrisis(cmn.Fmt("Error opening file %s: %v", filePath, err))
- }0 A$ e, w* E0 I0 y+ H4 \* E
- defer r.Close()
- aJSON := &addrBookJSON{}3 m. N& q, g" l
- dec := json.NewDecoder(r)2 s) e* U) z: o! s/ n, Q
- err = dec.Decode(aJSON)
- if err != nil {
- cmn.PanicCrisis(cmn.Fmt("Error reading file %s: %v", filePath, err)); x7 ^7 K$ m. ~ Q; v/ Z4 Q
- }
- // 填充addrNew、addrOld等0 V4 U8 b! B2 I' u8 S2 }# {8 n* D
- // Restore all the fields...$ l g& k% B$ X6 `- |! V
- // Restore the key, d \; ~) P% e8 Z2 V8 \
- a.key = aJSON.Key
- // Restore .addrNew & .addrOld
- for _, ka := range aJSON.Addrs {) }1 l9 n" |) D; `; j# z& M
- for _, bucketIndex := range ka.Buckets {
- bucket := a.getBucket(ka.BucketType, bucketIndex)) A% M# q$ {' }, ~% i( O4 ?
- bucket[ka.Addr.String()] = ka
- }# K } A: U, {' p* @' U
- a.addrLookup[ka.Addr.String()] = ka8 h! |& R! Z2 Z/ c4 q2 } W
- if ka.BucketType == bucketTypeNew {
- a.nNew++
- } else {0 j/ M: k& g5 z" J# ~" I) |
- a.nOld+++ T& v7 g4 d0 B' Y, p" _
- }, I6 D$ L. Z; e4 _5 f, Y) d
- }' i) j7 e' o7 }: ]: A% w
- return true
- }
定时更新地址簿
bytomd会定时更新本地地址簿,默认2分钟一次
- func (a *AddrBook) saveRoutine() {
- dumpAddressTicker := time.NewTicker(dumpAddressInterval)$ U3 p6 M$ R' ?2 ~ w8 _; a
- out: C2 H" m# O$ ]
- for {
- select {: ]. v3 H; U' w6 y8 N7 V d: h, I R
- case
添加新地址
当peer之间交换addr时,节点会收到对端节点已知的地址信息,这些信息会被当前节点添加到地址簿中6 Q( z8 C3 D i1 u2 B
- func (a *AddrBook) AddAddress(addr *NetAddress, src *NetAddress) {# t5 K3 a0 O& }9 I: f2 J
- a.mtx.Lock()% y6 L% Z9 R$ }1 L
- defer a.mtx.Unlock()7 I |" O1 K/ s0 J5 e* m
- log.WithFields(log.Fields{9 x5 B- m7 q: }6 n) i3 s
- "addr": addr,
- "src": src,
- }).Debug("Add address to book")
- a.addAddress(addr, src)
- }
- func (a *AddrBook) addAddress(addr, src *NetAddress) {( G/ c4 W" r% G9 b3 j
- // 验证地址是否为可路由地址) `! ~3 C: k6 Z6 Y( ?3 h$ V
- if a.routabilityStrict && !addr.Routable() { ?4 @" `% f5 a. ?
- log.Error(cmn.Fmt("Cannot add non-routable address %v", addr))
- return
- }, |, t2 F" k' C9 K& y. P6 }4 o F
- // 验证地址是否为本地节点地址8 e3 G' p/ Q% d
- if _, ok := a.ourAddrs[addr.String()]; ok {
- // Ignore our own listener address.
- return
- }, g5 q1 v$ k) v+ \) A
- // 验证地址是否存在地址集中
- // 如果存在:则判断该地址是否为old可靠地址、是否超过了最大buckets中。否则根据该地址已经被ka.Buckets引用的个数来随机决定是否添加到地址集中
- // 如果不存在:则添加到地址集中。并标识为bucketTypeNew地址类型
- ka := a.addrLookup[addr.String()]; ?) c$ u) l2 C k, h0 H. v
- if ka != nil {
- // Already old.+ Z9 Q/ u" ^3 q
- if ka.isOld() {, n8 }6 v. t3 J6 C6 l
- return/ Z; Z4 `7 E: `; x+ e
- }- D$ R$ g3 F% n/ X
- // Already in max new buckets.
- if len(ka.Buckets) == maxNewBucketsPerAddress {/ k( Q3 b0 z; D9 _& L4 h
- return
- }
- // The more entries we have, the less likely we are to add more.# p7 q, n& l4 ?( x. j3 ]
- factor := int32(2 * len(ka.Buckets))
- if a.rand.Int31n(factor) != 0 {
- return
- }1 b7 u# l0 ?9 i& _: K! [1 {
- } else {* o j/ E8 Y& P5 |5 t
- ka = newKnownAddress(addr, src)& N8 p7 `7 f3 N
- }/ N! B% ^2 J% \6 A& }6 X3 X
- // 找到该地址在地址集的索引位置并添加) V# U: N: W X8 s. X" q2 I% c+ _
- bucket := a.calcNewBucket(addr, src)
- a.addToNewBucket(ka, bucket)2 q3 I% e2 l, Q6 z6 q
- log.Info("Added new address ", "address:", addr, " total:", a.size())# W* t& Q" e5 L3 ]4 i
- }
选择最优节点
地址簿中存储众多地址,在p2p网络中需选择最优的地址去连接
PickAddress(newBias int)函数中newBias是由pex_reactor产生的地址评分。如何计算地址分数在其他章节中再讲$ q$ p* o" V3 x7 I! z+ R& }+ w
根据地址评分随机选择地址可增加区块链安全性- Z! @/ y3 b. m4 X3 P; \2 l
- // Pick an address to connect to with new/old bias.
- func (a *AddrBook) PickAddress(newBias int) *NetAddress {
- a.mtx.Lock()0 W3 K8 f O+ ~- f1 \$ h. }
- defer a.mtx.Unlock()+ A: S O1 x2 w. p+ C
- if a.size() == 0 {
- return nil' \4 ?+ u1 Q z& ]7 l+ d0 v9 F
- }% \3 q+ _4 b$ t) V
- // newBias地址分数限制在0-100分数之间
- if newBias > 100 {
- newBias = 1006 b3 R, v7 W8 d" u9 |# P" ?
- }: p. V$ d5 U, f& ^0 ~3 p- o# M
- if newBias
移除一个地址
当一个地址被标记为Bad时则从地址集中移除。目前bytomd的代码版本并未调用过
- func (a *AddrBook) MarkBad(addr *NetAddress) {
- a.RemoveAddress(addr): s7 i& F- T% @) L0 j6 X* x4 x
- }
- // RemoveAddress removes the address from the book.
- func (a *AddrBook) RemoveAddress(addr *NetAddress) {' b# k) _7 I, @" x
- a.mtx.Lock()0 Q1 d) ]- N# H7 E
- defer a.mtx.Unlock(): I9 }- v! D) x3 m
- ka := a.addrLookup[addr.String()]
- if ka == nil {* G" Q$ G; R, j1 r3 f- q3 [! _+ @
- return. L, `; T( t/ v+ W+ j* t# ^
- }# T/ N6 Q' y1 K
- log.WithField("addr", addr).Info("Remove address from book")
- a.removeFromAllBuckets(ka)
- }
- func (a *AddrBook) removeFromAllBuckets(ka *knownAddress) {
- for _, bucketIdx := range ka.Buckets {
- bucket := a.getBucket(ka.BucketType, bucketIdx)
- delete(bucket, ka.Addr.String())
- }
- ka.Buckets = nil/ |' d' i- }: Z6 ? l7 N) `
- if ka.BucketType == bucketTypeNew {$ l( t( K, l1 N4 G$ @! m& g
- a.nNew--9 M/ U6 s, j- D; y: G8 f
- } else {1 b0 r4 e% u1 s( O
- a.nOld--
- }
- delete(a.addrLookup, ka.Addr.String())
- }