2022-11-15 12:14:41 +00:00
|
|
|
package history
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
2023-10-04 12:00:12 +00:00
|
|
|
"fmt"
|
2022-11-15 12:14:41 +00:00
|
|
|
"math/big"
|
|
|
|
"time"
|
|
|
|
|
2024-10-28 20:54:17 +00:00
|
|
|
"go.uber.org/zap"
|
|
|
|
|
2022-11-15 12:14:41 +00:00
|
|
|
"github.com/ethereum/go-ethereum/common"
|
|
|
|
"github.com/ethereum/go-ethereum/common/hexutil"
|
2024-10-28 20:54:17 +00:00
|
|
|
"github.com/status-im/status-go/logutils"
|
2022-11-15 12:14:41 +00:00
|
|
|
)
|
|
|
|
|
2023-10-04 12:00:12 +00:00
|
|
|
const genesisTimestamp = 1438269988
|
2022-11-15 12:14:41 +00:00
|
|
|
|
2023-10-04 12:00:12 +00:00
|
|
|
// Specific time intervals for which balance history can be fetched
|
2022-11-15 12:14:41 +00:00
|
|
|
type TimeInterval int
|
|
|
|
|
|
|
|
const (
|
|
|
|
BalanceHistory7Days TimeInterval = iota + 1
|
|
|
|
BalanceHistory1Month
|
|
|
|
BalanceHistory6Months
|
|
|
|
BalanceHistory1Year
|
|
|
|
BalanceHistoryAllTime
|
|
|
|
)
|
|
|
|
|
2023-10-04 12:00:12 +00:00
|
|
|
const aDay = time.Duration(24) * time.Hour
|
|
|
|
|
2022-11-15 12:14:41 +00:00
|
|
|
var timeIntervalDuration = map[TimeInterval]time.Duration{
|
|
|
|
BalanceHistory7Days: time.Duration(7) * aDay,
|
|
|
|
BalanceHistory1Month: time.Duration(30) * aDay,
|
|
|
|
BalanceHistory6Months: time.Duration(6*30) * aDay,
|
|
|
|
BalanceHistory1Year: time.Duration(365) * aDay,
|
|
|
|
}
|
|
|
|
|
2023-10-04 12:00:12 +00:00
|
|
|
func TimeIntervalDurationSecs(timeInterval TimeInterval) uint64 {
|
|
|
|
return uint64(timeIntervalDuration[timeInterval].Seconds())
|
2022-11-15 12:14:41 +00:00
|
|
|
}
|
|
|
|
|
2023-10-04 12:00:12 +00:00
|
|
|
type DataPoint struct {
|
|
|
|
Balance *hexutil.Big
|
|
|
|
Timestamp uint64
|
|
|
|
BlockNumber *hexutil.Big
|
2022-11-15 12:14:41 +00:00
|
|
|
}
|
|
|
|
|
2023-10-04 12:00:12 +00:00
|
|
|
// String returns a string representation of the data point
|
|
|
|
func (d *DataPoint) String() string {
|
|
|
|
return fmt.Sprintf("timestamp: %d balance: %v block: %v", d.Timestamp, d.Balance.ToInt(), d.BlockNumber.ToInt())
|
|
|
|
}
|
2023-02-22 21:22:50 +00:00
|
|
|
|
2023-10-04 12:00:12 +00:00
|
|
|
type Balance struct {
|
|
|
|
db *BalanceDB
|
2023-02-22 21:22:50 +00:00
|
|
|
}
|
|
|
|
|
2022-11-15 12:14:41 +00:00
|
|
|
func NewBalance(db *BalanceDB) *Balance {
|
2023-10-04 12:00:12 +00:00
|
|
|
return &Balance{db}
|
2022-11-15 12:14:41 +00:00
|
|
|
}
|
|
|
|
|
2023-10-04 12:00:12 +00:00
|
|
|
// get returns the balance history for the given address from the given timestamp till now
|
2023-11-29 11:20:18 +00:00
|
|
|
func (b *Balance) get(ctx context.Context, chainID uint64, currency string, addresses []common.Address, fromTimestamp uint64) ([]*entry, error) {
|
2024-10-28 20:54:17 +00:00
|
|
|
logutils.ZapLogger().Debug("Getting balance history",
|
|
|
|
zap.Uint64("chainID", chainID),
|
|
|
|
zap.String("currency", currency),
|
|
|
|
zap.Stringers("address", addresses),
|
|
|
|
zap.Uint64("fromTimestamp", fromTimestamp),
|
|
|
|
)
|
2022-11-15 12:14:41 +00:00
|
|
|
|
2023-11-29 11:20:18 +00:00
|
|
|
cached, err := b.db.getNewerThan(&assetIdentity{chainID, addresses, currency}, fromTimestamp)
|
2023-10-04 12:00:12 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return cached, nil
|
2022-11-15 12:14:41 +00:00
|
|
|
}
|
|
|
|
|
2023-11-29 11:20:18 +00:00
|
|
|
func (b *Balance) addEdgePoints(chainID uint64, currency string, addresses []common.Address, fromTimestamp, toTimestamp uint64, data []*entry) (res []*entry, err error) {
|
2024-10-28 20:54:17 +00:00
|
|
|
logutils.ZapLogger().Debug("Adding edge points",
|
|
|
|
zap.Uint64("chainID", chainID),
|
|
|
|
zap.String("currency", currency),
|
|
|
|
zap.Stringers("address", addresses),
|
|
|
|
zap.Uint64("fromTimestamp", fromTimestamp),
|
|
|
|
)
|
2022-11-15 12:14:41 +00:00
|
|
|
|
2024-07-16 14:02:52 +00:00
|
|
|
if len(addresses) == 0 {
|
|
|
|
return nil, errors.New("addresses must not be empty")
|
|
|
|
}
|
2023-10-04 12:00:12 +00:00
|
|
|
|
2024-07-16 14:02:52 +00:00
|
|
|
res = data
|
2023-11-29 11:20:18 +00:00
|
|
|
|
2024-07-16 14:02:52 +00:00
|
|
|
var firstEntry *entry
|
2022-11-15 12:14:41 +00:00
|
|
|
|
2024-07-16 14:02:52 +00:00
|
|
|
if len(data) > 0 {
|
|
|
|
firstEntry = data[0]
|
|
|
|
} else {
|
|
|
|
firstEntry = &entry{
|
|
|
|
chainID: chainID,
|
|
|
|
address: addresses[0],
|
|
|
|
tokenSymbol: currency,
|
|
|
|
timestamp: int64(fromTimestamp),
|
2023-11-29 11:20:18 +00:00
|
|
|
}
|
2024-07-16 14:02:52 +00:00
|
|
|
}
|
2023-11-29 11:20:18 +00:00
|
|
|
|
2024-07-16 14:02:52 +00:00
|
|
|
previous, err := b.db.getEntryPreviousTo(firstEntry)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-11-15 12:14:41 +00:00
|
|
|
|
2024-07-16 14:02:52 +00:00
|
|
|
firstTimestamp, lastTimestamp := timestampBoundaries(fromTimestamp, toTimestamp, data)
|
2023-10-04 12:00:12 +00:00
|
|
|
|
2024-07-16 14:02:52 +00:00
|
|
|
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{
|
|
|
|
{
|
2023-10-04 12:00:12 +00:00
|
|
|
chainID: chainID,
|
2024-07-16 14:02:52 +00:00
|
|
|
address: addresses[0],
|
2023-10-04 12:00:12 +00:00
|
|
|
tokenSymbol: currency,
|
2024-07-16 14:02:52 +00:00
|
|
|
timestamp: int64(firstTimestamp),
|
|
|
|
balance: big.NewInt(0),
|
|
|
|
},
|
|
|
|
}, res...)
|
|
|
|
}
|
|
|
|
|
|
|
|
lastPoint := res[len(res)-1]
|
|
|
|
if lastPoint.timestamp < int64(lastTimestamp) {
|
|
|
|
// Add a last point to draw a line to
|
|
|
|
res = append(res, &entry{
|
|
|
|
chainID: chainID,
|
|
|
|
address: lastPoint.address,
|
|
|
|
tokenSymbol: currency,
|
|
|
|
timestamp: int64(lastTimestamp),
|
|
|
|
balance: lastPoint.balance,
|
|
|
|
})
|
2022-11-15 12:14:41 +00:00
|
|
|
}
|
|
|
|
|
2023-10-04 12:00:12 +00:00
|
|
|
return res, nil
|
2022-11-15 12:14:41 +00:00
|
|
|
}
|
|
|
|
|
2024-07-16 14:02:52 +00:00
|
|
|
func timestampBoundaries(fromTimestamp, toTimestamp uint64, data []*entry) (firstTimestamp, lastTimestamp uint64) {
|
2023-10-04 12:00:12 +00:00
|
|
|
firstTimestamp = fromTimestamp
|
|
|
|
if fromTimestamp == 0 {
|
|
|
|
if len(data) > 0 {
|
2024-07-16 14:02:52 +00:00
|
|
|
if data[0].timestamp == 0 {
|
|
|
|
panic("data[0].timestamp must never be 0")
|
2023-10-04 12:00:12 +00:00
|
|
|
}
|
2024-07-16 14:02:52 +00:00
|
|
|
firstTimestamp = uint64(data[0].timestamp) - 1
|
|
|
|
} else {
|
2023-10-04 12:00:12 +00:00
|
|
|
firstTimestamp = genesisTimestamp
|
2022-11-15 12:14:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-04 12:00:12 +00:00
|
|
|
if toTimestamp < firstTimestamp {
|
|
|
|
panic("toTimestamp < fromTimestamp")
|
2022-11-15 12:14:41 +00:00
|
|
|
}
|
|
|
|
|
2023-10-04 12:00:12 +00:00
|
|
|
lastTimestamp = toTimestamp
|
2022-11-15 12:14:41 +00:00
|
|
|
|
2023-10-04 12:00:12 +00:00
|
|
|
return firstTimestamp, lastTimestamp
|
2022-11-15 12:14:41 +00:00
|
|
|
}
|
|
|
|
|
2023-11-29 11:20:18 +00:00
|
|
|
func addPaddingPoints(currency string, addresses []common.Address, toTimestamp uint64, data []*entry, limit int) (res []*entry, err error) {
|
2024-10-28 20:54:17 +00:00
|
|
|
logutils.ZapLogger().Debug("addPaddingPoints start",
|
|
|
|
zap.String("currency", currency),
|
|
|
|
zap.Stringers("address", addresses),
|
|
|
|
zap.Int("len(data)", len(data)),
|
|
|
|
zap.Any("data", data),
|
|
|
|
zap.Int("limit", limit),
|
|
|
|
)
|
2022-11-15 12:14:41 +00:00
|
|
|
|
2023-10-04 12:00:12 +00:00
|
|
|
if len(data) < 2 { // Edge points must be added separately during the previous step
|
|
|
|
return nil, errors.New("slice is empty")
|
2022-11-15 12:14:41 +00:00
|
|
|
}
|
|
|
|
|
2023-10-04 12:00:12 +00:00
|
|
|
if limit <= len(data) {
|
|
|
|
return data, nil
|
2022-11-15 12:14:41 +00:00
|
|
|
}
|
|
|
|
|
2023-10-04 12:00:12 +00:00
|
|
|
fromTimestamp := uint64(data[0].timestamp)
|
|
|
|
delta := (toTimestamp - fromTimestamp) / uint64(limit-1)
|
2022-11-15 12:14:41 +00:00
|
|
|
|
2023-10-04 12:00:12 +00:00
|
|
|
res = make([]*entry, len(data))
|
|
|
|
copy(res, data)
|
2022-11-15 12:14:41 +00:00
|
|
|
|
2023-11-29 11:20:18 +00:00
|
|
|
var address common.Address
|
|
|
|
if len(addresses) > 0 {
|
|
|
|
address = addresses[0]
|
|
|
|
}
|
|
|
|
|
2023-10-04 12:00:12 +00:00
|
|
|
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,
|
|
|
|
// but when merging with points from other chains, we might get wrong balance if we don't have it.
|
|
|
|
paddingTimestamp := int64(fromTimestamp + delta*uint64(i))
|
2022-11-15 12:14:41 +00:00
|
|
|
|
2023-10-04 12:00:12 +00:00
|
|
|
if paddingTimestamp < data[j].timestamp {
|
|
|
|
// make a room for a new point
|
|
|
|
res = append(res[:index+1], res[index:]...)
|
|
|
|
// insert a new point
|
|
|
|
entry := &entry{
|
|
|
|
address: address,
|
|
|
|
tokenSymbol: currency,
|
|
|
|
timestamp: paddingTimestamp,
|
|
|
|
balance: data[j-1].balance, // take the previous balance
|
2024-07-16 14:02:52 +00:00
|
|
|
chainID: data[j-1].chainID,
|
2022-11-15 12:14:41 +00:00
|
|
|
}
|
2023-10-04 12:00:12 +00:00
|
|
|
res[index] = entry
|
2022-11-15 12:14:41 +00:00
|
|
|
|
2024-10-28 20:54:17 +00:00
|
|
|
logutils.ZapLogger().Debug("Added padding point",
|
|
|
|
zap.Stringer("entry", entry),
|
|
|
|
zap.Int64("timestamp", paddingTimestamp),
|
|
|
|
zap.Int("i", i),
|
|
|
|
zap.Int("j", j),
|
|
|
|
zap.Int("index", index),
|
|
|
|
)
|
2023-10-04 12:00:12 +00:00
|
|
|
i++
|
|
|
|
} else if paddingTimestamp >= data[j].timestamp {
|
2024-10-28 20:54:17 +00:00
|
|
|
logutils.ZapLogger().Debug("Kept real point",
|
|
|
|
zap.Any("entry", data[j]),
|
|
|
|
zap.Int64("timestamp", paddingTimestamp),
|
|
|
|
zap.Int("i", i),
|
|
|
|
zap.Int("j", j),
|
|
|
|
zap.Int("index", index),
|
|
|
|
)
|
2023-10-04 12:00:12 +00:00
|
|
|
j++
|
|
|
|
}
|
2022-11-15 12:14:41 +00:00
|
|
|
}
|
|
|
|
|
2024-10-28 20:54:17 +00:00
|
|
|
logutils.ZapLogger().Debug("addPaddingPoints end", zap.Int("len(res)", len(res)))
|
2022-11-15 12:14:41 +00:00
|
|
|
|
2023-10-04 12:00:12 +00:00
|
|
|
return res, nil
|
2022-11-15 12:14:41 +00:00
|
|
|
}
|