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  }