github.com/status-im/status-go@v1.1.0/protocol/messenger_curated_communities.go (about)

     1  package protocol
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"reflect"
     7  	"time"
     8  
     9  	"go.uber.org/zap"
    10  
    11  	"github.com/ethereum/go-ethereum/accounts/abi/bind"
    12  	"github.com/status-im/status-go/eth-node/types"
    13  	"github.com/status-im/status-go/protocol/communities"
    14  )
    15  
    16  const (
    17  	curatedCommunitiesUpdateInterval = time.Hour
    18  	communitiesUpdateFailureInterval = time.Minute
    19  )
    20  
    21  // Regularly gets list of curated communities and signals them to client
    22  func (m *Messenger) startCuratedCommunitiesUpdateLoop() {
    23  	logger := m.logger.Named("curatedCommunitiesUpdateLoop")
    24  
    25  	if m.contractMaker == nil {
    26  		logger.Warn("not starting curated communities loop: contract maker not initialized")
    27  		return
    28  	}
    29  
    30  	go func() {
    31  		// Initialize interval to 0 for immediate execution
    32  		var interval time.Duration = 0
    33  
    34  		cache, err := m.communitiesManager.GetCuratedCommunities()
    35  		if err != nil {
    36  			logger.Error("failed to start curated communities loop", zap.Error(err))
    37  			return
    38  		}
    39  
    40  		for {
    41  			select {
    42  			case <-time.After(interval):
    43  				// Immediate execution on first run, then set to regular interval
    44  				interval = curatedCommunitiesUpdateInterval
    45  
    46  				curatedCommunities, err := m.getCuratedCommunitiesFromContract()
    47  				if err != nil {
    48  					interval = communitiesUpdateFailureInterval
    49  					logger.Error("failed to get curated communities from contract", zap.Error(err))
    50  					continue
    51  				}
    52  
    53  				if reflect.DeepEqual(cache.ContractCommunities, curatedCommunities.ContractCommunities) &&
    54  					reflect.DeepEqual(cache.ContractFeaturedCommunities, curatedCommunities.ContractFeaturedCommunities) {
    55  					// nothing changed
    56  					continue
    57  				}
    58  
    59  				err = m.communitiesManager.SetCuratedCommunities(curatedCommunities)
    60  				if err == nil {
    61  					cache = curatedCommunities
    62  				} else {
    63  					logger.Error("failed to save curated communities", zap.Error(err))
    64  				}
    65  
    66  				response, err := m.fetchCuratedCommunities(curatedCommunities)
    67  				if err != nil {
    68  					interval = communitiesUpdateFailureInterval
    69  					logger.Error("failed to fetch curated communities", zap.Error(err))
    70  					continue
    71  				}
    72  
    73  				m.config.messengerSignalsHandler.SendCuratedCommunitiesUpdate(response)
    74  
    75  			case <-m.quit:
    76  				return
    77  			}
    78  		}
    79  	}()
    80  }
    81  
    82  func (m *Messenger) getCuratedCommunitiesFromContract() (*communities.CuratedCommunities, error) {
    83  	if m.contractMaker == nil {
    84  		return nil, errors.New("contract maker not initialized")
    85  	}
    86  
    87  	testNetworksEnabled, err := m.settings.GetTestNetworksEnabled()
    88  	if err != nil {
    89  		return nil, err
    90  	}
    91  
    92  	chainID := uint64(10) // Optimism Mainnet
    93  	if testNetworksEnabled {
    94  		chainID = 420 // Optimism Goerli
    95  	}
    96  
    97  	directory, err := m.contractMaker.NewDirectory(chainID)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	callOpts := &bind.CallOpts{Context: context.Background(), Pending: false}
   103  
   104  	contractCommunities, err := directory.GetCommunities(callOpts)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	var contractCommunityIDs []string
   109  	for _, c := range contractCommunities {
   110  		contractCommunityIDs = append(contractCommunityIDs, types.HexBytes(c).String())
   111  	}
   112  
   113  	featuredContractCommunities, err := directory.GetFeaturedCommunities(callOpts)
   114  	if err != nil {
   115  		return nil, err
   116  	}
   117  	var contractFeaturedCommunityIDs []string
   118  	for _, c := range featuredContractCommunities {
   119  		contractFeaturedCommunityIDs = append(contractFeaturedCommunityIDs, types.HexBytes(c).String())
   120  	}
   121  
   122  	return &communities.CuratedCommunities{
   123  		ContractCommunities:         contractCommunityIDs,
   124  		ContractFeaturedCommunities: contractFeaturedCommunityIDs,
   125  	}, nil
   126  }
   127  
   128  func (m *Messenger) fetchCuratedCommunities(curatedCommunities *communities.CuratedCommunities) (*communities.KnownCommunitiesResponse, error) {
   129  	response, err := m.communitiesManager.GetStoredDescriptionForCommunities(curatedCommunities.ContractCommunities)
   130  	if err != nil {
   131  		return nil, err
   132  	}
   133  	response.ContractFeaturedCommunities = curatedCommunities.ContractFeaturedCommunities
   134  
   135  	// TODO: use mechanism to obtain shard from community ID (https://github.com/status-im/status-desktop/issues/12585)
   136  	var unknownCommunities []communities.CommunityShard
   137  	for _, u := range response.UnknownCommunities {
   138  		unknownCommunities = append(unknownCommunities, communities.CommunityShard{
   139  			CommunityID: u,
   140  		})
   141  	}
   142  
   143  	go func() {
   144  		_ = m.fetchCommunities(unknownCommunities)
   145  	}()
   146  
   147  	return response, nil
   148  }
   149  
   150  func (m *Messenger) CuratedCommunities() (*communities.KnownCommunitiesResponse, error) {
   151  	curatedCommunities, err := m.communitiesManager.GetCuratedCommunities()
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  	return m.fetchCuratedCommunities(curatedCommunities)
   156  }