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 }