github.com/decred/politeia@v1.4.0/politeiad/backend/gitbe/gitbe.go (about) 1 // Copyright (c) 2017-2020 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 gitbe 6 7 import ( 8 "bytes" 9 "crypto/sha1" 10 "crypto/sha256" 11 "encoding/base64" 12 "encoding/hex" 13 "encoding/json" 14 "errors" 15 "fmt" 16 "io" 17 "os" 18 "path/filepath" 19 "sort" 20 "strconv" 21 "strings" 22 "sync" 23 "time" 24 25 "github.com/davecgh/go-spew/spew" 26 "github.com/decred/dcrd/chaincfg/v3" 27 v1 "github.com/decred/dcrtime/api/v1" 28 "github.com/decred/dcrtime/merkle" 29 pd "github.com/decred/politeia/politeiad/api/v1" 30 "github.com/decred/politeia/politeiad/api/v1/identity" 31 "github.com/decred/politeia/politeiad/api/v1/mime" 32 "github.com/decred/politeia/politeiad/backend" 33 "github.com/decred/politeia/politeiad/backend/gitbe/cmsplugin" 34 "github.com/decred/politeia/politeiad/backend/gitbe/decredplugin" 35 "github.com/decred/politeia/util" 36 filesystem "github.com/otiai10/copy" 37 "github.com/robfig/cron" 38 "github.com/subosito/gozaru" 39 ) 40 41 const ( 42 // LockDuration is the maximum lock time duration allowed. 15 seconds 43 // is ~3x of anchoring without internet delay. 44 LockDuration = 15 * time.Second 45 46 // defaultUnvettedPath is the landing zone for unvetted content. 47 DefaultUnvettedPath = "unvetted" 48 49 // defaultVettedPath is the publicly visible git vetted record repo. 50 DefaultVettedPath = "vetted" 51 52 // defaultJournalsPath is the path where data is journaled and/or 53 // cached. 54 DefaultJournalsPath = "journals" // XXX it looks like this belongs in plugins 55 56 // defaultRecordMetadataFilename is the filename of record record. 57 defaultRecordMetadataFilename = "recordmetadata.json" 58 59 // defaultMDFilenameSuffix is the filename suffic for the user provided 60 // metadata record. The metadata record shall be string encoded. 61 defaultMDFilenameSuffix = ".metadata.txt" 62 63 // defaultAuditTrailFile is the filename where a human readable audit 64 // trail is kept. 65 defaultAuditTrailFile = "anchor_audit_trail.txt" 66 67 // defaultAnchorsDirectory is the directory where anchors are stored. 68 // They are indexed by TX. 69 defaultAnchorsDirectory = "anchors" 70 71 // defaultPayloadDir is the default path to store a record payload. 72 defaultPayloadDir = "payload" 73 74 // anchorSchedule determines how often we anchor the vetted repo. 75 // Seconds Minutes Hours Days Months DayOfWeek 76 anchorSchedule = "0 58 * * * *" // At 58 minutes every hour 77 78 // expectedTestTX is a fake TX used by unit tests. 79 expectedTestTX = "TESTTX" 80 81 // markerAnchor is used in commit messages to determine 82 // where an anchor has been committed. This value is 83 // parsed and therefore must be a const. 84 markerAnchor = "Anchor" 85 86 // markerAnchorConfirmation is used in commit messages to determine 87 // where an anchor confirmation has been committed. This value is 88 // parsed and therefore must be a const. 89 markerAnchorConfirmation = "Anchor confirmation" 90 ) 91 92 var ( 93 _ backend.Backend = (*gitBackEnd)(nil) 94 95 defaultRepoConfig = map[string]string{ 96 // This prevents git from converting CRLF when committing and checking 97 // out files, which helps when running on Windows. 98 "core.autocrlf": "false", 99 "user.name": "Politeia", 100 "user.email": "noreply@decred.org", 101 } 102 103 errNothingToDo = errors.New("nothing to do") 104 ) 105 106 // file is an internal representation of a file that resides in memory. 107 type file struct { 108 name string // Basename of the file 109 digest []byte // SHA256 of payload 110 payload []byte // Actual file payload 111 } 112 113 // gitBackEnd is a git based backend context that satisfies the backend 114 // interface. 115 type gitBackEnd struct { 116 sync.Mutex // Global lock 117 cron *cron.Cron // Scheduler for periodic tasks 118 activeNetParams *chaincfg.Params // indicator if we are running on testnet 119 journal *Journal // Journal context 120 shutdown bool // Backend is shutdown 121 root string // Root directory 122 unvetted string // Unvettend content 123 vetted string // Vetted, public, visible content 124 journals string // Journals/cache 125 dcrtimeHost string // Dcrtimed host 126 gitPath string // Path to git 127 gitTrace bool // Enable git tracing 128 test bool // Set during UT 129 exit chan struct{} // Close channel 130 checkAnchor chan struct{} // Work notification 131 plugins []backend.Plugin // Plugins 132 prefixCache map[string]struct{} // Cache prefixes of existing tokens 133 134 // The following items are used for testing only 135 testAnchors map[string]bool // [digest]anchored 136 } 137 138 func pijoin(elements ...string) string { 139 return filepath.Join(elements...) 140 } 141 142 // getLatest returns the latest version as a string. 143 // This function must be called with the lock held. 144 func getLatest(dir string) (string, error) { 145 files, err := os.ReadDir(dir) 146 if err != nil { 147 return "", backend.ErrRecordNotFound 148 } 149 150 if len(files) == 0 { 151 return "", backend.ErrRecordNotFound 152 } 153 154 // We expect only numeric filenames 155 versions := make([]int, 0, len(files)) 156 for _, v := range files { 157 u, err := strconv.ParseInt(v.Name(), 10, 64) 158 if err != nil { 159 return "", err 160 } 161 versions = append(versions, int(u)) 162 } 163 sort.Ints(versions) 164 165 return strconv.FormatInt(int64(versions[len(versions)-1]), 10), nil 166 } 167 168 // getNext looks at the current latest version and increments the count by one. 169 // This function must be called with the lock held. 170 func getNext(dir string) (string, string, error) { 171 v, err := getLatest(dir) 172 if err != nil { 173 return "", "", backend.ErrRecordNotFound 174 } 175 176 vv, err := strconv.ParseInt(v, 10, 64) 177 if err != nil { 178 return "", "", err 179 } 180 vv++ 181 182 // Sanity 183 if vv <= 0 { 184 return "", "", fmt.Errorf("invalid version") 185 } 186 187 return v, strconv.FormatInt(vv, 10), nil 188 } 189 190 // _joinLatest joins the provided path elements and adds the latest version of 191 // the provided directory. 192 func _joinLatest(elements ...string) (string, error) { 193 dir := pijoin(elements...) 194 v, err := getLatest(dir) 195 if err != nil { 196 return "", err 197 } 198 return pijoin(dir, v), nil 199 } 200 201 // getPathToVersion returns the directory path to the specified record version 202 // if the version isn't provided, the latest version is returned by default 203 func getPathToVersion(path, id, version string) string { 204 if version == "" { 205 return joinLatest(path, id) 206 } else { 207 return pijoin(path, id, version) 208 } 209 } 210 211 // joinLatest joins the provided path elements and adds the latest version of 212 // the provided directory. This function panic when it errors out, this is by 213 // design in order to find all incorrect invocations. 214 func joinLatest(elements ...string) string { 215 path, err := _joinLatest(elements...) 216 if err != nil { 217 panic(err) 218 } 219 return path 220 } 221 222 // extendSHA1 appends 0 to make a SHA1 the size of a SHA256 digest. 223 func extendSHA1(d []byte) []byte { 224 if len(d) != sha1.Size { 225 panic("invalid sha1 length") 226 } 227 digest := make([]byte, sha256.Size) 228 copy(digest, d) 229 return digest 230 } 231 232 // unextendSHA1ToSha256 removes 0 to make a SHA256 the size of a SHA1 digest. 233 func unextendSHA256(d []byte) []byte { 234 if len(d) != sha256.Size { 235 panic("invalid sha256 length") 236 } 237 // make sure this was an extended digest 238 for _, x := range d[sha1.Size:] { 239 if x != 0 { 240 panic("invalid extended sha256") 241 } 242 } 243 digest := make([]byte, sha1.Size) 244 copy(digest, d) 245 return digest 246 } 247 248 // extendSHA1FromString takes a string and ensures it is a digest and then 249 // extends it using extendSHA1. It returns a string representation of the 250 // digest. 251 func extendSHA1FromString(s string) (string, error) { 252 ds, err := hex.DecodeString(s) 253 if err != nil { 254 return "", fmt.Errorf("not hex: %v", s) 255 } 256 d := extendSHA1(ds) 257 return hex.EncodeToString(d), nil 258 } 259 260 // TODO this should use the backend.VerifyContent 261 // verifyContent verifies that all provided backend.MetadataStream and 262 // backend.File are sane and returns a cooked array of the files. 263 func verifyContent(metadata []backend.MetadataStream, files []backend.File, filesDel []string) ([]file, error) { 264 // Make sure all metadata is within maxima. 265 for _, v := range metadata { 266 if v.ID > pd.MetadataStreamsMax-1 { 267 return nil, backend.ContentVerificationError{ 268 ErrorCode: pd.ErrorStatusInvalidMDID, 269 ErrorContext: []string{ 270 strconv.FormatUint(v.ID, 10), 271 }, 272 } 273 } 274 } 275 for i := range metadata { 276 for j := range metadata { 277 // Skip self and non duplicates. 278 if i == j || metadata[i].ID != metadata[j].ID { 279 continue 280 } 281 return nil, backend.ContentVerificationError{ 282 ErrorCode: pd.ErrorStatusDuplicateMDID, 283 ErrorContext: []string{ 284 strconv.FormatUint(metadata[i].ID, 10), 285 }, 286 } 287 } 288 } 289 290 // Prevent paths 291 for i := range files { 292 if filepath.Base(files[i].Name) != files[i].Name { 293 return nil, backend.ContentVerificationError{ 294 ErrorCode: pd.ErrorStatusInvalidFilename, 295 ErrorContext: []string{ 296 files[i].Name, 297 }, 298 } 299 } 300 } 301 for _, v := range filesDel { 302 if filepath.Base(v) != v { 303 return nil, backend.ContentVerificationError{ 304 ErrorCode: pd.ErrorStatusInvalidFilename, 305 ErrorContext: []string{ 306 v, 307 }, 308 } 309 } 310 } 311 312 // Now check files 313 if len(files) == 0 { 314 return nil, backend.ContentVerificationError{ 315 ErrorCode: pd.ErrorStatusEmpty, 316 } 317 } 318 319 // Prevent bad filenames and duplicate filenames 320 for i := range files { 321 for j := range files { 322 if i == j { 323 continue 324 } 325 if files[i].Name == files[j].Name { 326 return nil, backend.ContentVerificationError{ 327 ErrorCode: pd.ErrorStatusDuplicateFilename, 328 ErrorContext: []string{ 329 files[i].Name, 330 }, 331 } 332 } 333 } 334 // Check against filesDel 335 for _, v := range filesDel { 336 if files[i].Name == v { 337 return nil, backend.ContentVerificationError{ 338 ErrorCode: pd.ErrorStatusDuplicateFilename, 339 ErrorContext: []string{ 340 files[i].Name, 341 }, 342 } 343 } 344 } 345 } 346 347 fa := make([]file, 0, len(files)) 348 for i := range files { 349 if gozaru.Sanitize(files[i].Name) != files[i].Name { 350 return nil, backend.ContentVerificationError{ 351 ErrorCode: pd.ErrorStatusInvalidFilename, 352 ErrorContext: []string{ 353 files[i].Name, 354 }, 355 } 356 } 357 358 // Validate digest 359 d, ok := util.ConvertDigest(files[i].Digest) 360 if !ok { 361 return nil, backend.ContentVerificationError{ 362 ErrorCode: pd.ErrorStatusInvalidFileDigest, 363 ErrorContext: []string{ 364 files[i].Name, 365 }, 366 } 367 } 368 369 // Setup cooked file. 370 f := file{ 371 name: files[i].Name, 372 } 373 374 // Decode base64 payload 375 var err error 376 f.payload, err = base64.StdEncoding.DecodeString(files[i].Payload) 377 if err != nil { 378 return nil, backend.ContentVerificationError{ 379 ErrorCode: pd.ErrorStatusInvalidBase64, 380 ErrorContext: []string{ 381 files[i].Name, 382 }, 383 } 384 } 385 386 // Calculate payload digest 387 dp := util.Digest(f.payload) 388 if !bytes.Equal(d[:], dp) { 389 return nil, backend.ContentVerificationError{ 390 ErrorCode: pd.ErrorStatusInvalidFileDigest, 391 ErrorContext: []string{ 392 files[i].Name, 393 }, 394 } 395 } 396 f.digest = dp 397 398 // Verify MIME 399 detectedMIMEType := mime.DetectMimeType(f.payload) 400 if detectedMIMEType != files[i].MIME { 401 return nil, backend.ContentVerificationError{ 402 ErrorCode: pd.ErrorStatusInvalidMIMEType, 403 ErrorContext: []string{ 404 files[i].Name, 405 detectedMIMEType, 406 }, 407 } 408 } 409 410 if !mime.MimeValid(files[i].MIME) { 411 return nil, backend.ContentVerificationError{ 412 ErrorCode: pd.ErrorStatusUnsupportedMIMEType, 413 ErrorContext: []string{ 414 files[i].Name, 415 files[i].MIME, 416 }, 417 } 418 } 419 420 fa = append(fa, f) 421 } 422 423 return fa, nil 424 } 425 426 // loadRecord loads an entire record of disk. It returns an array of 427 // backend.File that is completely filled out. 428 // 429 // This function must be called with the lock held. 430 func loadRecord(path, id, version string) ([]backend.File, error) { 431 pathToVersion := getPathToVersion(path, id, version) 432 // Get dir. 433 recordDir := pijoin(pathToVersion, defaultPayloadDir) 434 files, err := os.ReadDir(recordDir) 435 if err != nil { 436 return nil, err 437 } 438 439 bf := make([]backend.File, 0, len(files)) 440 // Load all files 441 for _, file := range files { 442 fn := pijoin(recordDir, file.Name()) 443 if file.IsDir() { 444 return nil, fmt.Errorf("record corrupt: %v", path) 445 } 446 447 f := backend.File{Name: file.Name()} 448 f.MIME, f.Digest, f.Payload, err = util.LoadFile(fn) 449 if err != nil { 450 return nil, err 451 } 452 bf = append(bf, f) 453 } 454 455 return bf, nil 456 } 457 458 // mdFilename generates the proper filename for a specified repo + proposal and 459 // metadata stream. 460 func mdFilename(path, id string, mdID int) string { 461 return pijoin(joinLatest(path, id), 462 strconv.FormatUint(uint64(mdID), 10)+defaultMDFilenameSuffix) 463 } 464 465 // loadMDStreams loads all streams of disk. It returns an array of 466 // backend.MetadataStream that is completely filled out. 467 // 468 // This function must be called with the lock held. 469 func loadMDStreams(path, id, version string) ([]backend.MetadataStream, error) { 470 pathToVersion := getPathToVersion(path, id, version) 471 files, err := os.ReadDir(pathToVersion) 472 if err != nil { 473 return nil, err 474 } 475 476 ms := make([]backend.MetadataStream, 0, len(files)) 477 for _, v := range files { 478 // Skip irrelevant files 479 if !strings.HasSuffix(v.Name(), defaultMDFilenameSuffix) { 480 continue 481 } 482 483 // Fish out metadata stream ID from filename 484 ids := strings.TrimSuffix(v.Name(), defaultMDFilenameSuffix) 485 mdid, err := strconv.ParseUint(ids, 10, 64) 486 if err != nil { 487 return nil, err 488 } 489 490 // Load metadata stream 491 fn := pijoin(pathToVersion, v.Name()) 492 md, err := os.ReadFile(fn) 493 if err != nil { 494 return nil, err 495 } 496 ms = append(ms, backend.MetadataStream{ 497 ID: mdid, 498 Payload: string(md), 499 }) 500 } 501 502 return ms, nil 503 } 504 505 // loadMD loads a RecordMetadata from the provided path/id. This may 506 // be unvetted/id or vetted/id. 507 // 508 // This function should be called with the lock held. 509 func loadMD(path, id, version string) (*backend.RecordMetadata, error) { 510 pathToVersion := getPathToVersion(path, id, version) 511 filename := pijoin(pathToVersion, 512 defaultRecordMetadataFilename) 513 f, err := os.Open(filename) 514 if err != nil { 515 if os.IsNotExist(err) { 516 err = backend.ErrRecordNotFound 517 } 518 return nil, err 519 } 520 defer f.Close() 521 522 var brm backend.RecordMetadata 523 decoder := json.NewDecoder(f) 524 if err = decoder.Decode(&brm); err != nil { 525 return nil, err 526 } 527 return &brm, nil 528 } 529 530 // createMD stores a RecordMetadata to the provided path/id. This may be 531 // unvetted/id or vetted/id. 532 // 533 // This function should be called with the lock held. 534 func createMD(path, id string, status backend.MDStatusT, iteration uint64, hashes []*[sha256.Size]byte) (*backend.RecordMetadata, error) { 535 // Create record metadata 536 m := *merkle.Root(hashes) 537 brm := backend.RecordMetadata{ 538 Version: backend.VersionRecordMD, 539 Iteration: iteration, 540 Status: status, 541 Merkle: hex.EncodeToString(m[:]), 542 Timestamp: time.Now().Unix(), 543 Token: id, 544 } 545 546 err := updateMD(path, id, &brm) 547 if err != nil { 548 return nil, err 549 } 550 551 return &brm, nil 552 } 553 554 // updateMD updates the RecordMetadata status to the provided path/id. 555 // 556 // This function should be called with the lock held. 557 func updateMD(path, id string, brm *backend.RecordMetadata) error { 558 // Store metadata record. 559 filename := pijoin(joinLatest(path, id), defaultRecordMetadataFilename) 560 f, err := os.Create(filename) 561 if err != nil { 562 return err 563 } 564 defer f.Close() 565 566 return json.NewEncoder(f).Encode(*brm) 567 } 568 569 // commitMD commits the MD into a git repo. 570 // 571 // This function should be called with the lock held. 572 func (g *gitBackEnd) commitMD(path, id, msg string) error { 573 // git add id/brm.json 574 filename := pijoin(joinLatest(path, id), 575 defaultRecordMetadataFilename) 576 err := g.gitAdd(path, filename) 577 if err != nil { 578 return err 579 } 580 581 // git commit -m "message" 582 return g.gitCommit(path, "Update record status "+id+" "+msg) 583 } 584 585 // deltaCommits returns sha1 extended digests and one line commit messages to 586 // the caller. If lastAnchor is empty then the range is from the dawn of time 587 // until now. If lastAnchor is a valid hash the range is from lastAnchor up 588 // until no. 589 // 590 // This function should be called with the lock held. 591 func (g *gitBackEnd) deltaCommits(path string, lastAnchor []byte) ([]*[sha256.Size]byte, []string, []string, error) { 592 // Sanity 593 if !(len(lastAnchor) == 0 || len(lastAnchor) == sha256.Size) { 594 return nil, nil, nil, fmt.Errorf("invalid digest size") 595 } 596 597 // Minimal git arguments 598 args := []string{"log", "--pretty=oneline"} 599 600 // Determine digest range 601 latestCommit, err := g.gitLastDigest(path) 602 if err != nil { 603 return nil, nil, nil, err 604 } 605 if len(lastAnchor) != 0 { 606 // git log lastAnchor..latestCommit --pretty=oneline 607 sha1LastAnchor := unextendSHA256(lastAnchor) 608 if bytes.Equal(sha1LastAnchor, latestCommit) { 609 return nil, nil, nil, errNothingToDo 610 } 611 args = append(args, hex.EncodeToString(sha1LastAnchor)+".."+ 612 hex.EncodeToString(latestCommit)) 613 } 614 615 // Execute git 616 out, err := g.git(path, args...) 617 if err != nil { 618 return nil, nil, nil, err 619 } 620 if len(out) == 0 { 621 return nil, nil, nil, fmt.Errorf("invalid git output") 622 } 623 624 // Generate return data 625 digests := make([]*[sha256.Size]byte, 0, len(out)) 626 commitMessages := make([]string, 0, len(out)) 627 for _, line := range out { 628 // Returned data is "<digest> <commit message>" 629 ds := strings.SplitN(line, " ", 2) 630 if len(ds) == 0 { 631 return nil, nil, nil, fmt.Errorf("invalid log") 632 } 633 634 // Ignore anchor confirmation commits 635 if regexAnchorConfirmation.MatchString(ds[1]) { 636 continue 637 } 638 639 // Validate returned digest 640 sha1Digest, err := hex.DecodeString(ds[0]) 641 if err != nil { 642 return nil, nil, nil, err 643 } 644 if len(sha1Digest) != sha1.Size { 645 return nil, nil, nil, fmt.Errorf("invalid sha1 size") 646 } 647 sha256DigestB := extendSHA1(sha1Digest) 648 var sha256Digest [sha256.Size]byte 649 copy(sha256Digest[:], sha256DigestB) 650 651 // Fill out return values 652 digests = append(digests, &sha256Digest) 653 commitMessages = append(commitMessages, ds[1]) 654 } 655 656 if len(digests) == 0 { 657 return nil, nil, nil, errNothingToDo 658 } 659 660 return digests, commitMessages, out, nil 661 } 662 663 // anchor takes a slice of commit digests and anchors them in dcrtime. 664 // 665 // This function is being clever with the anchors. It sends two values to 666 // dcrtime. We anchor the merkle root, and we *also* anchor all 667 // individual commit hashes. We do the last bit in order to be able to 668 // externally validate that a commit hash made it into the time stamp. If we 669 // don't do that we'd have to create a tool to verify individual hashes for the 670 // truly curious. This is essentially free because dcrtime compresses all 671 // digests into a single merkle root. 672 // 673 // This function should be called with the lock held. 674 // TODO: the physical write to dcrtime needs to come out of the lock. 675 func (g *gitBackEnd) anchor(digests []*[sha256.Size]byte) error { 676 // Anchor all digests 677 if g.test { 678 // We always append the anchorKey as the last element 679 x := len(digests) - 1 680 g.testAnchors[hex.EncodeToString(digests[x][:])] = false 681 return nil 682 } 683 684 return timestamp("politeia", g.dcrtimeHost, digests) 685 } 686 687 // appendAuditTrail adds a record to the audit trail. 688 func (g *gitBackEnd) appendAuditTrail(path string, ts int64, merkle [sha256.Size]byte, lines []string) error { 689 f, err := os.OpenFile(pijoin(path, defaultAuditTrailFile), 690 os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) 691 if err != nil { 692 return err 693 } 694 defer f.Close() 695 696 fmt.Fprintf(f, "%v: --- Audit Trail Record %x ---\n", ts, merkle) 697 for _, line := range lines { 698 fmt.Fprintf(f, "%v: %v\n", ts, strings.Trim(line, " \t\n")) 699 } 700 701 return nil 702 } 703 704 // runAnchorFsck returns whether a git fsck should be run as part of the 705 // anchoring process. As a the git repos grow in the size the git fsck command 706 // can keep the gitBackEnd locked for an untolerable amount of time. This 707 // function can be used to specify specific times that the git fsck should be 708 // run instead of it being run everytime an anchor it dropped. 709 func (g *gitBackEnd) runAnchorFsck() bool { 710 // We want to run the git fsck during the 06:58 UTC anchor drop 711 // only. This time was chosen because it falls in the middle of 712 // the night for the USA and Brazil, which is likely the majority 713 // of politeia traffic. 714 utc, err := time.LoadLocation("UTC") 715 if err != nil { 716 e := fmt.Sprintf("load time location UTC: %v", err) 717 panic(e) 718 } 719 now := time.Now().In(utc) 720 y, m, d := now.Date() 721 722 // Give a 5 minute buffer on either side of 06:58 UTC. If the 723 // current time falls within this window then true is returned 724 // to indicate a fsck should be run. 725 start := time.Date(y, m, d, 6, 53, 0, 0, utc) 726 end := time.Date(y, m, d, 7, 03, 0, 0, utc) 727 if now.After(start) && now.Before(end) { 728 return true 729 } 730 731 return false 732 } 733 734 // anchorRepo drops an anchor for an individual repo. 735 // It prints the basename during its actions. 736 // 737 // This function should be called with the lock held. 738 func (g *gitBackEnd) anchorRepo(path string) (*[sha256.Size]byte, error) { 739 // Make sure we have a repo we understand 740 repo := filepath.Base(path) 741 742 // Fsck 743 err := g.gitCheckout(path, "master") 744 if err != nil { 745 return nil, fmt.Errorf("anchor checkout master %v: %v", repo, 746 err) 747 } 748 749 if g.runAnchorFsck() { 750 log.Infof("Running git fsck on %v repository", repo) 751 _, err = g.gitFsck(path) 752 if err != nil { 753 return nil, fmt.Errorf("anchor fsck master %v: %v", repo, err) 754 } 755 } 756 757 // Check for unanchored commits 758 last, err := g.readLastAnchorRecord() 759 if err != nil { 760 return nil, fmt.Errorf("could not find last %v digest: %v", repo, 761 err) 762 } 763 764 // Fill out unvetted digests 765 digests, messages, _, err := g.deltaCommits(path, last.Last) 766 if err != nil { 767 if errors.Is(err, errNothingToDo) { 768 return nil, err 769 } 770 return nil, fmt.Errorf("could not determine delta %v: %v", 771 repo, err) 772 } 773 if len(digests) != len(messages) { 774 // Really can't happen 775 return nil, fmt.Errorf("invalid digests(%v)/messages(%v) count", 776 len(digests), len(messages)) 777 } 778 779 // Create commit message BEFORE calling anchor. anchor calls 780 // merkle.Root which in turn sorts the digests and that is fine but not 781 // what we want to display to the user. 782 commitMessage := "" 783 auditLines := make([]string, 0, len(digests)) 784 for k, digest := range digests { 785 line := fmt.Sprintf("%x %v\n", *digest, messages[k]) 786 commitMessage += line 787 auditLines = append(auditLines, line) 788 } 789 790 // Create anchor record early for the same reason. 791 anchorRecord, anchorKey, err := newAnchorRecord(AnchorUnverified, 792 digests, messages) 793 if err != nil { 794 return nil, fmt.Errorf("newAnchorRecord: %v", err) 795 } 796 797 // Append MerkleRoot to digests. We have to do this since this is 798 // politeia's lookup key but dcrtime will likely return a different 799 // merkle. Dcrtime returns a different merkle when there are 800 // additional digests in the set. 801 digests = append(digests, anchorKey) 802 803 // Anchor commits 804 log.Infof("Anchoring %v repository", repo) 805 err = g.anchor(digests) 806 if err != nil { 807 return nil, fmt.Errorf("anchor: %v", err) 808 } 809 810 // Prefix commitMessage with merkle root 811 commitMessage = fmt.Sprintf("%v %x\n\n%v", markerAnchor, *anchorKey, 812 commitMessage) 813 814 // Commit merkle root as an anchor and append included commits to audit 815 // trail 816 err = g.appendAuditTrail(path, anchorRecord.Time, *anchorKey, 817 auditLines) 818 if err != nil { 819 return nil, fmt.Errorf("could not append to audit trail: %v", 820 err) 821 } 822 err = g.gitAdd(path, defaultAuditTrailFile) 823 if err != nil { 824 return nil, fmt.Errorf("gitAdd: %v", err) 825 } 826 err = g.gitCommit(path, commitMessage) 827 if err != nil { 828 return nil, fmt.Errorf("gitCommit: %v", err) 829 } 830 831 return anchorKey, nil 832 } 833 834 // anchor verifies if there are new commits in all repos and if that is the 835 // case it drops and anchor in dcrtime for each of them. 836 func (g *gitBackEnd) anchorAllRepos() error { 837 log.Infof("Dropping anchor") 838 // Lock filesystem 839 g.Lock() 840 defer g.Unlock() 841 if g.shutdown { 842 return fmt.Errorf("anchorAllRepos: %v", backend.ErrShutdown) 843 } 844 845 // Anchor vetted 846 log.Infof("Anchoring %v", g.vetted) 847 mr, err := g.anchorRepo(g.vetted) 848 if err != nil { 849 if errors.Is(err, errNothingToDo) { 850 log.Infof("Anchoring %v: nothing to do", g.vetted) 851 return nil 852 } 853 return fmt.Errorf("anchor repo %v: %v", g.vetted, err) 854 } 855 856 // Sync vetted to unvetted 857 858 // git pull --ff-only --rebase 859 err = g.gitPull(g.unvetted, true) 860 if err != nil { 861 return err 862 } 863 864 log.Infof("Dropping anchor complete: %x", *mr) 865 866 return nil 867 } 868 869 // periodicAnchorChecker must be run as a go routine. It sits around and 870 // periodically checks if there is work to do. It can also be tickled by 871 // messaging checkAnchor. 872 func (g *gitBackEnd) periodicAnchorChecker() { 873 log.Infof("Periodic anchor checker launched") 874 defer log.Infof("Periodic anchor checker exited") 875 for { 876 select { 877 case <-g.exit: 878 return 879 case <-g.checkAnchor: 880 case <-time.After(5 * time.Minute): 881 } 882 883 g.Lock() 884 isShutdown := g.shutdown 885 g.Unlock() 886 if isShutdown { 887 return 888 } 889 890 // Do lengthy work, this may have to be its own go routine 891 err := g.anchorChecker() 892 if err != nil { 893 // Not much we can do past logging 894 log.Errorf("periodicAnchorChecker: %v", err) 895 } 896 } 897 } 898 899 // anchorChecker does the work for periodicAnchorChecker. It lives in its own 900 // function for testing purposes. 901 func (g *gitBackEnd) anchorChecker() error { 902 ua, err := g.readUnconfirmedAnchorRecord() 903 if err != nil { 904 return fmt.Errorf("anchorChecker read: %v", err) 905 } 906 907 // Check for work 908 if len(ua.Merkles) == 0 { 909 return nil 910 } 911 912 // Do one verify at a time for now 913 vrs := make([]v1.VerifyDigest, 0, len(ua.Merkles)) 914 for _, u := range ua.Merkles { 915 digest := hex.EncodeToString(u) 916 vr, err := g.verifyAnchor(digest) 917 if err != nil { 918 log.Errorf("anchorChecker verify: %v", err) 919 continue 920 } 921 vrs = append(vrs, *vr) 922 } 923 924 err = g.afterAnchorVerify(vrs) 925 if err != nil { 926 return fmt.Errorf("afterAnchorVerify: %v", err) 927 } 928 929 return nil 930 } 931 932 // afterAnchorVerify completes the anchor verification process. It is a 933 // separate function in order not having to futz with locks. 934 func (g *gitBackEnd) afterAnchorVerify(vrs []v1.VerifyDigest) error { 935 // Lock filesystem 936 g.Lock() 937 defer g.Unlock() 938 939 var err error 940 941 if len(vrs) != 0 { 942 // git checkout master 943 err = g.gitCheckout(g.vetted, "master") 944 if err != nil { 945 return err 946 } 947 } 948 // Handle verified vrs 949 for _, vr := range vrs { 950 if vr.ChainInformation.ChainTimestamp == 0 { 951 // dcrtime returns 0 when there are not enough 952 // confirmations yet. 953 return fmt.Errorf("not enough confirmations: %v", 954 vr.Digest) 955 } 956 957 // Use the audit trail as the file to be committed 958 mr, ok := util.ConvertDigest(vr.Digest) 959 if !ok { 960 return fmt.Errorf("invalid digest: %v", vr.Digest) 961 } 962 txLine := fmt.Sprintf("%v anchored in TX %v\n", vr.Digest, 963 vr.ChainInformation.Transaction) 964 err = g.appendAuditTrail(g.vetted, 965 vr.ChainInformation.ChainTimestamp, mr, []string{txLine}) 966 if err != nil { 967 return err 968 } 969 err = g.gitAdd(g.vetted, defaultAuditTrailFile) 970 if err != nil { 971 return err 972 } 973 974 // Store dcrtime information. 975 // In vetted store the ChainInformation as a json object in 976 // directory anchor. 977 // In Vetted in the record directory add a file called anchor 978 // that points to the TX id. 979 anchorDir := pijoin(g.vetted, defaultAnchorsDirectory) 980 err = os.MkdirAll(anchorDir, 0774) 981 if err != nil { 982 return err 983 } 984 ar, err := json.Marshal(vr.ChainInformation) 985 if err != nil { 986 return err 987 } 988 err = os.WriteFile(pijoin(anchorDir, vr.Digest), 989 ar, 0664) 990 if err != nil { 991 return err 992 } 993 err = g.gitAdd(g.vetted, 994 pijoin(defaultAnchorsDirectory, vr.Digest)) 995 if err != nil { 996 return err 997 } 998 999 // git commit anchor confirmation 1000 commitMsg := markerAnchorConfirmation + " " + vr.Digest + "\n\n" + txLine 1001 err = g.gitCommit(g.vetted, commitMsg) 1002 if err != nil { 1003 return err 1004 } 1005 1006 // Mark test anchors as confirmed by dcrtime 1007 if g.test { 1008 g.testAnchors[vr.Digest] = true 1009 } 1010 } 1011 if len(vrs) != 0 { 1012 // git checkout master unvetted 1013 err = g.gitCheckout(g.unvetted, "master") 1014 if err != nil { 1015 return err 1016 } 1017 1018 // git pull --ff-only --rebase 1019 err = g.gitPull(g.unvetted, true) 1020 if err != nil { 1021 return err 1022 } 1023 } 1024 1025 return nil 1026 } 1027 1028 // anchorAllReposCronJob is the cron job that anchors all repos at a preset time. 1029 func (g *gitBackEnd) anchorAllReposCronJob() { 1030 err := g.anchorAllRepos() 1031 if err != nil { 1032 log.Errorf("%v", err) 1033 } 1034 } 1035 1036 // verifyAnchor asks dcrtime if an anchor has been verified and returns a TX if 1037 // it has. 1038 func (g *gitBackEnd) verifyAnchor(digest string) (*v1.VerifyDigest, error) { 1039 var ( 1040 vr *v1.VerifyReply 1041 err error 1042 ) 1043 1044 // In test mode we fake success. 1045 if g.test { 1046 // Fake success 1047 vr = &v1.VerifyReply{} 1048 anchored, ok := g.testAnchors[digest] 1049 if !ok { 1050 return nil, fmt.Errorf("test not found") 1051 } 1052 if anchored { 1053 return nil, fmt.Errorf("already anchored") 1054 } 1055 vr.Digests = append(vr.Digests, v1.VerifyDigest{ 1056 Digest: digest, 1057 Result: v1.ResultOK, 1058 ChainInformation: v1.ChainInformation{ 1059 ChainTimestamp: time.Now().Unix(), 1060 Transaction: expectedTestTX, 1061 }, 1062 }) 1063 } else { 1064 // Call dcrtime 1065 vr, err = verifyTimestamp("politeia", g.dcrtimeHost, 1066 []string{digest}) 1067 if err != nil { 1068 return nil, err 1069 } 1070 } 1071 1072 // Do some sanity checks 1073 if len(vr.Digests) != 1 { 1074 return nil, fmt.Errorf("unexpected number of digests") 1075 } 1076 if vr.Digests[0].Result != v1.ResultOK { 1077 return nil, fmt.Errorf("unexpected result: %v", 1078 vr.Digests[0].Result) 1079 } 1080 1081 return &vr.Digests[0], nil 1082 } 1083 1084 // _newRecord adds a new record to the unvetted repo. Note that this function 1085 // must be wrapped by a function that delivers the call with the unvetted repo 1086 // sitting in the correct branch. The idea is that if this function fails we 1087 // can simply unwind it said branch. 1088 // 1089 // Function must be called with the lock held. 1090 func (g *gitBackEnd) _newRecord(id string, metadata []backend.MetadataStream, fa []file) (*backend.RecordMetadata, error) { 1091 // Process files. 1092 path := pijoin(g.unvetted, id, "1", defaultPayloadDir) 1093 err := os.MkdirAll(path, 0774) 1094 if err != nil { 1095 return nil, err 1096 } 1097 1098 hashes := make([]*[sha256.Size]byte, 0, len(fa)) 1099 for i := range fa { 1100 // Copy files into directory id/payload/filename. 1101 filename := pijoin(path, fa[i].name) 1102 err = os.WriteFile(filename, fa[i].payload, 0664) 1103 if err != nil { 1104 return nil, err 1105 } 1106 var d [sha256.Size]byte 1107 copy(d[:], fa[i].digest) 1108 hashes = append(hashes, &d) 1109 1110 // git add id/payload/filename 1111 err = g.gitAdd(g.unvetted, filename) 1112 if err != nil { 1113 return nil, err 1114 } 1115 } 1116 1117 // Save all metadata streams 1118 for i := range metadata { 1119 filename := pijoin(joinLatest(g.unvetted, id), 1120 fmt.Sprintf("%02v%v", metadata[i].ID, 1121 defaultMDFilenameSuffix)) 1122 err = os.WriteFile(filename, []byte(metadata[i].Payload), 1123 0664) 1124 if err != nil { 1125 return nil, err 1126 } 1127 // git add id/metadata.txt 1128 err = g.gitAdd(g.unvetted, filename) 1129 if err != nil { 1130 return nil, err 1131 } 1132 } 1133 1134 // Save record metadata 1135 brm, err := createMD(g.unvetted, id, backend.MDStatusUnvetted, 1, 1136 hashes) 1137 if err != nil { 1138 return nil, err 1139 } 1140 1141 // git add id/version/recordmetadata.json 1142 filename := pijoin(joinLatest(g.unvetted, id), 1143 defaultRecordMetadataFilename) 1144 err = g.gitAdd(g.unvetted, filename) 1145 if err != nil { 1146 return nil, err 1147 } 1148 1149 // git commit -m "message" 1150 err = g.gitCommit(path, "Add record "+id) 1151 if err != nil { 1152 return nil, err 1153 } 1154 1155 return brm, nil 1156 } 1157 1158 // newRecord adds a new record to the unvetted repo. If something fails it 1159 // unwinds changes made and returns sitting in the master branch. Note that if 1160 // the call fails the branch is deleted because the branch does not contain 1161 // anything of value. 1162 // 1163 // Function must be called with the lock held. 1164 func (g *gitBackEnd) newRecord(token []byte, metadata []backend.MetadataStream, fa []file) (*backend.RecordMetadata, error) { 1165 id := hex.EncodeToString(token) 1166 1167 log.Tracef("newRecord %v", id) 1168 1169 // git checkout -b id 1170 err := g.gitNewBranch(g.unvetted, id) 1171 if err != nil { 1172 return nil, err 1173 } 1174 1175 rm, err2 := g._newRecord(id, metadata, fa) 1176 if err2 != nil { 1177 // Unwind and complain 1178 err = g.gitUnwindBranch(g.unvetted, id) 1179 if err != nil { 1180 // We are in trouble and should consider a panic 1181 log.Criticalf("newRecord: %v", err) 1182 } 1183 return nil, err2 1184 } 1185 1186 // git checkout master 1187 err = g.gitCheckout(g.unvetted, "master") 1188 if err != nil { 1189 return nil, err 1190 } 1191 1192 return rm, nil 1193 } 1194 1195 // getVettedTokens gets the tokens of all vetted records by retrieving the 1196 // names of the folders in the vetted directory. 1197 // 1198 // Function must be called with the lock held. 1199 func (g *gitBackEnd) getVettedTokens() ([]string, error) { 1200 files, err := os.ReadDir(g.vetted) 1201 if err != nil { 1202 return nil, err 1203 } 1204 1205 vettedTokens := make([]string, 0, len(files)) 1206 for _, v := range files { 1207 id := v.Name() 1208 if !util.IsDigest(id) { 1209 continue 1210 } 1211 vettedTokens = append(vettedTokens, id) 1212 } 1213 1214 return vettedTokens, nil 1215 } 1216 1217 // getUnvettedTokens gets the tokens of all unvetted records by retrieving the 1218 // names of the git branches in the unvetted directory. 1219 // 1220 // Function must be called with the lock held. 1221 func (g *gitBackEnd) getUnvettedTokens() ([]string, error) { 1222 branches, err := g.gitBranches(g.unvetted) 1223 if err != nil { 1224 return nil, err 1225 } 1226 1227 unvettedTokens := make([]string, 0, len(branches)) 1228 for _, id := range branches { 1229 if !util.IsDigest(id) { 1230 continue 1231 } 1232 unvettedTokens = append(unvettedTokens, id) 1233 } 1234 1235 return unvettedTokens, nil 1236 } 1237 1238 // populateTokenPrefixCache populates the prefix cache on the gitBackEnd 1239 // object with the prefixes of the tokens of both vetted and unvetted 1240 // records. This cache is used to ensure that only tokens with unique prefixes 1241 // are generated, because this allows lookups based on the prefix of a token. 1242 // 1243 // This function must be called with the lock held. 1244 // 1245 // This must be called after the vetted and unvetted repos are created, 1246 // otherwise it will return an error. 1247 func (g *gitBackEnd) populateTokenPrefixCache() error { 1248 vettedTokens, err := g.getVettedTokens() 1249 if err != nil { 1250 return err 1251 } 1252 1253 unvettedTokens, err := g.getUnvettedTokens() 1254 if err != nil { 1255 return err 1256 } 1257 1258 prefixCache := make(map[string]struct{}, 1259 len(vettedTokens)+len(unvettedTokens)) 1260 1261 vettedPrefixes := util.TokensToPrefixes(vettedTokens) 1262 unvettedPrefixes := util.TokensToPrefixes(unvettedTokens) 1263 1264 for _, prefix := range vettedPrefixes { 1265 prefixCache[prefix] = struct{}{} 1266 } 1267 for _, prefix := range unvettedPrefixes { 1268 prefixCache[prefix] = struct{}{} 1269 } 1270 g.prefixCache = prefixCache 1271 1272 return nil 1273 } 1274 1275 // randomUniqueToken generates a new token of length pd.TokenSize which 1276 // does not share a prefix of length pd.TokenPrefixSize with any existing 1277 // token. This is needed to allow lookups based on the prefix of a token. 1278 // 1279 // This method must be called with the lock held. 1280 func (g *gitBackEnd) randomUniqueToken() ([]byte, error) { 1281 TRIES := 1000 1282 for i := 0; i < TRIES; i++ { 1283 token, err := util.Random(pd.TokenSize) 1284 if err != nil { 1285 return nil, err 1286 } 1287 1288 newToken := hex.EncodeToString(token) 1289 prefix := util.TokenToPrefix(newToken) 1290 1291 if _, ok := g.prefixCache[prefix]; !ok { 1292 g.prefixCache[prefix] = struct{}{} 1293 return token, nil 1294 } 1295 } 1296 1297 return nil, fmt.Errorf("failed to find unique token after %v tries", TRIES) 1298 } 1299 1300 // New takes a record verifies it and drops it on disk in the unvetted 1301 // directory. Records and metadata are stored in unvetted/token/. the 1302 // function returns a RecordMetadata. 1303 // 1304 // New satisfies the backend interface. 1305 func (g *gitBackEnd) New(metadata []backend.MetadataStream, files []backend.File) (*backend.RecordMetadata, error) { 1306 log.Tracef("New") 1307 fa, err := verifyContent(metadata, files, []string{}) 1308 if err != nil { 1309 return nil, err 1310 } 1311 1312 // Lock filesystem 1313 g.Lock() 1314 defer g.Unlock() 1315 if g.shutdown { 1316 return nil, backend.ErrShutdown 1317 } 1318 1319 token, err := g.randomUniqueToken() 1320 if err != nil { 1321 return nil, err 1322 } 1323 1324 log.Debugf("New %x", token) 1325 1326 // git checkout master 1327 err = g.gitCheckout(g.unvetted, "master") 1328 if err != nil { 1329 return nil, err 1330 } 1331 1332 // git pull --ff-only --rebase 1333 err = g.gitPull(g.unvetted, true) 1334 if err != nil { 1335 return nil, err 1336 } 1337 1338 return g.newRecord(token, metadata, fa) 1339 } 1340 1341 // updateMetadata appends or overwrites in the unvetted repository. 1342 // Additionally it does the git bits when called. 1343 // Function must be called with the lock held. 1344 func (g *gitBackEnd) updateMetadata(id string, mdAppend, mdOverwrite []backend.MetadataStream) error { 1345 // Overwrite metadata 1346 for i := range mdOverwrite { 1347 filename := pijoin(joinLatest(g.unvetted, id), 1348 fmt.Sprintf("%02v%v", mdOverwrite[i].ID, 1349 defaultMDFilenameSuffix)) 1350 err := os.WriteFile(filename, []byte(mdOverwrite[i].Payload), 1351 0664) 1352 if err != nil { 1353 return err 1354 } 1355 // git add id/metadata.txt 1356 err = g.gitAdd(g.unvetted, filename) 1357 if err != nil { 1358 return err 1359 } 1360 } 1361 1362 // Append metadata 1363 for i := range mdAppend { 1364 filename := pijoin(joinLatest(g.unvetted, id), 1365 fmt.Sprintf("%02v%v", mdAppend[i].ID, 1366 defaultMDFilenameSuffix)) 1367 f, err := os.OpenFile(filename, 1368 os.O_RDWR|os.O_CREATE|os.O_APPEND, 0644) 1369 if err != nil { 1370 return err 1371 } 1372 _, err = io.WriteString(f, mdAppend[i].Payload) 1373 if err != nil { 1374 f.Close() 1375 return err 1376 } 1377 f.Close() 1378 // git add id/metadata.txt 1379 err = g.gitAdd(g.unvetted, filename) 1380 if err != nil { 1381 return err 1382 } 1383 } 1384 return nil 1385 } 1386 1387 func (g *gitBackEnd) checkoutRecordBranch(id string) (bool, error) { 1388 // See if branch already exists 1389 branches, err := g.gitBranches(g.unvetted) 1390 if err != nil { 1391 return false, err 1392 } 1393 var found bool 1394 for _, v := range branches { 1395 if !util.IsDigest(v) { 1396 continue 1397 } 1398 if v == id { 1399 found = true 1400 break 1401 } 1402 } 1403 1404 if found { 1405 // Branch exists, modify branch 1406 err := g.gitCheckout(g.unvetted, id) 1407 if err != nil { 1408 return true, backend.ErrRecordNotFound 1409 } 1410 } else { 1411 // Branch does not exist, create it if record exists 1412 fi, err := os.Stat(pijoin(g.unvetted, id)) 1413 if err != nil { 1414 if os.IsNotExist(err) { 1415 return false, backend.ErrRecordNotFound 1416 } 1417 } 1418 if !fi.IsDir() { 1419 return false, fmt.Errorf("unvetted repo corrupt: %v "+ 1420 "is not a dir", fi.Name()) 1421 } 1422 // git checkout -b id 1423 err = g.gitNewBranch(g.unvetted, id) 1424 if err != nil { 1425 return false, err 1426 } 1427 } 1428 1429 return found, nil 1430 } 1431 1432 // _updateRecord takes various parameters to update a record. Note that this 1433 // function must be wrapped by a function that delivers the call with the repo 1434 // sitting in the correct branch/master. The idea is that if this function 1435 // fails we can simply unwind it by calling a git stash. 1436 // If commit is true the changes will be committed to record, if it is false 1437 // it'll return ErrChangesRecord if the error would change; the caller is 1438 // responsible to unwinding the changes. 1439 // 1440 // Function must be called with the lock held. 1441 func (g *gitBackEnd) _updateRecord(commit bool, id string, mdAppend, mdOverwrite []backend.MetadataStream, fa []file, filesDel []string) error { 1442 // Get version for relative git rm command later. 1443 version, err := getLatest(pijoin(g.unvetted, id)) 1444 if err != nil { 1445 return err 1446 } 1447 1448 log.Tracef("updating %v %v", commit, id) 1449 defer func() { log.Tracef("updating complete: %v", id) }() 1450 1451 // Load MD 1452 brm, err := loadMD(g.unvetted, id, "") 1453 if err != nil { 1454 return err 1455 } 1456 if !(brm.Status == backend.MDStatusVetted || 1457 brm.Status == backend.MDStatusUnvetted || 1458 brm.Status == backend.MDStatusIterationUnvetted || 1459 brm.Status == backend.MDStatusArchived) { 1460 return fmt.Errorf("can not update record that "+ 1461 "has status: %v %v", brm.Status, 1462 backend.MDStatus[brm.Status]) 1463 } 1464 1465 // Verify all deletes before executing 1466 for _, v := range filesDel { 1467 fi, err := os.Stat(pijoin(joinLatest(g.unvetted, id), 1468 defaultPayloadDir, v)) 1469 if err != nil { 1470 if os.IsNotExist(err) { 1471 return backend.ContentVerificationError{ 1472 ErrorCode: pd.ErrorStatusFileNotFound, 1473 ErrorContext: []string{v}, 1474 } 1475 } 1476 } 1477 if !fi.Mode().IsRegular() { 1478 return fmt.Errorf("not a file: %v", fi.Name()) 1479 } 1480 } 1481 1482 // At this point we should be ready to add/remove/update all the things. 1483 path := pijoin(joinLatest(g.unvetted, id), defaultPayloadDir) 1484 for i := range fa { 1485 // Copy files into directory id/payload/filename. 1486 filename := pijoin(path, fa[i].name) 1487 err = os.WriteFile(filename, fa[i].payload, 0664) 1488 if err != nil { 1489 return err 1490 } 1491 1492 // git add id/payload/filename 1493 err = g.gitAdd(g.unvetted, filename) 1494 if err != nil { 1495 return err 1496 } 1497 } 1498 1499 // Delete files 1500 relativeRmDir := pijoin(id, version, defaultPayloadDir) 1501 for _, v := range filesDel { 1502 err = g.gitRm(g.unvetted, pijoin(relativeRmDir, v), true) 1503 if err != nil { 1504 return err 1505 } 1506 } 1507 1508 // Handle metadata 1509 err = g.updateMetadata(id, mdAppend, mdOverwrite) 1510 if err != nil { 1511 return err 1512 } 1513 1514 // Find all hashes 1515 hashes := make([]*[sha256.Size]byte, 0, len(fa)) 1516 ppath := pijoin(joinLatest(g.unvetted, id), defaultPayloadDir) 1517 newRecordFiles, err := os.ReadDir(ppath) 1518 if err != nil { 1519 if os.IsNotExist(err) { 1520 return backend.ContentVerificationError{ 1521 ErrorCode: pd.ErrorStatusEmpty, 1522 } 1523 } 1524 return err 1525 } 1526 for _, v := range newRecordFiles { 1527 digest, err := util.DigestFileBytes(pijoin(ppath, 1528 v.Name())) 1529 if err != nil { 1530 return err 1531 } 1532 var d [sha256.Size]byte 1533 copy(d[:], digest) 1534 hashes = append(hashes, &d) 1535 } 1536 1537 // If there are no changes DO NOT update the record and reply with no 1538 // changes. 1539 log.Tracef("_updateRecord: verify changes %v", id) 1540 if !g.gitHasChanges(g.unvetted) { 1541 return backend.ErrNoChanges 1542 } 1543 1544 if !commit { 1545 return backend.ErrChangesRecord 1546 } 1547 1548 log.Tracef("_updateRecord: committing %v", id) 1549 1550 // Update record metadata 1551 ns := backend.MDStatusIterationUnvetted 1552 if brm.Status == backend.MDStatusVetted { 1553 ns = backend.MDStatusVetted 1554 } 1555 _, err = createMD(g.unvetted, id, ns, brm.Iteration+1, hashes) 1556 if err != nil { 1557 return err 1558 } 1559 1560 // Call plugin hooks 1561 f, ok := decredPluginHooks[PluginPostHookEdit] 1562 if ok { 1563 log.Tracef("Calling hook: %v(%v)", PluginPostHookEdit, id) 1564 err = f(id) 1565 if err != nil { 1566 return err 1567 } 1568 } 1569 1570 // git add id/recordmetadata.json 1571 filename := pijoin(joinLatest(g.unvetted, id), 1572 defaultRecordMetadataFilename) 1573 err = g.gitAdd(g.unvetted, filename) 1574 if err != nil { 1575 return err 1576 } 1577 1578 // git commit -m "message" 1579 err = g.gitCommit(g.unvetted, "Update record "+id) 1580 if err != nil { 1581 return err 1582 } 1583 1584 log.Tracef("Returning complete record: %v", id) 1585 // This is a bit inefficient but let's roll with it for now. 1586 return nil 1587 } 1588 1589 // wouldChange applies a diff into a repo and undoes that. The point of this 1590 // call is to determine if applying said diff would change the repo. 1591 // 1592 // This is a very expensive call. Only use this sparingly. 1593 // 1594 // Must be called WITHOUT the lock held. 1595 func (g *gitBackEnd) wouldChange(id string, mdAppend []backend.MetadataStream, mdOverwrite []backend.MetadataStream, fa []file, filesDel []string) (bool, error) { 1596 idTmp := id + "_rm" 1597 _ = g.gitBranchDelete(g.unvetted, idTmp) // Delete it just in case 1598 err := g.gitNewBranch(g.unvetted, idTmp) 1599 if err != nil { 1600 return false, err 1601 } 1602 1603 var rv bool 1604 err = g._updateRecord(false, id, mdAppend, mdOverwrite, fa, filesDel) 1605 if errors.Is(err, backend.ErrChangesRecord) { 1606 rv = true 1607 } 1608 return rv, g.gitUnwindBranch(g.unvetted, idTmp) 1609 } 1610 1611 // updateRecord puts the correct git repo in the correct state (branch or 1612 // master) and then updates the the record content. It returns a version if an 1613 // update occurred on master. 1614 // 1615 // Must be called WITHOUT the lock held. 1616 func (g *gitBackEnd) updateRecord(token []byte, mdAppend []backend.MetadataStream, mdOverwrite []backend.MetadataStream, filesAdd []backend.File, filesDel []string, master bool) (*backend.Record, error) { 1617 log.Tracef("updateRecord: %x", token) 1618 1619 // Send in a single metadata array to verify there are no dups. 1620 allMD := append(mdAppend, mdOverwrite...) 1621 fa, err := verifyContent(allMD, filesAdd, filesDel) 1622 if err != nil { 1623 var e backend.ContentVerificationError 1624 if !errors.As(err, &e) { 1625 return nil, err 1626 } 1627 // Allow ErrorStatusEmpty 1628 if e.ErrorCode != pd.ErrorStatusEmpty { 1629 return nil, err 1630 } 1631 } 1632 1633 // Lock filesystem 1634 g.Lock() 1635 defer g.Unlock() 1636 if g.shutdown { 1637 return nil, backend.ErrShutdown 1638 } 1639 1640 // git checkout master 1641 err = g.gitCheckout(g.unvetted, "master") 1642 if err != nil { 1643 return nil, err 1644 } 1645 1646 // git pull --ff-only --rebase 1647 err = g.gitPull(g.unvetted, true) 1648 if err != nil { 1649 return nil, err 1650 } 1651 1652 id := hex.EncodeToString(token) 1653 if master { 1654 // Vetted path 1655 1656 // Check to make sure this prop is vetted 1657 dir := pijoin(g.unvetted, id) 1658 _, err = os.Stat(dir) 1659 if err != nil { 1660 return nil, backend.ErrRecordNotFound 1661 } 1662 1663 // Make sure there are actually changes before we commence the 1664 // revision update 1665 1666 // We got a new revision, do the work 1667 change, err := g.wouldChange(id, mdAppend, mdOverwrite, fa, 1668 filesDel) 1669 if err != nil { 1670 return nil, err 1671 } 1672 if !change { 1673 return nil, backend.ErrNoChanges 1674 } 1675 1676 // Get old and new version 1677 oldV, newV, err := getNext(dir) 1678 if err != nil { 1679 return nil, err 1680 } 1681 1682 // Checkout temporary branch 1683 idTmp := id + "_tmp" 1684 _ = g.gitBranchDelete(g.unvetted, idTmp) // Delete leftovers 1685 err = g.gitNewBranch(g.unvetted, idTmp) 1686 if err != nil { 1687 return nil, err 1688 } 1689 1690 // Copy entire prop 1691 log.Debugf("cp %v, %v", pijoin(g.unvetted, id, oldV), 1692 pijoin(g.unvetted, id, newV)) 1693 err = filesystem.Copy(pijoin(g.unvetted, id, oldV), 1694 pijoin(g.unvetted, id, newV)) 1695 if err != nil { 1696 return nil, err 1697 } 1698 1699 // We need to add the new path here so that git rm can delete a 1700 // known file 1701 err = g.gitAdd(g.unvetted, pijoin(g.unvetted, id, newV)) 1702 if err != nil { 1703 return nil, err 1704 } 1705 1706 // defer branch delete 1707 log.Debugf("updating vetted %v -> %v %v", oldV, newV, id) 1708 1709 // Do the work, if there is an error we must unwind git. 1710 errReturn := g._updateRecord(true, id, mdAppend, mdOverwrite, 1711 fa, filesDel) 1712 if errReturn == nil { 1713 // Success path 1714 1715 // create and rebase PR 1716 err = g.rebasePR(idTmp) 1717 if err != nil { 1718 return nil, err 1719 } 1720 1721 // g.vetted is correct! 1722 return g.getRecord(token, "", g.vetted, true) 1723 } 1724 1725 // git stash 1726 err = g.gitUnwindBranch(g.unvetted, idTmp) 1727 if err != nil { 1728 // We are in trouble! Consider a panic. 1729 log.Criticalf("update vetted record unwind: %v", err) 1730 } 1731 1732 return nil, errReturn 1733 } 1734 1735 // Unvetted path 1736 1737 // Check to make sure this prop is not vetted 1738 _, err = os.Stat(pijoin(g.unvetted, id)) 1739 if err == nil { 1740 return nil, backend.ErrRecordFound 1741 } 1742 1743 // Checkout branch 1744 _, err = g.checkoutRecordBranch(id) 1745 if err != nil { 1746 return nil, err 1747 } 1748 1749 // We now are sitting in branch id 1750 log.Debugf("updating unvetted %v", id) 1751 1752 // Do the work, if there is an error we must unwind git. 1753 errReturn := g._updateRecord(true, id, mdAppend, mdOverwrite, fa, 1754 filesDel) 1755 if errReturn == nil { 1756 // success 1757 return g.getRecord(token, "", g.unvetted, true) 1758 } 1759 1760 // git stash 1761 err = g.gitUnwind(g.unvetted) 1762 if err != nil { 1763 log.Criticalf("update unvetted record unwind: %v", err) 1764 } 1765 1766 // git checkout master 1767 err = g.gitCheckout(g.unvetted, "master") 1768 if err != nil { 1769 log.Criticalf("update unvetted record checkout master: %v", err) 1770 } 1771 1772 return nil, errReturn 1773 } 1774 1775 // UpdateVettedRecord updates the vetted record. 1776 // 1777 // This function is part of the interface. 1778 func (g *gitBackEnd) UpdateVettedRecord(token []byte, mdAppend []backend.MetadataStream, mdOverwrite []backend.MetadataStream, filesAdd []backend.File, filesDel []string) (*backend.Record, error) { 1779 log.Tracef("UpdateVettedRecord %x", token) 1780 return g.updateRecord(token, mdAppend, mdOverwrite, filesAdd, filesDel, 1781 true) 1782 } 1783 1784 // UpdateUnvettedRecord updates the unvetted record. 1785 // 1786 // This function is part of the interface. 1787 func (g *gitBackEnd) UpdateUnvettedRecord(token []byte, mdAppend []backend.MetadataStream, mdOverwrite []backend.MetadataStream, filesAdd []backend.File, filesDel []string) (*backend.Record, error) { 1788 log.Tracef("UpdateUnvettedRecord %x", token) 1789 return g.updateRecord(token, mdAppend, mdOverwrite, filesAdd, filesDel, 1790 false) 1791 } 1792 1793 // updateVettedMetadata updates metadata in the unvetted repo and pushes it 1794 // upstream followed by a rebase. Record is not updated. 1795 // This function must be called with the lock held. 1796 func (g *gitBackEnd) updateVettedMetadata(id, idTmp string, mdAppend []backend.MetadataStream, mdOverwrite []backend.MetadataStream) error { 1797 _ = g.gitBranchDelete(g.unvetted, idTmp) // Delete leftovers 1798 1799 // Checkout temporary branch 1800 err := g.gitNewBranch(g.unvetted, idTmp) 1801 if err != nil { 1802 return err 1803 } 1804 1805 // Update metadata changes 1806 err = g.updateMetadata(id, mdAppend, mdOverwrite) 1807 if err != nil { 1808 return err 1809 } 1810 1811 // If there are no changes DO NOT update the record and reply with no 1812 // changes. 1813 if !g.gitHasChanges(g.unvetted) { 1814 return backend.ErrNoChanges 1815 } 1816 1817 // Commit change 1818 err = g.gitCommit(g.unvetted, "Update record metadata "+id) 1819 if err != nil { 1820 return err 1821 } 1822 1823 // create and rebase PR 1824 return g.rebasePR(idTmp) 1825 } 1826 1827 // _updateVettedMetadata updates metadata in vetted record. It goes through 1828 // the normal stages of updating unvetted, pushing PR, merge PR, pull remote. 1829 // Note that the content must have been validated before this call. Record 1830 // itself is not changed. 1831 // 1832 // This function must be called with the lock held. 1833 func (g *gitBackEnd) _updateVettedMetadata(token []byte, mdAppend []backend.MetadataStream, mdOverwrite []backend.MetadataStream) error { 1834 // git checkout master 1835 err := g.gitCheckout(g.unvetted, "master") 1836 if err != nil { 1837 return err 1838 } 1839 1840 // git pull --ff-only --rebase 1841 err = g.gitPull(g.unvetted, true) 1842 if err != nil { 1843 return err 1844 } 1845 1846 // Check if temporary branch exists (should never be the case) 1847 id := hex.EncodeToString(token) 1848 idTmp := id + "_tmp" 1849 1850 // Make sure vetted exists 1851 _, err = os.Stat(pijoin(g.unvetted, id)) 1852 if err != nil { 1853 if os.IsNotExist(err) { 1854 return backend.ErrRecordNotFound 1855 } 1856 } 1857 1858 // Make sure record is not locked. 1859 md, err := loadMD(g.unvetted, id, "") 1860 if err != nil { 1861 return err 1862 } 1863 if md.Status == backend.MDStatusArchived { 1864 return backend.ErrRecordArchived 1865 } 1866 1867 log.Debugf("updating vetted metadata %x", token) 1868 1869 // Do the work, if there is an error we must unwind git. 1870 err = g.updateVettedMetadata(id, idTmp, mdAppend, mdOverwrite) 1871 if err != nil { 1872 err2 := g.gitUnwindBranch(g.unvetted, idTmp) 1873 if err2 != nil { 1874 // We are in trouble! Consider a panic. 1875 log.Criticalf("updateVettedMetadata: %v", err2) 1876 } 1877 return err 1878 } 1879 1880 return nil 1881 } 1882 1883 // UpdateVettedMetadata updates metadata in vetted record. It goes through the 1884 // normal stages of updating unvetted, pushing PR, merge PR, pull remote. 1885 // Record itself is not changed. 1886 // 1887 // This function must be called without the lock held. 1888 func (g *gitBackEnd) UpdateVettedMetadata(token []byte, mdAppend []backend.MetadataStream, mdOverwrite []backend.MetadataStream) error { 1889 log.Tracef("UpdateVettedMetadata: %x", token) 1890 1891 // Send in a single metadata array to verify there are no dups. 1892 allMD := append(mdAppend, mdOverwrite...) 1893 _, err := verifyContent(allMD, []backend.File{}, []string{}) 1894 if err != nil { 1895 var e backend.ContentVerificationError 1896 if !errors.As(err, &e) { 1897 return err 1898 } 1899 // Allow ErrorStatusEmpty 1900 if e.ErrorCode != pd.ErrorStatusEmpty { 1901 return err 1902 } 1903 } 1904 1905 // Lock filesystem 1906 g.Lock() 1907 defer g.Unlock() 1908 if g.shutdown { 1909 return backend.ErrShutdown 1910 } 1911 1912 return g._updateVettedMetadata(token, mdAppend, mdOverwrite) 1913 } 1914 1915 // updateMetadata describes a metadata update for a specific record. This 1916 // struct is used when the metadata of multiple records needs to be updated 1917 // atomically. A []updateMetadata can be passed in to represent multiple 1918 // metadata updates. 1919 type updateMetadata struct { 1920 token string 1921 mdAppend []backend.MetadataStream 1922 mdOverwrite []backend.MetadataStream 1923 } 1924 1925 // _updateVettedMetadataMulti updates metadata of multiple vetted record. It 1926 // goes through the normal stages of updating unvetted, pushing PR, merge PR, 1927 // pull remote. Note that the content must have been validated before this 1928 // call. Record itself is not changed. 1929 // 1930 // This function must be called with the lock held. 1931 func (g *gitBackEnd) updateVettedMetadataMulti(um []updateMetadata, idTmp string) error { 1932 // Checkout temporary branch 1933 err := g.gitNewBranch(g.unvetted, idTmp) 1934 if err != nil { 1935 return err 1936 } 1937 1938 // Update metadata changes 1939 for _, v := range um { 1940 log.Debugf("update vetted metadata: %v", v.token) 1941 err = g.updateMetadata(v.token, v.mdAppend, v.mdOverwrite) 1942 if err != nil { 1943 return err 1944 } 1945 } 1946 1947 // If there are no changes DO NOT update the record 1948 if !g.gitHasChanges(g.unvetted) { 1949 return backend.ErrNoChanges 1950 } 1951 1952 // Commit changes 1953 msg := "Update record metadata multi " 1954 for _, v := range um { 1955 // '-m' puts the token on a new line 1956 msg += "-m " + v.token 1957 } 1958 err = g.gitCommit(g.unvetted, msg) 1959 if err != nil { 1960 return err 1961 } 1962 1963 // Create and rebase 1964 return g.rebasePR(idTmp) 1965 } 1966 1967 // _updateVettedMetadataMulti updates metadata of multiple vetted record. It 1968 // goes through the normal stages of updating unvetted, pushing PR, merge PR, 1969 // pull remote. Note that the content must have been validated before this 1970 // call. Record itself is not changed. If any parts of the update fail then 1971 // all work is unwound. 1972 // 1973 // This function must be called WITH the lock held. 1974 func (g *gitBackEnd) _updateVettedMetadataMulti(um []updateMetadata, idTmp string) error { 1975 if len(um) == 0 { 1976 return backend.ErrNoChanges 1977 } 1978 1979 // git checkout master 1980 err := g.gitCheckout(g.unvetted, "master") 1981 if err != nil { 1982 return err 1983 } 1984 1985 // git pull --ff-only --rebase 1986 err = g.gitPull(g.unvetted, true) 1987 if err != nil { 1988 return err 1989 } 1990 1991 // Ensure none of the records have been archived 1992 for _, v := range um { 1993 md, err := loadMD(g.unvetted, v.token, "") 1994 if err != nil { 1995 return err 1996 } 1997 if md.Status == backend.MDStatusArchived { 1998 return backend.ErrRecordArchived 1999 } 2000 } 2001 2002 // Do the work, if there is an error we must unwind git. 2003 err = g.updateVettedMetadataMulti(um, idTmp) 2004 if err != nil { 2005 err2 := g.gitUnwindBranch(g.unvetted, idTmp) 2006 if err2 != nil { 2007 // We are in trouble! Consider a panic. 2008 log.Criticalf("updateVettedMetadataMulti: %v", err2) 2009 } 2010 return err 2011 } 2012 2013 return nil 2014 } 2015 2016 // _updateReadme updates the README.md file in the unvetted repo, then 2017 // does a commit. This function must be called WITH the lock 2018 // held, and must be wrapped with a function that puts the repo 2019 // into the proper state and unwinds it in case something goes wrong. 2020 func (g *gitBackEnd) _updateReadme(content string) error { 2021 // Update readme file 2022 filename := pijoin(g.unvetted, "README.md") 2023 err := os.WriteFile(filename, []byte(content), 0664) 2024 if err != nil { 2025 return err 2026 } 2027 2028 // If there are no changes, do not continue 2029 if !g.gitHasChanges(g.unvetted) { 2030 return backend.ErrNoChanges 2031 } 2032 2033 // Add readme file 2034 err = g.gitAdd(g.unvetted, "README.md") 2035 if err != nil { 2036 return err 2037 } 2038 2039 // Commit change 2040 return g.gitCommit(g.unvetted, "Update README.md") 2041 } 2042 2043 // updateReadme updates the README.md file in the unvetted repo 2044 // then rebases the change and pushes it to the vetted repo. If 2045 // anything goes wrong it unwinds the changes are returns the repo 2046 // to master. 2047 // This function must be called WITH the lock held. 2048 func (g *gitBackEnd) updateReadme(content string) error { 2049 const tmpBranch = "updateReadmeTmp" 2050 2051 // Delete old temporary branch if it exists 2052 g.gitBranchDelete(g.unvetted, tmpBranch) 2053 2054 // Checkout temporary branch 2055 err := g.gitNewBranch(g.unvetted, tmpBranch) 2056 if err != nil { 2057 return err 2058 } 2059 2060 err2 := g._updateReadme(content) 2061 if err2 != nil { 2062 // Unwind and complain 2063 err := g.gitUnwindBranch(g.unvetted, tmpBranch) 2064 if err != nil { 2065 // We are in trouble and should consider a panic 2066 log.Criticalf("updateReadme: %v", err) 2067 } 2068 return err2 2069 } 2070 2071 // create and rebase PR 2072 return g.rebasePR(tmpBranch) 2073 } 2074 2075 // UpdateReadme updates the README.md file in the unvetted repo, 2076 // then rebases the change and pushes it to the vetted repo. 2077 // This function must be called WITHOUT the lock held. 2078 // 2079 // UpdateReadme satisfies the backend interface. 2080 func (g *gitBackEnd) UpdateReadme(content string) error { 2081 log.Tracef("UpdateReadme") 2082 2083 // Lock filesystem 2084 g.Lock() 2085 defer g.Unlock() 2086 if g.shutdown { 2087 return backend.ErrShutdown 2088 } 2089 // git checkout master 2090 err := g.gitCheckout(g.unvetted, "master") 2091 if err != nil { 2092 return err 2093 } 2094 2095 // git pull --ff-only --rebase 2096 err = g.gitPull(g.unvetted, true) 2097 if err != nil { 2098 return err 2099 } 2100 2101 return g.updateReadme(content) 2102 } 2103 2104 // getRecordLock is the generic implementation of GetUnvetted/GetVetted. It 2105 // returns a record record from the provided repo. 2106 // 2107 // This function must be called WITHOUT the lock held. 2108 func (g *gitBackEnd) getRecordLock(token []byte, version, repo string, includeFiles bool) (*backend.Record, error) { 2109 // Lock filesystem 2110 g.Lock() 2111 defer g.Unlock() 2112 if g.shutdown { 2113 return nil, backend.ErrShutdown 2114 } 2115 2116 return g.getRecord(token, version, repo, includeFiles) 2117 } 2118 2119 // _getRecord loads a record from the current branch on the provided repo. 2120 // 2121 // This function must be called WITH the lock held. 2122 func (g *gitBackEnd) _getRecord(id, version, repo string, includeFiles bool) (*backend.Record, error) { 2123 // Use latestVersion if version isn't specified 2124 if version == "" { 2125 latestVersion, err := getLatest(pijoin(repo, id)) 2126 if err != nil { 2127 return nil, err 2128 } 2129 version = latestVersion 2130 } 2131 2132 // load MD 2133 brm, err := loadMD(repo, id, version) 2134 if err != nil { 2135 return nil, err 2136 } 2137 2138 // load metadata streams 2139 mds, err := loadMDStreams(repo, id, version) 2140 if err != nil { 2141 return nil, err 2142 } 2143 2144 var files []backend.File 2145 if includeFiles { 2146 // load files 2147 files, err = loadRecord(repo, id, version) 2148 if err != nil { 2149 return nil, err 2150 } 2151 } 2152 2153 return &backend.Record{ 2154 RecordMetadata: *brm, 2155 Version: version, 2156 Metadata: mds, 2157 Files: files, 2158 }, nil 2159 } 2160 2161 // getRecord is the generic implementation of GetUnvetted/GetVetted. It 2162 // returns a record record from the provided repo. 2163 // 2164 // This function must be called WITH the lock held. 2165 func (g *gitBackEnd) getRecord(token []byte, version, repo string, includeFiles bool) (*backend.Record, error) { 2166 log.Tracef("getRecord: %x", token) 2167 2168 id := hex.EncodeToString(token) 2169 if repo == g.unvetted { 2170 // git checkout id 2171 err := g.gitCheckout(repo, id) 2172 if err != nil { 2173 return nil, backend.ErrRecordNotFound 2174 } 2175 branchNow, err := g.gitBranchNow(repo) 2176 if err != nil || branchNow != id { 2177 return nil, backend.ErrRecordNotFound 2178 } 2179 } 2180 defer func() { 2181 // git checkout master 2182 err := g.gitCheckout(repo, "master") 2183 if err != nil { 2184 log.Errorf("could not switch to master: %v", err) 2185 } 2186 }() 2187 2188 return g._getRecord(id, version, repo, includeFiles) 2189 } 2190 2191 // fsck performs a git fsck and additionally it validates the git tree against 2192 // dcrtime. This is an expensive operation and should not be run during 2193 // runtime. 2194 // 2195 // This function must be called WITH holding the lock. 2196 func (g *gitBackEnd) fsck(path string) error { 2197 // obtain all commit digests and verify them. We don't store anchor 2198 // confirmations so we have to skip those. 2199 out, err := g.git(path, "log", "--pretty=oneline") 2200 if err != nil { 2201 return err 2202 } 2203 if len(out) == 0 { 2204 return fmt.Errorf("invalid git output") 2205 } 2206 2207 var seenAnchor bool 2208 // gitDigests is an index of all git digests to verify with dcrtime 2209 gitDigests := make(map[string]struct{}) 2210 // confirmedAnchors keeps track of anchors that were timestamped with dcrtime but not verified, 2211 // since periodicAnchorChecker only checks recent unconfirmed anchors and ignores older ones 2212 confirmedAnchors := make(map[string]struct{}) 2213 var unconfirmedAnchors []string 2214 for _, v := range out { 2215 if regexAnchorConfirmation.MatchString(v) { 2216 // Store confirmed anchor merkle roots to look up later 2217 merkleRoot := regexAnchorConfirmation.FindStringSubmatch(v)[1] 2218 confirmedAnchors[merkleRoot] = struct{}{} 2219 continue 2220 } else if regexAnchor.MatchString(v) { 2221 // We now have seen an Anchor commit. The following digests are now precious. 2222 seenAnchor = true 2223 // We should have seen its confirmation already, since we're parsing top to bottom 2224 // If we didn't, save the anchor key to verify with dcrtime later 2225 merkleRoot := regexAnchor.FindStringSubmatch(v)[1] 2226 _, confirmed := confirmedAnchors[merkleRoot] 2227 if !confirmed { 2228 unconfirmedAnchors = append(unconfirmedAnchors, merkleRoot) 2229 } 2230 continue 2231 } 2232 if !seenAnchor { 2233 // We have not seen an Anchor yet so this digest is not 2234 // precious. 2235 continue 2236 } 2237 // git output is digest followed by one liner commit message 2238 s := strings.SplitN(v, " ", 2) 2239 if len(s) != 2 { 2240 log.Infof("%v", spew.Sdump(s)) 2241 return fmt.Errorf("unexpected split: %v", v) 2242 } 2243 ds, err := extendSHA1FromString(s[0]) 2244 if err != nil { 2245 return fmt.Errorf("not a digest: %v", v) 2246 } 2247 if _, ok := gitDigests[ds]; ok { 2248 return fmt.Errorf("duplicate git digest: %v", ds) 2249 } 2250 gitDigests[ds] = struct{}{} 2251 } 2252 2253 if len(gitDigests) == 0 { 2254 log.Infof("fsck: nothing to do") 2255 return nil 2256 } 2257 2258 log.Infof("fsck: dcrtime verification started") 2259 2260 // Verify the unconfirmed anchors 2261 vrs := make([]v1.VerifyDigest, 0, len(unconfirmedAnchors)) 2262 for _, merkleRoot := range unconfirmedAnchors { 2263 vr, err := g.verifyAnchor(merkleRoot) 2264 if err != nil { 2265 log.Errorf("Error verifying anchor during fsck: %v", err) 2266 continue 2267 } else { 2268 vrs = append(vrs, *vr) 2269 } 2270 } 2271 2272 err = g.afterAnchorVerify(vrs) 2273 if err != nil { 2274 return err 2275 } 2276 2277 // Now we should be able to verify all the precious git digests 2278 digests := make([]string, 0, len(gitDigests)) 2279 for d := range gitDigests { 2280 digests = append(digests, d) 2281 } 2282 vr, err := verifyTimestamp("politeia", g.dcrtimeHost, digests) 2283 if err != nil { 2284 return err 2285 } 2286 2287 // Verify all results 2288 var fail bool 2289 for _, v := range vr.Digests { 2290 if v.Result != v1.ResultOK { 2291 fail = true 2292 log.Errorf("dcrtime error: %v %v %v", v.Digest, 2293 v.Result, v1.Result[v.Result]) 2294 } 2295 } 2296 if fail { 2297 return fmt.Errorf("dcrtime fsck failed") 2298 } 2299 2300 return nil 2301 } 2302 2303 // UnvettedExists returns whether the given token corresponds to a record in 2304 // the unvetted repo. 2305 // 2306 // UnvettedExists satisfies the backend interface. 2307 func (g *gitBackEnd) UnvettedExists(token []byte) bool { 2308 log.Tracef("UnvettedExists %x", token) 2309 2310 // Unvetted records exists as branches in the unvetted repo where 2311 // the branch name is the record token. 2312 branches, err := g.gitBranches(g.unvetted) 2313 if err != nil { 2314 return false 2315 } 2316 t := hex.EncodeToString(token) 2317 for _, v := range branches { 2318 if v == t { 2319 return true 2320 } 2321 } 2322 2323 return false 2324 } 2325 2326 // VettedExists returns whether the given token corresponds to a record in 2327 // the vetted repo. 2328 // 2329 // VettedExists satisfies the backend interface. 2330 func (g *gitBackEnd) VettedExists(token []byte) bool { 2331 log.Tracef("VettedExists %x", token) 2332 _, err := os.Stat(pijoin(g.vetted, hex.EncodeToString(token))) 2333 return err == nil 2334 } 2335 2336 // UnvettedTokens returns the censorship record token of all unvetted records 2337 // in the backend. 2338 func (g *gitBackEnd) UnvettedTokens() ([][]byte, error) { 2339 log.Tracef("UnvettedTokens") 2340 2341 g.Lock() 2342 defer g.Unlock() 2343 if g.shutdown { 2344 return nil, backend.ErrShutdown 2345 } 2346 2347 tokens, err := g.getUnvettedTokens() 2348 if err != nil { 2349 return nil, err 2350 } 2351 2352 tokensb := make([][]byte, 0, len(tokens)) 2353 for _, v := range tokens { 2354 b, err := hex.DecodeString(v) 2355 if err != nil { 2356 return nil, err 2357 } 2358 tokensb = append(tokensb, b) 2359 } 2360 2361 return tokensb, nil 2362 } 2363 2364 // VettedTokens returns the censorship record token of all vetted records in 2365 // the backend. 2366 func (g *gitBackEnd) VettedTokens() ([][]byte, error) { 2367 log.Tracef("VettedTokens") 2368 2369 g.Lock() 2370 defer g.Unlock() 2371 if g.shutdown { 2372 return nil, backend.ErrShutdown 2373 } 2374 2375 tokens, err := g.getVettedTokens() 2376 if err != nil { 2377 return nil, err 2378 } 2379 2380 tokensb := make([][]byte, 0, len(tokens)) 2381 for _, v := range tokens { 2382 b, err := hex.DecodeString(v) 2383 if err != nil { 2384 return nil, err 2385 } 2386 tokensb = append(tokensb, b) 2387 } 2388 2389 return tokensb, nil 2390 } 2391 2392 // vettedMetadataStreamExists returns whether the given metadata stream exists. 2393 // 2394 // This function must be called with the read lock held. 2395 func (g *gitBackEnd) vettedMetadataStreamExists(token []byte, mdstreamID int) bool { 2396 fn := fmt.Sprintf("%02v%v", mdstreamID, defaultMDFilenameSuffix) 2397 dir := joinLatest(g.vetted, hex.EncodeToString(token)) 2398 _, err := os.Stat(pijoin(dir, fn)) 2399 return err == nil 2400 } 2401 2402 // GetUnvetted checks out branch token and returns the content of 2403 // unvetted/token directory. 2404 // 2405 // GetUnvetted satisfies the backend interface. 2406 func (g *gitBackEnd) GetUnvetted(token []byte) (*backend.Record, error) { 2407 log.Tracef("GetUnvetted %x", token) 2408 2409 return g.getRecordLock(token, "", g.unvetted, true) 2410 } 2411 2412 // GetVetted returns the content of vetted/token directory. 2413 // 2414 // GetVetted satisfies the backend interface. 2415 func (g *gitBackEnd) GetVetted(token []byte, version string) (*backend.Record, error) { 2416 log.Tracef("GetVetted %x %v", token, version) 2417 return g.getRecordLock(token, version, g.vetted, true) 2418 } 2419 2420 // getVettedMetadataStream returns a byte slice of the given metadata stream. 2421 // 2422 // This function must be called with the read lock held. 2423 func (g *gitBackEnd) getVettedMetadataStream(token []byte, mdstreamID int) ([]byte, error) { 2424 fn := fmt.Sprintf("%02v%v", mdstreamID, defaultMDFilenameSuffix) 2425 dir := joinLatest(g.vetted, hex.EncodeToString(token)) 2426 return os.ReadFile(pijoin(dir, fn)) 2427 } 2428 2429 // setUnvettedStatus takes various parameters to update a record metadata and 2430 // status. Note that this function must be wrapped by a function that delivers 2431 // the call with the unvetted repo sitting in master. The idea is that if this 2432 // function fails we can simply unwind it by calling a git stash. 2433 // Function must be called with the lock held. 2434 func (g *gitBackEnd) setUnvettedStatus(token []byte, status backend.MDStatusT, mdAppend, mdOverwrite []backend.MetadataStream) (*backend.Record, error) { 2435 // git checkout id 2436 id := hex.EncodeToString(token) 2437 err := g.gitCheckout(g.unvetted, id) 2438 if err != nil { 2439 return nil, backend.ErrRecordNotFound 2440 } 2441 2442 // Load record 2443 record, err := g._getRecord(id, "", g.unvetted, false) 2444 if err != nil { 2445 return nil, err 2446 } 2447 2448 // We only allow a transition from unvetted to vetted or censored 2449 switch { 2450 case (record.RecordMetadata.Status == backend.MDStatusUnvetted || 2451 record.RecordMetadata.Status == backend.MDStatusIterationUnvetted) && 2452 status == backend.MDStatusVetted: 2453 2454 // unvetted -> vetted 2455 2456 // Update MD first 2457 record.RecordMetadata.Status = backend.MDStatusVetted 2458 record.RecordMetadata.Iteration += 1 2459 record.RecordMetadata.Timestamp = time.Now().Unix() 2460 err = updateMD(g.unvetted, id, &record.RecordMetadata) 2461 if err != nil { 2462 return nil, err 2463 } 2464 2465 // Handle metadata 2466 err = g.updateMetadata(id, mdAppend, mdOverwrite) 2467 if err != nil { 2468 return nil, err 2469 } 2470 2471 // Commit brm 2472 err = g.commitMD(g.unvetted, id, "published") 2473 if err != nil { 2474 return nil, err 2475 } 2476 2477 // Create and rebase PR 2478 err = g.rebasePR(id) 2479 if err != nil { 2480 return nil, err 2481 } 2482 2483 case (record.RecordMetadata.Status == backend.MDStatusUnvetted || 2484 record.RecordMetadata.Status == backend.MDStatusIterationUnvetted) && 2485 status == backend.MDStatusCensored: 2486 // unvetted -> censored 2487 record.RecordMetadata.Status = backend.MDStatusCensored 2488 record.RecordMetadata.Iteration += 1 2489 record.RecordMetadata.Timestamp = time.Now().Unix() 2490 err = updateMD(g.unvetted, id, &record.RecordMetadata) 2491 if err != nil { 2492 return nil, err 2493 } 2494 2495 // Handle metadata 2496 err = g.updateMetadata(id, mdAppend, mdOverwrite) 2497 if err != nil { 2498 return nil, err 2499 } 2500 2501 // Commit brm 2502 err = g.commitMD(g.unvetted, id, "censored") 2503 if err != nil { 2504 return nil, err 2505 } 2506 default: 2507 return nil, backend.StateTransitionError{ 2508 From: record.RecordMetadata.Status, 2509 To: status, 2510 } 2511 } 2512 2513 return g._getRecord(id, "", g.unvetted, false) 2514 } 2515 2516 // SetUnvettedStatus tries to update the status for an unvetted record. It 2517 // returns the updated record if successful but without the Files component. 2518 // 2519 // SetUnvettedStatus satisfies the backend interface. 2520 func (g *gitBackEnd) SetUnvettedStatus(token []byte, status backend.MDStatusT, mdAppend, mdOverwrite []backend.MetadataStream) (*backend.Record, error) { 2521 // Lock filesystem 2522 g.Lock() 2523 defer g.Unlock() 2524 if g.shutdown { 2525 return nil, backend.ErrShutdown 2526 } 2527 2528 log.Debugf("setting status %v (%v) -> %x", status, 2529 backend.MDStatus[status], token) 2530 record, err := g.setUnvettedStatus(token, status, mdAppend, mdOverwrite) 2531 if err != nil { 2532 // XXX this needs to call the unwind function instead 2533 // git stash 2534 err2 := g.gitUnwind(g.unvetted) 2535 if err2 != nil { 2536 log.Criticalf("SetUnvettedStatus: unwind %v", err2) 2537 } 2538 err2 = g.gitCheckout(g.unvetted, "master") 2539 if err2 != nil { 2540 log.Criticalf("SetUnvettedStatus: checkout %v", err2) 2541 } 2542 return nil, err 2543 } 2544 2545 // git checkout master 2546 err = g.gitCheckout(g.unvetted, "master") 2547 if err != nil { 2548 return nil, err 2549 } 2550 2551 return record, nil 2552 } 2553 2554 // setVettedStatus takes various parameters to update a record metadata and 2555 // status. It goes through the normal stages of updating unvetted, pushing PR, 2556 // merge PR, pull remote. Note that this function must be wrapped by a function 2557 // that delivers the call with the unvetted repo sitting in master. The idea 2558 // is that if this function fails we can simply unwind it. 2559 // 2560 // setVettedStatus must be called with the lock held. 2561 func (g *gitBackEnd) _setVettedStatus(token []byte, status backend.MDStatusT, mdAppend, mdOverwrite []backend.MetadataStream) (*backend.Record, error) { 2562 // git checkout master 2563 err := g.gitCheckout(g.unvetted, "master") 2564 if err != nil { 2565 return nil, err 2566 } 2567 2568 // git pull --ff-only --rebase 2569 err = g.gitPull(g.unvetted, true) 2570 if err != nil { 2571 return nil, err 2572 } 2573 2574 // Make sure vetted exists 2575 id := hex.EncodeToString(token) 2576 _, err = os.Stat(pijoin(g.unvetted, id)) 2577 if err != nil { 2578 if os.IsNotExist(err) { 2579 return nil, backend.ErrRecordNotFound 2580 } 2581 } 2582 2583 // Make sure record is not locked. 2584 md, err := loadMD(g.unvetted, id, "") 2585 if err != nil { 2586 return nil, err 2587 } 2588 if md.Status == backend.MDStatusArchived { 2589 return nil, backend.ErrRecordArchived 2590 } 2591 2592 // Load record 2593 record, err := g._getRecord(id, "", g.unvetted, false) 2594 if err != nil { 2595 return nil, err 2596 } 2597 2598 // We only allow a transition from vetted to archived 2599 if record.RecordMetadata.Status != backend.MDStatusVetted || 2600 status != backend.MDStatusArchived { 2601 return nil, backend.StateTransitionError{ 2602 From: record.RecordMetadata.Status, 2603 To: status, 2604 } 2605 } 2606 2607 // Delete any leftover tmp branch. There shouldn't be one. 2608 idTmp := id + "_tmp" 2609 _ = g.gitBranchDelete(g.unvetted, idTmp) 2610 2611 // Checkout temporary branch 2612 err = g.gitNewBranch(g.unvetted, idTmp) 2613 if err != nil { 2614 return nil, err 2615 } 2616 2617 // Update MD first 2618 record.RecordMetadata.Status = backend.MDStatusArchived 2619 record.RecordMetadata.Iteration += 1 2620 record.RecordMetadata.Timestamp = time.Now().Unix() 2621 err = updateMD(g.unvetted, id, &record.RecordMetadata) 2622 if err != nil { 2623 return nil, err 2624 } 2625 2626 // Handle metadata 2627 err = g.updateMetadata(id, mdAppend, mdOverwrite) 2628 if err != nil { 2629 return nil, err 2630 } 2631 2632 // Commit changes 2633 err = g.commitMD(g.unvetted, id, "archived") 2634 if err != nil { 2635 return nil, err 2636 } 2637 2638 // Create and rebase PR 2639 err = g.rebasePR(idTmp) 2640 if err != nil { 2641 return nil, err 2642 } 2643 2644 return g._getRecord(id, "", g.unvetted, false) 2645 } 2646 2647 // SetVettedStatus tries to update the status for a vetted record. It returns 2648 // the updated record if successful but without the Files component. 2649 // 2650 // SetVettedStatus satisfies the backend interface. 2651 func (g *gitBackEnd) SetVettedStatus(token []byte, status backend.MDStatusT, mdAppend, mdOverwrite []backend.MetadataStream) (*backend.Record, error) { 2652 // Lock filesystem 2653 g.Lock() 2654 defer g.Unlock() 2655 if g.shutdown { 2656 return nil, backend.ErrShutdown 2657 } 2658 2659 log.Debugf("setting status %v (%v) -> %x", status, 2660 backend.MDStatus[status], token) 2661 record, err := g._setVettedStatus(token, status, mdAppend, mdOverwrite) 2662 if err != nil { 2663 err2 := g.gitUnwind(g.unvetted) 2664 if err2 != nil { 2665 log.Debugf("SetVettedStatus: unwind %v", err2) 2666 } 2667 err2 = g.gitCheckout(g.unvetted, "master") 2668 if err2 != nil { 2669 log.Criticalf("SetVettedStatus: checkout %v", err2) 2670 } 2671 return nil, err 2672 } 2673 2674 // git checkout master 2675 err = g.gitCheckout(g.unvetted, "master") 2676 if err != nil { 2677 return nil, err 2678 } 2679 2680 return record, nil 2681 } 2682 2683 // Inventory returns an inventory of vetted and unvetted records. If 2684 // includeFiles is set the content is also returned. 2685 func (g *gitBackEnd) Inventory(vettedCount, vettedStart, branchCount uint, includeFiles, allVersions bool) ([]backend.Record, []backend.Record, error) { 2686 log.Tracef("Inventory: %v %v %v %v", vettedCount, vettedStart, branchCount, includeFiles) 2687 2688 // Lock filesystem 2689 g.Lock() 2690 defer g.Unlock() 2691 if g.shutdown { 2692 return nil, nil, backend.ErrShutdown 2693 } 2694 2695 // Walk vetted, we can simply take the vetted directory and sort the 2696 // entries by time. 2697 files, err := os.ReadDir(g.vetted) 2698 if err != nil { 2699 return nil, nil, err 2700 } 2701 2702 fileNames := make([]string, 0, len(files)) 2703 for _, v := range files { 2704 if !util.IsDigest(v.Name()) { 2705 continue 2706 } 2707 fileNames = append(fileNames, v.Name()) 2708 } 2709 if vettedCount != 0 { 2710 switch { 2711 case uint(len(fileNames)) <= vettedStart: 2712 fileNames = []string{} 2713 case uint(len(fileNames[vettedStart:])) < vettedCount: 2714 fileNames = fileNames[vettedStart:] 2715 default: 2716 fileNames = fileNames[vettedStart : vettedStart+vettedCount] 2717 } 2718 } 2719 2720 // Strip non record directories 2721 pr := make([]backend.Record, 0, len(fileNames)) 2722 for _, id := range fileNames { 2723 ids, err := hex.DecodeString(id) 2724 if err != nil { 2725 return nil, nil, err 2726 } 2727 prv, err := g.getRecord(ids, "", g.vetted, includeFiles) 2728 if err != nil { 2729 return nil, nil, err 2730 } 2731 pr = append(pr, *prv) 2732 2733 if allVersions { 2734 // Include all versions of the proposal 2735 latest, err := strconv.Atoi(prv.Version) 2736 if err != nil { 2737 return nil, nil, err 2738 } 2739 for i := 1; i < latest; i++ { 2740 r, err := g.getRecord(ids, strconv.Itoa(i), g.vetted, includeFiles) 2741 if err != nil { 2742 return nil, nil, err 2743 } 2744 pr = append(pr, *r) 2745 } 2746 } 2747 } 2748 // Walk Branches on unvetted 2749 branches, err := g.gitBranches(g.unvetted) 2750 if err != nil { 2751 return nil, nil, err 2752 } 2753 br := make([]backend.Record, 0, len(branches)) 2754 for _, id := range branches { 2755 if !util.IsDigest(id) { 2756 continue 2757 } 2758 2759 ids, err := hex.DecodeString(id) 2760 if err != nil { 2761 return nil, nil, err 2762 } 2763 pru, err := g.getRecord(ids, "", g.unvetted, includeFiles) 2764 if err != nil { 2765 // We probably should not fail the entire call 2766 return nil, nil, err 2767 } 2768 br = append(br, *pru) 2769 } 2770 2771 return pr, br, nil 2772 } 2773 2774 // GetPlugins returns a list of currently supported plugins and their settings. 2775 // 2776 // GetPlugins satisfies the backend interface. 2777 func (g *gitBackEnd) GetPlugins() ([]backend.Plugin, error) { 2778 log.Tracef("GetPlugins") 2779 return g.plugins, nil 2780 } 2781 2782 // Plugin send a passthrough command. The return values are: incomming command 2783 // identifier, encoded command result and an error if the command failed to 2784 // execute. 2785 // 2786 // Plugin satisfies the backend interface. 2787 func (g *gitBackEnd) Plugin(command, payload string) (string, string, error) { 2788 log.Tracef("Plugin: %v", command) 2789 switch command { 2790 case decredplugin.CmdBestBlock: 2791 payload, err := g.pluginBestBlock() 2792 return decredplugin.CmdBestBlock, payload, err 2793 case decredplugin.CmdNewComment: 2794 payload, err := g.pluginNewComment(payload) 2795 return decredplugin.CmdNewComment, payload, err 2796 case decredplugin.CmdCensorComment: 2797 payload, err := g.pluginCensorComment(payload) 2798 return decredplugin.CmdCensorComment, payload, err 2799 case decredplugin.CmdGetComments: 2800 payload, err := g.pluginGetComments(payload) 2801 return decredplugin.CmdGetComments, payload, err 2802 case cmsplugin.CmdInventory: 2803 payload, err := g.pluginCMSInventory() 2804 return cmsplugin.CmdInventory, payload, err 2805 case cmsplugin.CmdStartVote: 2806 payload, err := g.pluginStartDCCVote(payload) 2807 return cmsplugin.CmdStartVote, payload, err 2808 case cmsplugin.CmdCastVote: 2809 payload, err := g.pluginCastVote(payload) 2810 return cmsplugin.CmdCastVote, payload, err 2811 case cmsplugin.CmdDCCVoteResults: 2812 payload, err := g.pluginDCCVoteResults(payload) 2813 return cmsplugin.CmdDCCVoteResults, payload, err 2814 case cmsplugin.CmdVoteDetails: 2815 payload, err := g.pluginDCCVoteDetails(payload) 2816 return cmsplugin.CmdVoteDetails, payload, err 2817 case cmsplugin.CmdVoteSummary: 2818 payload, err := g.pluginDCCVoteSummary(payload) 2819 return cmsplugin.CmdVoteSummary, payload, err 2820 } 2821 return "", "", fmt.Errorf("invalid payload command") // XXX this needs to become a type error 2822 } 2823 2824 // Close shuts down the backend. It obtains the lock and sets the shutdown 2825 // boolean to true. All interface functions MUST return with errShutdown if 2826 // the backend is shutting down. 2827 // 2828 // Close satisfies the backend interface. 2829 func (g *gitBackEnd) Close() { 2830 log.Tracef("Close") 2831 2832 g.Lock() 2833 defer g.Unlock() 2834 2835 g.shutdown = true 2836 close(g.exit) 2837 } 2838 2839 // newLocked runs the portion of new that has to be locked. 2840 func (g *gitBackEnd) newLocked() error { 2841 g.Lock() 2842 defer g.Unlock() 2843 2844 // Ensure git works 2845 version, err := g.gitVersion() 2846 if err != nil { 2847 return err 2848 } 2849 2850 log.Infof("Git version: %v", version) 2851 2852 // Init vetted git repo 2853 err = g.gitInitRepo(g.vetted, defaultRepoConfig) 2854 if err != nil { 2855 return err 2856 } 2857 2858 // Clone vetted repo into unvetted 2859 err = g.gitClone(g.vetted, g.unvetted, defaultRepoConfig) 2860 if err != nil { 2861 return err 2862 } 2863 2864 // Fsck _o/ 2865 log.Infof("Running git fsck on vetted repository") 2866 _, err = g.gitFsck(g.vetted) 2867 if err != nil { 2868 return err 2869 } 2870 log.Infof("Running git fsck on unvetted repository") 2871 _, err = g.gitFsck(g.unvetted) 2872 if err != nil { 2873 return err 2874 } 2875 2876 return g.populateTokenPrefixCache() 2877 } 2878 2879 // rebasePR pushes branch id into upstream (vetted repo) and rebases it onto 2880 // master followed by replaying the rebase into origin (unvetted repo). 2881 // This function must be called with the lock held. 2882 func (g *gitBackEnd) rebasePR(id string) error { 2883 // on unvetted repo: 2884 // git checkout master 2885 // git pull --ff--only --rebase 2886 // git checkout id 2887 // git rebase master 2888 // git push --set-upstream origin id 2889 // on vetted repo: 2890 // git rebase id 2891 // git branch -D id 2892 // on unvetted repo: 2893 // git checkout master 2894 // git branch -D id 2895 // git pull --ff-only 2896 2897 // 2898 // UNVETTED REPO CREATE PR 2899 // 2900 // git checkout master 2901 err := g.gitCheckout(g.unvetted, "master") 2902 if err != nil { 2903 return err 2904 } 2905 2906 // git pull --ff-only --rebase 2907 err = g.gitPull(g.unvetted, true) 2908 if err != nil { 2909 return err 2910 } 2911 2912 // git checkout id 2913 err = g.gitCheckout(g.unvetted, id) 2914 if err != nil { 2915 return backend.ErrRecordNotFound 2916 } 2917 2918 // git rebase master 2919 err = g.gitRebase(g.unvetted, "master") 2920 if err != nil { 2921 return err 2922 } 2923 2924 // git push --set-upstream origin id 2925 err = g.gitPush(g.unvetted, "origin", id, true) 2926 if err != nil { 2927 return err 2928 } 2929 2930 // 2931 // VETTED REPO REPLAY BRANCH 2932 // 2933 2934 // git rebase id 2935 err = g.gitRebase(g.vetted, id) 2936 if err != nil { 2937 return err 2938 } 2939 2940 // git branch -D id 2941 err = g.gitBranchDelete(g.vetted, id) 2942 if err != nil { 2943 return err 2944 } 2945 2946 // 2947 // UNVETTED REPO SYNC 2948 // 2949 2950 // git checkout master 2951 err = g.gitCheckout(g.unvetted, "master") 2952 if err != nil { 2953 return err 2954 } 2955 2956 // git pull --ff-only --rebase 2957 err = g.gitPull(g.unvetted, true) 2958 if err != nil { 2959 return err 2960 } 2961 2962 // git branch -D id 2963 return g.gitBranchDelete(g.unvetted, id) 2964 } 2965 2966 // New returns a gitBackEnd context. It verifies that git is installed. 2967 func New(anp *chaincfg.Params, root string, dcrtimeHost string, gitPath string, id *identity.FullIdentity, gitTrace bool, dcrdataHost string) (*gitBackEnd, error) { 2968 2969 // Default to system git 2970 if gitPath == "" { 2971 gitPath = "git" 2972 } 2973 2974 g := &gitBackEnd{ 2975 activeNetParams: anp, 2976 root: root, 2977 cron: cron.New(), 2978 unvetted: filepath.Join(root, DefaultUnvettedPath), 2979 vetted: filepath.Join(root, DefaultVettedPath), 2980 journals: filepath.Join(root, DefaultJournalsPath), 2981 gitPath: gitPath, 2982 dcrtimeHost: dcrtimeHost, 2983 gitTrace: gitTrace, 2984 exit: make(chan struct{}), 2985 checkAnchor: make(chan struct{}), 2986 testAnchors: make(map[string]bool), 2987 prefixCache: make(map[string]struct{}), 2988 plugins: []backend.Plugin{getDecredPlugin(dcrdataHost)}, 2989 } 2990 2991 idJSON, err := id.Marshal() 2992 if err != nil { 2993 return nil, err 2994 } 2995 2996 // Register all plugins 2997 g.plugins = []backend.Plugin{ 2998 getDecredPlugin(dcrdataHost), 2999 getCMSPlugin(anp.Name != chaincfg.MainNetParams().Name), 3000 } 3001 3002 // Setup cms plugin 3003 setCMSPluginSetting(cmsPluginIdentity, string(idJSON)) 3004 setCMSPluginSetting(cmsPluginJournals, g.journals) 3005 3006 setDecredPluginSetting(decredPluginIdentity, string(idJSON)) 3007 setDecredPluginSetting(decredPluginJournals, g.journals) 3008 setDecredPluginHook(PluginPostHookEdit, g.decredPluginPostEdit) 3009 3010 // Create jounals path 3011 // XXX this needs to move into plugin init 3012 log.Infof("Journals directory: %v", g.journals) 3013 err = os.MkdirAll(g.journals, 0760) 3014 if err != nil { 3015 return nil, err 3016 } 3017 3018 g.journal = NewJournal() 3019 3020 // this function must be called after g.journal is created 3021 err = g.initDecredPluginJournals() 3022 if err != nil { 3023 return nil, err 3024 } 3025 3026 // this function must be called after g.journal is created 3027 err = g.initCMSPluginJournals() 3028 if err != nil { 3029 return nil, err 3030 } 3031 3032 err = g.newLocked() 3033 if err != nil { 3034 return nil, err 3035 } 3036 3037 // Launch anchor checker and don't do any work just yet. The 3038 // unanchored bits will be picked up during the next go-round. We 3039 // don't try to be clever in order to prevent dual commits for the same 3040 // anchor which can happen if the daemon is launched right around the 3041 // scheduled anchor drop. 3042 go g.periodicAnchorChecker() 3043 3044 // Launch cron. 3045 err = g.cron.AddFunc(anchorSchedule, func() { 3046 // Flush journals 3047 g.decredPluginJournalFlusher() 3048 g.cmsPluginJournalFlusher() 3049 3050 // Anchor commit 3051 g.anchorAllReposCronJob() 3052 }) 3053 if err != nil { 3054 return nil, err 3055 } 3056 g.cron.Start() 3057 3058 // Message user 3059 log.Infof("Timestamp host: %v", g.dcrtimeHost) 3060 3061 log.Infof("Running dcrtime fsck on vetted repository") 3062 err = g.fsck(g.vetted) 3063 if err != nil { 3064 // Log error but continue 3065 log.Errorf("fsck: dcrtime %v", err) 3066 } 3067 3068 return g, nil 3069 }