github.com/decred/politeia@v1.4.0/politeiad/backendv2/tstorebe/tstorebe.go (about) 1 // Copyright (c) 2020-2021 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 tstorebe 6 7 import ( 8 "bytes" 9 "encoding/base64" 10 "encoding/hex" 11 "encoding/json" 12 "fmt" 13 "path/filepath" 14 "sort" 15 "strconv" 16 "sync" 17 "time" 18 19 "github.com/decred/dcrd/chaincfg/v3" 20 "github.com/decred/politeia/politeiad/api/v1/mime" 21 backend "github.com/decred/politeia/politeiad/backendv2" 22 "github.com/decred/politeia/politeiad/backendv2/tstorebe/plugins" 23 "github.com/decred/politeia/politeiad/backendv2/tstorebe/tstore" 24 "github.com/decred/politeia/util" 25 "github.com/subosito/gozaru" 26 ) 27 28 var ( 29 _ backend.Backend = (*tstoreBackend)(nil) 30 ) 31 32 // tstoreBackend implements the backendv2 Backend interface using a tstore as 33 // the backing data store. 34 type tstoreBackend struct { 35 sync.RWMutex 36 appDir string 37 dataDir string 38 shutdown bool 39 tstore *tstore.Tstore 40 41 // recordMtxs allows the backend to hold a lock on an individual 42 // record so that it can perform multiple read/write operations 43 // in a concurrent safe manner. These mutexes are lazy loaded. 44 recordMtxs map[string]*sync.Mutex 45 } 46 47 // isShutdown returns whether the backend is shutdown. 48 func (t *tstoreBackend) isShutdown() bool { 49 t.RLock() 50 defer t.RUnlock() 51 52 return t.shutdown 53 } 54 55 // recordMutex returns the mutex for a record. 56 func (t *tstoreBackend) recordMutex(token []byte) *sync.Mutex { 57 t.Lock() 58 defer t.Unlock() 59 60 ts := hex.EncodeToString(token) 61 m, ok := t.recordMtxs[ts] 62 if !ok { 63 // recordMtxs is lazy loaded 64 m = &sync.Mutex{} 65 t.recordMtxs[ts] = m 66 } 67 68 return m 69 } 70 71 // metadataStreamsVerify verifies that all provided metadata streams are sane. 72 func metadataStreamsVerify(metadata []backend.MetadataStream) error { 73 // Verify metadata 74 md := make(map[string]map[uint32]struct{}, len(metadata)) 75 for i, v := range metadata { 76 // Verify all fields are provided 77 switch { 78 case v.PluginID == "": 79 e := fmt.Sprintf("plugin id missing at index %v", i) 80 return backend.ContentError{ 81 ErrorCode: backend.ContentErrorMetadataStreamInvalid, 82 ErrorContext: e, 83 } 84 case v.StreamID == 0: 85 e := fmt.Sprintf("stream id missing at index %v", i) 86 return backend.ContentError{ 87 ErrorCode: backend.ContentErrorMetadataStreamInvalid, 88 ErrorContext: e, 89 } 90 case v.Payload == "": 91 e := fmt.Sprintf("payload missing on %v %v", v.PluginID, v.StreamID) 92 return backend.ContentError{ 93 ErrorCode: backend.ContentErrorMetadataStreamInvalid, 94 ErrorContext: e, 95 } 96 } 97 98 // Verify no duplicates 99 m, ok := md[v.PluginID] 100 if !ok { 101 m = make(map[uint32]struct{}, len(metadata)) 102 md[v.PluginID] = m 103 } 104 if _, ok := m[v.StreamID]; ok { 105 e := fmt.Sprintf("%v %v", v.PluginID, v.StreamID) 106 return backend.ContentError{ 107 ErrorCode: backend.ContentErrorMetadataStreamDuplicate, 108 ErrorContext: e, 109 } 110 } 111 112 // Add to metadata list 113 m[v.StreamID] = struct{}{} 114 md[v.PluginID] = m 115 } 116 117 return nil 118 } 119 120 func metadataStreamsUpdate(curr, mdAppend, mdOverwrite []backend.MetadataStream) []backend.MetadataStream { 121 // Put current metadata into a map 122 md := make(map[string]backend.MetadataStream, len(curr)) 123 for _, v := range curr { 124 k := v.PluginID + strconv.FormatUint(uint64(v.StreamID), 10) 125 md[k] = v 126 } 127 128 // Apply overwrites 129 for _, v := range mdOverwrite { 130 k := v.PluginID + strconv.FormatUint(uint64(v.StreamID), 10) 131 md[k] = v 132 } 133 134 // Apply appends. Its ok if an append is specified but there is no 135 // existing metadata for that metadata stream. In this case the 136 // append data will become the full metadata stream. 137 for _, v := range mdAppend { 138 k := v.PluginID + strconv.FormatUint(uint64(v.StreamID), 10) 139 m, ok := md[k] 140 if !ok { 141 // No existing metadata. Use append data as full metadata 142 // stream. 143 md[k] = v 144 continue 145 } 146 147 // Metadata exists. Append to it. 148 buf := bytes.NewBuffer([]byte(m.Payload)) 149 buf.WriteString(v.Payload) 150 m.Payload = buf.String() 151 md[k] = m 152 } 153 154 // Convert metadata back to a slice 155 metadata := make([]backend.MetadataStream, 0, len(md)) 156 for _, v := range md { 157 metadata = append(metadata, v) 158 } 159 160 return metadata 161 } 162 163 // filesVerify verifies that all provided files are sane. 164 func filesVerify(files []backend.File, filesDel []string) error { 165 // Verify files are being updated 166 if len(files) == 0 && len(filesDel) == 0 { 167 return backend.ContentError{ 168 ErrorCode: backend.ContentErrorFilesEmpty, 169 } 170 } 171 172 // Prevent paths 173 for i := range files { 174 if filepath.Base(files[i].Name) != files[i].Name { 175 e := fmt.Sprintf("%v contains a file path", files[i].Name) 176 return backend.ContentError{ 177 ErrorCode: backend.ContentErrorFileNameInvalid, 178 ErrorContext: e, 179 } 180 } 181 } 182 for _, v := range filesDel { 183 if filepath.Base(v) != v { 184 e := fmt.Sprintf("%v contains a file path", v) 185 return backend.ContentError{ 186 ErrorCode: backend.ContentErrorFileNameInvalid, 187 ErrorContext: e, 188 } 189 } 190 } 191 192 // Prevent duplicate filenames 193 fn := make(map[string]struct{}, len(files)+len(filesDel)) 194 for i := range files { 195 if _, ok := fn[files[i].Name]; ok { 196 return backend.ContentError{ 197 ErrorCode: backend.ContentErrorFileNameDuplicate, 198 ErrorContext: files[i].Name, 199 } 200 } 201 fn[files[i].Name] = struct{}{} 202 } 203 for _, v := range filesDel { 204 if _, ok := fn[v]; ok { 205 return backend.ContentError{ 206 ErrorCode: backend.ContentErrorFileNameDuplicate, 207 ErrorContext: v, 208 } 209 } 210 fn[v] = struct{}{} 211 } 212 213 // Prevent bad filenames 214 for i := range files { 215 if gozaru.Sanitize(files[i].Name) != files[i].Name { 216 e := fmt.Sprintf("%v is not sanitized", files[i].Name) 217 return backend.ContentError{ 218 ErrorCode: backend.ContentErrorFileNameInvalid, 219 ErrorContext: e, 220 } 221 } 222 223 // Verify digest 224 d, ok := util.ConvertDigest(files[i].Digest) 225 if !ok { 226 return backend.ContentError{ 227 ErrorCode: backend.ContentErrorFileDigestInvalid, 228 ErrorContext: files[i].Name, 229 } 230 } 231 232 // Verify payload is not empty 233 if files[i].Payload == "" { 234 e := fmt.Sprintf("%v payload empty", files[i].Name) 235 return backend.ContentError{ 236 ErrorCode: backend.ContentErrorFilePayloadInvalid, 237 ErrorContext: e, 238 } 239 } 240 241 // Decode base64 payload 242 payload, err := base64.StdEncoding.DecodeString(files[i].Payload) 243 if err != nil { 244 e := fmt.Sprintf("%v invalid base64", files[i].Name) 245 return backend.ContentError{ 246 ErrorCode: backend.ContentErrorFilePayloadInvalid, 247 ErrorContext: e, 248 } 249 } 250 251 // Calculate payload digest 252 dp := util.Digest(payload) 253 if !bytes.Equal(d[:], dp) { 254 e := fmt.Sprintf("%v digest got %x, want %x", 255 files[i].Name, d[:], dp) 256 return backend.ContentError{ 257 ErrorCode: backend.ContentErrorFileDigestInvalid, 258 ErrorContext: e, 259 } 260 } 261 262 // Verify MIME 263 detectedMIMEType := mime.DetectMimeType(payload) 264 if detectedMIMEType != files[i].MIME { 265 e := fmt.Sprintf("%v mime got %v, want %v", 266 files[i].Name, files[i].MIME, detectedMIMEType) 267 return backend.ContentError{ 268 ErrorCode: backend.ContentErrorFileMIMETypeInvalid, 269 ErrorContext: e, 270 } 271 } 272 273 if !mime.MimeValid(files[i].MIME) { 274 return backend.ContentError{ 275 ErrorCode: backend.ContentErrorFileMIMETypeUnsupported, 276 ErrorContext: files[i].Name, 277 } 278 } 279 } 280 281 return nil 282 } 283 284 // filesUpdate updates the current files with new file adds and deletes. 285 func filesUpdate(filesCurr, filesAdd []backend.File, filesDel []string) []backend.File { 286 // Put current files into a map 287 curr := make(map[string]backend.File, len(filesCurr)) // [filename]File 288 for _, v := range filesCurr { 289 curr[v.Name] = v 290 } 291 292 // Apply deletes 293 for _, fn := range filesDel { 294 _, ok := curr[fn] 295 if ok { 296 delete(curr, fn) 297 } 298 } 299 300 // Apply adds 301 for _, v := range filesAdd { 302 curr[v.Name] = v 303 } 304 305 // Convert back to a slice 306 f := make([]backend.File, 0, len(curr)) 307 for _, v := range curr { 308 f = append(f, v) 309 } 310 311 return f 312 } 313 314 // recordMetadataNew returns a new record metadata. 315 func recordMetadataNew(token []byte, files []backend.File, state backend.StateT, status backend.StatusT, version, iteration uint32) (*backend.RecordMetadata, error) { 316 digests := make([]string, 0, len(files)) 317 for _, v := range files { 318 digests = append(digests, v.Digest) 319 } 320 m, err := util.MerkleRoot(digests) 321 if err != nil { 322 return nil, err 323 } 324 return &backend.RecordMetadata{ 325 Token: hex.EncodeToString(token), 326 Version: version, 327 Iteration: iteration, 328 State: state, 329 Status: status, 330 Timestamp: time.Now().Unix(), 331 Merkle: hex.EncodeToString(m[:]), 332 }, nil 333 } 334 335 // RecordNew creates a new record. 336 // 337 // This function satisfies the backendv2 Backend interface. 338 func (t *tstoreBackend) RecordNew(metadata []backend.MetadataStream, files []backend.File) (*backend.Record, error) { 339 log.Tracef("RecordNew: %v metadata, %v files", len(metadata), len(files)) 340 341 // Verify record content 342 err := metadataStreamsVerify(metadata) 343 if err != nil { 344 return nil, err 345 } 346 err = filesVerify(files, nil) 347 if err != nil { 348 return nil, err 349 } 350 351 // Call pre plugin hooks 352 pre := plugins.HookNewRecordPre{ 353 Metadata: metadata, 354 Files: files, 355 } 356 b, err := json.Marshal(pre) 357 if err != nil { 358 return nil, err 359 } 360 err = t.tstore.PluginHookPre(plugins.HookTypeNewRecordPre, string(b)) 361 if err != nil { 362 return nil, err 363 } 364 365 // Create a new token 366 token, err := t.tstore.RecordNew() 367 if err != nil { 368 return nil, err 369 } 370 371 // Create record metadata 372 rm, err := recordMetadataNew(token, files, backend.StateUnvetted, 373 backend.StatusUnreviewed, 1, 1) 374 if err != nil { 375 return nil, err 376 } 377 378 // Save the record 379 err = t.tstore.RecordSave(token, *rm, metadata, files) 380 if err != nil { 381 return nil, fmt.Errorf("RecordSave: %v", err) 382 } 383 384 // Call post plugin hooks 385 post := plugins.HookNewRecordPost{ 386 Metadata: metadata, 387 Files: files, 388 RecordMetadata: *rm, 389 } 390 b, err = json.Marshal(post) 391 if err != nil { 392 return nil, err 393 } 394 t.tstore.PluginHookPost(plugins.HookTypeNewRecordPost, string(b)) 395 396 // Update the inventory cache 397 t.inventoryAdd(backend.StateUnvetted, token, backend.StatusUnreviewed) 398 399 // Get the full record to return 400 r, err := t.tstore.RecordLatest(token) 401 if err != nil { 402 return nil, fmt.Errorf("RecordLatest %x: %v", token, err) 403 } 404 405 return r, nil 406 } 407 408 // RecordEdit edits an existing record. This creates a new version of the 409 // record. 410 // 411 // This function satisfies the backendv2 Backend interface. 412 func (t *tstoreBackend) RecordEdit(token []byte, mdAppend, mdOverwrite []backend.MetadataStream, filesAdd []backend.File, filesDel []string) (*backend.Record, error) { 413 log.Tracef("RecordEdit: %x", token) 414 415 // Verify record contents. Send in a single metadata array to 416 // verify there are no dups. 417 allMD := append(mdAppend, mdOverwrite...) 418 err := metadataStreamsVerify(allMD) 419 if err != nil { 420 return nil, err 421 } 422 err = filesVerify(filesAdd, filesDel) 423 if err != nil { 424 return nil, err 425 } 426 427 // Verify record exists 428 if !t.RecordExists(token) { 429 return nil, backend.ErrRecordNotFound 430 } 431 432 // Apply the record changes and save the new version. The record 433 // lock needs to be held for the remainder of the function. 434 if t.isShutdown() { 435 return nil, backend.ErrShutdown 436 } 437 m := t.recordMutex(token) 438 m.Lock() 439 defer m.Unlock() 440 441 // Get existing record 442 r, err := t.tstore.RecordLatest(token) 443 if err != nil { 444 return nil, fmt.Errorf("RecordLatest: %v", err) 445 } 446 447 // Apply changes 448 var ( 449 rm = r.RecordMetadata 450 metadata = metadataStreamsUpdate(r.Metadata, mdAppend, mdOverwrite) 451 files = filesUpdate(r.Files, filesAdd, filesDel) 452 ) 453 recordMD, err := recordMetadataNew(token, files, rm.State, rm.Status, 454 rm.Version+1, rm.Iteration+1) 455 if err != nil { 456 return nil, err 457 } 458 459 // Verify that file changes are being made. The merkle root is the 460 // merkle root of the files. It will be the same if no file changes 461 // are being made. 462 if r.RecordMetadata.Merkle == recordMD.Merkle { 463 // No file changes found 464 return nil, backend.ErrNoRecordChanges 465 } 466 467 // Call pre plugin hooks 468 her := plugins.HookEditRecord{ 469 Record: *r, 470 RecordMetadata: *recordMD, 471 Metadata: metadata, 472 Files: files, 473 } 474 b, err := json.Marshal(her) 475 if err != nil { 476 return nil, err 477 } 478 err = t.tstore.PluginHookPre(plugins.HookTypeEditRecordPre, string(b)) 479 if err != nil { 480 return nil, err 481 } 482 483 // Save record 484 err = t.tstore.RecordSave(token, *recordMD, metadata, files) 485 if err != nil { 486 switch err { 487 case backend.ErrRecordLocked: 488 return nil, err 489 default: 490 return nil, fmt.Errorf("RecordSave: %v", err) 491 } 492 } 493 494 // Call post plugin hooks 495 t.tstore.PluginHookPost(plugins.HookTypeEditRecordPost, string(b)) 496 497 // Return updated record 498 r, err = t.tstore.RecordLatest(token) 499 if err != nil { 500 return nil, fmt.Errorf("RecordLatest: %v", err) 501 } 502 503 return r, nil 504 } 505 506 // RecordEditMetadata edits the metadata of a record without changing any 507 // record files. This creates a new iteration of the record, but not a new 508 // version of the record. 509 // 510 // This function satisfies the backendv2 Backend interface. 511 func (t *tstoreBackend) RecordEditMetadata(token []byte, mdAppend, mdOverwrite []backend.MetadataStream) (*backend.Record, error) { 512 log.Tracef("RecordEditMetadata: %x", token) 513 514 // Verify metadata. Send in a single metadata array to verify there 515 // are no dups. 516 allMD := append(mdAppend, mdOverwrite...) 517 err := metadataStreamsVerify(allMD) 518 if err != nil { 519 return nil, err 520 } 521 if len(mdAppend) == 0 && len(mdOverwrite) == 0 { 522 return nil, backend.ErrNoRecordChanges 523 } 524 525 // Verify record exists 526 if !t.RecordExists(token) { 527 return nil, backend.ErrRecordNotFound 528 } 529 530 // Apply the record changes and save the new version. The record 531 // lock needs to be held for the remainder of the function. 532 if t.isShutdown() { 533 return nil, backend.ErrShutdown 534 } 535 m := t.recordMutex(token) 536 m.Lock() 537 defer m.Unlock() 538 539 // Get existing record 540 r, err := t.tstore.RecordLatest(token) 541 if err != nil { 542 return nil, fmt.Errorf("RecordLatest: %v", err) 543 } 544 545 // Apply changes. The version is not incremented for metadata only 546 // updates. The iteration is incremented. 547 var ( 548 rm = r.RecordMetadata 549 metadata = metadataStreamsUpdate(r.Metadata, mdAppend, mdOverwrite) 550 ) 551 recordMD, err := recordMetadataNew(token, r.Files, rm.State, rm.Status, 552 rm.Version, rm.Iteration+1) 553 if err != nil { 554 return nil, err 555 } 556 557 // Call pre plugin hooks 558 hem := plugins.HookEditMetadata{ 559 Record: *r, 560 Metadata: metadata, 561 } 562 b, err := json.Marshal(hem) 563 if err != nil { 564 return nil, err 565 } 566 err = t.tstore.PluginHookPre(plugins.HookTypeEditMetadataPre, string(b)) 567 if err != nil { 568 return nil, err 569 } 570 571 // Update metadata 572 err = t.tstore.RecordSave(token, *recordMD, metadata, r.Files) 573 if err != nil { 574 switch err { 575 case backend.ErrRecordLocked, backend.ErrNoRecordChanges: 576 return nil, err 577 default: 578 return nil, fmt.Errorf("RecordSave: %v", err) 579 } 580 } 581 582 // Call post plugin hooks 583 t.tstore.PluginHookPost(plugins.HookTypeEditMetadataPost, string(b)) 584 585 // Return updated record 586 r, err = t.tstore.RecordLatest(token) 587 if err != nil { 588 return nil, fmt.Errorf("RecordLatest: %v", err) 589 } 590 591 return r, nil 592 } 593 594 var ( 595 // statusChanges contains the allowed record status changes. If 596 // statusChanges[currentStatus][newStatus] exists then the status 597 // change is allowed. 598 statusChanges = map[backend.StatusT]map[backend.StatusT]struct{}{ 599 // Unreviewed to... 600 backend.StatusUnreviewed: { 601 backend.StatusPublic: {}, 602 backend.StatusCensored: {}, 603 }, 604 // Public to... 605 backend.StatusPublic: { 606 backend.StatusCensored: {}, 607 backend.StatusArchived: {}, 608 }, 609 // Statuses that do not allow any further transitions 610 backend.StatusCensored: {}, 611 backend.StatusArchived: {}, 612 } 613 ) 614 615 // statusChangeIsAllowed returns whether the provided status change is allowed. 616 func statusChangeIsAllowed(from, to backend.StatusT) bool { 617 allowed, ok := statusChanges[from] 618 if !ok { 619 return false 620 } 621 _, ok = allowed[to] 622 return ok 623 } 624 625 // setStatusPublic updates the status of a record to public. 626 // 627 // This function must be called WITH the record lock held. 628 func (t *tstoreBackend) setStatusPublic(token []byte, rm backend.RecordMetadata, metadata []backend.MetadataStream, files []backend.File) error { 629 return t.tstore.RecordSave(token, rm, metadata, files) 630 } 631 632 // setStatusArchived updates the status of a record to archived. 633 // 634 // This function must be called WITH the record lock held. 635 func (t *tstoreBackend) setStatusArchived(token []byte, rm backend.RecordMetadata, metadata []backend.MetadataStream, files []backend.File) error { 636 // Freeze record 637 err := t.tstore.RecordFreeze(token, rm, metadata, files) 638 if err != nil { 639 return fmt.Errorf("RecordFreeze: %v", err) 640 } 641 642 log.Debugf("Record frozen %x", token) 643 644 // Nothing else needs to be done for a archived record 645 646 return nil 647 } 648 649 // setStatusCensored updates the status of a record to censored. 650 // 651 // This function must be called WITH the record lock held. 652 func (t *tstoreBackend) setStatusCensored(token []byte, rm backend.RecordMetadata, metadata []backend.MetadataStream, files []backend.File) error { 653 // Freeze the tree 654 err := t.tstore.RecordFreeze(token, rm, metadata, files) 655 if err != nil { 656 return fmt.Errorf("RecordFreeze: %v", err) 657 } 658 659 log.Debugf("Record frozen %x", token) 660 661 // Delete all record files 662 err = t.tstore.RecordDel(token) 663 if err != nil { 664 return fmt.Errorf("RecordDel: %v", err) 665 } 666 667 log.Debugf("Record contents deleted %x", token) 668 669 return nil 670 } 671 672 // RecordSetStatus sets the status of a record. 673 // 674 // This function satisfies the backendv2 Backend interface. 675 func (t *tstoreBackend) RecordSetStatus(token []byte, status backend.StatusT, mdAppend, mdOverwrite []backend.MetadataStream) (*backend.Record, error) { 676 log.Tracef("RecordSetStatus: %x %v", token, status) 677 678 // Verify record exists 679 if !t.RecordExists(token) { 680 return nil, backend.ErrRecordNotFound 681 } 682 683 // The existing record must be pulled and updated. The record 684 // lock must be held for the rest of this function. 685 if t.isShutdown() { 686 return nil, backend.ErrShutdown 687 } 688 m := t.recordMutex(token) 689 m.Lock() 690 defer m.Unlock() 691 692 // Get existing record 693 r, err := t.tstore.RecordLatest(token) 694 if err != nil { 695 return nil, fmt.Errorf("RecordLatest: %v", err) 696 } 697 currStatus := r.RecordMetadata.Status 698 699 // Validate status change 700 if !statusChangeIsAllowed(currStatus, status) { 701 return nil, backend.StatusTransitionError{ 702 From: currStatus, 703 To: status, 704 } 705 } 706 707 // If the record is being made public the record state gets updated 708 // to vetted and the version and iteration are reset. Otherwise, 709 // the state and version remain the same while the iteration gets 710 // incremented to reflect the status change. 711 var ( 712 state = r.RecordMetadata.State 713 version = r.RecordMetadata.Version 714 iter = r.RecordMetadata.Iteration + 1 // Increment for status change 715 ) 716 if status == backend.StatusPublic { 717 state = backend.StateVetted 718 version = 1 719 iter = 1 720 } 721 722 // Apply changes 723 recordMD, err := recordMetadataNew(token, r.Files, 724 state, status, version, iter) 725 if err != nil { 726 return nil, err 727 } 728 metadata := metadataStreamsUpdate(r.Metadata, mdAppend, mdOverwrite) 729 730 // Call pre plugin hooks 731 hsrs := plugins.HookSetRecordStatus{ 732 Record: *r, 733 RecordMetadata: *recordMD, 734 Metadata: metadata, 735 } 736 b, err := json.Marshal(hsrs) 737 if err != nil { 738 return nil, err 739 } 740 err = t.tstore.PluginHookPre(plugins.HookTypeSetRecordStatusPre, string(b)) 741 if err != nil { 742 return nil, err 743 } 744 745 // Update record status 746 switch status { 747 case backend.StatusPublic: 748 err := t.setStatusPublic(token, *recordMD, metadata, r.Files) 749 if err != nil { 750 return nil, err 751 } 752 case backend.StatusArchived: 753 err := t.setStatusArchived(token, *recordMD, metadata, r.Files) 754 if err != nil { 755 return nil, err 756 } 757 case backend.StatusCensored: 758 err := t.setStatusCensored(token, *recordMD, metadata, r.Files) 759 if err != nil { 760 return nil, err 761 } 762 default: 763 // Should not happen 764 return nil, fmt.Errorf("unknown status %v", status) 765 } 766 767 log.Debugf("Status updated %x from %v (%v) to %v (%v)", 768 token, backend.Statuses[currStatus], currStatus, 769 backend.Statuses[status], status) 770 771 // Call post plugin hooks 772 t.tstore.PluginHookPost(plugins.HookTypeSetRecordStatusPost, string(b)) 773 774 // Update inventory cache 775 switch status { 776 case backend.StatusPublic: 777 // The state is updated to vetted when a record is made public 778 t.inventoryMoveToVetted(token, status) 779 default: 780 t.inventoryUpdate(r.RecordMetadata.State, token, status) 781 } 782 783 // Return updated record 784 r, err = t.tstore.RecordLatest(token) 785 if err != nil { 786 return nil, fmt.Errorf("RecordLatest: %v", err) 787 } 788 789 return r, nil 790 } 791 792 // RecordExists returns whether a record exists. 793 // 794 // This method only returns whether a tree exists for the provided token. It's 795 // possible for a tree to exist that does not correspond to a record in the 796 // rare case that a tree was created but an unexpected error, such as a network 797 // error, was encoutered prior to the record being saved to the tree. We ignore 798 // this edge case because: 799 // 800 // 1. A user has no way to obtain this token unless the trillian instance has 801 // been opened to the public. 802 // 803 // 2. Even if they have the token they cannot do anything with it. Any attempt 804 // to read from the tree or write to the tree will return a RecordNotFound 805 // error. 806 // 807 // Pulling the leaves from the tree to see if a record has been saved to the 808 // tree adds a large amount of overhead to this call, which should be a very 809 // light weight. Its for this reason that we rely on the tree exists call 810 // despite the edge case. 811 // 812 // This function satisfies the backendv2 Backend interface. 813 func (t *tstoreBackend) RecordExists(token []byte) bool { 814 log.Tracef("RecordExists: %x", token) 815 816 return t.tstore.RecordExists(token) 817 } 818 819 // RecordTimestamps returns the timestamps for a record. If no version is 820 // provided then timestamps for the most recent version will be returned. 821 // 822 // This function satisfies the backendv2 Backend interface. 823 func (t *tstoreBackend) RecordTimestamps(token []byte, version uint32) (*backend.RecordTimestamps, error) { 824 log.Tracef("RecordTimestamps: %x %v", token, version) 825 826 return t.tstore.RecordTimestamps(token, version) 827 } 828 829 // Records retreives a batch of records. Individual record errors are not 830 // returned. If the record was not found then it will not be included in the 831 // returned map. 832 // 833 // This function satisfies the backendv2 Backend interface. 834 func (t *tstoreBackend) Records(reqs []backend.RecordRequest) (map[string]backend.Record, error) { 835 log.Tracef("Records: %v reqs", len(reqs)) 836 837 records := make(map[string]backend.Record, len(reqs)) // [token]Record 838 for _, v := range reqs { 839 // Lookup the record 840 r, err := t.tstore.RecordPartial(v.Token, v.Version, 841 v.Filenames, v.OmitAllFiles) 842 if err != nil { 843 if err == backend.ErrRecordNotFound { 844 // Record doesn't exist. This is ok. It will not be included 845 // in the reply. 846 log.Debugf("Record not found %x", v.Token) 847 continue 848 } 849 // An unexpected error occurred. Log it and continue. 850 log.Errorf("RecordPartial %x: %v", v.Token, err) 851 continue 852 } 853 854 // Update reply. Use whatever token was provided as the key so 855 // that the client can validate the reply using the same token 856 // that they provided, regardless of whether its a short token 857 // or full length token. 858 records[util.TokenEncode(v.Token)] = *r 859 } 860 861 return records, nil 862 } 863 864 // Inventory returns the tokens of records in the inventory categorized by 865 // record state and record status. The tokens are ordered by the timestamp of 866 // their most recent status change, sorted from newest to oldest. 867 // 868 // The state, status, and page arguments can be provided to request a specific 869 // page of record tokens. 870 // 871 // If no status is provided then the most recent page of tokens for all 872 // statuses will be returned. All other arguments are ignored. 873 // 874 // This function satisfies the backendv2 Backend interface. 875 func (t *tstoreBackend) Inventory(state backend.StateT, status backend.StatusT, pageSize, pageNumber uint32) (*backend.Inventory, error) { 876 log.Tracef("Inventory: %v %v %v %v", state, status, pageSize, pageNumber) 877 878 inv, err := t.invByStatus(state, status, pageSize, pageNumber) 879 if err != nil { 880 return nil, err 881 } 882 883 return &backend.Inventory{ 884 Unvetted: inv.Unvetted, 885 Vetted: inv.Vetted, 886 }, nil 887 } 888 889 // InventoryOrdered returns a page of record tokens ordered by the timestamp of 890 // their most recent status change. The returned tokens will include all record 891 // statuses. 892 // 893 // This function satisfies the backendv2 Backend interface. 894 func (t *tstoreBackend) InventoryOrdered(state backend.StateT, pageSize, pageNumber uint32) ([]string, error) { 895 log.Tracef("InventoryOrdered: %v %v %v", state, pageSize, pageNumber) 896 897 tokens, err := t.invOrdered(state, pageSize, pageNumber) 898 if err != nil { 899 return nil, err 900 } 901 902 return tokens, nil 903 } 904 905 // PluginRegister registers a plugin. 906 // 907 // This function satisfies the backendv2 Backend interface. 908 func (t *tstoreBackend) PluginRegister(p backend.Plugin) error { 909 return t.tstore.PluginRegister(t, p) 910 } 911 912 // PluginSetup performs any required plugin setup. 913 // 914 // This function satisfies the backendv2 Backend interface. 915 func (t *tstoreBackend) PluginSetup(pluginID string) error { 916 log.Tracef("PluginSetup: %v", pluginID) 917 918 return t.tstore.PluginSetup(pluginID) 919 } 920 921 // PluginRead executes a read-only plugin command. 922 // 923 // This function satisfies the backendv2 Backend interface. 924 func (t *tstoreBackend) PluginRead(token []byte, pluginID, pluginCmd, payload string) (string, error) { 925 log.Tracef("PluginRead: %x %v %v", token, pluginID, pluginCmd) 926 927 // Verify record exists if a token was provided. The token is 928 // optional on read commands so one may not exist. 929 if len(token) > 0 && !t.RecordExists(token) { 930 return "", backend.ErrRecordNotFound 931 } 932 933 // Execute plugin command 934 return t.tstore.PluginRead(token, pluginID, pluginCmd, payload) 935 } 936 937 // PluginWrite executes a plugin command that writes data. 938 // 939 // This function satisfies the backendv2 Backend interface. 940 func (t *tstoreBackend) PluginWrite(token []byte, pluginID, pluginCmd, payload string) (string, error) { 941 log.Tracef("PluginWrite: %x %v %v", token, pluginID, pluginCmd) 942 943 // Verify record exists 944 if !t.RecordExists(token) { 945 return "", backend.ErrRecordNotFound 946 } 947 948 log.Infof("Plugin '%v' write cmd '%v' on %x", 949 pluginID, pluginCmd, token) 950 951 // Hold the record lock for the remainder of this function. We 952 // do this here in the backend so that the individual plugins 953 // implementations don't need to worry about race conditions. 954 if t.isShutdown() { 955 return "", backend.ErrShutdown 956 } 957 m := t.recordMutex(token) 958 m.Lock() 959 defer m.Unlock() 960 961 // Call pre plugin hooks 962 hp := plugins.HookPluginPre{ 963 Token: token, 964 PluginID: pluginID, 965 Cmd: pluginCmd, 966 Payload: payload, 967 } 968 b, err := json.Marshal(hp) 969 if err != nil { 970 return "", err 971 } 972 err = t.tstore.PluginHookPre(plugins.HookTypePluginPre, string(b)) 973 if err != nil { 974 return "", err 975 } 976 977 // Execute plugin command 978 reply, err := t.tstore.PluginWrite(token, pluginID, pluginCmd, payload) 979 if err != nil { 980 return "", err 981 } 982 983 // Call post plugin hooks 984 hpp := plugins.HookPluginPost{ 985 PluginID: pluginID, 986 Cmd: pluginCmd, 987 Payload: payload, 988 Reply: reply, 989 } 990 b, err = json.Marshal(hpp) 991 if err != nil { 992 return "", err 993 } 994 t.tstore.PluginHookPost(plugins.HookTypePluginPost, string(b)) 995 996 return reply, nil 997 } 998 999 // PluginInventory returns all registered plugins. 1000 // 1001 // This function satisfies the backendv2 Backend interface. 1002 func (t *tstoreBackend) PluginInventory() []backend.Plugin { 1003 log.Tracef("Plugins") 1004 1005 return t.tstore.Plugins() 1006 } 1007 1008 // Fsck performs a synchronous filesystem check that verifies the coherency 1009 // of record and plugin data and caches. 1010 // 1011 // This function satisfies the backendv2 Backend interface. 1012 func (t *tstoreBackend) Fsck() error { 1013 log.Infof("Performing fsck on the tstore backend") 1014 1015 // The tstore backend fsck includes: 1016 // 1017 // - Rebuilding the inventory cache. The inventory cache contains 1018 // the tokens of all records in backend, categorized by their 1019 // record status and sorted from oldest to newest. 1020 1021 // Get the tokens for all records in the backend 1022 allTokens, err := t.tstore.Inventory() 1023 if err != nil { 1024 return err 1025 } 1026 1027 log.Infof("%v records found in the tstore backend", len(allTokens)) 1028 log.Infof("Sorting the records by timestamp") 1029 1030 // Get the partial record for all tokens 1031 records := make(map[string]*backend.Record, len(allTokens)) 1032 for _, token := range allTokens { 1033 r, err := t.tstore.RecordPartial(token, 0, nil, true) 1034 if err != nil { 1035 return err 1036 } 1037 records[r.RecordMetadata.Token] = r 1038 } 1039 1040 // Sort the records by their record state: vetted or unvetted. 1041 var ( 1042 vetted = make([]*backend.Record, 0, len(allTokens)) 1043 unvetted = make([]*backend.Record, 0, len(allTokens)) 1044 ) 1045 for _, token := range allTokens { 1046 record := records[hex.EncodeToString(token)] 1047 if record.RecordMetadata.State == backend.StateVetted { 1048 vetted = append(vetted, record) 1049 } 1050 if record.RecordMetadata.State == backend.StateUnvetted { 1051 unvetted = append(unvetted, record) 1052 } 1053 } 1054 1055 // Sort records by the timestamp of their record metadata, from 1056 // oldest to newest. 1057 // 1058 // The order of the record inventory when rebuilt by this function 1059 // will be slightly different than the order that is created at 1060 // runtime. At runtime, the inventory cache updated when the status 1061 // of the record is changed. This fsck function uses the record 1062 // metadata timestamp, which is updated anytime the record status 1063 // is changed, but also when the record is edited. This means that 1064 // the runtime cache that was built will differ from the fsck cache 1065 // that is built if there are records that were edited after their 1066 // most recent status change. This difference is inconsequential, 1067 // so just making a note of it is fine for now. 1068 sort.Slice(vetted, func(i, j int) bool { 1069 return vetted[i].RecordMetadata.Timestamp < 1070 vetted[j].RecordMetadata.Timestamp 1071 }) 1072 sort.Slice(unvetted, func(i, j int) bool { 1073 return unvetted[i].RecordMetadata.Timestamp < 1074 unvetted[j].RecordMetadata.Timestamp 1075 }) 1076 1077 // Delete the inventory cache. We must actually delete 1078 // the cache and rebuilt it from scratch instead of just 1079 // checking the coherency of it because of the ordering 1080 // difference that is noted in the comment above. 1081 err = t.invRemoveVetted() 1082 if err != nil { 1083 return err 1084 } 1085 err = t.invRemoveUnvetted() 1086 if err != nil { 1087 return err 1088 } 1089 1090 // Build the vetted inventory cache. 1091 // 1092 // This is done by adding the record to the unvetted inventory 1093 // then moving it to the vetted inventory. This is how it is 1094 // done during normal operations, so we do it the same way here 1095 // because that is what the inventory API allows for. 1096 log.Infof("Building the inventory cache for %v vetted records", len(vetted)) 1097 1098 for _, record := range vetted { 1099 bToken, err := hex.DecodeString(record.RecordMetadata.Token) 1100 if err != nil { 1101 return err 1102 } 1103 t.inventoryAdd(backend.StateUnvetted, bToken, backend.StatusUnreviewed) 1104 t.inventoryMoveToVetted(bToken, record.RecordMetadata.Status) 1105 } 1106 1107 // Build the unvetted inventory cache 1108 log.Infof("Building the inventory cache for %v unvetted records", 1109 len(unvetted)) 1110 1111 for _, record := range unvetted { 1112 bToken, err := hex.DecodeString(record.RecordMetadata.Token) 1113 if err != nil { 1114 return err 1115 } 1116 t.inventoryAdd(record.RecordMetadata.State, bToken, 1117 record.RecordMetadata.Status) 1118 } 1119 1120 // Update all plugin caches 1121 return t.tstore.Fsck(allTokens) 1122 } 1123 1124 // Close performs cleanup of the backend. 1125 // 1126 // This function satisfies the backendv2 Backend interface. 1127 func (t *tstoreBackend) Close() { 1128 log.Tracef("Close") 1129 1130 t.Lock() 1131 defer t.Unlock() 1132 1133 // Shutdown backend 1134 t.shutdown = true 1135 1136 // Close tstore connections 1137 t.tstore.Close() 1138 } 1139 1140 // setup performs any required work to setup the tstore instance. 1141 func (t *tstoreBackend) setup() error { 1142 return t.tstore.Setup() 1143 } 1144 1145 // New returns a new tstoreBackend. 1146 func New(appDir, dataDir string, anp *chaincfg.Params, tlogHost, dbHost, dbPass, dcrtimeHost, dcrtimeCert string) (*tstoreBackend, error) { 1147 // Setup tstore instances 1148 ts, err := tstore.New(appDir, dataDir, anp, tlogHost, 1149 dbHost, dbPass, dcrtimeHost, dcrtimeCert) 1150 if err != nil { 1151 return nil, fmt.Errorf("new tstore: %v", err) 1152 } 1153 1154 // Setup backend 1155 t := tstoreBackend{ 1156 appDir: appDir, 1157 dataDir: dataDir, 1158 tstore: ts, 1159 recordMtxs: make(map[string]*sync.Mutex), 1160 } 1161 1162 // Perform any required setup 1163 err = t.setup() 1164 if err != nil { 1165 return nil, fmt.Errorf("setup: %v", err) 1166 } 1167 1168 return &t, nil 1169 }