Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bids 2622/fix address page with many tokens #2657

Merged
merged 11 commits into from
Nov 2, 2023
6 changes: 1 addition & 5 deletions cmd/explorer/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -311,11 +311,7 @@ func main() {
apiV1Router.HandleFunc("/execution/{addressIndexOrPubkey}/produced", handlers.ApiETH1AccountProducedBlocks).Methods("GET", "OPTIONS")

apiV1Router.HandleFunc("/execution/address/{address}", handlers.ApiEth1Address).Methods("GET", "OPTIONS")
apiV1Router.HandleFunc("/execution/address/{address}/transactions", handlers.ApiEth1AddressTx).Methods("GET", "OPTIONS")
peterbitfly marked this conversation as resolved.
Show resolved Hide resolved
apiV1Router.HandleFunc("/execution/address/{address}/internalTx", handlers.ApiEth1AddressItx).Methods("GET", "OPTIONS")
apiV1Router.HandleFunc("/execution/address/{address}/blocks", handlers.ApiEth1AddressBlocks).Methods("GET", "OPTIONS")
apiV1Router.HandleFunc("/execution/address/{address}/uncles", handlers.ApiEth1AddressUncles).Methods("GET", "OPTIONS")
apiV1Router.HandleFunc("/execution/address/{address}/tokens", handlers.ApiEth1AddressTokens).Methods("GET", "OPTIONS")
apiV1Router.HandleFunc("/execution/address/{address}/erc20tokens", handlers.ApiEth1AddressERC20Tokens).Methods("GET", "OPTIONS")

apiV1Router.HandleFunc("/validator/{indexOrPubkey}/widget", handlers.GetMobileWidgetStatsGet).Methods("GET")
apiV1Router.HandleFunc("/dashboard/widget", handlers.GetMobileWidgetStatsPost).Methods("POST")
Expand Down
70 changes: 44 additions & 26 deletions db/bigtable_eth1.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,13 @@ import (
eth_types "github.com/ethereum/go-ethereum/core/types"
"github.com/go-redis/redis/v8"

"github.com/shopspring/decimal"
"github.com/sirupsen/logrus"
"google.golang.org/protobuf/proto"
)

// when changing this, you will have to update the swagger docu for func ApiEth1Address too
var ECR20TokensPerAddressLimit = uint64(200)

var ErrBlockNotFound = errors.New("block not found")

type IndexFilter string
Expand Down Expand Up @@ -3207,7 +3209,7 @@ func (bigtable *Bigtable) GetMetadata(startToken string, limit int) ([]string, [
return keys, pairs, err
}

func (bigtable *Bigtable) GetMetadataForAddress(address []byte) (*types.Eth1AddressMetadata, error) {
func (bigtable *Bigtable) GetMetadataForAddress(address []byte, offset uint64, limit uint64) (*types.Eth1AddressMetadata, error) {

tmr := time.AfterFunc(REPORT_TIMEOUT, func() {
logger.WithFields(logrus.Fields{
Expand All @@ -3219,7 +3221,8 @@ func (bigtable *Bigtable) GetMetadataForAddress(address []byte) (*types.Eth1Addr
ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(time.Second*30))
defer cancel()

row, err := bigtable.tableMetadata.ReadRow(ctx, fmt.Sprintf("%s:%x", bigtable.chainId, address))
filter := gcp_bigtable.FamilyFilter(ACCOUNT_METADATA_FAMILY)
row, err := bigtable.tableMetadata.ReadRow(ctx, fmt.Sprintf("%s:%x", bigtable.chainId, address), gcp_bigtable.RowFilter(filter))

if err != nil {
return nil, err
Expand All @@ -3232,25 +3235,55 @@ func (bigtable *Bigtable) GetMetadataForAddress(address []byte) (*types.Eth1Addr
EthBalance: &types.Eth1AddressBalance{
Metadata: &types.ERC20Metadata{},
},
ERC20TokenLimit: ECR20TokensPerAddressLimit,
}

if limit == 0 || limit > ECR20TokensPerAddressLimit {
limit = ECR20TokensPerAddressLimit
}

tokenCount := uint64(0)

g := new(errgroup.Group)
g.SetLimit(10)

mux := sync.Mutex{}
for _, ri := range row {
for _, column := range ri {
if strings.HasPrefix(column.Column, ACCOUNT_METADATA_FAMILY+":B:") {
column := column

if bytes.Equal(address, ZERO_ADDRESS) && column.Column != ACCOUNT_METADATA_FAMILY+":B:00" { //do not return token balances for the zero address
if bytes.Equal(address, ZERO_ADDRESS) && column.Column != ACCOUNT_METADATA_FAMILY+":B:00" {
//do not return token balances for the zero address
continue
}

g.Go(func() error {
token := common.FromHex(strings.TrimPrefix(column.Column, "a:B:"))
token := common.FromHex(strings.TrimPrefix(column.Column, "a:B:"))

isNativeEth := bytes.Equal([]byte{0x00}, token)
if !isNativeEth {
// token is not ETH, check if token limit is reached
if tokenCount >= limit {
ret.ERC20TokenLimitExceeded = true
continue
}

// skip token without value
if len(column.Value) == 0 && len(token) > 1 {
return nil
continue
}

// handle pagination
if offset > 0 {
offset--
continue
}

// at this point, token will be added
tokenCount++
}

g.Go(func() error {
balance := &types.Eth1AddressBalance{
Address: address,
Token: token,
Expand All @@ -3264,7 +3297,7 @@ func (bigtable *Bigtable) GetMetadataForAddress(address []byte) (*types.Eth1Addr
balance.Metadata = metadata

mux.Lock()
if bytes.Equal([]byte{0x00}, token) {
if isNativeEth {
ret.EthBalance = balance
} else {
ret.Balances = append(ret.Balances, balance)
Expand All @@ -3273,9 +3306,7 @@ func (bigtable *Bigtable) GetMetadataForAddress(address []byte) (*types.Eth1Addr

return nil
})
}

if column.Column == ACCOUNT_METADATA_FAMILY+":"+ACCOUNT_COLUMN_NAME {
} else if column.Column == ACCOUNT_METADATA_FAMILY+":"+ACCOUNT_COLUMN_NAME {
ret.Name = string(column.Value)
}
}
Expand All @@ -3286,22 +3317,9 @@ func (bigtable *Bigtable) GetMetadataForAddress(address []byte) (*types.Eth1Addr
return nil, err
}

// sort balances based on token address (required for proper pagination)
sort.Slice(ret.Balances, func(i, j int) bool {
priceI := decimal.New(0, 0)
priceJ := decimal.New(0, 0)
if len(ret.Balances[i].Metadata.Price) > 0 {
priceI = decimal.NewFromBigInt(new(big.Int).SetBytes(ret.Balances[i].Metadata.Price), 0)
}
if len(ret.Balances[j].Metadata.Price) > 0 {
priceJ = decimal.NewFromBigInt(new(big.Int).SetBytes(ret.Balances[j].Metadata.Price), 0)
}

mulI := decimal.NewFromFloat(float64(10)).Pow(decimal.NewFromBigInt(new(big.Int).SetBytes(ret.Balances[i].Metadata.Decimals), 0))
mulJ := decimal.NewFromFloat(float64(10)).Pow(decimal.NewFromBigInt(new(big.Int).SetBytes(ret.Balances[j].Metadata.Decimals), 0))
mkI := priceI.Mul(decimal.NewFromBigInt(new(big.Int).SetBytes(ret.Balances[i].Balance), 0).DivRound(mulI, 18))
mkJ := priceJ.Mul(decimal.NewFromBigInt(new(big.Int).SetBytes(ret.Balances[j].Balance), 0).DivRound(mulJ, 18))

return mkI.Cmp(mkJ) >= 0
return bytes.Compare(ret.Balances[i].Token, ret.Balances[j].Token) < 0
peterbitfly marked this conversation as resolved.
Show resolved Hide resolved
})

return ret, nil
Expand Down
Loading