github.com/decred/politeia@v1.4.0/politeiad/backendv2/tstorebe/plugins/ticketvote/timestamp.go (about)

     1  // Copyright (c) 2022 The Decred developers
     2  // Use of this source code is governed by an ISC
     3  // license that can be found in the LICENSE file.
     4  
     5  package ticketvote
     6  
     7  import (
     8  	"encoding/hex"
     9  	"encoding/json"
    10  	"fmt"
    11  	"strconv"
    12  	"strings"
    13  
    14  	"github.com/decred/politeia/politeiad/plugins/ticketvote"
    15  	"github.com/decred/politeia/util"
    16  	"github.com/pkg/errors"
    17  )
    18  
    19  const (
    20  	// voteTimestampKey is the key for a ticket vote timestamp entry in the
    21  	// key-value store cache.
    22  	voteTimestampKey = "timestamp-vote-{shorttoken}-{page}-{index}"
    23  
    24  	// authTimestampKey is the key for a vote auth timestamp entry in the
    25  	// key-value store cache.
    26  	authTimestampKey = "timestamp-auth-{shorttoken}-{index}"
    27  
    28  	// detailsTimestampKey is the key for a vote details timestamp entry in the
    29  	// key-value store cache.
    30  	detailsTimestampKey = "timestamp-details-{shorttoken}"
    31  )
    32  
    33  // cacheFinalVoteTimestamps accepts a slice of vote timestamps, it collects the
    34  // final timestamps then stores them in the key-value store.
    35  func (p *ticketVotePlugin) cacheFinalVoteTimestamps(token []byte, ts []ticketvote.Timestamp, page uint32) error {
    36  	// Collect final timestamps
    37  	fts := make([]ticketvote.Timestamp, 0, len(ts))
    38  	for _, t := range ts {
    39  		if timestampIsFinal(t) {
    40  			fts = append(fts, t)
    41  		}
    42  	}
    43  
    44  	// Store final timestamp
    45  	err := p.saveVoteTimestamps(token, fts, page)
    46  	if err != nil {
    47  		return err
    48  	}
    49  
    50  	log.Debugf("Cached final vote timestamps of %v/%v",
    51  		len(fts), len(ts))
    52  	return nil
    53  }
    54  
    55  // saveVoteTimestamps saves a slice of vote timestamps to the key-value cache.
    56  func (p *ticketVotePlugin) saveVoteTimestamps(token []byte, ts []ticketvote.Timestamp, page uint32) error {
    57  	// Setup the blob entries
    58  	blobs := make(map[string][]byte, len(ts))
    59  	keys := make([]string, 0, len(ts))
    60  	for i, v := range ts {
    61  		k, err := getVoteTimestampKey(token, page, uint32(i))
    62  		if err != nil {
    63  			return err
    64  		}
    65  		b, err := json.Marshal(v)
    66  		if err != nil {
    67  			return err
    68  		}
    69  		blobs[k] = b
    70  		keys = append(keys, k)
    71  	}
    72  
    73  	// Delete exisiting digests
    74  	err := p.tstore.CacheDel(keys)
    75  	if err != nil {
    76  		return err
    77  	}
    78  
    79  	// Save the blob entries
    80  	return p.tstore.CachePut(blobs, false)
    81  }
    82  
    83  // cachedVoteTimestamps returns cached vote timestamps if they exist. It
    84  // accepts the requested page as the vote timestamps request is paginated and
    85  // both the page number and the vote index are part of the vote's cache key.
    86  func (p *ticketVotePlugin) cachedVoteTimestamps(token []byte, page, pageSize uint32) ([]ticketvote.Timestamp, error) {
    87  	// Setup the timestamp keys
    88  	keys := make([]string, 0, pageSize)
    89  	for i := uint32(0); i < pageSize; i++ {
    90  		key, err := getVoteTimestampKey(token, page, i)
    91  		if err != nil {
    92  			return nil, err
    93  		}
    94  
    95  		keys = append(keys, key)
    96  	}
    97  
    98  	// Get the timestamp blob entries
    99  	blobs, err := p.tstore.CacheGet(keys)
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  
   104  	// Decode the timestamps
   105  	ts := make([]ticketvote.Timestamp, len(blobs))
   106  	for k, v := range blobs {
   107  		var t ticketvote.Timestamp
   108  		err := json.Unmarshal(v, &t)
   109  		if err != nil {
   110  			return nil, err
   111  		}
   112  		idx, err := parseVoteTimestampKey(k)
   113  		if err != nil {
   114  			return nil, err
   115  		}
   116  		ts[idx] = t
   117  	}
   118  
   119  	log.Debugf("Retrieved %v cached final vote timestamps", len(ts))
   120  	return ts, nil
   121  }
   122  
   123  // getVoteTimestampVoteKey returns the key for a vote timestamp in the
   124  // key-value store cache.
   125  func getVoteTimestampKey(token []byte, page, index uint32) (string, error) {
   126  	t, err := util.ShortTokenEncode(token)
   127  	if err != nil {
   128  		return "", err
   129  	}
   130  	pageStr := strconv.FormatUint(uint64(page), 10)
   131  	indexStr := strconv.FormatUint(uint64(index), 10)
   132  	key := strings.Replace(voteTimestampKey, "{shorttoken}", t, 1)
   133  	key = strings.Replace(key, "{page}", pageStr, 1)
   134  	key = strings.Replace(key, "{index}", indexStr, 1)
   135  	return key, nil
   136  }
   137  
   138  // parseVoteTimestampKey parses the item index from a vote timestamp key.
   139  func parseVoteTimestampKey(key string) (uint32, error) {
   140  	s := strings.Split(key, "-")
   141  	if len(s) != 5 {
   142  		return 0, errors.Errorf("invalid vote timestamp key")
   143  	}
   144  	index, err := strconv.ParseUint(s[4], 10, 64)
   145  	if err != nil {
   146  		return 0, err
   147  	}
   148  	return uint32(index), nil
   149  }
   150  
   151  // cacheFinalVoteTimestamps accepts a slice of auth timestamps, it collects the
   152  // final timestamps then stores them in the key-value store.
   153  func (p *ticketVotePlugin) cacheFinalAuthTimestamps(token []byte, ts []ticketvote.Timestamp) error {
   154  	// Collect final timestamps
   155  	fts := make([]ticketvote.Timestamp, 0, len(ts))
   156  	for _, t := range ts {
   157  		if timestampIsFinal(t) {
   158  			fts = append(fts, t)
   159  		}
   160  	}
   161  
   162  	// Store final timestamp
   163  	err := p.saveAuthTimestamps(token, fts)
   164  	if err != nil {
   165  		return err
   166  	}
   167  
   168  	log.Debugf("Cached final auth timestamps of %v/%v",
   169  		len(fts), len(ts))
   170  	return nil
   171  }
   172  
   173  // saveAuthTimestamps saves a slice of vote timestamps to the key-value cache.
   174  func (p *ticketVotePlugin) saveAuthTimestamps(token []byte, ts []ticketvote.Timestamp) error {
   175  	// Setup the blob entries
   176  	blobs := make(map[string][]byte, len(ts))
   177  	keys := make([]string, 0, len(ts))
   178  	for i, v := range ts {
   179  		k, err := getAuthTimestampKey(token, uint32(i))
   180  		if err != nil {
   181  			return err
   182  		}
   183  		b, err := json.Marshal(v)
   184  		if err != nil {
   185  			return err
   186  		}
   187  		blobs[k] = b
   188  		keys = append(keys, k)
   189  	}
   190  
   191  	// Delete exisiting digests
   192  	err := p.tstore.CacheDel(keys)
   193  	if err != nil {
   194  		return err
   195  	}
   196  
   197  	// Save the blob entries
   198  	return p.tstore.CachePut(blobs, false)
   199  }
   200  
   201  // cachedAuthTimestamps returns cached auth timestamps if they exist.
   202  func (p *ticketVotePlugin) cachedAuthTimestamps(token []byte) ([]ticketvote.Timestamp, error) {
   203  	// Setup the timestamp keys
   204  	keys := make([]string, 0, 256)
   205  	for i := uint32(0); i < 256; i++ {
   206  		key, err := getAuthTimestampKey(token, i)
   207  		if err != nil {
   208  			return nil, err
   209  		}
   210  
   211  		keys = append(keys, key)
   212  	}
   213  
   214  	// Get the timestamp blob entries
   215  	blobs, err := p.tstore.CacheGet(keys)
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  
   220  	// Decode the timestamps
   221  	ts := make([]ticketvote.Timestamp, len(blobs))
   222  	for k, v := range blobs {
   223  		var t ticketvote.Timestamp
   224  		err := json.Unmarshal(v, &t)
   225  		if err != nil {
   226  			return nil, err
   227  		}
   228  		idx, err := parseAuthTimestampKey(k)
   229  		if err != nil {
   230  			return nil, err
   231  		}
   232  		ts[idx] = t
   233  	}
   234  
   235  	log.Debugf("Retrieved %v cached final auth timestamps", len(ts))
   236  	return ts, nil
   237  }
   238  
   239  // getAuthTimestampVoteKey returns the key for a auth timestamp in the
   240  // key-value store cache.
   241  func getAuthTimestampKey(token []byte, index uint32) (string, error) {
   242  	t, err := util.ShortTokenEncode(token)
   243  	if err != nil {
   244  		return "", err
   245  	}
   246  	indexStr := strconv.FormatUint(uint64(index), 10)
   247  	key := strings.Replace(authTimestampKey, "{shorttoken}", t, 1)
   248  	key = strings.Replace(key, "{index}", indexStr, 1)
   249  	return key, nil
   250  }
   251  
   252  // parseAuthTimestampKey parses the item index from a auth timestamp key.
   253  func parseAuthTimestampKey(key string) (uint32, error) {
   254  	s := strings.Split(key, "-")
   255  	if len(s) != 4 {
   256  		return 0, errors.Errorf("invalid auth timestamp key")
   257  	}
   258  	index, err := strconv.ParseUint(s[3], 10, 64)
   259  	if err != nil {
   260  		return 0, err
   261  	}
   262  	return uint32(index), nil
   263  }
   264  
   265  // cacheFinalDetailsTimestamp accepts a vote details timestamp, if the given
   266  // vote details timestamp is final it stores in the key-value store.
   267  func (p *ticketVotePlugin) cacheFinalDetailsTimestamp(token []byte, t ticketvote.Timestamp) error {
   268  	// Check whether given timestamp is final
   269  	if timestampIsFinal(t) {
   270  		// Store final timestamp in cache
   271  		err := p.saveDetailsTimestamp(token, t)
   272  		if err != nil {
   273  			return err
   274  		}
   275  
   276  		log.Debugf("Cached final vote details timestamp of %v",
   277  			hex.EncodeToString(token))
   278  	}
   279  
   280  	return nil
   281  }
   282  
   283  // saveDetailsTimestamp saves a slice of vote timestamps to the key-value cache.
   284  func (p *ticketVotePlugin) saveDetailsTimestamp(token []byte, t ticketvote.Timestamp) error {
   285  	// Setup the blob entry
   286  	blobs := make(map[string][]byte, 1)
   287  	k, err := getDetailsTimestampKey(token)
   288  	if err != nil {
   289  		return err
   290  	}
   291  	b, err := json.Marshal(t)
   292  	if err != nil {
   293  		return err
   294  	}
   295  	blobs[k] = b
   296  
   297  	// Delete exisiting digests
   298  	err = p.tstore.CacheDel([]string{k})
   299  	if err != nil {
   300  		return err
   301  	}
   302  
   303  	// Save the blob entries
   304  	return p.tstore.CachePut(blobs, false)
   305  }
   306  
   307  // cachedDetailsTimestamp returns cached vote details timestamp if one exist.
   308  func (p *ticketVotePlugin) cachedDetailsTimestamp(token []byte) (*ticketvote.Timestamp, error) {
   309  	// Setup the timestamp key
   310  	key, err := getDetailsTimestampKey(token)
   311  	if err != nil {
   312  		return nil, err
   313  	}
   314  
   315  	// Get the timestamp blob entry
   316  	blobs, err := p.tstore.CacheGet([]string{key})
   317  	if err != nil {
   318  		return nil, err
   319  	}
   320  
   321  	// There should never be more than a one cached vote details
   322  	if len(blobs) > 1 {
   323  		return nil, fmt.Errorf("invalid vote details count: "+
   324  			"got %v, want 1", len(blobs))
   325  	}
   326  
   327  	// Decode the timestamp if one found
   328  	if len(blobs) > 0 {
   329  		var t ticketvote.Timestamp
   330  		err = json.Unmarshal(blobs[key], &t)
   331  		if err != nil {
   332  			return nil, err
   333  		}
   334  
   335  		log.Debugf("Retrieved cached vote details for %v",
   336  			hex.EncodeToString(token))
   337  		return &t, nil
   338  	}
   339  
   340  	return nil, nil
   341  }
   342  
   343  // getDetailsTimestampVoteKey returns the key for a auth timestamp in the
   344  // key-value store cache.
   345  func getDetailsTimestampKey(token []byte) (string, error) {
   346  	t, err := util.ShortTokenEncode(token)
   347  	if err != nil {
   348  		return "", err
   349  	}
   350  	key := strings.Replace(detailsTimestampKey, "{shorttoken}", t, 1)
   351  	return key, nil
   352  }
   353  
   354  // timestampIsFinal returns whether the timestamp is considered to be final and
   355  // will not change in the future. Once the TxID is present then the timestamp
   356  // is considered to be final since it has been included in a DCR transaction.
   357  func timestampIsFinal(t ticketvote.Timestamp) bool {
   358  	return t.TxID != ""
   359  }