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  }