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 }