feat(wallet): refactored collectibles manager using circuit breaker
Removed goerli from rarible and opt-goerli from alchemy clients as not supported any more
This commit is contained in:
parent
7d7fa07754
commit
b0a0f078c4
|
@ -8,14 +8,14 @@ import (
|
||||||
"math/big"
|
"math/big"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/afex/hystrix-go/hystrix"
|
|
||||||
|
|
||||||
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
"github.com/ethereum/go-ethereum/accounts/abi/bind"
|
||||||
"github.com/ethereum/go-ethereum/common"
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/event"
|
"github.com/ethereum/go-ethereum/event"
|
||||||
"github.com/ethereum/go-ethereum/log"
|
"github.com/ethereum/go-ethereum/log"
|
||||||
|
"github.com/status-im/status-go/circuitbreaker"
|
||||||
"github.com/status-im/status-go/contracts/community-tokens/collectibles"
|
"github.com/status-im/status-go/contracts/community-tokens/collectibles"
|
||||||
"github.com/status-im/status-go/contracts/ierc1155"
|
"github.com/status-im/status-go/contracts/ierc1155"
|
||||||
"github.com/status-im/status-go/rpc"
|
"github.com/status-im/status-go/rpc"
|
||||||
|
@ -32,8 +32,6 @@ import (
|
||||||
const requestTimeout = 5 * time.Second
|
const requestTimeout = 5 * time.Second
|
||||||
const signalUpdatedCollectiblesDataPageSize = 10
|
const signalUpdatedCollectiblesDataPageSize = 10
|
||||||
|
|
||||||
const hystrixContractOwnershipClientName = "contractOwnershipClient"
|
|
||||||
|
|
||||||
const EventCollectiblesConnectionStatusChanged walletevent.EventType = "wallet-collectible-status-changed"
|
const EventCollectiblesConnectionStatusChanged walletevent.EventType = "wallet-collectible-status-changed"
|
||||||
|
|
||||||
// ERC721 does not support function "TokenURI" if call
|
// ERC721 does not support function "TokenURI" if call
|
||||||
|
@ -69,9 +67,10 @@ type Manager struct {
|
||||||
|
|
||||||
mediaServer *server.MediaServer
|
mediaServer *server.MediaServer
|
||||||
|
|
||||||
statuses map[string]*connection.Status
|
statuses map[string]*connection.Status
|
||||||
statusNotifier *connection.StatusNotifier
|
statusNotifier *connection.StatusNotifier
|
||||||
feed *event.Feed
|
feed *event.Feed
|
||||||
|
circuitBreakers sync.Map
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewManager(
|
func NewManager(
|
||||||
|
@ -84,12 +83,6 @@ func NewManager(
|
||||||
collectionDataProviders []thirdparty.CollectionDataProvider,
|
collectionDataProviders []thirdparty.CollectionDataProvider,
|
||||||
mediaServer *server.MediaServer,
|
mediaServer *server.MediaServer,
|
||||||
feed *event.Feed) *Manager {
|
feed *event.Feed) *Manager {
|
||||||
hystrix.ConfigureCommand(hystrixContractOwnershipClientName, hystrix.CommandConfig{
|
|
||||||
Timeout: 10000,
|
|
||||||
MaxConcurrentRequests: 100,
|
|
||||||
SleepWindow: 300000,
|
|
||||||
ErrorPercentThreshold: 25,
|
|
||||||
})
|
|
||||||
|
|
||||||
ownershipDB := NewOwnershipDB(db)
|
ownershipDB := NewOwnershipDB(db)
|
||||||
|
|
||||||
|
@ -161,35 +154,6 @@ func mapToList[K comparable, T any](m map[K]T) []T {
|
||||||
return list
|
return list
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeContractOwnershipCall(main func() (any, error), fallback func() (any, error)) (any, error) {
|
|
||||||
resultChan := make(chan any, 1)
|
|
||||||
errChan := hystrix.Go(hystrixContractOwnershipClientName, func() error {
|
|
||||||
res, err := main()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resultChan <- res
|
|
||||||
return nil
|
|
||||||
}, func(err error) error {
|
|
||||||
if fallback == nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
res, err := fallback()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
resultChan <- res
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
select {
|
|
||||||
case result := <-resultChan:
|
|
||||||
return result, nil
|
|
||||||
case err := <-errChan:
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Manager) doContentTypeRequest(ctx context.Context, url string) (string, error) {
|
func (o *Manager) doContentTypeRequest(ctx context.Context, url string) (string, error) {
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodHead, url, nil)
|
req, err := http.NewRequestWithContext(ctx, http.MethodHead, url, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -254,68 +218,91 @@ func (o *Manager) FetchBalancesByOwnerAndContractAddress(ctx context.Context, ch
|
||||||
func (o *Manager) FetchAllAssetsByOwnerAndContractAddress(ctx context.Context, chainID walletCommon.ChainID, owner common.Address, contractAddresses []common.Address, cursor string, limit int, providerID string) (*thirdparty.FullCollectibleDataContainer, error) {
|
func (o *Manager) FetchAllAssetsByOwnerAndContractAddress(ctx context.Context, chainID walletCommon.ChainID, owner common.Address, contractAddresses []common.Address, cursor string, limit int, providerID string) (*thirdparty.FullCollectibleDataContainer, error) {
|
||||||
defer o.checkConnectionStatus(chainID)
|
defer o.checkConnectionStatus(chainID)
|
||||||
|
|
||||||
anyProviderAvailable := false
|
cmd := circuitbreaker.Command{}
|
||||||
for _, provider := range o.accountOwnershipProviders {
|
for _, provider := range o.accountOwnershipProviders {
|
||||||
if !provider.IsChainSupported(chainID) {
|
if !provider.IsChainSupported(chainID) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
anyProviderAvailable = true
|
|
||||||
if providerID != thirdparty.FetchFromAnyProvider && providerID != provider.ID() {
|
if providerID != thirdparty.FetchFromAnyProvider && providerID != provider.ID() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
assetContainer, err := provider.FetchAllAssetsByOwnerAndContractAddress(ctx, chainID, owner, contractAddresses, cursor, limit)
|
provider := provider
|
||||||
if err != nil {
|
f := circuitbreaker.NewFunctor(
|
||||||
log.Error("FetchAllAssetsByOwnerAndContractAddress failed for", "provider", provider.ID(), "chainID", chainID, "err", err)
|
func() ([]interface{}, error) {
|
||||||
continue
|
assetContainer, err := provider.FetchAllAssetsByOwnerAndContractAddress(ctx, chainID, owner, contractAddresses, cursor, limit)
|
||||||
}
|
if err != nil {
|
||||||
|
log.Error("FetchAllAssetsByOwnerAndContractAddress failed for", "provider", provider.ID(), "chainID", chainID, "err", err)
|
||||||
_, err = o.processFullCollectibleData(ctx, assetContainer.Items, true)
|
}
|
||||||
if err != nil {
|
return []interface{}{assetContainer}, err
|
||||||
return nil, err
|
},
|
||||||
}
|
)
|
||||||
|
cmd.Add(f)
|
||||||
return assetContainer, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if anyProviderAvailable {
|
if cmd.IsEmpty() {
|
||||||
return nil, ErrAllProvidersFailedForChainID
|
return nil, ErrNoProvidersAvailableForChainID
|
||||||
}
|
}
|
||||||
return nil, ErrNoProvidersAvailableForChainID
|
|
||||||
|
cmdRes := o.getCircuitBreaker(chainID).Execute(cmd)
|
||||||
|
if cmdRes.Error() != nil {
|
||||||
|
log.Error("FetchAllAssetsByOwnerAndContractAddress failed for", "chainID", chainID, "err", cmdRes.Error())
|
||||||
|
return nil, cmdRes.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
assetContainer := cmdRes.Result()[0].(*thirdparty.FullCollectibleDataContainer)
|
||||||
|
_, err := o.processFullCollectibleData(ctx, assetContainer.Items, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return assetContainer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Manager) FetchAllAssetsByOwner(ctx context.Context, chainID walletCommon.ChainID, owner common.Address, cursor string, limit int, providerID string) (*thirdparty.FullCollectibleDataContainer, error) {
|
func (o *Manager) FetchAllAssetsByOwner(ctx context.Context, chainID walletCommon.ChainID, owner common.Address, cursor string, limit int, providerID string) (*thirdparty.FullCollectibleDataContainer, error) {
|
||||||
defer o.checkConnectionStatus(chainID)
|
defer o.checkConnectionStatus(chainID)
|
||||||
|
|
||||||
anyProviderAvailable := false
|
cmd := circuitbreaker.Command{}
|
||||||
for _, provider := range o.accountOwnershipProviders {
|
for _, provider := range o.accountOwnershipProviders {
|
||||||
if !provider.IsChainSupported(chainID) {
|
if !provider.IsChainSupported(chainID) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
anyProviderAvailable = true
|
|
||||||
if providerID != thirdparty.FetchFromAnyProvider && providerID != provider.ID() {
|
if providerID != thirdparty.FetchFromAnyProvider && providerID != provider.ID() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
assetContainer, err := provider.FetchAllAssetsByOwner(ctx, chainID, owner, cursor, limit)
|
provider := provider
|
||||||
if err != nil {
|
f := circuitbreaker.NewFunctor(
|
||||||
log.Error("FetchAllAssetsByOwner failed for", "provider", provider.ID(), "chainID", chainID, "err", err)
|
func() ([]interface{}, error) {
|
||||||
continue
|
assetContainer, err := provider.FetchAllAssetsByOwner(ctx, chainID, owner, cursor, limit)
|
||||||
}
|
if err != nil {
|
||||||
|
log.Error("FetchAllAssetsByOwner failed for", "provider", provider.ID(), "chainID", chainID, "err", err)
|
||||||
_, err = o.processFullCollectibleData(ctx, assetContainer.Items, true)
|
}
|
||||||
if err != nil {
|
return []interface{}{assetContainer}, err
|
||||||
return nil, err
|
},
|
||||||
}
|
)
|
||||||
|
cmd.Add(f)
|
||||||
return assetContainer, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if anyProviderAvailable {
|
if cmd.IsEmpty() {
|
||||||
return nil, ErrAllProvidersFailedForChainID
|
return nil, ErrNoProvidersAvailableForChainID
|
||||||
}
|
}
|
||||||
return nil, ErrNoProvidersAvailableForChainID
|
|
||||||
|
cmdRes := o.getCircuitBreaker(chainID).Execute(cmd)
|
||||||
|
if cmdRes.Error() != nil {
|
||||||
|
log.Error("FetchAllAssetsByOwner failed for", "chainID", chainID, "err", cmdRes.Error())
|
||||||
|
return nil, cmdRes.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
assetContainer := cmdRes.Result()[0].(*thirdparty.FullCollectibleDataContainer)
|
||||||
|
_, err := o.processFullCollectibleData(ctx, assetContainer.Items, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return assetContainer, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Manager) FetchERC1155Balances(ctx context.Context, owner common.Address, chainID walletCommon.ChainID, contractAddress common.Address, tokenIDs []*bigint.BigInt) ([]*bigint.BigInt, error) {
|
func (o *Manager) FetchERC1155Balances(ctx context.Context, owner common.Address, chainID walletCommon.ChainID, contractAddress common.Address, tokenIDs []*bigint.BigInt) ([]*bigint.BigInt, error) {
|
||||||
if len(tokenIDs) == 0 {
|
if len(tokenIDs) == 0 {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
|
@ -420,50 +407,81 @@ func (o *Manager) FetchCollectibleOwnershipByOwner(ctx context.Context, chainID
|
||||||
// If asyncFetch is true, empty metadata will be returned for any missing collectibles and an EventCollectiblesDataUpdated will be sent when the data is ready.
|
// If asyncFetch is true, empty metadata will be returned for any missing collectibles and an EventCollectiblesDataUpdated will be sent when the data is ready.
|
||||||
// If asyncFetch is false, it will wait for all collectibles' metadata to be retrieved before returning.
|
// If asyncFetch is false, it will wait for all collectibles' metadata to be retrieved before returning.
|
||||||
func (o *Manager) FetchAssetsByCollectibleUniqueID(ctx context.Context, uniqueIDs []thirdparty.CollectibleUniqueID, asyncFetch bool) ([]thirdparty.FullCollectibleData, error) {
|
func (o *Manager) FetchAssetsByCollectibleUniqueID(ctx context.Context, uniqueIDs []thirdparty.CollectibleUniqueID, asyncFetch bool) ([]thirdparty.FullCollectibleData, error) {
|
||||||
missingIDs, err := o.collectiblesDataDB.GetIDsNotInDB(uniqueIDs)
|
err := o.FetchMissingAssetsByCollectibleUniqueID(ctx, uniqueIDs, asyncFetch)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
missingIDsPerChainID := thirdparty.GroupCollectibleUIDsByChainID(missingIDs)
|
return o.getCacheFullCollectibleData(uniqueIDs)
|
||||||
|
}
|
||||||
|
|
||||||
group := async.NewGroup(ctx)
|
func (o *Manager) FetchMissingAssetsByCollectibleUniqueID(ctx context.Context, uniqueIDs []thirdparty.CollectibleUniqueID, asyncFetch bool) error {
|
||||||
group.Add(func(ctx context.Context) error {
|
missingIDs, err := o.collectiblesDataDB.GetIDsNotInDB(uniqueIDs)
|
||||||
for chainID, idsToFetch := range missingIDsPerChainID {
|
if err != nil {
|
||||||
defer o.checkConnectionStatus(chainID)
|
return err
|
||||||
|
|
||||||
for _, provider := range o.collectibleDataProviders {
|
|
||||||
if !provider.IsChainSupported(chainID) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
fetchedAssets, err := provider.FetchAssetsByCollectibleUniqueID(ctx, idsToFetch)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("FetchAssetsByCollectibleUniqueID failed for", "provider", provider.ID(), "chainID", chainID, "err", err)
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
updatedCollectibles, err := o.processFullCollectibleData(ctx, fetchedAssets, asyncFetch)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("processFullCollectibleData failed for", "provider", provider.ID(), "chainID", chainID, "len(fetchedAssets)", len(fetchedAssets), "err", err)
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
if asyncFetch {
|
|
||||||
o.signalUpdatedCollectiblesData(updatedCollectibles)
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
|
|
||||||
if !asyncFetch {
|
|
||||||
group.Wait()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return o.getCacheFullCollectibleData(uniqueIDs)
|
missingIDsPerChainID := thirdparty.GroupCollectibleUIDsByChainID(missingIDs)
|
||||||
|
|
||||||
|
// Atomic group stores the error from the first failed command and stops other commands on error
|
||||||
|
group := async.NewAtomicGroup(ctx)
|
||||||
|
for chainID, idsToFetch := range missingIDsPerChainID {
|
||||||
|
group.Add(func(ctx context.Context) error {
|
||||||
|
defer o.checkConnectionStatus(chainID)
|
||||||
|
|
||||||
|
fetchedAssets, err := o.fetchMissingAssetsForChainByCollectibleUniqueID(ctx, chainID, idsToFetch)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("FetchMissingAssetsByCollectibleUniqueID failed for", "chainID", chainID, "ids", idsToFetch, "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
updatedCollectibles, err := o.processFullCollectibleData(ctx, fetchedAssets, asyncFetch)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("processFullCollectibleData failed for", "chainID", chainID, "len(fetchedAssets)", len(fetchedAssets), "err", err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
o.signalUpdatedCollectiblesData(updatedCollectibles)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
if asyncFetch {
|
||||||
|
group.Wait()
|
||||||
|
return group.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (o *Manager) fetchMissingAssetsForChainByCollectibleUniqueID(ctx context.Context, chainID walletCommon.ChainID, idsToFetch []thirdparty.CollectibleUniqueID) ([]thirdparty.FullCollectibleData, error) {
|
||||||
|
cmd := circuitbreaker.Command{}
|
||||||
|
for _, provider := range o.collectibleDataProviders {
|
||||||
|
if !provider.IsChainSupported(chainID) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := provider
|
||||||
|
cmd.Add(circuitbreaker.NewFunctor(func() ([]any, error) {
|
||||||
|
fetchedAssets, err := provider.FetchAssetsByCollectibleUniqueID(ctx, idsToFetch)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("fetchMissingAssetsForChainByCollectibleUniqueID failed for", "provider", provider.ID(), "chainID", chainID, "err", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return []any{fetchedAssets}, err
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd.IsEmpty() {
|
||||||
|
return nil, ErrNoProvidersAvailableForChainID // lets not stop the group if no providers are available for the chain
|
||||||
|
}
|
||||||
|
|
||||||
|
cmdRes := o.getCircuitBreaker(chainID).Execute(cmd)
|
||||||
|
if cmdRes.Error() != nil {
|
||||||
|
log.Error("fetchMissingAssetsForChainByCollectibleUniqueID failed for", "chainID", chainID, "err", cmdRes.Error())
|
||||||
|
return nil, cmdRes.Error()
|
||||||
|
}
|
||||||
|
return cmdRes.Result()[0].([]thirdparty.FullCollectibleData), cmdRes.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Manager) FetchCollectionsDataByContractID(ctx context.Context, ids []thirdparty.ContractID) ([]thirdparty.CollectionData, error) {
|
func (o *Manager) FetchCollectionsDataByContractID(ctx context.Context, ids []thirdparty.ContractID) ([]thirdparty.CollectionData, error) {
|
||||||
|
@ -474,27 +492,49 @@ func (o *Manager) FetchCollectionsDataByContractID(ctx context.Context, ids []th
|
||||||
|
|
||||||
missingIDsPerChainID := thirdparty.GroupContractIDsByChainID(missingIDs)
|
missingIDsPerChainID := thirdparty.GroupContractIDsByChainID(missingIDs)
|
||||||
|
|
||||||
|
// Atomic group stores the error from the first failed command and stops other commands on error
|
||||||
|
group := async.NewAtomicGroup(ctx)
|
||||||
for chainID, idsToFetch := range missingIDsPerChainID {
|
for chainID, idsToFetch := range missingIDsPerChainID {
|
||||||
defer o.checkConnectionStatus(chainID)
|
group.Add(func(ctx context.Context) error {
|
||||||
|
defer o.checkConnectionStatus(chainID)
|
||||||
|
|
||||||
for _, provider := range o.collectionDataProviders {
|
cmd := circuitbreaker.Command{}
|
||||||
if !provider.IsChainSupported(chainID) {
|
for _, provider := range o.collectionDataProviders {
|
||||||
continue
|
if !provider.IsChainSupported(chainID) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := provider
|
||||||
|
cmd.Add(circuitbreaker.NewFunctor(func() ([]any, error) {
|
||||||
|
fetchedCollections, err := provider.FetchCollectionsDataByContractID(ctx, idsToFetch)
|
||||||
|
return []any{fetchedCollections}, err
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fetchedCollections, err := provider.FetchCollectionsDataByContractID(ctx, idsToFetch)
|
if cmd.IsEmpty() {
|
||||||
if err != nil {
|
return nil
|
||||||
log.Error("FetchCollectionsDataByContractID failed for", "provider", provider.ID(), "chainID", chainID, "err", err)
|
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
cmdRes := o.getCircuitBreaker(chainID).Execute(cmd)
|
||||||
|
if cmdRes.Error() != nil {
|
||||||
|
log.Error("FetchCollectionsDataByContractID failed for", "chainID", chainID, "err", cmdRes.Error())
|
||||||
|
return cmdRes.Error()
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchedCollections := cmdRes.Result()[0].([]thirdparty.CollectionData)
|
||||||
err = o.processCollectionData(ctx, fetchedCollections)
|
err = o.processCollectionData(ctx, fetchedCollections)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
break
|
return err
|
||||||
}
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
group.Wait()
|
||||||
|
|
||||||
|
if group.Error() != nil {
|
||||||
|
return nil, group.Error()
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := o.collectionsDataDB.GetData(ids)
|
data, err := o.collectionsDataDB.GetData(ids)
|
||||||
|
@ -509,55 +549,35 @@ func (o *Manager) GetCollectibleOwnership(id thirdparty.CollectibleUniqueID) ([]
|
||||||
return o.ownershipDB.GetOwnership(id)
|
return o.ownershipDB.GetOwnership(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Manager) getContractOwnershipProviders(chainID walletCommon.ChainID) (mainProvider thirdparty.CollectibleContractOwnershipProvider, fallbackProvider thirdparty.CollectibleContractOwnershipProvider) {
|
|
||||||
mainProvider = nil
|
|
||||||
fallbackProvider = nil
|
|
||||||
|
|
||||||
for _, provider := range o.contractOwnershipProviders {
|
|
||||||
if provider.IsChainSupported(chainID) {
|
|
||||||
if mainProvider == nil {
|
|
||||||
// First provider found
|
|
||||||
mainProvider = provider
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
// Second provider found
|
|
||||||
fallbackProvider = provider
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func getCollectibleOwnersByContractAddressFunc(ctx context.Context, chainID walletCommon.ChainID, contractAddress common.Address, provider thirdparty.CollectibleContractOwnershipProvider) func() (any, error) {
|
|
||||||
if provider == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return func() (any, error) {
|
|
||||||
res, err := provider.FetchCollectibleOwnersByContractAddress(ctx, chainID, contractAddress)
|
|
||||||
if err != nil {
|
|
||||||
log.Error("FetchCollectibleOwnersByContractAddress failed for", "provider", provider.ID(), "chainID", chainID, "err", err)
|
|
||||||
}
|
|
||||||
return res, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (o *Manager) FetchCollectibleOwnersByContractAddress(ctx context.Context, chainID walletCommon.ChainID, contractAddress common.Address) (*thirdparty.CollectibleContractOwnership, error) {
|
func (o *Manager) FetchCollectibleOwnersByContractAddress(ctx context.Context, chainID walletCommon.ChainID, contractAddress common.Address) (*thirdparty.CollectibleContractOwnership, error) {
|
||||||
defer o.checkConnectionStatus(chainID)
|
defer o.checkConnectionStatus(chainID)
|
||||||
|
|
||||||
mainProvider, fallbackProvider := o.getContractOwnershipProviders(chainID)
|
cmd := circuitbreaker.Command{}
|
||||||
if mainProvider == nil {
|
for _, provider := range o.contractOwnershipProviders {
|
||||||
|
if !provider.IsChainSupported(chainID) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
provider := provider
|
||||||
|
cmd.Add(circuitbreaker.NewFunctor(func() ([]any, error) {
|
||||||
|
res, err := provider.FetchCollectibleOwnersByContractAddress(ctx, chainID, contractAddress)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("FetchCollectibleOwnersByContractAddress failed for", "provider", provider.ID(), "chainID", chainID, "err", err)
|
||||||
|
}
|
||||||
|
return []any{res}, err
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
if cmd.IsEmpty() {
|
||||||
return nil, ErrNoProvidersAvailableForChainID
|
return nil, ErrNoProvidersAvailableForChainID
|
||||||
}
|
}
|
||||||
|
|
||||||
mainFn := getCollectibleOwnersByContractAddressFunc(ctx, chainID, contractAddress, mainProvider)
|
cmdRes := o.getCircuitBreaker(chainID).Execute(cmd)
|
||||||
fallbackFn := getCollectibleOwnersByContractAddressFunc(ctx, chainID, contractAddress, fallbackProvider)
|
if cmdRes.Error() != nil {
|
||||||
|
log.Error("FetchCollectibleOwnersByContractAddress failed for", "chainID", chainID, "err", cmdRes.Error())
|
||||||
owners, err := makeContractOwnershipCall(mainFn, fallbackFn)
|
return nil, cmdRes.Error()
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
}
|
||||||
|
return cmdRes.Result()[0].(*thirdparty.CollectibleContractOwnership), cmdRes.Error()
|
||||||
return owners.(*thirdparty.CollectibleContractOwnership), nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *Manager) fetchTokenURI(ctx context.Context, id thirdparty.CollectibleUniqueID) (string, error) {
|
func (o *Manager) fetchTokenURI(ctx context.Context, id thirdparty.CollectibleUniqueID) (string, error) {
|
||||||
|
@ -960,3 +980,19 @@ func (o *Manager) signalUpdatedCollectiblesData(ids []thirdparty.CollectibleUniq
|
||||||
o.feed.Send(event)
|
o.feed.Send(event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (o *Manager) getCircuitBreaker(chainID walletCommon.ChainID) *circuitbreaker.CircuitBreaker {
|
||||||
|
cb, ok := o.circuitBreakers.Load(chainID.String())
|
||||||
|
if !ok {
|
||||||
|
cb = circuitbreaker.NewCircuitBreaker(circuitbreaker.Config{
|
||||||
|
CommandName: chainID.String(),
|
||||||
|
Timeout: 10000,
|
||||||
|
MaxConcurrentRequests: 100,
|
||||||
|
RequestVolumeThreshold: 25,
|
||||||
|
SleepWindow: 300000,
|
||||||
|
ErrorPercentThreshold: 25,
|
||||||
|
})
|
||||||
|
o.circuitBreakers.Store(chainID.String(), cb)
|
||||||
|
}
|
||||||
|
return cb.(*circuitbreaker.CircuitBreaker)
|
||||||
|
}
|
||||||
|
|
|
@ -33,8 +33,6 @@ func getBaseURL(chainID walletCommon.ChainID) (string, error) {
|
||||||
return "https://eth-sepolia.g.alchemy.com", nil
|
return "https://eth-sepolia.g.alchemy.com", nil
|
||||||
case walletCommon.OptimismMainnet:
|
case walletCommon.OptimismMainnet:
|
||||||
return "https://opt-mainnet.g.alchemy.com", nil
|
return "https://opt-mainnet.g.alchemy.com", nil
|
||||||
case walletCommon.OptimismGoerli:
|
|
||||||
return "https://opt-goerli.g.alchemy.com", nil
|
|
||||||
case walletCommon.OptimismSepolia:
|
case walletCommon.OptimismSepolia:
|
||||||
return "https://opt-sepolia.g.alchemy.com", nil
|
return "https://opt-sepolia.g.alchemy.com", nil
|
||||||
case walletCommon.ArbitrumMainnet:
|
case walletCommon.ArbitrumMainnet:
|
||||||
|
|
|
@ -42,7 +42,7 @@ func getBaseURL(chainID walletCommon.ChainID) (string, error) {
|
||||||
switch uint64(chainID) {
|
switch uint64(chainID) {
|
||||||
case walletCommon.EthereumMainnet, walletCommon.ArbitrumMainnet:
|
case walletCommon.EthereumMainnet, walletCommon.ArbitrumMainnet:
|
||||||
return "https://api.rarible.org", nil
|
return "https://api.rarible.org", nil
|
||||||
case walletCommon.EthereumGoerli, walletCommon.ArbitrumSepolia:
|
case walletCommon.ArbitrumSepolia:
|
||||||
return "https://testnet-api.rarible.org", nil
|
return "https://testnet-api.rarible.org", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue