github.com/status-im/status-go@v1.1.0/services/wallet/history/balance.go (about) 1 package history 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "math/big" 8 "time" 9 10 "github.com/ethereum/go-ethereum/common" 11 "github.com/ethereum/go-ethereum/common/hexutil" 12 "github.com/ethereum/go-ethereum/log" 13 ) 14 15 const genesisTimestamp = 1438269988 16 17 // Specific time intervals for which balance history can be fetched 18 type TimeInterval int 19 20 const ( 21 BalanceHistory7Days TimeInterval = iota + 1 22 BalanceHistory1Month 23 BalanceHistory6Months 24 BalanceHistory1Year 25 BalanceHistoryAllTime 26 ) 27 28 const aDay = time.Duration(24) * time.Hour 29 30 var timeIntervalDuration = map[TimeInterval]time.Duration{ 31 BalanceHistory7Days: time.Duration(7) * aDay, 32 BalanceHistory1Month: time.Duration(30) * aDay, 33 BalanceHistory6Months: time.Duration(6*30) * aDay, 34 BalanceHistory1Year: time.Duration(365) * aDay, 35 } 36 37 func TimeIntervalDurationSecs(timeInterval TimeInterval) uint64 { 38 return uint64(timeIntervalDuration[timeInterval].Seconds()) 39 } 40 41 type DataPoint struct { 42 Balance *hexutil.Big 43 Timestamp uint64 44 BlockNumber *hexutil.Big 45 } 46 47 // String returns a string representation of the data point 48 func (d *DataPoint) String() string { 49 return fmt.Sprintf("timestamp: %d balance: %v block: %v", d.Timestamp, d.Balance.ToInt(), d.BlockNumber.ToInt()) 50 } 51 52 type Balance struct { 53 db *BalanceDB 54 } 55 56 func NewBalance(db *BalanceDB) *Balance { 57 return &Balance{db} 58 } 59 60 // get returns the balance history for the given address from the given timestamp till now 61 func (b *Balance) get(ctx context.Context, chainID uint64, currency string, addresses []common.Address, fromTimestamp uint64) ([]*entry, error) { 62 log.Debug("Getting balance history", "chainID", chainID, "currency", currency, "address", addresses, "fromTimestamp", fromTimestamp) 63 64 cached, err := b.db.getNewerThan(&assetIdentity{chainID, addresses, currency}, fromTimestamp) 65 if err != nil { 66 return nil, err 67 } 68 69 return cached, nil 70 } 71 72 func (b *Balance) addEdgePoints(chainID uint64, currency string, addresses []common.Address, fromTimestamp, toTimestamp uint64, data []*entry) (res []*entry, err error) { 73 log.Debug("Adding edge points", "chainID", chainID, "currency", currency, "address", addresses, "fromTimestamp", fromTimestamp) 74 75 if len(addresses) == 0 { 76 return nil, errors.New("addresses must not be empty") 77 } 78 79 res = data 80 81 var firstEntry *entry 82 83 if len(data) > 0 { 84 firstEntry = data[0] 85 } else { 86 firstEntry = &entry{ 87 chainID: chainID, 88 address: addresses[0], 89 tokenSymbol: currency, 90 timestamp: int64(fromTimestamp), 91 } 92 } 93 94 previous, err := b.db.getEntryPreviousTo(firstEntry) 95 if err != nil { 96 return nil, err 97 } 98 99 firstTimestamp, lastTimestamp := timestampBoundaries(fromTimestamp, toTimestamp, data) 100 101 if previous != nil { 102 previous.timestamp = int64(firstTimestamp) // We might need to use another minimal offset respecting the time interval 103 previous.block = nil 104 res = append([]*entry{previous}, res...) 105 } else { 106 // Add a zero point at the beginning to draw a line from 107 res = append([]*entry{ 108 { 109 chainID: chainID, 110 address: addresses[0], 111 tokenSymbol: currency, 112 timestamp: int64(firstTimestamp), 113 balance: big.NewInt(0), 114 }, 115 }, res...) 116 } 117 118 lastPoint := res[len(res)-1] 119 if lastPoint.timestamp < int64(lastTimestamp) { 120 // Add a last point to draw a line to 121 res = append(res, &entry{ 122 chainID: chainID, 123 address: lastPoint.address, 124 tokenSymbol: currency, 125 timestamp: int64(lastTimestamp), 126 balance: lastPoint.balance, 127 }) 128 } 129 130 return res, nil 131 } 132 133 func timestampBoundaries(fromTimestamp, toTimestamp uint64, data []*entry) (firstTimestamp, lastTimestamp uint64) { 134 firstTimestamp = fromTimestamp 135 if fromTimestamp == 0 { 136 if len(data) > 0 { 137 if data[0].timestamp == 0 { 138 panic("data[0].timestamp must never be 0") 139 } 140 firstTimestamp = uint64(data[0].timestamp) - 1 141 } else { 142 firstTimestamp = genesisTimestamp 143 } 144 } 145 146 if toTimestamp < firstTimestamp { 147 panic("toTimestamp < fromTimestamp") 148 } 149 150 lastTimestamp = toTimestamp 151 152 return firstTimestamp, lastTimestamp 153 } 154 155 func addPaddingPoints(currency string, addresses []common.Address, toTimestamp uint64, data []*entry, limit int) (res []*entry, err error) { 156 log.Debug("addPaddingPoints start", "currency", currency, "address", addresses, "len(data)", len(data), "data", data, "limit", limit) 157 158 if len(data) < 2 { // Edge points must be added separately during the previous step 159 return nil, errors.New("slice is empty") 160 } 161 162 if limit <= len(data) { 163 return data, nil 164 } 165 166 fromTimestamp := uint64(data[0].timestamp) 167 delta := (toTimestamp - fromTimestamp) / uint64(limit-1) 168 169 res = make([]*entry, len(data)) 170 copy(res, data) 171 172 var address common.Address 173 if len(addresses) > 0 { 174 address = addresses[0] 175 } 176 177 for i, j, index := 1, 0, 0; len(res) < limit; index++ { 178 // Add a last point to draw a line to. For some cases we might not need it, 179 // but when merging with points from other chains, we might get wrong balance if we don't have it. 180 paddingTimestamp := int64(fromTimestamp + delta*uint64(i)) 181 182 if paddingTimestamp < data[j].timestamp { 183 // make a room for a new point 184 res = append(res[:index+1], res[index:]...) 185 // insert a new point 186 entry := &entry{ 187 address: address, 188 tokenSymbol: currency, 189 timestamp: paddingTimestamp, 190 balance: data[j-1].balance, // take the previous balance 191 chainID: data[j-1].chainID, 192 } 193 res[index] = entry 194 195 log.Debug("Added padding point", "entry", entry, "timestamp", paddingTimestamp, "i", i, "j", j, "index", index) 196 i++ 197 } else if paddingTimestamp >= data[j].timestamp { 198 log.Debug("Kept real point", "entry", data[j], "timestamp", paddingTimestamp, "i", i, "j", j, "index", index) 199 j++ 200 } 201 } 202 203 log.Debug("addPaddingPoints end", "len(res)", len(res)) 204 205 return res, nil 206 }