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

     1  package lib
     2  
     3  import (
     4  	"fmt"
     5  	"github.com/btcsuite/btcd/btcec"
     6  	"github.com/golang/glog"
     7  	"github.com/pkg/errors"
     8  	"math"
     9  	"math/big"
    10  	"reflect"
    11  )
    12  
    13  func (bav *UtxoView) _setNFTEntryMappings(nftEntry *NFTEntry) {
    14  	// This function shouldn't be called with nil.
    15  	if nftEntry == nil {
    16  		glog.Errorf("_setNFTEntryMappings: Called with nil NFTEntry; " +
    17  			"this should never happen.")
    18  		return
    19  	}
    20  
    21  	nftKey := MakeNFTKey(nftEntry.NFTPostHash, nftEntry.SerialNumber)
    22  	bav.NFTKeyToNFTEntry[nftKey] = nftEntry
    23  }
    24  
    25  func (bav *UtxoView) _deleteNFTEntryMappings(nftEntry *NFTEntry) {
    26  
    27  	// Create a tombstone entry.
    28  	tombstoneNFTEntry := *nftEntry
    29  	tombstoneNFTEntry.isDeleted = true
    30  
    31  	// Set the mappings to point to the tombstone entry.
    32  	bav._setNFTEntryMappings(&tombstoneNFTEntry)
    33  }
    34  
    35  func (bav *UtxoView) GetNFTEntryForNFTKey(nftKey *NFTKey) *NFTEntry {
    36  	// If an entry exists in the in-memory map, return the value of that mapping.
    37  	mapValue, existsMapValue := bav.NFTKeyToNFTEntry[*nftKey]
    38  	if existsMapValue {
    39  		return mapValue
    40  	}
    41  
    42  	// If we get here it means no value exists in our in-memory map. In this case,
    43  	// defer to the db. If a mapping exists in the db, return it. If not, return
    44  	// nil.
    45  	var nftEntry *NFTEntry
    46  	if bav.Postgres != nil {
    47  		nft := bav.Postgres.GetNFT(&nftKey.NFTPostHash, nftKey.SerialNumber)
    48  		if nft != nil {
    49  			nftEntry = nft.NewNFTEntry()
    50  		}
    51  	} else {
    52  		nftEntry = DBGetNFTEntryByPostHashSerialNumber(bav.Handle, &nftKey.NFTPostHash, nftKey.SerialNumber)
    53  	}
    54  
    55  	if nftEntry != nil {
    56  		bav._setNFTEntryMappings(nftEntry)
    57  	}
    58  	return nftEntry
    59  }
    60  
    61  func (bav *UtxoView) GetNFTEntriesForPostHash(nftPostHash *BlockHash) []*NFTEntry {
    62  	// Get all the entries in the DB.
    63  	var dbNFTEntries []*NFTEntry
    64  	if bav.Postgres != nil {
    65  		nfts := bav.Postgres.GetNFTsForPostHash(nftPostHash)
    66  		for _, nft := range nfts {
    67  			dbNFTEntries = append(dbNFTEntries, nft.NewNFTEntry())
    68  		}
    69  	} else {
    70  		dbNFTEntries = DBGetNFTEntriesForPostHash(bav.Handle, nftPostHash)
    71  	}
    72  
    73  	// Make sure all of the DB entries are loaded in the view.
    74  	for _, dbNFTEntry := range dbNFTEntries {
    75  		nftKey := MakeNFTKey(dbNFTEntry.NFTPostHash, dbNFTEntry.SerialNumber)
    76  
    77  		// If the NFT is not in the view, add it to the view.
    78  		if _, ok := bav.NFTKeyToNFTEntry[nftKey]; !ok {
    79  			bav._setNFTEntryMappings(dbNFTEntry)
    80  		}
    81  	}
    82  
    83  	// Loop over the view and build the final set of NFTEntries to return.
    84  	nftEntries := []*NFTEntry{}
    85  	for _, nftEntry := range bav.NFTKeyToNFTEntry {
    86  		if !nftEntry.isDeleted && reflect.DeepEqual(nftEntry.NFTPostHash, nftPostHash) {
    87  			nftEntries = append(nftEntries, nftEntry)
    88  		}
    89  	}
    90  	return nftEntries
    91  }
    92  
    93  func (bav *UtxoView) GetNFTEntriesForPKID(ownerPKID *PKID) []*NFTEntry {
    94  	var dbNFTEntries []*NFTEntry
    95  	if bav.Postgres != nil {
    96  		nfts := bav.Postgres.GetNFTsForPKID(ownerPKID)
    97  		for _, nft := range nfts {
    98  			dbNFTEntries = append(dbNFTEntries, nft.NewNFTEntry())
    99  		}
   100  	} else {
   101  		dbNFTEntries = DBGetNFTEntriesForPKID(bav.Handle, ownerPKID)
   102  	}
   103  
   104  	// Make sure all of the DB entries are loaded in the view.
   105  	for _, dbNFTEntry := range dbNFTEntries {
   106  		nftKey := MakeNFTKey(dbNFTEntry.NFTPostHash, dbNFTEntry.SerialNumber)
   107  
   108  		// If the NFT is not in the view, add it to the view.
   109  		if _, ok := bav.NFTKeyToNFTEntry[nftKey]; !ok {
   110  			bav._setNFTEntryMappings(dbNFTEntry)
   111  		}
   112  	}
   113  
   114  	// Loop over the view and build the final set of NFTEntries to return.
   115  	nftEntries := []*NFTEntry{}
   116  	for _, nftEntry := range bav.NFTKeyToNFTEntry {
   117  		if !nftEntry.isDeleted && reflect.DeepEqual(nftEntry.OwnerPKID, ownerPKID) {
   118  			nftEntries = append(nftEntries, nftEntry)
   119  		}
   120  	}
   121  	return nftEntries
   122  }
   123  
   124  func (bav *UtxoView) GetNFTBidEntriesForPKID(bidderPKID *PKID) (_nftBidEntries []*NFTBidEntry) {
   125  	var dbNFTBidEntries []*NFTBidEntry
   126  	if bav.Postgres != nil {
   127  		bids := bav.Postgres.GetNFTBidsForPKID(bidderPKID)
   128  		for _, bid := range bids {
   129  			dbNFTBidEntries = append(dbNFTBidEntries, bid.NewNFTBidEntry())
   130  		}
   131  	} else {
   132  		dbNFTBidEntries = DBGetNFTBidEntriesForPKID(bav.Handle, bidderPKID)
   133  	}
   134  
   135  	// Make sure all of the DB entries are loaded in the view.
   136  	for _, dbNFTBidEntry := range dbNFTBidEntries {
   137  		nftBidKey := MakeNFTBidKey(bidderPKID, dbNFTBidEntry.NFTPostHash, dbNFTBidEntry.SerialNumber)
   138  
   139  		// If the NFT is not in the view, add it to the view.
   140  		if _, ok := bav.NFTBidKeyToNFTBidEntry[nftBidKey]; !ok {
   141  			bav._setNFTBidEntryMappings(dbNFTBidEntry)
   142  		}
   143  	}
   144  
   145  	// Loop over the view and build the final set of NFTEntries to return.
   146  	nftBidEntries := []*NFTBidEntry{}
   147  	for _, nftBidEntry := range bav.NFTBidKeyToNFTBidEntry {
   148  		if !nftBidEntry.isDeleted && reflect.DeepEqual(nftBidEntry.BidderPKID, bidderPKID) {
   149  			nftBidEntries = append(nftBidEntries, nftBidEntry)
   150  		}
   151  	}
   152  	return nftBidEntries
   153  }
   154  
   155  // TODO: Postgres
   156  func (bav *UtxoView) GetHighAndLowBidsForNFTCollection(
   157  	nftHash *BlockHash,
   158  ) (_highBid uint64, _lowBid uint64) {
   159  	highBid := uint64(0)
   160  	lowBid := uint64(0)
   161  	postEntry := bav.GetPostEntryForPostHash(nftHash)
   162  
   163  	// First we get the highest and lowest bids from the db.
   164  	for ii := uint64(1); ii <= postEntry.NumNFTCopies; ii++ {
   165  		highBidForSerialNum, lowBidForSerialNum := bav.GetDBHighAndLowBidsForNFT(nftHash, ii)
   166  
   167  		if highBidForSerialNum > highBid {
   168  			highBid = highBidForSerialNum
   169  		}
   170  
   171  		if lowBidForSerialNum < lowBid {
   172  			lowBid = lowBidForSerialNum
   173  		}
   174  	}
   175  
   176  	// Then we loop over the view to for anything we missed.
   177  	for _, nftBidEntry := range bav.NFTBidKeyToNFTBidEntry {
   178  		if !nftBidEntry.isDeleted && reflect.DeepEqual(nftBidEntry.NFTPostHash, nftHash) {
   179  			if nftBidEntry.BidAmountNanos > highBid {
   180  				highBid = nftBidEntry.BidAmountNanos
   181  			}
   182  
   183  			if nftBidEntry.BidAmountNanos < lowBid {
   184  				lowBid = nftBidEntry.BidAmountNanos
   185  			}
   186  		}
   187  	}
   188  
   189  	return highBid, lowBid
   190  }
   191  
   192  // TODO: Postgres
   193  func (bav *UtxoView) GetHighAndLowBidsForNFTSerialNumber(nftHash *BlockHash, serialNumber uint64) (_highBid uint64, _lowBid uint64) {
   194  	highBid := uint64(0)
   195  	lowBid := uint64(0)
   196  
   197  	highBidEntry, lowBidEntry := bav.GetDBHighAndLowBidEntriesForNFT(nftHash, serialNumber)
   198  
   199  	if highBidEntry != nil {
   200  		highBidKey := MakeNFTBidKey(highBidEntry.BidderPKID, highBidEntry.NFTPostHash, highBidEntry.SerialNumber)
   201  		if _, exists := bav.NFTBidKeyToNFTBidEntry[highBidKey]; !exists {
   202  			bav._setNFTBidEntryMappings(highBidEntry)
   203  		}
   204  		highBid = highBidEntry.BidAmountNanos
   205  	}
   206  
   207  	if lowBidEntry != nil {
   208  		lowBidKey := MakeNFTBidKey(lowBidEntry.BidderPKID, lowBidEntry.NFTPostHash, lowBidEntry.SerialNumber)
   209  		if _, exists := bav.NFTBidKeyToNFTBidEntry[lowBidKey]; !exists {
   210  			bav._setNFTBidEntryMappings(lowBidEntry)
   211  		}
   212  		lowBid = lowBidEntry.BidAmountNanos
   213  	}
   214  
   215  	// Then we loop over the view to for anything we missed.
   216  	for _, nftBidEntry := range bav.NFTBidKeyToNFTBidEntry {
   217  		if !nftBidEntry.isDeleted && nftBidEntry.SerialNumber == serialNumber && reflect.DeepEqual(nftBidEntry.NFTPostHash, nftHash) {
   218  			if nftBidEntry.BidAmountNanos > highBid {
   219  				highBid = nftBidEntry.BidAmountNanos
   220  			}
   221  
   222  			if nftBidEntry.BidAmountNanos < lowBid {
   223  				lowBid = nftBidEntry.BidAmountNanos
   224  			}
   225  		}
   226  	}
   227  	return highBid, lowBid
   228  }
   229  
   230  // TODO: Postgres
   231  func (bav *UtxoView) GetDBHighAndLowBidsForNFT(nftHash *BlockHash, serialNumber uint64) (_highBid uint64, _lowBid uint64) {
   232  	highBidAmount := uint64(0)
   233  	lowBidAmount := uint64(0)
   234  	highBidEntry, lowBidEntry := bav.GetDBHighAndLowBidEntriesForNFT(nftHash, serialNumber)
   235  	if highBidEntry != nil {
   236  		highBidAmount = highBidEntry.BidAmountNanos
   237  	}
   238  	if lowBidEntry != nil {
   239  		lowBidAmount = lowBidEntry.BidAmountNanos
   240  	}
   241  	return highBidAmount, lowBidAmount
   242  }
   243  
   244  // This function gets the highest and lowest bids for a specific NFT that
   245  // have not been deleted in the view.
   246  // TODO: Postgres
   247  func (bav *UtxoView) GetDBHighAndLowBidEntriesForNFT(
   248  	nftHash *BlockHash, serialNumber uint64,
   249  ) (_highBidEntry *NFTBidEntry, _lowBidEntry *NFTBidEntry) {
   250  	numPerDBFetch := 5
   251  	var highestBidEntry *NFTBidEntry
   252  	var lowestBidEntry *NFTBidEntry
   253  
   254  	// Loop until we find the highest bid in the database that hasn't been deleted in the view.
   255  	exitLoop := false
   256  	highBidEntries := DBGetNFTBidEntriesPaginated(
   257  		bav.Handle, nftHash, serialNumber, nil, numPerDBFetch, true)
   258  	for _, bidEntry := range highBidEntries {
   259  		bidEntryKey := MakeNFTBidKey(bidEntry.BidderPKID, bidEntry.NFTPostHash, bidEntry.SerialNumber)
   260  		if _, exists := bav.NFTBidKeyToNFTBidEntry[bidEntryKey]; !exists {
   261  			bav._setNFTBidEntryMappings(bidEntry)
   262  		}
   263  	}
   264  	for {
   265  		for _, highBidEntry := range highBidEntries {
   266  			bidKey := &NFTBidKey{
   267  				NFTPostHash:  *highBidEntry.NFTPostHash,
   268  				SerialNumber: highBidEntry.SerialNumber,
   269  				BidderPKID:   *highBidEntry.BidderPKID,
   270  			}
   271  			bidEntry := bav.NFTBidKeyToNFTBidEntry[*bidKey]
   272  			if !bidEntry.isDeleted && !exitLoop {
   273  				exitLoop = true
   274  				highestBidEntry = bidEntry
   275  			}
   276  		}
   277  
   278  		if len(highBidEntries) < numPerDBFetch {
   279  			exitLoop = true
   280  		}
   281  
   282  		if exitLoop {
   283  			break
   284  		} else {
   285  			nextStartEntry := highBidEntries[len(highBidEntries)-1]
   286  			highBidEntries = DBGetNFTBidEntriesPaginated(
   287  				bav.Handle, nftHash, serialNumber, nextStartEntry, numPerDBFetch, true,
   288  			)
   289  		}
   290  	}
   291  
   292  	// Loop until we find the lowest bid in the database that hasn't been deleted in the view.
   293  	exitLoop = false
   294  	lowBidEntries := DBGetNFTBidEntriesPaginated(
   295  		bav.Handle, nftHash, serialNumber, nil, numPerDBFetch, false)
   296  	for _, bidEntry := range lowBidEntries {
   297  		bidEntryKey := MakeNFTBidKey(bidEntry.BidderPKID, bidEntry.NFTPostHash, bidEntry.SerialNumber)
   298  		if _, exists := bav.NFTBidKeyToNFTBidEntry[bidEntryKey]; !exists {
   299  			bav._setNFTBidEntryMappings(bidEntry)
   300  		}
   301  	}
   302  	for {
   303  		for _, lowBidEntry := range lowBidEntries {
   304  			bidKey := &NFTBidKey{
   305  				NFTPostHash:  *lowBidEntry.NFTPostHash,
   306  				SerialNumber: lowBidEntry.SerialNumber,
   307  				BidderPKID:   *lowBidEntry.BidderPKID,
   308  			}
   309  			bidEntry := bav.NFTBidKeyToNFTBidEntry[*bidKey]
   310  			if !bidEntry.isDeleted && !exitLoop {
   311  				exitLoop = true
   312  				lowestBidEntry = bidEntry
   313  			}
   314  		}
   315  
   316  		if len(lowBidEntries) < numPerDBFetch {
   317  			exitLoop = true
   318  		}
   319  
   320  		if exitLoop {
   321  			break
   322  		} else {
   323  			nextStartEntry := lowBidEntries[len(lowBidEntries)-1]
   324  			lowBidEntries = DBGetNFTBidEntriesPaginated(
   325  				bav.Handle, nftHash, serialNumber, nextStartEntry, numPerDBFetch, false,
   326  			)
   327  		}
   328  	}
   329  
   330  	return highestBidEntry, lowestBidEntry
   331  }
   332  
   333  func (bav *UtxoView) _setAcceptNFTBidHistoryMappings(nftKey NFTKey, nftBidEntries *[]*NFTBidEntry) {
   334  	if nftBidEntries == nil {
   335  		glog.Errorf("_setAcceptedNFTBidHistoryMappings: Called with nil nftBidEntries; " +
   336  			"this should never happen.")
   337  		return
   338  	}
   339  
   340  	bav.NFTKeyToAcceptedNFTBidHistory[nftKey] = nftBidEntries
   341  }
   342  
   343  func (bav *UtxoView) GetAcceptNFTBidHistoryForNFTKey(nftKey *NFTKey) *[]*NFTBidEntry {
   344  	// If an entry exists in the in-memory map, return the value of that mapping.
   345  
   346  	mapValue, existsMapValue := bav.NFTKeyToAcceptedNFTBidHistory[*nftKey]
   347  	if existsMapValue {
   348  		return mapValue
   349  	}
   350  
   351  	// If we get here it means no value exists in our in-memory map. In this case,
   352  	// defer to the db. If a mapping exists in the db, return it. If not, return
   353  	// nil.
   354  	dbNFTBidEntries := DBGetAcceptedNFTBidEntriesByPostHashSerialNumber(bav.Handle, &nftKey.NFTPostHash, nftKey.SerialNumber)
   355  	if dbNFTBidEntries != nil {
   356  		bav._setAcceptNFTBidHistoryMappings(*nftKey, dbNFTBidEntries)
   357  		return dbNFTBidEntries
   358  	}
   359  	// We return an empty slice instead of nil
   360  	return &[]*NFTBidEntry{}
   361  }
   362  
   363  func (bav *UtxoView) _setNFTBidEntryMappings(nftBidEntry *NFTBidEntry) {
   364  	// This function shouldn't be called with nil.
   365  	if nftBidEntry == nil {
   366  		glog.Errorf("_setNFTBidEntryMappings: Called with nil nftBidEntry; " +
   367  			"this should never happen.")
   368  		return
   369  	}
   370  
   371  	nftBidKey := MakeNFTBidKey(nftBidEntry.BidderPKID, nftBidEntry.NFTPostHash, nftBidEntry.SerialNumber)
   372  	bav.NFTBidKeyToNFTBidEntry[nftBidKey] = nftBidEntry
   373  }
   374  
   375  func (bav *UtxoView) _deleteNFTBidEntryMappings(nftBidEntry *NFTBidEntry) {
   376  
   377  	// Create a tombstone entry.
   378  	tombstoneNFTBidEntry := *nftBidEntry
   379  	tombstoneNFTBidEntry.isDeleted = true
   380  
   381  	// Set the mappings to point to the tombstone entry.
   382  	bav._setNFTBidEntryMappings(&tombstoneNFTBidEntry)
   383  }
   384  
   385  func (bav *UtxoView) GetNFTBidEntryForNFTBidKey(nftBidKey *NFTBidKey) *NFTBidEntry {
   386  	// If an entry exists in the in-memory map, return the value of that mapping.
   387  	mapValue, existsMapValue := bav.NFTBidKeyToNFTBidEntry[*nftBidKey]
   388  	if existsMapValue {
   389  		return mapValue
   390  	}
   391  
   392  	// If we get here it means no value exists in our in-memory map. In this case,
   393  	// defer to the db. If a mapping exists in the db, return it. If not, return
   394  	// nil.
   395  	var dbNFTBidEntry *NFTBidEntry
   396  	if bav.Postgres != nil {
   397  		bidEntry := bav.Postgres.GetNFTBid(&nftBidKey.NFTPostHash, &nftBidKey.BidderPKID, nftBidKey.SerialNumber)
   398  		if bidEntry != nil {
   399  			dbNFTBidEntry = bidEntry.NewNFTBidEntry()
   400  		}
   401  	} else {
   402  		dbNFTBidEntry = DBGetNFTBidEntryForNFTBidKey(bav.Handle, nftBidKey)
   403  	}
   404  
   405  	if dbNFTBidEntry != nil {
   406  		bav._setNFTBidEntryMappings(dbNFTBidEntry)
   407  	}
   408  
   409  	return dbNFTBidEntry
   410  }
   411  
   412  func (bav *UtxoView) GetAllNFTBidEntries(nftPostHash *BlockHash, serialNumber uint64) []*NFTBidEntry {
   413  	// Get all the entries in the DB.
   414  	var dbEntries []*NFTBidEntry
   415  	if bav.Postgres != nil {
   416  		bids := bav.Postgres.GetNFTBidsForSerial(nftPostHash, serialNumber)
   417  		for _, bid := range bids {
   418  			dbEntries = append(dbEntries, bid.NewNFTBidEntry())
   419  		}
   420  	} else {
   421  		dbEntries = DBGetNFTBidEntries(bav.Handle, nftPostHash, serialNumber)
   422  	}
   423  
   424  	// Make sure all of the DB entries are loaded in the view.
   425  	for _, dbEntry := range dbEntries {
   426  		nftBidKey := MakeNFTBidKey(dbEntry.BidderPKID, dbEntry.NFTPostHash, dbEntry.SerialNumber)
   427  
   428  		// If the bidEntry is not in the view, add it to the view.
   429  		if _, ok := bav.NFTBidKeyToNFTBidEntry[nftBidKey]; !ok {
   430  			bav._setNFTBidEntryMappings(dbEntry)
   431  		}
   432  	}
   433  
   434  	// Loop over the view and build the final set of NFTBidEntries to return.
   435  	nftBidEntries := []*NFTBidEntry{}
   436  	for _, nftBidEntry := range bav.NFTBidKeyToNFTBidEntry {
   437  
   438  		if nftBidEntry.SerialNumber == serialNumber && !nftBidEntry.isDeleted &&
   439  			reflect.DeepEqual(nftBidEntry.NFTPostHash, nftPostHash) {
   440  
   441  			nftBidEntries = append(nftBidEntries, nftBidEntry)
   442  		}
   443  	}
   444  	return nftBidEntries
   445  }
   446  
   447  func (bav *UtxoView) _connectCreateNFT(
   448  	txn *MsgDeSoTxn, txHash *BlockHash, blockHeight uint32, verifySignatures bool) (
   449  	_totalInput uint64, _totalOutput uint64, _utxoOps []*UtxoOperation, _err error) {
   450  	if bav.GlobalParamsEntry.MaxCopiesPerNFT == 0 {
   451  		return 0, 0, nil, fmt.Errorf("_connectCreateNFT: called with zero MaxCopiesPerNFT")
   452  	}
   453  
   454  	// Check that the transaction has the right TxnType.
   455  	if txn.TxnMeta.GetTxnType() != TxnTypeCreateNFT {
   456  		return 0, 0, nil, fmt.Errorf("_connectCreateNFT: called with bad TxnType %s",
   457  			txn.TxnMeta.GetTxnType().String())
   458  	}
   459  	txMeta := txn.TxnMeta.(*CreateNFTMetadata)
   460  
   461  	// Validate the txMeta.
   462  	if txMeta.NumCopies > bav.GlobalParamsEntry.MaxCopiesPerNFT {
   463  		return 0, 0, nil, RuleErrorTooManyNFTCopies
   464  	}
   465  	if txMeta.NumCopies == 0 {
   466  		return 0, 0, nil, RuleErrorNFTMustHaveNonZeroCopies
   467  	}
   468  	// Make sure we won't oveflow when we add the royalty basis points.
   469  	if math.MaxUint64-txMeta.NFTRoyaltyToCreatorBasisPoints < txMeta.NFTRoyaltyToCoinBasisPoints {
   470  		return 0, 0, nil, RuleErrorNFTRoyaltyOverflow
   471  	}
   472  	royaltyBasisPoints := txMeta.NFTRoyaltyToCreatorBasisPoints + txMeta.NFTRoyaltyToCoinBasisPoints
   473  	if royaltyBasisPoints > bav.Params.MaxNFTRoyaltyBasisPoints {
   474  		return 0, 0, nil, RuleErrorNFTRoyaltyHasTooManyBasisPoints
   475  	}
   476  	postEntry := bav.GetPostEntryForPostHash(txMeta.NFTPostHash)
   477  	if postEntry == nil || postEntry.isDeleted {
   478  		return 0, 0, nil, RuleErrorCreateNFTOnNonexistentPost
   479  	}
   480  	if IsVanillaRepost(postEntry) {
   481  		return 0, 0, nil, RuleErrorCreateNFTOnVanillaRepost
   482  	}
   483  	if !reflect.DeepEqual(postEntry.PosterPublicKey, txn.PublicKey) {
   484  		return 0, 0, nil, RuleErrorCreateNFTMustBeCalledByPoster
   485  	}
   486  	if postEntry.IsNFT {
   487  		return 0, 0, nil, RuleErrorCreateNFTOnPostThatAlreadyIsNFT
   488  	}
   489  	profileEntry := bav.GetProfileEntryForPublicKey(postEntry.PosterPublicKey)
   490  	if profileEntry == nil || profileEntry.isDeleted {
   491  		return 0, 0, nil, RuleErrorCantCreateNFTWithoutProfileEntry
   492  	}
   493  
   494  	// Connect basic txn to get the total input and the total output without
   495  	// considering the transaction metadata.
   496  	totalInput, totalOutput, utxoOpsForTxn, err := bav._connectBasicTransfer(
   497  		txn, txHash, blockHeight, verifySignatures)
   498  	if err != nil {
   499  		return 0, 0, nil, errors.Wrapf(err, "_connectCreateNFT: ")
   500  	}
   501  
   502  	// Force the input to be non-zero so that we can prevent replay attacks.
   503  	if totalInput == 0 {
   504  		return 0, 0, nil, RuleErrorCreateNFTRequiresNonZeroInput
   505  	}
   506  
   507  	if verifySignatures {
   508  		// _connectBasicTransfer has already checked that the transaction is
   509  		// signed by the top-level public key, which we take to be the poster's
   510  		// public key.
   511  	}
   512  
   513  	// Since issuing N copies of an NFT multiplies the downstream processing overhead by N,
   514  	// we charge a fee for each additional copy minted.
   515  	// We do not need to check for overflow as these values are managed by the ParamUpdater.
   516  	nftFee := txMeta.NumCopies * bav.GlobalParamsEntry.CreateNFTFeeNanos
   517  
   518  	// Sanity check overflow and then ensure that the transaction covers the NFT fee.
   519  	if math.MaxUint64-totalOutput < nftFee {
   520  		return 0, 0, nil, fmt.Errorf("_connectCreateNFTFee: nft Fee overflow")
   521  	}
   522  	totalOutput += nftFee
   523  	if totalInput < totalOutput {
   524  		return 0, 0, nil, RuleErrorCreateNFTWithInsufficientFunds
   525  	}
   526  
   527  	// Save a copy of the post entry so that we can safely modify it.
   528  	prevPostEntry := &PostEntry{}
   529  	*prevPostEntry = *postEntry
   530  
   531  	// Update and save the post entry.
   532  	postEntry.IsNFT = true
   533  	postEntry.NumNFTCopies = txMeta.NumCopies
   534  	if txMeta.IsForSale {
   535  		postEntry.NumNFTCopiesForSale = txMeta.NumCopies
   536  	}
   537  	postEntry.HasUnlockable = txMeta.HasUnlockable
   538  	postEntry.NFTRoyaltyToCreatorBasisPoints = txMeta.NFTRoyaltyToCreatorBasisPoints
   539  	postEntry.NFTRoyaltyToCoinBasisPoints = txMeta.NFTRoyaltyToCoinBasisPoints
   540  	bav._setPostEntryMappings(postEntry)
   541  
   542  	posterPKID := bav.GetPKIDForPublicKey(postEntry.PosterPublicKey)
   543  	if posterPKID == nil || posterPKID.isDeleted {
   544  		return 0, 0, nil, fmt.Errorf("_connectCreateNFT: non-existent posterPKID: %s",
   545  			PkToString(postEntry.PosterPublicKey, bav.Params))
   546  	}
   547  
   548  	// Add the appropriate NFT entries.
   549  	for ii := uint64(1); ii <= txMeta.NumCopies; ii++ {
   550  		nftEntry := &NFTEntry{
   551  			OwnerPKID:         posterPKID.PKID,
   552  			NFTPostHash:       txMeta.NFTPostHash,
   553  			SerialNumber:      ii,
   554  			IsForSale:         txMeta.IsForSale,
   555  			MinBidAmountNanos: txMeta.MinBidAmountNanos,
   556  		}
   557  		bav._setNFTEntryMappings(nftEntry)
   558  	}
   559  
   560  	// Add an operation to the utxoOps list indicating we've created an NFT.
   561  	utxoOpsForTxn = append(utxoOpsForTxn, &UtxoOperation{
   562  		Type:          OperationTypeCreateNFT,
   563  		PrevPostEntry: prevPostEntry,
   564  	})
   565  
   566  	return totalInput, totalOutput, utxoOpsForTxn, nil
   567  }
   568  
   569  func (bav *UtxoView) _connectUpdateNFT(
   570  	txn *MsgDeSoTxn, txHash *BlockHash, blockHeight uint32, verifySignatures bool) (
   571  	_totalInput uint64, _totalOutput uint64, _utxoOps []*UtxoOperation, _err error) {
   572  	if bav.GlobalParamsEntry.MaxCopiesPerNFT == 0 {
   573  		return 0, 0, nil, fmt.Errorf("_connectUpdateNFT: called with zero MaxCopiesPerNFT")
   574  	}
   575  
   576  	// Check that the transaction has the right TxnType.
   577  	if txn.TxnMeta.GetTxnType() != TxnTypeUpdateNFT {
   578  		return 0, 0, nil, fmt.Errorf("_connectUpdateNFT: called with bad TxnType %s",
   579  			txn.TxnMeta.GetTxnType().String())
   580  	}
   581  	txMeta := txn.TxnMeta.(*UpdateNFTMetadata)
   582  
   583  	// Verify the NFT entry exists.
   584  	nftKey := MakeNFTKey(txMeta.NFTPostHash, txMeta.SerialNumber)
   585  	prevNFTEntry := bav.GetNFTEntryForNFTKey(&nftKey)
   586  	if prevNFTEntry == nil || prevNFTEntry.isDeleted {
   587  		return 0, 0, nil, RuleErrorCannotUpdateNonExistentNFT
   588  	}
   589  
   590  	// Verify the NFT is not a pending transfer.
   591  	if prevNFTEntry.IsPending {
   592  		return 0, 0, nil, RuleErrorCannotUpdatePendingNFTTransfer
   593  	}
   594  
   595  	// Get the postEntry so we can update the number of NFT copies for sale.
   596  	postEntry := bav.GetPostEntryForPostHash(txMeta.NFTPostHash)
   597  	if postEntry == nil || postEntry.isDeleted {
   598  		return 0, 0, nil, fmt.Errorf("_connectUpdateNFT: non-existent postEntry for NFTPostHash: %s",
   599  			txMeta.NFTPostHash.String())
   600  	}
   601  
   602  	// Verify that the updater is the owner of the NFT.
   603  	updaterPKID := bav.GetPKIDForPublicKey(txn.PublicKey)
   604  	if updaterPKID == nil || updaterPKID.isDeleted {
   605  		return 0, 0, nil, fmt.Errorf("_connectUpdateNFT: non-existent updaterPKID: %s",
   606  			PkToString(txn.PublicKey, bav.Params))
   607  	}
   608  	if !reflect.DeepEqual(prevNFTEntry.OwnerPKID, updaterPKID.PKID) {
   609  		return 0, 0, nil, RuleErrorUpdateNFTByNonOwner
   610  	}
   611  
   612  	// Sanity check that the NFT entry is correct.
   613  	if !reflect.DeepEqual(prevNFTEntry.NFTPostHash, txMeta.NFTPostHash) ||
   614  		!reflect.DeepEqual(prevNFTEntry.SerialNumber, txMeta.SerialNumber) {
   615  		return 0, 0, nil, fmt.Errorf("_connectUpdateNFT: prevNFTEntry %v is inconsistent with txMeta %v;"+
   616  			" this should never happen.", prevNFTEntry, txMeta)
   617  	}
   618  
   619  	// At the moment, updates can only be made if the 'IsForSale' status of the NFT is changing.
   620  	// As a result, you cannot change the MinBidAmountNanos of an NFT while it is for sale.
   621  	if prevNFTEntry.IsForSale == txMeta.IsForSale {
   622  		return 0, 0, nil, RuleErrorNFTUpdateMustUpdateIsForSaleStatus
   623  	}
   624  
   625  	// Connect basic txn to get the total input and the total output without
   626  	// considering the transaction metadata.
   627  	totalInput, totalOutput, utxoOpsForTxn, err := bav._connectBasicTransfer(
   628  		txn, txHash, blockHeight, verifySignatures)
   629  	if err != nil {
   630  		return 0, 0, nil, errors.Wrapf(err, "_connectUpdateNFT: ")
   631  	}
   632  
   633  	// Force the input to be non-zero so that we can prevent replay attacks.
   634  	if totalInput == 0 {
   635  		return 0, 0, nil, RuleErrorUpdateNFTRequiresNonZeroInput
   636  	}
   637  
   638  	if verifySignatures {
   639  		// _connectBasicTransfer has already checked that the transaction is
   640  		// signed by the top-level public key, which we take to be the poster's
   641  		// public key.
   642  	}
   643  
   644  	// Now we are ready to update the NFT. Three things must happen:
   645  	// 	(1) Update the NFT entry.
   646  	//  (2) If the NFT entry is being updated to "is not for sale", kill all the bids.
   647  	//  (3) Update the number of NFT copies for sale on the post entry.
   648  
   649  	// Create the updated NFTEntry.
   650  	newNFTEntry := &NFTEntry{
   651  		LastOwnerPKID:     prevNFTEntry.LastOwnerPKID,
   652  		OwnerPKID:         updaterPKID.PKID,
   653  		NFTPostHash:       txMeta.NFTPostHash,
   654  		SerialNumber:      txMeta.SerialNumber,
   655  		IsForSale:         txMeta.IsForSale,
   656  		MinBidAmountNanos: txMeta.MinBidAmountNanos,
   657  		UnlockableText:    prevNFTEntry.UnlockableText,
   658  		// Keep the last accepted bid amount nanos from the previous entry since this
   659  		// value is only updated when a new bid is accepted.
   660  		LastAcceptedBidAmountNanos: prevNFTEntry.LastAcceptedBidAmountNanos,
   661  	}
   662  	bav._setNFTEntryMappings(newNFTEntry)
   663  
   664  	// If we are going from ForSale->NotForSale, delete all the NFTBidEntries for this NFT.
   665  	deletedBidEntries := []*NFTBidEntry{}
   666  	if prevNFTEntry.IsForSale && !txMeta.IsForSale {
   667  		bidEntries := bav.GetAllNFTBidEntries(txMeta.NFTPostHash, txMeta.SerialNumber)
   668  		for _, bidEntry := range bidEntries {
   669  			deletedBidEntries = append(deletedBidEntries, bidEntry)
   670  			bav._deleteNFTBidEntryMappings(bidEntry)
   671  		}
   672  	}
   673  
   674  	// Save a copy of the post entry so that we can safely modify it.
   675  	prevPostEntry := &PostEntry{}
   676  	*prevPostEntry = *postEntry
   677  
   678  	// Update the number of NFT copies that are for sale.
   679  	if prevNFTEntry.IsForSale && !txMeta.IsForSale {
   680  		// For sale --> Not for sale.
   681  		postEntry.NumNFTCopiesForSale--
   682  	} else if !prevNFTEntry.IsForSale && txMeta.IsForSale {
   683  		// Not for sale --> For sale.
   684  		postEntry.NumNFTCopiesForSale++
   685  	}
   686  
   687  	// Set the new postEntry.
   688  	bav._setPostEntryMappings(postEntry)
   689  
   690  	// Add an operation to the list at the end indicating we've connected an NFT update.
   691  	utxoOpsForTxn = append(utxoOpsForTxn, &UtxoOperation{
   692  		Type:                 OperationTypeUpdateNFT,
   693  		PrevNFTEntry:         prevNFTEntry,
   694  		PrevPostEntry:        prevPostEntry,
   695  		DeletedNFTBidEntries: deletedBidEntries,
   696  	})
   697  
   698  	return totalInput, totalOutput, utxoOpsForTxn, nil
   699  }
   700  
   701  func (bav *UtxoView) _connectAcceptNFTBid(
   702  	txn *MsgDeSoTxn, txHash *BlockHash, blockHeight uint32, verifySignatures bool) (
   703  	_totalInput uint64, _totalOutput uint64, _utxoOps []*UtxoOperation, _err error) {
   704  	if bav.GlobalParamsEntry.MaxCopiesPerNFT == 0 {
   705  		return 0, 0, nil, fmt.Errorf("_connectAcceptNFTBid: called with zero MaxCopiesPerNFT")
   706  	}
   707  
   708  	// Check that the transaction has the right TxnType.
   709  	if txn.TxnMeta.GetTxnType() != TxnTypeAcceptNFTBid {
   710  		return 0, 0, nil, fmt.Errorf("_connectAcceptNFTBid: called with bad TxnType %s",
   711  			txn.TxnMeta.GetTxnType().String())
   712  	}
   713  	txMeta := txn.TxnMeta.(*AcceptNFTBidMetadata)
   714  
   715  	// Verify the NFT entry that is being bid on exists and is on sale.
   716  	nftKey := MakeNFTKey(txMeta.NFTPostHash, txMeta.SerialNumber)
   717  	prevNFTEntry := bav.GetNFTEntryForNFTKey(&nftKey)
   718  	if prevNFTEntry == nil || prevNFTEntry.isDeleted {
   719  		// We wrap these errors in order to differentiate versus _connectNFTBid().
   720  		return 0, 0, nil, errors.Wrapf(RuleErrorNFTBidOnNonExistentNFTEntry, "_connectAcceptNFTBid: ")
   721  	}
   722  	if !prevNFTEntry.IsForSale {
   723  		return 0, 0, nil, errors.Wrapf(RuleErrorNFTBidOnNFTThatIsNotForSale, "_connectAcceptNFTBid: ")
   724  	}
   725  
   726  	// Verify the NFT is not a pending transfer.
   727  	if prevNFTEntry.IsPending {
   728  		return 0, 0, nil, RuleErrorCannotAcceptBidForPendingNFTTransfer
   729  	}
   730  
   731  	// Verify that the updater is the owner of the NFT.
   732  	updaterPKID := bav.GetPKIDForPublicKey(txn.PublicKey)
   733  	if updaterPKID == nil || updaterPKID.isDeleted {
   734  		return 0, 0, nil, fmt.Errorf("_connectAcceptNFTBid: non-existent updaterPKID: %s",
   735  			PkToString(txn.PublicKey, bav.Params))
   736  	}
   737  	if !reflect.DeepEqual(prevNFTEntry.OwnerPKID, updaterPKID.PKID) {
   738  		return 0, 0, nil, RuleErrorAcceptNFTBidByNonOwner
   739  	}
   740  
   741  	// Get the post entry, verify it exists.
   742  	nftPostEntry := bav.GetPostEntryForPostHash(txMeta.NFTPostHash)
   743  
   744  	// If this is an unlockable NFT, make sure that an unlockable string was provided.
   745  	if nftPostEntry == nil || nftPostEntry.isDeleted {
   746  		return 0, 0, nil, RuleErrorPostEntryNotFoundForAcceptedNFTBid
   747  	}
   748  	if nftPostEntry.HasUnlockable && len(txMeta.UnlockableText) == 0 {
   749  		return 0, 0, nil, RuleErrorUnlockableNFTMustProvideUnlockableText
   750  	}
   751  
   752  	// Check the length of the UnlockableText.
   753  	if uint64(len(txMeta.UnlockableText)) > bav.Params.MaxPrivateMessageLengthBytes {
   754  		return 0, 0, nil, errors.Wrapf(
   755  			RuleErrorUnlockableTextLengthExceedsMax, "_connectAcceptNFTBid: "+
   756  				"UnlockableTextLen = %d; Max length = %d",
   757  			len(txMeta.UnlockableText), bav.Params.MaxPrivateMessageLengthBytes)
   758  	}
   759  
   760  	// Get the poster's profile.
   761  	existingProfileEntry := bav.GetProfileEntryForPublicKey(nftPostEntry.PosterPublicKey)
   762  	if existingProfileEntry == nil || existingProfileEntry.isDeleted {
   763  		return 0, 0, nil, fmt.Errorf(
   764  			"_connectAcceptNFTBid: Profile missing for NFT pub key: %v %v",
   765  			PkToStringMainnet(nftPostEntry.PosterPublicKey), PkToStringTestnet(nftPostEntry.PosterPublicKey))
   766  	}
   767  	// Save all the old values from the CoinEntry before we potentially
   768  	// update them. Note that CoinEntry doesn't contain any pointers and so
   769  	// a direct copy is OK.
   770  	prevCoinEntry := existingProfileEntry.CoinEntry
   771  
   772  	// Verify the NFT bid entry being accepted exists and has a bid consistent with the metadata.
   773  	// If we did not require an AcceptNFTBid txn to have a bid amount, it would leave the door
   774  	// open for an attack where someone replaces a high bid with a low bid after the owner accepts.
   775  	nftBidKey := MakeNFTBidKey(txMeta.BidderPKID, txMeta.NFTPostHash, txMeta.SerialNumber)
   776  	nftBidEntry := bav.GetNFTBidEntryForNFTBidKey(&nftBidKey)
   777  	if nftBidEntry == nil || nftBidEntry.isDeleted {
   778  		// NOTE: Users can submit a bid for SerialNumber zero as a blanket bid for any SerialNumber
   779  		// in an NFT collection. Thus, we must check to see if a SerialNumber zero bid exists
   780  		// for this bidder before we return an error.
   781  		nftBidKey = MakeNFTBidKey(txMeta.BidderPKID, txMeta.NFTPostHash, uint64(0))
   782  		nftBidEntry = bav.GetNFTBidEntryForNFTBidKey(&nftBidKey)
   783  		if nftBidEntry == nil || nftBidEntry.isDeleted {
   784  			return 0, 0, nil, RuleErrorCantAcceptNonExistentBid
   785  		}
   786  	}
   787  	if nftBidEntry.BidAmountNanos != txMeta.BidAmountNanos {
   788  		return 0, 0, nil, RuleErrorAcceptedNFTBidAmountDoesNotMatch
   789  	}
   790  
   791  	bidderPublicKey := bav.GetPublicKeyForPKID(txMeta.BidderPKID)
   792  
   793  	//
   794  	// Store starting balances of all the participants to check diff later.
   795  	//
   796  	// We assume the tip is right before the block in which this txn is about to be applied.
   797  	tipHeight := uint32(0)
   798  	if blockHeight > 0 {
   799  		tipHeight = blockHeight - 1
   800  	}
   801  	sellerBalanceBefore, err := bav.GetSpendableDeSoBalanceNanosForPublicKey(txn.PublicKey, tipHeight)
   802  	if err != nil {
   803  		return 0, 0, nil, fmt.Errorf(
   804  			"_connectAcceptNFTBid: Problem getting initial balance for seller pubkey: %v",
   805  			PkToStringBoth(txn.PublicKey))
   806  	}
   807  	bidderBalanceBefore, err := bav.GetSpendableDeSoBalanceNanosForPublicKey(
   808  		bidderPublicKey, tipHeight)
   809  	if err != nil {
   810  		return 0, 0, nil, fmt.Errorf(
   811  			"_connectAcceptNFTBid: Problem getting initial balance for bidder pubkey: %v",
   812  			PkToStringBoth(bidderPublicKey))
   813  	}
   814  	creatorBalanceBefore, err := bav.GetSpendableDeSoBalanceNanosForPublicKey(
   815  		nftPostEntry.PosterPublicKey, tipHeight)
   816  	if err != nil {
   817  		return 0, 0, nil, fmt.Errorf(
   818  			"_connectAcceptNFTBid: Problem getting initial balance for poster pubkey: %v",
   819  			PkToStringBoth(nftPostEntry.PosterPublicKey))
   820  	}
   821  
   822  	//
   823  	// Validate bidder UTXOs.
   824  	//
   825  	if len(txMeta.BidderInputs) == 0 {
   826  		return 0, 0, nil, RuleErrorAcceptedNFTBidMustSpecifyBidderInputs
   827  	}
   828  	totalBidderInput := uint64(0)
   829  	spentUtxoEntries := []*UtxoEntry{}
   830  	utxoOpsForTxn := []*UtxoOperation{}
   831  	for _, bidderInput := range txMeta.BidderInputs {
   832  		bidderUtxoKey := UtxoKey(*bidderInput)
   833  		bidderUtxoEntry := bav.GetUtxoEntryForUtxoKey(&bidderUtxoKey)
   834  		if bidderUtxoEntry == nil || bidderUtxoEntry.isSpent {
   835  			return 0, 0, nil, RuleErrorBidderInputForAcceptedNFTBidNoLongerExists
   836  		}
   837  
   838  		// Make sure that the utxo specified is actually from the bidder.
   839  		if !reflect.DeepEqual(bidderUtxoEntry.PublicKey, bidderPublicKey) {
   840  			return 0, 0, nil, RuleErrorInputWithPublicKeyDifferentFromTxnPublicKey
   841  		}
   842  
   843  		// If the utxo is from a block reward txn, make sure enough time has passed to
   844  		// make it spendable.
   845  		if _isEntryImmatureBlockReward(bidderUtxoEntry, blockHeight, bav.Params) {
   846  			return 0, 0, nil, RuleErrorInputSpendsImmatureBlockReward
   847  		}
   848  		totalBidderInput += bidderUtxoEntry.AmountNanos
   849  
   850  		// Make sure we spend the utxo so that the bidder can't reuse it.
   851  		utxoOp, err := bav._spendUtxo(&bidderUtxoKey)
   852  		if err != nil {
   853  			return 0, 0, nil, errors.Wrapf(err, "_connectAcceptNFTBid: Problem spending bidder utxo")
   854  		}
   855  		spentUtxoEntries = append(spentUtxoEntries, bidderUtxoEntry)
   856  
   857  		// Track the UtxoOperations so we can rollback, and for Rosetta
   858  		utxoOpsForTxn = append(utxoOpsForTxn, utxoOp)
   859  	}
   860  
   861  	if totalBidderInput < txMeta.BidAmountNanos {
   862  		return 0, 0, nil, RuleErrorAcceptNFTBidderInputsInsufficientForBidAmount
   863  	}
   864  
   865  	// The bidder gets back any unspent nanos from the inputs specified.
   866  	bidderChangeNanos := totalBidderInput - txMeta.BidAmountNanos
   867  	// The amount of deso that should go to the original creator from this purchase.
   868  	// Calculated as: (BidAmountNanos * NFTRoyaltyToCreatorBasisPoints) / (100 * 100)
   869  	creatorRoyaltyNanos := IntDiv(
   870  		IntMul(
   871  			big.NewInt(int64(txMeta.BidAmountNanos)),
   872  			big.NewInt(int64(nftPostEntry.NFTRoyaltyToCreatorBasisPoints))),
   873  		big.NewInt(100*100)).Uint64()
   874  	// The amount of deso that should go to the original creator's coin from this purchase.
   875  	// Calculated as: (BidAmountNanos * NFTRoyaltyToCoinBasisPoints) / (100 * 100)
   876  	creatorCoinRoyaltyNanos := IntDiv(
   877  		IntMul(
   878  			big.NewInt(int64(txMeta.BidAmountNanos)),
   879  			big.NewInt(int64(nftPostEntry.NFTRoyaltyToCoinBasisPoints))),
   880  		big.NewInt(100*100)).Uint64()
   881  	//glog.Infof("Bid amount: %d, coin basis points: %d, coin royalty: %d",
   882  	//	txMeta.BidAmountNanos, nftPostEntry.NFTRoyaltyToCoinBasisPoints, creatorCoinRoyaltyNanos)
   883  
   884  	// Sanity check that the royalties are reasonable and won't cause underflow.
   885  	if txMeta.BidAmountNanos < (creatorRoyaltyNanos + creatorCoinRoyaltyNanos) {
   886  		return 0, 0, nil, fmt.Errorf(
   887  			"_connectAcceptNFTBid: sum of royalties (%d, %d) is less than bid amount (%d)",
   888  			creatorRoyaltyNanos, creatorCoinRoyaltyNanos, txMeta.BidAmountNanos)
   889  	}
   890  
   891  	bidAmountMinusRoyalties := txMeta.BidAmountNanos - creatorRoyaltyNanos - creatorCoinRoyaltyNanos
   892  
   893  	// Connect basic txn to get the total input and the total output without
   894  	// considering the transaction metadata.
   895  	totalInput, totalOutput, utxoOpsFromBasicTransfer, err := bav._connectBasicTransfer(
   896  		txn, txHash, blockHeight, verifySignatures)
   897  	if err != nil {
   898  		return 0, 0, nil, errors.Wrapf(err, "_connectAcceptNFTBid: ")
   899  	}
   900  	// Append the basic transfer utxoOps to our list
   901  	utxoOpsForTxn = append(utxoOpsForTxn, utxoOpsFromBasicTransfer...)
   902  
   903  	// Force the input to be non-zero so that we can prevent replay attacks.
   904  	if totalInput == 0 {
   905  		return 0, 0, nil, RuleErrorAcceptNFTBidRequiresNonZeroInput
   906  	}
   907  
   908  	if verifySignatures {
   909  		// _connectBasicTransfer has already checked that the transaction is
   910  		// signed by the top-level public key, which we take to be the poster's
   911  		// public key.
   912  	}
   913  
   914  	// Now we are ready to accept the bid. When we accept, the following must happen:
   915  	// 	(1) Update the nft entry with the new owner and set it as "not for sale".
   916  	//  (2) Delete all of the bids on this NFT since they are no longer relevant.
   917  	//  (3) Pay the seller.
   918  	//  (4) Pay royalties to the original creator.
   919  	//  (5) Pay change to the bidder.
   920  	//  (6) Add creator coin royalties to deso locked.
   921  	//  (7) Decrement the nftPostEntry NumNFTCopiesForSale.
   922  
   923  	// (1) Set an appropriate NFTEntry for the new owner.
   924  
   925  	newNFTEntry := &NFTEntry{
   926  		LastOwnerPKID:  updaterPKID.PKID,
   927  		OwnerPKID:      txMeta.BidderPKID,
   928  		NFTPostHash:    txMeta.NFTPostHash,
   929  		SerialNumber:   txMeta.SerialNumber,
   930  		IsForSale:      false,
   931  		UnlockableText: txMeta.UnlockableText,
   932  
   933  		LastAcceptedBidAmountNanos: txMeta.BidAmountNanos,
   934  	}
   935  	bav._setNFTEntryMappings(newNFTEntry)
   936  
   937  	// append the accepted bid entry to the list of accepted bid entries
   938  	prevAcceptedBidHistory := bav.GetAcceptNFTBidHistoryForNFTKey(&nftKey)
   939  	newAcceptedBidHistory := append(*prevAcceptedBidHistory, nftBidEntry)
   940  	bav._setAcceptNFTBidHistoryMappings(nftKey, &newAcceptedBidHistory)
   941  
   942  	// (2) Iterate over all the NFTBidEntries for this NFT and delete them.
   943  	bidEntries := bav.GetAllNFTBidEntries(txMeta.NFTPostHash, txMeta.SerialNumber)
   944  	if len(bidEntries) == 0 && nftBidEntry.SerialNumber != 0 {
   945  		// Quick sanity check to make sure that we found bid entries. There should be at least 1.
   946  		return 0, 0, nil, fmt.Errorf(
   947  			"_connectAcceptNFTBid: found zero bid entries to delete; this should never happen.")
   948  	}
   949  	deletedBidEntries := []*NFTBidEntry{}
   950  	for _, bidEntry := range bidEntries {
   951  		deletedBidEntries = append(deletedBidEntries, bidEntry)
   952  		bav._deleteNFTBidEntryMappings(bidEntry)
   953  	}
   954  	// If this is a SerialNumber zero BidEntry, we must delete it specifically.
   955  	if nftBidEntry.SerialNumber == uint64(0) {
   956  		deletedBidEntries = append(deletedBidEntries, nftBidEntry)
   957  		bav._deleteNFTBidEntryMappings(nftBidEntry)
   958  	}
   959  
   960  	// (3) Pay the seller by creating a new entry for this output and add it to the view.
   961  	nftPaymentUtxoKeys := []*UtxoKey{}
   962  	nextUtxoIndex := uint32(len(txn.TxOutputs))
   963  	sellerOutputKey := &UtxoKey{
   964  		TxID:  *txHash,
   965  		Index: nextUtxoIndex,
   966  	}
   967  
   968  	utxoEntry := UtxoEntry{
   969  		AmountNanos: bidAmountMinusRoyalties,
   970  		PublicKey:   txn.PublicKey,
   971  		BlockHeight: blockHeight,
   972  		UtxoType:    UtxoTypeNFTSeller,
   973  		UtxoKey:     sellerOutputKey,
   974  		// We leave the position unset and isSpent to false by default.
   975  		// The position will be set in the call to _addUtxo.
   976  	}
   977  
   978  	// Create a new scope to avoid name collisions
   979  	{
   980  		utxoOp, err := bav._addUtxo(&utxoEntry)
   981  		if err != nil {
   982  			return 0, 0, nil, errors.Wrapf(
   983  				err, "_connectAcceptNFTBid: Problem adding output utxo")
   984  		}
   985  		nftPaymentUtxoKeys = append(nftPaymentUtxoKeys, sellerOutputKey)
   986  
   987  		// Rosetta uses this UtxoOperation to provide INPUT amounts
   988  		utxoOpsForTxn = append(utxoOpsForTxn, utxoOp)
   989  	}
   990  
   991  	// (4) Pay royalties to the original artist.
   992  	if creatorRoyaltyNanos > 0 {
   993  		nextUtxoIndex += 1
   994  		royaltyOutputKey := &UtxoKey{
   995  			TxID:  *txHash,
   996  			Index: nextUtxoIndex,
   997  		}
   998  
   999  		utxoEntry := UtxoEntry{
  1000  			AmountNanos: creatorRoyaltyNanos,
  1001  			PublicKey:   nftPostEntry.PosterPublicKey,
  1002  			BlockHeight: blockHeight,
  1003  			UtxoType:    UtxoTypeNFTCreatorRoyalty,
  1004  
  1005  			UtxoKey: royaltyOutputKey,
  1006  			// We leave the position unset and isSpent to false by default.
  1007  			// The position will be set in the call to _addUtxo.
  1008  		}
  1009  
  1010  		utxoOp, err := bav._addUtxo(&utxoEntry)
  1011  		if err != nil {
  1012  			return 0, 0, nil, errors.Wrapf(err, "_connectAcceptNFTBid: Problem adding output utxo")
  1013  		}
  1014  		nftPaymentUtxoKeys = append(nftPaymentUtxoKeys, royaltyOutputKey)
  1015  
  1016  		// Rosetta uses this UtxoOperation to provide INPUT amounts
  1017  		utxoOpsForTxn = append(utxoOpsForTxn, utxoOp)
  1018  	}
  1019  
  1020  	// (5) Give any change back to the bidder.
  1021  	if bidderChangeNanos > 0 {
  1022  		nextUtxoIndex += 1
  1023  		bidderChangeOutputKey := &UtxoKey{
  1024  			TxID:  *txHash,
  1025  			Index: nextUtxoIndex,
  1026  		}
  1027  
  1028  		utxoEntry := UtxoEntry{
  1029  			AmountNanos: bidderChangeNanos,
  1030  			PublicKey:   bidderPublicKey,
  1031  			BlockHeight: blockHeight,
  1032  			UtxoType:    UtxoTypeNFTCreatorRoyalty,
  1033  
  1034  			UtxoKey: bidderChangeOutputKey,
  1035  			// We leave the position unset and isSpent to false by default.
  1036  			// The position will be set in the call to _addUtxo.
  1037  		}
  1038  
  1039  		utxoOp, err := bav._addUtxo(&utxoEntry)
  1040  		if err != nil {
  1041  			return 0, 0, nil, errors.Wrapf(err, "_connectAcceptNFTBid: Problem adding output utxo")
  1042  		}
  1043  		nftPaymentUtxoKeys = append(nftPaymentUtxoKeys, bidderChangeOutputKey)
  1044  
  1045  		// Rosetta uses this UtxoOperation to provide INPUT amounts
  1046  		utxoOpsForTxn = append(utxoOpsForTxn, utxoOp)
  1047  	}
  1048  
  1049  	// We don't do a royalty if the number of coins in circulation is too low.
  1050  	if existingProfileEntry.CoinsInCirculationNanos < bav.Params.CreatorCoinAutoSellThresholdNanos {
  1051  		creatorCoinRoyaltyNanos = 0
  1052  	}
  1053  
  1054  	// (6) Add creator coin royalties to deso locked. If the number of coins in circulation is
  1055  	// less than the "auto sell threshold" we burn the deso.
  1056  	newCoinEntry := prevCoinEntry
  1057  	if creatorCoinRoyaltyNanos > 0 {
  1058  		// Make a copy of the previous coin entry. It has no pointers, so a direct copy is ok.
  1059  		newCoinEntry.DeSoLockedNanos += creatorCoinRoyaltyNanos
  1060  		existingProfileEntry.CoinEntry = newCoinEntry
  1061  		bav._setProfileEntryMappings(existingProfileEntry)
  1062  	}
  1063  
  1064  	// (7) Save a copy of the previous postEntry and then decrement NumNFTCopiesForSale.
  1065  	prevPostEntry := &PostEntry{}
  1066  	*prevPostEntry = *nftPostEntry
  1067  	nftPostEntry.NumNFTCopiesForSale--
  1068  	bav._setPostEntryMappings(nftPostEntry)
  1069  
  1070  	// Add an operation to the list at the end indicating we've connected an NFT bid.
  1071  	utxoOpsForTxn = append(utxoOpsForTxn, &UtxoOperation{
  1072  		Type:                      OperationTypeAcceptNFTBid,
  1073  		PrevNFTEntry:              prevNFTEntry,
  1074  		PrevPostEntry:             prevPostEntry,
  1075  		PrevCoinEntry:             &prevCoinEntry,
  1076  		DeletedNFTBidEntries:      deletedBidEntries,
  1077  		NFTPaymentUtxoKeys:        nftPaymentUtxoKeys,
  1078  		NFTSpentUtxoEntries:       spentUtxoEntries,
  1079  		PrevAcceptedNFTBidEntries: prevAcceptedBidHistory,
  1080  
  1081  		// Rosetta fields.
  1082  		AcceptNFTBidCreatorPublicKey:    nftPostEntry.PosterPublicKey,
  1083  		AcceptNFTBidBidderPublicKey:     bidderPublicKey,
  1084  		AcceptNFTBidCreatorRoyaltyNanos: creatorCoinRoyaltyNanos,
  1085  	})
  1086  
  1087  	// HARDCORE SANITY CHECK:
  1088  	//  - Before returning we do one more sanity check that money hasn't been printed.
  1089  	//
  1090  	// Seller balance diff:
  1091  	sellerBalanceAfter, err := bav.GetSpendableDeSoBalanceNanosForPublicKey(txn.PublicKey, tipHeight)
  1092  	if err != nil {
  1093  		return 0, 0, nil, fmt.Errorf(
  1094  			"_connectAcceptNFTBid: Problem getting final balance for seller pubkey: %v",
  1095  			PkToStringBoth(txn.PublicKey))
  1096  	}
  1097  	sellerDiff := int64(sellerBalanceAfter) - int64(sellerBalanceBefore)
  1098  	// Bidder balance diff (only relevant if bidder != seller):
  1099  	bidderDiff := int64(0)
  1100  	if !reflect.DeepEqual(bidderPublicKey, txn.PublicKey) {
  1101  		bidderBalanceAfter, err := bav.GetSpendableDeSoBalanceNanosForPublicKey(bidderPublicKey, tipHeight)
  1102  		if err != nil {
  1103  			return 0, 0, nil, fmt.Errorf(
  1104  				"_connectAcceptNFTBid: Problem getting final balance for bidder pubkey: %v",
  1105  				PkToStringBoth(bidderPublicKey))
  1106  		}
  1107  		bidderDiff = int64(bidderBalanceAfter) - int64(bidderBalanceBefore)
  1108  	}
  1109  	// Creator balance diff (only relevant if creator != seller and creator != bidder):
  1110  	creatorDiff := int64(0)
  1111  	if !reflect.DeepEqual(nftPostEntry.PosterPublicKey, txn.PublicKey) &&
  1112  		!reflect.DeepEqual(nftPostEntry.PosterPublicKey, bidderPublicKey) {
  1113  		creatorBalanceAfter, err := bav.GetSpendableDeSoBalanceNanosForPublicKey(nftPostEntry.PosterPublicKey, tipHeight)
  1114  		if err != nil {
  1115  			return 0, 0, nil, fmt.Errorf(
  1116  				"_connectAcceptNFTBid: Problem getting final balance for poster pubkey: %v",
  1117  				PkToStringBoth(nftPostEntry.PosterPublicKey))
  1118  		}
  1119  		creatorDiff = int64(creatorBalanceAfter) - int64(creatorBalanceBefore)
  1120  	}
  1121  	// Creator coin diff:
  1122  	coinDiff := int64(newCoinEntry.DeSoLockedNanos) - int64(prevCoinEntry.DeSoLockedNanos)
  1123  	// Now the actual check. Use bigints to avoid getting fooled by overflow.
  1124  	sellerPlusBidderDiff := big.NewInt(0).Add(big.NewInt(sellerDiff), big.NewInt(bidderDiff))
  1125  	creatorPlusCoinDiff := big.NewInt(0).Add(big.NewInt(creatorDiff), big.NewInt(coinDiff))
  1126  	totalDiff := big.NewInt(0).Add(sellerPlusBidderDiff, creatorPlusCoinDiff)
  1127  	if totalDiff.Cmp(big.NewInt(0)) > 0 {
  1128  		return 0, 0, nil, fmt.Errorf(
  1129  			"_connectAcceptNFTBid: Sum of participant diffs is >0 (%d, %d, %d, %d)",
  1130  			sellerDiff, bidderDiff, creatorDiff, coinDiff)
  1131  	}
  1132  
  1133  	return totalInput, totalOutput, utxoOpsForTxn, nil
  1134  }
  1135  
  1136  func (bav *UtxoView) _connectNFTBid(
  1137  	txn *MsgDeSoTxn, txHash *BlockHash, blockHeight uint32, verifySignatures bool) (
  1138  	_totalInput uint64, _totalOutput uint64, _utxoOps []*UtxoOperation, _err error) {
  1139  	if bav.GlobalParamsEntry.MaxCopiesPerNFT == 0 {
  1140  		return 0, 0, nil, fmt.Errorf("_connectNFTBid: called with zero MaxCopiesPerNFT")
  1141  	}
  1142  
  1143  	// Check that the transaction has the right TxnType.
  1144  	if txn.TxnMeta.GetTxnType() != TxnTypeNFTBid {
  1145  		return 0, 0, nil, fmt.Errorf("_connectNFTBid: called with bad TxnType %s",
  1146  			txn.TxnMeta.GetTxnType().String())
  1147  	}
  1148  	txMeta := txn.TxnMeta.(*NFTBidMetadata)
  1149  
  1150  	// Verify that the postEntry being bid on exists, is an NFT, and supports the given serial #.
  1151  	postEntry := bav.GetPostEntryForPostHash(txMeta.NFTPostHash)
  1152  	if postEntry == nil || postEntry.isDeleted {
  1153  		return 0, 0, nil, RuleErrorNFTBidOnNonExistentPost
  1154  	} else if !postEntry.IsNFT {
  1155  		return 0, 0, nil, RuleErrorNFTBidOnPostThatIsNotAnNFT
  1156  	} else if txMeta.SerialNumber > postEntry.NumNFTCopies {
  1157  		return 0, 0, nil, RuleErrorNFTBidOnInvalidSerialNumber
  1158  	}
  1159  
  1160  	// Validate the nftEntry.  Note that there is a special case where a bidder can submit a bid
  1161  	// on SerialNumber zero.  This acts as a blanket bid on any serial number version of this NFT
  1162  	// As a result, the nftEntry will be nil and should not be validated.
  1163  	nftKey := MakeNFTKey(txMeta.NFTPostHash, txMeta.SerialNumber)
  1164  	nftEntry := bav.GetNFTEntryForNFTKey(&nftKey)
  1165  	bidderPKID := bav.GetPKIDForPublicKey(txn.PublicKey)
  1166  	if bidderPKID == nil || bidderPKID.isDeleted {
  1167  		return 0, 0, nil, fmt.Errorf("_connectNFTBid: PKID for bidder public key %v doesn't exist; this should never happen", string(txn.PublicKey))
  1168  	}
  1169  
  1170  	// Save a copy of the bid entry so that we can use it in the disconnect.
  1171  	nftBidKey := MakeNFTBidKey(bidderPKID.PKID, txMeta.NFTPostHash, txMeta.SerialNumber)
  1172  	prevNFTBidEntry := bav.GetNFTBidEntryForNFTBidKey(&nftBidKey)
  1173  
  1174  	if txMeta.SerialNumber != uint64(0) {
  1175  		// Verify the NFT entry that is being bid on exists.
  1176  		if nftEntry == nil || nftEntry.isDeleted {
  1177  			return 0, 0, nil, RuleErrorNFTBidOnNonExistentNFTEntry
  1178  		}
  1179  
  1180  		// Verify the NFT entry being bid on is for sale.
  1181  		if !nftEntry.IsForSale {
  1182  			return 0, 0, nil, RuleErrorNFTBidOnNFTThatIsNotForSale
  1183  		}
  1184  
  1185  		// Verify the NFT is not a pending transfer.
  1186  		if nftEntry.IsPending {
  1187  			return 0, 0, nil, RuleErrorCannotBidForPendingNFTTransfer
  1188  		}
  1189  
  1190  		// Verify that the bidder is not the current owner of the NFT.
  1191  		if reflect.DeepEqual(nftEntry.OwnerPKID, bidderPKID.PKID) {
  1192  			return 0, 0, nil, RuleErrorNFTOwnerCannotBidOnOwnedNFT
  1193  		}
  1194  
  1195  		// Verify that the bid amount is greater than the min bid amount for this NFT.
  1196  		// We allow BidAmountNanos to be 0 if there exists a previous bid entry. A value of 0 indicates that we should delete the entry.
  1197  		if txMeta.BidAmountNanos < nftEntry.MinBidAmountNanos && !(txMeta.BidAmountNanos == 0 && prevNFTBidEntry != nil) {
  1198  			return 0, 0, nil, RuleErrorNFTBidLessThanMinBidAmountNanos
  1199  		}
  1200  	}
  1201  
  1202  	// Connect basic txn to get the total input and the total output without
  1203  	// considering the transaction metadata.
  1204  	totalInput, totalOutput, utxoOpsForTxn, err := bav._connectBasicTransfer(
  1205  		txn, txHash, blockHeight, verifySignatures)
  1206  	if err != nil {
  1207  		return 0, 0, nil, errors.Wrapf(err, "_connectNFTBid: ")
  1208  	}
  1209  
  1210  	// We assume the tip is right before the block in which this txn is about to be applied.
  1211  	tipHeight := uint32(0)
  1212  	if blockHeight > 0 {
  1213  		tipHeight = blockHeight - 1
  1214  	}
  1215  	// Verify that the transaction creator has sufficient deso to create the bid.
  1216  	spendableBalance, err := bav.GetSpendableDeSoBalanceNanosForPublicKey(txn.PublicKey, tipHeight)
  1217  	if err != nil {
  1218  		return 0, 0, nil, errors.Wrapf(err, "_connectNFTBid: Error getting bidder balance: ")
  1219  
  1220  	} else if txMeta.BidAmountNanos > spendableBalance && blockHeight > BrokenNFTBidsFixBlockHeight {
  1221  		return 0, 0, nil, RuleErrorInsufficientFundsForNFTBid
  1222  	}
  1223  
  1224  	// Force the input to be non-zero so that we can prevent replay attacks.
  1225  	if totalInput == 0 {
  1226  		return 0, 0, nil, RuleErrorNFTBidRequiresNonZeroInput
  1227  	}
  1228  
  1229  	if verifySignatures {
  1230  		// _connectBasicTransfer has already checked that the transaction is
  1231  		// signed by the top-level public key, which we take to be the poster's
  1232  		// public key.
  1233  	}
  1234  
  1235  	// If an old bid exists, delete it.
  1236  	if prevNFTBidEntry != nil {
  1237  		bav._deleteNFTBidEntryMappings(prevNFTBidEntry)
  1238  	}
  1239  
  1240  	// If the new bid has a non-zero amount, set it.
  1241  	if txMeta.BidAmountNanos != 0 {
  1242  		// Zero bids are not allowed, submitting a zero bid effectively withdraws a prior bid.
  1243  		newBidEntry := &NFTBidEntry{
  1244  			BidderPKID:     bidderPKID.PKID,
  1245  			NFTPostHash:    txMeta.NFTPostHash,
  1246  			SerialNumber:   txMeta.SerialNumber,
  1247  			BidAmountNanos: txMeta.BidAmountNanos,
  1248  		}
  1249  		bav._setNFTBidEntryMappings(newBidEntry)
  1250  	}
  1251  
  1252  	// Add an operation to the list at the end indicating we've connected an NFT bid.
  1253  	utxoOpsForTxn = append(utxoOpsForTxn, &UtxoOperation{
  1254  		Type:            OperationTypeNFTBid,
  1255  		PrevNFTBidEntry: prevNFTBidEntry,
  1256  	})
  1257  
  1258  	return totalInput, totalOutput, utxoOpsForTxn, nil
  1259  }
  1260  
  1261  func (bav *UtxoView) _connectNFTTransfer(
  1262  	txn *MsgDeSoTxn, txHash *BlockHash, blockHeight uint32, verifySignatures bool) (
  1263  	_totalInput uint64, _totalOutput uint64, _utxoOps []*UtxoOperation, _err error) {
  1264  
  1265  	if blockHeight < NFTTransferOrBurnAndDerivedKeysBlockHeight {
  1266  		return 0, 0, nil, RuleErrorNFTTransferBeforeBlockHeight
  1267  	}
  1268  
  1269  	// Check that the transaction has the right TxnType.
  1270  	if txn.TxnMeta.GetTxnType() != TxnTypeNFTTransfer {
  1271  		return 0, 0, nil, fmt.Errorf("_connectNFTTransfer: called with bad TxnType %s",
  1272  			txn.TxnMeta.GetTxnType().String())
  1273  	}
  1274  	txMeta := txn.TxnMeta.(*NFTTransferMetadata)
  1275  
  1276  	// Check that the specified receiver public key is valid.
  1277  	if len(txMeta.ReceiverPublicKey) != btcec.PubKeyBytesLenCompressed {
  1278  		return 0, 0, nil, RuleErrorNFTTransferInvalidReceiverPubKeySize
  1279  	}
  1280  
  1281  	// Check that the sender and receiver public keys are different.
  1282  	if reflect.DeepEqual(txn.PublicKey, txMeta.ReceiverPublicKey) {
  1283  		return 0, 0, nil, RuleErrorNFTTransferCannotTransferToSelf
  1284  	}
  1285  
  1286  	// Verify the NFT entry exists.
  1287  	nftKey := MakeNFTKey(txMeta.NFTPostHash, txMeta.SerialNumber)
  1288  	prevNFTEntry := bav.GetNFTEntryForNFTKey(&nftKey)
  1289  	if prevNFTEntry == nil || prevNFTEntry.isDeleted {
  1290  		return 0, 0, nil, RuleErrorCannotTransferNonExistentNFT
  1291  	}
  1292  
  1293  	// Verify that the updater is the owner of the NFT.
  1294  	updaterPKID := bav.GetPKIDForPublicKey(txn.PublicKey)
  1295  	if updaterPKID == nil || updaterPKID.isDeleted {
  1296  		return 0, 0, nil, fmt.Errorf("_connectNFTTransfer: non-existent updaterPKID: %s",
  1297  			PkToString(txn.PublicKey, bav.Params))
  1298  	}
  1299  	if !reflect.DeepEqual(prevNFTEntry.OwnerPKID, updaterPKID.PKID) {
  1300  		return 0, 0, nil, RuleErrorNFTTransferByNonOwner
  1301  	}
  1302  
  1303  	// Fetch the receiver's PKID and make sure it exists.
  1304  	receiverPKID := bav.GetPKIDForPublicKey(txMeta.ReceiverPublicKey)
  1305  	// Sanity check that we found a PKID entry for these pub keys (should never fail).
  1306  	if receiverPKID == nil || receiverPKID.isDeleted {
  1307  		return 0, 0, nil, fmt.Errorf(
  1308  			"_connectNFTTransfer: Found nil or deleted PKID for receiver, this should never "+
  1309  				"happen. Receiver pubkey: %v", PkToStringMainnet(txMeta.ReceiverPublicKey))
  1310  	}
  1311  
  1312  	// Make sure that the NFT entry is not for sale.
  1313  	if prevNFTEntry.IsForSale {
  1314  		return 0, 0, nil, RuleErrorCannotTransferForSaleNFT
  1315  	}
  1316  
  1317  	// Sanity check that the NFT entry is correct.
  1318  	if !reflect.DeepEqual(prevNFTEntry.NFTPostHash, txMeta.NFTPostHash) ||
  1319  		!reflect.DeepEqual(prevNFTEntry.SerialNumber, txMeta.SerialNumber) {
  1320  		return 0, 0, nil, fmt.Errorf("_connectNFTTransfer: prevNFTEntry %v is inconsistent with txMeta %v;"+
  1321  			" this should never happen.", prevNFTEntry, txMeta)
  1322  	}
  1323  
  1324  	// Get the postEntry so we can check for unlockable content.
  1325  	nftPostEntry := bav.GetPostEntryForPostHash(txMeta.NFTPostHash)
  1326  	if nftPostEntry == nil || nftPostEntry.isDeleted {
  1327  		return 0, 0, nil, fmt.Errorf("_connectNFTTransfer: non-existent nftPostEntry for NFTPostHash: %s",
  1328  			txMeta.NFTPostHash.String())
  1329  	}
  1330  
  1331  	// If the post entry requires the NFT to have unlockable text, make sure it is provided.
  1332  	if nftPostEntry.HasUnlockable && len(txMeta.UnlockableText) == 0 {
  1333  		return 0, 0, nil, RuleErrorCannotTransferUnlockableNFTWithoutUnlockable
  1334  	}
  1335  
  1336  	// Check the length of the UnlockableText.
  1337  	if uint64(len(txMeta.UnlockableText)) > bav.Params.MaxPrivateMessageLengthBytes {
  1338  		return 0, 0, nil, errors.Wrapf(
  1339  			RuleErrorUnlockableTextLengthExceedsMax, "_connectNFTTransfer: "+
  1340  				"UnlockableTextLen = %d; Max length = %d",
  1341  			len(txMeta.UnlockableText), bav.Params.MaxPrivateMessageLengthBytes)
  1342  	}
  1343  
  1344  	// Connect basic txn to get the total input and the total output without
  1345  	// considering the transaction metadata.
  1346  	totalInput, totalOutput, utxoOpsForTxn, err := bav._connectBasicTransfer(
  1347  		txn, txHash, blockHeight, verifySignatures)
  1348  	if err != nil {
  1349  		return 0, 0, nil, errors.Wrapf(err, "_connectNFTTransfer: ")
  1350  	}
  1351  
  1352  	// Force the input to be non-zero so that we can prevent replay attacks.
  1353  	if totalInput == 0 {
  1354  		return 0, 0, nil, RuleErrorNFTTransferRequiresNonZeroInput
  1355  	}
  1356  
  1357  	if verifySignatures {
  1358  		// _connectBasicTransfer has already checked that the transaction is
  1359  		// signed by the top-level public key, which we take to be the NFT owner's
  1360  		// public key.
  1361  	}
  1362  
  1363  	// Now we are ready to transfer the NFT.
  1364  
  1365  	// Make a copy of the previous NFT
  1366  	newNFTEntry := *prevNFTEntry
  1367  	// Update the fields that were set during this transfer.
  1368  	newNFTEntry.LastOwnerPKID = prevNFTEntry.OwnerPKID
  1369  	newNFTEntry.OwnerPKID = receiverPKID.PKID
  1370  	newNFTEntry.UnlockableText = txMeta.UnlockableText
  1371  	newNFTEntry.IsPending = true
  1372  
  1373  	// Set the new entry in the view.
  1374  	bav._deleteNFTEntryMappings(prevNFTEntry)
  1375  	bav._setNFTEntryMappings(&newNFTEntry)
  1376  
  1377  	// Add an operation to the list at the end indicating we've connected an NFT update.
  1378  	utxoOpsForTxn = append(utxoOpsForTxn, &UtxoOperation{
  1379  		Type:         OperationTypeNFTTransfer,
  1380  		PrevNFTEntry: prevNFTEntry,
  1381  	})
  1382  
  1383  	return totalInput, totalOutput, utxoOpsForTxn, nil
  1384  }
  1385  
  1386  func (bav *UtxoView) _connectAcceptNFTTransfer(
  1387  	txn *MsgDeSoTxn, txHash *BlockHash, blockHeight uint32, verifySignatures bool) (
  1388  	_totalInput uint64, _totalOutput uint64, _utxoOps []*UtxoOperation, _err error) {
  1389  
  1390  	if blockHeight < NFTTransferOrBurnAndDerivedKeysBlockHeight {
  1391  		return 0, 0, nil, RuleErrorAcceptNFTTransferBeforeBlockHeight
  1392  	}
  1393  
  1394  	// Check that the transaction has the right TxnType.
  1395  	if txn.TxnMeta.GetTxnType() != TxnTypeAcceptNFTTransfer {
  1396  		return 0, 0, nil, fmt.Errorf("_connectAcceptNFTTransfer: called with bad TxnType %s",
  1397  			txn.TxnMeta.GetTxnType().String())
  1398  	}
  1399  	txMeta := txn.TxnMeta.(*AcceptNFTTransferMetadata)
  1400  
  1401  	// Verify the NFT entry exists.
  1402  	nftKey := MakeNFTKey(txMeta.NFTPostHash, txMeta.SerialNumber)
  1403  	prevNFTEntry := bav.GetNFTEntryForNFTKey(&nftKey)
  1404  	if prevNFTEntry == nil || prevNFTEntry.isDeleted {
  1405  		return 0, 0, nil, RuleErrorCannotAcceptTransferOfNonExistentNFT
  1406  	}
  1407  
  1408  	// Verify that the updater is the owner of the NFT.
  1409  	updaterPKID := bav.GetPKIDForPublicKey(txn.PublicKey)
  1410  	if updaterPKID == nil || updaterPKID.isDeleted {
  1411  		return 0, 0, nil, fmt.Errorf("_connectAcceptNFTTransfer: non-existent updaterPKID: %s",
  1412  			PkToString(txn.PublicKey, bav.Params))
  1413  	}
  1414  	if !reflect.DeepEqual(prevNFTEntry.OwnerPKID, updaterPKID.PKID) {
  1415  		return 0, 0, nil, RuleErrorAcceptNFTTransferByNonOwner
  1416  	}
  1417  
  1418  	// Verify that the NFT is actually pending.
  1419  	if !prevNFTEntry.IsPending {
  1420  		return 0, 0, nil, RuleErrorAcceptNFTTransferForNonPendingNFT
  1421  	}
  1422  
  1423  	// Sanity check that the NFT entry is not for sale.
  1424  	if prevNFTEntry.IsForSale {
  1425  		return 0, 0, nil, fmt.Errorf(
  1426  			"_connectAcceptNFTTransfer: attempted to accept NFT transfer of NFT that is for "+
  1427  				"sale. This should never happen; txMeta %v.", txMeta)
  1428  	}
  1429  
  1430  	// Sanity check that the NFT entry is correct.
  1431  	if !reflect.DeepEqual(prevNFTEntry.NFTPostHash, txMeta.NFTPostHash) ||
  1432  		!reflect.DeepEqual(prevNFTEntry.SerialNumber, txMeta.SerialNumber) {
  1433  		return 0, 0, nil, fmt.Errorf("_connectAcceptNFTTransfer: prevNFTEntry %v is "+
  1434  			"inconsistent with txMeta %v; this should never happen.", prevNFTEntry, txMeta)
  1435  	}
  1436  
  1437  	// Connect basic txn to get the total input and the total output without
  1438  	// considering the transaction metadata.
  1439  	totalInput, totalOutput, utxoOpsForTxn, err := bav._connectBasicTransfer(
  1440  		txn, txHash, blockHeight, verifySignatures)
  1441  	if err != nil {
  1442  		return 0, 0, nil, errors.Wrapf(err, "_connectAcceptNFTTransfer: ")
  1443  	}
  1444  
  1445  	// Force the input to be non-zero so that we can prevent replay attacks.
  1446  	if totalInput == 0 {
  1447  		return 0, 0, nil, RuleErrorAcceptNFTTransferRequiresNonZeroInput
  1448  	}
  1449  
  1450  	if verifySignatures {
  1451  		// _connectBasicTransfer has already checked that the transaction is
  1452  		// signed by the top-level public key, which we take to be the NFT owner's
  1453  		// public key.
  1454  	}
  1455  
  1456  	// Now we are ready to transfer the NFT.
  1457  
  1458  	// Create the updated NFTEntry (everything the same except for IsPending) and set it.
  1459  	newNFTEntry := *prevNFTEntry
  1460  	newNFTEntry.IsPending = false
  1461  	bav._deleteNFTEntryMappings(prevNFTEntry)
  1462  	bav._setNFTEntryMappings(&newNFTEntry)
  1463  
  1464  	// Add an operation for the accepted NFT transfer.
  1465  	utxoOpsForTxn = append(utxoOpsForTxn, &UtxoOperation{
  1466  		Type:         OperationTypeAcceptNFTTransfer,
  1467  		PrevNFTEntry: prevNFTEntry,
  1468  	})
  1469  
  1470  	return totalInput, totalOutput, utxoOpsForTxn, nil
  1471  }
  1472  
  1473  func (bav *UtxoView) _connectBurnNFT(
  1474  	txn *MsgDeSoTxn, txHash *BlockHash, blockHeight uint32, verifySignatures bool) (
  1475  	_totalInput uint64, _totalOutput uint64, _utxoOps []*UtxoOperation, _err error) {
  1476  
  1477  	if blockHeight < NFTTransferOrBurnAndDerivedKeysBlockHeight {
  1478  		return 0, 0, nil, RuleErrorBurnNFTBeforeBlockHeight
  1479  	}
  1480  
  1481  	// Check that the transaction has the right TxnType.
  1482  	if txn.TxnMeta.GetTxnType() != TxnTypeBurnNFT {
  1483  		return 0, 0, nil, fmt.Errorf("_connectBurnNFT: called with bad TxnType %s",
  1484  			txn.TxnMeta.GetTxnType().String())
  1485  	}
  1486  	txMeta := txn.TxnMeta.(*BurnNFTMetadata)
  1487  
  1488  	// Verify the NFT entry exists.
  1489  	nftKey := MakeNFTKey(txMeta.NFTPostHash, txMeta.SerialNumber)
  1490  	nftEntry := bav.GetNFTEntryForNFTKey(&nftKey)
  1491  	if nftEntry == nil || nftEntry.isDeleted {
  1492  		return 0, 0, nil, RuleErrorCannotBurnNonExistentNFT
  1493  	}
  1494  
  1495  	// Verify that the updater is the owner of the NFT.
  1496  	updaterPKID := bav.GetPKIDForPublicKey(txn.PublicKey)
  1497  	if updaterPKID == nil || updaterPKID.isDeleted {
  1498  		return 0, 0, nil, fmt.Errorf("_connectBurnNFT: non-existent updaterPKID: %s",
  1499  			PkToString(txn.PublicKey, bav.Params))
  1500  	}
  1501  	if !reflect.DeepEqual(nftEntry.OwnerPKID, updaterPKID.PKID) {
  1502  		return 0, 0, nil, RuleErrorBurnNFTByNonOwner
  1503  	}
  1504  
  1505  	// Verify that the NFT is not for sale.
  1506  	if nftEntry.IsForSale {
  1507  		return 0, 0, nil, RuleErrorCannotBurnNFTThatIsForSale
  1508  	}
  1509  
  1510  	// Sanity check that the NFT entry is correct.
  1511  	if !reflect.DeepEqual(nftEntry.NFTPostHash, txMeta.NFTPostHash) ||
  1512  		!reflect.DeepEqual(nftEntry.SerialNumber, txMeta.SerialNumber) {
  1513  		return 0, 0, nil, fmt.Errorf("_connectBurnNFT: nftEntry %v is "+
  1514  			"inconsistent with txMeta %v; this should never happen.", nftEntry, txMeta)
  1515  	}
  1516  
  1517  	// Get the postEntry so we can increment the burned copies count.
  1518  	nftPostEntry := bav.GetPostEntryForPostHash(txMeta.NFTPostHash)
  1519  	if nftPostEntry == nil || nftPostEntry.isDeleted {
  1520  		return 0, 0, nil, fmt.Errorf(
  1521  			"_connectBurnNFT: non-existent nftPostEntry for NFTPostHash: %s",
  1522  			txMeta.NFTPostHash.String())
  1523  	}
  1524  
  1525  	// Connect basic txn to get the total input and the total output without
  1526  	// considering the transaction metadata.
  1527  	totalInput, totalOutput, utxoOpsForTxn, err := bav._connectBasicTransfer(
  1528  		txn, txHash, blockHeight, verifySignatures)
  1529  	if err != nil {
  1530  		return 0, 0, nil, errors.Wrapf(err, "_connectBurnNFT: ")
  1531  	}
  1532  
  1533  	// Force the input to be non-zero so that we can prevent replay attacks.
  1534  	if totalInput == 0 {
  1535  		return 0, 0, nil, RuleErrorBurnNFTRequiresNonZeroInput
  1536  	}
  1537  
  1538  	if verifySignatures {
  1539  		// _connectBasicTransfer has already checked that the transaction is
  1540  		// signed by the top-level public key, which we take to be the NFT owner's
  1541  		// public key.
  1542  	}
  1543  
  1544  	// Create a backup before we burn the NFT.
  1545  	prevNFTEntry := *nftEntry
  1546  
  1547  	// Delete the NFT.
  1548  	bav._deleteNFTEntryMappings(nftEntry)
  1549  
  1550  	// Save a copy of the previous postEntry and then increment NumNFTCopiesBurned.
  1551  	prevPostEntry := *nftPostEntry
  1552  	nftPostEntry.NumNFTCopiesBurned++
  1553  	bav._deletePostEntryMappings(&prevPostEntry)
  1554  	bav._setPostEntryMappings(nftPostEntry)
  1555  
  1556  	// Add an operation for the burnt NFT.
  1557  	utxoOpsForTxn = append(utxoOpsForTxn, &UtxoOperation{
  1558  		Type:          OperationTypeBurnNFT,
  1559  		PrevNFTEntry:  &prevNFTEntry,
  1560  		PrevPostEntry: &prevPostEntry,
  1561  	})
  1562  
  1563  	return totalInput, totalOutput, utxoOpsForTxn, nil
  1564  }
  1565  
  1566  func (bav *UtxoView) _disconnectCreateNFT(
  1567  	operationType OperationType, currentTxn *MsgDeSoTxn, txnHash *BlockHash,
  1568  	utxoOpsForTxn []*UtxoOperation, blockHeight uint32) error {
  1569  
  1570  	// Verify that the last operation is a CreateNFT operation
  1571  	if len(utxoOpsForTxn) == 0 {
  1572  		return fmt.Errorf("_disconnectCreateNFT: utxoOperations are missing")
  1573  	}
  1574  	operationIndex := len(utxoOpsForTxn) - 1
  1575  	if utxoOpsForTxn[operationIndex].Type != OperationTypeCreateNFT {
  1576  		return fmt.Errorf("_disconnectCreateNFT: Trying to revert "+
  1577  			"OperationTypeCreateNFT but found type %v",
  1578  			utxoOpsForTxn[operationIndex].Type)
  1579  	}
  1580  	txMeta := currentTxn.TxnMeta.(*CreateNFTMetadata)
  1581  	operationData := utxoOpsForTxn[operationIndex]
  1582  	operationIndex--
  1583  
  1584  	// Get the postEntry corresponding to this txn.
  1585  	existingPostEntry := bav.GetPostEntryForPostHash(txMeta.NFTPostHash)
  1586  	// Sanity-check that it exists.
  1587  	if existingPostEntry == nil || existingPostEntry.isDeleted {
  1588  		return fmt.Errorf("_disconnectCreateNFT: Post entry for "+
  1589  			"post hash %v doesn't exist; this should never happen",
  1590  			txMeta.NFTPostHash.String())
  1591  	}
  1592  
  1593  	// Revert to the old post entry since we changed IsNFT, etc.
  1594  	bav._setPostEntryMappings(operationData.PrevPostEntry)
  1595  
  1596  	// Delete the NFT entries.
  1597  	posterPKID := bav.GetPKIDForPublicKey(existingPostEntry.PosterPublicKey)
  1598  	if posterPKID == nil || posterPKID.isDeleted {
  1599  		return fmt.Errorf("_disconnectCreateNFT: PKID for poster public key %v doesn't exist; this should never happen", string(existingPostEntry.PosterPublicKey))
  1600  	}
  1601  	for ii := uint64(1); ii <= txMeta.NumCopies; ii++ {
  1602  		nftEntry := &NFTEntry{
  1603  			OwnerPKID:    posterPKID.PKID,
  1604  			NFTPostHash:  txMeta.NFTPostHash,
  1605  			SerialNumber: ii,
  1606  			IsForSale:    true,
  1607  		}
  1608  		bav._deleteNFTEntryMappings(nftEntry)
  1609  	}
  1610  
  1611  	// Now revert the basic transfer with the remaining operations. Cut off
  1612  	// the CreatorCoin operation at the end since we just reverted it.
  1613  	return bav._disconnectBasicTransfer(
  1614  		currentTxn, txnHash, utxoOpsForTxn[:operationIndex+1], blockHeight)
  1615  }
  1616  
  1617  func (bav *UtxoView) _disconnectUpdateNFT(
  1618  	operationType OperationType, currentTxn *MsgDeSoTxn, txnHash *BlockHash,
  1619  	utxoOpsForTxn []*UtxoOperation, blockHeight uint32) error {
  1620  
  1621  	// Verify that the last operation is an UpdateNFT operation
  1622  	if len(utxoOpsForTxn) == 0 {
  1623  		return fmt.Errorf("_disconnectUpdateNFT: utxoOperations are missing")
  1624  	}
  1625  	operationIndex := len(utxoOpsForTxn) - 1
  1626  	if utxoOpsForTxn[operationIndex].Type != OperationTypeUpdateNFT {
  1627  		return fmt.Errorf("_disconnectUpdateNFT: Trying to revert "+
  1628  			"OperationTypeUpdateNFT but found type %v",
  1629  			utxoOpsForTxn[operationIndex].Type)
  1630  	}
  1631  	txMeta := currentTxn.TxnMeta.(*UpdateNFTMetadata)
  1632  	operationData := utxoOpsForTxn[operationIndex]
  1633  	operationIndex--
  1634  
  1635  	// In order to disconnect an updated NFT, we need to do the following:
  1636  	// 	(1) Revert the NFT entry to the previous one.
  1637  	//  (2) Add back all of the bids that were deleted (if any).
  1638  	//  (3) Revert the post entry since we updated num NFT copies for sale.
  1639  
  1640  	// Make sure that there is a prev NFT entry.
  1641  	if operationData.PrevNFTEntry == nil || operationData.PrevNFTEntry.isDeleted {
  1642  		return fmt.Errorf("_disconnectUpdateNFT: prev NFT entry doesn't exist; " +
  1643  			"this should never happen")
  1644  	}
  1645  
  1646  	// If the previous NFT entry was not for sale, it should not have had any bids to delete.
  1647  	if !operationData.PrevNFTEntry.IsForSale &&
  1648  		operationData.DeletedNFTBidEntries != nil &&
  1649  		len(operationData.DeletedNFTBidEntries) > 0 {
  1650  
  1651  		return fmt.Errorf("_disconnectUpdateNFT: prev NFT entry was not for sale but found " +
  1652  			"deleted bids anyway; this should never happen")
  1653  	}
  1654  
  1655  	// Set the old NFT entry.
  1656  	bav._setNFTEntryMappings(operationData.PrevNFTEntry)
  1657  
  1658  	// Set the old bids.
  1659  	if operationData.DeletedNFTBidEntries != nil {
  1660  		for _, nftBid := range operationData.DeletedNFTBidEntries {
  1661  			bav._setNFTBidEntryMappings(nftBid)
  1662  		}
  1663  	}
  1664  
  1665  	// Get the postEntry corresponding to this txn.
  1666  	existingPostEntry := bav.GetPostEntryForPostHash(txMeta.NFTPostHash)
  1667  	// Sanity-check that it exists.
  1668  	if existingPostEntry == nil || existingPostEntry.isDeleted {
  1669  		return fmt.Errorf("_disconnectUpdateNFT: Post entry for "+
  1670  			"post hash %v doesn't exist; this should never happen",
  1671  			txMeta.NFTPostHash.String())
  1672  	}
  1673  
  1674  	// Revert to the old post entry since we changed NumNFTCopiesForSale.
  1675  	bav._setPostEntryMappings(operationData.PrevPostEntry)
  1676  
  1677  	// Now revert the basic transfer with the remaining operations.
  1678  	return bav._disconnectBasicTransfer(
  1679  		currentTxn, txnHash, utxoOpsForTxn[:operationIndex+1], blockHeight)
  1680  }
  1681  
  1682  func (bav *UtxoView) _disconnectAcceptNFTBid(
  1683  	operationType OperationType, currentTxn *MsgDeSoTxn, txnHash *BlockHash,
  1684  	utxoOpsForTxn []*UtxoOperation, blockHeight uint32) error {
  1685  
  1686  	// Verify that the last operation is a CreatorCoinTransfer operation
  1687  	if len(utxoOpsForTxn) == 0 {
  1688  		return fmt.Errorf("_disconnectAcceptNFTBid: utxoOperations are missing")
  1689  	}
  1690  	operationIndex := len(utxoOpsForTxn) - 1
  1691  	if utxoOpsForTxn[operationIndex].Type != OperationTypeAcceptNFTBid {
  1692  		return fmt.Errorf("_disconnectAcceptNFTBid: Trying to revert "+
  1693  			"OperationTypeAcceptNFTBid but found type %v",
  1694  			utxoOpsForTxn[operationIndex].Type)
  1695  	}
  1696  	txMeta := currentTxn.TxnMeta.(*AcceptNFTBidMetadata)
  1697  	operationData := utxoOpsForTxn[operationIndex]
  1698  	operationIndex--
  1699  
  1700  	// We sometimes have some extra AddUtxo operations we need to remove
  1701  	// These are "implicit" outputs that always occur at the end of the
  1702  	// list of UtxoOperations. The number of implicit outputs is equal to
  1703  	// the total number of "Add" operations minus the explicit outputs.
  1704  	numUtxoAdds := 0
  1705  	for _, utxoOp := range utxoOpsForTxn {
  1706  		if utxoOp.Type == OperationTypeAddUtxo {
  1707  			numUtxoAdds += 1
  1708  		}
  1709  	}
  1710  	operationIndex -= numUtxoAdds - len(currentTxn.TxOutputs)
  1711  
  1712  	// In order to disconnect an accepted bid, we need to do the following:
  1713  	// 	(1) Revert the NFT entry to the previous one with the previous owner.
  1714  	//  (2) Add back all of the bids that were deleted.
  1715  	//  (3) Disconnect payment UTXOs.
  1716  	//  (4) Unspend bidder UTXOs.
  1717  	//  (5) Revert profileEntry to undo royalties added to DeSoLockedNanos.
  1718  	//  (6) Revert the postEntry since NumNFTCopiesForSale was decremented.
  1719  
  1720  	// (1) Set the old NFT entry.
  1721  	if operationData.PrevNFTEntry == nil || operationData.PrevNFTEntry.isDeleted {
  1722  		return fmt.Errorf("_disconnectAcceptNFTBid: prev NFT entry doesn't exist; " +
  1723  			"this should never happen")
  1724  	}
  1725  
  1726  	prevNFTEntry := operationData.PrevNFTEntry
  1727  	bav._setNFTEntryMappings(prevNFTEntry)
  1728  
  1729  	// Revert the accepted NFT bid history mappings
  1730  	bav._setAcceptNFTBidHistoryMappings(MakeNFTKey(prevNFTEntry.NFTPostHash, prevNFTEntry.SerialNumber), operationData.PrevAcceptedNFTBidEntries)
  1731  
  1732  	// (2) Set the old bids.
  1733  	if operationData.DeletedNFTBidEntries == nil || len(operationData.DeletedNFTBidEntries) == 0 {
  1734  		return fmt.Errorf("_disconnectAcceptNFTBid: DeletedNFTBidEntries doesn't exist; " +
  1735  			"this should never happen")
  1736  	}
  1737  
  1738  	for _, nftBid := range operationData.DeletedNFTBidEntries {
  1739  		bav._setNFTBidEntryMappings(nftBid)
  1740  	}
  1741  
  1742  	// (3) Revert payments made from accepting the NFT bids.
  1743  	if operationData.NFTPaymentUtxoKeys == nil || len(operationData.NFTPaymentUtxoKeys) == 0 {
  1744  		return fmt.Errorf("_disconnectAcceptNFTBid: NFTPaymentUtxoKeys was nil; " +
  1745  			"this should never happen")
  1746  	}
  1747  	// Note: these UTXOs need to be unadded in reverse order.
  1748  	for ii := len(operationData.NFTPaymentUtxoKeys) - 1; ii >= 0; ii-- {
  1749  		paymentUtxoKey := operationData.NFTPaymentUtxoKeys[ii]
  1750  		if err := bav._unAddUtxo(paymentUtxoKey); err != nil {
  1751  			return errors.Wrapf(err, "_disconnectAcceptNFTBid: Problem unAdding utxo %v: ", paymentUtxoKey)
  1752  		}
  1753  	}
  1754  
  1755  	// (4) Revert spent bidder UTXOs.
  1756  	if operationData.NFTSpentUtxoEntries == nil || len(operationData.NFTSpentUtxoEntries) == 0 {
  1757  		return fmt.Errorf("_disconnectAcceptNFTBid: NFTSpentUtxoEntries was nil; " +
  1758  			"this should never happen")
  1759  	}
  1760  	// Note: these UTXOs need to be unspent in reverse order.
  1761  	for ii := len(operationData.NFTSpentUtxoEntries) - 1; ii >= 0; ii-- {
  1762  		spentUtxoEntry := operationData.NFTSpentUtxoEntries[ii]
  1763  		if err := bav._unSpendUtxo(spentUtxoEntry); err != nil {
  1764  			return errors.Wrapf(err, "_disconnectAcceptNFTBid: Problem unSpending utxo %v: ", spentUtxoEntry)
  1765  		}
  1766  	}
  1767  
  1768  	// (5) Revert the creator's CoinEntry if a previous one exists.
  1769  	if operationData.PrevCoinEntry != nil {
  1770  		nftPostEntry := bav.GetPostEntryForPostHash(operationData.PrevNFTEntry.NFTPostHash)
  1771  		// We have to get the post entry first so that we have the poster's pub key.
  1772  		if nftPostEntry == nil || nftPostEntry.isDeleted {
  1773  			return fmt.Errorf("_disconnectAcceptNFTBid: nftPostEntry was nil; " +
  1774  				"this should never happen")
  1775  		}
  1776  		existingProfileEntry := bav.GetProfileEntryForPublicKey(nftPostEntry.PosterPublicKey)
  1777  		if existingProfileEntry == nil || existingProfileEntry.isDeleted {
  1778  			return fmt.Errorf("_disconnectAcceptNFTBid: existingProfileEntry was nil; " +
  1779  				"this should never happen")
  1780  		}
  1781  		existingProfileEntry.CoinEntry = *operationData.PrevCoinEntry
  1782  		bav._setProfileEntryMappings(existingProfileEntry)
  1783  	}
  1784  
  1785  	// (6) Verify a postEntry exists and then revert it since NumNFTCopiesForSale was decremented.
  1786  
  1787  	// Get the postEntry corresponding to this txn.
  1788  	existingPostEntry := bav.GetPostEntryForPostHash(txMeta.NFTPostHash)
  1789  	// Sanity-check that it exists.
  1790  	if existingPostEntry == nil || existingPostEntry.isDeleted {
  1791  		return fmt.Errorf("_disconnectAcceptNFTBid: Post entry for "+
  1792  			"post hash %v doesn't exist; this should never happen",
  1793  			txMeta.NFTPostHash.String())
  1794  	}
  1795  
  1796  	// Revert to the old post entry since we changed NumNFTCopiesForSale.
  1797  	bav._setPostEntryMappings(operationData.PrevPostEntry)
  1798  
  1799  	// Now revert the basic transfer with the remaining operations.
  1800  	return bav._disconnectBasicTransfer(
  1801  		currentTxn, txnHash, utxoOpsForTxn[:operationIndex+1], blockHeight)
  1802  }
  1803  
  1804  func (bav *UtxoView) _disconnectNFTBid(
  1805  	operationType OperationType, currentTxn *MsgDeSoTxn, txnHash *BlockHash,
  1806  	utxoOpsForTxn []*UtxoOperation, blockHeight uint32) error {
  1807  
  1808  	// Verify that the last operation is a CreatorCoinTransfer operation
  1809  	if len(utxoOpsForTxn) == 0 {
  1810  		return fmt.Errorf("_disconnectNFTBid: utxoOperations are missing")
  1811  	}
  1812  	operationIndex := len(utxoOpsForTxn) - 1
  1813  	if utxoOpsForTxn[operationIndex].Type != OperationTypeNFTBid {
  1814  		return fmt.Errorf("_disconnectNFTBid: Trying to revert "+
  1815  			"OperationTypeNFTBid but found type %v",
  1816  			utxoOpsForTxn[operationIndex].Type)
  1817  	}
  1818  	txMeta := currentTxn.TxnMeta.(*NFTBidMetadata)
  1819  	operationData := utxoOpsForTxn[operationIndex]
  1820  	operationIndex--
  1821  
  1822  	// Get the NFTBidEntry corresponding to this txn.
  1823  	bidderPKID := bav.GetPKIDForPublicKey(currentTxn.PublicKey)
  1824  	if bidderPKID == nil || bidderPKID.isDeleted {
  1825  		return fmt.Errorf("_disconnectNFTBid: PKID for bidder public key %v doesn't exist; this should never happen", string(currentTxn.PublicKey))
  1826  	}
  1827  	nftBidKey := MakeNFTBidKey(bidderPKID.PKID, txMeta.NFTPostHash, txMeta.SerialNumber)
  1828  	nftBidEntry := bav.GetNFTBidEntryForNFTBidKey(&nftBidKey)
  1829  
  1830  	// Only delete the bid entry mapping if it exists. Bids of 0 nanos delete bids without creating new ones.
  1831  	if nftBidEntry != nil {
  1832  		// We do not check if the existing entry is deleted or not. Because a bid amount of 0 cancels a bid (deletes
  1833  		// without creating one), if a user were to create a bid, cancel it, and create a new one, this disconnect logic
  1834  		// would encounter a state where the bid entry is delete.
  1835  		bav._deleteNFTBidEntryMappings(nftBidEntry)
  1836  	}
  1837  
  1838  	// If a previous entry exists, set it.
  1839  	if operationData.PrevNFTBidEntry != nil {
  1840  		bav._setNFTBidEntryMappings(operationData.PrevNFTBidEntry)
  1841  	}
  1842  
  1843  	// Now revert the basic transfer with the remaining operations.
  1844  	return bav._disconnectBasicTransfer(
  1845  		currentTxn, txnHash, utxoOpsForTxn[:operationIndex+1], blockHeight)
  1846  }
  1847  
  1848  func (bav *UtxoView) _disconnectNFTTransfer(
  1849  	operationType OperationType, currentTxn *MsgDeSoTxn, txnHash *BlockHash,
  1850  	utxoOpsForTxn []*UtxoOperation, blockHeight uint32) error {
  1851  
  1852  	// Verify that the last operation is an NFTTransfer operation
  1853  	if len(utxoOpsForTxn) == 0 {
  1854  		return fmt.Errorf("_disconnectNFTTransfer: utxoOperations are missing")
  1855  	}
  1856  	operationIndex := len(utxoOpsForTxn) - 1
  1857  	if utxoOpsForTxn[operationIndex].Type != OperationTypeNFTTransfer {
  1858  		return fmt.Errorf("_disconnectNFTTransfer: Trying to revert "+
  1859  			"OperationTypeNFTTransfer but found type %v",
  1860  			utxoOpsForTxn[operationIndex].Type)
  1861  	}
  1862  	txMeta := currentTxn.TxnMeta.(*NFTTransferMetadata)
  1863  	operationData := utxoOpsForTxn[operationIndex]
  1864  	operationIndex--
  1865  
  1866  	// Make sure that there is a prev NFT entry.
  1867  	if operationData.PrevNFTEntry == nil || operationData.PrevNFTEntry.isDeleted {
  1868  		return fmt.Errorf("_disconnectNFTTransfer: prev NFT entry doesn't exist; " +
  1869  			"this should never happen.")
  1870  	}
  1871  
  1872  	// Sanity check the old NFT entry PKID / PostHash / SerialNumber.
  1873  	updaterPKID := bav.GetPKIDForPublicKey(currentTxn.PublicKey)
  1874  	if updaterPKID == nil || updaterPKID.isDeleted {
  1875  		return fmt.Errorf("_disconnectNFTTransfer: non-existent updaterPKID: %s",
  1876  			PkToString(currentTxn.PublicKey, bav.Params))
  1877  	}
  1878  	if !reflect.DeepEqual(operationData.PrevNFTEntry.OwnerPKID, updaterPKID.PKID) {
  1879  		return fmt.Errorf(
  1880  			"_disconnectNFTTransfer: updaterPKID does not match NFT owner: %s, %s",
  1881  			PkToString(updaterPKID.PKID[:], bav.Params),
  1882  			PkToString(operationData.PrevNFTEntry.OwnerPKID[:], bav.Params))
  1883  	}
  1884  	if !reflect.DeepEqual(txMeta.NFTPostHash, operationData.PrevNFTEntry.NFTPostHash) ||
  1885  		txMeta.SerialNumber != operationData.PrevNFTEntry.SerialNumber {
  1886  		return fmt.Errorf("_disconnectNFTTransfer: txMeta post hash and serial number do "+
  1887  			"not match previous NFT entry; this should never happen (%v, %v).",
  1888  			txMeta, operationData.PrevNFTEntry)
  1889  	}
  1890  
  1891  	// Sanity check that the old NFT entry was not for sale.
  1892  	if operationData.PrevNFTEntry.IsForSale {
  1893  		return fmt.Errorf("_disconnecttNFTTransfer: prevNFT Entry was either not "+
  1894  			"pending or for sale (%v); this should never happen.", operationData.PrevNFTEntry)
  1895  	}
  1896  
  1897  	// Get the current NFT entry so we can delete it.
  1898  	nftKey := MakeNFTKey(txMeta.NFTPostHash, txMeta.SerialNumber)
  1899  	currNFTEntry := bav.GetNFTEntryForNFTKey(&nftKey)
  1900  	if currNFTEntry == nil || currNFTEntry.isDeleted {
  1901  		return fmt.Errorf("_disconnectNFTTransfer: currNFTEntry not found: %s, %d",
  1902  			txMeta.NFTPostHash.String(), txMeta.SerialNumber)
  1903  	}
  1904  
  1905  	// Set the old NFT entry.
  1906  	bav._deleteNFTEntryMappings(currNFTEntry)
  1907  	bav._setNFTEntryMappings(operationData.PrevNFTEntry)
  1908  
  1909  	// Now revert the basic transfer with the remaining operations.
  1910  	return bav._disconnectBasicTransfer(
  1911  		currentTxn, txnHash, utxoOpsForTxn[:operationIndex+1], blockHeight)
  1912  }
  1913  
  1914  func (bav *UtxoView) _disconnectAcceptNFTTransfer(
  1915  	operationType OperationType, currentTxn *MsgDeSoTxn, txnHash *BlockHash,
  1916  	utxoOpsForTxn []*UtxoOperation, blockHeight uint32) error {
  1917  
  1918  	// Verify that the last operation is an AcceptNFTTransfer operation
  1919  	if len(utxoOpsForTxn) == 0 {
  1920  		return fmt.Errorf("_disconnectAcceptNFTTransfer: utxoOperations are missing")
  1921  	}
  1922  	operationIndex := len(utxoOpsForTxn) - 1
  1923  	if utxoOpsForTxn[operationIndex].Type != OperationTypeAcceptNFTTransfer {
  1924  		return fmt.Errorf("_disconnectAcceptNFTTransfer: Trying to revert "+
  1925  			"OperationTypeAcceptNFTTransfer but found type %v",
  1926  			utxoOpsForTxn[operationIndex].Type)
  1927  	}
  1928  	txMeta := currentTxn.TxnMeta.(*AcceptNFTTransferMetadata)
  1929  	operationData := utxoOpsForTxn[operationIndex]
  1930  	operationIndex--
  1931  
  1932  	// Make sure that there is a prev NFT entry.
  1933  	if operationData.PrevNFTEntry == nil || operationData.PrevNFTEntry.isDeleted {
  1934  		return fmt.Errorf("_disconnectAcceptNFTTransfer: prev NFT entry doesn't exist; " +
  1935  			"this should never happen.")
  1936  	}
  1937  
  1938  	// Sanity check the old NFT entry PKID / PostHash / SerialNumber.
  1939  	updaterPKID := bav.GetPKIDForPublicKey(currentTxn.PublicKey)
  1940  	if updaterPKID == nil || updaterPKID.isDeleted {
  1941  		return fmt.Errorf("_disconnectAcceptNFTTransfer: non-existent updaterPKID: %s",
  1942  			PkToString(currentTxn.PublicKey, bav.Params))
  1943  	}
  1944  	if !reflect.DeepEqual(operationData.PrevNFTEntry.OwnerPKID, updaterPKID.PKID) {
  1945  		return fmt.Errorf(
  1946  			"_disconnectAcceptNFTTransfer: updaterPKID does not match NFT owner: %s, %s",
  1947  			PkToString(updaterPKID.PKID[:], bav.Params),
  1948  			PkToString(operationData.PrevNFTEntry.OwnerPKID[:], bav.Params))
  1949  	}
  1950  	if !reflect.DeepEqual(txMeta.NFTPostHash, operationData.PrevNFTEntry.NFTPostHash) ||
  1951  		txMeta.SerialNumber != operationData.PrevNFTEntry.SerialNumber {
  1952  		return fmt.Errorf("_disconnectAcceptNFTTransfer: txMeta post hash and serial number"+
  1953  			" do not match previous NFT entry; this should never happen (%v, %v).",
  1954  			txMeta, operationData.PrevNFTEntry)
  1955  	}
  1956  
  1957  	// Sanity check that the old NFT entry was pending and not for sale.
  1958  	if !operationData.PrevNFTEntry.IsPending || operationData.PrevNFTEntry.IsForSale {
  1959  		return fmt.Errorf("_disconnectAcceptNFTTransfer: prevNFT Entry was either not "+
  1960  			"pending or for sale (%v); this should never happen.", operationData.PrevNFTEntry)
  1961  	}
  1962  
  1963  	// Get the current NFT entry so we can delete it.
  1964  	nftKey := MakeNFTKey(txMeta.NFTPostHash, txMeta.SerialNumber)
  1965  	currNFTEntry := bav.GetNFTEntryForNFTKey(&nftKey)
  1966  	if currNFTEntry == nil || currNFTEntry.isDeleted {
  1967  		return fmt.Errorf("_disconnectAcceptNFTTransfer: currNFTEntry not found: %s, %d",
  1968  			txMeta.NFTPostHash.String(), txMeta.SerialNumber)
  1969  	}
  1970  
  1971  	// Delete the current NFT entry and set the old one.
  1972  	bav._deleteNFTEntryMappings(currNFTEntry)
  1973  	bav._setNFTEntryMappings(operationData.PrevNFTEntry)
  1974  
  1975  	// Now revert the basic transfer with the remaining operations.
  1976  	return bav._disconnectBasicTransfer(
  1977  		currentTxn, txnHash, utxoOpsForTxn[:operationIndex+1], blockHeight)
  1978  }
  1979  
  1980  func (bav *UtxoView) _disconnectBurnNFT(
  1981  	operationType OperationType, currentTxn *MsgDeSoTxn, txnHash *BlockHash,
  1982  	utxoOpsForTxn []*UtxoOperation, blockHeight uint32) error {
  1983  
  1984  	// Verify that the last operation is an BurnNFT operation
  1985  	if len(utxoOpsForTxn) == 0 {
  1986  		return fmt.Errorf("_disconnectBurnNFT: utxoOperations are missing")
  1987  	}
  1988  	operationIndex := len(utxoOpsForTxn) - 1
  1989  	if utxoOpsForTxn[operationIndex].Type != OperationTypeBurnNFT {
  1990  		return fmt.Errorf("_disconnectBurnNFT: Trying to revert "+
  1991  			"OperationTypeBurnNFT but found type %v",
  1992  			utxoOpsForTxn[operationIndex].Type)
  1993  	}
  1994  	txMeta := currentTxn.TxnMeta.(*BurnNFTMetadata)
  1995  	operationData := utxoOpsForTxn[operationIndex]
  1996  	operationIndex--
  1997  
  1998  	// Make sure that there is a prev NFT entry.
  1999  	if operationData.PrevNFTEntry == nil || operationData.PrevNFTEntry.isDeleted {
  2000  		return fmt.Errorf("_disconnectBurnNFT: prev NFT entry doesn't exist; " +
  2001  			"this should never happen.")
  2002  	}
  2003  
  2004  	// Make sure that there is a prev post entry.
  2005  	if operationData.PrevPostEntry == nil || operationData.PrevPostEntry.isDeleted {
  2006  		return fmt.Errorf("_disconnectBurnNFT: prev NFT entry doesn't exist; " +
  2007  			"this should never happen.")
  2008  	}
  2009  
  2010  	// Sanity check the old NFT entry PKID / PostHash / SerialNumber.
  2011  	updaterPKID := bav.GetPKIDForPublicKey(currentTxn.PublicKey)
  2012  	if updaterPKID == nil || updaterPKID.isDeleted {
  2013  		return fmt.Errorf("_disconnectBurnNFT: non-existent updaterPKID: %s",
  2014  			PkToString(currentTxn.PublicKey, bav.Params))
  2015  	}
  2016  	if !reflect.DeepEqual(operationData.PrevNFTEntry.OwnerPKID, updaterPKID.PKID) {
  2017  		return fmt.Errorf("_disconnectBurnNFT: updaterPKID does not match NFT owner: %s, %s",
  2018  			PkToString(updaterPKID.PKID[:], bav.Params),
  2019  			PkToString(operationData.PrevNFTEntry.OwnerPKID[:], bav.Params))
  2020  	}
  2021  	if !reflect.DeepEqual(txMeta.NFTPostHash, operationData.PrevNFTEntry.NFTPostHash) ||
  2022  		txMeta.SerialNumber != operationData.PrevNFTEntry.SerialNumber {
  2023  		return fmt.Errorf("_disconnectBurnNFT: txMeta post hash and serial number do "+
  2024  			"not match previous NFT entry; this should never happen (%v, %v).",
  2025  			txMeta, operationData.PrevNFTEntry)
  2026  	}
  2027  
  2028  	// Sanity check that the old NFT entry was not for sale.
  2029  	if operationData.PrevNFTEntry.IsForSale {
  2030  		return fmt.Errorf("_disconnectBurnNFT: prevNFTEntry was for sale (%v); this should"+
  2031  			" never happen.", operationData.PrevNFTEntry)
  2032  	}
  2033  
  2034  	// Get the postEntry for sanity checking / deletion later.
  2035  	currPostEntry := bav.GetPostEntryForPostHash(txMeta.NFTPostHash)
  2036  	if currPostEntry == nil || currPostEntry.isDeleted {
  2037  		return fmt.Errorf(
  2038  			"_disconnectBurnNFT: non-existent nftPostEntry for NFTPostHash: %s",
  2039  			txMeta.NFTPostHash.String())
  2040  	}
  2041  
  2042  	// Sanity check that the previous num NFT copies burned makes sense.
  2043  	if operationData.PrevPostEntry.NumNFTCopiesBurned != currPostEntry.NumNFTCopiesBurned-1 {
  2044  		return fmt.Errorf(
  2045  			"_disconnectBurnNFT: prevPostEntry has the wrong num NFT copies burned %d != %d-1",
  2046  			operationData.PrevPostEntry.NumNFTCopiesBurned, currPostEntry.NumNFTCopiesBurned)
  2047  	}
  2048  
  2049  	// Sanity check that there is no current NFT entry.
  2050  	nftKey := MakeNFTKey(txMeta.NFTPostHash, txMeta.SerialNumber)
  2051  	currNFTEntry := bav.GetNFTEntryForNFTKey(&nftKey)
  2052  	if currNFTEntry != nil && !currNFTEntry.isDeleted {
  2053  		return fmt.Errorf("_disconnectBurnNFT: found currNFTEntry for burned NFT: %s, %d",
  2054  			txMeta.NFTPostHash.String(), txMeta.SerialNumber)
  2055  	}
  2056  
  2057  	// Set the old NFT entry (no need to delete first since there is no current entry).
  2058  	bav._setNFTEntryMappings(operationData.PrevNFTEntry)
  2059  
  2060  	// Delete the current post entry and set the old one.
  2061  	bav._deletePostEntryMappings(currPostEntry)
  2062  	bav._setPostEntryMappings(operationData.PrevPostEntry)
  2063  
  2064  	// Now revert the basic transfer with the remaining operations.
  2065  	return bav._disconnectBasicTransfer(
  2066  		currentTxn, txnHash, utxoOpsForTxn[:operationIndex+1], blockHeight)
  2067  }