decred.org/dcrdex@v1.0.5/tatanka/db/reputation.go (about)

     1  // This code is available on the terms of the project LICENSE.md file,
     2  // also available online at https://blueoakcouncil.org/license/1.0.0.
     3  
     4  package db
     5  
     6  import (
     7  	"encoding"
     8  	"encoding/binary"
     9  	"fmt"
    10  	"time"
    11  
    12  	"decred.org/dcrdex/dex"
    13  	"decred.org/dcrdex/tatanka/tanka"
    14  )
    15  
    16  // Reputation entries with key encoded as
    17  //    peer_id | 6_bytes_millisecond_timestamp | 32_byte_penalty_id
    18  // Value is empty if it's a success. For penalties, the value is
    19  //   1_byte_score_decrement | encoded_penalty_proof.
    20  
    21  func (d *DB) Reputation(peerID tanka.PeerID) (rep *tanka.Reputation, err error) {
    22  	rep = &tanka.Reputation{
    23  		Points: make([]int8, 0, tanka.MaxReputationEntries),
    24  	}
    25  	return rep, d.reputationDB.ForEach(func(k, v []byte) error {
    26  		if len(v) == 0 {
    27  			// Success
    28  			rep.Score++
    29  			rep.Points = append(rep.Points, 1)
    30  			return nil
    31  		}
    32  		rep.Score -= int16(v[0])
    33  		rep.Points = append(rep.Points, -int8(v[0]))
    34  		return nil
    35  	}, WithPrefix(peerID[:]), WithReverse(), WithMaxEntries(tanka.MaxReputationEntries, true))
    36  }
    37  
    38  func (d *DB) RegisterSuccess(peerID tanka.PeerID, stamp time.Time, refID [32]byte) error {
    39  	const keyLength = tanka.PeerIDLength + 6 + 32
    40  	k := make([]byte, keyLength)
    41  	copy(k[:tanka.PeerIDLength], peerID[:])
    42  	stampB := make([]byte, 8)
    43  	binary.BigEndian.PutUint64(stampB, uint64(stamp.UnixMilli()))
    44  	copy(k[tanka.PeerIDLength:tanka.PeerIDLength+6], stampB[2:])
    45  	copy(k[tanka.PeerIDLength+6:], refID[:])
    46  
    47  	if err := d.reputationDB.Store(k, dex.Bytes{}); err != nil {
    48  		return fmt.Errorf("error storing success for user %q: %v", peerID, err)
    49  	}
    50  
    51  	// TODO: Periodically run a nanny function to clear extra entries. Otherwise
    52  	// entries are only deleted during the score scan. So theoretically, someone
    53  	// could fill the db with a billion successes. Same in RegisterPenalty.
    54  
    55  	return nil
    56  }
    57  
    58  func (d *DB) RegisterPenalty(peerID tanka.PeerID, stamp time.Time, penaltyID [32]byte, scoreDecrement uint8, penaltyInfo encoding.BinaryMarshaler) error {
    59  	const keyLength = tanka.PeerIDLength + 6 + 32
    60  	k := make([]byte, keyLength)
    61  	copy(k[:tanka.PeerIDLength], peerID[:])
    62  	stampB := make([]byte, 8)
    63  	binary.BigEndian.PutUint64(stampB, uint64(stamp.UnixMilli()))
    64  	copy(k[tanka.PeerIDLength:tanka.PeerIDLength+6], stampB[2:])
    65  	copy(k[tanka.PeerIDLength+6:], penaltyID[:])
    66  
    67  	b, err := penaltyInfo.MarshalBinary()
    68  	if err != nil {
    69  		return fmt.Errorf("error marshaling penalty info: %w", err)
    70  	}
    71  	v := make(dex.Bytes, 1+len(b))
    72  	v[0] = scoreDecrement
    73  	copy(v[1:], b)
    74  	if err := d.reputationDB.Store(k, v); err != nil {
    75  		return fmt.Errorf("error storing penalty: %w", err)
    76  	}
    77  	return nil
    78  }