github.com/codingfuture/orig-energi3@v0.8.4/energi/service/masternode.go (about) 1 // Copyright 2019 The Energi Core Authors 2 // This file is part of the Energi Core library. 3 // 4 // The Energi Core library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The Energi Core library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the Energi Core library. If not, see <http://www.gnu.org/licenses/>. 16 17 package service 18 19 import ( 20 "errors" 21 "math/big" 22 "sync/atomic" 23 "time" 24 25 "github.com/ethereum/go-ethereum/accounts/abi/bind" 26 "github.com/ethereum/go-ethereum/common" 27 "github.com/ethereum/go-ethereum/core" 28 "github.com/ethereum/go-ethereum/core/types" 29 "github.com/ethereum/go-ethereum/crypto" 30 "github.com/ethereum/go-ethereum/eth" 31 "github.com/ethereum/go-ethereum/eth/downloader" 32 "github.com/ethereum/go-ethereum/log" 33 "github.com/ethereum/go-ethereum/node" 34 "github.com/ethereum/go-ethereum/p2p" 35 "github.com/ethereum/go-ethereum/rpc" 36 37 energi_abi "energi.world/core/gen3/energi/abi" 38 energi_common "energi.world/core/gen3/energi/common" 39 energi_params "energi.world/core/gen3/energi/params" 40 ) 41 42 var ( 43 heartbeatInterval = time.Duration(30) * time.Minute 44 recheckInterval = time.Minute 45 ) 46 47 const ( 48 masternodeCallGas uint64 = 500000 49 50 // cpChanBufferSize defines the number of checkpoint to be pushed into the 51 // checkpoints channel before it can be considered to be full. 52 cpChanBufferSize = 16 53 chainHeadChanSize = 10 54 ) 55 56 type checkpointVote struct { 57 address common.Address 58 signature []byte 59 } 60 61 type MasternodeService struct { 62 server *p2p.Server 63 eth *eth.Ethereum 64 65 inSync int32 66 67 address common.Address 68 owner common.Address 69 registry *energi_abi.IMasternodeRegistryV2Session 70 71 cpRegistry *energi_abi.ICheckpointRegistrySession 72 lastCPBlock uint64 73 cpVoteChan chan *checkpointVote 74 75 nextHB time.Time 76 features *big.Int 77 78 validator *peerValidator 79 } 80 81 func NewMasternodeService(ethServ *eth.Ethereum, owner common.Address) (node.Service, error) { 82 r := &MasternodeService{ 83 eth: ethServ, 84 inSync: 1, 85 features: energi_common.SWVersionToInt(), 86 owner: owner, 87 // NOTE: we need to avoid triggering DoS on restart. 88 // There is no reliable way to check blockchain and all pools in the network. 89 nextHB: time.Now().Add(recheckInterval), 90 91 cpVoteChan: make(chan *checkpointVote, cpChanBufferSize), 92 } 93 go r.listenDownloader() 94 return r, nil 95 } 96 97 func (m *MasternodeService) Protocols() []p2p.Protocol { 98 return nil 99 } 100 101 func (m *MasternodeService) APIs() []rpc.API { 102 return nil 103 } 104 105 func (m *MasternodeService) Start(server *p2p.Server) error { 106 address := crypto.PubkeyToAddress(server.PrivateKey.PublicKey) 107 m.address = address 108 109 //--- 110 m.eth.TxPool().RemoveBySender(address) 111 112 //--- 113 contract, err := energi_abi.NewIMasternodeRegistryV2( 114 energi_params.Energi_MasternodeRegistry, m.eth.APIBackend) 115 if err != nil { 116 return err 117 } 118 119 m.registry = &energi_abi.IMasternodeRegistryV2Session{ 120 Contract: contract, 121 CallOpts: bind.CallOpts{ 122 From: address, 123 }, 124 TransactOpts: bind.TransactOpts{ 125 From: address, 126 Signer: func( 127 signer types.Signer, 128 addr common.Address, 129 tx *types.Transaction, 130 ) (*types.Transaction, error) { 131 if addr != address { 132 log.Error("Invalid Masternode address!", "addr", addr) 133 return nil, errors.New("Invalid MN address") 134 } 135 136 return types.SignTx(tx, signer, server.PrivateKey) 137 }, 138 Value: common.Big0, 139 GasLimit: masternodeCallGas, 140 GasPrice: common.Big0, 141 }, 142 } 143 144 //--- 145 146 cpContract, err := energi_abi.NewICheckpointRegistry( 147 energi_params.Energi_CheckpointRegistry, m.eth.APIBackend) 148 if err != nil { 149 return err 150 } 151 152 m.cpRegistry = &energi_abi.ICheckpointRegistrySession{ 153 Contract: cpContract, 154 CallOpts: m.registry.CallOpts, 155 TransactOpts: m.registry.TransactOpts, 156 } 157 158 m.server = server 159 m.validator = newPeerValidator(common.Address{}, m) 160 go m.loop() 161 162 log.Info("Started Energi Masternode", "addr", address) 163 return nil 164 } 165 166 func (m *MasternodeService) Stop() error { 167 log.Info("Shutting down Energi Masternode", "addr", m.address) 168 m.validator.cancel() 169 return nil 170 } 171 172 func (m *MasternodeService) listenDownloader() { 173 events := m.eth.EventMux().Subscribe( 174 downloader.StartEvent{}, 175 downloader.DoneEvent{}, 176 downloader.FailedEvent{}, 177 ) 178 defer events.Unsubscribe() 179 180 for { 181 select { 182 case ev := <-events.Chan(): 183 if ev == nil { 184 return 185 } 186 switch ev.Data.(type) { 187 case downloader.StartEvent: 188 atomic.StoreInt32(&m.inSync, 0) 189 log.Debug("Masternode is not in sync") 190 case downloader.DoneEvent, downloader.FailedEvent: 191 atomic.StoreInt32(&m.inSync, 1) 192 log.Debug("Masternode is in sync") 193 return 194 } 195 } 196 } 197 } 198 199 func (m *MasternodeService) isActive() bool { 200 if atomic.LoadInt32(&m.inSync) == 0 { 201 return false 202 } 203 204 if m.owner != (common.Address{}) { 205 mninfo, err := m.registry.Info(m.address) 206 if err != nil { 207 log.Error("Masternode info fetch Err: %v", err) 208 return false 209 } 210 211 if mninfo.Owner != m.owner { 212 log.Error("Masternode owner mismatch", " needed=", m.owner, " got=", mninfo.Owner) 213 return false 214 } 215 } 216 217 res, err := m.registry.IsActive(m.address) 218 219 if err != nil { 220 log.Error("Masternode check failed", "err", err) 221 return false 222 } 223 224 return res 225 } 226 227 func (m *MasternodeService) loop() { 228 bc := m.eth.BlockChain() 229 230 chainHeadCh := make(chan core.ChainHeadEvent, chainHeadChanSize) 231 headSub := bc.SubscribeChainHeadEvent(chainHeadCh) 232 defer headSub.Unsubscribe() 233 234 events := m.eth.EventMux().Subscribe( 235 CheckpointProposalEvent{}, 236 ) 237 defer events.Unsubscribe() 238 239 //--- 240 for { 241 select { 242 case ev := <-events.Chan(): 243 if ev == nil { 244 return 245 } 246 switch ev.Data.(type) { 247 case CheckpointProposalEvent: 248 m.onCheckpoint(ev.Data.(CheckpointProposalEvent)) 249 } 250 break 251 252 case ev := <-chainHeadCh: 253 m.onChainHead(ev.Block) 254 break 255 256 // Shutdown 257 case <-headSub.Err(): 258 return 259 } 260 } 261 } 262 263 func (m *MasternodeService) onCheckpoint(cpe CheckpointProposalEvent) { 264 cpAddr := cpe.Proposal 265 266 cp, err := energi_abi.NewICheckpointV2Caller(cpAddr, m.eth.APIBackend) 267 if err != nil { 268 log.Error("Failed to create the checkpoint iface", "cp", cpAddr, "err", err) 269 return 270 } 271 272 callOpts := &m.cpRegistry.CallOpts 273 274 // Check if the current masternode has voted and vote on it if not yet. 275 can_vote, err := cp.CanVote(callOpts, m.address) 276 if err != nil { 277 log.Warn("Failed at Checkpoint.canVote()", "cp", cpAddr, "err", err) 278 return 279 } 280 281 if !can_vote { 282 return 283 } 284 285 if h := m.eth.BlockChain().GetHeaderByNumber(cpe.Number); h == nil { 286 log.Warn("Checkpoint Proposal is ahead of this MN blockchain", 287 "number", cpe.Number, "cp", cpe.Hash) 288 return 289 } else if h.Hash() != cpe.Hash { 290 log.Warn("Checkpoint Proposal is not aligned with this MN blockchain", 291 "number", cpe.Number, "header", h.Hash(), "cp", cpe.Hash) 292 return 293 } 294 295 log.Info("MN checkpoint signature not found, now generating a new one", "cp", cpAddr) 296 297 baseHash, err := cp.SignatureBase(callOpts) 298 if err != nil { 299 log.Error("Failed to get base hash", "cp", cpAddr, "err", err) 300 return 301 } 302 303 signature, err := crypto.Sign(baseHash[:], m.server.PrivateKey) 304 if err != nil { 305 log.Error("Failed to sign base hash", "cp", cpAddr, "err", err) 306 return 307 } 308 309 signature[64] += 27 310 311 m.cpVoteChan <- &checkpointVote{ 312 address: cpAddr, 313 signature: signature, 314 } 315 } 316 317 // voteOnCheckpoints recieves the identified checkpoints vote information and 318 // attempts to vote them in. 319 func (m *MasternodeService) voteOnCheckpoints() { 320 var cpVote *checkpointVote 321 322 for { 323 select { 324 case cpVote = <-m.cpVoteChan: 325 // Only vote on the latest due to zero fee triggers 326 break 327 328 default: 329 if cpVote != nil { 330 tx, err := m.cpRegistry.Sign(cpVote.address, cpVote.signature) 331 if tx != nil { 332 txhash := tx.Hash() 333 log.Warn("Voting on checkpoint", "addr", cpVote.address, "tx", txhash.Hex()) 334 } 335 336 if err != nil { 337 log.Error("Checkpoint vote failed", "checkpoint", cpVote.address, "err", err) 338 } 339 } 340 341 return 342 } 343 } 344 } 345 346 func (m *MasternodeService) onChainHead(block *types.Block) { 347 if !m.isActive() { 348 do_cleanup := m.validator.target != common.Address{} 349 m.validator.cancel() 350 351 if do_cleanup { 352 m.eth.TxPool().RemoveBySender(m.address) 353 } 354 return 355 } 356 357 // MN-4 - Heartbeats 358 now := time.Now() 359 360 if now.After(m.nextHB) { 361 // It is more important than invalidation duty. 362 // Some chance of race is still left, but at acceptable probability. 363 m.validator.cancel() 364 365 // Only present in IMasternodeRegistryV2 366 if ok, err := m.registry.CanHeartbeat(m.address); err == nil && !ok { 367 return 368 } 369 370 // Ensure heartbeat on clean queue 371 if !m.eth.TxPool().RemoveBySender(m.address) { 372 current := m.eth.BlockChain().CurrentHeader() 373 tx, err := m.registry.Heartbeat(current.Number, current.Hash(), m.features) 374 375 if err == nil { 376 log.Info("Masternode Heartbeat", "tx", tx.Hash()) 377 m.nextHB = now.Add(heartbeatInterval) 378 } else { 379 log.Error("Failed to send Masternode Heartbeat", "err", err) 380 m.nextHB = now.Add(recheckInterval) 381 } 382 } else { 383 // NOTE: we need to recover from Nonce mismatch to enable heartbeats 384 // as soon as possible. 385 log.Warn("Delaying Masternode Heartbeat due to pending zero-fee tx") 386 } 387 388 return 389 } 390 391 // Vote on the identified checkpoints. 392 m.voteOnCheckpoints() 393 394 // 395 target, err := m.registry.ValidationTarget(m.address) 396 if err != nil { 397 log.Warn("MNTarget error", "mn", m.address, "err", err) 398 m.validator.cancel() 399 return 400 } 401 402 // MN-14: validation duty 403 if old_target := m.validator.target; old_target != target { 404 m.validator.cancel() 405 m.validator = newPeerValidator(target, m) 406 407 // Only present in IMasternodeRegistryV2 408 if ok, err := m.registry.CanInvalidate(m.address); err == nil && !ok { 409 return 410 } 411 412 // Skip the first validation cycle to prevent possible DoS trigger on restart 413 if (old_target != common.Address{}) { 414 go m.validator.validate() 415 } 416 } 417 } 418 419 type peerValidator struct { 420 target common.Address 421 mnsvc *MasternodeService 422 cancelCh chan struct{} 423 } 424 425 func newPeerValidator( 426 target common.Address, 427 mnsvc *MasternodeService, 428 ) *peerValidator { 429 return &peerValidator{ 430 target: target, 431 mnsvc: mnsvc, 432 cancelCh: make(chan struct{}), 433 } 434 } 435 436 func (v *peerValidator) cancel() { 437 if v.mnsvc != nil { 438 close(v.cancelCh) 439 v.mnsvc = nil 440 } 441 } 442 443 func (v *peerValidator) validate() { 444 log.Debug("Masternode validation started", "target", v.target.Hex()) 445 defer log.Debug("Masternode validation stopped", "target", v.target.Hex()) 446 447 mnsvc := v.mnsvc 448 if mnsvc == nil { 449 return 450 } 451 server := mnsvc.server 452 453 //--- 454 mninfo, err := mnsvc.registry.Info(v.target) 455 if err != nil { 456 log.Warn("MNInfo error", "mn", v.target, "err", err) 457 return 458 } 459 460 cfg := mnsvc.eth.BlockChain().Config() 461 enode := energi_common.MastenodeEnode(mninfo.Ipv4address, mninfo.Enode, cfg) 462 463 if enode == nil { 464 log.Debug("Invalid ipv4address or public key was used") 465 return 466 } 467 468 // Check if the node is already connected as a peer and 469 // skip MN Validation if true. 470 if isFound := server.IsPeerActive(enode); isFound { 471 log.Debug("Masternode validation skipped since peer is already connected", 472 "target", v.target.Hex()) 473 return 474 } 475 476 peerCh := make(chan *p2p.PeerEvent) 477 defer close(peerCh) 478 479 peerSub := server.SubscribeEvents(peerCh) 480 481 // EnableMsg Events. 482 server.EnableMsgEvents = true 483 defer peerSub.Unsubscribe() 484 485 server.AddPeer(enode) 486 487 defer func() { 488 // Disconnect this peer if more than half of the max peers are connected. 489 if server.PeerCount() > server.MaxPeers/2 { 490 server.RemovePeer(enode) 491 } 492 }() 493 494 //--- 495 deadline := time.Now().Add(time.Minute) 496 497 for { 498 select { 499 case <-v.cancelCh: 500 return 501 case pe := <-peerCh: 502 if pe.Peer != enode.ID() || pe.Type != p2p.PeerEventTypeMsgRecv { 503 break 504 } 505 // TODO: validate block availability as per MN-14 506 return 507 case <-time.After(deadline.Sub(time.Now())): 508 log.Info("MN Invalidation", "mn", v.target) 509 _, err := mnsvc.registry.Invalidate(v.target) 510 if err != nil { 511 log.Warn("MN Invalidate error", "mn", v.target, "err", err) 512 } 513 return 514 } 515 } 516 }