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{}