Author: Derek

Introduction to the

Github address: github.com/Bytom/bytom

Gitee address: gitee.com/BytomBlockc…

This chapter introduces byTom code addrbook address book in P2P network

The author uses MacOS, and other platforms are much the same

Golang Version: 1.8

Addrbook introduction

Addrbook used to store retain recently to the node in P2P network address Under MacOS, the default address book path is stored in ~ / Library/Bytom/addrbook json

Address book format

** ~/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": "The 2018-05-04 T12:58:23. 894057702 + 08:00"."LastSuccess": "0001-01-01T00:00:00Z"."BucketType": 1,
            "Buckets": [181, 10]}]}Copy the code

Address type

There are two kinds of addresses stored in addrbook: ** p2p/addrbook.go **

Const (bucketTypeNew = 0x01 // identifies new address, unreliable address (failed to connect) Only one bucket bucketTypeOld = 0x02 // Identifies the old, reliable address (successfully connected). Can be stored in multiple buckets, up to maxNewBucketsPerAddress)Copy the code

Note: An address type change is not covered in this article and will be covered in a later article

Address book related structure

Address book

typeAddrBook struct {cmn.BaseService MTX sync.Mutex filePath String // Address Book path routabilityStrict bool // Whether it is routable. The default value istrueOurAddrs map[string]*NetAddress // Store the local network address, AddrLookup map[string]*knownAddress addrLookup map[string] AddrNew []map[string]*knownAddress addrOld []map[string]*knownAddress addrOld []map[string]*knownAddress // nNew int // nNew intCopy the code

The known address

typeKnownAddress struct {Addr *NetAddress Src *NetAddress // Addr Attempts int32 // Time // Time of the last successful connection attempt LastSuccess time. time // Time of the last successful connection BucketType byte // Type of address (reliable address or unreliable address) Buckets []int // current addr belongs to Buckets}Copy the code

The routabilityStrict parameter indicates whether the IP stored in the address book is routable. Routability is classified according to RFC. For details, see RFC standard

Initialize the address book

// 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() {// Address book unique identity. k. = crypto.CRandHex(24) // 24/2 * 8 = 96 Bits // New addr buckets, A.ddrnew = make([]map[string]*knownAddress, NewBucketCount) for I := range a.ddrnew {a.ddrnew [I] = make(map[string]*knownAddress)} // Old addr buckets, A. addrold = make([]map[string]*knownAddress, oldBucketCount) for i := range a.addrOld { a.addrOld[i] = make(map[string]*knownAddress) } }Copy the code

The local address book is loaded when Bytomd starts

LoadFromFile first loads the local address book when Bytomd starts

// 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. // If the local address book does not exist, return _, Err := os.stat (filePath) if os.isnotexist (err) {return false} // 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))} // 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 }Copy the code

Update the address book periodically

Bytomd updates the local address book periodically, every 2 minutes by default

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")
	iferr ! = nil { log.WithField("err", err).Error("Failed to save AddrBook to file")
		return
	}
	err = cmn.WriteFileAtomic(filePath, jsonBytes, 0644)
	iferr ! = nil { log.WithFields(log.Fields{"file": filePath,
			"err":  err,
		}).Error("Failed to save AddrBook to file")}}Copy the code

Adding a New Address

When addr is exchanged between peers, each node receives the known address information of the peer node, which is added to the address book by the current node

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) {// Verify that the address is routableifa.routabilityStrict && ! addr.Routable() {
		log.Error(cmn.Fmt("Cannot add non-routable address %v", addr))
		return} // Verify that the address is the local node addressif _, ok := a.ourAddrs[addr.String()]; ok {
		// Ignore our own listener address.
		return} // Check whether the address is in an address set. // If yes, check whether the address is old and reliable and exceeds the maximum buckets. Otherwise, add the address to the address set according to the number of references by Ka.Buckets. // If the address does not exist, add it to the address set. Ka := a.ddrlookup [Addr.string ()]ifka ! = 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))
		ifa.rand.Int31n(factor) ! = 0 {return}}else{ka = newKnownAddress(addr, SRC)} // Add bucket := a.calcnewbucket (addr, SRC) a.addtonewbucket (ka, bucket) log.Info("Added new address "."address:", addr, " total:", a.size())
}
Copy the code

Select the optimal node

There are many addresses stored in the address book. In the P2P network, the optimal address must be selected to connect to the PickAddress(newBias INT) function. NewBias is the address score generated by PEX_REACTOR. How to calculate the address score which will be covered in other chapters randomly selecting addresses based on the address score can increase blockchain security

// 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 {
		returnNil} // newBias address fractions are limited to 0-100 fractionsif newBias > 100 {
		newBias = 100
	}
	if newBias < 0 {
		newBias = 0
	}

	// Bias between new and old addresses.
	oldCorrelation := math.Sqrt(float64 (a.n Old)) * (100.0 -float64(newBias))
	newCorrelation := math.Sqrt(float64(a.nNew)) * float64(newBias) // Calculates whether to randomly select an address from addrOld or addrNew based on the address scoreif (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
}
Copy the code

Remove an address

When an address is marked Bad, it is removed from the address set. The current version of bytomd code is not called

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())
}
Copy the code