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  }