github.com/deso-protocol/core@v1.2.9/lib/block_view_follow.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  	"reflect"
     9  )
    10  
    11  func (bav *UtxoView) GetFollowEntryForFollowerPublicKeyCreatorPublicKey(followerPublicKey []byte, creatorPublicKey []byte) *FollowEntry {
    12  	followerPKID := bav.GetPKIDForPublicKey(followerPublicKey)
    13  	creatorPKID := bav.GetPKIDForPublicKey(creatorPublicKey)
    14  
    15  	if followerPKID == nil || creatorPKID == nil {
    16  		return nil
    17  	}
    18  
    19  	followKey := MakeFollowKey(followerPKID.PKID, creatorPKID.PKID)
    20  	return bav._getFollowEntryForFollowKey(&followKey)
    21  }
    22  
    23  func (bav *UtxoView) _getFollowEntryForFollowKey(followKey *FollowKey) *FollowEntry {
    24  	// If an entry exists in the in-memory map, return the value of that mapping.
    25  	mapValue, existsMapValue := bav.FollowKeyToFollowEntry[*followKey]
    26  	if existsMapValue {
    27  		return mapValue
    28  	}
    29  
    30  	// If we get here it means no value exists in our in-memory map. In this case,
    31  	// defer to the db. If a mapping exists in the db, return it. If not, return
    32  	// nil. Either way, save the value to the in-memory view mapping got later.
    33  	followExists := false
    34  	if bav.Postgres != nil {
    35  		followExists = bav.Postgres.GetFollow(&followKey.FollowerPKID, &followKey.FollowedPKID) != nil
    36  	} else {
    37  		followExists = DbGetFollowerToFollowedMapping(bav.Handle, &followKey.FollowerPKID, &followKey.FollowedPKID) != nil
    38  	}
    39  
    40  	if followExists {
    41  		followEntry := FollowEntry{
    42  			FollowerPKID: &followKey.FollowerPKID,
    43  			FollowedPKID: &followKey.FollowedPKID,
    44  		}
    45  		bav._setFollowEntryMappings(&followEntry)
    46  		return &followEntry
    47  	}
    48  
    49  	return nil
    50  }
    51  
    52  // Make sure that follows are loaded into the view before calling this
    53  func (bav *UtxoView) _followEntriesForPubKey(publicKey []byte, getEntriesFollowingPublicKey bool) (
    54  	_followEntries []*FollowEntry) {
    55  
    56  	// Return an empty list if no public key is provided
    57  	if len(publicKey) == 0 {
    58  		return []*FollowEntry{}
    59  	}
    60  
    61  	// Look up the PKID for the public key. This should always be set.
    62  	pkidForPublicKey := bav.GetPKIDForPublicKey(publicKey)
    63  	if pkidForPublicKey == nil || pkidForPublicKey.isDeleted {
    64  		glog.Errorf("PKID for public key %v was nil or deleted on the view; this "+
    65  			"should never happen", PkToString(publicKey, bav.Params))
    66  		return nil
    67  	}
    68  
    69  	// Now that the view mappings are a complete picture, iterate through them
    70  	// and set them on the map we're returning. Skip entries that don't match
    71  	// our public key or that are deleted. Note that only considering mappings
    72  	// where our public key is part of the key should ensure there are no
    73  	// duplicates in the resulting list.
    74  	followEntriesToReturn := []*FollowEntry{}
    75  	for viewFollowKey, viewFollowEntry := range bav.FollowKeyToFollowEntry {
    76  		if viewFollowEntry.isDeleted {
    77  			continue
    78  		}
    79  
    80  		var followKey FollowKey
    81  		if getEntriesFollowingPublicKey {
    82  			// publicKey is the followed public key
    83  			followKey = MakeFollowKey(viewFollowEntry.FollowerPKID, pkidForPublicKey.PKID)
    84  		} else {
    85  			// publicKey is the follower public key
    86  			followKey = MakeFollowKey(pkidForPublicKey.PKID, viewFollowEntry.FollowedPKID)
    87  		}
    88  
    89  		// Skip the follow entries that don't involve our publicKey
    90  		if viewFollowKey != followKey {
    91  			continue
    92  		}
    93  
    94  		// At this point we are confident the map key is equal to the message
    95  		// key containing the passed-in public key so add it to the mapping.
    96  		followEntriesToReturn = append(followEntriesToReturn, viewFollowEntry)
    97  	}
    98  
    99  	return followEntriesToReturn
   100  }
   101  
   102  // getEntriesFollowingPublicKey == true => Returns FollowEntries for people that follow publicKey
   103  // getEntriesFollowingPublicKey == false => Returns FollowEntries for people that publicKey follows
   104  func (bav *UtxoView) GetFollowEntriesForPublicKey(publicKey []byte, getEntriesFollowingPublicKey bool) (
   105  	_followEntries []*FollowEntry, _err error) {
   106  
   107  	// If the public key is not set then there are no FollowEntrys to return.
   108  	if len(publicKey) == 0 {
   109  		return []*FollowEntry{}, nil
   110  	}
   111  
   112  	// Look up the PKID for the public key. This should always be set.
   113  	pkidForPublicKey := bav.GetPKIDForPublicKey(publicKey)
   114  	if pkidForPublicKey == nil || pkidForPublicKey.isDeleted {
   115  		return nil, fmt.Errorf("GetFollowEntriesForPublicKey: PKID for public key %v was nil "+
   116  			"or deleted on the view; this should never happen",
   117  			PkToString(publicKey, bav.Params))
   118  	}
   119  
   120  	// Start by fetching all the follows we have in the db.
   121  	if bav.Postgres != nil {
   122  		var follows []*PGFollow
   123  		if getEntriesFollowingPublicKey {
   124  			follows = bav.Postgres.GetFollowers(pkidForPublicKey.PKID)
   125  		} else {
   126  			follows = bav.Postgres.GetFollowing(pkidForPublicKey.PKID)
   127  		}
   128  
   129  		for _, follow := range follows {
   130  			bav._setFollowEntryMappings(follow.NewFollowEntry())
   131  		}
   132  	} else {
   133  		var dbPKIDs []*PKID
   134  		var err error
   135  		if getEntriesFollowingPublicKey {
   136  			dbPKIDs, err = DbGetPKIDsFollowingYou(bav.Handle, pkidForPublicKey.PKID)
   137  		} else {
   138  			dbPKIDs, err = DbGetPKIDsYouFollow(bav.Handle, pkidForPublicKey.PKID)
   139  		}
   140  		if err != nil {
   141  			return nil, errors.Wrapf(err, "GetFollowsForUser: Problem fetching FollowEntrys from db: ")
   142  		}
   143  
   144  		// Iterate through the entries found in the db and force the view to load them.
   145  		// This fills in any gaps in the view so that, after this, the view should contain
   146  		// the union of what it had before plus what was in the db.
   147  		for _, dbPKID := range dbPKIDs {
   148  			var followKey FollowKey
   149  			if getEntriesFollowingPublicKey {
   150  				// publicKey is the followed public key
   151  				followKey = MakeFollowKey(dbPKID, pkidForPublicKey.PKID)
   152  			} else {
   153  				// publicKey is the follower public key
   154  				followKey = MakeFollowKey(pkidForPublicKey.PKID, dbPKID)
   155  			}
   156  
   157  			bav._getFollowEntryForFollowKey(&followKey)
   158  		}
   159  	}
   160  
   161  	followEntriesToReturn := bav._followEntriesForPubKey(publicKey, getEntriesFollowingPublicKey)
   162  
   163  	return followEntriesToReturn, nil
   164  }
   165  
   166  func (bav *UtxoView) _setFollowEntryMappings(followEntry *FollowEntry) {
   167  	// This function shouldn't be called with nil.
   168  	if followEntry == nil {
   169  		glog.Errorf("_setFollowEntryMappings: Called with nil FollowEntry; " +
   170  			"this should never happen.")
   171  		return
   172  	}
   173  
   174  	followerKey := MakeFollowKey(followEntry.FollowerPKID, followEntry.FollowedPKID)
   175  	bav.FollowKeyToFollowEntry[followerKey] = followEntry
   176  }
   177  
   178  func (bav *UtxoView) _deleteFollowEntryMappings(followEntry *FollowEntry) {
   179  
   180  	// Create a tombstone entry.
   181  	tombstoneFollowEntry := *followEntry
   182  	tombstoneFollowEntry.isDeleted = true
   183  
   184  	// Set the mappings to point to the tombstone entry.
   185  	bav._setFollowEntryMappings(&tombstoneFollowEntry)
   186  }
   187  
   188  func (bav *UtxoView) _connectFollow(
   189  	txn *MsgDeSoTxn, txHash *BlockHash, blockHeight uint32, verifySignatures bool) (
   190  	_totalInput uint64, _totalOutput uint64, _utxoOps []*UtxoOperation, _err error) {
   191  
   192  	// Check that the transaction has the right TxnType.
   193  	if txn.TxnMeta.GetTxnType() != TxnTypeFollow {
   194  		return 0, 0, nil, fmt.Errorf("_connectFollow: called with bad TxnType %s",
   195  			txn.TxnMeta.GetTxnType().String())
   196  	}
   197  	txMeta := txn.TxnMeta.(*FollowMetadata)
   198  
   199  	// Check that a proper public key is provided in the message metadata
   200  	if len(txMeta.FollowedPublicKey) != btcec.PubKeyBytesLenCompressed {
   201  		return 0, 0, nil, errors.Wrapf(
   202  			RuleErrorFollowPubKeyLen, "_connectFollow: "+
   203  				"FollowedPubKeyLen = %d; Expected length = %d",
   204  			len(txMeta.FollowedPublicKey), btcec.PubKeyBytesLenCompressed)
   205  	}
   206  
   207  	// TODO: This check feels unnecessary and is expensive
   208  	//_, err := btcec.ParsePubKey(txMeta.FollowedPublicKey, btcec.S256())
   209  	//if err != nil {
   210  	//	return 0, 0, nil, errors.Wrapf(
   211  	//		RuleErrorFollowParsePubKeyError, "_connectFollow: Parse error: %v", err)
   212  	//}
   213  
   214  	// Check that the profile to follow actually exists.
   215  	existingProfileEntry := bav.GetProfileEntryForPublicKey(txMeta.FollowedPublicKey)
   216  	if existingProfileEntry == nil || existingProfileEntry.isDeleted {
   217  		return 0, 0, nil, errors.Wrapf(
   218  			RuleErrorFollowingNonexistentProfile,
   219  			"_connectFollow: Profile pub key: %v",
   220  			PkToStringBoth(txMeta.FollowedPublicKey))
   221  	}
   222  
   223  	// Connect basic txn to get the total input and the total output without
   224  	// considering the transaction metadata.
   225  	totalInput, totalOutput, utxoOpsForTxn, err := bav._connectBasicTransfer(
   226  		txn, txHash, blockHeight, verifySignatures)
   227  	if err != nil {
   228  		return 0, 0, nil, errors.Wrapf(err, "_connectFollow: ")
   229  	}
   230  
   231  	if verifySignatures {
   232  		// _connectBasicTransfer has already checked that the transaction is
   233  		// signed by the top-level public key, which we take to be the sender's
   234  		// public key so there is no need to verify anything further.
   235  	}
   236  
   237  	// At this point the inputs and outputs have been processed. Now we
   238  	// need to handle the metadata.
   239  
   240  	// Get the PKIDs for the public keys associated with the follower and the followed.
   241  	followerPKID := bav.GetPKIDForPublicKey(txn.PublicKey)
   242  	if followerPKID == nil || followerPKID.isDeleted {
   243  		return 0, 0, nil, fmt.Errorf("_connectFollow: followerPKID was nil or deleted; this should never happen")
   244  	}
   245  	followedPKID := bav.GetPKIDForPublicKey(txMeta.FollowedPublicKey)
   246  	if followedPKID == nil || followerPKID.isDeleted {
   247  		return 0, 0, nil, fmt.Errorf("_connectFollow: followedPKID was nil or deleted; this should never happen")
   248  	}
   249  
   250  	// Here we consider existing followEntries.  It is handled differently in the follow
   251  	// vs. unfollow case so the code splits those cases out.
   252  	followKey := MakeFollowKey(followerPKID.PKID, followedPKID.PKID)
   253  	existingFollowEntry := bav._getFollowEntryForFollowKey(&followKey)
   254  	if txMeta.IsUnfollow {
   255  		// If this is an unfollow, a FollowEntry *should* exist.
   256  		if existingFollowEntry == nil || existingFollowEntry.isDeleted {
   257  			return 0, 0, nil, errors.Wrapf(
   258  				RuleErrorCannotUnfollowNonexistentFollowEntry,
   259  				"_connectFollow: Follow key: %v", &followKey)
   260  		}
   261  
   262  		// Now that we know that this is a valid unfollow entry, delete mapping.
   263  		bav._deleteFollowEntryMappings(existingFollowEntry)
   264  	} else {
   265  		if existingFollowEntry != nil && !existingFollowEntry.isDeleted {
   266  			// If this is a follow, a Follow entry *should not* exist.
   267  			return 0, 0, nil, errors.Wrapf(
   268  				RuleErrorFollowEntryAlreadyExists,
   269  				"_connectFollow: Follow key: %v", &followKey)
   270  		}
   271  
   272  		// Now that we know that this is a valid follow, update the mapping.
   273  		followEntry := &FollowEntry{
   274  			FollowerPKID: followerPKID.PKID,
   275  			FollowedPKID: followedPKID.PKID,
   276  		}
   277  		bav._setFollowEntryMappings(followEntry)
   278  	}
   279  
   280  	// Add an operation to the list at the end indicating we've added a follow.
   281  	utxoOpsForTxn = append(utxoOpsForTxn, &UtxoOperation{
   282  		Type: OperationTypeFollow,
   283  	})
   284  
   285  	return totalInput, totalOutput, utxoOpsForTxn, nil
   286  }
   287  
   288  func (bav *UtxoView) _disconnectFollow(
   289  	operationType OperationType, currentTxn *MsgDeSoTxn, txnHash *BlockHash,
   290  	utxoOpsForTxn []*UtxoOperation, blockHeight uint32) error {
   291  
   292  	// Verify that the last operation is a Follow operation
   293  	if len(utxoOpsForTxn) == 0 {
   294  		return fmt.Errorf("_disconnectFollow: utxoOperations are missing")
   295  	}
   296  	operationIndex := len(utxoOpsForTxn) - 1
   297  	if utxoOpsForTxn[operationIndex].Type != OperationTypeFollow {
   298  		return fmt.Errorf("_disconnectFollow: Trying to revert "+
   299  			"OperationTypeFollow but found type %v",
   300  			utxoOpsForTxn[operationIndex].Type)
   301  	}
   302  
   303  	// Now we know the txMeta is a Follow
   304  	txMeta := currentTxn.TxnMeta.(*FollowMetadata)
   305  
   306  	// Look up the PKIDs for the follower and the followed.
   307  	// Get the PKIDs for the public keys associated with the follower and the followed.
   308  	followerPKID := bav.GetPKIDForPublicKey(currentTxn.PublicKey)
   309  	if followerPKID == nil || followerPKID.isDeleted {
   310  		return fmt.Errorf("_disconnectFollow: followerPKID was nil or deleted; this should never happen")
   311  	}
   312  	followedPKID := bav.GetPKIDForPublicKey(txMeta.FollowedPublicKey)
   313  	if followedPKID == nil || followerPKID.isDeleted {
   314  		return fmt.Errorf("_disconnectFollow: followedPKID was nil or deleted; this should never happen")
   315  	}
   316  
   317  	// If the transaction is an unfollow, it removed the follow entry from the DB
   318  	// so we have to add it back.  Then we can finish by reverting the basic transfer.
   319  	if txMeta.IsUnfollow {
   320  		followEntry := FollowEntry{
   321  			FollowerPKID: followerPKID.PKID,
   322  			FollowedPKID: followedPKID.PKID,
   323  		}
   324  		bav._setFollowEntryMappings(&followEntry)
   325  		return bav._disconnectBasicTransfer(
   326  			currentTxn, txnHash, utxoOpsForTxn[:operationIndex], blockHeight)
   327  	}
   328  
   329  	// Get the FollowEntry. If we don't find it or idDeleted=true, that's an error.
   330  	followKey := MakeFollowKey(followerPKID.PKID, followedPKID.PKID)
   331  	followEntry := bav._getFollowEntryForFollowKey(&followKey)
   332  	if followEntry == nil || followEntry.isDeleted {
   333  		return fmt.Errorf("_disconnectFollow: FollowEntry for "+
   334  			"followKey %v was found to be nil or isDeleted not set appropriately: %v",
   335  			&followKey, followEntry)
   336  	}
   337  
   338  	// Verify that the sender and recipient in the entry match the TxnMeta as
   339  	// a sanity check.
   340  	if !reflect.DeepEqual(followEntry.FollowerPKID, followerPKID.PKID) {
   341  		return fmt.Errorf("_disconnectFollow: Follower PKID on "+
   342  			"FollowEntry was %s but the PKID looked up from the txn was %s",
   343  			PkToString(followEntry.FollowerPKID[:], bav.Params),
   344  			PkToString(followerPKID.PKID[:], bav.Params))
   345  	}
   346  	if !reflect.DeepEqual(followEntry.FollowedPKID, followedPKID.PKID) {
   347  		return fmt.Errorf("_disconnectFollow: Followed PKID on "+
   348  			"FollowEntry was %s but the FollowedPKID looked up from the txn was %s",
   349  			PkToString(followEntry.FollowedPKID[:], bav.Params),
   350  			PkToString(followedPKID.PKID[:], bav.Params))
   351  	}
   352  
   353  	// Now that we are confident the FollowEntry lines up with the transaction we're
   354  	// rolling back, delete the mappings.
   355  	bav._deleteFollowEntryMappings(followEntry)
   356  
   357  	// Now revert the basic transfer with the remaining operations. Cut off
   358  	// the FollowMessage operation at the end since we just reverted it.
   359  	return bav._disconnectBasicTransfer(
   360  		currentTxn, txnHash, utxoOpsForTxn[:operationIndex], blockHeight)
   361  }