github.com/decred/politeia@v1.4.0/politeiad/backend/gitbe/anchors.go (about)

     1  // Copyright (c) 2017-2019 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  	"crypto/sha256"
     9  	"encoding/hex"
    10  	"fmt"
    11  	"regexp"
    12  	"strings"
    13  	"time"
    14  
    15  	"github.com/decred/dcrtime/merkle"
    16  )
    17  
    18  // An anchor corresponds to a set of git commit hashes, along with their
    19  // merkle root, that get checkpointed in dcrtime. This provides censorship
    20  // resistance by anchoring activity on politeia to the blockchain.
    21  //
    22  // To help process anchors, we need to look up the last anchor and unconfirmed anchors that
    23  // have not been checkpointed in dcrtime yet. To identify these, we parse the
    24  // git log, which keeps a record of all anchors dropped and anchors confirmed.
    25  
    26  // AnchorType discriminates between the various Anchor record types.
    27  type AnchorType uint32
    28  
    29  const (
    30  	AnchorInvalid    AnchorType = 0 // Invalid anchor
    31  	AnchorUnverified AnchorType = 1 // Unverified anchor
    32  	AnchorVerified   AnchorType = 2 // Verified anchor
    33  )
    34  
    35  type Anchor struct {
    36  	Type     AnchorType // Type of anchor this record represents
    37  	Time     int64      // OS time when record was created
    38  	Digests  [][]byte   // All digests that were merkled to get to key of record
    39  	Messages []string   // All one-line Commit messages
    40  	// len(Digests) == len(Messages) and index offsets are linked. e.g. Digests[15]
    41  	// commit messages is in Messages[15].
    42  }
    43  
    44  // LastAnchor stores the last commit anchored in dcrtime.
    45  type LastAnchor struct {
    46  	Last   []byte // Last git digest that was anchored
    47  	Time   int64  // OS time when record was created
    48  	Merkle []byte // Merkle root that points to Anchor record, if valid
    49  }
    50  
    51  // UnconfirmedAnchor stores Merkle roots of anchors that have not been confirmed
    52  // yet by dcrtime.
    53  type UnconfirmedAnchor struct {
    54  	Merkles [][]byte // List of Merkle root that points to Anchor records
    55  }
    56  
    57  // newAnchorRecord creates an Anchor Record and the Merkle Root from the
    58  // provided pieces.  Note that the merkle root is of the git digests!
    59  func newAnchorRecord(t AnchorType, digests []*[sha256.Size]byte, messages []string) (*Anchor, *[sha256.Size]byte, error) {
    60  	if len(digests) != len(messages) {
    61  		return nil, nil, fmt.Errorf("invalid digest and messages length")
    62  	}
    63  
    64  	if t == AnchorInvalid {
    65  		return nil, nil, fmt.Errorf("invalid anchor type")
    66  	}
    67  
    68  	a := Anchor{
    69  		Type:     t,
    70  		Messages: messages,
    71  		Digests:  make([][]byte, 0, len(digests)),
    72  		Time:     time.Now().Unix(),
    73  	}
    74  
    75  	for _, digest := range digests {
    76  		d := make([]byte, sha256.Size)
    77  		copy(d, digest[:])
    78  		a.Digests = append(a.Digests, d)
    79  	}
    80  
    81  	return &a, merkle.Root(digests), nil
    82  }
    83  
    84  type GitCommit struct {
    85  	Hash    string
    86  	Time    int64
    87  	Message []string
    88  	Error   error
    89  }
    90  
    91  var (
    92  	regexCommitHash           = regexp.MustCompile(`^commit\s+(\S+)`)
    93  	regexCommitDate           = regexp.MustCompile(`^Date:\s+(.+)`)
    94  	anchorConfirmationPattern = fmt.Sprintf(`\s*%s\s*(\S*)`, markerAnchorConfirmation)
    95  	regexAnchorConfirmation   = regexp.MustCompile(anchorConfirmationPattern)
    96  	anchorPattern             = fmt.Sprintf(`\s*%s\s*(\S*)`, markerAnchor)
    97  	regexAnchor               = regexp.MustCompile(anchorPattern)
    98  )
    99  
   100  const (
   101  	gitDateTemplate = "Mon Jan 2 15:04:05 2006 -0700"
   102  )
   103  
   104  // extractCommit takes a slice of a git log and parses the next commit into a GitCommit struct.
   105  func extractCommit(logSlice []string) (*GitCommit, int, error) {
   106  	var commit GitCommit
   107  
   108  	// Make sure we're at the start of a new commit
   109  	firstLine := logSlice[0]
   110  	if !regexCommitHash.MatchString(firstLine) {
   111  		return nil, 0, fmt.Errorf("Error parsing git log. Commit expected, found %q instead", firstLine)
   112  	}
   113  	commit.Hash = regexCommitHash.FindStringSubmatch(logSlice[0])[1]
   114  
   115  	// Skip the next line, which has the commit author
   116  
   117  	dateLine := logSlice[2]
   118  	if !regexCommitDate.MatchString(dateLine) {
   119  		return nil, 0, fmt.Errorf("Error parsing git log. Date expected, found %q instead", dateLine)
   120  	}
   121  	dateStr := regexCommitDate.FindStringSubmatch(logSlice[2])[1]
   122  	commitTime, err := time.Parse(gitDateTemplate, dateStr)
   123  	if err != nil {
   124  		return nil, 0, fmt.Errorf("Error parsing git log. Unable to parse date: %v", err)
   125  	}
   126  	commit.Time = commitTime.Unix()
   127  
   128  	// The first three lines are the commit hash, the author, and the date.
   129  	// The fourth is a blank line. Start accumulating the message at the 5th line.
   130  	// Append message lines until the start of the next commit is found.
   131  	for _, line := range logSlice[4:] {
   132  		if regexCommitHash.MatchString(line) {
   133  			break
   134  		}
   135  
   136  		commit.Message = append(commit.Message, line)
   137  	}
   138  
   139  	// In total, we used 4 lines initially, plus the number of lines in the message.
   140  	return &commit, len(commit.Message) + 4, nil
   141  }
   142  
   143  // Some helper functions to navigate git commit message bodies
   144  
   145  // anchorConfirmationMerkle extracts the Merkle Root from an anchor confirmation commit.
   146  func anchorConfirmationMerkle(commit *GitCommit) string {
   147  	anchorConfirmations := regexAnchorConfirmation.FindStringSubmatch(commit.Message[0])
   148  	if len(anchorConfirmations) < 2 {
   149  		return ""
   150  	}
   151  	return anchorConfirmations[1]
   152  }
   153  
   154  // anchorConfirmationMerkle extracts the Merkle Root from an anchor commit.
   155  func anchorCommitMerkle(commit *GitCommit) string {
   156  	return regexAnchor.FindStringSubmatch(commit.Message[0])[1]
   157  }
   158  
   159  // parseAnchorCommit returns a list of digest bytes from an anchor GitCommit,
   160  // as well as a list of commit messages for what was committed.
   161  func parseAnchorCommit(commit *GitCommit) ([][]byte, []string, error) {
   162  	// Make sure it is an anchor commit
   163  	firstLine := commit.Message[0]
   164  	if !regexAnchor.MatchString(firstLine) {
   165  		return nil, nil, fmt.Errorf("Error parsing git log. Expected an anchor commit. Instead got %q", firstLine)
   166  	}
   167  
   168  	// Hashes are listed starting from the 3rd line in the commit message
   169  	// The hash is the first word in the line. The commit message is the rest.
   170  	// Ignore the last blank line
   171  	var digests [][]byte
   172  	var messages []string
   173  	for _, line := range commit.Message[2 : len(commit.Message)-1] {
   174  		// The first word is the commit hash. The rest is the one-line commit message.
   175  		lineParts := strings.SplitN(line, " ", 2)
   176  		digest, err := hex.DecodeString(lineParts[0])
   177  		if err != nil {
   178  			return nil, nil, err
   179  		}
   180  		digests = append(digests, digest)
   181  		messages = append(messages, lineParts[1])
   182  	}
   183  
   184  	return digests, messages, nil
   185  }
   186  
   187  // readAnchorRecord matches an anchor by its Merkle root and retrieves it from the git log.
   188  func (g *gitBackEnd) readAnchorRecord(key [sha256.Size]byte) (*Anchor, error) {
   189  	// Get the git log
   190  	gitLog, err := g.gitLog(g.vetted)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  
   195  	// Iterate over commits to find the target anchor
   196  	keyStr := hex.EncodeToString(key[:])
   197  	anchorConfirmed := AnchorUnverified
   198  	currLine := 0
   199  	for currLine < len(gitLog) {
   200  		commit, linesUsed, err := extractCommit(gitLog[currLine:])
   201  		if err != nil {
   202  			return nil, err
   203  		}
   204  		currLine += linesUsed
   205  
   206  		// Check the first line to see if the commit matches the target
   207  		firstLine := commit.Message[0]
   208  		// If it is an anchor confirmation, mark the anchor as verified but
   209  		// keep looking for the main anchor commit
   210  		if regexAnchorConfirmation.MatchString(firstLine) &&
   211  			keyStr == anchorConfirmationMerkle(commit) {
   212  			anchorConfirmed = AnchorVerified
   213  		} else if regexAnchor.MatchString(firstLine) &&
   214  			keyStr == anchorCommitMerkle(commit) {
   215  			// Found the anchor
   216  			digests, messages, err := parseAnchorCommit(commit)
   217  			if err != nil {
   218  				return nil, err
   219  			}
   220  			return &Anchor{
   221  				Type:     anchorConfirmed,
   222  				Time:     commit.Time,
   223  				Digests:  digests,
   224  				Messages: messages,
   225  			}, nil
   226  		}
   227  	}
   228  
   229  	// Anchor wasn't found
   230  	return nil, fmt.Errorf("Anchor not found for key %v", key)
   231  }
   232  
   233  // readLastAnchorRecord retrieves the last anchor record.
   234  func (g *gitBackEnd) readLastAnchorRecord() (*LastAnchor, error) {
   235  	// Get the git log
   236  	gitLog, err := g.gitLog(g.vetted)
   237  	if err != nil {
   238  		return nil, err
   239  	}
   240  
   241  	// Iterate over commits to find the last anchor
   242  	var found bool
   243  	var la LastAnchor
   244  	var lastAnchorCommit *GitCommit
   245  	currLine := 0
   246  	for currLine < len(gitLog) {
   247  		commit, linesUsed, err := extractCommit(gitLog[currLine:])
   248  		if err != nil {
   249  			return nil, err
   250  		}
   251  		currLine += linesUsed
   252  
   253  		// Check the first line of the commit message
   254  		// Make sure it is an anchor, not an anchor confirmation
   255  		if !regexAnchorConfirmation.MatchString(commit.Message[0]) &&
   256  			regexAnchor.MatchString(commit.Message[0]) {
   257  			found = true
   258  			lastAnchorCommit = commit
   259  			break
   260  		}
   261  	}
   262  	// If not found, return a blank last anchor
   263  	if !found {
   264  		return &la, nil
   265  	}
   266  
   267  	merkleStr := anchorCommitMerkle(lastAnchorCommit)
   268  	merkleBytes, err := hex.DecodeString(merkleStr)
   269  	if err != nil {
   270  		return nil, err
   271  	}
   272  	la.Merkle = merkleBytes
   273  	la.Time = lastAnchorCommit.Time
   274  
   275  	hashBytes, err := hex.DecodeString(lastAnchorCommit.Hash)
   276  	if err != nil {
   277  		return nil, err
   278  	}
   279  	la.Last = extendSHA1(hashBytes)
   280  
   281  	return &la, nil
   282  }
   283  
   284  // readUnconfirmedAnchorRecord retrieves the unconfirmed anchor record.
   285  func (g *gitBackEnd) readUnconfirmedAnchorRecord() (*UnconfirmedAnchor, error) {
   286  	// Get the git log
   287  	gitLog, err := g.gitLog(g.vetted)
   288  	if err != nil {
   289  		return nil, err
   290  	}
   291  
   292  	// Look for unconfirmed commits and store their Merkle roots
   293  	// Stop looking at the latest confirmed commit
   294  	var ua UnconfirmedAnchor
   295  	currLine := 0
   296  	for currLine < len(gitLog) {
   297  		commit, linesUsed, err := extractCommit(gitLog[currLine:])
   298  		if err != nil {
   299  			return nil, err
   300  		}
   301  		currLine += linesUsed
   302  
   303  		// Check the first line of the commit message to see if it is an
   304  		// anchor confirmation or an anchor.
   305  		firstLine := commit.Message[0]
   306  		if regexAnchorConfirmation.MatchString(firstLine) {
   307  			// Found the latest confirmed anchor. Stop looking.
   308  			break
   309  		} else if regexAnchor.MatchString(firstLine) {
   310  			merkleStr := anchorCommitMerkle(commit)
   311  			merkleBytes, err := hex.DecodeString(merkleStr)
   312  			if err != nil {
   313  				return nil, err
   314  			}
   315  			ua.Merkles = append(ua.Merkles, merkleBytes)
   316  		}
   317  	}
   318  
   319  	return &ua, nil
   320  }