func TestStorageCacheRemoveCachedAliases(t *testing.T) { ala := &Alias{ Direction: "*out", Tenant: "vdf", Category: "0", Account: "b1", Subject: "b1", Context: utils.ALIAS_CONTEXT_RATING, } alb := &Alias{ Direction: "*out", Tenant: "vdf", Category: "0", Account: "b1", Subject: "b1", Context: utils.ALIAS_CONTEXT_RATING, } accountingStorage.RemoveAlias(ala.GetId()) accountingStorage.RemoveAlias(alb.GetId()) if _, err := cache2go.Get(utils.ALIASES_PREFIX + ala.GetId()); err == nil { t.Error("Error removing cached alias: ", err) } if _, err := cache2go.Get(utils.ALIASES_PREFIX + alb.GetId()); err == nil { t.Error("Error removing cached alias: ", err) } if _, err := cache2go.Get(utils.REVERSE_ALIASES_PREFIX + "aaa" + utils.ALIAS_CONTEXT_RATING); err == nil { t.Error("Error removing cached reverse alias: ", err) } if _, err := cache2go.Get(utils.REVERSE_ALIASES_PREFIX + "aaa" + utils.ALIAS_CONTEXT_RATING); err == nil { t.Error("Error removing cached reverse alias: ", err) } }
func (ms *MapStorage) GetRatingPlan(key string, skipCache bool) (rp *RatingPlan, err error) { key = utils.RATING_PLAN_PREFIX + key if !skipCache { if x, err := cache2go.Get(key); err == nil { return x.(*RatingPlan), nil } else { return nil, err } } if values, ok := ms.dict[key]; ok { b := bytes.NewBuffer(values) r, err := zlib.NewReader(b) if err != nil { return nil, err } out, err := ioutil.ReadAll(r) if err != nil { return nil, err } r.Close() rp = new(RatingPlan) err = ms.ms.Unmarshal(out, rp) cache2go.Cache(key, rp) } else { return nil, utils.ErrNotFound } return }
func (lcra *LCRActivation) GetLCREntryForPrefix(destination string) *LCREntry { var potentials LCREntriesSorter for _, p := range utils.SplitPrefix(destination, MIN_PREFIX_MATCH) { if x, err := cache2go.Get(utils.DESTINATION_PREFIX + p); err == nil { destIds := x.(map[interface{}]struct{}) for idId := range destIds { dId := idId.(string) for _, entry := range lcra.Entries { if entry.DestinationId == dId { entry.precision = len(p) potentials = append(potentials, entry) } } } } } if len(potentials) > 0 { // sort by precision and weight potentials.Sort() return potentials[0] } // return the *any entry if it exists for _, entry := range lcra.Entries { if entry.DestinationId == utils.ANY { return entry } } return nil }
func (ms *MapStorage) RemoveAlias(key string) error { al := &Alias{} al.SetId(key) origKey := key key = utils.ALIASES_PREFIX + key aliasValues := make(AliasValues, 0) if values, ok := ms.dict[key]; ok { ms.ms.Unmarshal(values, &aliasValues) } delete(ms.dict, key) for _, value := range aliasValues { for target, pairs := range value.Pairs { for _, alias := range pairs { var existingKeys map[string]bool rKey := utils.REVERSE_ALIASES_PREFIX + alias + target + al.Context if x, err := cache2go.Get(rKey); err == nil { existingKeys = x.(map[string]bool) } for eKey := range existingKeys { if strings.HasPrefix(eKey, origKey) { delete(existingKeys, eKey) } } if len(existingKeys) == 0 { cache2go.RemKey(rKey) } else { cache2go.Cache(rKey, existingKeys) } } cache2go.RemKey(key) } } return nil }
func TestCleanStalePrefixes(t *testing.T) { x := struct{}{} cache2go.Cache(utils.DESTINATION_PREFIX+"1", map[interface{}]struct{}{"D1": x, "D2": x}) cache2go.Cache(utils.DESTINATION_PREFIX+"2", map[interface{}]struct{}{"D1": x}) cache2go.Cache(utils.DESTINATION_PREFIX+"3", map[interface{}]struct{}{"D2": x}) CleanStalePrefixes([]string{"D1"}) if r, err := cache2go.Get(utils.DESTINATION_PREFIX + "1"); err != nil || len(r.(map[interface{}]struct{})) != 1 { t.Error("Error cleaning stale destination ids", r) } if r, err := cache2go.Get(utils.DESTINATION_PREFIX + "2"); err == nil { t.Error("Error removing stale prefix: ", r) } if r, err := cache2go.Get(utils.DESTINATION_PREFIX + "3"); err != nil || len(r.(map[interface{}]struct{})) != 1 { t.Error("Error performing stale cleaning: ", r) } }
func (am *AliasHandler) GetReverseAlias(attr AttrReverseAlias, result *map[string][]*Alias) error { am.mu.Lock() defer am.mu.Unlock() aliases := make(map[string][]*Alias) rKey := utils.REVERSE_ALIASES_PREFIX + attr.Alias + attr.Target + attr.Context if x, err := cache2go.Get(rKey); err == nil { existingKeys := x.(map[interface{}]struct{}) for iKey := range existingKeys { key := iKey.(string) // get destination id elems := strings.Split(key, utils.CONCATENATED_KEY_SEP) var destID string if len(elems) > 0 { destID = elems[len(elems)-1] key = strings.Join(elems[:len(elems)-1], utils.CONCATENATED_KEY_SEP) } if r, err := am.accountingDb.GetAlias(key, false); err != nil { return err } else { aliases[destID] = append(aliases[destID], r) } } } *result = aliases return nil }
func (ms *MongoStorage) GetDerivedChargers(key string, skipCache bool, transactionID string) (dcs *utils.DerivedChargers, err error) { if !skipCache { if x, ok := cache2go.Get(utils.DERIVEDCHARGERS_PREFIX + key); ok { if x != nil { return x.(*utils.DerivedChargers), nil } return nil, utils.ErrNotFound } } var kv struct { Key string Value *utils.DerivedChargers } session, col := ms.conn(colDcs) defer session.Close() err = col.Find(bson.M{"key": key}).One(&kv) cCommit := cacheCommit(transactionID) if err == nil { dcs = kv.Value } else { cache2go.Set(utils.DERIVEDCHARGERS_PREFIX+key, nil, cCommit, transactionID) return nil, utils.ErrNotFound } cache2go.Set(utils.DERIVEDCHARGERS_PREFIX+key, dcs, cCommit, transactionID) return }
// Limit will only retrieve the last n items out of history, newest first func (ms *MongoStorage) GetLoadHistory(limit int, skipCache bool, transactionID string) (loadInsts []*utils.LoadInstance, err error) { if limit == 0 { return nil, nil } if !skipCache { if x, ok := cache2go.Get(utils.LOADINST_KEY); ok { if x != nil { items := x.([]*utils.LoadInstance) if len(items) < limit || limit == -1 { return items, nil } return items[:limit], nil } return nil, utils.ErrNotFound } } var kv struct { Key string Value []*utils.LoadInstance } session, col := ms.conn(colLht) defer session.Close() err = col.Find(bson.M{"key": utils.LOADINST_KEY}).One(&kv) cCommit := cacheCommit(transactionID) if err == nil { loadInsts = kv.Value cache2go.RemKey(utils.LOADINST_KEY, cCommit, transactionID) cache2go.Set(utils.LOADINST_KEY, loadInsts, cCommit, transactionID) } if len(loadInsts) < limit || limit == -1 { return loadInsts, nil } return loadInsts[:limit], nil }
func (ms *MongoStorage) GetRatingPlan(key string, skipCache bool) (rp *RatingPlan, err error) { if !skipCache { if x, err := cache2go.Get(utils.RATING_PLAN_PREFIX + key); err == nil { return x.(*RatingPlan), nil } else { return nil, err } } rp = new(RatingPlan) var kv struct { Key string Value []byte } err = ms.db.C(colRpl).Find(bson.M{"key": key}).One(&kv) if err == nil { b := bytes.NewBuffer(kv.Value) r, err := zlib.NewReader(b) if err != nil { return nil, err } out, err := ioutil.ReadAll(r) if err != nil { return nil, err } r.Close() err = ms.ms.Unmarshal(out, &rp) if err != nil { return nil, err } cache2go.Cache(utils.RATING_PLAN_PREFIX+key, rp) } return }
func (rs *RedisStorage) GetActionPlan(key string, skipCache bool, transactionID string) (ats *ActionPlan, err error) { key = utils.ACTION_PLAN_PREFIX + key if !skipCache { if x, ok := cache2go.Get(key); ok { if x != nil { return x.(*ActionPlan), nil } return nil, utils.ErrNotFound } } var values []byte if values, err = rs.db.Cmd("GET", key).Bytes(); err == nil { b := bytes.NewBuffer(values) r, err := zlib.NewReader(b) if err != nil { return nil, err } out, err := ioutil.ReadAll(r) if err != nil { return nil, err } r.Close() ats = &ActionPlan{} err = rs.ms.Unmarshal(out, &ats) } cache2go.Set(key, ats, cacheCommit(transactionID), transactionID) return }
func (rs *RedisStorage) GetAlias(key string, skipCache bool, transactionID string) (al *Alias, err error) { origKey := key key = utils.ALIASES_PREFIX + key if !skipCache { if x, ok := cache2go.Get(key); ok { if x != nil { al = &Alias{Values: x.(AliasValues)} al.SetId(origKey) return al, nil } return nil, utils.ErrNotFound } } var values []byte if values, err = rs.db.Cmd("GET", key).Bytes(); err == nil { al = &Alias{Values: make(AliasValues, 0)} al.SetId(origKey) err = rs.ms.Unmarshal(values, &al.Values) } else { cache2go.Set(key, nil, cacheCommit(transactionID), transactionID) return nil, utils.ErrNotFound } cache2go.Set(key, al.Values, cacheCommit(transactionID), transactionID) return }
func (rs *RedisStorage) GetDestination(key string, skipCache bool, transactionID string) (dest *Destination, err error) { key = utils.DESTINATION_PREFIX + key if !skipCache { if x, ok := cache2go.Get(key); ok { if x != nil { return x.(*Destination), nil } return nil, utils.ErrNotFound } } var values []byte if values, err = rs.db.Cmd("GET", key).Bytes(); len(values) > 0 && err == nil { b := bytes.NewBuffer(values) r, err := zlib.NewReader(b) if err != nil { return nil, err } out, err := ioutil.ReadAll(r) if err != nil { return nil, err } r.Close() dest = new(Destination) err = rs.ms.Unmarshal(out, dest) if err != nil { cache2go.Set(key, dest, cacheCommit(transactionID), transactionID) } } else { cache2go.Set(key, nil, cacheCommit(transactionID), transactionID) return nil, err } return }
func (rs *RedisStorage) GetRatingPlan(key string, skipCache bool, transactionID string) (rp *RatingPlan, err error) { key = utils.RATING_PLAN_PREFIX + key if !skipCache { if x, ok := cache2go.Get(key); ok { if x != nil { return x.(*RatingPlan), nil } return nil, utils.ErrNotFound } } var values []byte if values, err = rs.Cmd("GET", key).Bytes(); err == nil { b := bytes.NewBuffer(values) r, err := zlib.NewReader(b) if err != nil { return nil, err } out, err := ioutil.ReadAll(r) if err != nil { return nil, err } r.Close() rp = new(RatingPlan) err = rs.ms.Unmarshal(out, rp) } cache2go.Set(key, rp, cacheCommit(transactionID), transactionID) return }
func (ms *MapStorage) GetAlias(key string, skipCache bool, transactionID string) (al *Alias, err error) { ms.mu.RLock() defer ms.mu.RUnlock() origKey := key key = utils.ALIASES_PREFIX + key if !skipCache { if x, ok := cache2go.Get(key); ok { if x != nil { al = &Alias{Values: x.(AliasValues)} al.SetId(origKey) return al, nil } return nil, utils.ErrNotFound } } cCommit := cacheCommit(transactionID) if values, ok := ms.dict[key]; ok { al = &Alias{Values: make(AliasValues, 0)} al.SetId(key[len(utils.ALIASES_PREFIX):]) err = ms.ms.Unmarshal(values, &al.Values) if err == nil { cache2go.Set(key, al.Values, cCommit, transactionID) } } else { cache2go.Set(key, nil, cCommit, transactionID) return nil, utils.ErrNotFound } return al, nil }
func (ms *MapStorage) GetDestination(key string, skipCache bool, transactionID string) (dest *Destination, err error) { ms.mu.RLock() defer ms.mu.RUnlock() cCommit := cacheCommit(transactionID) key = utils.DESTINATION_PREFIX + key if !skipCache { if x, ok := cache2go.Get(key); ok { if x != nil { return x.(*Destination), nil } return nil, utils.ErrNotFound } } if values, ok := ms.dict[key]; ok { b := bytes.NewBuffer(values) r, err := zlib.NewReader(b) if err != nil { return nil, err } out, err := ioutil.ReadAll(r) if err != nil { return nil, err } r.Close() dest = new(Destination) err = ms.ms.Unmarshal(out, dest) if err != nil { cache2go.Set(key, dest, cCommit, transactionID) } } else { cache2go.Set(key, nil, cCommit, transactionID) return nil, utils.ErrNotFound } return }
func (ms *MongoStorage) GetReverseAlias(reverseID string, skipCache bool, transactionID string) (ids []string, err error) { if !skipCache { if x, ok := cache2go.Get(utils.REVERSE_ALIASES_PREFIX + reverseID); ok { if x != nil { return x.([]string), nil } return nil, utils.ErrNotFound } } var result struct { Key string Value []string } session, col := ms.conn(colRls) defer session.Close() if err = col.Find(bson.M{"key": reverseID}).One(&result); err == nil { ids = result.Value cache2go.Set(utils.REVERSE_ALIASES_PREFIX+reverseID, ids, cacheCommit(transactionID), transactionID) } else { cache2go.Set(utils.REVERSE_ALIASES_PREFIX+reverseID, nil, cacheCommit(transactionID), transactionID) return nil, utils.ErrNotFound } return }
// Reverse search in cache to see if prefix belongs to destination id func CachedDestHasPrefix(destId, prefix string) bool { if cached, err := cache2go.Get(utils.DESTINATION_PREFIX + prefix); err == nil { _, found := cached.(map[interface{}]struct{})[destId] return found } return false }
func (ms *MongoStorage) GetActionPlan(key string, skipCache bool, transactionID string) (ats *ActionPlan, err error) { if !skipCache { if x, ok := cache2go.Get(utils.ACTION_PLAN_PREFIX + key); ok { if x != nil { return x.(*ActionPlan), nil } return nil, utils.ErrNotFound } } var kv struct { Key string Value []byte } session, col := ms.conn(colApl) defer session.Close() err = col.Find(bson.M{"key": key}).One(&kv) if err == nil { b := bytes.NewBuffer(kv.Value) r, err := zlib.NewReader(b) if err != nil { return nil, err } out, err := ioutil.ReadAll(r) if err != nil { return nil, err } r.Close() err = ms.ms.Unmarshal(out, &ats) if err != nil { return nil, err } } cache2go.Set(utils.ACTION_PLAN_PREFIX+key, ats, cacheCommit(transactionID), transactionID) return }
// Adds the units from the received balance to an existing balance if the destination // is the same or ads the balance to the list if none matches. func (uc *UnitsCounter) addUnits(amount float64, prefix string) { counted := false if prefix != "" { for _, mb := range uc.Balances { if !mb.HasDestination() { continue } for _, p := range utils.SplitPrefix(prefix, MIN_PREFIX_MATCH) { if x, err := cache2go.Get(utils.DESTINATION_PREFIX + p); err == nil { destIds := x.(map[interface{}]struct{}) if _, found := destIds[mb.DestinationIds]; found { mb.AddValue(amount) counted = true break } } if counted { break } } } } if !counted { // use general balance b := uc.GetGeneralBalance() b.AddValue(amount) } }
func (ms *MongoStorage) GetLCR(key string, skipCache bool, transactionID string) (lcr *LCR, err error) { if !skipCache { if x, ok := cache2go.Get(utils.LCR_PREFIX + key); ok { if x != nil { return x.(*LCR), nil } return nil, utils.ErrNotFound } } var result struct { Key string Value *LCR } session, col := ms.conn(colLcr) defer session.Close() cCommit := cacheCommit(transactionID) if err = col.Find(bson.M{"key": key}).One(&result); err == nil { lcr = result.Value } else { cache2go.Set(utils.LCR_PREFIX+key, nil, cCommit, transactionID) return nil, utils.ErrNotFound } cache2go.Set(utils.LCR_PREFIX+key, lcr, cCommit, transactionID) return }
func (ms *MongoStorage) GetAlias(key string, skipCache bool) (al *Alias, err error) { origKey := key key = utils.ALIASES_PREFIX + key if !skipCache { if x, err := cache2go.Get(key); err == nil { al = &Alias{Values: x.(AliasValues)} al.SetId(origKey) return al, nil } else { return nil, err } } var kv struct { Key string Value AliasValues } if err = ms.db.C(colAls).Find(bson.M{"key": origKey}).One(&kv); err == nil { al = &Alias{Values: kv.Value} al.SetId(origKey) if err == nil { cache2go.Cache(key, al.Values) // cache reverse alias al.SetReverseCache() } } return }
func (b *Balance) MatchCCFilter(cc *CallCost) bool { if len(b.Categories) > 0 && cc.Category != "" && b.Categories[cc.Category] == false { return false } if len(b.Directions) > 0 && cc.Direction != "" && b.Directions[cc.Direction] == false { return false } // match destination ids foundMatchingDestId := false if len(b.DestinationIds) > 0 && cc.Destination != "" { for _, p := range utils.SplitPrefix(cc.Destination, MIN_PREFIX_MATCH) { if x, err := cache2go.Get(utils.DESTINATION_PREFIX + p); err == nil { destIds := x.(map[interface{}]struct{}) for filterDestId := range b.DestinationIds { if _, ok := destIds[filterDestId]; ok { foundMatchingDestId = true break } } } if foundMatchingDestId { break } } } else { foundMatchingDestId = true } if !foundMatchingDestId { return false } return true }
func (ms *MongoStorage) GetAlias(key string, skipCache bool, transactionID string) (al *Alias, err error) { origKey := key key = utils.ALIASES_PREFIX + key if !skipCache { if x, ok := cache2go.Get(key); ok { if x != nil { al = &Alias{Values: x.(AliasValues)} al.SetId(origKey) return al, nil } return nil, utils.ErrNotFound } } var kv struct { Key string Value AliasValues } session, col := ms.conn(colAls) defer session.Close() cCommit := cacheCommit(transactionID) if err = col.Find(bson.M{"key": origKey}).One(&kv); err == nil { al = &Alias{Values: kv.Value} al.SetId(origKey) if err == nil { cache2go.Set(key, al.Values, cCommit, transactionID) } } else { cache2go.Set(key, nil, cCommit, transactionID) return nil, utils.ErrNotFound } return }
// Limit will only retrieve the last n items out of history, newest first func (ms *MongoStorage) GetLoadHistory(limit int, skipCache bool) (loadInsts []*LoadInstance, err error) { if limit == 0 { return nil, nil } if !skipCache { if x, err := cache2go.Get(utils.LOADINST_KEY); err != nil { return nil, err } else { items := x.([]*LoadInstance) if len(items) < limit || limit == -1 { return items, nil } return items[:limit], nil } } var kv struct { Key string Value []*LoadInstance } err = ms.db.C(colLht).Find(bson.M{"key": utils.LOADINST_KEY}).One(&kv) if err == nil { loadInsts = kv.Value cache2go.RemKey(utils.LOADINST_KEY) cache2go.Cache(utils.LOADINST_KEY, loadInsts) } return loadInsts, err }
func (ms *MapStorage) GetRatingPlan(key string, skipCache bool, transactionID string) (rp *RatingPlan, err error) { ms.mu.RLock() defer ms.mu.RUnlock() key = utils.RATING_PLAN_PREFIX + key if !skipCache { if x, ok := cache2go.Get(key); ok { if x != nil { return x.(*RatingPlan), nil } return nil, utils.ErrNotFound } } cCommit := cacheCommit(transactionID) if values, ok := ms.dict[key]; ok { b := bytes.NewBuffer(values) r, err := zlib.NewReader(b) if err != nil { return nil, err } out, err := ioutil.ReadAll(r) if err != nil { return nil, err } r.Close() rp = new(RatingPlan) err = ms.ms.Unmarshal(out, rp) } else { cache2go.Set(key, nil, cCommit, transactionID) return nil, utils.ErrNotFound } cache2go.Set(key, rp, cCommit, transactionID) return }
// Limit will only retrieve the last n items out of history, newest first func (rs *RedisStorage) GetLoadHistory(limit int, skipCache bool) ([]*LoadInstance, error) { if limit == 0 { return nil, nil } if !skipCache { if x, err := cache2go.Get(utils.LOADINST_KEY); err != nil { return nil, err } else { items := x.([]*LoadInstance) if len(items) < limit || limit == -1 { return items, nil } return items[:limit], nil } } if limit != -1 { limit -= -1 // Decrease limit to match redis approach on lrange } marshaleds, err := rs.db.Lrange(utils.LOADINST_KEY, 0, limit) if err != nil { return nil, err } loadInsts := make([]*LoadInstance, len(marshaleds)) for idx, marshaled := range marshaleds { var lInst LoadInstance err = rs.ms.Unmarshal(marshaled, &lInst) if err != nil { return nil, err } loadInsts[idx] = &lInst } cache2go.RemKey(utils.LOADINST_KEY) cache2go.Cache(utils.LOADINST_KEY, loadInsts) return loadInsts, nil }
func (rs *RedisStorage) GetRatingPlan(key string, skipCache bool) (rp *RatingPlan, err error) { key = utils.RATING_PLAN_PREFIX + key if !skipCache { if x, err := cache2go.Get(key); err == nil { return x.(*RatingPlan), nil } else { return nil, err } } var values []byte if values, err = rs.db.Get(key); err == nil { b := bytes.NewBuffer(values) r, err := zlib.NewReader(b) if err != nil { return nil, err } out, err := ioutil.ReadAll(r) if err != nil { return nil, err } r.Close() rp = new(RatingPlan) err = rs.ms.Unmarshal(out, rp) cache2go.Cache(key, rp) } return }
func (rs *RedisStorage) GetActionPlan(key string, skipCache bool) (ats *ActionPlan, err error) { key = utils.ACTION_PLAN_PREFIX + key if !skipCache { if x, err := cache2go.Get(key); err == nil { return x.(*ActionPlan), nil } else { return nil, err } } var values []byte if values, err = rs.db.Cmd("GET", key).Bytes(); err == nil { b := bytes.NewBuffer(values) r, err := zlib.NewReader(b) if err != nil { return nil, err } out, err := ioutil.ReadAll(r) if err != nil { return nil, err } r.Close() err = rs.ms.Unmarshal(out, &ats) cache2go.Cache(key, ats) } return }
func (rs *RedisStorage) GetAlias(key string, skipCache bool) (al *Alias, err error) { origKey := key key = utils.ALIASES_PREFIX + key if !skipCache { if x, err := cache2go.Get(key); err == nil { al = &Alias{Values: x.(AliasValues)} al.SetId(origKey) return al, nil } else { return nil, err } } var values []byte if values, err = rs.db.Cmd("GET", key).Bytes(); err == nil { al = &Alias{Values: make(AliasValues, 0)} al.SetId(origKey) err = rs.ms.Unmarshal(values, &al.Values) if err == nil { cache2go.Cache(key, al.Values) // cache reverse alias al.SetReverseCache() } } return }
func (ub *Account) getBalancesForPrefix(prefix, category, direction, tor string, sharedGroup string) BalanceChain { var balances BalanceChain balances = append(balances, ub.BalanceMap[tor]...) if tor != utils.MONETARY && tor != utils.GENERIC { balances = append(balances, ub.BalanceMap[utils.GENERIC]...) } var usefulBalances BalanceChain for _, b := range balances { if b.Disabled { continue } if b.IsExpired() || (len(b.SharedGroups) == 0 && b.GetValue() <= 0) { continue } if sharedGroup != "" && b.SharedGroups[sharedGroup] == false { continue } if !b.MatchCategory(category) { continue } if b.HasDirection() && b.Directions[direction] == false { continue } b.account = ub if len(b.DestinationIds) > 0 && b.DestinationIds[utils.ANY] == false { for _, p := range utils.SplitPrefix(prefix, MIN_PREFIX_MATCH) { if x, err := cache2go.Get(utils.DESTINATION_PREFIX + p); err == nil { destIds := x.(map[interface{}]struct{}) for dId, _ := range destIds { if b.DestinationIds[dId.(string)] == true { b.precision = len(p) usefulBalances = append(usefulBalances, b) break } if b.precision > 0 { break } } } if b.precision > 0 { break } } } else { usefulBalances = append(usefulBalances, b) } } // resort by precision usefulBalances.Sort() // clear precision for _, b := range usefulBalances { b.precision = 0 } return usefulBalances }