https://github.com/Bytom/bytom
本章介绍bytom代码P2P网络中addrbook地址簿) I! \4 P% Z: c! [6 G
作者使用MacOS操作系统,其他平台也大同小异9 b; h9 n8 K( ?( F; z: Q3 Q
6 a( v4 w: X% t- e7 T4 a
0 ?/ g! ^: L% R% U5 @ G# t
Golang Version: 1.8
4 n$ J) L- h) a( q) ]
addrbook介绍. j. j7 a( i: H$ Q4 M' Q
addrbook用于存储P2P网络中保留最近的对端节点地址0 q2 Y+ s4 L% Z* d
在MacOS下,默认的地址簿路径存储在~/Library/Bytom/addrbook.json/ i4 T2 j" I5 Q' q) k
地址簿格式, W3 |- f3 l& @, `' |
- ** ~/Library/Bytom/addrbook.json **
- {
- "Key": "359be6d08bc0c6e21c84bbb2",9 |) Y* ^) n+ b& Z( c7 T
- "Addrs": [3 X+ l" g, \& {: F9 E+ M
- {
- "Addr": {0 `+ J8 W& Z [9 D# i
- "IP": "122.224.11.144",) z: ~6 ^' A3 v9 r! Y* t K( z6 x
- "Port": 46657
- },9 |$ s u6 T; G5 Z* @: L
- "Src": {
- "IP": "198.74.61.131",
- "Port": 46657
- },9 q) t5 x. h' x; S
- "Attempts": 0,
- "LastAttempt": "2018-05-04T12:58:23.894057702+08:00",; J. v1 j$ L8 W% {6 H9 |
- "LastSuccess": "0001-01-01T00:00:00Z"," i4 s9 e, S% Y" U
- "BucketType": 1,9 i; Q1 \7 S+ A8 N8 I/ N
- "Buckets": [$ @. z/ r) m9 I5 r" l$ }0 ~9 V
- 181,
- 10
- ] p9 E. E. z/ u
- }+ T3 p' u& s Y0 s1 }
- ]8 b N. s9 ~% e
- }
地址类型$ R' @! G$ d0 T& x3 I4 i- E
在addrbook中存储的地址有两种:
- ** p2p/addrbook.go **/ d/ t! n/ Y, N/ Y0 t X0 t
- const (
- bucketTypeNew = 0x01 // 标识新地址,不可靠地址(未成功连接过)。只存储在一个bucket中
- bucketTypeOld = 0x02 // 标识旧地址,可靠地址(已成功连接过)。可以存储在多个bucket中,最多为maxNewBucketsPerAddress个
- )
注意: 一个地址的类型变更不在此文章中做介绍,后期的文章会讨论该问题% T9 R( j0 ]8 a, {, B5 `; G
地址簿相关结构体7 J0 ?/ R. [ D3 K
地址簿
- type AddrBook struct {* o; h5 ], H7 Z% ? c5 f
- cmn.BaseService# p& P' i$ ~- x: V5 m
- mtx sync.Mutex
- filePath string // 地址簿路径$ p/ Z, u9 l b5 w. O: ^
- routabilityStrict bool // 是否可路由,默认为true
- rand *rand.Rand
- key string // 地址簿标识,用于计算addrNew和addrOld的索引4 N+ s9 o4 ]: m5 t, F3 t* V
- ourAddrs map[string]*NetAddress // 存储本地网络地址,用于添加p2p地址时做排除使用
- addrLookup map[string]*knownAddress // 存储新、旧地址集,用于查询
- addrNew []map[string]*knownAddress // 存储新地址
- addrOld []map[string]*knownAddress // 存储旧地址
- wg sync.WaitGroup
- nOld int // 旧地址数量
- nNew int // 新地址数量' R3 C7 z( f) T( K" T. k4 W
- }
已知地址
- type knownAddress struct {
- Addr *NetAddress // 已知peer的addr* \+ R. W8 `7 }4 h1 D" c
- Src *NetAddress // 已知peer的addr的来源addr( C+ e f* V a9 Y$ v: C
- Attempts int32 // 连接peer的重试次数
- LastAttempt time.Time // 最近一次尝试连接的时间
- LastSuccess time.Time // 最近一次尝试成功连接的时间2 I7 a7 {; v: z" j% r9 _( ]% l, y
- BucketType byte // 地址的类型(表示可靠地址或不可靠地址)
- Buckets []int // 当前addr所属的buckets
- }
routabilityStrict参数表示地址簿是否存储的ip是否可路由。可路由是根据RFC划分,具体参考资料:RFC标准
初始化地址簿1 j% }2 ~+ P$ Q+ Q; F
- // NewAddrBook creates a new address book.6 u. q0 @ P5 s; x) o% s+ K
- // Use Start to begin processing asynchronous address updates. y* D- ]2 l4 p: N P0 ~* D6 U% O. B
- func NewAddrBook(filePath string, routabilityStrict bool) *AddrBook {
- am := &AddrBook{+ i7 y7 t" `5 U
- rand: rand.New(rand.NewSource(time.Now().UnixNano())),7 ~( ~! R) H9 N7 q
- ourAddrs: make(map[string]*NetAddress),/ n* c5 v6 h) t
- addrLookup: make(map[string]*knownAddress),+ {7 w4 u; E# A% H0 d9 w( ^
- filePath: filePath,
- routabilityStrict: routabilityStrict,
- }
- am.init()
- am.BaseService = *cmn.NewBaseService(nil, "AddrBook", am)
- return am
- }
- // When modifying this, don't forget to update loadFromFile()
- func (a *AddrBook) init() {
- // 地址簿唯一标识- L# R9 u" d0 b x) Q
- a.key = crypto.CRandHex(24) // 24/2 * 8 = 96 bits5 W& `0 o$ o. @: X9 N% X0 V$ y
- // New addr buckets, 默认为256个大小7 O) {" z) e& p& C5 p9 R
- a.addrNew = make([]map[string]*knownAddress, newBucketCount)
- for i := range a.addrNew {
- a.addrNew<i> = make(map[string]*knownAddress)0 i x+ B- k7 G) R6 A" b
- }8 s3 Z9 Q8 j& F% P: `' P: a0 x6 k
- // Old addr buckets,默认为64个大小2 E- u4 [3 v% |- j3 c+ A+ I U
- a.addrOld = make([]map[string]*knownAddress, oldBucketCount)
- for i := range a.addrOld {0 d: \$ y1 h- c0 E
- a.addrOld<i> = make(map[string]*knownAddress). H( ]3 o9 ^3 T; C- t
- }9 {! S) z0 U0 q9 b/ @- Y9 Q0 M
- }</i></i>
bytomd启动时加载本地地址簿
loadFromFile在bytomd启动时,首先会加载本地的地址簿$ ?: c5 @% B4 e" U ^" x; `$ {, U
- // OnStart implements Service.
- func (a *AddrBook) OnStart() error {
- a.BaseService.OnStart()
- a.loadFromFile(a.filePath)
- a.wg.Add(1)
- go a.saveRoutine()/ a* [& _. w6 [: i, \$ m3 H
- return nil" \/ {$ v% [, L, }
- }
- // Returns false if file does not exist.
- // cmn.Panics if file is corrupt.# H( i) y# [6 N0 y Z& p( N( i
- func (a *AddrBook) loadFromFile(filePath string) bool {1 a- c6 ~8 x7 R1 a& h
- // If doesn't exist, do nothing.$ x, C: ^0 z6 S' [
- // 如果本地地址簿不存在则直接返回' G& {9 V1 H- S
- _, err := os.Stat(filePath)
- if os.IsNotExist(err) {- B6 C% M+ d3 H6 \
- return false7 J1 N8 D3 S% q4 r( S2 A A* p' t( ?# h
- }
- // 加载地址簿json内容
- // Load addrBookJSON{}: L k' H5 M4 R
- r, err := os.Open(filePath)* ~- s f) q$ o8 \
- if err != nil {
- cmn.PanicCrisis(cmn.Fmt("Error opening file %s: %v", filePath, err))$ J7 Y: z6 [: V+ m! I8 U! I1 l
- }
- defer r.Close()9 N0 y' |' A+ x0 P& Q- h
- aJSON := &addrBookJSON{}
- dec := json.NewDecoder(r)
- err = dec.Decode(aJSON)
- if err != nil {
- cmn.PanicCrisis(cmn.Fmt("Error reading file %s: %v", filePath, err)), g/ @# ]: s- m2 k3 N% l% f
- }9 u; v0 a) W% ]- `+ Z
- // 填充addrNew、addrOld等3 @# L$ C$ c) T* N+ s G
- // Restore all the fields...
- // Restore the key8 ^7 J5 K# v# \9 W2 T
- a.key = aJSON.Key
- // Restore .addrNew & .addrOld ]* j/ f6 \! y8 P3 p6 d
- for _, ka := range aJSON.Addrs {
- for _, bucketIndex := range ka.Buckets {
- bucket := a.getBucket(ka.BucketType, bucketIndex)
- bucket[ka.Addr.String()] = ka
- }
- a.addrLookup[ka.Addr.String()] = ka" _# p6 Z3 |( L: ]- l$ c4 Y
- if ka.BucketType == bucketTypeNew {& _. @% I, P @4 @" Z
- a.nNew++% k& l: K8 w p( I1 _; F
- } else {
- a.nOld++3 o' B5 Y! f% g8 Q& r
- }
- }/ _0 n& w* r0 \6 V) W
- return true7 n4 d6 G+ S$ R6 F5 v& Q1 A
- }
定时更新地址簿
bytomd会定时更新本地地址簿,默认2分钟一次
- func (a *AddrBook) saveRoutine() {
- dumpAddressTicker := time.NewTicker(dumpAddressInterval)" ?2 U" W2 U; O, u* `2 [' `
- out:6 b) S" I( m/ L' H
- for {
- select {, {; a% ~% D7 e& @
- case
添加新地址
当peer之间交换addr时,节点会收到对端节点已知的地址信息,这些信息会被当前节点添加到地址簿中: i9 {6 T( k0 y- l' ]
- func (a *AddrBook) AddAddress(addr *NetAddress, src *NetAddress) {
- a.mtx.Lock()2 d; Z/ g8 c4 Q
- defer a.mtx.Unlock()
- log.WithFields(log.Fields{
- "addr": addr,* j5 n* H8 x6 P1 c* Z
- "src": src," [+ J; v6 B: X, }
- }).Debug("Add address to book")4 p& y( B& d, w- F
- a.addAddress(addr, src)9 ~! ^4 D) }9 d# M
- }
- func (a *AddrBook) addAddress(addr, src *NetAddress) {+ v. T3 m3 s& c" }/ h
- // 验证地址是否为可路由地址
- if a.routabilityStrict && !addr.Routable() {0 N$ D: t1 V% Q: L. ^" C9 D. z3 k
- log.Error(cmn.Fmt("Cannot add non-routable address %v", addr))9 h0 w& C) u& Y3 Q( q
- return
- }9 `" V7 F& S1 r) {
- // 验证地址是否为本地节点地址
- if _, ok := a.ourAddrs[addr.String()]; ok {( `7 D0 t9 N4 h3 s, R
- // Ignore our own listener address.& {7 k- j2 c3 Z& S6 c% |& @2 _' h
- return0 D' m' Z6 ?# V! I. w3 h
- }
- // 验证地址是否存在地址集中4 f) Z( G+ \/ q! V F& G/ w
- // 如果存在:则判断该地址是否为old可靠地址、是否超过了最大buckets中。否则根据该地址已经被ka.Buckets引用的个数来随机决定是否添加到地址集中
- // 如果不存在:则添加到地址集中。并标识为bucketTypeNew地址类型6 N- {6 V9 l& C' X% Y+ c
- ka := a.addrLookup[addr.String()]& X; d. d0 W) J0 Z* @( D+ g7 L
- if ka != nil {8 i. H: A, b: L# b! ?( }0 x
- // Already old.
- if ka.isOld() {7 o% X$ j) P L T
- return7 V# f1 F- g6 _* K8 T' F
- }% ?. c# X0 I0 ^$ I" c* a T
- // Already in max new buckets.: ^9 C; F0 }' [& J
- if len(ka.Buckets) == maxNewBucketsPerAddress {
- return
- }
- // The more entries we have, the less likely we are to add more.. C5 V' N$ q: s! k! [. L
- factor := int32(2 * len(ka.Buckets))
- if a.rand.Int31n(factor) != 0 {
- return
- }
- } else {* {5 }/ b0 I6 Z$ { E7 z8 x/ B' g2 F
- ka = newKnownAddress(addr, src); B3 n- j: w% s) F$ U, ? t' L. u1 C
- }
- // 找到该地址在地址集的索引位置并添加6 ~( R8 R$ i/ o$ b
- bucket := a.calcNewBucket(addr, src)/ L& ?! i7 i5 U0 p% V9 @: y
- a.addToNewBucket(ka, bucket)
- log.Info("Added new address ", "address:", addr, " total:", a.size())
- }
选择最优节点
地址簿中存储众多地址,在p2p网络中需选择最优的地址去连接/ |/ |. J& n& i+ j. ^7 q1 y- F
PickAddress(newBias int)函数中newBias是由pex_reactor产生的地址评分。如何计算地址分数在其他章节中再讲
根据地址评分随机选择地址可增加区块链安全性
- // Pick an address to connect to with new/old bias.% w! i; o) D6 G9 T: I6 o0 h! {
- func (a *AddrBook) PickAddress(newBias int) *NetAddress {
- a.mtx.Lock()
- defer a.mtx.Unlock()
- if a.size() == 0 {0 _' @2 B5 t( B H. t
- return nil
- }
- // newBias地址分数限制在0-100分数之间
- if newBias > 100 {$ D, L% C: F- [
- newBias = 100
- }
- if newBias
移除一个地址# U& d7 O: w. u
当一个地址被标记为Bad时则从地址集中移除。目前bytomd的代码版本并未调用过4 Q4 J- d# O& |% O# X( ?5 e
- func (a *AddrBook) MarkBad(addr *NetAddress) {
- a.RemoveAddress(addr)7 U1 {, a. j- M3 X) b& P6 V
- }
- // RemoveAddress removes the address from the book.: i' [& @! Q. f
- func (a *AddrBook) RemoveAddress(addr *NetAddress) {
- a.mtx.Lock()% Y W. Q& T9 W- y
- defer a.mtx.Unlock()
- ka := a.addrLookup[addr.String()]
- if ka == nil {6 H6 A3 x( c& ^6 P! U
- return
- }
- log.WithField("addr", addr).Info("Remove address from book")' }! H: C' I$ f9 N
- a.removeFromAllBuckets(ka), ?, U- {4 ]1 k u4 F
- }
- func (a *AddrBook) removeFromAllBuckets(ka *knownAddress) {) X& }$ }8 M' |; T' ]$ I5 O4 b
- for _, bucketIdx := range ka.Buckets {+ _. |) j0 o; Q" S+ q8 B6 _
- bucket := a.getBucket(ka.BucketType, bucketIdx)
- delete(bucket, ka.Addr.String())
- }
- ka.Buckets = nil) f H( b3 b' }) @
- if ka.BucketType == bucketTypeNew {6 f+ R( M: ]: e& D
- a.nNew--
- } else {( f5 P; D/ R4 ^- e
- a.nOld--
- }6 z# A u6 d5 x* T- ^: o% S3 E" L
- delete(a.addrLookup, ka.Addr.String())! @% Z( K: R$ z; G$ a' v( h. M
- }