fix: Handle balance for all accounts (#4261)
This commit is contained in:
parent
3ab1afaae8
commit
ecbb1cb4e0
|
@ -132,8 +132,8 @@ func (api *API) FetchDecodedTxData(ctx context.Context, data string) (*thirdpart
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBalanceHistory retrieves token balance history for token identity on multiple chains
|
// GetBalanceHistory retrieves token balance history for token identity on multiple chains
|
||||||
func (api *API) GetBalanceHistory(ctx context.Context, chainIDs []uint64, address common.Address, tokenSymbol string, currencySymbol string, timeInterval history.TimeInterval) ([]*history.ValuePoint, error) {
|
func (api *API) GetBalanceHistory(ctx context.Context, chainIDs []uint64, addresses []common.Address, tokenSymbol string, currencySymbol string, timeInterval history.TimeInterval) ([]*history.ValuePoint, error) {
|
||||||
log.Debug("wallet.api.GetBalanceHistory", "chainIDs", chainIDs, "address", address, "tokenSymbol", tokenSymbol, "currencySymbol", currencySymbol, "timeInterval", timeInterval)
|
log.Debug("wallet.api.GetBalanceHistory", "chainIDs", chainIDs, "address", addresses, "tokenSymbol", tokenSymbol, "currencySymbol", currencySymbol, "timeInterval", timeInterval)
|
||||||
|
|
||||||
var fromTimestamp uint64
|
var fromTimestamp uint64
|
||||||
now := uint64(time.Now().UTC().Unix())
|
now := uint64(time.Now().UTC().Unix())
|
||||||
|
@ -152,14 +152,14 @@ func (api *API) GetBalanceHistory(ctx context.Context, chainIDs []uint64, addres
|
||||||
return nil, fmt.Errorf("unknown time interval: %v", timeInterval)
|
return nil, fmt.Errorf("unknown time interval: %v", timeInterval)
|
||||||
}
|
}
|
||||||
|
|
||||||
return api.GetBalanceHistoryRange(ctx, chainIDs, address, tokenSymbol, currencySymbol, fromTimestamp, now)
|
return api.GetBalanceHistoryRange(ctx, chainIDs, addresses, tokenSymbol, currencySymbol, fromTimestamp, now)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBalanceHistoryRange retrieves token balance history for token identity on multiple chains for a time range
|
// GetBalanceHistoryRange retrieves token balance history for token identity on multiple chains for a time range
|
||||||
// 'toTimestamp' is ignored for now, but will be used in the future to limit the range of the history
|
// 'toTimestamp' is ignored for now, but will be used in the future to limit the range of the history
|
||||||
func (api *API) GetBalanceHistoryRange(ctx context.Context, chainIDs []uint64, address common.Address, tokenSymbol string, currencySymbol string, fromTimestamp uint64, _ uint64) ([]*history.ValuePoint, error) {
|
func (api *API) GetBalanceHistoryRange(ctx context.Context, chainIDs []uint64, addresses []common.Address, tokenSymbol string, currencySymbol string, fromTimestamp uint64, _ uint64) ([]*history.ValuePoint, error) {
|
||||||
log.Debug("wallet.api.GetBalanceHistoryRange", "chainIDs", chainIDs, "address", address, "tokenSymbol", tokenSymbol, "currencySymbol", currencySymbol, "fromTimestamp", fromTimestamp)
|
log.Debug("wallet.api.GetBalanceHistoryRange", "chainIDs", chainIDs, "address", addresses, "tokenSymbol", tokenSymbol, "currencySymbol", currencySymbol, "fromTimestamp", fromTimestamp)
|
||||||
return api.s.history.GetBalanceHistory(ctx, chainIDs, address, tokenSymbol, currencySymbol, fromTimestamp)
|
return api.s.history.GetBalanceHistory(ctx, chainIDs, addresses, tokenSymbol, currencySymbol, fromTimestamp)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (api *API) GetTokenList(ctx context.Context) ([]*token.List, error) {
|
func (api *API) GetTokenList(ctx context.Context) ([]*token.List, error) {
|
||||||
|
|
|
@ -58,10 +58,10 @@ func NewBalance(db *BalanceDB) *Balance {
|
||||||
}
|
}
|
||||||
|
|
||||||
// get returns the balance history for the given address from the given timestamp till now
|
// get returns the balance history for the given address from the given timestamp till now
|
||||||
func (b *Balance) get(ctx context.Context, chainID uint64, currency string, address common.Address, fromTimestamp uint64) ([]*entry, error) {
|
func (b *Balance) get(ctx context.Context, chainID uint64, currency string, addresses []common.Address, fromTimestamp uint64) ([]*entry, error) {
|
||||||
log.Debug("Getting balance history", "chainID", chainID, "currency", currency, "address", address, "fromTimestamp", fromTimestamp)
|
log.Debug("Getting balance history", "chainID", chainID, "currency", currency, "address", addresses, "fromTimestamp", fromTimestamp)
|
||||||
|
|
||||||
cached, err := b.db.getNewerThan(&assetIdentity{chainID, address, currency}, fromTimestamp)
|
cached, err := b.db.getNewerThan(&assetIdentity{chainID, addresses, currency}, fromTimestamp)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -69,69 +69,85 @@ func (b *Balance) get(ctx context.Context, chainID uint64, currency string, addr
|
||||||
return cached, nil
|
return cached, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Balance) addEdgePoints(chainID uint64, currency string, address common.Address, fromTimestamp, toTimestamp uint64, data []*entry) (res []*entry, err error) {
|
func (b *Balance) addEdgePoints(chainID uint64, currency string, addresses []common.Address, fromTimestamp, toTimestamp uint64, data []*entry) (res []*entry, err error) {
|
||||||
log.Debug("Adding edge points", "chainID", chainID, "currency", currency, "address", address, "fromTimestamp", fromTimestamp)
|
log.Debug("Adding edge points", "chainID", chainID, "currency", currency, "address", addresses, "fromTimestamp", fromTimestamp)
|
||||||
|
|
||||||
var firstEntry *entry
|
res = data
|
||||||
|
|
||||||
if len(data) > 0 {
|
for _, address := range addresses {
|
||||||
firstEntry = data[0]
|
var firstEntry *entry
|
||||||
} else {
|
|
||||||
firstEntry = &entry{
|
if len(data) > 0 {
|
||||||
chainID: chainID,
|
for _, entry := range data {
|
||||||
address: address,
|
if entry.address == address {
|
||||||
tokenSymbol: currency,
|
firstEntry = entry
|
||||||
timestamp: int64(fromTimestamp),
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
if firstEntry == nil {
|
||||||
|
firstEntry = &entry{
|
||||||
previous, err := b.db.getEntryPreviousTo(firstEntry)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
firstTimestamp, lastTimestamp := timestampBoundaries(fromTimestamp, toTimestamp, data)
|
|
||||||
|
|
||||||
if previous != nil {
|
|
||||||
previous.timestamp = int64(firstTimestamp) // We might need to use another minimal offset respecting the time interval
|
|
||||||
previous.block = nil
|
|
||||||
res = append([]*entry{previous}, data...)
|
|
||||||
} else {
|
|
||||||
// Add a zero point at the beginning to draw a line from
|
|
||||||
res = append([]*entry{
|
|
||||||
{
|
|
||||||
chainID: chainID,
|
chainID: chainID,
|
||||||
address: address,
|
address: address,
|
||||||
tokenSymbol: currency,
|
tokenSymbol: currency,
|
||||||
timestamp: int64(firstTimestamp),
|
timestamp: int64(fromTimestamp),
|
||||||
balance: big.NewInt(0),
|
}
|
||||||
},
|
}
|
||||||
}, data...)
|
|
||||||
}
|
|
||||||
|
|
||||||
if res[len(res)-1].timestamp < int64(lastTimestamp) {
|
previous, err := b.db.getEntryPreviousTo(firstEntry)
|
||||||
// Add a last point to draw a line to
|
if err != nil {
|
||||||
res = append(res, &entry{
|
return nil, err
|
||||||
chainID: chainID,
|
}
|
||||||
address: address,
|
|
||||||
tokenSymbol: currency,
|
firstTimestamp, lastTimestamp := timestampBoundaries(fromTimestamp, toTimestamp, address, data)
|
||||||
timestamp: int64(lastTimestamp),
|
|
||||||
balance: res[len(res)-1].balance,
|
if previous != nil {
|
||||||
})
|
previous.timestamp = int64(firstTimestamp) // We might need to use another minimal offset respecting the time interval
|
||||||
|
previous.block = nil
|
||||||
|
res = append([]*entry{previous}, res...)
|
||||||
|
} else {
|
||||||
|
// Add a zero point at the beginning to draw a line from
|
||||||
|
res = append([]*entry{
|
||||||
|
{
|
||||||
|
chainID: chainID,
|
||||||
|
address: address,
|
||||||
|
tokenSymbol: currency,
|
||||||
|
timestamp: int64(firstTimestamp),
|
||||||
|
balance: big.NewInt(0),
|
||||||
|
},
|
||||||
|
}, res...)
|
||||||
|
}
|
||||||
|
|
||||||
|
if res[len(res)-1].timestamp < int64(lastTimestamp) {
|
||||||
|
// Add a last point to draw a line to
|
||||||
|
res = append(res, &entry{
|
||||||
|
chainID: chainID,
|
||||||
|
address: address,
|
||||||
|
tokenSymbol: currency,
|
||||||
|
timestamp: int64(lastTimestamp),
|
||||||
|
balance: res[len(res)-1].balance,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func timestampBoundaries(fromTimestamp, toTimestamp uint64, data []*entry) (firstTimestamp, lastTimestamp uint64) {
|
func timestampBoundaries(fromTimestamp, toTimestamp uint64, address common.Address, data []*entry) (firstTimestamp, lastTimestamp uint64) {
|
||||||
firstTimestamp = fromTimestamp
|
firstTimestamp = fromTimestamp
|
||||||
if fromTimestamp == 0 {
|
if fromTimestamp == 0 {
|
||||||
if len(data) > 0 {
|
if len(data) > 0 {
|
||||||
if data[0].timestamp == 0 {
|
for _, entry := range data {
|
||||||
panic("data[0].timestamp must never be 0")
|
if entry.address == address {
|
||||||
|
if entry.timestamp == 0 {
|
||||||
|
panic("data[0].timestamp must never be 0")
|
||||||
|
}
|
||||||
|
firstTimestamp = uint64(entry.timestamp) - 1
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
firstTimestamp = uint64(data[0].timestamp) - 1
|
}
|
||||||
} else {
|
if firstTimestamp == fromTimestamp {
|
||||||
firstTimestamp = genesisTimestamp
|
firstTimestamp = genesisTimestamp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -145,8 +161,8 @@ func timestampBoundaries(fromTimestamp, toTimestamp uint64, data []*entry) (firs
|
||||||
return firstTimestamp, lastTimestamp
|
return firstTimestamp, lastTimestamp
|
||||||
}
|
}
|
||||||
|
|
||||||
func addPaddingPoints(currency string, address common.Address, toTimestamp uint64, data []*entry, limit int) (res []*entry, err error) {
|
func addPaddingPoints(currency string, addresses []common.Address, toTimestamp uint64, data []*entry, limit int) (res []*entry, err error) {
|
||||||
log.Debug("addPaddingPoints start", "currency", currency, "address", address, "len(data)", len(data), "data", data, "limit", limit)
|
log.Debug("addPaddingPoints start", "currency", currency, "address", addresses, "len(data)", len(data), "data", data, "limit", limit)
|
||||||
|
|
||||||
if len(data) < 2 { // Edge points must be added separately during the previous step
|
if len(data) < 2 { // Edge points must be added separately during the previous step
|
||||||
return nil, errors.New("slice is empty")
|
return nil, errors.New("slice is empty")
|
||||||
|
@ -162,6 +178,11 @@ func addPaddingPoints(currency string, address common.Address, toTimestamp uint6
|
||||||
res = make([]*entry, len(data))
|
res = make([]*entry, len(data))
|
||||||
copy(res, data)
|
copy(res, data)
|
||||||
|
|
||||||
|
var address common.Address
|
||||||
|
if len(addresses) > 0 {
|
||||||
|
address = addresses[0]
|
||||||
|
}
|
||||||
|
|
||||||
for i, j, index := 1, 0, 0; len(res) < limit; index++ {
|
for i, j, index := 1, 0, 0; len(res) < limit; index++ {
|
||||||
// Add a last point to draw a line to. For some cases we might not need it,
|
// Add a last point to draw a line to. For some cases we might not need it,
|
||||||
// but when merging with points from other chains, we might get wrong balance if we don't have it.
|
// but when merging with points from other chains, we might get wrong balance if we don't have it.
|
||||||
|
|
|
@ -2,6 +2,7 @@ package history
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"database/sql"
|
"database/sql"
|
||||||
|
"encoding/hex"
|
||||||
"fmt"
|
"fmt"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
||||||
|
@ -33,10 +34,23 @@ type entry struct {
|
||||||
|
|
||||||
type assetIdentity struct {
|
type assetIdentity struct {
|
||||||
ChainID uint64
|
ChainID uint64
|
||||||
Address common.Address
|
Addresses []common.Address
|
||||||
TokenSymbol string
|
TokenSymbol string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (a *assetIdentity) addressesToString() string {
|
||||||
|
var addressesStr string
|
||||||
|
for i, address := range a.Addresses {
|
||||||
|
addressStr := hex.EncodeToString(address[:])
|
||||||
|
if i == 0 {
|
||||||
|
addressesStr = "X'" + addressStr + "'"
|
||||||
|
} else {
|
||||||
|
addressesStr += ", X'" + addressStr + "'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return addressesStr
|
||||||
|
}
|
||||||
|
|
||||||
func (e *entry) String() string {
|
func (e *entry) String() string {
|
||||||
return fmt.Sprintf("chainID: %v, address: %v, tokenSymbol: %v, tokenAddress: %v, block: %v, timestamp: %v, balance: %v",
|
return fmt.Sprintf("chainID: %v, address: %v, tokenSymbol: %v, tokenAddress: %v, block: %v, timestamp: %v, balance: %v",
|
||||||
e.chainID, e.address, e.tokenSymbol, e.tokenAddress, e.block, e.timestamp, e.balance)
|
e.chainID, e.address, e.tokenSymbol, e.tokenAddress, e.block, e.timestamp, e.balance)
|
||||||
|
@ -87,8 +101,9 @@ func (b *BalanceDB) getEntriesWithoutBalances(chainID uint64, address common.Add
|
||||||
|
|
||||||
func (b *BalanceDB) getNewerThan(identity *assetIdentity, timestamp uint64) (entries []*entry, err error) {
|
func (b *BalanceDB) getNewerThan(identity *assetIdentity, timestamp uint64) (entries []*entry, err error) {
|
||||||
// DISTINCT removes duplicates that can happen when a block has multiple transfers of same token
|
// DISTINCT removes duplicates that can happen when a block has multiple transfers of same token
|
||||||
rawQueryStr := "SELECT DISTINCT block, timestamp, balance FROM balance_history WHERE chain_id = ? AND address = ? AND currency = ? AND timestamp > ? ORDER BY timestamp"
|
rawQueryStr := "SELECT DISTINCT block, timestamp, balance, address FROM balance_history WHERE chain_id = ? AND address IN (%s) AND currency = ? AND timestamp > ? ORDER BY timestamp"
|
||||||
rows, err := b.db.Query(rawQueryStr, identity.ChainID, identity.Address, identity.TokenSymbol, timestamp)
|
queryString := fmt.Sprintf(rawQueryStr, identity.addressesToString())
|
||||||
|
rows, err := b.db.Query(queryString, identity.ChainID, identity.TokenSymbol, timestamp)
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
return nil, nil
|
return nil, nil
|
||||||
} else if err != nil {
|
} else if err != nil {
|
||||||
|
@ -101,12 +116,11 @@ func (b *BalanceDB) getNewerThan(identity *assetIdentity, timestamp uint64) (ent
|
||||||
for rows.Next() {
|
for rows.Next() {
|
||||||
entry := &entry{
|
entry := &entry{
|
||||||
chainID: identity.ChainID,
|
chainID: identity.ChainID,
|
||||||
address: identity.Address,
|
|
||||||
tokenSymbol: identity.TokenSymbol,
|
tokenSymbol: identity.TokenSymbol,
|
||||||
block: new(big.Int),
|
block: new(big.Int),
|
||||||
balance: new(big.Int),
|
balance: new(big.Int),
|
||||||
}
|
}
|
||||||
err := rows.Scan((*bigint.SQLBigInt)(entry.block), &entry.timestamp, (*bigint.SQLBigIntBytes)(entry.balance))
|
err := rows.Scan((*bigint.SQLBigInt)(entry.block), &entry.timestamp, (*bigint.SQLBigIntBytes)(entry.balance), &entry.address)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,7 +30,7 @@ func dbWithEntries(t *testing.T, entries []*entry) *BalanceDB {
|
||||||
func TestBalance_addPaddingPoints(t *testing.T) {
|
func TestBalance_addPaddingPoints(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
currency string
|
currency string
|
||||||
address common.Address
|
addresses []common.Address
|
||||||
fromTimestamp uint64
|
fromTimestamp uint64
|
||||||
currentTimestamp uint64
|
currentTimestamp uint64
|
||||||
data []*entry
|
data []*entry
|
||||||
|
@ -46,7 +46,7 @@ func TestBalance_addPaddingPoints(t *testing.T) {
|
||||||
name: "addOnePaddingPointAtMiddle",
|
name: "addOnePaddingPointAtMiddle",
|
||||||
args: args{
|
args: args{
|
||||||
currency: "ETH",
|
currency: "ETH",
|
||||||
address: common.Address{1},
|
addresses: []common.Address{common.Address{1}},
|
||||||
fromTimestamp: 0,
|
fromTimestamp: 0,
|
||||||
currentTimestamp: 2,
|
currentTimestamp: 2,
|
||||||
data: []*entry{
|
data: []*entry{
|
||||||
|
@ -91,7 +91,7 @@ func TestBalance_addPaddingPoints(t *testing.T) {
|
||||||
name: "noPaddingEqualsLimit",
|
name: "noPaddingEqualsLimit",
|
||||||
args: args{
|
args: args{
|
||||||
currency: "ETH",
|
currency: "ETH",
|
||||||
address: common.Address{1},
|
addresses: []common.Address{common.Address{1}},
|
||||||
fromTimestamp: 0,
|
fromTimestamp: 0,
|
||||||
currentTimestamp: 2,
|
currentTimestamp: 2,
|
||||||
data: []*entry{
|
data: []*entry{
|
||||||
|
@ -134,7 +134,7 @@ func TestBalance_addPaddingPoints(t *testing.T) {
|
||||||
name: "limitLessThanDataSize",
|
name: "limitLessThanDataSize",
|
||||||
args: args{
|
args: args{
|
||||||
currency: "ETH",
|
currency: "ETH",
|
||||||
address: common.Address{1},
|
addresses: []common.Address{common.Address{1}},
|
||||||
fromTimestamp: 0,
|
fromTimestamp: 0,
|
||||||
currentTimestamp: 2,
|
currentTimestamp: 2,
|
||||||
data: []*entry{
|
data: []*entry{
|
||||||
|
@ -177,7 +177,7 @@ func TestBalance_addPaddingPoints(t *testing.T) {
|
||||||
name: "addMultiplePaddingPoints",
|
name: "addMultiplePaddingPoints",
|
||||||
args: args{
|
args: args{
|
||||||
currency: "ETH",
|
currency: "ETH",
|
||||||
address: common.Address{1},
|
addresses: []common.Address{common.Address{1}},
|
||||||
fromTimestamp: 1,
|
fromTimestamp: 1,
|
||||||
currentTimestamp: 5,
|
currentTimestamp: 5,
|
||||||
data: []*entry{
|
data: []*entry{
|
||||||
|
@ -240,7 +240,7 @@ func TestBalance_addPaddingPoints(t *testing.T) {
|
||||||
name: "addMultiplePaddingPointsDuplicateTimestamps",
|
name: "addMultiplePaddingPointsDuplicateTimestamps",
|
||||||
args: args{
|
args: args{
|
||||||
currency: "ETH",
|
currency: "ETH",
|
||||||
address: common.Address{1},
|
addresses: []common.Address{common.Address{1}},
|
||||||
fromTimestamp: 1,
|
fromTimestamp: 1,
|
||||||
currentTimestamp: 5,
|
currentTimestamp: 5,
|
||||||
data: []*entry{
|
data: []*entry{
|
||||||
|
@ -309,7 +309,7 @@ func TestBalance_addPaddingPoints(t *testing.T) {
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
gotRes, err := addPaddingPoints(tt.args.currency, tt.args.address, tt.args.currentTimestamp, tt.args.data, tt.args.limit)
|
gotRes, err := addPaddingPoints(tt.args.currency, tt.args.addresses, tt.args.currentTimestamp, tt.args.data, tt.args.limit)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("Balance.addPaddingPoints() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("Balance.addPaddingPoints() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
|
@ -331,7 +331,7 @@ func TestBalance_addEdgePoints(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
chainID uint64
|
chainID uint64
|
||||||
currency string
|
currency string
|
||||||
address common.Address
|
addresses []common.Address
|
||||||
fromTimestamp uint64
|
fromTimestamp uint64
|
||||||
toTimestamp uint64
|
toTimestamp uint64
|
||||||
data []*entry
|
data []*entry
|
||||||
|
@ -351,7 +351,7 @@ func TestBalance_addEdgePoints(t *testing.T) {
|
||||||
args: args{
|
args: args{
|
||||||
chainID: 111,
|
chainID: 111,
|
||||||
currency: "SNT",
|
currency: "SNT",
|
||||||
address: common.Address{1},
|
addresses: []common.Address{common.Address{1}},
|
||||||
fromTimestamp: 1,
|
fromTimestamp: 1,
|
||||||
toTimestamp: 2,
|
toTimestamp: 2,
|
||||||
data: []*entry{},
|
data: []*entry{},
|
||||||
|
@ -382,7 +382,7 @@ func TestBalance_addEdgePoints(t *testing.T) {
|
||||||
args: args{
|
args: args{
|
||||||
chainID: 111,
|
chainID: 111,
|
||||||
currency: "SNT",
|
currency: "SNT",
|
||||||
address: common.Address{1},
|
addresses: []common.Address{common.Address{1}},
|
||||||
fromTimestamp: 0, // will set to genesisTimestamp
|
fromTimestamp: 0, // will set to genesisTimestamp
|
||||||
toTimestamp: genesisTimestamp + 1,
|
toTimestamp: genesisTimestamp + 1,
|
||||||
data: []*entry{},
|
data: []*entry{},
|
||||||
|
@ -422,7 +422,7 @@ func TestBalance_addEdgePoints(t *testing.T) {
|
||||||
args: args{
|
args: args{
|
||||||
chainID: 111,
|
chainID: 111,
|
||||||
currency: "SNT",
|
currency: "SNT",
|
||||||
address: common.Address{1},
|
addresses: []common.Address{common.Address{1}},
|
||||||
fromTimestamp: 2,
|
fromTimestamp: 2,
|
||||||
toTimestamp: 4,
|
toTimestamp: 4,
|
||||||
data: []*entry{
|
data: []*entry{
|
||||||
|
@ -477,7 +477,7 @@ func TestBalance_addEdgePoints(t *testing.T) {
|
||||||
b := &Balance{
|
b := &Balance{
|
||||||
db: tt.fields.db,
|
db: tt.fields.db,
|
||||||
}
|
}
|
||||||
gotRes, err := b.addEdgePoints(tt.args.chainID, tt.args.currency, tt.args.address, tt.args.fromTimestamp, tt.args.toTimestamp, tt.args.data)
|
gotRes, err := b.addEdgePoints(tt.args.chainID, tt.args.currency, tt.args.addresses, tt.args.fromTimestamp, tt.args.toTimestamp, tt.args.data)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("Balance.addEdgePoints() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("Balance.addEdgePoints() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
|
|
|
@ -108,8 +108,8 @@ func (s *Service) Start() {
|
||||||
}()
|
}()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Service) mergeChainsBalances(chainIDs []uint64, address common.Address, tokenSymbol string, fromTimestamp uint64, data map[uint64][]*entry) ([]*DataPoint, error) {
|
func (s *Service) mergeChainsBalances(chainIDs []uint64, addresses []common.Address, tokenSymbol string, fromTimestamp uint64, data map[uint64][]*entry) ([]*DataPoint, error) {
|
||||||
log.Debug("Merging balances", "address", address, "tokenSymbol", tokenSymbol, "fromTimestamp", fromTimestamp, "len(data)", len(data))
|
log.Debug("Merging balances", "address", addresses, "tokenSymbol", tokenSymbol, "fromTimestamp", fromTimestamp, "len(data)", len(data))
|
||||||
|
|
||||||
toTimestamp := uint64(time.Now().UTC().Unix())
|
toTimestamp := uint64(time.Now().UTC().Unix())
|
||||||
allData := make([]*entry, 0)
|
allData := make([]*entry, 0)
|
||||||
|
@ -118,7 +118,7 @@ func (s *Service) mergeChainsBalances(chainIDs []uint64, address common.Address,
|
||||||
// Iterate over chainIDs param, not data keys, because data may not contain all the chains, but we need edge points for all of them
|
// Iterate over chainIDs param, not data keys, because data may not contain all the chains, but we need edge points for all of them
|
||||||
for _, chainID := range chainIDs {
|
for _, chainID := range chainIDs {
|
||||||
// edge points are needed to properly calculate total balance, as they contain the balance for the first and last timestamp
|
// edge points are needed to properly calculate total balance, as they contain the balance for the first and last timestamp
|
||||||
chainData, err := s.balance.addEdgePoints(chainID, tokenSymbol, address, fromTimestamp, toTimestamp, data[chainID])
|
chainData, err := s.balance.addEdgePoints(chainID, tokenSymbol, addresses, fromTimestamp, toTimestamp, data[chainID])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -137,34 +137,60 @@ func (s *Service) mergeChainsBalances(chainIDs []uint64, address common.Address,
|
||||||
|
|
||||||
// Add padding points to make chart look nice
|
// Add padding points to make chart look nice
|
||||||
if len(allData) < minPointsForGraph {
|
if len(allData) < minPointsForGraph {
|
||||||
allData, _ = addPaddingPoints(tokenSymbol, address, toTimestamp, allData, minPointsForGraph)
|
allData, _ = addPaddingPoints(tokenSymbol, addresses, toTimestamp, allData, minPointsForGraph)
|
||||||
}
|
}
|
||||||
|
|
||||||
return entriesToDataPoints(chainIDs, allData)
|
return entriesToDataPoints(allData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expects sorted data
|
// Expects sorted data
|
||||||
func entriesToDataPoints(chainIDs []uint64, data []*entry) ([]*DataPoint, error) {
|
func entriesToDataPoints(data []*entry) ([]*DataPoint, error) {
|
||||||
var resSlice []*DataPoint
|
var resSlice []*DataPoint
|
||||||
var groupedEntries []*entry // Entries with the same timestamp
|
var groupedEntries []*entry // Entries with the same timestamp
|
||||||
|
|
||||||
sumBalances := func(entries []*entry) *big.Int {
|
type AddressKey struct {
|
||||||
|
Address common.Address
|
||||||
|
ChainID uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
sumBalances := func(balanceMap map[AddressKey]*big.Int) *big.Int {
|
||||||
|
// Sum balances of all accounts and chains in current timestamp
|
||||||
sum := big.NewInt(0)
|
sum := big.NewInt(0)
|
||||||
for _, entry := range entries {
|
for _, balance := range balanceMap {
|
||||||
sum.Add(sum, entry.balance)
|
sum.Add(sum, balance)
|
||||||
}
|
}
|
||||||
return sum
|
return sum
|
||||||
}
|
}
|
||||||
|
|
||||||
// calculate balance for entries with the same timestam and add a single point for them
|
updateBalanceMap := func(balanceMap map[AddressKey]*big.Int, entries []*entry) map[AddressKey]*big.Int {
|
||||||
|
// Update balance map for this timestamp
|
||||||
|
for _, entry := range entries {
|
||||||
|
if entry.chainID == 0 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
key := AddressKey{
|
||||||
|
Address: entry.address,
|
||||||
|
ChainID: entry.chainID,
|
||||||
|
}
|
||||||
|
balanceMap[key] = entry.balance
|
||||||
|
}
|
||||||
|
return balanceMap
|
||||||
|
}
|
||||||
|
|
||||||
|
// Balance map always contains current balance for each address in specific timestamp
|
||||||
|
// It is required to sum up balances from previous timestamp from accounts not present in current timestamp
|
||||||
|
balanceMap := make(map[AddressKey]*big.Int)
|
||||||
|
|
||||||
for _, entry := range data {
|
for _, entry := range data {
|
||||||
if len(groupedEntries) > 0 {
|
if len(groupedEntries) > 0 {
|
||||||
if entry.timestamp == groupedEntries[0].timestamp {
|
if entry.timestamp == groupedEntries[0].timestamp {
|
||||||
groupedEntries = append(groupedEntries, entry)
|
groupedEntries = append(groupedEntries, entry)
|
||||||
continue
|
continue
|
||||||
} else {
|
} else {
|
||||||
// Calculate balance for the grouped entries
|
// Split grouped entries into addresses
|
||||||
cumulativeBalance := sumBalances(groupedEntries)
|
balanceMap = updateBalanceMap(balanceMap, groupedEntries)
|
||||||
|
// Calculate balance for all the addresses
|
||||||
|
cumulativeBalance := sumBalances(balanceMap)
|
||||||
// Points in slice contain balances for all chains
|
// Points in slice contain balances for all chains
|
||||||
resSlice = appendPointToSlice(resSlice, &DataPoint{
|
resSlice = appendPointToSlice(resSlice, &DataPoint{
|
||||||
Timestamp: uint64(groupedEntries[0].timestamp),
|
Timestamp: uint64(groupedEntries[0].timestamp),
|
||||||
|
@ -182,7 +208,10 @@ func entriesToDataPoints(chainIDs []uint64, data []*entry) ([]*DataPoint, error)
|
||||||
|
|
||||||
// If only edge points are present, groupedEntries will be non-empty
|
// If only edge points are present, groupedEntries will be non-empty
|
||||||
if len(groupedEntries) > 0 {
|
if len(groupedEntries) > 0 {
|
||||||
cumulativeBalance := sumBalances(groupedEntries)
|
// Split grouped entries into addresses
|
||||||
|
balanceMap = updateBalanceMap(balanceMap, groupedEntries)
|
||||||
|
// Calculate balance for all the addresses
|
||||||
|
cumulativeBalance := sumBalances(balanceMap)
|
||||||
resSlice = appendPointToSlice(resSlice, &DataPoint{
|
resSlice = appendPointToSlice(resSlice, &DataPoint{
|
||||||
Timestamp: uint64(groupedEntries[0].timestamp),
|
Timestamp: uint64(groupedEntries[0].timestamp),
|
||||||
Balance: (*hexutil.Big)(cumulativeBalance),
|
Balance: (*hexutil.Big)(cumulativeBalance),
|
||||||
|
@ -210,12 +239,12 @@ func appendPointToSlice(slice []*DataPoint, point *DataPoint) []*DataPoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBalanceHistory returns token count balance
|
// GetBalanceHistory returns token count balance
|
||||||
func (s *Service) GetBalanceHistory(ctx context.Context, chainIDs []uint64, address common.Address, tokenSymbol string, currencySymbol string, fromTimestamp uint64) ([]*ValuePoint, error) {
|
func (s *Service) GetBalanceHistory(ctx context.Context, chainIDs []uint64, addresses []common.Address, tokenSymbol string, currencySymbol string, fromTimestamp uint64) ([]*ValuePoint, error) {
|
||||||
log.Debug("GetBalanceHistory", "chainIDs", chainIDs, "address", address.String(), "tokenSymbol", tokenSymbol, "currencySymbol", currencySymbol, "fromTimestamp", fromTimestamp)
|
log.Debug("GetBalanceHistory", "chainIDs", chainIDs, "address", addresses, "tokenSymbol", tokenSymbol, "currencySymbol", currencySymbol, "fromTimestamp", fromTimestamp)
|
||||||
|
|
||||||
chainDataMap := make(map[uint64][]*entry)
|
chainDataMap := make(map[uint64][]*entry)
|
||||||
for _, chainID := range chainIDs {
|
for _, chainID := range chainIDs {
|
||||||
chainData, err := s.balance.get(ctx, chainID, tokenSymbol, address, fromTimestamp) // TODO Make chainID a slice?
|
chainData, err := s.balance.get(ctx, chainID, tokenSymbol, addresses, fromTimestamp) // TODO Make chainID a slice?
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -228,7 +257,8 @@ func (s *Service) GetBalanceHistory(ctx context.Context, chainIDs []uint64, addr
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to get balance for all the chains for the first timestamp, otherwise total values will be incorrect
|
// Need to get balance for all the chains for the first timestamp, otherwise total values will be incorrect
|
||||||
data, err := s.mergeChainsBalances(chainIDs, address, tokenSymbol, fromTimestamp, chainDataMap)
|
data, err := s.mergeChainsBalances(chainIDs, addresses, tokenSymbol, fromTimestamp, chainDataMap)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
} else if len(data) == 0 {
|
} else if len(data) == 0 {
|
||||||
|
|
|
@ -5,13 +5,13 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/ethereum/go-ethereum/common"
|
||||||
"github.com/ethereum/go-ethereum/common/hexutil"
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Test_entriesToDataPoints(t *testing.T) {
|
func Test_entriesToDataPoints(t *testing.T) {
|
||||||
type args struct {
|
type args struct {
|
||||||
chainIDs []uint64
|
data []*entry
|
||||||
data []*entry
|
|
||||||
}
|
}
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
|
@ -22,7 +22,6 @@ func Test_entriesToDataPoints(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "zeroAllChainsSameTimestamp",
|
name: "zeroAllChainsSameTimestamp",
|
||||||
args: args{
|
args: args{
|
||||||
chainIDs: []uint64{1, 2},
|
|
||||||
data: []*entry{
|
data: []*entry{
|
||||||
{
|
{
|
||||||
chainID: 1,
|
chainID: 1,
|
||||||
|
@ -49,7 +48,6 @@ func Test_entriesToDataPoints(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "oneZeroAllChainsDifferentTimestamp",
|
name: "oneZeroAllChainsDifferentTimestamp",
|
||||||
args: args{
|
args: args{
|
||||||
chainIDs: []uint64{1, 2},
|
|
||||||
data: []*entry{
|
data: []*entry{
|
||||||
{
|
{
|
||||||
chainID: 2,
|
chainID: 2,
|
||||||
|
@ -80,7 +78,6 @@ func Test_entriesToDataPoints(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "nonZeroAllChainsDifferentTimestamp",
|
name: "nonZeroAllChainsDifferentTimestamp",
|
||||||
args: args{
|
args: args{
|
||||||
chainIDs: []uint64{1, 2},
|
|
||||||
data: []*entry{
|
data: []*entry{
|
||||||
{
|
{
|
||||||
chainID: 2,
|
chainID: 2,
|
||||||
|
@ -100,7 +97,7 @@ func Test_entriesToDataPoints(t *testing.T) {
|
||||||
Timestamp: 1,
|
Timestamp: 1,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Balance: (*hexutil.Big)(big.NewInt(2)),
|
Balance: (*hexutil.Big)(big.NewInt(3)),
|
||||||
Timestamp: 2,
|
Timestamp: 2,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -109,7 +106,6 @@ func Test_entriesToDataPoints(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "sameChainDifferentTimestamp",
|
name: "sameChainDifferentTimestamp",
|
||||||
args: args{
|
args: args{
|
||||||
chainIDs: []uint64{1, 2},
|
|
||||||
data: []*entry{
|
data: []*entry{
|
||||||
{
|
{
|
||||||
chainID: 1,
|
chainID: 1,
|
||||||
|
@ -149,7 +145,6 @@ func Test_entriesToDataPoints(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "sameChainDifferentTimestampOtherChainsEmpty",
|
name: "sameChainDifferentTimestampOtherChainsEmpty",
|
||||||
args: args{
|
args: args{
|
||||||
chainIDs: []uint64{1, 2},
|
|
||||||
data: []*entry{
|
data: []*entry{
|
||||||
{
|
{
|
||||||
chainID: 1,
|
chainID: 1,
|
||||||
|
@ -195,7 +190,6 @@ func Test_entriesToDataPoints(t *testing.T) {
|
||||||
{
|
{
|
||||||
name: "onlyEdgePointsOnManyChainsWithPadding",
|
name: "onlyEdgePointsOnManyChainsWithPadding",
|
||||||
args: args{
|
args: args{
|
||||||
chainIDs: []uint64{1, 2, 3},
|
|
||||||
data: []*entry{
|
data: []*entry{
|
||||||
// Left edge - same timestamp
|
// Left edge - same timestamp
|
||||||
{
|
{
|
||||||
|
@ -271,11 +265,67 @@ func Test_entriesToDataPoints(t *testing.T) {
|
||||||
},
|
},
|
||||||
wantErr: false,
|
wantErr: false,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
name: "multipleAddresses",
|
||||||
|
args: args{
|
||||||
|
data: []*entry{
|
||||||
|
{
|
||||||
|
chainID: 2,
|
||||||
|
balance: big.NewInt(5),
|
||||||
|
timestamp: 1,
|
||||||
|
address: common.Address{1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
chainID: 1,
|
||||||
|
balance: big.NewInt(6),
|
||||||
|
timestamp: 1,
|
||||||
|
address: common.Address{2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
chainID: 1,
|
||||||
|
balance: big.NewInt(1),
|
||||||
|
timestamp: 2,
|
||||||
|
address: common.Address{1},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
chainID: 1,
|
||||||
|
balance: big.NewInt(2),
|
||||||
|
timestamp: 3,
|
||||||
|
address: common.Address{2},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
chainID: 1,
|
||||||
|
balance: big.NewInt(4),
|
||||||
|
timestamp: 4,
|
||||||
|
address: common.Address{2},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
want: []*DataPoint{
|
||||||
|
{
|
||||||
|
Balance: (*hexutil.Big)(big.NewInt(11)),
|
||||||
|
Timestamp: 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Balance: (*hexutil.Big)(big.NewInt(12)),
|
||||||
|
Timestamp: 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Balance: (*hexutil.Big)(big.NewInt(8)),
|
||||||
|
Timestamp: 3,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Balance: (*hexutil.Big)(big.NewInt(10)),
|
||||||
|
Timestamp: 4,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
got, err := entriesToDataPoints(tt.args.chainIDs, tt.args.data)
|
got, err := entriesToDataPoints(tt.args.data)
|
||||||
if (err != nil) != tt.wantErr {
|
if (err != nil) != tt.wantErr {
|
||||||
t.Errorf("entriesToDataPoints() error = %v, wantErr %v", err, tt.wantErr)
|
t.Errorf("entriesToDataPoints() error = %v, wantErr %v", err, tt.wantErr)
|
||||||
return
|
return
|
||||||
|
|
Loading…
Reference in New Issue