Derek解讀Bytom源碼-P2P網路 地址簿

來源:互聯網
上載者:User

作者:Derek

簡介

Github地址:https://github.com/Bytom/bytom

Gitee地址:https://gitee.com/BytomBlockc...

本章介紹bytom代碼P2P網路中addrbook地址簿

作者使用MacOS作業系統,其他平台也大同小異

Golang Version: 1.8

addrbook介紹

addrbook用於儲存P2P網路中保留最近的對端節點地址
在MacOS下,預設的地址簿路徑儲存在~/Library/Bytom/addrbook.json

地址簿格式

~/Library/Bytom/addrbook.json

{    "Key": "359be6d08bc0c6e21c84bbb2",    "Addrs": [        {            "Addr": {                "IP": "122.224.11.144",                "Port": 46657            },            "Src": {                "IP": "198.74.61.131",                "Port": 46657            },            "Attempts": 0,            "LastAttempt": "2018-05-04T12:58:23.894057702+08:00",            "LastSuccess": "0001-01-01T00:00:00Z",            "BucketType": 1,            "Buckets": [                181,                10            ]        }    ]}

地址類型

在addrbook中儲存的地址有兩種:
p2p/addrbook.go

const (    bucketTypeNew = 0x01  // 標識新地址,不可靠地址(未成功串連過)。只儲存在一個bucket中    bucketTypeOld = 0x02  // 標識舊地址,可靠地址(已成功串連過)。可以儲存在多個bucket中,最多為maxNewBucketsPerAddress個)

<font color=red>注意: 一個地址的類型變更不在此文章中做介紹,後期的文章會討論該問題</font>

地址簿相關結構體

地址簿

type AddrBook struct {    cmn.BaseService    mtx               sync.Mutex    filePath          string  // 地址簿路徑    routabilityStrict bool  // 是否可路由,預設為true    rand              *rand.Rand     key               string  // 地址簿標識,用於計算addrNew和addrOld的索引    ourAddrs          map[string]*NetAddress  // 儲存本網地址,用於添加p2p地址時做排除使用    addrLookup        map[string]*knownAddress // 儲存新、舊地址集,用於查詢    addrNew           []map[string]*knownAddress // 儲存新地址    addrOld           []map[string]*knownAddress // 儲存舊地址    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 // 最近一次嘗試串連的時間    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.func NewAddrBook(filePath string, routabilityStrict bool) *AddrBook {    am := &AddrBook{        rand:              rand.New(rand.NewSource(time.Now().UnixNano())),        ourAddrs:          make(map[string]*NetAddress),        addrLookup:        make(map[string]*knownAddress),        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() {  // 地址簿唯一標識    a.key = crypto.CRandHex(24) // 24/2 * 8 = 96 bits    // 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)    for i := range a.addrOld {        a.addrOld[i] = make(map[string]*knownAddress)    }}

bytomd啟動時載入本地地址簿

loadFromFile在bytomd啟動時,首先會載入本地的地址簿

// OnStart implements Service.func (a *AddrBook) OnStart() error {    a.BaseService.OnStart()    a.loadFromFile(a.filePath)    a.wg.Add(1)    go a.saveRoutine()    return nil}// Returns false if file does not exist.// cmn.Panics if file is corrupt.func (a *AddrBook) loadFromFile(filePath string) bool {    // If doesn't exist, do nothing.    // 如果本地地址簿不存在則直接返回    _, err := os.Stat(filePath)    if os.IsNotExist(err) {        return false    }  // 載入地址簿json內容    // Load addrBookJSON{}    r, err := os.Open(filePath)    if err != nil {        cmn.PanicCrisis(cmn.Fmt("Error opening file %s: %v", filePath, err))    }    defer r.Close()    aJSON := &addrBookJSON{}    dec := json.NewDecoder(r)    err = dec.Decode(aJSON)    if err != nil {        cmn.PanicCrisis(cmn.Fmt("Error reading file %s: %v", filePath, err))    }  // 填充addrNew、addrOld等    // Restore all the fields...    // Restore the key    a.key = aJSON.Key    // Restore .addrNew & .addrOld    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        if ka.BucketType == bucketTypeNew {            a.nNew++        } else {            a.nOld++        }    }    return true}

定時更新地址簿

bytomd會定時更新本地地址簿,預設2分鐘一次

func (a *AddrBook) saveRoutine() {    dumpAddressTicker := time.NewTicker(dumpAddressInterval)out:    for {        select {        case <-dumpAddressTicker.C:            a.saveToFile(a.filePath)        case <-a.Quit:            break out        }    }    dumpAddressTicker.Stop()    a.saveToFile(a.filePath)    a.wg.Done()    log.Info("Address handler done")}func (a *AddrBook) saveToFile(filePath string) {    log.WithField("size", a.Size()).Info("Saving AddrBook to file")    a.mtx.Lock()    defer a.mtx.Unlock()    // Compile Addrs    addrs := []*knownAddress{}    for _, ka := range a.addrLookup {        addrs = append(addrs, ka)    }    aJSON := &addrBookJSON{        Key:   a.key,        Addrs: addrs,    }    jsonBytes, err := json.MarshalIndent(aJSON, "", "\t")    if err != nil {        log.WithField("err", err).Error("Failed to save AddrBook to file")        return    }    err = cmn.WriteFileAtomic(filePath, jsonBytes, 0644)    if err != nil {        log.WithFields(log.Fields{            "file": filePath,            "err":  err,        }).Error("Failed to save AddrBook to file")    }}

添加新地址

當peer之間交換addr時,節點會收到對端節點已知的地址資訊,這些資訊會被當前節點添加到地址簿中

func (a *AddrBook) AddAddress(addr *NetAddress, src *NetAddress) {    a.mtx.Lock()    defer a.mtx.Unlock()    log.WithFields(log.Fields{        "addr": addr,        "src":  src,    }).Debug("Add address to book")    a.addAddress(addr, src)}func (a *AddrBook) addAddress(addr, src *NetAddress) {    // 驗證地址是否為可路由地址    if a.routabilityStrict && !addr.Routable() {        log.Error(cmn.Fmt("Cannot add non-routable address %v", addr))        return    }    // 驗證地址是否為本地節點地址    if _, ok := a.ourAddrs[addr.String()]; ok {        // Ignore our own listener address.        return    }    // 驗證地址是否存在地址集中    // 如果存在:則判斷該地址是否為old可靠地址、是否超過了最大buckets中。否則根據該地址已經被ka.Buckets引用的個數來隨機決定是否添加到地址集中    // 如果不存在:則添加到地址集中。並標識為bucketTypeNew地址類型    ka := a.addrLookup[addr.String()]    if ka != nil {        // Already old.        if ka.isOld() {            return        }        // Already in max new buckets.        if len(ka.Buckets) == maxNewBucketsPerAddress {            return        }        // 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 {            return        }    } else {        ka = newKnownAddress(addr, src)    }    // 找到該地址在地址集的索引位置並添加    bucket := a.calcNewBucket(addr, src)    a.addToNewBucket(ka, bucket)    log.Info("Added new address ", "address:", addr, " total:", a.size())}

選擇最優節點

地址簿中儲存眾多地址,在p2p網路中需選擇最優的地址去串連
PickAddress(newBias int)函數中newBias是由pex_reactor產生的地址評分。如何計算地址分數在其他章節中再講
根據地址評分隨機播放地址可增加區塊鏈安全性

// 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 {        return nil    }    // newBias地址分數限制在0-100分數之間    if newBias > 100 {        newBias = 100    }    if newBias < 0 {        newBias = 0    }    // Bias between new and old addresses.    oldCorrelation := math.Sqrt(float64(a.nOld)) * (100.0 - float64(newBias))    newCorrelation := math.Sqrt(float64(a.nNew)) * float64(newBias)  // 根據地址分數計算是否從addrOld或addrNew中隨機播放一個地址    if (newCorrelation+oldCorrelation)*a.rand.Float64() < oldCorrelation {        // pick random Old bucket.        var bucket map[string]*knownAddress = nil        num := 0        for len(bucket) == 0 && num < oldBucketCount {            bucket = a.addrOld[a.rand.Intn(len(a.addrOld))]            num++        }        if num == oldBucketCount {            return nil        }        // pick a random ka from bucket.        randIndex := a.rand.Intn(len(bucket))        for _, ka := range bucket {            if randIndex == 0 {                return ka.Addr            }            randIndex--        }        cmn.PanicSanity("Should not happen")    } else {        // pick random New bucket.        var bucket map[string]*knownAddress = nil        num := 0        for len(bucket) == 0 && num < newBucketCount {            bucket = a.addrNew[a.rand.Intn(len(a.addrNew))]            num++        }        if num == newBucketCount {            return nil        }        // pick a random ka from bucket.        randIndex := a.rand.Intn(len(bucket))        for _, ka := range bucket {            if randIndex == 0 {                return ka.Addr            }            randIndex--        }        cmn.PanicSanity("Should not happen")    }    return nil}

移除一個地址

當一個地址被標記為Bad時則從地址集中移除。目前bytomd的代碼版本並未調用過

func (a *AddrBook) MarkBad(addr *NetAddress) {    a.RemoveAddress(addr)}// RemoveAddress removes the address from the book.func (a *AddrBook) RemoveAddress(addr *NetAddress) {    a.mtx.Lock()    defer a.mtx.Unlock()    ka := a.addrLookup[addr.String()]    if ka == nil {        return    }    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    if ka.BucketType == bucketTypeNew {        a.nNew--    } else {        a.nOld--    }    delete(a.addrLookup, ka.Addr.String())}
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.