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 }