https://github.com/Bytom/bytom+ X6 }0 J+ X6 X% ~' r
本章介绍bytom代码P2P网络中addrbook地址簿% W7 b/ @3 V7 w; K: J
作者使用MacOS操作系统,其他平台也大同小异: h* l0 K0 q7 K* W( j' a
# c7 A& i0 I' G$ ?2 v- W
Golang Version: 1.8- g* I5 R- C. a2 k3 g, ?
8 V$ n2 k9 P8 {8 C
addrbook介绍
addrbook用于存储P2P网络中保留最近的对端节点地址. [4 \ {1 Q. I' V; h: D
在MacOS下,默认的地址簿路径存储在~/Library/Bytom/addrbook.json
地址簿格式8 `8 d( Z+ | I1 P2 S0 N( h
- ** ~/Library/Bytom/addrbook.json **) I8 e$ c4 ~ V' L6 B5 c7 q
- {
- "Key": "359be6d08bc0c6e21c84bbb2",
- "Addrs": [4 ]1 d7 t! F1 Q: t4 |$ F/ l
- {0 @/ d; N2 t3 w- w, Q
- "Addr": {8 ^9 ^+ z6 l! B, ~; x7 F
- "IP": "122.224.11.144",% d$ }" v @7 B
- "Port": 46657
- },3 M; a; t! h) g
- "Src": {* C% O. E+ d6 C8 j X+ W4 {
- "IP": "198.74.61.131",
- "Port": 46657
- },3 P7 W6 B/ j7 m# f. z9 `
- "Attempts": 0, {. E4 d$ Q7 ?- r% _
- "LastAttempt": "2018-05-04T12:58:23.894057702+08:00",9 q8 } _, A) O! {9 T. _
- "LastSuccess": "0001-01-01T00:00:00Z",0 c, h7 [6 I8 r% B
- "BucketType": 1,4 _4 E2 n9 ?' q% T7 h( `
- "Buckets": [8 |$ ^; d. I! L3 w6 n, i
- 181,# Q& ~2 x- B" v' t8 }6 M- g
- 10" O+ a9 a3 r5 g' T
- ]
- }5 I2 F; ~5 n4 M8 o: l+ s/ H* G5 v
- ], y7 ` x P( X6 q
- }
地址类型# @/ {6 \ E; t8 X+ g/ l% \
在addrbook中存储的地址有两种:# u8 K. j. C1 |
- ** p2p/addrbook.go **
- const (
- bucketTypeNew = 0x01 // 标识新地址,不可靠地址(未成功连接过)。只存储在一个bucket中
- bucketTypeOld = 0x02 // 标识旧地址,可靠地址(已成功连接过)。可以存储在多个bucket中,最多为maxNewBucketsPerAddress个6 E" c, Y y& s9 g+ o1 N
- )
, _4 K" t' ~# A* w1 k: F/ E
注意: 一个地址的类型变更不在此文章中做介绍,后期的文章会讨论该问题% J3 K' t" N- l+ m& ?* ?) \
4 C, Q" s) R0 |0 t1 I( q
地址簿相关结构体& G( h) U) k1 ^2 d
地址簿
- type AddrBook struct {
- cmn.BaseService% Y/ U0 N9 l/ c. e
- mtx sync.Mutex: k% w6 p; u# f9 ]8 m1 N1 z& ~
- filePath string // 地址簿路径1 P, [; M! S& y$ O' E
- routabilityStrict bool // 是否可路由,默认为true
- rand *rand.Rand
- key string // 地址簿标识,用于计算addrNew和addrOld的索引& d9 Q9 k2 V/ u, e; R
- ourAddrs map[string]*NetAddress // 存储本地网络地址,用于添加p2p地址时做排除使用
- addrLookup map[string]*knownAddress // 存储新、旧地址集,用于查询7 F3 l4 o' w5 Y' G4 ~. X1 G+ `
- addrNew []map[string]*knownAddress // 存储新地址, D- k( S, G% V4 ^
- addrOld []map[string]*knownAddress // 存储旧地址. o" W' Y& C, Z" {6 V' B; @: f
- wg sync.WaitGroup7 l: | ?) u# j7 T( U$ A" ]
- nOld int // 旧地址数量
- nNew int // 新地址数量' X+ q3 `) g. D, i8 j
- }
已知地址
- type knownAddress struct {
- Addr *NetAddress // 已知peer的addr$ `+ W4 l6 A1 V& i0 L
- Src *NetAddress // 已知peer的addr的来源addr+ j+ l* u/ j6 P5 s0 D
- Attempts int32 // 连接peer的重试次数
- LastAttempt time.Time // 最近一次尝试连接的时间# m0 ?# Q" d- p' h C6 [
- LastSuccess time.Time // 最近一次尝试成功连接的时间! [6 L7 g. X- a# i |/ F( A
- BucketType byte // 地址的类型(表示可靠地址或不可靠地址) U5 e5 W$ v) R }" J: }, ^
- Buckets []int // 当前addr所属的buckets
- }
routabilityStrict参数表示地址簿是否存储的ip是否可路由。可路由是根据RFC划分,具体参考资料:RFC标准
初始化地址簿0 d! J1 r' F5 ^3 b! |2 j( [5 i6 P
- // NewAddrBook creates a new address book.
- // Use Start to begin processing asynchronous address updates.& @) v8 L; v/ M
- func NewAddrBook(filePath string, routabilityStrict bool) *AddrBook {' k3 N& F% G; R& E# |+ y3 d
- am := &AddrBook{4 v0 k5 `+ \7 `1 h; T9 b
- rand: rand.New(rand.NewSource(time.Now().UnixNano())),0 `1 u% h. W7 t& I6 h% G9 B5 ?
- ourAddrs: make(map[string]*NetAddress),, _' p7 a6 H7 P
- addrLookup: make(map[string]*knownAddress),
- filePath: filePath,5 b% G- z9 G- G
- routabilityStrict: routabilityStrict,9 ?' o& c3 P! h& c$ I4 p! X
- }9 J0 u( Y/ }; F1 w! j' R$ e
- am.init()
- am.BaseService = *cmn.NewBaseService(nil, "AddrBook", am)+ W. k2 S; f! R T# ]
- return am- ^3 [, s& W8 `/ K$ F5 v
- }
- // When modifying this, don't forget to update loadFromFile()
- func (a *AddrBook) init() {
- // 地址簿唯一标识7 F# r* a8 b, [5 e! b5 w. ]
- a.key = crypto.CRandHex(24) // 24/2 * 8 = 96 bits, f+ j0 @0 L: c: \( t: A2 I
- // New addr buckets, 默认为256个大小+ Y# Z: Q7 F) t6 z5 r
- a.addrNew = make([]map[string]*knownAddress, newBucketCount)
- for i := range a.addrNew {
- a.addrNew<i> = make(map[string]*knownAddress)" Q: H8 q, l: v# R( @$ G2 A' z9 g
- }2 W& ]' D, @9 @/ Q, b/ j
- // Old addr buckets,默认为64个大小
- a.addrOld = make([]map[string]*knownAddress, oldBucketCount)8 W( h- `1 i$ h# I6 {
- for i := range a.addrOld {
- a.addrOld<i> = make(map[string]*knownAddress)
- }
- }</i></i>
bytomd启动时加载本地地址簿
loadFromFile在bytomd启动时,首先会加载本地的地址簿! F0 G7 ~& F" S
- // OnStart implements Service.9 D2 Y0 K/ C4 i( S+ [
- func (a *AddrBook) OnStart() error {9 z. K, i! U. f9 Y: [
- a.BaseService.OnStart()7 Z& z4 k4 k1 h! J# Y
- a.loadFromFile(a.filePath)- L9 x3 c: f" I! L: j
- a.wg.Add(1)! x2 Q* }4 G3 P3 B/ U
- go a.saveRoutine()' Z# y$ x1 M4 L7 U
- return nil
- }8 }, v% F+ ^) V8 ~$ Y0 ?
- // Returns false if file does not exist.* @, F/ ~4 I- r, E: n8 C
- // cmn.Panics if file is corrupt.8 b# F' O$ [0 S% w* w$ L
- func (a *AddrBook) loadFromFile(filePath string) bool {" A3 u/ s6 i$ u, e
- // If doesn't exist, do nothing.
- // 如果本地地址簿不存在则直接返回4 r; d0 p8 d5 [) l( _
- _, err := os.Stat(filePath)% i, T3 a" K& e% V2 v
- if os.IsNotExist(err) {
- return false
- }
- // 加载地址簿json内容
- // Load addrBookJSON{}5 y) U8 s1 k9 A; `" ~5 p3 R
- r, err := os.Open(filePath)5 _; t5 O2 s2 |0 w6 H- o" ?
- if err != nil {( o+ o) L5 @2 o1 b$ q* L D3 C
- cmn.PanicCrisis(cmn.Fmt("Error opening file %s: %v", filePath, err))
- }1 w- @/ i4 I! F; ?
- defer r.Close()5 c% u) _( z* k3 y
- aJSON := &addrBookJSON{}/ _& L; H- u4 y; K4 G& i& {% I6 Y
- dec := json.NewDecoder(r)$ x4 U% L1 j% o/ C f9 h
- err = dec.Decode(aJSON)
- if err != nil {9 h; E0 S& ?* A" s" L
- cmn.PanicCrisis(cmn.Fmt("Error reading file %s: %v", filePath, err))1 M- c8 N( Z( P" E
- }
- // 填充addrNew、addrOld等. O' r6 ~3 m; {0 K( T" Y2 f1 w
- // Restore all the fields...6 V/ `, A( Q- R( H2 `1 S4 j
- // Restore the key# D* L9 ^' c( J
- a.key = aJSON.Key9 l3 p/ l: E7 G h, v+ k
- // Restore .addrNew & .addrOld8 [* w' V7 o" k/ }; Q- {! m7 p
- for _, ka := range aJSON.Addrs {
- for _, bucketIndex := range ka.Buckets {8 s/ P; ]) T; ^- B8 r: G( T
- bucket := a.getBucket(ka.BucketType, bucketIndex)
- bucket[ka.Addr.String()] = ka$ Q# V8 }; L2 M3 P# N
- }
- a.addrLookup[ka.Addr.String()] = ka
- if ka.BucketType == bucketTypeNew {+ ~8 ^- v3 j/ d8 ^* r2 K# W
- a.nNew++
- } else {
- a.nOld++2 E0 f; F; f; K: h# ~
- }
- }+ u R+ C* V# d A+ G
- return true1 m% A0 s8 W5 g8 j
- }
定时更新地址簿
bytomd会定时更新本地地址簿,默认2分钟一次
- func (a *AddrBook) saveRoutine() {
- dumpAddressTicker := time.NewTicker(dumpAddressInterval)
- out:
- for {% E" P4 S$ Q1 F% c5 \( [
- select {/ u6 }9 Q$ P; k3 V8 B
- case
添加新地址
当peer之间交换addr时,节点会收到对端节点已知的地址信息,这些信息会被当前节点添加到地址簿中
- func (a *AddrBook) AddAddress(addr *NetAddress, src *NetAddress) {
- a.mtx.Lock()3 w' E' `/ W2 m! c9 z: {, j5 _
- defer a.mtx.Unlock()
- log.WithFields(log.Fields{
- "addr": addr,
- "src": src,3 L6 W( O8 o) J
- }).Debug("Add address to book")
- a.addAddress(addr, src), K# ?) J- B8 z8 t
- }0 Y/ B, Q* F0 h7 B1 Q) g; ^; R
- func (a *AddrBook) addAddress(addr, src *NetAddress) {/ D: r% [1 Z8 M1 h/ ^; L v
- // 验证地址是否为可路由地址. Y8 l. h) V+ R. C( n& N; n: |
- if a.routabilityStrict && !addr.Routable() {3 {) l; w8 ~/ V# ]" V4 s4 w3 M w
- log.Error(cmn.Fmt("Cannot add non-routable address %v", addr))
- return
- }
- // 验证地址是否为本地节点地址
- if _, ok := a.ourAddrs[addr.String()]; ok {" |! k2 M) q2 `# G ^
- // Ignore our own listener address.- f. Y& F, W% c) B& M- ]
- return7 F0 A' U$ Q3 O9 `- s
- }
- // 验证地址是否存在地址集中
- // 如果存在:则判断该地址是否为old可靠地址、是否超过了最大buckets中。否则根据该地址已经被ka.Buckets引用的个数来随机决定是否添加到地址集中4 X* J& o# w( h7 M$ }% `
- // 如果不存在:则添加到地址集中。并标识为bucketTypeNew地址类型
- ka := a.addrLookup[addr.String()]
- if ka != nil {1 t! U% P" t. D7 @; M/ N+ o3 E
- // Already old.
- if ka.isOld() {
- return! x) Y: C6 R8 r
- }
- // Already in max new buckets., h/ D* q6 O, z* `/ P
- if len(ka.Buckets) == maxNewBucketsPerAddress {( B8 T: Z8 \+ }" F
- return# ]9 p! c/ A8 s3 P
- }
- // The more entries we have, the less likely we are to add more., l9 v4 I1 i% @! w- k) ~
- factor := int32(2 * len(ka.Buckets))
- if a.rand.Int31n(factor) != 0 {
- return/ i) {6 g" Q& w7 F4 u. |+ p
- }
- } else {
- ka = newKnownAddress(addr, src)
- }
- // 找到该地址在地址集的索引位置并添加
- bucket := a.calcNewBucket(addr, src)
- a.addToNewBucket(ka, bucket)% a0 E1 v- i4 h( @7 b) i8 B" t
- log.Info("Added new address ", "address:", addr, " total:", a.size())
- }
选择最优节点0 e _% i" p2 r8 g) D3 L. e
地址簿中存储众多地址,在p2p网络中需选择最优的地址去连接+ L' e, s& x4 r! J6 e( o
PickAddress(newBias int)函数中newBias是由pex_reactor产生的地址评分。如何计算地址分数在其他章节中再讲8 T, @0 T: Q. f( [9 {) U
根据地址评分随机选择地址可增加区块链安全性
- // Pick an address to connect to with new/old bias.
- func (a *AddrBook) PickAddress(newBias int) *NetAddress {
- a.mtx.Lock()
- defer a.mtx.Unlock()
- if a.size() == 0 {0 ]5 |8 U5 r' C7 O& f1 Y
- return nil
- }
- // newBias地址分数限制在0-100分数之间3 B3 [: d: l5 N2 Q4 R8 o [
- if newBias > 100 {" W* ^; f P* z6 Y3 m: ~
- newBias = 100
- }
- if newBias
移除一个地址) t9 C% g" w9 G& z$ W( z" f
当一个地址被标记为Bad时则从地址集中移除。目前bytomd的代码版本并未调用过
- func (a *AddrBook) MarkBad(addr *NetAddress) {
- a.RemoveAddress(addr)' m& F( x. c- |9 s: h: x: G
- }' D8 o2 P/ q7 @, V6 x1 ^8 [
- // RemoveAddress removes the address from the book.
- func (a *AddrBook) RemoveAddress(addr *NetAddress) {+ A9 g1 T- s4 l9 Z+ l! [$ |
- a.mtx.Lock()' q* P2 E. Q( e/ d8 T9 A/ M/ X
- defer a.mtx.Unlock()
- ka := a.addrLookup[addr.String()]
- if ka == nil {8 i& e! ?0 d! w; c4 n- ~: Y+ W# ^: V
- return
- }
- log.WithField("addr", addr).Info("Remove address from book")2 _" i! |5 {3 X0 @) s# M: j
- a.removeFromAllBuckets(ka)
- }6 X# c: g# j2 T9 s
- func (a *AddrBook) removeFromAllBuckets(ka *knownAddress) {! x3 y% a1 k+ R5 `# T
- for _, bucketIdx := range ka.Buckets {
- bucket := a.getBucket(ka.BucketType, bucketIdx)" z, s! {8 u0 h- [6 b* T
- delete(bucket, ka.Addr.String())
- }
- ka.Buckets = nil
- if ka.BucketType == bucketTypeNew {
- a.nNew--* `. F% }* n# @8 r
- } else {
- a.nOld--
- }3 u1 h% x: v' ~5 \3 ]) t
- delete(a.addrLookup, ka.Addr.String())
- }