github.com/decred/politeia@v1.4.0/politeiad/backendv2/tstorebe/plugins/comments/fsck.go (about) 1 // Copyright (c) 2021-2022 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 comments 6 7 import ( 8 "encoding/hex" 9 ) 10 11 // fsckRecordIndex verifies the coherency of a record index. The record index 12 // is rebuilt from scratch if any errors are found. The returned bool will be 13 // true if the record index was rebuilt. 14 func (p *commentsPlugin) fsckRecordIndex(token []byte) (bool, error) { 15 log.Debugf("%x fsck record index", token) 16 17 // Get the digests for all of the comment add, del, and 18 // vote entries for the record. The digests are the keys 19 // that are used to pull the full entries from tstore. 20 addD, err := p.tstore.DigestsByDataDesc(token, 21 []string{dataDescriptorCommentAdd}) 22 if err != nil { 23 return false, err 24 } 25 delD, err := p.tstore.DigestsByDataDesc(token, 26 []string{dataDescriptorCommentDel}) 27 if err != nil { 28 return false, err 29 } 30 voteD, err := p.tstore.DigestsByDataDesc(token, 31 []string{dataDescriptorCommentVote}) 32 if err != nil { 33 return false, err 34 } 35 36 // Get the cached record index 37 state, err := p.tstore.RecordState(token) 38 if err != nil { 39 return false, err 40 } 41 rindex, err := p.recordIndex(token, state) 42 if err != nil { 43 return false, err 44 } 45 46 // Verify the coherency of the record index 47 if recordIndexIsCoherent(*rindex, addD, delD, voteD) { 48 log.Debugf("%x indexes are coherent", token) 49 50 return false, nil 51 } 52 53 // The record index is not coherent. Rebuilt it from scratch. 54 log.Infof("%x rebuilding indexes", token) 55 56 err = p.rebuildRecordIndex(token, addD, delD, voteD) 57 if err != nil { 58 return false, err 59 } 60 61 return true, nil 62 } 63 64 // rebuildRecordIndex rebuilds a recordIndex and saves it to the cache. If 65 // a recordIndex already exists in the cache for this token, it will be 66 // overwritten by this function. 67 func (p *commentsPlugin) rebuildRecordIndex(token []byte, addDigests, delDigests, voteDigests [][]byte) error { 68 // indexes contains a commentIndex for each comment 69 // that has been made on the record. 70 // 71 // A commentIndex contains pointers to the full comment 72 // add, del, and vote records for a comment. 73 indexes := make(map[uint32]commentIndex) 74 75 // Add the adds to the comment indexes 76 adds, err := p.commentAdds(token, addDigests) 77 if err != nil { 78 return err 79 } 80 for i, a := range adds { 81 cindex, ok := indexes[a.CommentID] 82 if !ok { 83 cindex = newCommentIndex() 84 } 85 cindex.Adds[a.Version] = addDigests[i] 86 indexes[a.CommentID] = cindex 87 } 88 89 // Add the dels to the comment indexes 90 dels, err := p.commentDels(token, delDigests) 91 if err != nil { 92 return err 93 } 94 for i, d := range dels { 95 // A comment index will not exist yet 96 // if the comment was deleted since the 97 // comment add entries will have been 98 // deleted. 99 cindex, ok := indexes[d.CommentID] 100 if !ok { 101 cindex = newCommentIndex() 102 } 103 cindex.Del = delDigests[i] 104 indexes[d.CommentID] = cindex 105 } 106 107 // Add the votes to the comment indexes 108 votes, err := p.commentVotes(token, voteDigests) 109 if err != nil { 110 return err 111 } 112 for i, v := range votes { 113 // A commentIndex should always exist. The 114 // code below will panic if one doesn't. 115 cindex := indexes[v.CommentID] 116 117 voteIndexes, ok := cindex.Votes[v.UserID] 118 if !ok { 119 voteIndexes = make([]voteIndex, 0, 1024) 120 } 121 voteIndexes = append(voteIndexes, voteIndex{ 122 Vote: v.Vote, 123 Digest: voteDigests[i], 124 }) 125 126 cindex.Votes[v.UserID] = voteIndexes 127 indexes[v.CommentID] = cindex 128 } 129 130 // Save the record index to the cache. This 131 // will overwrite any existing record index. 132 state, err := p.tstore.RecordState(token) 133 if err != nil { 134 return err 135 } 136 rindex := recordIndex{ 137 Comments: indexes, 138 } 139 p.recordIndexSave(token, state, rindex) 140 141 return nil 142 } 143 144 // recordIndexIsCoherent returns whether the provided recordIndex contains all 145 // of the provided comment add, del, and vote digests. If any of the provided 146 // digests are not found then the recordIndex is considered incoherent and this 147 // function will return false. 148 func recordIndexIsCoherent(rindex recordIndex, addDigests, delDigests, voteDigests [][]byte) bool { 149 // digests contains all of the digests found in the 150 // record index. This includes the digests for all 151 // comment add, del, and vote entries. 152 digests := make(map[string]struct{}, 1024) 153 154 // Aggregate all of the digests that are included in the 155 // record index. 156 for _, cindex := range rindex.Comments { 157 for _, addDigest := range cindex.Adds { 158 digests[hex.EncodeToString(addDigest)] = struct{}{} 159 } 160 for _, voteIndexes := range cindex.Votes { 161 for _, voteIndex := range voteIndexes { 162 digests[hex.EncodeToString(voteIndex.Digest)] = struct{}{} 163 } 164 } 165 if len(cindex.Del) > 0 { 166 digests[hex.EncodeToString(cindex.Del)] = struct{}{} 167 } 168 } 169 170 // Verify that each of the provided add, del, and vote digests 171 // have a corresponding entry in the record index. If a match 172 // is not found for any of the provided digests then the record 173 // index is not coherent. 174 for _, d := range addDigests { 175 _, ok := digests[hex.EncodeToString(d)] 176 if !ok { 177 return false 178 } 179 } 180 for _, d := range delDigests { 181 _, ok := digests[hex.EncodeToString(d)] 182 if !ok { 183 return false 184 } 185 } 186 for _, d := range voteDigests { 187 _, ok := digests[hex.EncodeToString(d)] 188 if !ok { 189 return false 190 } 191 } 192 193 return true 194 }