Skip to content

Commit

Permalink
Various Fixes & Features (#24)
Browse files Browse the repository at this point in the history
* add a bunch of fixes

* last minute fixes

* remove empty branch
  • Loading branch information
keefertaylor authored Dec 13, 2023
1 parent 339f9b0 commit cc93be2
Show file tree
Hide file tree
Showing 13 changed files with 425 additions and 126 deletions.
13 changes: 13 additions & 0 deletions arrays/batch_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,19 @@ import (
"github.com/tessellated-io/pickaxe/arrays"
)

func TestBatch_OneSizeBatch(t *testing.T) {
arr := []int{1, 2}
batched := arrays.Batch(arr, 1)

require.Equal(t, len(batched), 2)

require.Equal(t, len(batched[0]), 1)
require.Equal(t, len(batched[1]), 1)

require.Equal(t, batched[0][0], 1)
require.Equal(t, batched[1][0], 2)
}

func TestBatch_EvenBatches(t *testing.T) {
arr := []int{1, 2, 3, 4}
batched := arrays.Batch(arr, 2)
Expand Down
91 changes: 91 additions & 0 deletions cosmos/chain-registry/asset_list.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package registry

import (
"encoding/json"
"math"
"strings"

sdk "github.com/cosmos/cosmos-sdk/types"
)

type AssetList struct {
Schema string `json:"$schema"`
ChainName string `json:"chain_name"`
Assets []Asset `json:"assets"`
}

type Asset struct {
Description string `json:"description"`
DenomUnits []DenomUnit `json:"denom_units"`
Base string `json:"base"`
Name string `json:"name"`
Display string `json:"display"`
Symbol string `json:"symbol"`
LogoURIs LogoURIs `json:"logo_URIs"`
CoingeckoID string `json:"coingecko_id"`
Images []ImageLinks `json:"images"`
}

type DenomUnit struct {
Denom string `json:"denom"`
Exponent int `json:"exponent"`
}

type ImageLinks struct {
Png string `json:"png"`
Svg string `json:"svg"`
}

func parseAssetListResponse(assetListBytes []byte) (*AssetList, error) {
// Unmarshal JSON data
var assetList AssetList
if err := json.Unmarshal(assetListBytes, &assetList); err != nil {
return nil, err
}
return &assetList, nil
}

// Convenience methods

func (al *AssetList) ExtractAssetByBaseSymbol(baseAssetSymbol string) (*Asset, error) {
assets := al.Assets
for _, asset := range assets {
if strings.EqualFold(asset.Base, baseAssetSymbol) {
return &asset, nil
}
}
return nil, ErrNoMatchingAsset
}

func (a *Asset) ExtractDenomByUnit(needleDenomUnit string) (*DenomUnit, error) {
denomUnits := a.DenomUnits
for _, denomUnit := range denomUnits {
if strings.EqualFold(denomUnit.Denom, needleDenomUnit) {
return &denomUnit, nil
}
}
return nil, ErrNoMatchingDenom
}

// Get a single token, given a base symbol. Ex. 1 JUNO = sdk.Coin{ denom: "ujuno", amount: "1_000_000" }
func (al *AssetList) OneToken(baseAssetSymbol string) (*sdk.Coin, error) {
// Extract the asset
asset, err := al.ExtractAssetByBaseSymbol(baseAssetSymbol)
if err != nil {
return nil, err
}

// Extract the denom unit
denomUnit, err := asset.ExtractDenomByUnit(baseAssetSymbol)
if err != nil {
return nil, err
}

decimals := denomUnit.Exponent

// Create one coin.
oneAmount := sdk.NewInt(int64(math.Pow(10, float64(decimals))))
oneCoin := sdk.NewCoin(baseAssetSymbol, oneAmount)

return &oneCoin, nil
}
31 changes: 0 additions & 31 deletions cosmos/chain-registry/chain_info.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
package registry

import (
"context"
"encoding/json"
"fmt"
"math"

"github.com/tessellated-io/pickaxe/cosmos/rpc"

sdk "github.com/cosmos/cosmos-sdk/types"
)

func parseChainResponse(responseBytes []byte) (*ChainInfo, error) {
Expand All @@ -32,30 +25,6 @@ func (ci *ChainInfo) FeeDenom() (string, error) {
return feeToken.Denom, nil
}

func (ci *ChainInfo) OneFeeToken(ctx context.Context, rpcClient rpc.RpcClient) (*sdk.Coin, error) {
feeDenom, err := ci.FeeDenom()
if err != nil {
return nil, err
}

// Fetch denom metadata from the registry to get decimals. It's a bummer chain registry doesn't seem to include this field.
denomMetadata, err := rpcClient.GetDenomMetadata(ctx, feeDenom)
if err != nil {
return nil, err
}
denomUnits := denomMetadata.DenomUnits
if len(denomUnits) == 0 {
return nil, fmt.Errorf("no denom unit found for %s", feeDenom)
}
decimals := denomUnits[0].Exponent

// Create one coin.
oneAmount := sdk.NewInt(int64(math.Pow(10, float64(decimals))))
oneCoin := sdk.NewCoin(feeDenom, oneAmount)

return &oneCoin, nil
}

func (ci *ChainInfo) StakingDenom() (string, error) {
stakingTokens := ci.Staking.StakingTokens
if len(stakingTokens) == 0 {
Expand Down
1 change: 1 addition & 0 deletions cosmos/chain-registry/chain_registry_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ type ChainRegistryClient interface {
AllChainNames(ctx context.Context) ([]string, error)
ChainNameForChainID(ctx context.Context, targetChainID string, refreshCache bool) (string, error)
ChainInfo(ctx context.Context, chainName string) (*ChainInfo, error)
AssetList(ctx context.Context, chainName string) (*AssetList, error)

// Restake Validator Registry
Validator(ctx context.Context, targetValidator string) (*Validator, error)
Expand Down
2 changes: 2 additions & 0 deletions cosmos/chain-registry/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,6 @@ var (
ErrNoChainFoundForChainID = errors.New("no chain found for chain ID")
ErrNoStakingTokenFound = errors.New("no staking tokens found in registry")
ErrNoFeeTokenFound = errors.New("no fee tokens found in registry")
ErrNoMatchingAsset = errors.New("no matching asset found")
ErrNoMatchingDenom = errors.New("no matching denom found")
)
23 changes: 21 additions & 2 deletions cosmos/chain-registry/registry.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ func NewChainRegistryClient(log *log.Logger) *chainRegistryClient {

func (rc *chainRegistryClient) ChainInfo(ctx context.Context, chainName string) (*ChainInfo, error) {
url := fmt.Sprintf("https://proxy.atomscan.com/directory/%s/chain.json", chainName)

bytes, err := rc.makeRequest(ctx, url)
if err != nil {
return nil, err
Expand All @@ -51,6 +52,21 @@ func (rc *chainRegistryClient) ChainInfo(ctx context.Context, chainName string)
return chainInfo, nil
}

func (rc *chainRegistryClient) AssetList(ctx context.Context, chainName string) (*AssetList, error) {
url := fmt.Sprintf("https://proxy.atomscan.com/directory/%s/assetlist.json", chainName)

bytes, err := rc.makeRequest(ctx, url)
if err != nil {
return nil, err
}

chainInfo, err := parseAssetListResponse(bytes)
if err != nil {
return nil, err
}
return chainInfo, nil
}

func (rc *chainRegistryClient) ChainNameForChainID(ctx context.Context, targetChainID string, refreshCache bool) (string, error) {
// If refresh cache is requested, clear the local values
if refreshCache {
Expand Down Expand Up @@ -84,13 +100,14 @@ func (rc *chainRegistryClient) ChainNameForChainID(ctx context.Context, targetCh
// NOTE: No retries because GetChainInfo manages that for us.
chainInfo, err := rc.ChainInfo(ctx, chainName)
if err != nil {
rc.log.Error().Err(err).Str("chain_name", chainName).Msg("error fetching chain information during chain id refresh")
return "", err
}

chainID = chainInfo.ChainID
}

if strings.EqualFold(chainName, chainID) {
if strings.EqualFold(targetChainID, chainID) {
return chainName, nil
}
}
Expand All @@ -100,7 +117,7 @@ func (rc *chainRegistryClient) ChainNameForChainID(ctx context.Context, targetCh

func (rc *chainRegistryClient) AllChainNames(ctx context.Context) ([]string, error) {
// Get all chain names
url := "https://cosmos-chain.directory/chains"
url := "https://cosmoschains.thesilverfox.pro/api/v1/mainnet"
bytes, err := rc.makeRequest(ctx, url)
if err != nil {
return nil, err
Expand Down Expand Up @@ -143,6 +160,8 @@ func (rc *chainRegistryClient) Validators(ctx context.Context) ([]Validator, err
// Private helpers

func (rc *chainRegistryClient) makeRequest(ctx context.Context, url string) ([]byte, error) {
rc.log.Debug().Str("url", url).Msg("making GET request to url")

request, err := http.NewRequestWithContext(ctx, "GET", url, nil)
if err != nil {
return nil, err
Expand Down
87 changes: 78 additions & 9 deletions cosmos/chain-registry/retryable_chain_registry_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"time"

retry "github.com/avast/retry-go/v4"
"github.com/tessellated-io/pickaxe/log"
)

// Implements a retryable and returns the last error
Expand All @@ -14,18 +15,22 @@ type retryableChainRegistryClient struct {

attempts retry.Option
delay retry.Option

logger *log.Logger
}

// Ensure that retryableChainRegistryClient implements ChainRegistryClient
var _ ChainRegistryClient = (*retryableChainRegistryClient)(nil)

// NewRetryableChainRegistryClient returns a new retryableChainRegistryClient
func NewRetryableChainRegistryClient(attempts uint, delay time.Duration, chainRegistryClient ChainRegistryClient) (ChainRegistryClient, error) {
func NewRetryableChainRegistryClient(attempts uint, delay time.Duration, chainRegistryClient ChainRegistryClient, logger *log.Logger) (ChainRegistryClient, error) {
return &retryableChainRegistryClient{
wrappedClient: chainRegistryClient,

attempts: retry.Attempts(attempts),
delay: retry.Delay(delay),

logger: logger,
}, nil
}

Expand All @@ -37,13 +42,22 @@ func (r *retryableChainRegistryClient) AllChainNames(ctx context.Context) ([]str

err = retry.Do(func() error {
result, err = r.wrappedClient.AllChainNames(ctx)
if err != nil {
r.logger.Error().Err(err).Str("method", "all_chain_names").Msg("failed call in registry client, will retry")
}
return err
}, r.delay, r.attempts, retry.Context(ctx))
if err != nil {
err = errors.Unwrap(err)
// If err is an error from a context, unwrapping will write out nil
unwrappedErr := errors.Unwrap(err)
if unwrappedErr != nil {
return result, unwrappedErr
} else {
return result, err
}
}

return result, err
return result, nil
}

func (r *retryableChainRegistryClient) ChainNameForChainID(ctx context.Context, targetChainID string, refreshCache bool) (string, error) {
Expand All @@ -52,13 +66,23 @@ func (r *retryableChainRegistryClient) ChainNameForChainID(ctx context.Context,

err = retry.Do(func() error {
result, err = r.wrappedClient.ChainNameForChainID(ctx, targetChainID, refreshCache)
if err != nil {
r.logger.Error().Err(err).Str("method", "chain_name_for_id").Msg("failed call in rpc client, will retry")
}
return err
}, r.delay, r.attempts, retry.Context(ctx))

if err != nil {
err = errors.Unwrap(err)
// If err is an error from a context, unwrapping will write out nil
unwrappedErr := errors.Unwrap(err)
if unwrappedErr != nil {
return result, unwrappedErr
} else {
return result, err
}
}

return result, err
return result, nil
}

func (r *retryableChainRegistryClient) ChainInfo(ctx context.Context, chainName string) (*ChainInfo, error) {
Expand All @@ -67,13 +91,48 @@ func (r *retryableChainRegistryClient) ChainInfo(ctx context.Context, chainName

err = retry.Do(func() error {
result, err = r.wrappedClient.ChainInfo(ctx, chainName)
if err != nil {
r.logger.Error().Err(err).Str("method", "chain_info").Msg("failed call in rpc client, will retry")
}
return err
}, r.delay, r.attempts, retry.Context(ctx))

if err != nil {
// If err is an error from a context, unwrapping will write out nil
unwrappedErr := errors.Unwrap(err)
if unwrappedErr != nil {
return result, unwrappedErr
} else {
return result, err
}
}

return result, nil
}

func (r *retryableChainRegistryClient) AssetList(ctx context.Context, chainName string) (*AssetList, error) {
var result *AssetList
var err error

err = retry.Do(func() error {
result, err = r.wrappedClient.AssetList(ctx, chainName)
if err != nil {
r.logger.Error().Err(err).Str("method", "asset_list").Msg("failed call in rpc client, will retry")
}
return err
}, r.delay, r.attempts, retry.Context(ctx))

if err != nil {
err = errors.Unwrap(err)
// If err is an error from a context, unwrapping will write out nil
unwrappedErr := errors.Unwrap(err)
if unwrappedErr != nil {
return result, unwrappedErr
} else {
return result, err
}
}

return result, err
return result, nil
}

func (r *retryableChainRegistryClient) Validator(ctx context.Context, targetValidator string) (*Validator, error) {
Expand All @@ -82,11 +141,21 @@ func (r *retryableChainRegistryClient) Validator(ctx context.Context, targetVali

err = retry.Do(func() error {
result, err = r.wrappedClient.Validator(ctx, targetValidator)
if err != nil {
r.logger.Error().Err(err).Str("method", "validator").Msg("failed call in rpc client, will retry")
}
return err
}, r.delay, r.attempts, retry.Context(ctx))

if err != nil {
err = errors.Unwrap(err)
// If err is an error from a context, unwrapping will write out nil
unwrappedErr := errors.Unwrap(err)
if unwrappedErr != nil {
return result, unwrappedErr
} else {
return result, err
}
}

return result, err
return result, nil
}
Loading

0 comments on commit cc93be2

Please sign in to comment.