github.com/decred/politeia@v1.4.0/politeiad/backend/gitbe/gitbe_test.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 "errors" 14 "fmt" 15 "os" 16 "path/filepath" 17 "reflect" 18 "strconv" 19 "strings" 20 "testing" 21 22 "github.com/davecgh/go-spew/spew" 23 "github.com/decred/dcrd/chaincfg/v3" 24 pd "github.com/decred/politeia/politeiad/api/v1" 25 "github.com/decred/politeia/politeiad/api/v1/mime" 26 "github.com/decred/politeia/politeiad/backend" 27 "github.com/decred/politeia/util" 28 "github.com/decred/slog" 29 ) 30 31 func validateMD(got, want *backend.RecordMetadata) error { 32 if got.Iteration != want.Iteration+1 || 33 got.Status != backend.MDStatusVetted || 34 want.Status != backend.MDStatusUnvetted || 35 got.Merkle != want.Merkle || 36 got.Token != want.Token { 37 return fmt.Errorf("unexpected rm got %v, wanted %v", 38 spew.Sdump(*got), spew.Sdump(*want)) 39 } 40 41 return nil 42 } 43 44 func TestExtendUnextend(t *testing.T) { 45 sha1Digest := make([]byte, sha1.Size) 46 for i := 0; i < sha1.Size; i++ { 47 sha1Digest[i] = byte(i) 48 } 49 50 sha256ExtendDigest := extendSHA1(sha1Digest) 51 sha256UnextendDigest := unextendSHA256(sha256ExtendDigest) 52 53 if !bytes.Equal(sha1Digest, sha256UnextendDigest) { 54 t.Fatalf("unextend") 55 } 56 } 57 58 func createTextFile(fileName string) (backend.File, error) { 59 r, err := util.Random(64) 60 if err != nil { 61 return backend.File{}, err 62 } 63 64 payload := hex.EncodeToString(r) 65 digest := hex.EncodeToString(util.Digest([]byte(payload))) 66 // We expect base64 encoded content 67 b64 := base64.StdEncoding.EncodeToString([]byte(payload)) 68 69 return backend.File{ 70 Name: fileName, 71 MIME: mime.DetectMimeType([]byte(payload)), 72 Digest: digest, 73 Payload: b64, 74 }, nil 75 } 76 77 func TestAnchorWithCommits(t *testing.T) { 78 log := slog.NewBackend(&testWriter{t}).Logger("TEST") 79 UseLogger(log) 80 81 dir, err := os.MkdirTemp("", "politeia.test") 82 if err != nil { 83 t.Fatal(err) 84 } 85 defer os.RemoveAll(dir) 86 87 // Initialize stuff we need 88 g, err := New(chaincfg.TestNet3Params(), dir, "", "", nil, 89 testing.Verbose(), "") 90 if err != nil { 91 t.Fatal(err) 92 } 93 g.test = true 94 95 // Create 5 unvetted records 96 propCount := 5 97 fileCount := 3 98 t.Logf("===== CREATE %v RECORDS WITH %v FILES =====", propCount, 99 fileCount) 100 rm := make([]*backend.RecordMetadata, propCount) 101 allFiles := make([][]backend.File, propCount) 102 for i := 0; i < propCount; i++ { 103 name := fmt.Sprintf("record%v", i) 104 files := make([]backend.File, 0, fileCount) 105 for j := 0; j < fileCount; j++ { 106 file, err := createTextFile(name + "_" + strconv.Itoa(j)) 107 if err != nil { 108 t.Fatal(err) 109 } 110 files = append(files, file) 111 } 112 allFiles[i] = files 113 114 rm[i], err = g.New([]backend.MetadataStream{{ 115 ID: 0, // XXX 116 Payload: "this is metadata", 117 }}, files) 118 if err != nil { 119 t.Fatal(err) 120 } 121 } 122 123 // Expect propCount + master branches in unvetted 124 branches, err := g.git(g.unvetted, "branch") 125 if err != nil { 126 t.Fatalf("%v", err) 127 } 128 found := 0 129 master := 0 130 for _, branch := range branches { 131 for _, v := range rm { 132 s := strings.Trim(branch, " \n") 133 if s == v.Token { 134 found++ 135 break 136 } 137 if strings.HasSuffix(s, "master") { 138 master++ 139 break 140 } 141 } 142 } 143 if found != propCount || master != 1 { 144 t.Fatalf("unexpected props got %v wanted %v master %v", 145 found, propCount, master) 146 } 147 148 // Read all MDs from the branches and call getunvetted to verify 149 // integrity 150 for k, v := range rm { 151 token, err := hex.DecodeString(v.Token) 152 if err != nil { 153 t.Fatal(err) 154 } 155 pru, err := g.GetUnvetted(token) 156 if err != nil { 157 t.Fatalf("%v", err) 158 } 159 if !reflect.DeepEqual(&pru.RecordMetadata, rm[k]) { 160 t.Fatalf("unexpected rm got %v, wanted %v", 161 spew.Sdump(pru.RecordMetadata), 162 spew.Sdump(rm[k])) 163 } 164 if !reflect.DeepEqual(pru.Files, allFiles[k]) { 165 t.Fatalf("unexpected payload got %v, wanted %v", 166 spew.Sdump(pru.Files), spew.Sdump(allFiles[k])) 167 } 168 } 169 170 // Expect 1 branch in vetted 171 branches, err = g.git(g.vetted, "branch") 172 if err != nil { 173 t.Fatalf("%v", err) 174 } 175 if len(branches) != 1 { 176 t.Fatalf("too many branches on master got %v want 1", 177 len(branches)) 178 } 179 180 // Vet 1 of the records 181 t.Logf("===== VET RECORD 1 =====") 182 emptyMD := []backend.MetadataStream{} 183 token, err := hex.DecodeString(rm[1].Token) 184 if err != nil { 185 t.Fatal(err) 186 } 187 record, err := g.SetUnvettedStatus(token, 188 backend.MDStatusVetted, emptyMD, emptyMD) 189 if err != nil { 190 t.Fatal(err) 191 } 192 if record.RecordMetadata.Status != backend.MDStatusVetted { 193 t.Fatalf("unexpected status: got %v wanted %v", 194 record.RecordMetadata.Status, backend.MDStatusVetted) 195 } 196 //Get it as well to validate the GetVetted call 197 pru, err := g.GetVetted(token, "") 198 if err != nil { 199 t.Fatal(err) 200 } 201 psrG := &pru.RecordMetadata 202 if psrG.Status != backend.MDStatusVetted { 203 t.Fatalf("unexpected status: got %v wanted %v", psrG.Status, 204 backend.MDStatusVetted) 205 } 206 207 err = validateMD(psrG, rm[1]) 208 if err != nil { 209 t.Fatal(err) 210 } 211 if !reflect.DeepEqual(pru.Files, allFiles[1]) { 212 t.Fatalf("unexpected payload got %v, wanted %v", 213 spew.Sdump(pru.Files), spew.Sdump(allFiles[1])) 214 } 215 216 // Anchor all repos 217 t.Logf("===== ANCHOR =====") 218 err = g.anchorAllRepos() 219 if err != nil { 220 t.Fatal(err) 221 } 222 // Read unconfirmed and verify content 223 unconfirmed, err := g.readUnconfirmedAnchorRecord() 224 if err != nil { 225 t.Fatal(err) 226 } 227 if len(unconfirmed.Merkles) != 1 { 228 t.Fatalf("invalid merkles len %v", len(unconfirmed.Merkles)) 229 } 230 // Read anchor pointed at by merkle from git log 231 var mr [sha256.Size]byte 232 copy(mr[:], unconfirmed.Merkles[0]) 233 anchor, err := g.readAnchorRecord(mr) 234 if err != nil { 235 t.Fatal(err) 236 } 237 // Verify last commit 238 lastGitDigest, err := g.gitLastDigest(g.vetted) 239 if err != nil { 240 t.Fatal(err) 241 } 242 lastGitDigest = extendSHA1(lastGitDigest) 243 la, err := g.readLastAnchorRecord() 244 if err != nil { 245 t.Fatal(err) 246 } 247 if !bytes.Equal(lastGitDigest, la.Last) { 248 t.Fatalf("invalid unconfirmed digest got %x wanted %x", 249 lastGitDigest, la.Last) 250 } 251 if anchor.Type != AnchorUnverified { 252 t.Fatalf("invalid anchor type %v expected %v", anchor.Type, 253 AnchorVerified) 254 } 255 256 // Anchor again and make sure nothing changed 257 t.Logf("===== REANCHOR NOTHING TO DO =====") 258 err = g.anchorAllRepos() 259 if err != nil { 260 t.Fatal(err) 261 } 262 // Read unconfirmed again and verify content 263 unconfirmed2, err := g.readUnconfirmedAnchorRecord() 264 if err != nil { 265 t.Fatal(err) 266 } 267 if len(unconfirmed2.Merkles) != 1 { 268 t.Fatalf("invalid merkles len %v", len(unconfirmed2.Merkles)) 269 } 270 if !reflect.DeepEqual(unconfirmed, unconfirmed2) { 271 t.Fatalf("unconfirmed got %v wanted %v", 272 spew.Sdump(unconfirmed2), 273 spew.Sdump(unconfirmed)) 274 } 275 // Read anchor again pointed at by merkle from git log 276 var mr2 [sha256.Size]byte 277 copy(mr2[:], unconfirmed.Merkles[0]) 278 anchor2, err := g.readAnchorRecord(mr2) 279 if err != nil { 280 t.Fatal(err) 281 } 282 if !bytes.Equal(mr[:], mr2[:]) { 283 t.Fatalf("mr got %x wanted %x", mr2, mr) 284 } 285 // Verify last commit again 286 lastGitDigest2, err := g.gitLastDigest(g.vetted) 287 if err != nil { 288 t.Fatal(err) 289 } 290 lastGitDigest2 = extendSHA1(lastGitDigest2) 291 la2, err := g.readLastAnchorRecord() 292 if err != nil { 293 t.Fatal(err) 294 } 295 if !bytes.Equal(lastGitDigest2, la2.Last) { 296 t.Fatalf("invalid unconfirmed digest got %x wanted %x", 297 lastGitDigest2, la2.Last) 298 } 299 if !bytes.Equal(lastGitDigest2, lastGitDigest) { 300 t.Fatalf("invalid lastGitDigest got %x wanted %x", 301 lastGitDigest2, lastGitDigest) 302 } 303 if !reflect.DeepEqual(anchor2, anchor) { 304 t.Fatalf("invalid anchor got %x wanted %x", 305 spew.Sdump(anchor2), spew.Sdump(anchor)) 306 } 307 308 // Complete anchor 309 t.Logf("===== COMPLETE ANCHOR PROCESS =====") 310 err = g.anchorChecker() 311 if err != nil { 312 t.Fatal(err) 313 } 314 // Verify that we updated unconfirmed 315 unconfirmed, err = g.readUnconfirmedAnchorRecord() 316 if err != nil { 317 t.Fatal(err) 318 } 319 if len(unconfirmed.Merkles) != 0 { 320 t.Fatalf("invalid merkles len %v", len(unconfirmed.Merkles)) 321 } 322 // Verify that anchor record was updated 323 anchor3, err := g.readAnchorRecord(mr) 324 if err != nil { 325 t.Fatal(err) 326 } 327 if anchor3.Type != AnchorVerified { 328 t.Fatalf("invalid anchor type %v expected %v", anchor3.Type, 329 AnchorVerified) 330 } 331 // Verify that Merkle was cleared in last anchor record 332 la, err = g.readLastAnchorRecord() 333 if err != nil { 334 t.Fatal(err) 335 } 336 337 // Drop an anchor to verify that we don't pick up the anchor commit 338 t.Logf("===== DROP ANCHOR ON TOP OF ANCHOR =====") 339 lastGitDigest, err = g.gitLastDigest(g.vetted) 340 if err != nil { 341 t.Fatal(err) 342 } 343 err = g.anchorAllRepos() 344 if err != nil { 345 t.Fatal(err) 346 } 347 lastGitDigestAfter, err := g.gitLastDigest(g.vetted) 348 if err != nil { 349 t.Fatal(err) 350 } 351 if !bytes.Equal(lastGitDigest, lastGitDigestAfter) { 352 t.Fatalf("git digest got %x wanted %x", 353 lastGitDigest, lastGitDigestAfter) 354 } 355 356 // Interleave incomplete anchors: 357 // vet -> anchor1 -> vet -> anchor2 -> confirm 358 359 // Vet + anchor 360 t.Logf("===== INTERLEAVE ANCHORS =====") 361 token2, err := hex.DecodeString(rm[2].Token) 362 if err != nil { 363 t.Fatal(err) 364 } 365 _, err = g.SetUnvettedStatus(token2, backend.MDStatusVetted, 366 emptyMD, emptyMD) 367 if err != nil { 368 t.Fatal(err) 369 } 370 err = g.anchorAllRepos() 371 if err != nil { 372 t.Fatal(err) 373 } 374 375 // Vet + anchor 376 token0, err := hex.DecodeString(rm[0].Token) 377 if err != nil { 378 t.Fatal(err) 379 } 380 _, err = g.SetUnvettedStatus(token0, backend.MDStatusVetted, 381 emptyMD, emptyMD) 382 if err != nil { 383 t.Fatal(err) 384 } 385 err = g.anchorAllRepos() 386 if err != nil { 387 t.Fatal(err) 388 } 389 390 // Complete anchor 391 t.Logf("===== COMPLETE INTERLEAVED ANCHOR PROCESS =====") 392 err = g.anchorChecker() 393 if err != nil { 394 t.Fatal(err) 395 } 396 397 // Drop an anchor to verify that we don't pick up the anchor commit 398 t.Logf("===== DROP ANCHOR ON TOP OF ANCHOR 2 =====") 399 err = g.anchorAllRepos() 400 if err != nil { 401 t.Fatal(err) 402 } 403 } 404 405 func TestFilePathVersion(t *testing.T) { 406 dir, err := os.MkdirTemp("", "pathversion") 407 if err != nil { 408 t.Fatal(err) 409 } 410 defer os.RemoveAll(dir) 411 412 t.Logf("dir: %v", dir) 413 d, err := _joinLatest(dir) 414 if !errors.Is(err, backend.ErrRecordNotFound) { 415 t.Fatal(err) 416 } 417 if d != "" { 418 t.Fatalf("expected \"\", got %v", d) 419 } 420 421 // Create version 0 and check again 422 newDir := pijoin(dir, "0") 423 err = os.MkdirAll(newDir, 0766) 424 if err != nil { 425 t.Fatal(err) 426 } 427 testDir := joinLatest(dir) 428 // Abuse filepath.Split by pretending 0 is a file 429 splitDir, splitFile := filepath.Split(testDir) 430 if splitDir != dir+"/" { 431 t.Fatalf("invalid dir, expected %v, got %v", dir+"/", splitDir) 432 } 433 if splitFile != "0" { 434 t.Fatalf("invalid dir, expected 0, got %v", splitFile) 435 } 436 437 // Create version 1 and check again 438 newDir = pijoin(dir, "1") 439 err = os.MkdirAll(newDir, 0766) 440 if err != nil { 441 t.Fatal(err) 442 } 443 testDir = joinLatest(dir) 444 // Abuse filepath.Split by pretending 1 is a file 445 splitDir, splitFile = filepath.Split(testDir) 446 if splitDir != dir+"/" { 447 t.Fatalf("invalid dir, expected %v, got %v", dir+"/", splitDir) 448 } 449 if splitFile != "1" { 450 t.Fatalf("invalid dir, expected 1, got %v", splitFile) 451 } 452 453 // Create version 33 and check again 454 newDir = pijoin(dir, "33") 455 err = os.MkdirAll(newDir, 0766) 456 if err != nil { 457 t.Fatal(err) 458 } 459 testDir = joinLatest(dir) 460 // Abuse filepath.Split by pretending 33 is a file 461 splitDir, splitFile = filepath.Split(testDir) 462 if splitDir != dir+"/" { 463 t.Fatalf("invalid dir, expected %v, got %v", dir+"/", splitDir) 464 } 465 if splitFile != "33" { 466 t.Fatalf("invalid dir, expected 33, got %v", splitFile) 467 } 468 } 469 470 func TestUpdateReadme(t *testing.T) { 471 dir, err := os.MkdirTemp("", "politeia.test") 472 if err != nil { 473 t.Fatal(err) 474 } 475 defer os.RemoveAll(dir) 476 477 g, err := New(chaincfg.TestNet3Params(), dir, "", "", nil, 478 testing.Verbose(), "") 479 if err != nil { 480 t.Fatal(err) 481 } 482 g.test = true 483 484 updatedReadmeContent := "Updated Readme Content!! \n" 485 err = g.UpdateReadme(updatedReadmeContent) 486 if err != nil { 487 t.Fatal(err) 488 } 489 490 unvettedReadmePath := filepath.Join(g.unvetted, "README.md") 491 unvettedReadmeContent, err := os.ReadFile(unvettedReadmePath) 492 if err != nil { 493 t.Fatal(err) 494 } 495 unvettedReadmeString := string(unvettedReadmeContent) 496 if unvettedReadmeString != updatedReadmeContent { 497 t.Fatalf("Expected README.md content to be: %s \n but got: %s ", 498 updatedReadmeContent, 499 unvettedReadmeString) 500 } 501 502 vettedReadmePath := filepath.Join(g.vetted, "README.md") 503 vettedReadmeContent, err := os.ReadFile(vettedReadmePath) 504 if err != nil { 505 t.Fatal(err) 506 } 507 vettedReadmeString := string(vettedReadmeContent) 508 if vettedReadmeString != updatedReadmeContent { 509 t.Fatalf("Expected README.md content to be: %s \n but got: %s ", 510 updatedReadmeContent, 511 vettedReadmeString) 512 } 513 514 branches, err := g.git(g.unvetted, "branch") 515 if err != nil { 516 t.Fatalf("%v", err) 517 } 518 if len(branches) != 1 { 519 t.Fatalf("Expected 1 branch in unvetted repo, but it got %v", 520 len(branches)) 521 } 522 if !strings.HasSuffix(branches[0], "master") { 523 t.Fatalf("The only branch in the vetted repo should be master") 524 } 525 526 branches, err = g.git(g.vetted, "branch") 527 if err != nil { 528 t.Fatalf("%v", err) 529 } 530 if len(branches) != 1 { 531 t.Fatalf("Expected 1 branch in vetted repo, but it got %v", 532 len(branches)) 533 } 534 if !strings.HasSuffix(branches[0], "master") { 535 t.Fatalf("The only branch in the vetted repo should be master") 536 } 537 538 // Trying to update readme to the same content returns an error, but does 539 // not add any new branches. 540 err = g.UpdateReadme(updatedReadmeContent) 541 if err == nil { 542 t.Fatal("Updating readme the current content should return an error") 543 } 544 545 branches, err = g.git(g.unvetted, "branch") 546 if err != nil { 547 t.Fatalf("%v", err) 548 } 549 if len(branches) != 1 { 550 t.Fatalf("Expected 1 branch in unvetted repo, but it got %v", 551 len(branches)) 552 } 553 if !strings.HasSuffix(branches[0], "master") { 554 t.Fatalf("The only branch in the vetted repo should be master") 555 } 556 557 branches, err = g.git(g.vetted, "branch") 558 if err != nil { 559 t.Fatalf("%v", err) 560 } 561 if len(branches) != 1 { 562 t.Fatalf("Expected 1 branch in vetted repo, but it got %v", 563 len(branches)) 564 } 565 if !strings.HasSuffix(branches[0], "master") { 566 t.Fatalf("The only branch in the vetted repo should be master") 567 } 568 } 569 570 func updateTokenPrefixLength(length int) { 571 pd.TokenPrefixLength = length 572 } 573 574 func TestTokenPrefixGeneration(t *testing.T) { 575 originalPrefixLength := pd.TokenPrefixLength 576 updateTokenPrefixLength(1) 577 defer updateTokenPrefixLength(originalPrefixLength) 578 579 dir, err := os.MkdirTemp("", "politeia.test") 580 if err != nil { 581 t.Fatal(err) 582 } 583 defer os.RemoveAll(dir) 584 585 g, err := New(chaincfg.TestNet3Params(), dir, "", "", nil, 586 testing.Verbose(), "") 587 if err != nil { 588 t.Fatal(err) 589 } 590 g.test = true 591 592 files := make([]backend.File, 0, 1) 593 file, err := createTextFile("randomFileName") 594 if err != nil { 595 t.Fatal(err) 596 } 597 files = append(files, file) 598 599 // Since we use a prefix length of 1 in test mode, only 16 unique tokens 600 // should be able to be generated. 601 for i := 0; i < 16; i++ { 602 _, err = g.New([]backend.MetadataStream{{ 603 ID: 0, 604 Payload: "this is metadata", 605 }}, files) 606 607 if err != nil { 608 t.Fatalf("Error creating less than 16 new records: %v", err) 609 } 610 } 611 612 _, err = g.New([]backend.MetadataStream{{ 613 ID: 0, 614 Payload: "this is metadata", 615 }}, files) 616 617 if err == nil { 618 t.Fatalf("Should only be able to create 16 tokens with unique " + 619 "prefix of length 1, but was able to create 17") 620 } 621 622 // Here we test that the getUnvettedTokens and getVettedTokens methods 623 // work as expected. 624 g.Lock() 625 unvettedTokens, err := g.getUnvettedTokens() 626 g.Unlock() 627 if err != nil { 628 t.Fatal(err) 629 } 630 if len(unvettedTokens) != 16 { 631 t.Fatalf("There should be 16 unvetted tokens, but there are %v", 632 len(unvettedTokens)) 633 } 634 635 // We update the status of the first two records to vetted. 636 emptyMD := []backend.MetadataStream{} 637 token, err := hex.DecodeString(unvettedTokens[0]) 638 if err != nil { 639 t.Fatal(err) 640 } 641 _, err = g.SetUnvettedStatus(token, 642 backend.MDStatusVetted, emptyMD, emptyMD) 643 if err != nil { 644 t.Fatal(err) 645 } 646 token, err = hex.DecodeString(unvettedTokens[1]) 647 if err != nil { 648 t.Fatal(err) 649 } 650 _, err = g.SetUnvettedStatus(token, 651 backend.MDStatusVetted, emptyMD, emptyMD) 652 if err != nil { 653 t.Fatal(err) 654 } 655 656 // Since we updated the status of 2 records, there should be 14 unvetted 657 // and 2 vetted proposals. 658 g.Lock() 659 unvettedTokens, err = g.getUnvettedTokens() 660 g.Unlock() 661 if err != nil { 662 t.Fatal(err) 663 } 664 if len(unvettedTokens) != 14 { 665 t.Fatalf("There should be 14 tokens, but there are %v", len(unvettedTokens)) 666 } 667 668 g.Lock() 669 vettedTokens, err := g.getVettedTokens() 670 g.Unlock() 671 if err != nil { 672 t.Fatal(err) 673 } 674 if len(vettedTokens) != 2 { 675 t.Fatalf("There should be 2 tokens, but there are %v", len(vettedTokens)) 676 } 677 678 // Now we test that when creating a new gitbe object on an existing folder, 679 // the prefix cache is populated correctly. 680 oldPrefixCache := g.prefixCache 681 g, err = New(chaincfg.TestNet3Params(), dir, "", "", nil, 682 testing.Verbose(), "") 683 if err != nil { 684 t.Fatal(err) 685 } 686 g.test = true 687 688 if len(oldPrefixCache) != len(g.prefixCache) { 689 t.Fatalf("The prefix cache does not contain the correct amount of" + 690 " prefixes") 691 } 692 for prefix := range oldPrefixCache { 693 if _, ok := g.prefixCache[prefix]; !ok { 694 t.Fatalf("The prefix map does not contain an expected prefix: %v", 695 prefix) 696 } 697 } 698 }