func handleMappingsRequest(request RequestP2P, mappingsLocal map[string]blockchain.DocumentMapping) MappingsP2P {
	mappingsToSend := make(map[string]blockchain.DocumentMapping)
	for collectionName, mapping := range mappingsLocal {
		if collectionName == "default" {
			continue
		}
		if funk.IsEmpty(request.RequestParameters[collectionName]) {
			mappingsToSend[collectionName] = mapping
		}
	}
	return MappingsP2P{Mappings: mappingsToSend}
}
handleMappingsRequest returns the new mappings which the peer doesn't have
package p2p
import (
	"bytes"
	"context"
	"encoding/hex"
	"encoding/json"
	"errors"
	"fmt"
	"net"
	"strconv"
	"strings"
	"time"
	"github.com/boltdb/bolt"
	"github.com/patrickmn/go-cache"
	"github.com/perlin-network/noise"
	"github.com/perlin-network/noise/kademlia"
	log "github.com/sirupsen/logrus"
	"github.com/thoas/go-funk"
	"github.com/codingpeasant/blocace/blockchain"
)
var DefaultPort = 6091
var PingIntervalInMs = 3000
type P2P struct {
	Node                *noise.Node
	BlockchainForest    *BlockchainForest
	ChallengeWordsCache *cache.Cache
	overlay             *kademlia.Protocol
	Accounts            map[string]blockchain.Account  
	mappings            map[string]blockchain.DocumentMapping
}
func (p *P2P) BroadcastObject(object noise.Serializable) {
	accountsToAdd, ok := object.(AccountsP2P)
	if ok {
		for address, account := range accountsToAdd.Accounts {
			p.Accounts[address] = account  
		}
	}
	mappingsToAdd, ok := object.(MappingsP2P)
	if ok {
		for collection, mapping := range mappingsToAdd.Mappings {
			p.mappings[collection] = mapping  
		}
	}
	for _, id := range p.overlay.Table().Peers() {
		ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
		err := p.Node.SendMessage(ctx, id.Address, object)
		cancel()
		if err != nil {
			log.Errorf("failed to send object to %s(%s). Skipping... [error: %s]\n",
				id.Address,
				id.ID.String(),
				err,
			)
			continue
		}
	}
}
func (p *P2P) GetPeers() []byte {
	var peers []noise.ID
	for _, peer := range p.overlay.Table().Peers() {
		peers = append(peers, peer)
	}
	peersJSON, _ := json.Marshal(peers)
	return peersJSON
}
func (p *P2P) SyncAccountsFromPeers() {
	for _, id := range p.overlay.Table().Peers() {
		sendAccountsRequest(p.Accounts, p.Node, id, p.BlockchainForest.Local)
	}
}
func (p *P2P) SyncMappingsFromPeers() {
	for _, id := range p.overlay.Table().Peers() {
		sendMappingsRequest(p.mappings, p.Node, id, p.BlockchainForest.Local.Search)
	}
}
func (p *P2P) SyncPeerBlockchains() {
	for _, id := range p.overlay.Table().Peers() {
		syncPeerBlockchain(p.Node, id, p.BlockchainForest, false)  
	}
}
func NewP2P(bc *blockchain.Blockchain, bindHost string, bindPort uint16, advertiseAddress string, connectionAddresses ...string) *P2P {
	accounts := initializeAccounts(bc.Db)
	mappings := initializeMappings(bc.Db)
	blockchainForest := NewBlockchainForest(bc)
	challengeWordsCache := cache.New(30*time.Second, 1*time.Minute)
	var p2pPrivKey noise.PrivateKey
	var p2pPrivKeyBytes []byte
	err := bc.Db.View(func(dbtx *bolt.Tx) error {
		bBucket := dbtx.Bucket([]byte(blockchain.BlocksBucket))
		p2pPrivKeyBytes = bBucket.Get([]byte(blockchain.P2PPrivateKeyKey))
		return nil
	})
	if err != nil {
		log.Panic(err)
	}
	copy(p2pPrivKey[:], p2pPrivKeyBytes)
	node, err := noise.NewNode(
		noise.WithNodeBindHost(net.ParseIP(bindHost)),
		noise.WithNodeBindPort(bindPort),
		noise.WithNodeAddress(advertiseAddress),
		noise.WithNodePrivateKey(p2pPrivKey),
	)
	if err != nil {
		log.Panic(err)
	}
	node.RegisterMessage(RequestP2P{}, unmarshalRequestP2P)
	node.RegisterMessage(AccountsP2P{}, unmarshalAccountsP2P)
	node.RegisterMessage(MappingsP2P{}, unmarshalMappingsP2P)
	node.RegisterMessage(ChallengeWordP2P{}, unmarshalChallengeWordP2P)
	node.RegisterMessage(BlockP2P{}, unmarshalBlockP2P)
	node.Handle(func(ctx noise.HandlerContext) error {
		if ctx.IsRequest() {
			obj, err := ctx.DecodeMessage()
			if err != nil {
				return err
			}
			requestP2P, ok := obj.(RequestP2P)
			if !ok {
				return nil
			}
			switch requestP2P.RequestType {
			case accountsRequestType:
				ctx.SendMessage(handleAccountsRequest(requestP2P, accounts))
				accountsRequestReverse(requestP2P, accounts, node, ctx.ID(), blockchainForest.Local)  
			case mappingsRequestType:
				ctx.SendMessage(handleMappingsRequest(requestP2P, mappings))
				mappingsRequestReverse(requestP2P, mappings, node, ctx.ID(), blockchainForest.Local.Search)  
			case blockRequestType:
				ctx.SendMessage(handleBlockRequest(requestP2P, blockchainForest))
				if requestP2P.RequestParameters["local"] == "tip" && requestP2P.RequestParameters["reverse"] != "reverse" {
					syncPeerBlockchain(node, ctx.ID(), blockchainForest, true)
				}
			default:
				log.Warnf("got unsupported RequestP2P request type: +%v", requestP2P)
				return nil
			}
		} else {
			obj, err := ctx.DecodeMessage()
			if err != nil {
				return err
			}
			switch objectP2p := obj.(type) {
			case AccountsP2P:
				for address, account := range objectP2p.Accounts {
					if funk.IsEmpty(accounts[address]) || accounts[address].LastModified < account.LastModified {
						accounts[address] = account  
						if err = bc.RegisterAccount([]byte(address), account); err != nil {
							return err
						}
					}
				}
			case MappingsP2P:
				for mappingName, mapping := range objectP2p.Mappings {
					if funk.IsEmpty(mappings[mappingName]) {
						mappings[mappingName] = mapping  
						if _, err = bc.Search.CreateMapping(mapping); err != nil {
							return err
						}
					}
				}
			case ChallengeWordP2P:
				if funk.IsEmpty(objectP2p.Address) && funk.IsEmpty(objectP2p.ChallengeWord) {
					challengeWordsCache.Set(objectP2p.ChallengeWord, objectP2p.Address, cache.DefaultExpiration)
				}
			case BlockP2P:
				log.Debugf("BlockFromPeer: %s(%s) > %+x; height: %d\n", ctx.ID().Address, ctx.ID().ID.String(), objectP2p.Hash, objectP2p.Height)
				blockchainForest.AddBlock(objectP2p)
			case RequestP2P:
				ctx.SendMessage(handleBlockRequest(objectP2p, blockchainForest))
			default:
				return errors.New("cannot parse the object from peer: " + ctx.ID().ID.String())
			}
		}
		return nil
	})
	events := kademlia.Events{
		OnPeerAdmitted: func(id noise.ID) {
			log.Infof("learned about a new peer %s (%s).\n", id.Address, id.ID.String())
		},
		OnPeerEvicted: func(id noise.ID) {
			log.Infof("forgotten a peer %s (%s).\n", id.Address, id.ID.String())
		},
	}
	overlay := kademlia.New(kademlia.WithProtocolEvents(events))
	node.Bind(overlay.Protocol())
	err = node.Listen()
	if err != nil {
		log.Panic(err)
	}
	if !funk.IsEmpty(connectionAddresses) {
		bootstrap(node, connectionAddresses)
		discover(overlay)
	} else {
		log.Info("no peer address(es) provided, starting without trying to discover")
	}
	go func() {
		ticker := time.NewTicker(time.Duration(PingIntervalInMs) * time.Millisecond)
		for range ticker.C {
			var peersAlive []noise.ID
			for _, id := range overlay.Table().Peers() {
				ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
				client, _ := node.Ping(ctx, id.Address)
				cancel()
				if client != nil {
					peersAlive = append(peersAlive, client.ID())
				}
			}
			for _, id := range overlay.Table().Peers() {
				if !funk.Contains(peersAlive, id) {
					overlay.Table().Delete(id.ID)
				}
			}
		}
	}()
	return &P2P{Node: node, overlay: overlay, BlockchainForest: blockchainForest, ChallengeWordsCache: challengeWordsCache, Accounts: accounts, mappings: mappings}
}
func bootstrap(node *noise.Node, addresses []string) {
	for _, addr := range addresses {
		ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
		_, err := node.Ping(ctx, addr)
		cancel()
		if err != nil {
			log.Warnf("failed to ping bootstrap node (%s). [%s]\n", addr, err)
			continue
		}
	}
}
func discover(overlay *kademlia.Protocol) {
	ids := overlay.Discover()
	var str []string
	for _, id := range ids {
		str = append(str, fmt.Sprintf("%s (%s)", id.Address, id.ID.String()))
	}
	if len(ids) > 0 {
		log.Infof("discovered %d peer(s): [%v]\n", len(ids), strings.Join(str, ", "))
	} else {
		log.Warn("did not discover any peers.")
	}
}
func initializeAccounts(db *bolt.DB) map[string]blockchain.Account {
	accountMap := make(map[string]blockchain.Account)
	db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(blockchain.AccountsBucket))
		c := b.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			accountMap[fmt.Sprintf("%s", k)] = *blockchain.DeserializeAccount(v)
		}
		return nil
	})
	return accountMap
}
func initializeMappings(db *bolt.DB) map[string]blockchain.DocumentMapping {
	collectionMapings := make(map[string]blockchain.DocumentMapping)
	db.View(func(tx *bolt.Tx) error {
		b := tx.Bucket([]byte(blockchain.CollectionsBucket))
		c := b.Cursor()
		for k, v := c.First(); k != nil; k, v = c.Next() {
			collectionMapings[fmt.Sprintf("%s", k)] = *blockchain.DeserializeDocumentMapping(v)
		}
		return nil
	})
	return collectionMapings
}
func handleAccountsRequest(request RequestP2P, accountsLocal map[string]blockchain.Account) AccountsP2P {
	accountsToSend := make(map[string]blockchain.Account)
	for address, account := range accountsLocal {
		if account.Role.Name == "admin" {
			continue
		}
		if funk.IsEmpty(request.RequestParameters[address]) {
			accountsToSend[address] = account
		} else {
			peerLastModified, err := strconv.ParseInt(request.RequestParameters[address], 10, 64)
			if err != nil {
				continue
			}
			if account.LastModified > peerLastModified {
				accountsToSend[address] = account
			}
		}
	}
	return AccountsP2P{Accounts: accountsToSend}
}