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 }