github.com/mysteriumnetwork/node@v0.0.0-20240516044423-365054f76801/core/discovery/discovery.go (about) 1 /* 2 * Copyright (C) 2018 The "MysteriumNetwork/node" Authors. 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU 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 * This program 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 General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <http://www.gnu.org/licenses/>. 16 */ 17 18 package discovery 19 20 import ( 21 "sync" 22 "time" 23 24 "github.com/rs/zerolog/log" 25 26 "github.com/mysteriumnetwork/node/config" 27 "github.com/mysteriumnetwork/node/eventbus" 28 "github.com/mysteriumnetwork/node/identity" 29 "github.com/mysteriumnetwork/node/identity/registry" 30 "github.com/mysteriumnetwork/node/market" 31 ) 32 33 // Status describes stage of proposal registration 34 type Status int 35 36 // Proposal registration stages 37 const ( 38 IdentityUnregistered Status = iota 39 WaitingForRegistration 40 IdentityRegisterFailed 41 RegisterProposal 42 PingProposal 43 UnregisterProposal 44 UnregisterProposalFailed 45 ProposalUnregistered 46 StatusUndefined 47 ) 48 49 // Discovery structure holds discovery service state 50 type Discovery struct { 51 identityRegistry registry.IdentityRegistry 52 ownIdentity identity.Identity 53 proposalRegistry ProposalRegistry 54 proposalPingTTL time.Duration 55 signerCreate identity.SignerFactory 56 signer identity.Signer 57 proposal func() market.ServiceProposal 58 eventBus eventbus.EventBus 59 60 statusChan chan Status 61 status Status 62 proposalAnnouncementStopped *sync.WaitGroup 63 stop chan struct{} 64 once sync.Once 65 66 mu sync.RWMutex 67 } 68 69 // NewService creates new discovery service 70 func NewService( 71 identityRegistry registry.IdentityRegistry, 72 proposalRegistry ProposalRegistry, 73 proposalPingTTL time.Duration, 74 signerCreate identity.SignerFactory, 75 eventBus eventbus.EventBus, 76 ) *Discovery { 77 return &Discovery{ 78 identityRegistry: identityRegistry, 79 proposalRegistry: proposalRegistry, 80 proposalPingTTL: proposalPingTTL, 81 eventBus: eventBus, 82 signerCreate: signerCreate, 83 statusChan: make(chan Status), 84 status: StatusUndefined, 85 proposalAnnouncementStopped: &sync.WaitGroup{}, 86 stop: make(chan struct{}), 87 } 88 } 89 90 // Start launches discovery service 91 func (d *Discovery) Start(ownIdentity identity.Identity, proposal func() market.ServiceProposal) { 92 log.Info().Msg("Starting discovery...") 93 d.mu.RLock() 94 defer d.mu.RUnlock() 95 96 d.ownIdentity = ownIdentity 97 d.signer = d.signerCreate(ownIdentity) 98 d.proposal = proposal 99 100 d.proposalAnnouncementStopped.Add(1) 101 102 go d.checkRegistration() 103 104 go d.mainDiscoveryLoop() 105 } 106 107 // Wait wait for proposal announcements to stop / unregister 108 func (d *Discovery) Wait() { 109 d.proposalAnnouncementStopped.Wait() 110 } 111 112 // Stop stops discovery loop 113 func (d *Discovery) Stop() { 114 d.once.Do(func() { 115 close(d.stop) 116 }) 117 } 118 119 func (d *Discovery) mainDiscoveryLoop() { 120 defer d.proposalAnnouncementStopped.Done() 121 for { 122 select { 123 case <-d.stop: 124 d.stopLoop() 125 d.unregisterProposal() 126 return 127 case event := <-d.statusChan: 128 switch event { 129 case IdentityUnregistered: 130 d.registerIdentity() 131 case RegisterProposal: 132 go d.registerProposal() 133 case PingProposal: 134 go d.pingProposal() 135 case UnregisterProposal: 136 go d.unregisterProposal() 137 case IdentityRegisterFailed, ProposalUnregistered, UnregisterProposalFailed: 138 return 139 } 140 } 141 } 142 } 143 144 func (d *Discovery) stopLoop() { 145 log.Info().Msg("Stopping discovery loop...") 146 d.mu.RLock() 147 if d.status == WaitingForRegistration { 148 d.mu.RUnlock() 149 d.mu.RLock() 150 } 151 152 if d.status == RegisterProposal || d.status == PingProposal { 153 d.mu.RUnlock() 154 d.changeStatus(UnregisterProposal) 155 return 156 } 157 d.mu.RUnlock() 158 } 159 160 func (d *Discovery) handleRegistrationEvent(rep registry.AppEventIdentityRegistration) { 161 log.Debug().Msgf("Registration event received for %v", rep.ID.Address) 162 if rep.ID.Address != d.ownIdentity.Address { 163 log.Debug().Msgf("Identity mismatch for registration. Expected %v got %v", d.ownIdentity.Address, rep.ID.Address) 164 return 165 } 166 167 switch rep.Status { 168 case registry.Registered: 169 log.Info().Msg("Identity registered, proceeding with proposal registration") 170 d.changeStatus(RegisterProposal) 171 case registry.RegistrationError: 172 log.Info().Msg("Cancelled identity registration") 173 d.changeStatus(IdentityRegisterFailed) 174 default: 175 log.Info().Msgf("Received status %v ignoring", rep.Status) 176 } 177 } 178 179 func (d *Discovery) registerIdentity() { 180 log.Info().Msg("Waiting for registration success event") 181 d.eventBus.Subscribe(registry.AppTopicIdentityRegistration, d.handleRegistrationEvent) 182 d.changeStatus(WaitingForRegistration) 183 } 184 185 func (d *Discovery) registerProposal() { 186 proposal := d.proposal() 187 err := d.proposalRegistry.RegisterProposal(proposal, d.signer) 188 if err != nil { 189 log.Error().Err(err).Msg("Failed to register proposal, retrying after 1 min") 190 select { 191 case <-d.stop: 192 return 193 case <-time.After(1 * time.Minute): 194 d.changeStatus(RegisterProposal) 195 return 196 } 197 } 198 d.eventBus.Publish(AppTopicProposalAnnounce, proposal) 199 d.changeStatus(PingProposal) 200 } 201 202 func (d *Discovery) pingProposal() { 203 select { 204 case <-d.stop: 205 return 206 case <-time.After(d.proposalPingTTL): 207 proposal := d.proposal() 208 err := d.proposalRegistry.PingProposal(proposal, d.signer) 209 if err != nil { 210 log.Error().Err(err).Msg("Failed to ping proposal") 211 } 212 d.eventBus.Publish(AppTopicProposalAnnounce, proposal) 213 d.changeStatus(PingProposal) 214 } 215 } 216 217 func (d *Discovery) unregisterProposal() { 218 proposal := d.proposal() 219 err := d.proposalRegistry.UnregisterProposal(proposal, d.signer) 220 if err != nil { 221 log.Error().Err(err).Msg("Failed to unregister proposal: ") 222 d.changeStatus(UnregisterProposalFailed) 223 } 224 log.Info().Msg("Proposal unregistered") 225 d.changeStatus(ProposalUnregistered) 226 } 227 228 func (d *Discovery) checkRegistration() { 229 // check if node's identity is registered 230 chainID := config.GetInt64(config.FlagChainID) 231 status, err := d.identityRegistry.GetRegistrationStatus(chainID, d.ownIdentity) 232 if err != nil { 233 log.Error().Err(err).Msg("Checking identity registration failed") 234 d.changeStatus(IdentityRegisterFailed) 235 return 236 } 237 switch status { 238 case registry.Registered: 239 d.changeStatus(RegisterProposal) 240 default: 241 log.Info().Msgf("Identity %s not registered, delaying proposal registration until identity is registered", d.ownIdentity.Address) 242 d.changeStatus(IdentityUnregistered) 243 return 244 } 245 } 246 247 func (d *Discovery) changeStatus(status Status) { 248 d.mu.Lock() 249 defer d.mu.Unlock() 250 d.status = status 251 252 go func() { 253 d.statusChan <- status 254 }() 255 }