github.com/turingchain2020/turingchain@v1.1.21/system/p2p/dht/p2p.go (about)

     1  // Copyright Turing Corp. 2018 All Rights Reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package dht 基于libp2p实现p2p 插件
     6  package dht
     7  
     8  import (
     9  	"context"
    10  	"encoding/hex"
    11  	"fmt"
    12  	"math/rand"
    13  	"sync"
    14  	"sync/atomic"
    15  	"time"
    16  
    17  	"github.com/turingchain2020/turingchain/client"
    18  	dbm "github.com/turingchain2020/turingchain/common/db"
    19  	"github.com/turingchain2020/turingchain/common/log/log15"
    20  	"github.com/turingchain2020/turingchain/p2p"
    21  	"github.com/turingchain2020/turingchain/queue"
    22  	"github.com/turingchain2020/turingchain/system/p2p/dht/extension"
    23  	"github.com/turingchain2020/turingchain/system/p2p/dht/manage"
    24  	"github.com/turingchain2020/turingchain/system/p2p/dht/protocol"
    25  	p2pty "github.com/turingchain2020/turingchain/system/p2p/dht/types"
    26  	"github.com/turingchain2020/turingchain/types"
    27  	"github.com/libp2p/go-libp2p"
    28  	circuit "github.com/libp2p/go-libp2p-circuit"
    29  	connmgr "github.com/libp2p/go-libp2p-connmgr"
    30  	core "github.com/libp2p/go-libp2p-core"
    31  	"github.com/libp2p/go-libp2p-core/crypto"
    32  	"github.com/libp2p/go-libp2p-core/metrics"
    33  	discovery "github.com/libp2p/go-libp2p-discovery"
    34  	pubsub "github.com/libp2p/go-libp2p-pubsub"
    35  	"github.com/multiformats/go-multiaddr"
    36  )
    37  
    38  var log = log15.New("module", p2pty.DHTTypeName)
    39  
    40  func init() {
    41  	p2p.RegisterP2PCreate(p2pty.DHTTypeName, New)
    42  }
    43  
    44  // P2P p2p struct
    45  type P2P struct {
    46  	chainCfg        *types.TuringchainConfig
    47  	host            core.Host
    48  	discovery       *Discovery
    49  	connManager     *manage.ConnManager
    50  	peerInfoManager *manage.PeerInfoManager
    51  	blackCache      *manage.TimeCache
    52  	api             client.QueueProtocolAPI
    53  	client          queue.Client
    54  	addrBook        *AddrBook
    55  	taskGroup       *sync.WaitGroup
    56  
    57  	pubsub  *extension.PubSub
    58  	restart int32
    59  	p2pCfg  *types.P2P
    60  	subCfg  *p2pty.P2PSubConfig
    61  	mgr     *p2p.Manager
    62  	subChan chan interface{}
    63  	ctx     context.Context
    64  	cancel  context.CancelFunc
    65  	db      dbm.DB
    66  
    67  	env *protocol.P2PEnv
    68  }
    69  
    70  // New new dht p2p network
    71  func New(mgr *p2p.Manager, subCfg []byte) p2p.IP2P {
    72  
    73  	chainCfg := mgr.ChainCfg
    74  	p2pCfg := chainCfg.GetModuleConfig().P2P
    75  	mcfg := &p2pty.P2PSubConfig{}
    76  	types.MustDecode(subCfg, mcfg)
    77  	if mcfg.Port == 0 {
    78  		mcfg.Port = p2pty.DefaultP2PPort
    79  	}
    80  	p := &P2P{
    81  		client:   mgr.Client,
    82  		chainCfg: chainCfg,
    83  		subCfg:   mcfg,
    84  		p2pCfg:   p2pCfg,
    85  		mgr:      mgr,
    86  		api:      mgr.SysAPI,
    87  		addrBook: NewAddrBook(p2pCfg),
    88  		subChan:  mgr.PubSub.Sub(p2pty.DHTTypeName),
    89  	}
    90  
    91  	return initP2P(p)
    92  }
    93  
    94  func initP2P(p *P2P) *P2P {
    95  	//other init work
    96  	p.ctx, p.cancel = context.WithCancel(context.Background())
    97  	priv := p.addrBook.GetPrivkey()
    98  	if priv == nil { //addrbook存储的peer key 为空
    99  		if p.p2pCfg.WaitPid { //p2p阻塞,直到创建钱包之后
   100  			p.genAirDropKey()
   101  		} else { //创建随机公私钥对,生成临时pid,待创建钱包之后,提换钱包生成的pid
   102  			p.addrBook.Randkey()
   103  			go p.genAirDropKey() //非阻塞模式
   104  		}
   105  	} else { //非阻塞模式
   106  		go p.genAirDropKey()
   107  	}
   108  
   109  	maddr, err := multiaddr.NewMultiaddr(fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", p.subCfg.Port))
   110  	if err != nil {
   111  		panic(err)
   112  	}
   113  	log.Info("NewMulti", "addr", maddr.String())
   114  
   115  	bandwidthTracker := metrics.NewBandwidthCounter()
   116  	p.blackCache = manage.NewTimeCache(p.ctx, time.Minute*5)
   117  	options := p.buildHostOptions(p.addrBook.GetPrivkey(), bandwidthTracker, maddr, p.blackCache)
   118  	host, err := libp2p.New(p.ctx, options...)
   119  	if err != nil {
   120  		panic(err)
   121  	}
   122  
   123  	p.host = host
   124  	psOpts := make([]pubsub.Option, 0)
   125  	// pubsub消息默认会基于节点私钥进行签名和验签,支持关闭
   126  	if p.subCfg.DisablePubSubMsgSign {
   127  		psOpts = append(psOpts, pubsub.WithMessageSigning(false), pubsub.WithStrictSignatureVerification(false))
   128  	}
   129  	ps, err := extension.NewPubSub(p.ctx, p.host, psOpts...)
   130  	if err != nil {
   131  		return nil
   132  	}
   133  	p.pubsub = ps
   134  	p.discovery = InitDhtDiscovery(p.ctx, p.host, p.addrBook.AddrsInfo(), p.chainCfg, p.subCfg)
   135  	p.connManager = manage.NewConnManager(p.ctx, p.host, p.discovery.RoutingTable(), bandwidthTracker, p.subCfg)
   136  	p.peerInfoManager = manage.NewPeerInfoManager(p.ctx, p.host, p.client)
   137  	p.taskGroup = &sync.WaitGroup{}
   138  	p.db = newDB("", p.p2pCfg.Driver, p.subCfg.DHTDataPath, p.subCfg.DHTDataCache)
   139  	return p
   140  }
   141  
   142  // StartP2P start p2p
   143  func (p *P2P) StartP2P() {
   144  	if atomic.LoadInt32(&p.restart) == 1 {
   145  		log.Info("RestartP2P...")
   146  		initP2P(p) //重新创建host
   147  	}
   148  	atomic.StoreInt32(&p.restart, 0)
   149  	p.addrBook.StoreHostID(p.host.ID(), p.p2pCfg.DbPath)
   150  	log.Info("NewP2p", "peerId", p.host.ID(), "addrs", p.host.Addrs())
   151  
   152  	env := &protocol.P2PEnv{
   153  		Ctx:              p.ctx,
   154  		ChainCfg:         p.chainCfg,
   155  		QueueClient:      p.client,
   156  		Host:             p.host,
   157  		P2PManager:       p.mgr,
   158  		SubConfig:        p.subCfg,
   159  		DB:               p.db,
   160  		RoutingDiscovery: discovery.NewRoutingDiscovery(p.discovery.kademliaDHT),
   161  		RoutingTable:     p.discovery.RoutingTable(),
   162  		API:              p.api,
   163  		Pubsub:           p.pubsub,
   164  		PeerInfoManager:  p.peerInfoManager,
   165  		ConnManager:      p.connManager,
   166  		ConnBlackList:    p.blackCache,
   167  	}
   168  	p.env = env
   169  	protocol.InitAllProtocol(env)
   170  	p.discovery.Start()
   171  	go p.managePeers()
   172  	go p.handleP2PEvent()
   173  	go p.findLANPeers()
   174  }
   175  
   176  // CloseP2P close p2p
   177  func (p *P2P) CloseP2P() {
   178  	log.Info("p2p closing")
   179  	p.discovery.Close()
   180  	p.waitTaskDone()
   181  	p.db.Close()
   182  
   183  	protocol.ClearEventHandler()
   184  	if !p.isRestart() {
   185  		p.mgr.PubSub.Unsub(p.subChan)
   186  
   187  	}
   188  	p.host.Close()
   189  	p.cancel()
   190  	log.Info("p2p closed")
   191  }
   192  
   193  func (p *P2P) reStart() {
   194  	atomic.StoreInt32(&p.restart, 1)
   195  	log.Info("reStart p2p")
   196  	if p.host == nil {
   197  		//说明p2p还没有开始启动,无需重启
   198  		log.Info("p2p no need restart...")
   199  		atomic.StoreInt32(&p.restart, 0)
   200  		return
   201  	}
   202  	p.CloseP2P()
   203  	p.StartP2P()
   204  }
   205  
   206  func (p *P2P) buildHostOptions(priv crypto.PrivKey, bandwidthTracker metrics.Reporter, maddr multiaddr.Multiaddr, timeCache *manage.TimeCache) []libp2p.Option {
   207  	if bandwidthTracker == nil {
   208  		bandwidthTracker = metrics.NewBandwidthCounter()
   209  	}
   210  
   211  	var options []libp2p.Option
   212  	if p.subCfg.RelayEnable {
   213  		if p.subCfg.RelayHop { //启用中继服务端
   214  			options = append(options, libp2p.EnableRelay(circuit.OptHop))
   215  		} else { //用配置的节点作为中继节点,需要打开HOP选项
   216  			//relays := append(p.subCfg.BootStraps, p.subCfg.RelayNodeAddr...)
   217  			relays := p.subCfg.RelayNodeAddr
   218  			options = append(options, libp2p.AddrsFactory(extension.WithRelayAddrs(relays)))
   219  			options = append(options, libp2p.EnableRelay())
   220  		}
   221  	}
   222  
   223  	options = append(options, libp2p.NATPortMap())
   224  	if maddr != nil {
   225  		options = append(options, libp2p.ListenAddrs(maddr))
   226  	}
   227  	if priv != nil {
   228  		options = append(options, libp2p.Identity(priv))
   229  	}
   230  
   231  	options = append(options, libp2p.BandwidthReporter(bandwidthTracker))
   232  
   233  	if p.subCfg.MaxConnectNum > 0 { //如果不设置最大连接数量,默认允许dht自由连接并填充路由表
   234  		var maxconnect = int(p.subCfg.MaxConnectNum)
   235  		minconnect := maxconnect - int(manage.CacheLimit) //调整为不超过配置的上限
   236  		if minconnect < 0 {
   237  			minconnect = maxconnect / 2
   238  		}
   239  		//2分钟的宽限期,定期清理
   240  		options = append(options, libp2p.ConnectionManager(connmgr.NewConnManager(minconnect, maxconnect, time.Minute*2)))
   241  		//ConnectionGater,处理网络连接的策略
   242  		options = append(options, libp2p.ConnectionGater(manage.NewConnGater(&p.host, p.subCfg.MaxConnectNum, timeCache, genAddrInfos(p.subCfg.WhitePeerList))))
   243  	}
   244  	//关闭ping
   245  	options = append(options, libp2p.Ping(false))
   246  	return options
   247  }
   248  
   249  func (p *P2P) managePeers() {
   250  	go p.connManager.MonitorAllPeers()
   251  
   252  	for {
   253  		log.Debug("managePeers", "table size", p.discovery.RoutingTable().Size())
   254  		select {
   255  		case <-p.ctx.Done():
   256  			log.Info("managePeers", "p2p", "closed")
   257  			return
   258  		case <-time.After(time.Minute * 10):
   259  			//Refresh addr book
   260  			peersInfo := p.discovery.FindLocalPeers(p.connManager.FetchNearestPeers(50))
   261  			if len(peersInfo) != 0 {
   262  				p.addrBook.SaveAddr(peersInfo)
   263  			}
   264  		}
   265  	}
   266  }
   267  
   268  //查询本局域网内是否有节点
   269  func (p *P2P) findLANPeers() {
   270  	if p.subCfg.DisableFindLANPeers {
   271  		return
   272  	}
   273  	peerChan, err := p.discovery.FindLANPeers(p.host, fmt.Sprintf("/%s-mdns/%d", p.chainCfg.GetTitle(), p.subCfg.Channel))
   274  	if err != nil {
   275  		log.Error("findLANPeers", "err", err.Error())
   276  		return
   277  	}
   278  
   279  	for {
   280  		select {
   281  		case neighbors := <-peerChan:
   282  			log.Debug("^_^! Well,findLANPeers Let's Play ^_^!<<<<<<<<<<<<<<<<<<<", "peerName", neighbors.ID, "addrs:", neighbors.Addrs, "paddr", p.host.Peerstore().Addrs(neighbors.ID))
   283  			//发现局域网内的邻居节点
   284  			err := p.host.Connect(context.Background(), neighbors)
   285  			if err != nil {
   286  				log.Error("findLANPeers", "err", err.Error())
   287  				continue
   288  			}
   289  			log.Info("findLANPeers", "connect neighbors success", neighbors.ID.Pretty())
   290  			p.connManager.AddNeighbors(&neighbors)
   291  
   292  		case <-p.ctx.Done():
   293  			log.Info("findLANPeers", "process", "done")
   294  			return
   295  		}
   296  	}
   297  }
   298  
   299  func (p *P2P) handleP2PEvent() {
   300  	//TODO, control goroutine num
   301  	for {
   302  		select {
   303  		case <-p.ctx.Done():
   304  			return
   305  		case data, ok := <-p.subChan:
   306  			if !ok {
   307  				return
   308  			}
   309  			msg, ok := data.(*queue.Message)
   310  			if !ok || data == nil {
   311  				log.Error("handleP2PEvent", "recv invalid msg, data=", data)
   312  				continue
   313  			}
   314  
   315  			p.taskGroup.Add(1)
   316  			go func(m *queue.Message) {
   317  				defer p.taskGroup.Done()
   318  				if handler := protocol.GetEventHandler(m.Ty); handler != nil {
   319  					handler(m)
   320  				} else {
   321  					log.Error("handleP2PEvent", "unknown message type", m.Ty)
   322  				}
   323  			}(msg)
   324  		}
   325  	}
   326  }
   327  
   328  func (p *P2P) isRestart() bool {
   329  	return atomic.LoadInt32(&p.restart) == 1
   330  }
   331  
   332  func (p *P2P) waitTaskDone() {
   333  	waitDone := make(chan struct{})
   334  	go func() {
   335  		defer close(waitDone)
   336  		p.taskGroup.Wait()
   337  	}()
   338  	select {
   339  	case <-waitDone:
   340  	case <-time.After(time.Second * 20):
   341  		log.Error("waitTaskDone", "err", "20s timeout")
   342  	}
   343  }
   344  
   345  //创建空投地址
   346  func (p *P2P) genAirDropKey() {
   347  
   348  	for { //等待钱包创建,解锁
   349  		select {
   350  		case <-p.ctx.Done():
   351  			log.Info("genAirDropKey", "p2p closed")
   352  			return
   353  		case <-time.After(time.Second):
   354  			resp, err := p.api.ExecWalletFunc("wallet", "GetWalletStatus", &types.ReqNil{})
   355  			if err != nil {
   356  				time.Sleep(time.Second)
   357  				continue
   358  			}
   359  			if !resp.(*types.WalletStatus).GetIsHasSeed() {
   360  				continue
   361  			}
   362  
   363  			if resp.(*types.WalletStatus).GetIsWalletLock() { //上锁状态,无法用助记词创建空投地址,等待...
   364  				continue
   365  			}
   366  		}
   367  		break
   368  	}
   369  
   370  	//用助记词和随机索引创建空投地址
   371  	r := rand.New(rand.NewSource(types.Now().Unix()))
   372  	var minIndex int32 = 100000000
   373  	randIndex := minIndex + r.Int31n(1000000)
   374  	reqIndex := &types.Int32{Data: randIndex}
   375  	msg, err := p.api.ExecWalletFunc("wallet", "NewAccountByIndex", reqIndex)
   376  	if err != nil {
   377  		log.Error("genAirDropKey", "NewAccountByIndex err", err)
   378  		return
   379  	}
   380  
   381  	var walletPrivkey string
   382  	if reply, ok := msg.(*types.ReplyString); !ok {
   383  		log.Error("genAirDropKey", "wrong format data", "")
   384  		panic(err)
   385  
   386  	} else {
   387  		walletPrivkey = reply.GetData()
   388  	}
   389  	if walletPrivkey != "" && walletPrivkey[:2] == "0x" {
   390  		walletPrivkey = walletPrivkey[2:]
   391  	}
   392  
   393  	walletPubkey, err := GenPubkey(walletPrivkey)
   394  	if err != nil {
   395  		return
   396  	}
   397  	//如果addrbook之前保存的savePrivkey相同,则意味着节点启动之前已经创建了airdrop 空投地址
   398  	savePrivkey, _ := p.addrBook.GetPrivPubKey()
   399  	if savePrivkey == walletPrivkey { //addrbook与wallet保存了相同的空投私钥,不需要继续导入
   400  		log.Debug("genAirDropKey", " same privekey ,process done")
   401  		return
   402  	}
   403  
   404  	if len(savePrivkey) != 2*privKeyCompressBytesLen { //非压缩私钥,兼容之前老版本的DHT非压缩私钥
   405  		log.Debug("len savePrivkey", len(savePrivkey))
   406  		unCompkey := p.addrBook.GetPrivkey()
   407  		if unCompkey == nil {
   408  			savePrivkey = ""
   409  		} else {
   410  			compkey, err := unCompkey.Raw() //compress key
   411  			if err != nil {
   412  				savePrivkey = ""
   413  				log.Error("genAirDropKey", "compressKey.Raw err", err)
   414  			} else {
   415  				savePrivkey = hex.EncodeToString(compkey)
   416  			}
   417  		}
   418  	}
   419  
   420  	if savePrivkey != "" && !p.p2pCfg.WaitPid { //如果是waitpid 则不生成dht node award,保证之后一个空投地址,即钱包创建的airdropaddr
   421  		//savePrivkey是随机私钥,兼容老版本,先对其进行导入钱包处理
   422  		//进行压缩处理
   423  		var parm types.ReqWalletImportPrivkey
   424  		parm.Privkey = savePrivkey
   425  		parm.Label = "dht node award"
   426  
   427  		for {
   428  			_, err = p.api.ExecWalletFunc("wallet", "WalletImportPrivkey", &parm)
   429  			if err == types.ErrLabelHasUsed {
   430  				//切换随机lable
   431  				parm.Label = fmt.Sprintf("node award %d", rand.Int31n(1024000))
   432  				time.Sleep(time.Second)
   433  				continue
   434  			}
   435  			break
   436  		}
   437  	}
   438  
   439  	p.addrBook.saveKey(walletPrivkey, walletPubkey)
   440  	p.reStart()
   441  }
   442  
   443  func newDB(name, backend, dir string, cache int32) dbm.DB {
   444  	if name == "" {
   445  		name = "p2pstore"
   446  	}
   447  	if backend == "" {
   448  		backend = "leveldb"
   449  	}
   450  	if dir == "" {
   451  		dir = "datadir"
   452  	}
   453  	if cache <= 0 {
   454  		cache = 128
   455  	}
   456  	return dbm.NewDB(name, backend, dir, cache)
   457  }