feat: Add community manager and fetch cached community metadata (#4450)

This commit is contained in:
Cuteivist 2023-12-14 17:50:46 +01:00 committed by GitHub
parent 64a0d9e340
commit 7af313cd53
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 153 additions and 99 deletions

View File

@ -128,7 +128,7 @@ func (b *StatusNode) initServices(config *params.NodeConfig, mediaServer *server
services = append(services, wakuext)
b.SetWalletCollectibleCommunityInfoProvider(wakuext)
b.SetWalletCommunityInfoProvider(wakuext)
}
if config.WakuV2Config.Enabled {
@ -155,7 +155,7 @@ func (b *StatusNode) initServices(config *params.NodeConfig, mediaServer *server
services = append(services, wakuext)
b.SetWalletCollectibleCommunityInfoProvider(wakuext)
b.SetWalletCommunityInfoProvider(wakuext)
}
// We ignore for now local notifications flag as users who are upgrading have no mean to enable it
@ -518,9 +518,9 @@ func (b *StatusNode) WalletService() *wallet.Service {
return b.walletSrvc
}
func (b *StatusNode) SetWalletCollectibleCommunityInfoProvider(provider thirdparty.CollectibleCommunityInfoProvider) {
func (b *StatusNode) SetWalletCommunityInfoProvider(provider thirdparty.CommunityInfoProvider) {
if b.walletSrvc != nil {
b.walletSrvc.SetCollectibleCommunityInfoProvider(provider)
b.walletSrvc.SetWalletCommunityInfoProvider(provider)
}
}

View File

@ -65,6 +65,7 @@ import (
localnotifications "github.com/status-im/status-go/services/local-notifications"
mailserversDB "github.com/status-im/status-go/services/mailservers"
"github.com/status-im/status-go/services/wallet"
"github.com/status-im/status-go/services/wallet/community"
"github.com/status-im/status-go/services/wallet/token"
"github.com/status-im/status-go/signal"
"github.com/status-im/status-go/telemetry"
@ -459,7 +460,7 @@ func NewMessenger(
if c.tokenManager != nil {
managerOptions = append(managerOptions, communities.WithTokenManager(c.tokenManager))
} else if c.rpcClient != nil {
tokenManager := token.NewTokenManager(c.walletDb, c.rpcClient, c.rpcClient.NetworkManager, database)
tokenManager := token.NewTokenManager(c.walletDb, c.rpcClient, community.NewManager(database), c.rpcClient.NetworkManager, database)
managerOptions = append(managerOptions, communities.WithTokenManager(communities.NewDefaultTokenManager(tokenManager)))
}

View File

@ -17,7 +17,6 @@ import (
"github.com/status-im/status-go/services/accounts/settingsevent"
"github.com/status-im/status-go/services/wallet/async"
walletCommon "github.com/status-im/status-go/services/wallet/common"
"github.com/status-im/status-go/services/wallet/community"
"github.com/status-im/status-go/services/wallet/transfer"
"github.com/status-im/status-go/services/wallet/walletevent"
)
@ -35,7 +34,6 @@ type timerPerAddressAndChainID = map[common.Address]timerPerChainID
type Controller struct {
manager *Manager
ownershipDB *OwnershipDB
communityDB *community.DataDB
walletFeed *event.Feed
accountsDB *accounts.Database
accountsFeed *event.Feed
@ -67,7 +65,6 @@ func NewController(
return &Controller{
manager: manager,
ownershipDB: NewOwnershipDB(db),
communityDB: community.NewDataDB(db),
walletFeed: walletFeed,
accountsDB: accountsDB,
accountsFeed: accountsFeed,

View File

@ -4,7 +4,6 @@ import (
"context"
"database/sql"
"errors"
"fmt"
"math/big"
"net/http"
"strings"
@ -27,7 +26,6 @@ import (
)
const requestTimeout = 5 * time.Second
const failedCommunityFetchRetryDelay = 1 * time.Hour
const hystrixContractOwnershipClientName = "contractOwnershipClient"
@ -56,13 +54,12 @@ type Manager struct {
collectibleDataProviders []thirdparty.CollectibleDataProvider
collectionDataProviders []thirdparty.CollectionDataProvider
collectibleProviders []thirdparty.CollectibleProvider
communityInfoProvider thirdparty.CollectibleCommunityInfoProvider
httpClient *http.Client
collectiblesDataDB *CollectibleDataDB
collectionsDataDB *CollectionDataDB
communityDataDB *community.DataDB
communityManager *community.Manager
statuses map[string]*connection.Status
statusNotifier *connection.StatusNotifier
@ -71,6 +68,7 @@ type Manager struct {
func NewManager(
db *sql.DB,
rpcClient *rpc.Client,
communityManager *community.Manager,
contractOwnershipProviders []thirdparty.CollectibleContractOwnershipProvider,
accountOwnershipProviders []thirdparty.CollectibleAccountOwnershipProvider,
collectibleDataProviders []thirdparty.CollectibleDataProvider,
@ -136,7 +134,7 @@ func NewManager(
},
collectiblesDataDB: NewCollectibleDataDB(db),
collectionsDataDB: NewCollectionDataDB(db),
communityDataDB: community.NewDataDB(db),
communityManager: communityManager,
statuses: statuses,
statusNotifier: statusNotifier,
}
@ -198,11 +196,6 @@ func (o *Manager) doContentTypeRequest(ctx context.Context, url string) (string,
return resp.Header.Get("Content-Type"), nil
}
// Used to break circular dependency, call once as soon as possible after initialization
func (o *Manager) SetCommunityInfoProvider(communityInfoProvider thirdparty.CollectibleCommunityInfoProvider) {
o.communityInfoProvider = communityInfoProvider
}
// Need to combine different providers to support all needed ChainIDs
func (o *Manager) FetchBalancesByOwnerAndContractAddress(ctx context.Context, chainID walletCommon.ChainID, ownerAddress common.Address, contractAddresses []common.Address) (thirdparty.TokenBalancesPerContractAddress, error) {
ret := make(thirdparty.TokenBalancesPerContractAddress)
@ -611,65 +604,22 @@ func (o *Manager) fillCommunityID(asset *thirdparty.FullCollectibleData) error {
communityID := ""
if tokenURI != "" {
communityID = o.communityInfoProvider.GetCommunityID(tokenURI)
communityID = o.communityManager.GetCommunityID(tokenURI)
}
asset.CollectibleData.CommunityID = communityID
return nil
}
func (o *Manager) mustFetchCommunityInfo(communityID string) bool {
// See if we have cached data
_, state, err := o.communityDataDB.GetCommunityInfo(communityID)
if err != nil {
return true
}
// If we don't have a state, this community has never been fetched before
if state == nil {
return true
}
// If the last fetch was successful, we can safely refresh our cache
if state.LastUpdateSuccesful {
return true
}
// If the last fetch was not successful, we should only retry after a delay
if time.Unix(int64(state.LastUpdateTimestamp), 0).Add(failedCommunityFetchRetryDelay).Before(time.Now()) {
return true
}
return false
}
func (o *Manager) fetchCommunityInfo(communityID string) (*thirdparty.CommunityInfo, error) {
if !o.mustFetchCommunityInfo(communityID) {
return nil, fmt.Errorf("backing off fetchCommunityInfo for id: %s", communityID)
}
communityInfo, err := o.communityInfoProvider.FetchCommunityInfo(communityID)
if err != nil {
dbErr := o.communityDataDB.SetCommunityInfo(communityID, nil)
if dbErr != nil {
log.Error("SetCommunityInfo failed", "communityID", communityID, "err", dbErr)
}
return nil, err
}
err = o.communityDataDB.SetCommunityInfo(communityID, communityInfo)
return communityInfo, err
}
func (o *Manager) fillCommunityInfo(communityID string, communityAssets []*thirdparty.FullCollectibleData) error {
communityInfo, err := o.fetchCommunityInfo(communityID)
communityInfo, err := o.communityManager.FetchCommunityInfo(communityID)
if err != nil {
return err
}
if communityInfo != nil {
for _, communityAsset := range communityAssets {
err := o.communityInfoProvider.FillCollectibleMetadata(communityAsset)
err := o.communityManager.FillCollectibleMetadata(communityAsset)
if err != nil {
return err
}

View File

@ -73,7 +73,7 @@ type Service struct {
controller *Controller
db *sql.DB
ownershipDB *OwnershipDB
communityDB *community.DataDB
communityManager *community.Manager
walletFeed *event.Feed
scheduler *async.MultiClientScheduler
}
@ -84,6 +84,7 @@ func NewService(
accountsDB *accounts.Database,
accountsFeed *event.Feed,
settingsFeed *event.Feed,
communityManager *community.Manager,
networkManager *network.Manager,
manager *Manager) *Service {
s := &Service{
@ -91,7 +92,7 @@ func NewService(
controller: NewController(db, walletFeed, accountsDB, accountsFeed, settingsFeed, networkManager, manager),
db: db,
ownershipDB: NewOwnershipDB(db),
communityDB: community.NewDataDB(db),
communityManager: communityManager,
walletFeed: walletFeed,
scheduler: async.NewMultiClientScheduler(),
}
@ -413,7 +414,7 @@ func (s *Service) fullCollectiblesDataToHeaders(data []thirdparty.FullCollectibl
header := fullCollectibleDataToHeader(c)
if c.CollectibleData.CommunityID != "" {
communityInfo, _, err := s.communityDB.GetCommunityInfo(c.CollectibleData.CommunityID)
communityInfo, _, err := s.communityManager.GetCommunityInfo(c.CollectibleData.CommunityID)
if err != nil {
return nil, err
}
@ -435,7 +436,7 @@ func (s *Service) fullCollectiblesDataToDetails(data []thirdparty.FullCollectibl
details := fullCollectibleDataToDetails(c)
if c.CollectibleData.CommunityID != "" {
communityInfo, _, err := s.communityDB.GetCommunityInfo(c.CollectibleData.CommunityID)
communityInfo, _, err := s.communityManager.GetCommunityInfo(c.CollectibleData.CommunityID)
if err != nil {
return nil, err
}
@ -461,7 +462,7 @@ func (s *Service) fullCollectiblesDataToCommunityHeader(data []thirdparty.FullCo
continue
}
communityInfo, _, err := s.communityDB.GetCommunityInfo(communityID)
communityInfo, _, err := s.communityManager.GetCommunityInfo(communityID)
if err != nil {
log.Error("Error fetching community info", "error", err)
continue

View File

@ -0,0 +1,87 @@
package community
import (
"database/sql"
"fmt"
"time"
"github.com/ethereum/go-ethereum/log"
"github.com/status-im/status-go/services/wallet/thirdparty"
)
const failedCommunityFetchRetryDelay = 1 * time.Hour
type Manager struct {
db *DataDB
communityInfoProvider thirdparty.CommunityInfoProvider
}
func NewManager(db *sql.DB) *Manager {
return &Manager{
db: NewDataDB(db),
}
}
// Used to break circular dependency, call once as soon as possible after initialization
func (cm *Manager) SetCommunityInfoProvider(communityInfoProvider thirdparty.CommunityInfoProvider) {
cm.communityInfoProvider = communityInfoProvider
}
func (cm *Manager) GetCommunityInfo(id string) (*thirdparty.CommunityInfo, *InfoState, error) {
return cm.db.GetCommunityInfo(id)
}
func (cm *Manager) GetCommunityID(tokenURI string) string {
return cm.communityInfoProvider.GetCommunityID(tokenURI)
}
func (cm *Manager) FillCollectibleMetadata(c *thirdparty.FullCollectibleData) error {
return cm.communityInfoProvider.FillCollectibleMetadata(c)
}
func (cm *Manager) setCommunityInfo(id string, c *thirdparty.CommunityInfo) (err error) {
return cm.db.SetCommunityInfo(id, c)
}
func (cm *Manager) mustFetchCommunityInfo(communityID string) bool {
// See if we have cached data
_, state, err := cm.GetCommunityInfo(communityID)
if err != nil {
return true
}
// If we don't have a state, this community has never been fetched before
if state == nil {
return true
}
// If the last fetch was successful, we can safely refresh our cache
if state.LastUpdateSuccesful {
return true
}
// If the last fetch was not successful, we should only retry after a delay
if time.Unix(int64(state.LastUpdateTimestamp), 0).Add(failedCommunityFetchRetryDelay).Before(time.Now()) {
return true
}
return false
}
func (cm *Manager) FetchCommunityInfo(communityID string) (*thirdparty.CommunityInfo, error) {
if !cm.mustFetchCommunityInfo(communityID) {
return nil, fmt.Errorf("backing off fetchCommunityInfo for id: %s", communityID)
}
communityInfo, err := cm.communityInfoProvider.FetchCommunityInfo(communityID)
if err != nil {
dbErr := cm.setCommunityInfo(communityID, nil)
if dbErr != nil {
log.Error("SetCommunityInfo failed", "communityID", communityID, "err", dbErr)
}
return nil, err
}
err = cm.setCommunityInfo(communityID, communityInfo)
return communityInfo, err
}

View File

@ -21,6 +21,7 @@ import (
"github.com/status-im/status-go/services/wallet/activity"
"github.com/status-im/status-go/services/wallet/balance"
"github.com/status-im/status-go/services/wallet/collectibles"
"github.com/status-im/status-go/services/wallet/community"
"github.com/status-im/status-go/services/wallet/currency"
"github.com/status-im/status-go/services/wallet/history"
"github.com/status-im/status-go/services/wallet/market"
@ -96,8 +97,9 @@ func NewService(
})
})
communityManager := community.NewManager(db)
balanceCacher := balance.NewCacherWithTTL(5 * time.Minute)
tokenManager := token.NewTokenManager(db, rpcClient, rpcClient.NetworkManager, appDB)
tokenManager := token.NewTokenManager(db, rpcClient, communityManager, rpcClient.NetworkManager, appDB)
savedAddressesManager := &SavedAddressesManager{db: db}
transactionManager := transfer.NewTransactionManager(db, gethManager, transactor, config, accountsDB, pendingTxManager, feed)
transferController := transfer.NewTransferController(db, accountsDB, rpcClient, accountFeed, feed, transactionManager, pendingTxManager,
@ -140,8 +142,8 @@ func NewService(
alchemyClient,
}
collectiblesManager := collectibles.NewManager(db, rpcClient, contractOwnershipProviders, accountOwnershipProviders, collectibleDataProviders, collectionDataProviders, feed)
collectibles := collectibles.NewService(db, feed, accountsDB, accountFeed, settingsFeed, rpcClient.NetworkManager, collectiblesManager)
collectiblesManager := collectibles.NewManager(db, rpcClient, communityManager, contractOwnershipProviders, accountOwnershipProviders, collectibleDataProviders, collectionDataProviders, feed)
collectibles := collectibles.NewService(db, feed, accountsDB, accountFeed, settingsFeed, communityManager, rpcClient.NetworkManager, collectiblesManager)
activity := activity.NewService(db, tokenManager, collectiblesManager, feed)
@ -152,6 +154,7 @@ func NewService(
accountsDB: accountsDB,
rpcClient: rpcClient,
tokenManager: tokenManager,
communityManager: communityManager,
savedAddressesManager: savedAddressesManager,
transactionManager: transactionManager,
pendingTxManager: pendingTxManager,
@ -185,6 +188,7 @@ type Service struct {
rpcClient *rpc.Client
savedAddressesManager *SavedAddressesManager
tokenManager *token.Manager
communityManager *community.Manager
transactionManager *transfer.TransactionManager
pendingTxManager *transactions.PendingTxTracker
cryptoOnRampManager *CryptoOnRampManager
@ -223,8 +227,8 @@ func (s *Service) Start() error {
}
// Set external Collectibles community info provider
func (s *Service) SetCollectibleCommunityInfoProvider(provider thirdparty.CollectibleCommunityInfoProvider) {
s.collectiblesManager.SetCommunityInfoProvider(provider)
func (s *Service) SetWalletCommunityInfoProvider(provider thirdparty.CommunityInfoProvider) {
s.communityManager.SetCommunityInfoProvider(provider)
}
// Stop reactor and close db.

View File

@ -216,8 +216,3 @@ type CollectionDataProvider interface {
CollectibleProvider
FetchCollectionsDataByContractID(ctx context.Context, ids []ContractID) ([]CollectionData, error)
}
type CollectibleCommunityInfoProvider interface {
CommunityInfoProvider
FillCollectibleMetadata(collectible *FullCollectibleData) error
}

View File

@ -10,4 +10,7 @@ type CommunityInfo struct {
type CommunityInfoProvider interface {
GetCommunityID(tokenURI string) string
FetchCommunityInfo(communityID string) (*CommunityInfo, error)
// Collectible-related methods
FillCollectibleMetadata(collectible *FullCollectibleData) error
}

View File

@ -26,6 +26,7 @@ import (
"github.com/status-im/status-go/services/communitytokens"
"github.com/status-im/status-go/services/utils"
"github.com/status-im/status-go/services/wallet/async"
"github.com/status-im/status-go/services/wallet/community"
)
var requestTimeout = 20 * time.Second
@ -55,7 +56,7 @@ type CommunityData struct {
ID string `json:"id"`
Name string `json:"name"`
Color string `json:"color"`
ImageURL *string `json:"image_url,omitempty"`
Image string `json:"image,omitempty"`
}
func (t *Token) IsNative() bool {
@ -86,6 +87,7 @@ type Manager struct {
networkManager *network.Manager
stores []store // Set on init, not changed afterwards
communityTokensDB *communitytokens.Database
communityManager *community.Manager
tokens []*Token
@ -110,6 +112,7 @@ func mergeTokens(sliceLists [][]*Token) []*Token {
func NewTokenManager(
db *sql.DB,
RPCClient *rpc.Client,
communityManager *community.Manager,
networkManager *network.Manager,
appDB *sql.DB,
) *Manager {
@ -143,6 +146,7 @@ func NewTokenManager(
RPCClient: RPCClient,
contractMaker: maker,
networkManager: networkManager,
communityManager: communityManager,
stores: stores,
communityTokensDB: communitytokens.NewCommunityTokensDatabase(appDB),
tokens: tokens,
@ -532,6 +536,16 @@ func (tm *Manager) getTokensFromDB(query string, args ...any) ([]*Token, error)
}
}
if tm.communityManager != nil && token.CommunityData != nil {
communityInfo, _, err := tm.communityManager.GetCommunityInfo(token.CommunityData.ID)
if err == nil && communityInfo != nil {
// Fetched data from cache. Cache is refreshed during every wallet token list call.
token.CommunityData.Name = communityInfo.CommunityName
token.CommunityData.Color = communityInfo.CommunityColor
token.CommunityData.Image = communityInfo.CommunityImage
}
}
rst = append(rst, token)
}

View File

@ -23,6 +23,7 @@ func setupTestTokenDB(t *testing.T) (*Manager, func()) {
networkManager: nil,
stores: nil,
communityTokensDB: nil,
communityManager: nil,
}, func() {
require.NoError(t, db.Close())
}

View File

@ -29,6 +29,7 @@ import (
"github.com/status-im/status-go/rpc/chain"
"github.com/status-im/status-go/services/wallet/async"
"github.com/status-im/status-go/services/wallet/balance"
"github.com/status-im/status-go/services/wallet/community"
"github.com/status-im/status-go/t/helpers"
"github.com/status-im/status-go/t/utils"
@ -932,7 +933,7 @@ func TestFindBlocksCommand(t *testing.T) {
}
client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db)
client.SetClient(tc.NetworkID(), tc)
tokenManager := token.NewTokenManager(db, client, network.NewManager(appdb), appdb)
tokenManager := token.NewTokenManager(db, client, community.NewManager(appdb), network.NewManager(appdb), appdb)
tokenManager.SetTokens([]*token.Token{
{
Address: tokenTXXAddress,
@ -1054,7 +1055,7 @@ func TestFetchTransfersForLoadedBlocks(t *testing.T) {
client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db)
client.SetClient(tc.NetworkID(), tc)
tokenManager := token.NewTokenManager(db, client, network.NewManager(appdb), appdb)
tokenManager := token.NewTokenManager(db, client, community.NewManager(appdb), network.NewManager(appdb), appdb)
tokenManager.SetTokens([]*token.Token{
{
@ -1171,7 +1172,7 @@ func TestFetchNewBlocksCommand_findBlocksWithEthTransfers(t *testing.T) {
client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db)
client.SetClient(tc.NetworkID(), tc)
tokenManager := token.NewTokenManager(db, client, network.NewManager(appdb), appdb)
tokenManager := token.NewTokenManager(db, client, community.NewManager(appdb), network.NewManager(appdb), appdb)
tokenManager.SetTokens([]*token.Token{
{
@ -1245,7 +1246,7 @@ func TestFetchNewBlocksCommand(t *testing.T) {
client, _ := statusRpc.NewClient(nil, 1, params.UpstreamRPCConfig{Enabled: false, URL: ""}, []params.Network{}, db)
client.SetClient(tc.NetworkID(), tc)
tokenManager := token.NewTokenManager(db, client, network.NewManager(appdb), appdb)
tokenManager := token.NewTokenManager(db, client, community.NewManager(appdb), network.NewManager(appdb), appdb)
tokenManager.SetTokens([]*token.Token{
{