github.com/lino-network/lino@v0.6.11/x/reputation/repv2/store.go (about) 1 package repv2 2 3 import ( 4 "strconv" 5 6 db "github.com/tendermint/tm-db" 7 ) 8 9 // Store - store. 10 type Store interface { 11 Set(key []byte, val []byte) 12 Get(key []byte) []byte 13 Has(key []byte) bool 14 Delete(key []byte) 15 // Iterator over a domain of keys in ascending order. End is exclusive. 16 // Start must be less than end, or the Iterator is invalid. 17 // Iterator must be closed by caller. 18 // To iterate over entire domain, use store.Iterator(nil, nil) 19 // CONTRACT: No writes may happen within a domain while an iterator exists over it. 20 Iterator(start, end []byte) db.Iterator 21 } 22 23 type UserIterator = func(user Uid) bool // return true to break. 24 25 // ReputationStore - store reputation values. 26 // a simple wrapper around kv-store. It does not handle any reputation computation logic, 27 // except for init value for some field, like customer score. 28 // This interface should only be used by the reputation system implementation. 29 type ReputationStore interface { 30 // Export all state to deterministic bytes 31 Export() *UserReputationTable 32 33 // Import state from bytes 34 Import(tb *UserReputationTable) 35 36 // Iterator over usernames 37 IterateUsers(UserIterator) 38 39 GetUserMeta(u Uid) *userMeta 40 SetUserMeta(u Uid, data *userMeta) 41 42 SetRoundMeta(r RoundId, dt *roundMeta) 43 GetRoundMeta(r RoundId) *roundMeta 44 45 // total donation power received of a @p post in @p r round. 46 GetRoundPostMeta(r RoundId, p Pid) *roundPostMeta 47 SetRoundPostMeta(r RoundId, p Pid, dt *roundPostMeta) 48 DelRoundPostMeta(r RoundId, p Pid) 49 50 /// ----------- In this round ------------- 51 // RoundId is the current round, starts from 1. 52 GetCurrentRound() RoundId 53 54 // Global data. 55 GetGameMeta() *gameMeta 56 SetGameMeta(dt *gameMeta) 57 } 58 59 // This store implementation does not have state. It is just a wrapper of read/write of 60 // data necessary for reputation system. Also, it takes the problem, that the underlying 61 // kv store may be an iavl, into account so the number of keys will have a large 62 // impact on performance into account, by trying to minimize the number of keys. 63 // we choosed amino json as serializer because it is deterministic. 64 65 var ( 66 KeySeparator = byte('/') 67 repUserMetaPrefix = []byte{0x00} 68 repRoundMetaPrefix = []byte{0x01} 69 repRoundPostMetaPrefix = []byte{0x02} 70 repGameMetaPrefix = []byte{0x03} 71 ) 72 73 // LastSettled: till which round has user settled, i.e. 74 // his customer score is accurate, initialized as 0. 75 // LastDonationRound: 76 // The last round that user has any donation. 77 // contract is that since any donation will force user to settle any previous customer score, 78 // so if there would be any unsettled customer score, when lastDonationRound > LastSettled, 79 // they are all in the lastDonationRound. 80 type userMeta struct { 81 Consumption Rep `json:"cs"` 82 Hold Rep `json:"hold"` 83 Reputation Rep `json:"rep"` 84 LastSettledRound RoundId `json:"ls"` 85 LastDonationRound RoundId `json:"ldr"` 86 Unsettled []Donation `json:"ust"` 87 } 88 89 // Result: the final result of round, should be set right after round ends. 90 // SumDp: sum of donation power of all donations happened during the round. 91 // top n posts. Ordering is maintained by caller. Note that this is not the final result. 92 type roundMeta struct { 93 Result []Pid `json:"result"` 94 SumIF IF `json:"sum_if"` 95 StartAt Time `json:"start_at"` 96 TopN []PostIFPair `json:"top_n"` 97 } 98 99 type roundPostMeta struct { 100 SumIF IF `json:"dp"` 101 } 102 103 type gameMeta struct { 104 CurrentRound RoundId `json:"current_round"` 105 } 106 107 func getUserMetaKey(u Uid) []byte { 108 return append(repUserMetaPrefix, []byte(u)...) 109 } 110 111 func getRoundMetaKey(r RoundId) []byte { 112 return append(repRoundMetaPrefix, strconv.FormatInt(int64(r), 36)...) 113 } 114 115 func getRoundPostMetaKey(r RoundId, p Pid) []byte { 116 prefix := append(repRoundPostMetaPrefix, strconv.FormatInt(int64(r), 36)...) 117 return append(append(prefix, KeySeparator), []byte(p)...) 118 } 119 120 func getGameKey() []byte { 121 return repGameMetaPrefix 122 } 123 124 // no state. 125 type reputationStoreImpl struct { 126 store Store 127 initReputation int64 128 } 129 130 func NewReputationStore(s Store, initRep int64) ReputationStore { 131 return &reputationStoreImpl{store: s, initReputation: initRep} 132 } 133 134 func (impl reputationStoreImpl) IterateUsers(cb UserIterator) { 135 itr := impl.store.Iterator(repUserMetaPrefix, PrefixEndBytes(repUserMetaPrefix)) 136 defer itr.Close() 137 for ; itr.Valid(); itr.Next() { 138 uid := Uid(itr.Key()[1:]) 139 if cb(uid) { 140 break 141 } 142 } 143 } 144 145 func (impl reputationStoreImpl) Export() *UserReputationTable { 146 rst := &UserReputationTable{} 147 impl.IterateUsers(func(uid Uid) bool { 148 v := impl.GetUserMeta(uid) 149 rst.Reputations = append(rst.Reputations, UserReputation{ 150 Username: uid, 151 CustomerScore: v.Reputation, 152 FreeScore: NewInt(0), 153 IsMiniDollar: true, 154 }) 155 156 return false 157 }) 158 return rst 159 } 160 161 func (impl reputationStoreImpl) Import(tb *UserReputationTable) { 162 for _, v := range tb.Reputations { 163 rep := IntAdd(v.FreeScore, v.CustomerScore) 164 // when import from upgrade-1, do a unit conversion. 165 // 1 testnetcoin = (10^-5 * 0.012) USD = 1200 MiniUSD 166 if !v.IsMiniDollar { 167 rep.Mul(NewInt(1200)) 168 } 169 impl.SetUserMeta(v.Username, &userMeta{ 170 Consumption: rep, 171 Reputation: rep, 172 }) 173 } 174 } 175 176 func (impl reputationStoreImpl) GetUserMeta(u Uid) *userMeta { 177 buf := impl.store.Get(getUserMetaKey(u)) 178 rst := decodeUserMeta(buf) 179 if rst == nil { 180 return &userMeta{ 181 Consumption: NewInt(impl.initReputation), 182 Hold: NewInt(0), 183 Reputation: NewInt(impl.initReputation), 184 LastSettledRound: 0, 185 LastDonationRound: 0, 186 Unsettled: nil, 187 } 188 } 189 return rst 190 } 191 192 func (impl reputationStoreImpl) SetUserMeta(u Uid, data *userMeta) { 193 if data != nil { 194 impl.store.Set(getUserMetaKey(u), encodeUserMeta(data)) 195 } 196 } 197 198 func (impl reputationStoreImpl) GetRoundMeta(r RoundId) *roundMeta { 199 buf := impl.store.Get(getRoundMetaKey(r)) 200 rst := decodeRoundMeta(buf) 201 if rst == nil { 202 return &roundMeta{ 203 Result: nil, 204 SumIF: NewInt(0), 205 StartAt: 0, 206 TopN: nil, 207 } 208 } 209 return rst 210 } 211 212 func (impl reputationStoreImpl) SetRoundMeta(r RoundId, dt *roundMeta) { 213 if dt != nil { 214 impl.store.Set(getRoundMetaKey(r), encodeRoundMeta(dt)) 215 } 216 } 217 218 func (impl reputationStoreImpl) GetRoundPostMeta(r RoundId, p Pid) *roundPostMeta { 219 buf := impl.store.Get(getRoundPostMetaKey(r, p)) 220 rst := decodeRoundPostMeta(buf) 221 if rst == nil { 222 return &roundPostMeta{ 223 SumIF: NewInt(0), 224 } 225 } 226 return rst 227 } 228 229 func (impl reputationStoreImpl) SetRoundPostMeta(r RoundId, p Pid, dt *roundPostMeta) { 230 if dt != nil { 231 impl.store.Set(getRoundPostMetaKey(r, p), encodeRoundPostMeta(dt)) 232 } 233 } 234 235 func (impl reputationStoreImpl) DelRoundPostMeta(r RoundId, p Pid) { 236 impl.store.Delete(getRoundPostMetaKey(r, p)) 237 } 238 239 // game starts with 1 240 func (impl reputationStoreImpl) GetGameMeta() *gameMeta { 241 buf := impl.store.Get(getGameKey()) 242 rst := decodeGameMeta(buf) 243 if rst == nil { 244 return &gameMeta{ 245 CurrentRound: 1, 246 } 247 } 248 return rst 249 } 250 251 func (impl reputationStoreImpl) SetGameMeta(dt *gameMeta) { 252 if dt != nil { 253 impl.store.Set(getGameKey(), encodeGameMeta(dt)) 254 } 255 } 256 257 func (impl reputationStoreImpl) GetCurrentRound() RoundId { 258 rst := impl.GetGameMeta() 259 return rst.CurrentRound 260 } 261 262 var _ ReputationStore = &reputationStoreImpl{}