github.com/deso-protocol/core@v1.2.9/lib/block_view_post.go (about)

     1  package lib
     2  
     3  import (
     4  	"encoding/hex"
     5  	"fmt"
     6  	"github.com/btcsuite/btcd/btcec"
     7  	"github.com/davecgh/go-spew/spew"
     8  	"github.com/dgraph-io/badger/v3"
     9  	"github.com/golang/glog"
    10  	"github.com/pkg/errors"
    11  	"math"
    12  	"reflect"
    13  	"sort"
    14  )
    15  
    16  func (bav *UtxoView) _getRepostEntryForRepostKey(repostKey *RepostKey) *RepostEntry {
    17  	// If an entry exists in the in-memory map, return the value of that mapping.
    18  	mapValue, existsMapValue := bav.RepostKeyToRepostEntry[*repostKey]
    19  	if existsMapValue {
    20  		return mapValue
    21  	}
    22  
    23  	// If we get here it means no value exists in our in-memory map. In this case,
    24  	// defer to the db. If a mapping exists in the db, return it. If not, return
    25  	// nil. Either way, save the value to the in-memory view mapping got later.
    26  	repostEntry := DbReposterPubKeyRepostedPostHashToRepostEntry(
    27  		bav.Handle, repostKey.ReposterPubKey[:], repostKey.RepostedPostHash)
    28  	if repostEntry != nil {
    29  		bav._setRepostEntryMappings(repostEntry)
    30  	}
    31  	return repostEntry
    32  }
    33  
    34  func (bav *UtxoView) _setRepostEntryMappings(repostEntry *RepostEntry) {
    35  	// This function shouldn't be called with nil.
    36  	if repostEntry == nil {
    37  		glog.Errorf("_setRepostEntryMappings: Called with nil RepostEntry; " +
    38  			"this should never happen.")
    39  		return
    40  	}
    41  
    42  	repostKey := MakeRepostKey(repostEntry.ReposterPubKey, *repostEntry.RepostedPostHash)
    43  	bav.RepostKeyToRepostEntry[repostKey] = repostEntry
    44  }
    45  
    46  func (bav *UtxoView) _deleteRepostEntryMappings(repostEntry *RepostEntry) {
    47  
    48  	if repostEntry == nil {
    49  		glog.Errorf("_deleteRepostEntryMappings: called with nil RepostEntry; " +
    50  			"this should never happen")
    51  		return
    52  	}
    53  	// Create a tombstone entry.
    54  	tombstoneRepostEntry := *repostEntry
    55  	tombstoneRepostEntry.isDeleted = true
    56  
    57  	// Set the mappings to point to the tombstone entry.
    58  	bav._setRepostEntryMappings(&tombstoneRepostEntry)
    59  }
    60  
    61  func (bav *UtxoView) _setDiamondEntryMappings(diamondEntry *DiamondEntry) {
    62  	// This function shouldn't be called with nil.
    63  	if diamondEntry == nil {
    64  		glog.Errorf("_setDiamondEntryMappings: Called with nil DiamondEntry; " +
    65  			"this should never happen.")
    66  		return
    67  	}
    68  
    69  	diamondKey := MakeDiamondKey(
    70  		diamondEntry.SenderPKID, diamondEntry.ReceiverPKID, diamondEntry.DiamondPostHash)
    71  	bav.DiamondKeyToDiamondEntry[diamondKey] = diamondEntry
    72  }
    73  
    74  func (bav *UtxoView) _deleteDiamondEntryMappings(diamondEntry *DiamondEntry) {
    75  
    76  	// Create a tombstone entry.
    77  	tombstoneDiamondEntry := *diamondEntry
    78  	tombstoneDiamondEntry.isDeleted = true
    79  
    80  	// Set the mappings to point to the tombstone entry.
    81  	bav._setDiamondEntryMappings(&tombstoneDiamondEntry)
    82  }
    83  
    84  func (bav *UtxoView) GetDiamondEntryForDiamondKey(diamondKey *DiamondKey) *DiamondEntry {
    85  	// If an entry exists in the in-memory map, return the value of that mapping.
    86  	bavDiamondEntry, existsMapValue := bav.DiamondKeyToDiamondEntry[*diamondKey]
    87  	if existsMapValue {
    88  		return bavDiamondEntry
    89  	}
    90  
    91  	// If we get here it means no value exists in our in-memory map. In this case,
    92  	// defer to the db. If a mapping exists in the db, return it. If not, return
    93  	// nil.
    94  	var diamondEntry *DiamondEntry
    95  	if bav.Postgres != nil {
    96  		diamond := bav.Postgres.GetDiamond(&diamondKey.SenderPKID, &diamondKey.ReceiverPKID, &diamondKey.DiamondPostHash)
    97  		if diamond != nil {
    98  			diamondEntry = &DiamondEntry{
    99  				SenderPKID:      diamond.SenderPKID,
   100  				ReceiverPKID:    diamond.ReceiverPKID,
   101  				DiamondPostHash: diamond.DiamondPostHash,
   102  				DiamondLevel:    int64(diamond.DiamondLevel),
   103  			}
   104  		}
   105  	} else {
   106  		diamondEntry = DbGetDiamondMappings(bav.Handle, &diamondKey.ReceiverPKID, &diamondKey.SenderPKID, &diamondKey.DiamondPostHash)
   107  	}
   108  
   109  	if diamondEntry != nil {
   110  		bav._setDiamondEntryMappings(diamondEntry)
   111  	}
   112  
   113  	return diamondEntry
   114  }
   115  
   116  func (bav *UtxoView) GetPostEntryForPostHash(postHash *BlockHash) *PostEntry {
   117  	// If an entry exists in the in-memory map, return the value of that mapping.
   118  	mapValue, existsMapValue := bav.PostHashToPostEntry[*postHash]
   119  	if existsMapValue {
   120  		return mapValue
   121  	}
   122  
   123  	// If we get here it means no value exists in our in-memory map. In this case,
   124  	// defer to the db. If a mapping exists in the db, return it. If not, return
   125  	// nil.
   126  	if bav.Postgres != nil {
   127  		post := bav.Postgres.GetPost(postHash)
   128  		if post != nil {
   129  			return bav.setPostMappings(post)
   130  		}
   131  		return nil
   132  	} else {
   133  		dbPostEntry := DBGetPostEntryByPostHash(bav.Handle, postHash)
   134  		if dbPostEntry != nil {
   135  			bav._setPostEntryMappings(dbPostEntry)
   136  		}
   137  		return dbPostEntry
   138  	}
   139  }
   140  
   141  func (bav *UtxoView) GetDiamondEntryMapForPublicKey(publicKey []byte, fetchYouDiamonded bool,
   142  ) (_pkidToDiamondsMap map[PKID][]*DiamondEntry, _err error) {
   143  	pkidEntry := bav.GetPKIDForPublicKey(publicKey)
   144  
   145  	dbPKIDToDiamondsMap, err := DbGetPKIDsThatDiamondedYouMap(bav.Handle, pkidEntry.PKID, fetchYouDiamonded)
   146  	if err != nil {
   147  		return nil, errors.Wrapf(err, "GetDiamondEntryMapForPublicKey: Error Getting "+
   148  			"PKIDs that diamonded you map from the DB.")
   149  	}
   150  
   151  	// Load all of the diamondEntries into the view.
   152  	for _, diamondEntryList := range dbPKIDToDiamondsMap {
   153  		for _, diamondEntry := range diamondEntryList {
   154  			diamondKey := &DiamondKey{
   155  				SenderPKID:      *diamondEntry.SenderPKID,
   156  				ReceiverPKID:    *diamondEntry.ReceiverPKID,
   157  				DiamondPostHash: *diamondEntry.DiamondPostHash,
   158  			}
   159  			// If the diamond key is not in the view, add it to the view.
   160  			if _, ok := bav.DiamondKeyToDiamondEntry[*diamondKey]; !ok {
   161  				bav._setDiamondEntryMappings(diamondEntry)
   162  			}
   163  		}
   164  	}
   165  
   166  	// Iterate over all the diamondEntries in the view and build the final map.
   167  	pkidToDiamondsMap := make(map[PKID][]*DiamondEntry)
   168  	for _, diamondEntry := range bav.DiamondKeyToDiamondEntry {
   169  		if diamondEntry.isDeleted {
   170  			continue
   171  		}
   172  		// Make sure the diamondEntry we are looking at is for the correct receiver public key.
   173  		if !fetchYouDiamonded && reflect.DeepEqual(diamondEntry.ReceiverPKID, pkidEntry.PKID) {
   174  			pkidToDiamondsMap[*diamondEntry.SenderPKID] = append(
   175  				pkidToDiamondsMap[*diamondEntry.SenderPKID], diamondEntry)
   176  		}
   177  
   178  		// Make sure the diamondEntry we are looking at is for the correct sender public key.
   179  		if fetchYouDiamonded && reflect.DeepEqual(diamondEntry.SenderPKID, pkidEntry.PKID) {
   180  			pkidToDiamondsMap[*diamondEntry.ReceiverPKID] = append(
   181  				pkidToDiamondsMap[*diamondEntry.ReceiverPKID], diamondEntry)
   182  		}
   183  	}
   184  
   185  	return pkidToDiamondsMap, nil
   186  }
   187  
   188  func (bav *UtxoView) GetDiamondEntriesForSenderToReceiver(receiverPublicKey []byte, senderPublicKey []byte,
   189  ) (_diamondEntries []*DiamondEntry, _err error) {
   190  
   191  	receiverPKIDEntry := bav.GetPKIDForPublicKey(receiverPublicKey)
   192  	senderPKIDEntry := bav.GetPKIDForPublicKey(senderPublicKey)
   193  	dbDiamondEntries, err := DbGetDiamondEntriesForSenderToReceiver(bav.Handle, receiverPKIDEntry.PKID, senderPKIDEntry.PKID)
   194  	if err != nil {
   195  		return nil, errors.Wrapf(err, "GetDiamondEntriesForGiverToReceiver: Error getting diamond entries from DB.")
   196  	}
   197  
   198  	// Load all of the diamondEntries into the view
   199  	for _, diamondEntry := range dbDiamondEntries {
   200  		diamondKey := &DiamondKey{
   201  			SenderPKID:      *diamondEntry.SenderPKID,
   202  			ReceiverPKID:    *diamondEntry.ReceiverPKID,
   203  			DiamondPostHash: *diamondEntry.DiamondPostHash,
   204  		}
   205  		// If the diamond key is not in the view, add it to the view.
   206  		if _, ok := bav.DiamondKeyToDiamondEntry[*diamondKey]; !ok {
   207  			bav._setDiamondEntryMappings(diamondEntry)
   208  		}
   209  	}
   210  
   211  	var diamondEntries []*DiamondEntry
   212  	for _, diamondEntry := range bav.DiamondKeyToDiamondEntry {
   213  		if diamondEntry.isDeleted {
   214  			continue
   215  		}
   216  
   217  		// Make sure the diamondEntry we are looking at is for the correct sender and receiver pair
   218  		if reflect.DeepEqual(diamondEntry.ReceiverPKID, receiverPKIDEntry.PKID) &&
   219  			reflect.DeepEqual(diamondEntry.SenderPKID, senderPKIDEntry.PKID) {
   220  			diamondEntries = append(diamondEntries, diamondEntry)
   221  		}
   222  	}
   223  
   224  	return diamondEntries, nil
   225  }
   226  
   227  func (bav *UtxoView) _setPostEntryMappings(postEntry *PostEntry) {
   228  	// This function shouldn't be called with nil.
   229  	if postEntry == nil {
   230  		glog.Errorf("_setPostEntryMappings: Called with nil PostEntry; this should never happen.")
   231  		return
   232  	}
   233  
   234  	// Add a mapping for the post.
   235  	bav.PostHashToPostEntry[*postEntry.PostHash] = postEntry
   236  }
   237  
   238  func (bav *UtxoView) _deletePostEntryMappings(postEntry *PostEntry) {
   239  
   240  	// Create a tombstone entry.
   241  	tombstonePostEntry := *postEntry
   242  	tombstonePostEntry.isDeleted = true
   243  
   244  	// Set the mappings to point to the tombstone entry.
   245  	bav._setPostEntryMappings(&tombstonePostEntry)
   246  }
   247  
   248  func (bav *UtxoView) setPostMappings(post *PGPost) *PostEntry {
   249  	postEntry := post.NewPostEntry()
   250  
   251  	// Add a mapping for the post.
   252  	bav.PostHashToPostEntry[*post.PostHash] = postEntry
   253  
   254  	return postEntry
   255  }
   256  
   257  func (bav *UtxoView) GetPostEntryReaderState(
   258  	readerPK []byte, postEntry *PostEntry) *PostEntryReaderState {
   259  	postEntryReaderState := &PostEntryReaderState{}
   260  
   261  	// Get like state.
   262  	postEntryReaderState.LikedByReader = bav.GetLikedByReader(readerPK, postEntry.PostHash)
   263  
   264  	// Get repost state.
   265  	postEntryReaderState.RepostPostHashHex, postEntryReaderState.RepostedByReader = bav.GetRepostPostEntryStateForReader(readerPK, postEntry.PostHash)
   266  
   267  	// Get diamond state.
   268  	senderPKID := bav.GetPKIDForPublicKey(readerPK)
   269  	receiverPKID := bav.GetPKIDForPublicKey(postEntry.PosterPublicKey)
   270  	if senderPKID == nil || receiverPKID == nil {
   271  		glog.V(1).Infof(
   272  			"GetPostEntryReaderState: Could not find PKID for reader PK: %s or poster PK: %s",
   273  			PkToString(readerPK, bav.Params), PkToString(postEntry.PosterPublicKey, bav.Params))
   274  	} else {
   275  		diamondKey := MakeDiamondKey(senderPKID.PKID, receiverPKID.PKID, postEntry.PostHash)
   276  		diamondEntry := bav.GetDiamondEntryForDiamondKey(&diamondKey)
   277  		if diamondEntry != nil {
   278  			postEntryReaderState.DiamondLevelBestowed = diamondEntry.DiamondLevel
   279  		}
   280  	}
   281  
   282  	return postEntryReaderState
   283  }
   284  
   285  func (bav *UtxoView) GetRepostPostEntryStateForReader(readerPK []byte, postHash *BlockHash) (string, bool) {
   286  	repostKey := MakeRepostKey(readerPK, *postHash)
   287  	repostEntry := bav._getRepostEntryForRepostKey(&repostKey)
   288  	if repostEntry == nil {
   289  		return "", false
   290  	}
   291  	repostPostEntry := bav.GetPostEntryForPostHash(repostEntry.RepostPostHash)
   292  	if repostPostEntry == nil {
   293  		glog.Errorf("Could not find repost post entry from post hash: %v", repostEntry.RepostedPostHash)
   294  		return "", false
   295  	}
   296  	// We include the PostHashHex of this user's post that reposts the current post to
   297  	// handle undo-ing (AKA hiding) a repost.
   298  	// If the user's repost of this post is hidden, we set RepostedByReader to false.
   299  	return hex.EncodeToString(repostEntry.RepostPostHash[:]), !repostPostEntry.IsHidden
   300  }
   301  
   302  func (bav *UtxoView) GetCommentEntriesForParentStakeID(parentStakeID []byte) ([]*PostEntry, error) {
   303  	if bav.Postgres != nil {
   304  		posts := bav.Postgres.GetComments(NewBlockHash(parentStakeID))
   305  		for _, post := range posts {
   306  			bav.setPostMappings(post)
   307  		}
   308  	} else {
   309  		_, dbCommentHashes, _, err := DBGetCommentPostHashesForParentStakeID(bav.Handle, parentStakeID, false)
   310  		if err != nil {
   311  			return nil, errors.Wrapf(err, "GetCommentEntriesForParentStakeID: Problem fetching comments: %v", err)
   312  		}
   313  
   314  		// Load comment hashes into the view.
   315  		for _, commentHash := range dbCommentHashes {
   316  			bav.GetPostEntryForPostHash(commentHash)
   317  		}
   318  	}
   319  
   320  	commentEntries := []*PostEntry{}
   321  	for _, postEntry := range bav.PostHashToPostEntry {
   322  		// Ignore deleted or rolled-back posts.
   323  		if postEntry.isDeleted {
   324  			continue
   325  		}
   326  
   327  		if len(postEntry.ParentStakeID) == 0 || !reflect.DeepEqual(postEntry.ParentStakeID, parentStakeID) {
   328  			continue // Skip posts that are not comments on the given parentStakeID.
   329  		} else {
   330  			// Add the comment to our map.
   331  			commentEntries = append(commentEntries, postEntry)
   332  		}
   333  	}
   334  
   335  	return commentEntries, nil
   336  }
   337  
   338  // Accepts a postEntry and returns as many parent posts as it can find up to maxDepth.
   339  // This function never returns an error, only an empty list if it hits a non-post parentStakeID.
   340  // If "rootFirst" is passed, the root of the tree will be returned first, not the 1st parent.
   341  // _truncatedTree is a flag that is true when the root post was not reached before the maxDepth was hit.
   342  func (bav *UtxoView) GetParentPostEntriesForPostEntry(postEntry *PostEntry, maxDepth uint32, rootFirst bool,
   343  ) (_parentPostEntries []*PostEntry, _truncatedTree bool) {
   344  
   345  	parentStakeID := postEntry.ParentStakeID
   346  	parentPostEntries := []*PostEntry{}
   347  
   348  	// If the post passed has no parent or isn't a post, we return the empty list.
   349  	if len(parentStakeID) != HashSizeBytes {
   350  		return parentPostEntries, false
   351  	}
   352  
   353  	iterations := uint32(0)
   354  	for len(parentStakeID) == HashSizeBytes && iterations < maxDepth {
   355  		parentPostHash := &BlockHash{}
   356  		copy(parentPostHash[:], parentStakeID)
   357  
   358  		parentPostEntry := bav.GetPostEntryForPostHash(parentPostHash)
   359  		if postEntry == nil {
   360  			break
   361  		}
   362  		if rootFirst {
   363  			parentPostEntries = append([]*PostEntry{parentPostEntry}, parentPostEntries...)
   364  		} else {
   365  			parentPostEntries = append(parentPostEntries, parentPostEntry)
   366  		}
   367  
   368  		// Set up the next iteration of the loop.
   369  		parentStakeID = parentPostEntry.ParentStakeID
   370  		iterations += 1
   371  	}
   372  
   373  	return parentPostEntries, iterations >= maxDepth
   374  }
   375  
   376  // Just fetch all the posts from the db and join them with all the posts
   377  // in the mempool. Then sort them by their timestamp. This can be called
   378  // on an empty view or a view that already has a lot of transactions
   379  // applied to it.
   380  func (bav *UtxoView) GetAllPosts() (_corePosts []*PostEntry, _commentsByPostHash map[BlockHash][]*PostEntry, _err error) {
   381  	// Start by fetching all the posts we have in the db.
   382  	//
   383  	// TODO(performance): This currently fetches all posts. We should implement
   384  	// some kind of pagination instead though.
   385  	_, _, dbPostEntries, err := DBGetAllPostsByTstamp(bav.Handle, true /*fetchEntries*/)
   386  	if err != nil {
   387  		return nil, nil, errors.Wrapf(err, "GetAllPosts: Problem fetching PostEntry's from db: ")
   388  	}
   389  
   390  	// Iterate through the entries found in the db and force the view to load them.
   391  	// This fills in any gaps in the view so that, after this, the view should contain
   392  	// the union of what it had before plus what was in the db.
   393  	for _, dbPostEntry := range dbPostEntries {
   394  		bav.GetPostEntryForPostHash(dbPostEntry.PostHash)
   395  	}
   396  
   397  	// Do one more pass to load all the comments from the DB.
   398  	for _, postEntry := range bav.PostHashToPostEntry {
   399  		// Ignore deleted or rolled-back posts.
   400  		if postEntry.isDeleted {
   401  			continue
   402  		}
   403  
   404  		// If we have a post in the view and if that post is not a comment
   405  		// then fetch its attached comments from the db. We need to do this
   406  		// because the tstamp index above only fetches "core" posts not
   407  		// comments.
   408  
   409  		if len(postEntry.ParentStakeID) == 0 {
   410  			_, dbCommentHashes, _, err := DBGetCommentPostHashesForParentStakeID(
   411  				bav.Handle, postEntry.ParentStakeID, false /*fetchEntries*/)
   412  			if err != nil {
   413  				return nil, nil, errors.Wrapf(err, "GetAllPosts: Problem fetching comment PostEntry's from db: ")
   414  			}
   415  			for _, commentHash := range dbCommentHashes {
   416  				bav.GetPostEntryForPostHash(commentHash)
   417  			}
   418  		}
   419  	}
   420  
   421  	allCorePosts := []*PostEntry{}
   422  	commentsByPostHash := make(map[BlockHash][]*PostEntry)
   423  	for _, postEntry := range bav.PostHashToPostEntry {
   424  		// Ignore deleted or rolled-back posts.
   425  		if postEntry.isDeleted {
   426  			continue
   427  		}
   428  
   429  		// Every post is either a core post or a comment. If it has a stake ID
   430  		// its a comment, and if it doesn't then it's a core post.
   431  		if len(postEntry.ParentStakeID) == 0 {
   432  			allCorePosts = append(allCorePosts, postEntry)
   433  		} else {
   434  			// Add the comment to our map.
   435  			commentsForPost := commentsByPostHash[*NewBlockHash(postEntry.ParentStakeID)]
   436  			commentsForPost = append(commentsForPost, postEntry)
   437  			commentsByPostHash[*NewBlockHash(postEntry.ParentStakeID)] = commentsForPost
   438  		}
   439  	}
   440  	// Sort all the comment lists as well. Here we put the latest comment at the
   441  	// end.
   442  	for _, commentList := range commentsByPostHash {
   443  		sort.Slice(commentList, func(ii, jj int) bool {
   444  			return commentList[ii].TimestampNanos < commentList[jj].TimestampNanos
   445  		})
   446  	}
   447  
   448  	return allCorePosts, commentsByPostHash, nil
   449  }
   450  
   451  func (bav *UtxoView) GetPostsPaginatedForPublicKeyOrderedByTimestamp(publicKey []byte, startPostHash *BlockHash, limit uint64, mediaRequired bool, nftRequired bool) (_posts []*PostEntry, _err error) {
   452  	if bav.Postgres != nil {
   453  		var startTime uint64 = math.MaxUint64
   454  		if startPostHash != nil {
   455  			startPostEntry := bav.GetPostEntryForPostHash(startPostHash)
   456  			startTime = startPostEntry.TimestampNanos
   457  		}
   458  		posts := bav.Postgres.GetPostsForPublicKey(publicKey, startTime, limit)
   459  		for _, post := range posts {
   460  			// TODO: Normalize this field so we get the correct number of results from the DB
   461  			if mediaRequired && !post.HasMedia() {
   462  				continue
   463  			}
   464  			// nftRequired set to determine if we only want posts that are NFTs
   465  			if nftRequired && !post.NFT {
   466  				continue
   467  			}
   468  			bav.setPostMappings(post)
   469  		}
   470  	} else {
   471  		handle := bav.Handle
   472  		dbPrefix := append([]byte{}, _PrefixPosterPublicKeyTimestampPostHash...)
   473  		dbPrefix = append(dbPrefix, publicKey...)
   474  		var prefix []byte
   475  		if startPostHash != nil {
   476  			startPostEntry := bav.GetPostEntryForPostHash(startPostHash)
   477  			if startPostEntry == nil {
   478  				return nil, fmt.Errorf("GetPostsPaginatedForPublicKeyOrderedByTimestamp: Invalid start post hash")
   479  			}
   480  			prefix = append(dbPrefix, EncodeUint64(startPostEntry.TimestampNanos)...)
   481  			prefix = append(prefix, startPostEntry.PostHash[:]...)
   482  		} else {
   483  			maxBigEndianUint64Bytes := []byte{0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}
   484  			prefix = append(dbPrefix, maxBigEndianUint64Bytes...)
   485  		}
   486  		timestampSizeBytes := 8
   487  		var posts []*PostEntry
   488  		err := handle.View(func(txn *badger.Txn) error {
   489  			opts := badger.DefaultIteratorOptions
   490  
   491  			opts.PrefetchValues = false
   492  
   493  			// Go in reverse order
   494  			opts.Reverse = true
   495  
   496  			it := txn.NewIterator(opts)
   497  			defer it.Close()
   498  			it.Seek(prefix)
   499  			if startPostHash != nil {
   500  				// Skip the first post if we have a startPostHash.
   501  				it.Next()
   502  			}
   503  			for ; it.ValidForPrefix(dbPrefix) && uint64(len(posts)) < limit; it.Next() {
   504  				rawKey := it.Item().Key()
   505  
   506  				keyWithoutPrefix := rawKey[1:]
   507  				//posterPublicKey := keyWithoutPrefix[:HashSizeBytes]
   508  				publicKeySizeBytes := HashSizeBytes + 1
   509  				//tstampNanos := DecodeUint64(keyWithoutPrefix[publicKeySizeBytes:(publicKeySizeBytes + timestampSizeBytes)])
   510  
   511  				postHash := &BlockHash{}
   512  				copy(postHash[:], keyWithoutPrefix[(publicKeySizeBytes+timestampSizeBytes):])
   513  				postEntry := bav.GetPostEntryForPostHash(postHash)
   514  				if postEntry == nil {
   515  					return fmt.Errorf("Missing post entry")
   516  				}
   517  				if postEntry.isDeleted || postEntry.ParentStakeID != nil || postEntry.IsHidden {
   518  					continue
   519  				}
   520  
   521  				// mediaRequired set to determine if we only want posts that include media and ignore posts without
   522  				if mediaRequired && !postEntry.HasMedia() {
   523  					continue
   524  				}
   525  
   526  				// nftRequired set to determine if we only want posts that are NFTs
   527  				if nftRequired && !postEntry.IsNFT {
   528  					continue
   529  				}
   530  
   531  				posts = append(posts, postEntry)
   532  			}
   533  			return nil
   534  		})
   535  		if err != nil {
   536  			return nil, err
   537  		}
   538  	}
   539  
   540  	var postEntries []*PostEntry
   541  	// Iterate over the view. Put all posts authored by the public key into our mempool posts slice
   542  	for _, postEntry := range bav.PostHashToPostEntry {
   543  		// Ignore deleted or hidden posts and any comments.
   544  		if postEntry.isDeleted || postEntry.IsHidden || len(postEntry.ParentStakeID) != 0 {
   545  			continue
   546  		}
   547  
   548  		// mediaRequired set to determine if we only want posts that include media and ignore posts without
   549  		if mediaRequired && !postEntry.HasMedia() {
   550  			continue
   551  		}
   552  
   553  		// nftRequired set to determine if we only want posts that are NFTs
   554  		if nftRequired && !postEntry.IsNFT {
   555  			continue
   556  		}
   557  
   558  		if reflect.DeepEqual(postEntry.PosterPublicKey, publicKey) {
   559  			postEntries = append(postEntries, postEntry)
   560  		}
   561  	}
   562  
   563  	return postEntries, nil
   564  }
   565  
   566  func (bav *UtxoView) GetDiamondSendersForPostHash(postHash *BlockHash) (_pkidToDiamondLevel map[PKID]int64, _err error) {
   567  	handle := bav.Handle
   568  	dbPrefix := append([]byte{}, _PrefixDiamondedPostHashDiamonderPKIDDiamondLevel...)
   569  	dbPrefix = append(dbPrefix, postHash[:]...)
   570  	keysFound, _ := EnumerateKeysForPrefix(handle, dbPrefix)
   571  
   572  	diamondPostEntry := bav.GetPostEntryForPostHash(postHash)
   573  	receiverPKIDEntry := bav.GetPKIDForPublicKey(diamondPostEntry.PosterPublicKey)
   574  
   575  	// Iterate over all the db keys & values and load them into the view.
   576  	expectedKeyLength := 1 + HashSizeBytes + btcec.PubKeyBytesLenCompressed + 8
   577  	for _, key := range keysFound {
   578  		// Sanity check that this is a reasonable key.
   579  		if len(key) != expectedKeyLength {
   580  			return nil, fmt.Errorf("UtxoView.GetDiamondsForPostHash: Invalid key length found: %d", len(key))
   581  		}
   582  
   583  		senderPKID := &PKID{}
   584  		copy(senderPKID[:], key[1+HashSizeBytes:])
   585  
   586  		diamondKey := &DiamondKey{
   587  			SenderPKID:      *senderPKID,
   588  			ReceiverPKID:    *receiverPKIDEntry.PKID,
   589  			DiamondPostHash: *postHash,
   590  		}
   591  
   592  		bav.GetDiamondEntryForDiamondKey(diamondKey)
   593  	}
   594  
   595  	// Iterate over the view and create the final map to return.
   596  	pkidToDiamondLevel := make(map[PKID]int64)
   597  	for _, diamondEntry := range bav.DiamondKeyToDiamondEntry {
   598  		if !diamondEntry.isDeleted && reflect.DeepEqual(diamondEntry.DiamondPostHash[:], postHash[:]) {
   599  			pkidToDiamondLevel[*diamondEntry.SenderPKID] = diamondEntry.DiamondLevel
   600  		}
   601  	}
   602  
   603  	return pkidToDiamondLevel, nil
   604  }
   605  
   606  func (bav *UtxoView) GetRepostsForPostHash(postHash *BlockHash) (_reposterPubKeys [][]byte, _err error) {
   607  	handle := bav.Handle
   608  	dbPrefix := append([]byte{}, _PrefixRepostedPostHashReposterPubKey...)
   609  	dbPrefix = append(dbPrefix, postHash[:]...)
   610  	keysFound, _ := EnumerateKeysForPrefix(handle, dbPrefix)
   611  
   612  	// Iterate over all the db keys & values and load them into the view.
   613  	expectedKeyLength := 1 + HashSizeBytes + btcec.PubKeyBytesLenCompressed
   614  	for _, key := range keysFound {
   615  		// Sanity check that this is a reasonable key.
   616  		if len(key) != expectedKeyLength {
   617  			return nil, fmt.Errorf("UtxoView.GetRepostersForPostHash: Invalid key length found: %d", len(key))
   618  		}
   619  
   620  		reposterPubKey := key[1+HashSizeBytes:]
   621  
   622  		repostKey := &RepostKey{
   623  			ReposterPubKey:   MakePkMapKey(reposterPubKey),
   624  			RepostedPostHash: *postHash,
   625  		}
   626  
   627  		bav._getRepostEntryForRepostKey(repostKey)
   628  	}
   629  
   630  	// Iterate over the view and create the final list to return.
   631  	reposterPubKeys := [][]byte{}
   632  	for _, repostEntry := range bav.RepostKeyToRepostEntry {
   633  		if !repostEntry.isDeleted && reflect.DeepEqual(repostEntry.RepostedPostHash[:], postHash[:]) {
   634  			reposterPubKeys = append(reposterPubKeys, repostEntry.ReposterPubKey)
   635  		}
   636  	}
   637  
   638  	return reposterPubKeys, nil
   639  }
   640  
   641  func (bav *UtxoView) GetQuoteRepostsForPostHash(postHash *BlockHash,
   642  ) (_quoteReposterPubKeys [][]byte, _quoteReposterPubKeyToPosts map[PkMapKey][]*PostEntry, _err error) {
   643  	handle := bav.Handle
   644  	dbPrefix := append([]byte{}, _PrefixRepostedPostHashReposterPubKeyRepostPostHash...)
   645  	dbPrefix = append(dbPrefix, postHash[:]...)
   646  	keysFound, _ := EnumerateKeysForPrefix(handle, dbPrefix)
   647  
   648  	// Iterate over all the db keys & values and load them into the view.
   649  	expectedKeyLength := 1 + HashSizeBytes + btcec.PubKeyBytesLenCompressed + HashSizeBytes
   650  
   651  	repostPostHashIdx := 1 + HashSizeBytes + btcec.PubKeyBytesLenCompressed
   652  	for _, key := range keysFound {
   653  		// Sanity check that this is a reasonable key.
   654  		if len(key) != expectedKeyLength {
   655  			return nil, nil, fmt.Errorf("UtxoView.GetQuoteRepostsForPostHash: Invalid key length found: %d", len(key))
   656  		}
   657  
   658  		repostPostHash := &BlockHash{}
   659  		copy(repostPostHash[:], key[repostPostHashIdx:])
   660  
   661  		bav.GetPostEntryForPostHash(repostPostHash)
   662  	}
   663  
   664  	// Iterate over the view and create the final map to return.
   665  	quoteReposterPubKeys := [][]byte{}
   666  	quoteReposterPubKeyToPosts := make(map[PkMapKey][]*PostEntry)
   667  
   668  	for _, postEntry := range bav.PostHashToPostEntry {
   669  		if !postEntry.isDeleted && postEntry.IsQuotedRepost && reflect.DeepEqual(postEntry.RepostedPostHash[:], postHash[:]) {
   670  			quoteReposterPubKeys = append(quoteReposterPubKeys, postEntry.PosterPublicKey)
   671  
   672  			quoteRepostPosts, _ := quoteReposterPubKeyToPosts[MakePkMapKey(postEntry.PosterPublicKey)]
   673  			quoteRepostPosts = append(quoteRepostPosts, postEntry)
   674  			quoteReposterPubKeyToPosts[MakePkMapKey(postEntry.PosterPublicKey)] = quoteRepostPosts
   675  		}
   676  	}
   677  
   678  	return quoteReposterPubKeys, quoteReposterPubKeyToPosts, nil
   679  }
   680  
   681  func (bav *UtxoView) _connectSubmitPost(
   682  	txn *MsgDeSoTxn, txHash *BlockHash, blockHeight uint32,
   683  	verifySignatures bool, ignoreUtxos bool) (
   684  	_totalInput uint64, _totalOutput uint64, _utxoOps []*UtxoOperation, _err error) {
   685  
   686  	// Check that the transaction has the right TxnType.
   687  	if txn.TxnMeta.GetTxnType() != TxnTypeSubmitPost {
   688  		return 0, 0, nil, fmt.Errorf("_connectSubmitPost: called with bad TxnType %s",
   689  			txn.TxnMeta.GetTxnType().String())
   690  	}
   691  	txMeta := txn.TxnMeta.(*SubmitPostMetadata)
   692  
   693  	// Connect basic txn to get the total input and the total output without
   694  	// considering the transaction metadata.
   695  	//
   696  	// The ignoreUtxos flag is used to connect "seed" transactions when initializing
   697  	// the blockchain. It allows us to "seed" the database with posts and profiles
   698  	// when we do a hard fork, without having the transactions rejected due to their
   699  	// not being spendable.
   700  	var totalInput, totalOutput uint64
   701  	var utxoOpsForTxn = []*UtxoOperation{}
   702  	var err error
   703  	if !ignoreUtxos {
   704  		totalInput, totalOutput, utxoOpsForTxn, err = bav._connectBasicTransfer(
   705  			txn, txHash, blockHeight, verifySignatures)
   706  		if err != nil {
   707  			return 0, 0, nil, errors.Wrapf(err, "_connectSubmitPost: ")
   708  		}
   709  
   710  		// Force the input to be non-zero so that we can prevent replay attacks.
   711  		if totalInput == 0 {
   712  			return 0, 0, nil, RuleErrorSubmitPostRequiresNonZeroInput
   713  		}
   714  	}
   715  
   716  	// Transaction extra data contains both consensus-related, such as repost info, and additional information about a post,
   717  	// whereas PostExtraData is an attribute of a PostEntry that contains only non-consensus related
   718  	// information about a post, such as a link to a video that is embedded.
   719  	extraData := make(map[string][]byte)
   720  	for k, v := range txn.ExtraData {
   721  		extraData[k] = v
   722  	}
   723  	// Set the IsQuotedRepost attribute of postEntry based on extra data
   724  	isQuotedRepost := false
   725  	if quotedRepost, hasQuotedRepost := extraData[IsQuotedRepostKey]; hasQuotedRepost {
   726  		if reflect.DeepEqual(quotedRepost, QuotedRepostVal) {
   727  			isQuotedRepost = true
   728  		}
   729  		// Delete key since it is not needed in the PostExtraData map as IsQuotedRepost is involved in consensus code.
   730  		delete(extraData, IsQuotedRepostKey)
   731  	}
   732  	var repostedPostHash *BlockHash
   733  	if repostedPostHashBytes, isRepost := extraData[RepostedPostHash]; isRepost {
   734  		repostedPostHash = &BlockHash{}
   735  		copy(repostedPostHash[:], repostedPostHashBytes)
   736  		delete(extraData, RepostedPostHash)
   737  	}
   738  
   739  	// At this point the inputs and outputs have been processed. Now we
   740  	// need to handle the metadata.
   741  
   742  	// If the metadata has a PostHashToModify then treat it as modifying an
   743  	// existing post rather than creating a new post.
   744  	var prevPostEntry *PostEntry
   745  	var prevParentPostEntry *PostEntry
   746  	var prevGrandparentPostEntry *PostEntry
   747  	var prevRepostedPostEntry *PostEntry
   748  	var prevRepostEntry *RepostEntry
   749  
   750  	var newPostEntry *PostEntry
   751  	var newParentPostEntry *PostEntry
   752  	var newGrandparentPostEntry *PostEntry
   753  	var newRepostedPostEntry *PostEntry
   754  	var newRepostEntry *RepostEntry
   755  	if len(txMeta.PostHashToModify) != 0 {
   756  		// Make sure the post hash is valid
   757  		if len(txMeta.PostHashToModify) != HashSizeBytes {
   758  			return 0, 0, nil, errors.Wrapf(
   759  				RuleErrorSubmitPostInvalidPostHashToModify,
   760  				"_connectSubmitPost: Bad post hash: %#v", txMeta.PostHashToModify)
   761  		}
   762  
   763  		// Get the existing post entry, which must exist and be undeleted.
   764  		postHash := &BlockHash{}
   765  		copy(postHash[:], txMeta.PostHashToModify[:])
   766  		existingPostEntryy := bav.GetPostEntryForPostHash(postHash)
   767  		if existingPostEntryy == nil || existingPostEntryy.isDeleted {
   768  			return 0, 0, nil, errors.Wrapf(
   769  				RuleErrorSubmitPostModifyingNonexistentPost,
   770  				"_connectSubmitPost: Post hash: %v", postHash)
   771  		}
   772  
   773  		// Post modification is only allowed by the original poster.
   774  		if !reflect.DeepEqual(txn.PublicKey, existingPostEntryy.PosterPublicKey) {
   775  
   776  			return 0, 0, nil, errors.Wrapf(
   777  				RuleErrorSubmitPostPostModificationNotAuthorized,
   778  				"_connectSubmitPost: Post hash: %v, poster public key: %v, "+
   779  					"txn public key: %v, paramUpdater: %v", postHash,
   780  				PkToStringBoth(existingPostEntryy.PosterPublicKey),
   781  				PkToStringBoth(txn.PublicKey), spew.Sdump(bav.Params.ParamUpdaterPublicKeys))
   782  		}
   783  
   784  		// Modification of an NFT is not allowed.
   785  		if existingPostEntryy.IsNFT {
   786  			return 0, 0, nil, errors.Wrapf(RuleErrorSubmitPostCannotUpdateNFT, "_connectSubmitPost: ")
   787  		}
   788  
   789  		// It's an error if we are updating the value of RepostedPostHash. A post can only ever repost a single post.
   790  		if !reflect.DeepEqual(repostedPostHash, existingPostEntryy.RepostedPostHash) {
   791  			return 0, 0, nil, errors.Wrapf(
   792  				RuleErrorSubmitPostUpdateRepostHash,
   793  				"_connectSubmitPost: cannot update reposted post hash when updating a post")
   794  		}
   795  
   796  		// It's an error if we are updating the value of IsQuotedRepost.
   797  		if isQuotedRepost != existingPostEntryy.IsQuotedRepost {
   798  			return 0, 0, nil, errors.Wrapf(
   799  				RuleErrorSubmitPostUpdateIsQuotedRepost,
   800  				"_connectSubmitPost: cannot update isQuotedRepost attribute of post when updating a post")
   801  		}
   802  
   803  		// Save the data from the post. Note that we don't make a deep copy
   804  		// because all the fields that we modify are non-pointer fields.
   805  		prevPostEntry = &PostEntry{}
   806  		*prevPostEntry = *existingPostEntryy
   807  
   808  		// Set the newPostEntry pointer to the existing entry
   809  		newPostEntry = existingPostEntryy
   810  
   811  		// The field values should have already been validated so set
   812  		// them.
   813  		if len(txMeta.Body) != 0 {
   814  			newPostEntry.Body = txMeta.Body
   815  		}
   816  
   817  		// Merge the remaining attributes of the transaction's ExtraData into the postEntry's PostExtraData map.
   818  		if len(extraData) > 0 {
   819  			newPostExtraData := make(map[string][]byte)
   820  			for k, v := range existingPostEntryy.PostExtraData {
   821  				newPostExtraData[k] = v
   822  			}
   823  			for k, v := range extraData {
   824  				// If we're given a value with length greater than 0, add it to the map.
   825  				if len(v) > 0 {
   826  					newPostExtraData[k] = v
   827  				} else {
   828  					// If the value we're given has a length of 0, this indicates that we should delete it if it exists.
   829  					delete(newPostExtraData, k)
   830  				}
   831  			}
   832  			newPostEntry.PostExtraData = newPostExtraData
   833  		}
   834  		// TODO: Right now a post can be undeleted by the owner of the post,
   835  		// which seems like undesired behavior if a paramUpdater is trying to reduce
   836  		// spam
   837  		newPostEntry.IsHidden = txMeta.IsHidden
   838  
   839  		// Obtain the parent posts
   840  		newParentPostEntry, newGrandparentPostEntry, err = bav._getParentAndGrandparentPostEntry(newPostEntry)
   841  		if err != nil {
   842  			return 0, 0, nil, errors.Wrapf(err, "_connectSubmitPost: error with _getParentAndGrandparentPostEntry: %v", postHash)
   843  		}
   844  
   845  		if newPostEntry.RepostedPostHash != nil {
   846  			newRepostedPostEntry = bav.GetPostEntryForPostHash(newPostEntry.RepostedPostHash)
   847  		}
   848  
   849  		// Figure out how much we need to change the parent / grandparent's comment count by
   850  		var commentCountUpdateAmount int
   851  		repostCountUpdateAmount := 0
   852  		quoteRepostCountUpdateAmount := 0
   853  		hidingPostEntry := !prevPostEntry.IsHidden && newPostEntry.IsHidden
   854  		if hidingPostEntry {
   855  			// If we're hiding a post then we need to decrement the comment count of the parent
   856  			// and grandparent posts.
   857  			commentCountUpdateAmount = -1 * int(1+prevPostEntry.CommentCount)
   858  
   859  			// If we're hiding a post that is a vanilla repost of another post, we decrement the repost count of the
   860  			// post that was reposted.
   861  			if IsVanillaRepost(newPostEntry) {
   862  				repostCountUpdateAmount = -1
   863  			} else if isQuotedRepost {
   864  				quoteRepostCountUpdateAmount = -1
   865  			}
   866  		}
   867  
   868  		unhidingPostEntry := prevPostEntry.IsHidden && !newPostEntry.IsHidden
   869  		if unhidingPostEntry {
   870  			// If we're unhiding a post then we need to increment the comment count of the parent
   871  			// and grandparent posts.
   872  			commentCountUpdateAmount = int(1 + prevPostEntry.CommentCount)
   873  			// If we are unhiding a post that is a vanilla repost of another post, we increment the repost count of
   874  			// the post that was reposted.
   875  			if IsVanillaRepost(newPostEntry) {
   876  				repostCountUpdateAmount = 1
   877  			} else if isQuotedRepost {
   878  				quoteRepostCountUpdateAmount = 1
   879  			}
   880  		}
   881  
   882  		// Save the data from the parent post. Note that we don't make a deep copy
   883  		// because all the fields that we modify are non-pointer fields.
   884  		if newParentPostEntry != nil {
   885  			prevParentPostEntry = &PostEntry{}
   886  			*prevParentPostEntry = *newParentPostEntry
   887  			bav._updateParentCommentCountForPost(newPostEntry, newParentPostEntry, commentCountUpdateAmount)
   888  		}
   889  
   890  		// Save the data from the grandparent post. Note that we don't make a deep copy
   891  		// because all the fields that we modify are non-pointer fields.
   892  		if newGrandparentPostEntry != nil {
   893  			prevGrandparentPostEntry = &PostEntry{}
   894  			*prevGrandparentPostEntry = *newGrandparentPostEntry
   895  			bav._updateParentCommentCountForPost(newPostEntry, newGrandparentPostEntry, commentCountUpdateAmount)
   896  		}
   897  		if newRepostedPostEntry != nil {
   898  			prevRepostedPostEntry = &PostEntry{}
   899  			*prevRepostedPostEntry = *newRepostedPostEntry
   900  			// If the previous post entry is a vanilla repost, we can set the prevRepostEntry.
   901  			if IsVanillaRepost(prevPostEntry) {
   902  				prevRepostKey := MakeRepostKey(prevPostEntry.PosterPublicKey, *prevPostEntry.RepostedPostHash)
   903  				prevRepostEntry = bav._getRepostEntryForRepostKey(&prevRepostKey)
   904  				if prevRepostEntry == nil {
   905  					return 0, 0, nil, fmt.Errorf("prevRepostEntry not found for prevPostEntry")
   906  				}
   907  				// Generally prevRepostEntry is identical to newRepostEntry. Currently, we enforce a check that
   908  				// the RepostedPostHash does not get modified when attempting to connect a submitPost transaction
   909  				newRepostEntry = &RepostEntry{
   910  					ReposterPubKey:   newPostEntry.PosterPublicKey,
   911  					RepostedPostHash: newPostEntry.RepostedPostHash,
   912  					RepostPostHash:   newPostEntry.PostHash,
   913  				}
   914  
   915  				// Update the repost count if it has changed.
   916  				bav._updateRepostCount(newRepostedPostEntry, repostCountUpdateAmount)
   917  			} else {
   918  				// Update the quote repost count if it has changed.
   919  				bav._updateQuoteRepostCount(newRepostedPostEntry, quoteRepostCountUpdateAmount)
   920  			}
   921  		}
   922  	} else {
   923  		// In this case we are creating a post from scratch so validate
   924  		// all the fields.
   925  
   926  		// StakeMultipleBasisPoints > 0 < max
   927  		// Between 1x = 100% and 10x = 10,000%
   928  		if txMeta.StakeMultipleBasisPoints < 100*100 ||
   929  			txMeta.StakeMultipleBasisPoints > bav.Params.MaxStakeMultipleBasisPoints {
   930  
   931  			return 0, 0, nil, errors.Wrapf(RuleErrorSubmitPostStakeMultipleSize,
   932  				"_connectSubmitPost: Invalid StakeMultipleSize: %d",
   933  				txMeta.StakeMultipleBasisPoints)
   934  		}
   935  		// CreatorBasisPoints > 0 < max
   936  		if txMeta.CreatorBasisPoints < 0 ||
   937  			txMeta.CreatorBasisPoints > bav.Params.MaxCreatorBasisPoints {
   938  
   939  			return 0, 0, nil, errors.Wrapf(RuleErrorSubmitPostCreatorPercentageSize,
   940  				"_connectSubmitPost: Invalid CreatorPercentageSize: %d",
   941  				txMeta.CreatorBasisPoints)
   942  		}
   943  		// TstampNanos != 0
   944  		if txMeta.TimestampNanos == 0 {
   945  			return 0, 0, nil, errors.Wrapf(RuleErrorSubmitPostTimestampIsZero,
   946  				"_connectSubmitPost: Invalid Timestamp: %d",
   947  				txMeta.TimestampNanos)
   948  		}
   949  		// The parent stake id should be a block hash or profile public key if it's set.
   950  		if len(txMeta.ParentStakeID) != 0 && len(txMeta.ParentStakeID) != HashSizeBytes &&
   951  			len(txMeta.ParentStakeID) != btcec.PubKeyBytesLenCompressed {
   952  			return 0, 0, nil, errors.Wrapf(RuleErrorSubmitPostInvalidParentStakeIDLength,
   953  				"_connectSubmitPost: Parent stake ID length %v must be either 0 or %v or %v",
   954  				len(txMeta.ParentStakeID), HashSizeBytes, btcec.PubKeyBytesLenCompressed)
   955  		}
   956  
   957  		// The PostHash is just the transaction hash.
   958  		postHash := txHash
   959  		existingPostEntry := bav.GetPostEntryForPostHash(postHash)
   960  		if existingPostEntry != nil && !existingPostEntry.isDeleted {
   961  			return 0, 0, nil, errors.Wrapf(
   962  				RuleErrorPostAlreadyExists,
   963  				"_connectSubmitPost: Post hash: %v", postHash)
   964  		}
   965  
   966  		if repostedPostHash != nil {
   967  			newRepostedPostEntry = bav.GetPostEntryForPostHash(repostedPostHash)
   968  			// It is an error if a post entry attempts to repost a post that does not exist.
   969  			if newRepostedPostEntry == nil {
   970  				return 0, 0, nil, RuleErrorSubmitPostRepostPostNotFound
   971  			}
   972  			// It is an error if a post is trying to repost a vanilla repost.
   973  			if IsVanillaRepost(newRepostedPostEntry) {
   974  				return 0, 0, nil, RuleErrorSubmitPostRepostOfRepost
   975  			}
   976  		}
   977  
   978  		// Set the post entry pointer to a brand new post.
   979  		newPostEntry = &PostEntry{
   980  			PostHash:                 postHash,
   981  			PosterPublicKey:          txn.PublicKey,
   982  			ParentStakeID:            txMeta.ParentStakeID,
   983  			Body:                     txMeta.Body,
   984  			RepostedPostHash:         repostedPostHash,
   985  			IsQuotedRepost:           isQuotedRepost,
   986  			CreatorBasisPoints:       txMeta.CreatorBasisPoints,
   987  			StakeMultipleBasisPoints: txMeta.StakeMultipleBasisPoints,
   988  			TimestampNanos:           txMeta.TimestampNanos,
   989  			ConfirmationBlockHeight:  blockHeight,
   990  			PostExtraData:            extraData,
   991  			// Don't set IsHidden on new posts.
   992  		}
   993  
   994  		// Obtain the parent posts
   995  		newParentPostEntry, newGrandparentPostEntry, err = bav._getParentAndGrandparentPostEntry(newPostEntry)
   996  		if err != nil {
   997  			return 0, 0, nil, errors.Wrapf(err, "_connectSubmitPost: error with _getParentAndGrandparentPostEntry: %v", postHash)
   998  		}
   999  
  1000  		// Save the data from the parent posts. Note that we don't make a deep copy
  1001  		// because all the fields that we modify are non-pointer fields.
  1002  		if newParentPostEntry != nil {
  1003  			prevParentPostEntry = &PostEntry{}
  1004  			*prevParentPostEntry = *newParentPostEntry
  1005  			bav._updateParentCommentCountForPost(newPostEntry, newParentPostEntry, 1 /*amountToChangeParentBy*/)
  1006  		}
  1007  
  1008  		if newGrandparentPostEntry != nil {
  1009  			prevGrandparentPostEntry = &PostEntry{}
  1010  			*prevGrandparentPostEntry = *newGrandparentPostEntry
  1011  			bav._updateParentCommentCountForPost(newPostEntry, newGrandparentPostEntry, 1 /*amountToChangeParentBy*/)
  1012  		}
  1013  
  1014  		// Save the data from the reposted post.
  1015  		if newRepostedPostEntry != nil {
  1016  			prevRepostedPostEntry = &PostEntry{}
  1017  			*prevRepostedPostEntry = *newRepostedPostEntry
  1018  
  1019  			// We only set repost entry mappings and increment counts for vanilla reposts.
  1020  			if !isQuotedRepost {
  1021  				// Increment the repost count of the post that was reposted by 1 as we are creating a new
  1022  				// vanilla repost.
  1023  				bav._updateRepostCount(newRepostedPostEntry, 1)
  1024  				// Create the new repostEntry
  1025  				newRepostEntry = &RepostEntry{
  1026  					ReposterPubKey:   newPostEntry.PosterPublicKey,
  1027  					RepostedPostHash: newPostEntry.RepostedPostHash,
  1028  					RepostPostHash:   newPostEntry.PostHash,
  1029  				}
  1030  			} else {
  1031  				// If it is a quote repost, we need to increment the corresponding count.
  1032  				bav._updateQuoteRepostCount(newRepostedPostEntry, 1)
  1033  			}
  1034  		}
  1035  	}
  1036  
  1037  	if verifySignatures {
  1038  		// _connectBasicTransfer has already checked that the transaction is
  1039  		// signed by the top-level public key, which we take to be the poster's
  1040  		// public key.
  1041  	}
  1042  
  1043  	// Set the mappings for the entry regardless of whether we modified it or
  1044  	// created it from scratch.
  1045  	bav._setPostEntryMappings(newPostEntry)
  1046  	if newParentPostEntry != nil {
  1047  		bav._setPostEntryMappings(newParentPostEntry)
  1048  	}
  1049  	if newGrandparentPostEntry != nil {
  1050  		bav._setPostEntryMappings(newGrandparentPostEntry)
  1051  	}
  1052  	if newRepostedPostEntry != nil {
  1053  		bav._setPostEntryMappings(newRepostedPostEntry)
  1054  	}
  1055  
  1056  	if newRepostEntry != nil {
  1057  		bav._setRepostEntryMappings(newRepostEntry)
  1058  	}
  1059  
  1060  	// Add an operation to the list at the end indicating we've added a post.
  1061  	utxoOpsForTxn = append(utxoOpsForTxn, &UtxoOperation{
  1062  		// PrevPostEntry should generally be nil when we created a new post from
  1063  		// scratch, but non-nil if we modified an existing post.
  1064  		PrevPostEntry:            prevPostEntry,
  1065  		PrevParentPostEntry:      prevParentPostEntry,
  1066  		PrevGrandparentPostEntry: prevGrandparentPostEntry,
  1067  		PrevRepostedPostEntry:    prevRepostedPostEntry,
  1068  		PrevRepostEntry:          prevRepostEntry,
  1069  		Type:                     OperationTypeSubmitPost,
  1070  	})
  1071  
  1072  	return totalInput, totalOutput, utxoOpsForTxn, nil
  1073  }
  1074  
  1075  func (bav *UtxoView) _getParentAndGrandparentPostEntry(postEntry *PostEntry) (
  1076  	_parentPostEntry *PostEntry, _grandparentPostEntry *PostEntry, _err error) {
  1077  	var parentPostEntry *PostEntry
  1078  	var grandparentPostEntry *PostEntry
  1079  
  1080  	// This length check ensures that the parent is a post (and not something else, like a profile)
  1081  	//
  1082  	// If we ever allow commenting on something else such that the parent is not a post, but where
  1083  	// ParentStakeID is also HashSizeBytes, then this logic would likely need to be changed.
  1084  	if len(postEntry.ParentStakeID) == HashSizeBytes {
  1085  		parentPostEntry = bav.GetPostEntryForPostHash(NewBlockHash(postEntry.ParentStakeID))
  1086  		if parentPostEntry == nil {
  1087  			return nil, nil, errors.Wrapf(
  1088  				RuleErrorSubmitPostParentNotFound,
  1089  				"_getParentAndGrandparentPostEntry: failed to find parent post for post hash: %v, parentStakeId: %v",
  1090  				postEntry.PostHash, hex.EncodeToString(postEntry.ParentStakeID),
  1091  			)
  1092  		}
  1093  	}
  1094  
  1095  	if parentPostEntry != nil && len(parentPostEntry.ParentStakeID) == HashSizeBytes {
  1096  		grandparentPostEntry = bav.GetPostEntryForPostHash(NewBlockHash(parentPostEntry.ParentStakeID))
  1097  		if grandparentPostEntry == nil {
  1098  			return nil, nil, errors.Wrapf(
  1099  				RuleErrorSubmitPostParentNotFound,
  1100  				"_getParentAndGrandparentPostEntry: failed to find grandparent post for post hash: %v, parentStakeId: %v, grandparentStakeId: %v",
  1101  				postEntry.PostHash, postEntry.ParentStakeID, parentPostEntry.ParentStakeID,
  1102  			)
  1103  		}
  1104  	}
  1105  
  1106  	return parentPostEntry, grandparentPostEntry, nil
  1107  }
  1108  
  1109  // Adds amount to the repost count of the post at repostPostHash
  1110  func (bav *UtxoView) _updateRepostCount(repostedPost *PostEntry, amount int) {
  1111  	result := int(repostedPost.RepostCount) + amount
  1112  
  1113  	// Repost count should never be below 0.
  1114  	if result < 0 {
  1115  		glog.Errorf("_updateRepostCountForPost: RepostCount < 0 for result %v, repost post hash: %v, amount : %v",
  1116  			result, repostedPost, amount)
  1117  		result = 0
  1118  	}
  1119  	repostedPost.RepostCount = uint64(result)
  1120  
  1121  }
  1122  
  1123  // Adds amount to the quote repost count of the post at repostPostHash
  1124  func (bav *UtxoView) _updateQuoteRepostCount(repostedPost *PostEntry, amount int) {
  1125  	result := int(repostedPost.QuoteRepostCount) + amount
  1126  
  1127  	// Repost count should never be below 0.
  1128  	if result < 0 {
  1129  		glog.Errorf("_updateQuoteRepostCountForPost: QuoteRepostCount < 0 for result %v, repost post hash: %v, amount : %v",
  1130  			result, repostedPost, amount)
  1131  		result = 0
  1132  	}
  1133  	repostedPost.QuoteRepostCount = uint64(result)
  1134  
  1135  }
  1136  
  1137  func (bav *UtxoView) _updateParentCommentCountForPost(postEntry *PostEntry, parentPostEntry *PostEntry, amountToChangeParentBy int) {
  1138  	result := int(parentPostEntry.CommentCount) + amountToChangeParentBy
  1139  	if result < 0 {
  1140  		glog.Errorf("_updateParentCommentCountForPost: CommentCount < 0 for result %v, postEntry hash: %v, parentPostEntry hash: %v, amountToChangeParentBy: %v",
  1141  			result, postEntry.PostHash, parentPostEntry.PostHash, amountToChangeParentBy)
  1142  		result = 0
  1143  	}
  1144  
  1145  	parentPostEntry.CommentCount = uint64(result)
  1146  }
  1147  
  1148  func (bav *UtxoView) _disconnectSubmitPost(
  1149  	operationType OperationType, currentTxn *MsgDeSoTxn, txnHash *BlockHash,
  1150  	utxoOpsForTxn []*UtxoOperation, blockHeight uint32) error {
  1151  
  1152  	// Verify that the last operation is a SubmitPost operation
  1153  	if len(utxoOpsForTxn) == 0 {
  1154  		return fmt.Errorf("_disconnectSubmitPost: utxoOperations are missing")
  1155  	}
  1156  	operationIndex := len(utxoOpsForTxn) - 1
  1157  	currentOperation := utxoOpsForTxn[operationIndex]
  1158  	if currentOperation.Type != OperationTypeSubmitPost {
  1159  		return fmt.Errorf("_disconnectSubmitPost: Trying to revert "+
  1160  			"OperationTypeSubmitPost but found type %v",
  1161  			currentOperation.Type)
  1162  	}
  1163  
  1164  	// Now we know the txMeta is SubmitPost
  1165  	txMeta := currentTxn.TxnMeta.(*SubmitPostMetadata)
  1166  
  1167  	// The post hash is either the transaction hash or the hash set
  1168  	// in the metadata.
  1169  	postHashModified := txnHash
  1170  	if len(txMeta.PostHashToModify) != 0 {
  1171  		postHashModified = &BlockHash{}
  1172  		copy(postHashModified[:], txMeta.PostHashToModify[:])
  1173  	}
  1174  
  1175  	// Get the PostEntry. If we don't find
  1176  	// it or if it has isDeleted=true that's an error.
  1177  	postEntry := bav.GetPostEntryForPostHash(postHashModified)
  1178  	if postEntry == nil || postEntry.isDeleted {
  1179  		return fmt.Errorf("_disconnectSubmitPost: PostEntry for "+
  1180  			"Post Hash %v was found to be nil or deleted: %v",
  1181  			&txnHash, postEntry)
  1182  	}
  1183  
  1184  	// Delete repost mappings if they exist. They will be added back later if there is a previous version of this
  1185  	// postEntry
  1186  	if IsVanillaRepost(postEntry) {
  1187  		repostKey := MakeRepostKey(postEntry.PosterPublicKey, *postEntry.RepostedPostHash)
  1188  		repostEntry := bav._getRepostEntryForRepostKey(&repostKey)
  1189  		if repostEntry == nil {
  1190  			return fmt.Errorf("_disconnectSubmitPost: RepostEntry for "+
  1191  				"Post Has %v could not be found: %v", &txnHash, postEntry)
  1192  		}
  1193  		bav._deleteRepostEntryMappings(repostEntry)
  1194  	}
  1195  
  1196  	// Now that we are confident the PostEntry lines up with the transaction we're
  1197  	// rolling back, use the entry to delete the mappings for this post.
  1198  	//
  1199  	// Note: We don't need to delete the existing PostEntry mappings for parent and grandparent
  1200  	// before setting the prev mappings because the only thing that could have changed is the
  1201  	// comment count, which isn't indexed.
  1202  	bav._deletePostEntryMappings(postEntry)
  1203  
  1204  	// If we have a non-nil previous post entry then set the mappings for
  1205  	// that.
  1206  	if currentOperation.PrevPostEntry != nil {
  1207  		bav._setPostEntryMappings(currentOperation.PrevPostEntry)
  1208  	}
  1209  	if currentOperation.PrevParentPostEntry != nil {
  1210  		bav._setPostEntryMappings(currentOperation.PrevParentPostEntry)
  1211  	}
  1212  	if currentOperation.PrevGrandparentPostEntry != nil {
  1213  		bav._setPostEntryMappings(currentOperation.PrevGrandparentPostEntry)
  1214  	}
  1215  	if currentOperation.PrevRepostedPostEntry != nil {
  1216  		bav._setPostEntryMappings(currentOperation.PrevRepostedPostEntry)
  1217  	}
  1218  	if currentOperation.PrevRepostEntry != nil {
  1219  		bav._setRepostEntryMappings(currentOperation.PrevRepostEntry)
  1220  	}
  1221  
  1222  	// Now revert the basic transfer with the remaining operations. Cut off
  1223  	// the SubmitPost operation at the end since we just reverted it.
  1224  	return bav._disconnectBasicTransfer(
  1225  		currentTxn, txnHash, utxoOpsForTxn[:operationIndex], blockHeight)
  1226  }