github.com/deso-protocol/core@v1.2.9/lib/block_view.go (about) 1 package lib 2 3 import ( 4 "fmt" 5 "math" 6 "reflect" 7 "strings" 8 "time" 9 10 "github.com/btcsuite/btcd/btcec" 11 "github.com/dgraph-io/badger/v3" 12 "github.com/golang/glog" 13 "github.com/pkg/errors" 14 ) 15 16 // block_view.go is the main work-horse for validating transactions in blocks. 17 // It generally works by creating an "in-memory view" of the current tip and 18 // then applying a transaction's operations to the view to see if those operations 19 // are allowed and consistent with the blockchain's current state. Generally, 20 // every transaction we define has a corresponding connect() and disconnect() 21 // function defined here that specifies what operations that transaction applies 22 // to the view and ultimately to the database. If you want to know how any 23 // particular transaction impacts the database, you've found the right file. A 24 // good place to start in this file is ConnectTransaction and DisconnectTransaction. 25 // ConnectBlock is also good. 26 27 type UtxoView struct { 28 // Utxo data 29 NumUtxoEntries uint64 30 UtxoKeyToUtxoEntry map[UtxoKey]*UtxoEntry 31 PublicKeyToDeSoBalanceNanos map[PublicKey]uint64 32 33 // BitcoinExchange data 34 NanosPurchased uint64 35 USDCentsPerBitcoin uint64 36 GlobalParamsEntry *GlobalParamsEntry 37 BitcoinBurnTxIDs map[BlockHash]bool 38 39 // Forbidden block signature pubkeys 40 ForbiddenPubKeyToForbiddenPubKeyEntry map[PkMapKey]*ForbiddenPubKeyEntry 41 42 // Messages data 43 MessageKeyToMessageEntry map[MessageKey]*MessageEntry 44 45 // Postgres stores message data slightly differently 46 MessageMap map[BlockHash]*PGMessage 47 48 // Follow data 49 FollowKeyToFollowEntry map[FollowKey]*FollowEntry 50 51 // NFT data 52 NFTKeyToNFTEntry map[NFTKey]*NFTEntry 53 NFTBidKeyToNFTBidEntry map[NFTBidKey]*NFTBidEntry 54 NFTKeyToAcceptedNFTBidHistory map[NFTKey]*[]*NFTBidEntry 55 56 // Diamond data 57 DiamondKeyToDiamondEntry map[DiamondKey]*DiamondEntry 58 59 // Like data 60 LikeKeyToLikeEntry map[LikeKey]*LikeEntry 61 62 // Repost data 63 RepostKeyToRepostEntry map[RepostKey]*RepostEntry 64 65 // Post data 66 PostHashToPostEntry map[BlockHash]*PostEntry 67 68 // Profile data 69 PublicKeyToPKIDEntry map[PkMapKey]*PKIDEntry 70 // The PKIDEntry is only used here to store the public key. 71 PKIDToPublicKey map[PKID]*PKIDEntry 72 ProfilePKIDToProfileEntry map[PKID]*ProfileEntry 73 ProfileUsernameToProfileEntry map[UsernameMapKey]*ProfileEntry 74 75 // Coin balance entries 76 HODLerPKIDCreatorPKIDToBalanceEntry map[BalanceEntryMapKey]*BalanceEntry 77 78 // Derived Key entries. Map key is a combination of owner and derived public keys. 79 DerivedKeyToDerivedEntry map[DerivedKeyMapKey]*DerivedKeyEntry 80 81 // The hash of the tip the view is currently referencing. Mainly used 82 // for error-checking when doing a bulk operation on the view. 83 TipHash *BlockHash 84 85 Handle *badger.DB 86 Postgres *Postgres 87 Params *DeSoParams 88 } 89 90 // Assumes the db Handle is already set on the view, but otherwise the 91 // initialization is full. 92 func (bav *UtxoView) _ResetViewMappingsAfterFlush() { 93 // Utxo data 94 bav.UtxoKeyToUtxoEntry = make(map[UtxoKey]*UtxoEntry) 95 // TODO: Deprecate this value 96 bav.NumUtxoEntries = GetUtxoNumEntries(bav.Handle) 97 bav.PublicKeyToDeSoBalanceNanos = make(map[PublicKey]uint64) 98 99 // BitcoinExchange data 100 bav.NanosPurchased = DbGetNanosPurchased(bav.Handle) 101 bav.USDCentsPerBitcoin = DbGetUSDCentsPerBitcoinExchangeRate(bav.Handle) 102 bav.GlobalParamsEntry = DbGetGlobalParamsEntry(bav.Handle) 103 bav.BitcoinBurnTxIDs = make(map[BlockHash]bool) 104 105 // Forbidden block signature pub key info. 106 bav.ForbiddenPubKeyToForbiddenPubKeyEntry = make(map[PkMapKey]*ForbiddenPubKeyEntry) 107 108 // Post and profile data 109 bav.PostHashToPostEntry = make(map[BlockHash]*PostEntry) 110 bav.PublicKeyToPKIDEntry = make(map[PkMapKey]*PKIDEntry) 111 bav.PKIDToPublicKey = make(map[PKID]*PKIDEntry) 112 bav.ProfilePKIDToProfileEntry = make(map[PKID]*ProfileEntry) 113 bav.ProfileUsernameToProfileEntry = make(map[UsernameMapKey]*ProfileEntry) 114 115 // Messages data 116 bav.MessageKeyToMessageEntry = make(map[MessageKey]*MessageEntry) 117 bav.MessageMap = make(map[BlockHash]*PGMessage) 118 119 // Follow data 120 bav.FollowKeyToFollowEntry = make(map[FollowKey]*FollowEntry) 121 122 // NFT data 123 bav.NFTKeyToNFTEntry = make(map[NFTKey]*NFTEntry) 124 bav.NFTBidKeyToNFTBidEntry = make(map[NFTBidKey]*NFTBidEntry) 125 bav.NFTKeyToAcceptedNFTBidHistory = make(map[NFTKey]*[]*NFTBidEntry) 126 127 // Diamond data 128 bav.DiamondKeyToDiamondEntry = make(map[DiamondKey]*DiamondEntry) 129 130 // Like data 131 bav.LikeKeyToLikeEntry = make(map[LikeKey]*LikeEntry) 132 133 // Repost data 134 bav.RepostKeyToRepostEntry = make(map[RepostKey]*RepostEntry) 135 136 // Coin balance entries 137 bav.HODLerPKIDCreatorPKIDToBalanceEntry = make(map[BalanceEntryMapKey]*BalanceEntry) 138 139 // Derived Key entries 140 bav.DerivedKeyToDerivedEntry = make(map[DerivedKeyMapKey]*DerivedKeyEntry) 141 } 142 143 func (bav *UtxoView) CopyUtxoView() (*UtxoView, error) { 144 newView, err := NewUtxoView(bav.Handle, bav.Params, bav.Postgres) 145 if err != nil { 146 return nil, err 147 } 148 149 // Copy the UtxoEntry data 150 // Note that using _setUtxoMappings is dangerous because the Pos within 151 // the UtxoEntrys is off. 152 newView.UtxoKeyToUtxoEntry = make(map[UtxoKey]*UtxoEntry, len(bav.UtxoKeyToUtxoEntry)) 153 for utxoKey, utxoEntry := range bav.UtxoKeyToUtxoEntry { 154 newUtxoEntry := *utxoEntry 155 newView.UtxoKeyToUtxoEntry[utxoKey] = &newUtxoEntry 156 } 157 newView.NumUtxoEntries = bav.NumUtxoEntries 158 159 // Copy the public key to balance data 160 newView.PublicKeyToDeSoBalanceNanos = make(map[PublicKey]uint64, len(bav.PublicKeyToDeSoBalanceNanos)) 161 for pkMapKey, desoBalance := range bav.PublicKeyToDeSoBalanceNanos { 162 newView.PublicKeyToDeSoBalanceNanos[pkMapKey] = desoBalance 163 } 164 165 // Copy the BitcoinExchange data 166 newView.BitcoinBurnTxIDs = make(map[BlockHash]bool, len(bav.BitcoinBurnTxIDs)) 167 for bh := range bav.BitcoinBurnTxIDs { 168 newView.BitcoinBurnTxIDs[bh] = true 169 } 170 newView.NanosPurchased = bav.NanosPurchased 171 newView.USDCentsPerBitcoin = bav.USDCentsPerBitcoin 172 173 // Copy the GlobalParamsEntry 174 newGlobalParamsEntry := *bav.GlobalParamsEntry 175 newView.GlobalParamsEntry = &newGlobalParamsEntry 176 177 // Copy the post data 178 newView.PostHashToPostEntry = make(map[BlockHash]*PostEntry, len(bav.PostHashToPostEntry)) 179 for postHash, postEntry := range bav.PostHashToPostEntry { 180 if postEntry == nil { 181 continue 182 } 183 184 newPostEntry := *postEntry 185 newView.PostHashToPostEntry[postHash] = &newPostEntry 186 } 187 188 // Copy the PKID data 189 newView.PublicKeyToPKIDEntry = make(map[PkMapKey]*PKIDEntry, len(bav.PublicKeyToPKIDEntry)) 190 for pkMapKey, pkid := range bav.PublicKeyToPKIDEntry { 191 newPKID := *pkid 192 newView.PublicKeyToPKIDEntry[pkMapKey] = &newPKID 193 } 194 195 newView.PKIDToPublicKey = make(map[PKID]*PKIDEntry, len(bav.PKIDToPublicKey)) 196 for pkid, pkidEntry := range bav.PKIDToPublicKey { 197 newPKIDEntry := *pkidEntry 198 newView.PKIDToPublicKey[pkid] = &newPKIDEntry 199 } 200 201 // Copy the profile data 202 newView.ProfilePKIDToProfileEntry = make(map[PKID]*ProfileEntry, len(bav.ProfilePKIDToProfileEntry)) 203 for profilePKID, profileEntry := range bav.ProfilePKIDToProfileEntry { 204 if profileEntry == nil { 205 continue 206 } 207 208 newProfileEntry := *profileEntry 209 newView.ProfilePKIDToProfileEntry[profilePKID] = &newProfileEntry 210 } 211 newView.ProfileUsernameToProfileEntry = make(map[UsernameMapKey]*ProfileEntry, len(bav.ProfileUsernameToProfileEntry)) 212 for profilePKID, profileEntry := range bav.ProfileUsernameToProfileEntry { 213 if profileEntry == nil { 214 continue 215 } 216 217 newProfileEntry := *profileEntry 218 newView.ProfileUsernameToProfileEntry[profilePKID] = &newProfileEntry 219 } 220 221 // Copy the message data 222 newView.MessageKeyToMessageEntry = make(map[MessageKey]*MessageEntry, len(bav.MessageKeyToMessageEntry)) 223 for msgKey, msgEntry := range bav.MessageKeyToMessageEntry { 224 newMsgEntry := *msgEntry 225 newView.MessageKeyToMessageEntry[msgKey] = &newMsgEntry 226 } 227 228 newView.MessageMap = make(map[BlockHash]*PGMessage, len(bav.MessageMap)) 229 for txnHash, message := range bav.MessageMap { 230 newMessage := *message 231 newView.MessageMap[txnHash] = &newMessage 232 } 233 234 // Copy the follow data 235 newView.FollowKeyToFollowEntry = make(map[FollowKey]*FollowEntry, len(bav.FollowKeyToFollowEntry)) 236 for followKey, followEntry := range bav.FollowKeyToFollowEntry { 237 if followEntry == nil { 238 continue 239 } 240 241 newFollowEntry := *followEntry 242 newView.FollowKeyToFollowEntry[followKey] = &newFollowEntry 243 } 244 245 // Copy the like data 246 newView.LikeKeyToLikeEntry = make(map[LikeKey]*LikeEntry, len(bav.LikeKeyToLikeEntry)) 247 for likeKey, likeEntry := range bav.LikeKeyToLikeEntry { 248 if likeEntry == nil { 249 continue 250 } 251 252 newLikeEntry := *likeEntry 253 newView.LikeKeyToLikeEntry[likeKey] = &newLikeEntry 254 } 255 256 // Copy the repost data 257 newView.RepostKeyToRepostEntry = make(map[RepostKey]*RepostEntry, len(bav.RepostKeyToRepostEntry)) 258 for repostKey, repostEntry := range bav.RepostKeyToRepostEntry { 259 newRepostEntry := *repostEntry 260 newView.RepostKeyToRepostEntry[repostKey] = &newRepostEntry 261 } 262 263 // Copy the balance entry data 264 newView.HODLerPKIDCreatorPKIDToBalanceEntry = make( 265 map[BalanceEntryMapKey]*BalanceEntry, len(bav.HODLerPKIDCreatorPKIDToBalanceEntry)) 266 for balanceEntryMapKey, balanceEntry := range bav.HODLerPKIDCreatorPKIDToBalanceEntry { 267 if balanceEntry == nil { 268 continue 269 } 270 271 newBalanceEntry := *balanceEntry 272 newView.HODLerPKIDCreatorPKIDToBalanceEntry[balanceEntryMapKey] = &newBalanceEntry 273 } 274 275 // Copy the Diamond data 276 newView.DiamondKeyToDiamondEntry = make( 277 map[DiamondKey]*DiamondEntry, len(bav.DiamondKeyToDiamondEntry)) 278 for diamondKey, diamondEntry := range bav.DiamondKeyToDiamondEntry { 279 newDiamondEntry := *diamondEntry 280 newView.DiamondKeyToDiamondEntry[diamondKey] = &newDiamondEntry 281 } 282 283 // Copy the NFT data 284 newView.NFTKeyToNFTEntry = make(map[NFTKey]*NFTEntry, len(bav.NFTKeyToNFTEntry)) 285 for nftKey, nftEntry := range bav.NFTKeyToNFTEntry { 286 newNFTEntry := *nftEntry 287 newView.NFTKeyToNFTEntry[nftKey] = &newNFTEntry 288 } 289 290 newView.NFTBidKeyToNFTBidEntry = make(map[NFTBidKey]*NFTBidEntry, len(bav.NFTBidKeyToNFTBidEntry)) 291 for nftBidKey, nftBidEntry := range bav.NFTBidKeyToNFTBidEntry { 292 newNFTBidEntry := *nftBidEntry 293 newView.NFTBidKeyToNFTBidEntry[nftBidKey] = &newNFTBidEntry 294 } 295 296 newView.NFTKeyToAcceptedNFTBidHistory = make(map[NFTKey]*[]*NFTBidEntry, len(bav.NFTKeyToAcceptedNFTBidHistory)) 297 for nftKey, nftBidEntries := range bav.NFTKeyToAcceptedNFTBidHistory { 298 newNFTBidEntries := *nftBidEntries 299 newView.NFTKeyToAcceptedNFTBidHistory[nftKey] = &newNFTBidEntries 300 } 301 302 // Copy the Derived Key data 303 newView.DerivedKeyToDerivedEntry = make(map[DerivedKeyMapKey]*DerivedKeyEntry, len(bav.DerivedKeyToDerivedEntry)) 304 for entryKey, entry := range bav.DerivedKeyToDerivedEntry { 305 newEntry := *entry 306 newView.DerivedKeyToDerivedEntry[entryKey] = &newEntry 307 } 308 309 return newView, nil 310 } 311 312 func NewUtxoView( 313 _handle *badger.DB, 314 _params *DeSoParams, 315 _postgres *Postgres, 316 ) (*UtxoView, error) { 317 318 view := UtxoView{ 319 Handle: _handle, 320 Params: _params, 321 // Note that the TipHash does not get reset as part of 322 // _ResetViewMappingsAfterFlush because it is not something that is affected by a 323 // flush operation. Moreover, its value is consistent with the view regardless of 324 // whether or not the view is flushed or not. Additionally the utxo view does 325 // not concern itself with the header chain (see comment on GetBestHash for more 326 // info on that). 327 TipHash: DbGetBestHash(_handle, ChainTypeDeSoBlock /* don't get the header chain */), 328 329 Postgres: _postgres, 330 // Set everything else in _ResetViewMappings() 331 } 332 333 // Note that the TipHash does not get reset as part of 334 // _ResetViewMappingsAfterFlush because it is not something that is affected by a 335 // flush operation. Moreover, its value is consistent with the view regardless of 336 // whether or not the view is flushed or not. Additionally the utxo view does 337 // not concern itself with the header chain (see comment on GetBestHash for more 338 // info on that). 339 if view.Postgres != nil { 340 view.TipHash = view.Postgres.GetChain(MAIN_CHAIN).TipHash 341 } else { 342 view.TipHash = DbGetBestHash(view.Handle, ChainTypeDeSoBlock /* don't get the header chain */) 343 } 344 345 // This function is generally used to reset the view after a flush has been performed 346 // but we can use it here to initialize the mappings. 347 view._ResetViewMappingsAfterFlush() 348 349 return &view, nil 350 } 351 352 func (bav *UtxoView) _deleteUtxoMappings(utxoEntry *UtxoEntry) error { 353 if utxoEntry.UtxoKey == nil { 354 return fmt.Errorf("_deleteUtxoMappings: utxoKey missing for utxoEntry %+v", utxoEntry) 355 } 356 357 // Deleting a utxo amounts to setting its mappings to point to an 358 // entry that has (isSpent = true). So we create such an entry and set 359 // the mappings to point to it. 360 tombstoneEntry := *utxoEntry 361 tombstoneEntry.isSpent = true 362 363 // _setUtxoMappings will take this and use its fields to update the 364 // mappings. 365 // TODO: We're doing a double-copy here at the moment. We should make this more 366 // efficient. 367 return bav._setUtxoMappings(&tombstoneEntry) 368 369 // Note at this point, the utxoEntry passed in is dangling and can 370 // be re-used for another purpose if desired. 371 } 372 373 func (bav *UtxoView) _setUtxoMappings(utxoEntry *UtxoEntry) error { 374 if utxoEntry.UtxoKey == nil { 375 return fmt.Errorf("_setUtxoMappings: utxoKey missing for utxoEntry %+v", utxoEntry) 376 } 377 bav.UtxoKeyToUtxoEntry[*utxoEntry.UtxoKey] = utxoEntry 378 379 return nil 380 } 381 382 func (bav *UtxoView) GetUtxoEntryForUtxoKey(utxoKey *UtxoKey) *UtxoEntry { 383 utxoEntry, ok := bav.UtxoKeyToUtxoEntry[*utxoKey] 384 // If the utxo entry isn't in our in-memory data structure, fetch it from the 385 // db. 386 if !ok { 387 if bav.Postgres != nil { 388 utxoEntry = bav.Postgres.GetUtxoEntryForUtxoKey(utxoKey) 389 } else { 390 utxoEntry = DbGetUtxoEntryForUtxoKey(bav.Handle, utxoKey) 391 } 392 if utxoEntry == nil { 393 // This means the utxo is neither in our map nor in the db so 394 // it doesn't exist. Return nil to signal that in this case. 395 return nil 396 } 397 398 // At this point we have the utxo entry so load it 399 // into our in-memory data structure for future reference. Note that 400 // isSpent should be false by default. Also note that a back-reference 401 // to the utxoKey should be set on the utxoEntry by this function. 402 utxoEntry.UtxoKey = utxoKey 403 if err := bav._setUtxoMappings(utxoEntry); err != nil { 404 glog.Errorf("GetUtxoEntryForUtxoKey: Problem encountered setting utxo mapping %v", err) 405 return nil 406 } 407 } 408 409 return utxoEntry 410 } 411 412 func (bav *UtxoView) GetDeSoBalanceNanosForPublicKey(publicKey []byte) (uint64, error) { 413 balanceNanos, hasBalance := bav.PublicKeyToDeSoBalanceNanos[*NewPublicKey(publicKey)] 414 if hasBalance { 415 return balanceNanos, nil 416 } 417 418 // If the utxo entry isn't in our in-memory data structure, fetch it from the db. 419 if bav.Postgres != nil { 420 balanceNanos = bav.Postgres.GetBalance(NewPublicKey(publicKey)) 421 } else { 422 var err error 423 balanceNanos, err = DbGetDeSoBalanceNanosForPublicKey(bav.Handle, publicKey) 424 if err != nil { 425 return uint64(0), errors.Wrap(err, "GetDeSoBalanceNanosForPublicKey: ") 426 } 427 } 428 429 // Add the balance to memory for future references. 430 bav.PublicKeyToDeSoBalanceNanos[*NewPublicKey(publicKey)] = balanceNanos 431 432 return balanceNanos, nil 433 } 434 435 func (bav *UtxoView) _unSpendUtxo(utxoEntryy *UtxoEntry) error { 436 // Operate on a copy of the entry in order to avoid bugs. Note that not 437 // doing this could result in us maintaining a reference to the entry and 438 // modifying it on subsequent calls to this function, which is bad. 439 utxoEntryCopy := *utxoEntryy 440 441 // If the utxoKey back-reference on the entry isn't set return an error. 442 if utxoEntryCopy.UtxoKey == nil { 443 return fmt.Errorf("_unSpendUtxo: utxoEntry must have utxoKey set") 444 } 445 // Make sure isSpent is set to false. It should be false by default if we 446 // read this entry from the db but set it in case the caller derived the 447 // entry via a different method. 448 utxoEntryCopy.isSpent = false 449 450 // Not setting this to a copy could cause issues down the road where we modify 451 // the utxo passed-in on subsequent calls. 452 if err := bav._setUtxoMappings(&utxoEntryCopy); err != nil { 453 return err 454 } 455 456 // Since we re-added the utxo, bump the number of entries. 457 bav.NumUtxoEntries++ 458 459 // Add the utxo back to the spender's balance. 460 desoBalanceNanos, err := bav.GetDeSoBalanceNanosForPublicKey(utxoEntryy.PublicKey) 461 if err != nil { 462 return errors.Wrap(err, "_unSpendUtxo: ") 463 } 464 desoBalanceNanos += utxoEntryy.AmountNanos 465 bav.PublicKeyToDeSoBalanceNanos[*NewPublicKey(utxoEntryy.PublicKey)] = desoBalanceNanos 466 467 return nil 468 } 469 470 func (bav *UtxoView) _spendUtxo(utxoKey *UtxoKey) (*UtxoOperation, error) { 471 // Swap this utxo's position with the utxo in the last position and delete it. 472 473 // Get the entry for this utxo from the view if it's cached, 474 // otherwise try and get it from the db. 475 utxoEntry := bav.GetUtxoEntryForUtxoKey(utxoKey) 476 if utxoEntry == nil { 477 return nil, fmt.Errorf("_spendUtxo: Attempting to spend non-existent UTXO") 478 } 479 if utxoEntry.isSpent { 480 return nil, fmt.Errorf("_spendUtxo: Attempting to spend an already-spent UTXO") 481 } 482 483 // Delete the entry by removing its mappings from our in-memory data 484 // structures. 485 if err := bav._deleteUtxoMappings(utxoEntry); err != nil { 486 return nil, errors.Wrapf(err, "_spendUtxo: ") 487 } 488 489 // Decrement the number of entries by one since we marked one as spent in the 490 // view. 491 bav.NumUtxoEntries-- 492 493 // Deduct the utxo from the spender's balance. 494 desoBalanceNanos, err := bav.GetDeSoBalanceNanosForPublicKey(utxoEntry.PublicKey) 495 if err != nil { 496 return nil, errors.Wrapf(err, "_spendUtxo: ") 497 } 498 desoBalanceNanos -= utxoEntry.AmountNanos 499 bav.PublicKeyToDeSoBalanceNanos[*NewPublicKey(utxoEntry.PublicKey)] = desoBalanceNanos 500 501 // Record a UtxoOperation in case we want to roll this back in the 502 // future. At this point, the UtxoEntry passed in still has all of its 503 // fields set to what they were right before SPEND was called. This is 504 // exactly what we want (see comment on OperationTypeSpendUtxo for more info). 505 // Make a copy of the entry to avoid issues where we accidentally modify 506 // the entry in the future. 507 utxoEntryCopy := *utxoEntry 508 return &UtxoOperation{ 509 Type: OperationTypeSpendUtxo, 510 Key: utxoKey, 511 Entry: &utxoEntryCopy, 512 }, nil 513 } 514 515 func (bav *UtxoView) _unAddUtxo(utxoKey *UtxoKey) error { 516 // Get the entry for this utxo from the view if it's cached, 517 // otherwise try and get it from the db. 518 utxoEntry := bav.GetUtxoEntryForUtxoKey(utxoKey) 519 if utxoEntry == nil { 520 return fmt.Errorf("_unAddUtxo: Attempting to remove non-existent UTXO") 521 } 522 if utxoEntry.isSpent { 523 return fmt.Errorf("_unAddUtxo: Attempting to remove an already-spent UTXO") 524 } 525 526 // At this point we should have the entry sanity-checked. To remove 527 // it from our data structure, it is sufficient to replace it with an 528 // entry that is marked as spent. When the view is eventually flushed 529 // to the database the output's status as spent will translate to it 530 // getting deleted, which is what we want. 531 if err := bav._deleteUtxoMappings(utxoEntry); err != nil { 532 return err 533 } 534 535 // In addition to marking the output as spent, we update the number of 536 // entries to reflect the output is no longer in our utxo list. 537 bav.NumUtxoEntries-- 538 539 // Remove the utxo back from the spender's balance. 540 desoBalanceNanos, err := bav.GetDeSoBalanceNanosForPublicKey(utxoEntry.PublicKey) 541 if err != nil { 542 return errors.Wrapf(err, "_unAddUtxo: ") 543 } 544 desoBalanceNanos -= utxoEntry.AmountNanos 545 bav.PublicKeyToDeSoBalanceNanos[*NewPublicKey(utxoEntry.PublicKey)] = desoBalanceNanos 546 547 return nil 548 } 549 550 // Note: We assume that the person passing in the utxo key and the utxo entry 551 // aren't going to modify them after. 552 func (bav *UtxoView) _addUtxo(utxoEntryy *UtxoEntry) (*UtxoOperation, error) { 553 // Use a copy of the utxo passed in so we avoid keeping a reference to it 554 // which could be modified in subsequent calls. 555 utxoEntryCopy := *utxoEntryy 556 557 // If the utxoKey back-reference on the entry isn't set then error. 558 if utxoEntryCopy.UtxoKey == nil { 559 return nil, fmt.Errorf("_addUtxo: utxoEntry must have utxoKey set") 560 } 561 // If the UtxoEntry passed in has isSpent set then error. The caller should only 562 // pass in entries that are unspent. 563 if utxoEntryCopy.isSpent { 564 return nil, fmt.Errorf("_addUtxo: UtxoEntry being added has isSpent = true") 565 } 566 567 // Put the utxo at the end and update our in-memory data structures with 568 // this change. 569 // 570 // Note this may over-write an existing entry but this is OK for a very subtle 571 // reason. When we roll back a transaction, e.g. due to a 572 // reorg, we mark the outputs of that transaction as "spent" but we don't delete them 573 // from our view because doing so would cause us to neglect to actually delete them 574 // when we flush the view to the db. What this means is that if we roll back a transaction 575 // in a block but then add it later in a different block, that second add could 576 // over-write the entry that is currently has isSpent=true with a similar (though 577 // not identical because the block height may differ) entry that has isSpent=false. 578 // This is OK however because the new entry we're over-writing the old entry with 579 // has the same key and so flushing the view to the database will result in the 580 // deletion of the old entry as intended when the new entry over-writes it. Put 581 // simply, the over-write that could happen here is an over-write we also want to 582 // happen when we flush and so it should be OK. 583 if err := bav._setUtxoMappings(&utxoEntryCopy); err != nil { 584 return nil, errors.Wrapf(err, "_addUtxo: ") 585 } 586 587 // Bump the number of entries since we just added this one at the end. 588 bav.NumUtxoEntries++ 589 590 // Add the utxo back to the spender's balance. 591 desoBalanceNanos, err := bav.GetDeSoBalanceNanosForPublicKey(utxoEntryy.PublicKey) 592 if err != nil { 593 return nil, errors.Wrapf(err, "_addUtxo: ") 594 } 595 desoBalanceNanos += utxoEntryy.AmountNanos 596 bav.PublicKeyToDeSoBalanceNanos[*NewPublicKey(utxoEntryy.PublicKey)] = desoBalanceNanos 597 598 // Finally record a UtxoOperation in case we want to roll back this ADD 599 // in the future. Note that Entry data isn't required for an ADD operation. 600 return &UtxoOperation{ 601 Type: OperationTypeAddUtxo, 602 // We don't technically need these in order to be able to roll back the 603 // transaction but they're useful for callers of connectTransaction to 604 // determine implicit outputs that were created like those that get created 605 // in a Bitcoin burn transaction. 606 Key: utxoEntryCopy.UtxoKey, 607 Entry: &utxoEntryCopy, 608 }, nil 609 } 610 611 func (bav *UtxoView) _disconnectBasicTransfer(currentTxn *MsgDeSoTxn, txnHash *BlockHash, utxoOpsForTxn []*UtxoOperation, blockHeight uint32) error { 612 // First we check to see if the last utxoOp was a diamond operation. If it was, we disconnect 613 // the diamond-related changes and decrement the operation index to move past it. 614 operationIndex := len(utxoOpsForTxn) - 1 615 if len(utxoOpsForTxn) > 0 && utxoOpsForTxn[operationIndex].Type == OperationTypeDeSoDiamond { 616 currentOperation := utxoOpsForTxn[operationIndex] 617 618 diamondPostHashBytes, hasDiamondPostHash := currentTxn.ExtraData[DiamondPostHashKey] 619 if !hasDiamondPostHash { 620 return fmt.Errorf("_disconnectBasicTransfer: Found diamond op without diamondPostHash") 621 } 622 623 // Sanity check the post hash bytes before creating the post hash. 624 diamondPostHash := &BlockHash{} 625 if len(diamondPostHashBytes) != HashSizeBytes { 626 return fmt.Errorf( 627 "_disconnectBasicTransfer: DiamondPostHashBytes has incorrect length: %d", 628 len(diamondPostHashBytes)) 629 } 630 copy(diamondPostHash[:], diamondPostHashBytes[:]) 631 632 // Get the diamonded post entry and make sure it exists. 633 diamondedPostEntry := bav.GetPostEntryForPostHash(diamondPostHash) 634 if diamondedPostEntry == nil || diamondedPostEntry.isDeleted { 635 return fmt.Errorf( 636 "_disconnectBasicTransfer: Could not find diamonded post entry: %s", 637 diamondPostHash.String()) 638 } 639 640 // Get the existing diamondEntry so we can delete it. 641 senderPKID := bav.GetPKIDForPublicKey(currentTxn.PublicKey) 642 receiverPKID := bav.GetPKIDForPublicKey(diamondedPostEntry.PosterPublicKey) 643 diamondKey := MakeDiamondKey(senderPKID.PKID, receiverPKID.PKID, diamondPostHash) 644 diamondEntry := bav.GetDiamondEntryForDiamondKey(&diamondKey) 645 646 // Sanity check that the diamondEntry is not nil. 647 if diamondEntry == nil { 648 return fmt.Errorf( 649 "_disconnectBasicTransfer: Found nil diamond entry for diamondKey: %v", &diamondKey) 650 } 651 652 // Delete the diamond entry mapping and re-add it if the previous mapping is not nil. 653 bav._deleteDiamondEntryMappings(diamondEntry) 654 if currentOperation.PrevDiamondEntry != nil { 655 bav._setDiamondEntryMappings(currentOperation.PrevDiamondEntry) 656 } 657 658 // Finally, revert the post entry mapping since we likely updated the DiamondCount. 659 bav._setPostEntryMappings(currentOperation.PrevPostEntry) 660 661 operationIndex-- 662 } 663 664 // Loop through the transaction's outputs backwards and remove them 665 // from the view. Since the outputs will have been added to the view 666 // at the end of the utxo list, removing them from the view amounts to 667 // removing the last element from the utxo list. 668 // 669 // Loop backwards over the utxo operations as we go along. 670 for outputIndex := len(currentTxn.TxOutputs) - 1; outputIndex >= 0; outputIndex-- { 671 currentOutput := currentTxn.TxOutputs[outputIndex] 672 673 // Compute the utxo key for this output so we can reference it in our 674 // data structures. 675 outputKey := &UtxoKey{ 676 TxID: *txnHash, 677 Index: uint32(outputIndex), 678 } 679 680 // Verify that the utxo operation we're undoing is an add and advance 681 // our index to the next operation. 682 currentOperation := utxoOpsForTxn[operationIndex] 683 operationIndex-- 684 if currentOperation.Type != OperationTypeAddUtxo { 685 return fmt.Errorf( 686 "_disconnectBasicTransfer: Output with key %v does not line up to an "+ 687 "ADD operation in the passed utxoOps", outputKey) 688 } 689 690 // The current output should be at the end of the utxo list so go 691 // ahead and fetch it. Do some sanity checks to make sure the view 692 // is in sync with the operations we're trying to perform. 693 outputEntry := bav.GetUtxoEntryForUtxoKey(outputKey) 694 if outputEntry == nil { 695 return fmt.Errorf( 696 "_disconnectBasicTransfer: Output with key %v is missing from "+ 697 "utxo view", outputKey) 698 } 699 if outputEntry.isSpent { 700 return fmt.Errorf( 701 "_disconnectBasicTransfer: Output with key %v was spent before "+ 702 "being removed from the utxo view. This should never "+ 703 "happen", outputKey) 704 } 705 if outputEntry.AmountNanos != currentOutput.AmountNanos { 706 return fmt.Errorf( 707 "_disconnectBasicTransfer: Output with key %v has amount (%d) "+ 708 "that differs from the amount for the output in the "+ 709 "view (%d)", outputKey, currentOutput.AmountNanos, 710 outputEntry.AmountNanos) 711 } 712 if !reflect.DeepEqual(outputEntry.PublicKey, currentOutput.PublicKey) { 713 return fmt.Errorf( 714 "_disconnectBasicTransfer: Output with key %v has public key (%v) "+ 715 "that differs from the public key for the output in the "+ 716 "view (%v)", outputKey, currentOutput.PublicKey, 717 outputEntry.PublicKey) 718 } 719 if outputEntry.BlockHeight != blockHeight { 720 return fmt.Errorf( 721 "_disconnectBasicTransfer: Output with key %v has block height (%d) "+ 722 "that differs from the block we're disconnecting (%d)", 723 outputKey, outputEntry.BlockHeight, blockHeight) 724 } 725 if outputEntry.UtxoType == UtxoTypeBlockReward && (currentTxn.TxnMeta.GetTxnType() != TxnTypeBlockReward) { 726 727 return fmt.Errorf( 728 "_disconnectBasicTransfer: Output with key %v is a block reward txn according "+ 729 "to the view, yet is not the first transaction referenced in "+ 730 "the block", outputKey) 731 } 732 733 if err := bav._unAddUtxo(outputKey); err != nil { 734 return errors.Wrapf(err, "_disconnectBasicTransfer: Problem unAdding utxo %v: ", outputKey) 735 } 736 } 737 738 // At this point we should have rolled back all of the transaction's outputs 739 // in the view. Now we roll back its inputs, similarly processing them in 740 // backwards order. 741 for inputIndex := len(currentTxn.TxInputs) - 1; inputIndex >= 0; inputIndex-- { 742 currentInput := currentTxn.TxInputs[inputIndex] 743 744 // Convert this input to a utxo key. 745 inputKey := UtxoKey(*currentInput) 746 747 // Get the output entry for this input from the utxoOps that were 748 // passed in and check its type. For every input that we're restoring 749 // we need a SPEND operation that lines up with it. 750 currentOperation := utxoOpsForTxn[operationIndex] 751 operationIndex-- 752 if currentOperation.Type != OperationTypeSpendUtxo { 753 return fmt.Errorf( 754 "_disconnectBasicTransfer: Input with key %v does not line up with a "+ 755 "SPEND operation in the passed utxoOps", inputKey) 756 } 757 758 // Check that the input matches the key of the spend we're rolling 759 // back. 760 if inputKey != *currentOperation.Key { 761 return fmt.Errorf( 762 "_disconnectBasicTransfer: Input with key %v does not match the key of the "+ 763 "corresponding SPEND operation in the passed utxoOps %v", 764 inputKey, *currentOperation.Key) 765 } 766 767 // Unspend the entry using the information in the UtxoOperation. If the entry 768 // was de-serialized from the db it will have its utxoKey unset so we need to 769 // set it here in order to make it unspendable. 770 currentOperation.Entry.UtxoKey = currentOperation.Key 771 if err := bav._unSpendUtxo(currentOperation.Entry); err != nil { 772 return errors.Wrapf(err, "_disconnectBasicTransfer: Problem unspending utxo %v: ", currentOperation.Key) 773 } 774 } 775 776 return nil 777 } 778 779 func (bav *UtxoView) _disconnectUpdateGlobalParams( 780 operationType OperationType, currentTxn *MsgDeSoTxn, txnHash *BlockHash, 781 utxoOpsForTxn []*UtxoOperation, blockHeight uint32) error { 782 // Check that the last operation has the required OperationType 783 operationIndex := len(utxoOpsForTxn) - 1 784 if len(utxoOpsForTxn) == 0 { 785 return fmt.Errorf("_disconnectUpdateGlobalParams: Trying to revert "+ 786 "%v but utxoOperations are missing", 787 OperationTypeUpdateGlobalParams) 788 } 789 if utxoOpsForTxn[operationIndex].Type != OperationTypeUpdateGlobalParams { 790 return fmt.Errorf("_disconnectUpdateGlobalParams: Trying to revert "+ 791 "%v but found type %v", 792 OperationTypeUpdateGlobalParams, utxoOpsForTxn[operationIndex].Type) 793 } 794 operationData := utxoOpsForTxn[operationIndex] 795 796 // Reset the global params to their previous value. 797 // This previous value comes from the UtxoOperation data. 798 prevGlobalParamEntry := operationData.PrevGlobalParamsEntry 799 if prevGlobalParamEntry == nil { 800 prevGlobalParamEntry = &InitialGlobalParamsEntry 801 } 802 bav.GlobalParamsEntry = prevGlobalParamEntry 803 804 // Reset any modified forbidden pub key entries if they exist. 805 if operationData.PrevForbiddenPubKeyEntry != nil { 806 pkMapKey := MakePkMapKey(operationData.PrevForbiddenPubKeyEntry.PubKey) 807 bav.ForbiddenPubKeyToForbiddenPubKeyEntry[pkMapKey] = operationData.PrevForbiddenPubKeyEntry 808 } 809 810 // Now revert the basic transfer with the remaining operations. Cut off 811 // the UpdateGlobalParams operation at the end since we just reverted it. 812 return bav._disconnectBasicTransfer( 813 currentTxn, txnHash, utxoOpsForTxn[:operationIndex], blockHeight) 814 } 815 816 func (bav *UtxoView) DisconnectTransaction(currentTxn *MsgDeSoTxn, txnHash *BlockHash, 817 utxoOpsForTxn []*UtxoOperation, blockHeight uint32) error { 818 819 if currentTxn.TxnMeta.GetTxnType() == TxnTypeBlockReward || currentTxn.TxnMeta.GetTxnType() == TxnTypeBasicTransfer { 820 return bav._disconnectBasicTransfer( 821 currentTxn, txnHash, utxoOpsForTxn, blockHeight) 822 823 } else if currentTxn.TxnMeta.GetTxnType() == TxnTypeBitcoinExchange { 824 return bav._disconnectBitcoinExchange( 825 OperationTypeBitcoinExchange, currentTxn, txnHash, utxoOpsForTxn, blockHeight) 826 827 } else if currentTxn.TxnMeta.GetTxnType() == TxnTypePrivateMessage { 828 return bav._disconnectPrivateMessage( 829 OperationTypePrivateMessage, currentTxn, txnHash, utxoOpsForTxn, blockHeight) 830 831 } else if currentTxn.TxnMeta.GetTxnType() == TxnTypeSubmitPost { 832 return bav._disconnectSubmitPost( 833 OperationTypeSubmitPost, currentTxn, txnHash, utxoOpsForTxn, blockHeight) 834 835 } else if currentTxn.TxnMeta.GetTxnType() == TxnTypeUpdateProfile { 836 return bav._disconnectUpdateProfile( 837 OperationTypeUpdateProfile, currentTxn, txnHash, utxoOpsForTxn, blockHeight) 838 839 } else if currentTxn.TxnMeta.GetTxnType() == TxnTypeUpdateBitcoinUSDExchangeRate { 840 return bav._disconnectUpdateBitcoinUSDExchangeRate( 841 OperationTypeUpdateBitcoinUSDExchangeRate, currentTxn, txnHash, utxoOpsForTxn, blockHeight) 842 843 } else if currentTxn.TxnMeta.GetTxnType() == TxnTypeUpdateGlobalParams { 844 return bav._disconnectUpdateGlobalParams( 845 OperationTypeUpdateGlobalParams, currentTxn, txnHash, utxoOpsForTxn, blockHeight) 846 847 } else if currentTxn.TxnMeta.GetTxnType() == TxnTypeFollow { 848 return bav._disconnectFollow( 849 OperationTypeFollow, currentTxn, txnHash, utxoOpsForTxn, blockHeight) 850 851 } else if currentTxn.TxnMeta.GetTxnType() == TxnTypeLike { 852 return bav._disconnectLike( 853 OperationTypeFollow, currentTxn, txnHash, utxoOpsForTxn, blockHeight) 854 855 } else if currentTxn.TxnMeta.GetTxnType() == TxnTypeCreatorCoin { 856 return bav._disconnectCreatorCoin( 857 OperationTypeCreatorCoin, currentTxn, txnHash, utxoOpsForTxn, blockHeight) 858 859 } else if currentTxn.TxnMeta.GetTxnType() == TxnTypeCreatorCoinTransfer { 860 return bav._disconnectCreatorCoinTransfer( 861 OperationTypeCreatorCoinTransfer, currentTxn, txnHash, utxoOpsForTxn, blockHeight) 862 863 } else if currentTxn.TxnMeta.GetTxnType() == TxnTypeSwapIdentity { 864 return bav._disconnectSwapIdentity( 865 OperationTypeSwapIdentity, currentTxn, txnHash, utxoOpsForTxn, blockHeight) 866 867 } else if currentTxn.TxnMeta.GetTxnType() == TxnTypeCreateNFT { 868 return bav._disconnectCreateNFT( 869 OperationTypeCreateNFT, currentTxn, txnHash, utxoOpsForTxn, blockHeight) 870 871 } else if currentTxn.TxnMeta.GetTxnType() == TxnTypeUpdateNFT { 872 return bav._disconnectUpdateNFT( 873 OperationTypeUpdateNFT, currentTxn, txnHash, utxoOpsForTxn, blockHeight) 874 875 } else if currentTxn.TxnMeta.GetTxnType() == TxnTypeAcceptNFTBid { 876 return bav._disconnectAcceptNFTBid( 877 OperationTypeAcceptNFTBid, currentTxn, txnHash, utxoOpsForTxn, blockHeight) 878 879 } else if currentTxn.TxnMeta.GetTxnType() == TxnTypeNFTBid { 880 return bav._disconnectNFTBid( 881 OperationTypeNFTBid, currentTxn, txnHash, utxoOpsForTxn, blockHeight) 882 883 } else if currentTxn.TxnMeta.GetTxnType() == TxnTypeNFTTransfer { 884 return bav._disconnectNFTTransfer( 885 OperationTypeNFTTransfer, currentTxn, txnHash, utxoOpsForTxn, blockHeight) 886 887 } else if currentTxn.TxnMeta.GetTxnType() == TxnTypeAcceptNFTTransfer { 888 return bav._disconnectAcceptNFTTransfer( 889 OperationTypeAcceptNFTTransfer, currentTxn, txnHash, utxoOpsForTxn, blockHeight) 890 891 } else if currentTxn.TxnMeta.GetTxnType() == TxnTypeBurnNFT { 892 return bav._disconnectBurnNFT( 893 OperationTypeBurnNFT, currentTxn, txnHash, utxoOpsForTxn, blockHeight) 894 895 } else if currentTxn.TxnMeta.GetTxnType() == TxnTypeAuthorizeDerivedKey { 896 return bav._disconnectAuthorizeDerivedKey( 897 OperationTypeAuthorizeDerivedKey, currentTxn, txnHash, utxoOpsForTxn, blockHeight) 898 899 } 900 901 return fmt.Errorf("DisconnectBlock: Unimplemented txn type %v", currentTxn.TxnMeta.GetTxnType().String()) 902 } 903 904 func (bav *UtxoView) DisconnectBlock( 905 desoBlock *MsgDeSoBlock, txHashes []*BlockHash, utxoOps [][]*UtxoOperation) error { 906 907 glog.Infof("DisconnectBlock: Disconnecting block %v", desoBlock) 908 909 // Verify that the block being disconnected is the current tip. DisconnectBlock 910 // can only be called on a block at the tip. We do this to keep the API simple. 911 blockHash, err := desoBlock.Header.Hash() 912 if err != nil { 913 return fmt.Errorf("DisconnectBlock: Problem computing block hash") 914 } 915 if *bav.TipHash != *blockHash { 916 return fmt.Errorf("DisconnectBlock: Block being disconnected does not match tip") 917 } 918 919 // Verify the number of ADD and SPEND operations in the utxOps list is equal 920 // to the number of outputs and inputs in the block respectively. 921 // 922 // There is a special case, which is that BidderInputs count as inputs in a 923 // txn and they result in SPEND operations being created. 924 numInputs := 0 925 numOutputs := 0 926 for _, txn := range desoBlock.Txns { 927 numInputs += len(txn.TxInputs) 928 if txn.TxnMeta.GetTxnType() == TxnTypeAcceptNFTBid { 929 numInputs += len(txn.TxnMeta.(*AcceptNFTBidMetadata).BidderInputs) 930 } 931 numOutputs += len(txn.TxOutputs) 932 } 933 numSpendOps := 0 934 numAddOps := 0 935 for _, utxoOpsForTxn := range utxoOps { 936 for _, op := range utxoOpsForTxn { 937 if op.Type == OperationTypeSpendUtxo { 938 numSpendOps++ 939 } else if op.Type == OperationTypeAddUtxo { 940 numAddOps++ 941 } 942 } 943 } 944 if numInputs != numSpendOps { 945 return fmt.Errorf( 946 "DisconnectBlock: Number of inputs in passed block (%d) "+ 947 "not equal to number of SPEND operations in passed "+ 948 "utxoOps (%d)", numInputs, numSpendOps) 949 } 950 // Note that the number of add operations can be greater than the number of "explicit" 951 // outputs in the block because transactions like BitcoinExchange 952 // produce "implicit" outputs when the transaction is applied. 953 if numOutputs > numAddOps { 954 return fmt.Errorf( 955 "DisconnectBlock: Number of outputs in passed block (%d) "+ 956 "not equal to number of ADD operations in passed "+ 957 "utxoOps (%d)", numOutputs, numAddOps) 958 } 959 960 // Loop through the txns backwards to process them. 961 // Track the operation we're performing as we go. 962 for txnIndex := len(desoBlock.Txns) - 1; txnIndex >= 0; txnIndex-- { 963 currentTxn := desoBlock.Txns[txnIndex] 964 txnHash := txHashes[txnIndex] 965 utxoOpsForTxn := utxoOps[txnIndex] 966 blockHeight := desoBlock.Header.Height 967 968 err := bav.DisconnectTransaction(currentTxn, txnHash, utxoOpsForTxn, uint32(blockHeight)) 969 if err != nil { 970 return errors.Wrapf(err, "DisconnectBlock: Problem disconnecting transaction: %v", currentTxn) 971 } 972 } 973 974 // At this point, all of the transactions in the block should be fully 975 // reversed and the view should therefore be in the state it was in before 976 // this block was applied. 977 978 // Update the tip to point to the parent of this block since we've managed 979 // to successfully disconnect it. 980 bav.TipHash = desoBlock.Header.PrevBlockHash 981 982 return nil 983 } 984 985 func _isEntryImmatureBlockReward(utxoEntry *UtxoEntry, blockHeight uint32, params *DeSoParams) bool { 986 if utxoEntry.UtxoType == UtxoTypeBlockReward { 987 blocksPassed := blockHeight - utxoEntry.BlockHeight 988 // Note multiplication is OK here and has no chance of overflowing because 989 // block heights are computed by our code and are guaranteed to be sane values. 990 timePassed := time.Duration(int64(params.TimeBetweenBlocks) * int64(blocksPassed)) 991 if timePassed < params.BlockRewardMaturity { 992 // Mark the block as invalid and return error if an immature block reward 993 // is being spent. 994 return true 995 } 996 } 997 return false 998 } 999 1000 func (bav *UtxoView) _verifySignature(txn *MsgDeSoTxn, blockHeight uint32) error { 1001 // Compute a hash of the transaction. 1002 txBytes, err := txn.ToBytes(true /*preSignature*/) 1003 if err != nil { 1004 return errors.Wrapf(err, "_verifySignature: Problem serializing txn without signature: ") 1005 } 1006 txHash := Sha256DoubleHash(txBytes) 1007 1008 // Look for the derived key in transaction ExtraData and validate it. For transactions 1009 // signed using a derived key, the derived public key is passed to ExtraData. 1010 var derivedPk *btcec.PublicKey 1011 var derivedPkBytes []byte 1012 if txn.ExtraData != nil { 1013 var isDerived bool 1014 derivedPkBytes, isDerived = txn.ExtraData[DerivedPublicKey] 1015 if isDerived { 1016 derivedPk, err = btcec.ParsePubKey(derivedPkBytes, btcec.S256()) 1017 if err != nil { 1018 return RuleErrorDerivedKeyInvalidExtraData 1019 } 1020 } 1021 } 1022 1023 // Get the owner public key and attempt turning it into *btcec.PublicKey. 1024 ownerPkBytes := txn.PublicKey 1025 ownerPk, err := btcec.ParsePubKey(ownerPkBytes, btcec.S256()) 1026 if err != nil { 1027 return errors.Wrapf(err, "_verifySignature: Problem parsing owner public key: ") 1028 } 1029 1030 // If no derived key is present in ExtraData, we check if transaction was signed by the owner. 1031 // If derived key is present in ExtraData, we check if transaction was signed by the derived key. 1032 if derivedPk == nil { 1033 // Verify that the transaction is signed by the specified key. 1034 if txn.Signature.Verify(txHash[:], ownerPk) { 1035 return nil 1036 } 1037 } else { 1038 // Look for a derived key entry in UtxoView and DB, check if it exists nor is deleted. 1039 derivedKeyEntry := bav._getDerivedKeyMappingForOwner(ownerPkBytes, derivedPkBytes) 1040 if derivedKeyEntry == nil || derivedKeyEntry.isDeleted { 1041 return RuleErrorDerivedKeyNotAuthorized 1042 } 1043 1044 // Sanity-check that transaction public keys line up with looked-up derivedKeyEntry public keys. 1045 if !reflect.DeepEqual(ownerPkBytes, derivedKeyEntry.OwnerPublicKey[:]) || 1046 !reflect.DeepEqual(derivedPkBytes, derivedKeyEntry.DerivedPublicKey[:]) { 1047 return RuleErrorDerivedKeyNotAuthorized 1048 } 1049 1050 // At this point, we know the derivedKeyEntry that we have is matching. 1051 // We check if the derived key hasn't been de-authorized or hasn't expired. 1052 if derivedKeyEntry.OperationType != AuthorizeDerivedKeyOperationValid || 1053 derivedKeyEntry.ExpirationBlock <= uint64(blockHeight) { 1054 return RuleErrorDerivedKeyNotAuthorized 1055 } 1056 1057 // All checks passed so we try to verify the signature. 1058 if txn.Signature.Verify(txHash[:], derivedPk) { 1059 return nil 1060 } 1061 1062 return RuleErrorDerivedKeyNotAuthorized 1063 } 1064 1065 return RuleErrorInvalidTransactionSignature 1066 } 1067 1068 func (bav *UtxoView) _connectBasicTransfer( 1069 txn *MsgDeSoTxn, txHash *BlockHash, blockHeight uint32, verifySignatures bool) ( 1070 _totalInput uint64, _totalOutput uint64, _utxoOps []*UtxoOperation, _err error) { 1071 1072 var utxoOpsForTxn []*UtxoOperation 1073 1074 // Loop through all the inputs and validate them. 1075 var totalInput uint64 1076 // Each input should have a UtxoEntry corresponding to it if the transaction 1077 // is legitimate. These should all have back-pointers to their UtxoKeys as well. 1078 utxoEntriesForInputs := []*UtxoEntry{} 1079 for _, desoInput := range txn.TxInputs { 1080 // Fetch the utxoEntry for this input from the view. Make a copy to 1081 // avoid having the iterator change under our feet. 1082 utxoKey := UtxoKey(*desoInput) 1083 utxoEntry := bav.GetUtxoEntryForUtxoKey(&utxoKey) 1084 // If the utxo doesn't exist mark the block as invalid and return an error. 1085 if utxoEntry == nil { 1086 return 0, 0, nil, RuleErrorInputSpendsNonexistentUtxo 1087 } 1088 // If the utxo exists but is already spent mark the block as invalid and 1089 // return an error. 1090 if utxoEntry.isSpent { 1091 return 0, 0, nil, RuleErrorInputSpendsPreviouslySpentOutput 1092 } 1093 // If the utxo is from a block reward txn, make sure enough time has passed to 1094 // make it spendable. 1095 if _isEntryImmatureBlockReward(utxoEntry, blockHeight, bav.Params) { 1096 glog.V(1).Infof("utxoKey: %v, utxoEntry: %v, height: %d", &utxoKey, utxoEntry, blockHeight) 1097 return 0, 0, nil, RuleErrorInputSpendsImmatureBlockReward 1098 } 1099 1100 // Verify that the input's public key is the same as the public key specified 1101 // in the transaction. 1102 // 1103 // TODO: Enforcing this rule isn't a clear-cut decision. On the one hand, 1104 // we save space and minimize complexity by enforcing this constraint. On 1105 // the other hand, we make certain things harder to implement in the 1106 // future. For example, implementing constant key rotation like Bitcoin 1107 // has is difficult to do with a scheme like this. As are things like 1108 // multi-sig (although that could probably be handled using transaction 1109 // metadata). Key rotation combined with the use of addresses also helps 1110 // a lot with quantum resistance. Nevertheless, if we assume the platform 1111 // is committed to "one identity = roughly one public key" for usability 1112 // reasons (e.g. reputation is way easier to manage without key rotation), 1113 // then I don't think this constraint should pose much of an issue. 1114 if !reflect.DeepEqual(utxoEntry.PublicKey, txn.PublicKey) { 1115 return 0, 0, nil, RuleErrorInputWithPublicKeyDifferentFromTxnPublicKey 1116 } 1117 1118 // Sanity check the amount of the input. 1119 if utxoEntry.AmountNanos > MaxNanos || 1120 totalInput >= (math.MaxUint64-utxoEntry.AmountNanos) || 1121 totalInput+utxoEntry.AmountNanos > MaxNanos { 1122 1123 return 0, 0, nil, RuleErrorInputSpendsOutputWithInvalidAmount 1124 } 1125 // Add the amount of the utxo to the total input and add the UtxoEntry to 1126 // our list. 1127 totalInput += utxoEntry.AmountNanos 1128 utxoEntriesForInputs = append(utxoEntriesForInputs, utxoEntry) 1129 1130 // At this point we know the utxo exists in the view and is unspent so actually 1131 // tell the view to spend the input. If the spend fails for any reason we return 1132 // an error. Don't mark the block as invalid though since this is not necessarily 1133 // a rule error and the block could benefit from reprocessing. 1134 newUtxoOp, err := bav._spendUtxo(&utxoKey) 1135 1136 if err != nil { 1137 return 0, 0, nil, errors.Wrapf(err, "_connectBasicTransfer: Problem spending input utxo") 1138 } 1139 1140 utxoOpsForTxn = append(utxoOpsForTxn, newUtxoOp) 1141 } 1142 1143 if len(txn.TxInputs) != len(utxoEntriesForInputs) { 1144 // Something went wrong if these lists differ in length. 1145 return 0, 0, nil, fmt.Errorf("_connectBasicTransfer: Length of list of " + 1146 "UtxoEntries does not match length of input list; this should never happen") 1147 } 1148 1149 // Block rewards are a bit special in that we don't allow them to have any 1150 // inputs. Part of the reason for this stems from the fact that we explicitly 1151 // require that block reward transactions not be signed. If a block reward is 1152 // not allowed to have a signature then it should not be trying to spend any 1153 // inputs. 1154 if txn.TxnMeta.GetTxnType() == TxnTypeBlockReward && len(txn.TxInputs) != 0 { 1155 return 0, 0, nil, RuleErrorBlockRewardTxnNotAllowedToHaveInputs 1156 } 1157 1158 // At this point, all of the utxos corresponding to inputs of this txn 1159 // should be marked as spent in the view. Now we go through and process 1160 // the outputs. 1161 var totalOutput uint64 1162 amountsByPublicKey := make(map[PkMapKey]uint64) 1163 for outputIndex, desoOutput := range txn.TxOutputs { 1164 // Sanity check the amount of the output. Mark the block as invalid and 1165 // return an error if it isn't sane. 1166 if desoOutput.AmountNanos > MaxNanos || 1167 totalOutput >= (math.MaxUint64-desoOutput.AmountNanos) || 1168 totalOutput+desoOutput.AmountNanos > MaxNanos { 1169 1170 return 0, 0, nil, RuleErrorTxnOutputWithInvalidAmount 1171 } 1172 1173 // Since the amount is sane, add it to the total. 1174 totalOutput += desoOutput.AmountNanos 1175 1176 // Create a map of total output by public key. This is used to check diamond 1177 // amounts below. 1178 // 1179 // Note that we don't need to check overflow here because overflow is checked 1180 // directly above when adding to totalOutput. 1181 currentAmount, _ := amountsByPublicKey[MakePkMapKey(desoOutput.PublicKey)] 1182 amountsByPublicKey[MakePkMapKey(desoOutput.PublicKey)] = currentAmount + desoOutput.AmountNanos 1183 1184 // Create a new entry for this output and add it to the view. It should be 1185 // added at the end of the utxo list. 1186 outputKey := UtxoKey{ 1187 TxID: *txHash, 1188 Index: uint32(outputIndex), 1189 } 1190 utxoType := UtxoTypeOutput 1191 if txn.TxnMeta.GetTxnType() == TxnTypeBlockReward { 1192 utxoType = UtxoTypeBlockReward 1193 } 1194 // A basic transfer cannot create any output other than a "normal" output 1195 // or a BlockReward. Outputs of other types must be created after processing 1196 // the "basic" outputs. 1197 1198 utxoEntry := UtxoEntry{ 1199 AmountNanos: desoOutput.AmountNanos, 1200 PublicKey: desoOutput.PublicKey, 1201 BlockHeight: blockHeight, 1202 UtxoType: utxoType, 1203 UtxoKey: &outputKey, 1204 // We leave the position unset and isSpent to false by default. 1205 // The position will be set in the call to _addUtxo. 1206 } 1207 // If we have a problem adding this utxo return an error but don't 1208 // mark this block as invalid since it's not a rule error and the block 1209 // could therefore benefit from being processed in the future. 1210 newUtxoOp, err := bav._addUtxo(&utxoEntry) 1211 if err != nil { 1212 return 0, 0, nil, errors.Wrapf(err, "_connectBasicTransfer: Problem adding output utxo") 1213 } 1214 1215 // Rosetta uses this UtxoOperation to provide INPUT amounts 1216 utxoOpsForTxn = append(utxoOpsForTxn, newUtxoOp) 1217 } 1218 1219 // Now that we have computed the outputs, we can finish processing diamonds if need be. 1220 diamondPostHashBytes, hasDiamondPostHash := txn.ExtraData[DiamondPostHashKey] 1221 diamondPostHash := &BlockHash{} 1222 diamondLevelBytes, hasDiamondLevel := txn.ExtraData[DiamondLevelKey] 1223 var previousDiamondPostEntry *PostEntry 1224 var previousDiamondEntry *DiamondEntry 1225 if hasDiamondPostHash && blockHeight > DeSoDiamondsBlockHeight && 1226 txn.TxnMeta.GetTxnType() == TxnTypeBasicTransfer { 1227 if !hasDiamondLevel { 1228 return 0, 0, nil, RuleErrorBasicTransferHasDiamondPostHashWithoutDiamondLevel 1229 } 1230 diamondLevel, bytesRead := Varint(diamondLevelBytes) 1231 // NOTE: Despite being an int, diamondLevel is required to be non-negative. This 1232 // is useful for sorting our dbkeys by diamondLevel. 1233 if bytesRead < 0 || diamondLevel < 0 { 1234 return 0, 0, nil, RuleErrorBasicTransferHasInvalidDiamondLevel 1235 } 1236 1237 // Get the post that is being diamonded. 1238 if len(diamondPostHashBytes) != HashSizeBytes { 1239 return 0, 0, nil, errors.Wrapf( 1240 RuleErrorBasicTransferDiamondInvalidLengthForPostHashBytes, 1241 "_connectBasicTransfer: DiamondPostHashBytes length: %d", len(diamondPostHashBytes)) 1242 } 1243 copy(diamondPostHash[:], diamondPostHashBytes[:]) 1244 1245 previousDiamondPostEntry = bav.GetPostEntryForPostHash(diamondPostHash) 1246 if previousDiamondPostEntry == nil || previousDiamondPostEntry.isDeleted { 1247 return 0, 0, nil, RuleErrorBasicTransferDiamondPostEntryDoesNotExist 1248 } 1249 1250 // Store the diamond recipient pub key so we can figure out how much they are paid. 1251 diamondRecipientPubKey := previousDiamondPostEntry.PosterPublicKey 1252 1253 // Check that the diamond sender and receiver public keys are different. 1254 if reflect.DeepEqual(txn.PublicKey, diamondRecipientPubKey) { 1255 return 0, 0, nil, RuleErrorBasicTransferDiamondCannotTransferToSelf 1256 } 1257 1258 expectedDeSoNanosToTransfer, netNewDiamonds, err := bav.ValidateDiamondsAndGetNumDeSoNanos( 1259 txn.PublicKey, diamondRecipientPubKey, diamondPostHash, diamondLevel, blockHeight) 1260 if err != nil { 1261 return 0, 0, nil, errors.Wrapf(err, "_connectBasicTransfer: ") 1262 } 1263 diamondRecipientTotal, _ := amountsByPublicKey[MakePkMapKey(diamondRecipientPubKey)] 1264 1265 if diamondRecipientTotal < expectedDeSoNanosToTransfer { 1266 return 0, 0, nil, RuleErrorBasicTransferInsufficientDeSoForDiamondLevel 1267 } 1268 1269 // The diamondPostEntry needs to be updated with the number of new diamonds. 1270 // We make a copy to avoid issues with disconnecting. 1271 newDiamondPostEntry := &PostEntry{} 1272 *newDiamondPostEntry = *previousDiamondPostEntry 1273 newDiamondPostEntry.DiamondCount += uint64(netNewDiamonds) 1274 bav._setPostEntryMappings(newDiamondPostEntry) 1275 1276 // Convert pub keys into PKIDs so we can make the DiamondEntry. 1277 senderPKID := bav.GetPKIDForPublicKey(txn.PublicKey) 1278 receiverPKID := bav.GetPKIDForPublicKey(diamondRecipientPubKey) 1279 1280 // Create a new DiamondEntry 1281 newDiamondEntry := &DiamondEntry{ 1282 SenderPKID: senderPKID.PKID, 1283 ReceiverPKID: receiverPKID.PKID, 1284 DiamondPostHash: diamondPostHash, 1285 DiamondLevel: diamondLevel, 1286 } 1287 1288 // Save the old DiamondEntry 1289 diamondKey := MakeDiamondKey(senderPKID.PKID, receiverPKID.PKID, diamondPostHash) 1290 existingDiamondEntry := bav.GetDiamondEntryForDiamondKey(&diamondKey) 1291 // Save the existing DiamondEntry, if it exists, so we can disconnect 1292 if existingDiamondEntry != nil { 1293 dd := &DiamondEntry{} 1294 *dd = *existingDiamondEntry 1295 previousDiamondEntry = dd 1296 } 1297 1298 // Now set the diamond entry mappings on the view so they are flushed to the DB. 1299 bav._setDiamondEntryMappings(newDiamondEntry) 1300 1301 // Add an op to help us with the disconnect. 1302 utxoOpsForTxn = append(utxoOpsForTxn, &UtxoOperation{ 1303 Type: OperationTypeDeSoDiamond, 1304 PrevPostEntry: previousDiamondPostEntry, 1305 PrevDiamondEntry: previousDiamondEntry, 1306 }) 1307 } 1308 1309 // If signature verification is requested then do that as well. 1310 if verifySignatures { 1311 // When we looped through the inputs we verified that all of them belong 1312 // to the public key specified in the transaction. So, as long as the transaction 1313 // public key has signed the transaction as a whole, we can assume that 1314 // all of the inputs are authorized to be spent. One signature to rule them 1315 // all. 1316 // 1317 // UPDATE: Transaction can be signed by a different key, called a derived key. 1318 // The derived key must be authorized through an AuthorizeDerivedKey transaction, 1319 // and then passed along in ExtraData for evey transaction signed with it. 1320 // 1321 // We treat block rewards as a special case in that we actually require that they 1322 // not have a transaction-level public key and that they not be signed. Doing this 1323 // simplifies things operationally for miners because it means they can run their 1324 // mining operation without having any private key material on any of the mining 1325 // nodes. Block rewards are the only transactions that get a pass on this. They are 1326 // also not allowed to have any inputs because they by construction cannot authorize 1327 // the spending of any inputs. 1328 if txn.TxnMeta.GetTxnType() == TxnTypeBlockReward { 1329 if len(txn.PublicKey) != 0 || txn.Signature != nil { 1330 return 0, 0, nil, RuleErrorBlockRewardTxnNotAllowedToHaveSignature 1331 } 1332 } else { 1333 if err := bav._verifySignature(txn, blockHeight); err != nil { 1334 return 0, 0, nil, errors.Wrapf(err, "_connectBasicTransfer: Problem verifying txn signature: ") 1335 } 1336 } 1337 } 1338 1339 // Now that we've processed the transaction, return all of the computed 1340 // data. 1341 return totalInput, totalOutput, utxoOpsForTxn, nil 1342 } 1343 1344 func (bav *UtxoView) _connectUpdateGlobalParams( 1345 txn *MsgDeSoTxn, txHash *BlockHash, blockHeight uint32, verifySignatures bool) ( 1346 _totalInput uint64, _totalOutput uint64, _utxoOps []*UtxoOperation, _err error) { 1347 1348 // Check that the transaction has the right TxnType. 1349 if txn.TxnMeta.GetTxnType() != TxnTypeUpdateGlobalParams { 1350 return 0, 0, nil, fmt.Errorf("_connectUpdateGlobalParams: called with bad TxnType %s", 1351 txn.TxnMeta.GetTxnType().String()) 1352 } 1353 1354 // Initialize the new global params entry as a copy of the old global params entry and 1355 // only overwrite values provided in extra data. 1356 prevGlobalParamsEntry := bav.GlobalParamsEntry 1357 newGlobalParamsEntry := *prevGlobalParamsEntry 1358 extraData := txn.ExtraData 1359 // Validate the public key. Only a paramUpdater is allowed to trigger this. 1360 _, updaterIsParamUpdater := bav.Params.ParamUpdaterPublicKeys[MakePkMapKey(txn.PublicKey)] 1361 if !updaterIsParamUpdater { 1362 return 0, 0, nil, RuleErrorUserNotAuthorizedToUpdateGlobalParams 1363 } 1364 if len(extraData[USDCentsPerBitcoinKey]) > 0 { 1365 // Validate that the exchange rate is not less than the floor as a sanity-check. 1366 newUSDCentsPerBitcoin, usdCentsPerBitcoinBytesRead := Uvarint(extraData[USDCentsPerBitcoinKey]) 1367 if usdCentsPerBitcoinBytesRead <= 0 { 1368 return 0, 0, nil, fmt.Errorf("_connectUpdateGlobalParams: unable to decode USDCentsPerBitcoin as uint64") 1369 } 1370 if newUSDCentsPerBitcoin < MinUSDCentsPerBitcoin { 1371 return 0, 0, nil, RuleErrorExchangeRateTooLow 1372 } 1373 if newUSDCentsPerBitcoin > MaxUSDCentsPerBitcoin { 1374 return 0, 0, nil, RuleErrorExchangeRateTooHigh 1375 } 1376 newGlobalParamsEntry.USDCentsPerBitcoin = newUSDCentsPerBitcoin 1377 } 1378 1379 if len(extraData[MinNetworkFeeNanosPerKBKey]) > 0 { 1380 newMinNetworkFeeNanosPerKB, minNetworkFeeNanosPerKBBytesRead := Uvarint(extraData[MinNetworkFeeNanosPerKBKey]) 1381 if minNetworkFeeNanosPerKBBytesRead <= 0 { 1382 return 0, 0, nil, fmt.Errorf("_connectUpdateGlobalParams: unable to decode MinNetworkFeeNanosPerKB as uint64") 1383 } 1384 if newMinNetworkFeeNanosPerKB < MinNetworkFeeNanosPerKBValue { 1385 return 0, 0, nil, RuleErrorMinNetworkFeeTooLow 1386 } 1387 if newMinNetworkFeeNanosPerKB > MaxNetworkFeeNanosPerKBValue { 1388 return 0, 0, nil, RuleErrorMinNetworkFeeTooHigh 1389 } 1390 newGlobalParamsEntry.MinimumNetworkFeeNanosPerKB = newMinNetworkFeeNanosPerKB 1391 } 1392 1393 if len(extraData[CreateProfileFeeNanosKey]) > 0 { 1394 newCreateProfileFeeNanos, createProfileFeeNanosBytesRead := Uvarint(extraData[CreateProfileFeeNanosKey]) 1395 if createProfileFeeNanosBytesRead <= 0 { 1396 return 0, 0, nil, fmt.Errorf("_connectUpdateGlobalParams: unable to decode CreateProfileFeeNanos as uint64") 1397 } 1398 if newCreateProfileFeeNanos < MinCreateProfileFeeNanos { 1399 return 0, 0, nil, RuleErrorCreateProfileFeeTooLow 1400 } 1401 if newCreateProfileFeeNanos > MaxCreateProfileFeeNanos { 1402 return 0, 0, nil, RuleErrorCreateProfileTooHigh 1403 } 1404 newGlobalParamsEntry.CreateProfileFeeNanos = newCreateProfileFeeNanos 1405 } 1406 1407 if len(extraData[CreateNFTFeeNanosKey]) > 0 { 1408 newCreateNFTFeeNanos, createNFTFeeNanosBytesRead := Uvarint(extraData[CreateNFTFeeNanosKey]) 1409 if createNFTFeeNanosBytesRead <= 0 { 1410 return 0, 0, nil, fmt.Errorf("_connectUpdateGlobalParams: unable to decode CreateNFTFeeNanos as uint64") 1411 } 1412 if newCreateNFTFeeNanos < MinCreateNFTFeeNanos { 1413 return 0, 0, nil, RuleErrorCreateNFTFeeTooLow 1414 } 1415 if newCreateNFTFeeNanos > MaxCreateNFTFeeNanos { 1416 return 0, 0, nil, RuleErrorCreateNFTFeeTooHigh 1417 } 1418 newGlobalParamsEntry.CreateNFTFeeNanos = newCreateNFTFeeNanos 1419 } 1420 1421 if len(extraData[MaxCopiesPerNFTKey]) > 0 { 1422 newMaxCopiesPerNFT, maxCopiesPerNFTBytesRead := Uvarint(extraData[MaxCopiesPerNFTKey]) 1423 if maxCopiesPerNFTBytesRead <= 0 { 1424 return 0, 0, nil, fmt.Errorf("_connectUpdateGlobalParams: unable to decode MaxCopiesPerNFT as uint64") 1425 } 1426 if newMaxCopiesPerNFT < MinMaxCopiesPerNFT { 1427 return 0, 0, nil, RuleErrorMaxCopiesPerNFTTooLow 1428 } 1429 if newMaxCopiesPerNFT > MaxMaxCopiesPerNFT { 1430 return 0, 0, nil, RuleErrorMaxCopiesPerNFTTooHigh 1431 } 1432 newGlobalParamsEntry.MaxCopiesPerNFT = newMaxCopiesPerNFT 1433 } 1434 1435 var newForbiddenPubKeyEntry *ForbiddenPubKeyEntry 1436 var prevForbiddenPubKeyEntry *ForbiddenPubKeyEntry 1437 var forbiddenPubKey []byte 1438 if _, exists := extraData[ForbiddenBlockSignaturePubKeyKey]; exists { 1439 forbiddenPubKey = extraData[ForbiddenBlockSignaturePubKeyKey] 1440 1441 if len(forbiddenPubKey) != btcec.PubKeyBytesLenCompressed { 1442 return 0, 0, nil, RuleErrorForbiddenPubKeyLength 1443 } 1444 1445 // If there is already an entry on the view for this pub key, save it. 1446 if val, ok := bav.ForbiddenPubKeyToForbiddenPubKeyEntry[MakePkMapKey(forbiddenPubKey)]; ok { 1447 prevForbiddenPubKeyEntry = val 1448 } 1449 1450 newForbiddenPubKeyEntry = &ForbiddenPubKeyEntry{ 1451 PubKey: forbiddenPubKey, 1452 } 1453 } 1454 1455 // Connect basic txn to get the total input and the total output without 1456 // considering the transaction metadata. 1457 totalInput, totalOutput, utxoOpsForTxn, err := bav._connectBasicTransfer( 1458 txn, txHash, blockHeight, verifySignatures) 1459 if err != nil { 1460 return 0, 0, nil, errors.Wrapf(err, "_connectUpdateGlobalParams: ") 1461 } 1462 1463 // Output must be non-zero 1464 if totalOutput == 0 { 1465 return 0, 0, nil, RuleErrorUserOutputMustBeNonzero 1466 } 1467 1468 if verifySignatures { 1469 // _connectBasicTransfer has already checked that the transaction is 1470 // signed by the top-level public key, which is all we need. 1471 } 1472 1473 // Update the GlobalParamsEntry using the txn's ExtraData. Save the previous value 1474 // so it can be easily reverted. 1475 bav.GlobalParamsEntry = &newGlobalParamsEntry 1476 1477 // Update the forbidden pub key entry on the view, if we have one to update. 1478 if newForbiddenPubKeyEntry != nil { 1479 bav.ForbiddenPubKeyToForbiddenPubKeyEntry[MakePkMapKey(forbiddenPubKey)] = newForbiddenPubKeyEntry 1480 } 1481 1482 // Save a UtxoOperation of type OperationTypeUpdateGlobalParams that will allow 1483 // us to easily revert when we disconnect the transaction. 1484 utxoOpsForTxn = append(utxoOpsForTxn, &UtxoOperation{ 1485 Type: OperationTypeUpdateGlobalParams, 1486 PrevGlobalParamsEntry: prevGlobalParamsEntry, 1487 PrevForbiddenPubKeyEntry: prevForbiddenPubKeyEntry, 1488 }) 1489 1490 return totalInput, totalOutput, utxoOpsForTxn, nil 1491 } 1492 1493 func (bav *UtxoView) ValidateDiamondsAndGetNumDeSoNanos( 1494 senderPublicKey []byte, 1495 receiverPublicKey []byte, 1496 diamondPostHash *BlockHash, 1497 diamondLevel int64, 1498 blockHeight uint32, 1499 ) (_numDeSoNanos uint64, _netNewDiamonds int64, _err error) { 1500 1501 // Check that the diamond level is reasonable 1502 diamondLevelMap := GetDeSoNanosDiamondLevelMapAtBlockHeight(int64(blockHeight)) 1503 if _, isAllowedLevel := diamondLevelMap[diamondLevel]; !isAllowedLevel { 1504 return 0, 0, fmt.Errorf( 1505 "ValidateDiamondsAndGetNumCreatorCoinNanos: Diamond level %v not allowed", 1506 diamondLevel) 1507 } 1508 1509 // Convert pub keys into PKIDs. 1510 senderPKID := bav.GetPKIDForPublicKey(senderPublicKey) 1511 receiverPKID := bav.GetPKIDForPublicKey(receiverPublicKey) 1512 1513 // Look up if there is an existing diamond entry. 1514 diamondKey := MakeDiamondKey(senderPKID.PKID, receiverPKID.PKID, diamondPostHash) 1515 diamondEntry := bav.GetDiamondEntryForDiamondKey(&diamondKey) 1516 1517 currDiamondLevel := int64(0) 1518 if diamondEntry != nil { 1519 currDiamondLevel = diamondEntry.DiamondLevel 1520 } 1521 1522 if currDiamondLevel >= diamondLevel { 1523 return 0, 0, RuleErrorCreatorCoinTransferPostAlreadyHasSufficientDiamonds 1524 } 1525 1526 // Calculate the number of creator coin nanos needed vs. already added for previous diamonds. 1527 currDeSoNanos := GetDeSoNanosForDiamondLevelAtBlockHeight(currDiamondLevel, int64(blockHeight)) 1528 neededDeSoNanos := GetDeSoNanosForDiamondLevelAtBlockHeight(diamondLevel, int64(blockHeight)) 1529 1530 // There is an edge case where, if the person's creator coin value goes down 1531 // by a large enough amount, then they can get a "free" diamond upgrade. This 1532 // seems fine for now. 1533 desoToTransferNanos := uint64(0) 1534 if neededDeSoNanos > currDeSoNanos { 1535 desoToTransferNanos = neededDeSoNanos - currDeSoNanos 1536 } 1537 1538 netNewDiamonds := diamondLevel - currDiamondLevel 1539 1540 return desoToTransferNanos, netNewDiamonds, nil 1541 } 1542 1543 func (bav *UtxoView) ConnectTransaction(txn *MsgDeSoTxn, txHash *BlockHash, 1544 txnSizeBytes int64, 1545 blockHeight uint32, verifySignatures bool, ignoreUtxos bool) ( 1546 _utxoOps []*UtxoOperation, _totalInput uint64, _totalOutput uint64, 1547 _fees uint64, _err error) { 1548 1549 return bav._connectTransaction(txn, txHash, 1550 txnSizeBytes, 1551 blockHeight, verifySignatures, 1552 ignoreUtxos) 1553 1554 } 1555 1556 func (bav *UtxoView) _connectTransaction(txn *MsgDeSoTxn, txHash *BlockHash, 1557 txnSizeBytes int64, blockHeight uint32, verifySignatures bool, ignoreUtxos bool) ( 1558 _utxoOps []*UtxoOperation, _totalInput uint64, _totalOutput uint64, 1559 _fees uint64, _err error) { 1560 1561 // Do a quick sanity check before trying to connect. 1562 if err := CheckTransactionSanity(txn); err != nil { 1563 return nil, 0, 0, 0, errors.Wrapf(err, "_connectTransaction: ") 1564 } 1565 1566 // Don't allow transactions that take up more than half of the block. 1567 txnBytes, err := txn.ToBytes(false) 1568 if err != nil { 1569 return nil, 0, 0, 0, errors.Wrapf( 1570 err, "CheckTransactionSanity: Problem serializing transaction: ") 1571 } 1572 if len(txnBytes) > int(bav.Params.MaxBlockSizeBytes/2) { 1573 return nil, 0, 0, 0, RuleErrorTxnTooBig 1574 } 1575 1576 var totalInput, totalOutput uint64 1577 var utxoOpsForTxn []*UtxoOperation 1578 if txn.TxnMeta.GetTxnType() == TxnTypeBlockReward || txn.TxnMeta.GetTxnType() == TxnTypeBasicTransfer { 1579 totalInput, totalOutput, utxoOpsForTxn, err = 1580 bav._connectBasicTransfer( 1581 txn, txHash, blockHeight, verifySignatures) 1582 1583 } else if txn.TxnMeta.GetTxnType() == TxnTypeBitcoinExchange { 1584 totalInput, totalOutput, utxoOpsForTxn, err = 1585 bav._connectBitcoinExchange( 1586 txn, txHash, blockHeight, verifySignatures) 1587 1588 } else if txn.TxnMeta.GetTxnType() == TxnTypePrivateMessage { 1589 totalInput, totalOutput, utxoOpsForTxn, err = 1590 bav._connectPrivateMessage( 1591 txn, txHash, blockHeight, verifySignatures) 1592 1593 } else if txn.TxnMeta.GetTxnType() == TxnTypeSubmitPost { 1594 totalInput, totalOutput, utxoOpsForTxn, err = 1595 bav._connectSubmitPost( 1596 txn, txHash, blockHeight, verifySignatures, ignoreUtxos) 1597 1598 } else if txn.TxnMeta.GetTxnType() == TxnTypeUpdateProfile { 1599 totalInput, totalOutput, utxoOpsForTxn, err = 1600 bav._connectUpdateProfile( 1601 txn, txHash, blockHeight, verifySignatures, ignoreUtxos) 1602 1603 } else if txn.TxnMeta.GetTxnType() == TxnTypeUpdateBitcoinUSDExchangeRate { 1604 totalInput, totalOutput, utxoOpsForTxn, err = 1605 bav._connectUpdateBitcoinUSDExchangeRate( 1606 txn, txHash, blockHeight, verifySignatures) 1607 1608 } else if txn.TxnMeta.GetTxnType() == TxnTypeUpdateGlobalParams { 1609 totalInput, totalOutput, utxoOpsForTxn, err = 1610 bav._connectUpdateGlobalParams( 1611 txn, txHash, blockHeight, verifySignatures) 1612 1613 } else if txn.TxnMeta.GetTxnType() == TxnTypeFollow { 1614 totalInput, totalOutput, utxoOpsForTxn, err = 1615 bav._connectFollow( 1616 txn, txHash, blockHeight, verifySignatures) 1617 1618 } else if txn.TxnMeta.GetTxnType() == TxnTypeLike { 1619 totalInput, totalOutput, utxoOpsForTxn, err = 1620 bav._connectLike(txn, txHash, blockHeight, verifySignatures) 1621 1622 } else if txn.TxnMeta.GetTxnType() == TxnTypeCreatorCoin { 1623 totalInput, totalOutput, utxoOpsForTxn, err = 1624 bav._connectCreatorCoin( 1625 txn, txHash, blockHeight, verifySignatures) 1626 1627 } else if txn.TxnMeta.GetTxnType() == TxnTypeCreatorCoinTransfer { 1628 totalInput, totalOutput, utxoOpsForTxn, err = 1629 bav._connectCreatorCoinTransfer( 1630 txn, txHash, blockHeight, verifySignatures) 1631 1632 } else if txn.TxnMeta.GetTxnType() == TxnTypeSwapIdentity { 1633 totalInput, totalOutput, utxoOpsForTxn, err = 1634 bav._connectSwapIdentity( 1635 txn, txHash, blockHeight, verifySignatures) 1636 1637 } else if txn.TxnMeta.GetTxnType() == TxnTypeCreateNFT { 1638 totalInput, totalOutput, utxoOpsForTxn, err = 1639 bav._connectCreateNFT( 1640 txn, txHash, blockHeight, verifySignatures) 1641 1642 } else if txn.TxnMeta.GetTxnType() == TxnTypeUpdateNFT { 1643 totalInput, totalOutput, utxoOpsForTxn, err = 1644 bav._connectUpdateNFT( 1645 txn, txHash, blockHeight, verifySignatures) 1646 1647 } else if txn.TxnMeta.GetTxnType() == TxnTypeAcceptNFTBid { 1648 totalInput, totalOutput, utxoOpsForTxn, err = 1649 bav._connectAcceptNFTBid( 1650 txn, txHash, blockHeight, verifySignatures) 1651 1652 } else if txn.TxnMeta.GetTxnType() == TxnTypeNFTBid { 1653 totalInput, totalOutput, utxoOpsForTxn, err = 1654 bav._connectNFTBid( 1655 txn, txHash, blockHeight, verifySignatures) 1656 1657 } else if txn.TxnMeta.GetTxnType() == TxnTypeNFTTransfer { 1658 totalInput, totalOutput, utxoOpsForTxn, err = 1659 bav._connectNFTTransfer( 1660 txn, txHash, blockHeight, verifySignatures) 1661 1662 } else if txn.TxnMeta.GetTxnType() == TxnTypeAcceptNFTTransfer { 1663 totalInput, totalOutput, utxoOpsForTxn, err = 1664 bav._connectAcceptNFTTransfer( 1665 txn, txHash, blockHeight, verifySignatures) 1666 1667 } else if txn.TxnMeta.GetTxnType() == TxnTypeBurnNFT { 1668 totalInput, totalOutput, utxoOpsForTxn, err = 1669 bav._connectBurnNFT( 1670 txn, txHash, blockHeight, verifySignatures) 1671 1672 } else if txn.TxnMeta.GetTxnType() == TxnTypeAuthorizeDerivedKey { 1673 totalInput, totalOutput, utxoOpsForTxn, err = 1674 bav._connectAuthorizeDerivedKey( 1675 txn, txHash, blockHeight, verifySignatures) 1676 1677 } else { 1678 err = fmt.Errorf("ConnectTransaction: Unimplemented txn type %v", txn.TxnMeta.GetTxnType().String()) 1679 } 1680 if err != nil { 1681 return nil, 0, 0, 0, errors.Wrapf(err, "ConnectTransaction: ") 1682 } 1683 1684 // Do some extra processing for non-block-reward transactions. Block reward transactions 1685 // will return zero for their fees. 1686 fees := uint64(0) 1687 if txn.TxnMeta.GetTxnType() != TxnTypeBlockReward { 1688 // If this isn't a block reward transaction, make sure the total input does 1689 // not exceed the total output. If it does, mark the block as invalid and 1690 // return an error. 1691 if totalInput < totalOutput { 1692 return nil, 0, 0, 0, RuleErrorTxnOutputExceedsInput 1693 } 1694 fees = totalInput - totalOutput 1695 } 1696 1697 // BitcoinExchange transactions have their own special fee that is computed as a function of how much 1698 // DeSo is being minted. They do not need to abide by the global minimum fee check, since if they had 1699 // enough fees to get mined into the Bitcoin blockchain itself then they're almost certainly not spam. 1700 // If the transaction size was set to 0, skip validating the fee is above the minimum. 1701 // If the current minimum network fee per kb is set to 0, that indicates we should not assess a minimum fee. 1702 if txn.TxnMeta.GetTxnType() != TxnTypeBitcoinExchange && txnSizeBytes != 0 && bav.GlobalParamsEntry.MinimumNetworkFeeNanosPerKB != 0 { 1703 // Make sure there isn't overflow in the fee. 1704 if fees != ((fees * 1000) / 1000) { 1705 return nil, 0, 0, 0, RuleErrorOverflowDetectedInFeeRateCalculation 1706 } 1707 // If the fee is less than the minimum network fee per KB, return an error. 1708 if (fees*1000)/uint64(txnSizeBytes) < bav.GlobalParamsEntry.MinimumNetworkFeeNanosPerKB { 1709 return nil, 0, 0, 0, RuleErrorTxnFeeBelowNetworkMinimum 1710 } 1711 } 1712 1713 return utxoOpsForTxn, totalInput, totalOutput, fees, nil 1714 } 1715 1716 func (bav *UtxoView) ConnectBlock( 1717 desoBlock *MsgDeSoBlock, txHashes []*BlockHash, verifySignatures bool, eventManager *EventManager) ( 1718 [][]*UtxoOperation, error) { 1719 1720 glog.V(1).Infof("ConnectBlock: Connecting block %v", desoBlock) 1721 1722 // Check that the block being connected references the current tip. ConnectBlock 1723 // can only add a block to the current tip. We do this to keep the API simple. 1724 if *desoBlock.Header.PrevBlockHash != *bav.TipHash { 1725 return nil, fmt.Errorf("ConnectBlock: Parent hash of block being connected does not match tip") 1726 } 1727 1728 blockHeader := desoBlock.Header 1729 // Loop through all the transactions and validate them using the view. Also 1730 // keep track of the total fees throughout. 1731 var totalFees uint64 1732 utxoOps := [][]*UtxoOperation{} 1733 for txIndex, txn := range desoBlock.Txns { 1734 txHash := txHashes[txIndex] 1735 1736 // ConnectTransaction validates all of the transactions in the block and 1737 // is responsible for verifying signatures. 1738 // 1739 // TODO: We currently don't check that the min transaction fee is satisfied when 1740 // connecting blocks. We skip this check because computing the transaction's size 1741 // would slow down block processing significantly. We should figure out a way to 1742 // enforce this check in the future, but for now the only attack vector is one in 1743 // which a miner is trying to spam the network, which should generally never happen. 1744 utxoOpsForTxn, totalInput, totalOutput, currentFees, err := bav.ConnectTransaction( 1745 txn, txHash, 0, uint32(blockHeader.Height), verifySignatures, false /*ignoreUtxos*/) 1746 _, _ = totalInput, totalOutput // A bit surprising we don't use these 1747 if err != nil { 1748 return nil, errors.Wrapf(err, "ConnectBlock: ") 1749 } 1750 1751 // Add the fees from this txn to the total fees. If any overflow occurs 1752 // mark the block as invalid and return a rule error. Note that block reward 1753 // txns should count as having zero fees. 1754 if totalFees > (math.MaxUint64 - currentFees) { 1755 return nil, RuleErrorTxnOutputWithInvalidAmount 1756 } 1757 totalFees += currentFees 1758 1759 // Add the utxo operations to our list for all the txns. 1760 utxoOps = append(utxoOps, utxoOpsForTxn) 1761 1762 // TODO: This should really be called at the end of _connectTransaction but it's 1763 // really annoying to change all the call signatures right now and we don't really 1764 // need it just yet. 1765 // 1766 // Call the event manager 1767 if eventManager != nil { 1768 eventManager.transactionConnected(&TransactionEvent{ 1769 Txn: txn, 1770 TxnHash: txHash, 1771 UtxoView: bav, 1772 UtxoOps: utxoOpsForTxn, 1773 }) 1774 } 1775 } 1776 1777 // We should now have computed totalFees. Use this to check that 1778 // the block reward's outputs are correct. 1779 // 1780 // Compute the sum of the outputs in the block reward. If an overflow 1781 // occurs mark the block as invalid and return a rule error. 1782 var blockRewardOutput uint64 1783 for _, bro := range desoBlock.Txns[0].TxOutputs { 1784 if bro.AmountNanos > MaxNanos || 1785 blockRewardOutput > (math.MaxUint64-bro.AmountNanos) { 1786 1787 return nil, RuleErrorBlockRewardOutputWithInvalidAmount 1788 } 1789 blockRewardOutput += bro.AmountNanos 1790 } 1791 // Verify that the block reward does not overflow when added to 1792 // the block's fees. 1793 blockReward := CalcBlockRewardNanos(uint32(blockHeader.Height)) 1794 if totalFees > MaxNanos || 1795 blockReward > (math.MaxUint64-totalFees) { 1796 1797 return nil, RuleErrorBlockRewardOverflow 1798 } 1799 maxBlockReward := blockReward + totalFees 1800 // If the outputs of the block reward txn exceed the max block reward 1801 // allowed then mark the block as invalid and return an error. 1802 if blockRewardOutput > maxBlockReward { 1803 glog.Errorf("ConnectBlock(RuleErrorBlockRewardExceedsMaxAllowed): "+ 1804 "blockRewardOutput %d exceeds maxBlockReward %d", blockRewardOutput, maxBlockReward) 1805 return nil, RuleErrorBlockRewardExceedsMaxAllowed 1806 } 1807 1808 // If we made it to the end and this block is valid, advance the tip 1809 // of the view to reflect that. 1810 blockHash, err := desoBlock.Header.Hash() 1811 if err != nil { 1812 return nil, fmt.Errorf("ConnectBlock: Problem computing block hash after validation") 1813 } 1814 bav.TipHash = blockHash 1815 1816 return utxoOps, nil 1817 } 1818 1819 // Preload tries to fetch all the relevant data needed to connect a block 1820 // in batches from Postgres. It marks many objects as "nil" in the respective 1821 // data structures and then fills in the objects it is able to retrieve from 1822 // the database. It's much faster to fetch data in bulk and cache "nil" values 1823 // then to query individual records when connecting every transaction. If something 1824 // is not preloaded the view falls back to individual queries. 1825 func (bav *UtxoView) Preload(desoBlock *MsgDeSoBlock) error { 1826 // We can only preload if we're using postgres 1827 if bav.Postgres == nil { 1828 return nil 1829 } 1830 1831 // One iteration for all the PKIDs 1832 // NOTE: Work in progress. Testing with follows for now. 1833 var publicKeys []*PublicKey 1834 for _, txn := range desoBlock.Txns { 1835 if txn.TxnMeta.GetTxnType() == TxnTypeFollow { 1836 txnMeta := txn.TxnMeta.(*FollowMetadata) 1837 publicKeys = append(publicKeys, NewPublicKey(txn.PublicKey)) 1838 publicKeys = append(publicKeys, NewPublicKey(txnMeta.FollowedPublicKey)) 1839 } else if txn.TxnMeta.GetTxnType() == TxnTypeCreatorCoin { 1840 txnMeta := txn.TxnMeta.(*CreatorCoinMetadataa) 1841 publicKeys = append(publicKeys, NewPublicKey(txn.PublicKey)) 1842 publicKeys = append(publicKeys, NewPublicKey(txnMeta.ProfilePublicKey)) 1843 } else if txn.TxnMeta.GetTxnType() == TxnTypeUpdateProfile { 1844 publicKeys = append(publicKeys, NewPublicKey(txn.PublicKey)) 1845 } 1846 } 1847 1848 if len(publicKeys) > 0 { 1849 for _, publicKey := range publicKeys { 1850 publicKeyBytes := publicKey.ToBytes() 1851 pkidEntry := &PKIDEntry{ 1852 PKID: PublicKeyToPKID(publicKeyBytes), 1853 PublicKey: publicKeyBytes, 1854 } 1855 1856 // Set pkid entries for all the public keys 1857 bav._setPKIDMappings(pkidEntry) 1858 1859 // Set nil profile entries 1860 bav.ProfilePKIDToProfileEntry[*pkidEntry.PKID] = nil 1861 } 1862 1863 // Set real entries for all the profiles that actually exist 1864 result := bav.Postgres.GetProfilesForPublicKeys(publicKeys) 1865 for _, profile := range result { 1866 bav.setProfileMappings(profile) 1867 } 1868 } 1869 1870 // One iteration for everything else 1871 // TODO: For some reason just fetching follows from the DB causes consensus issues?? 1872 var outputs []*PGTransactionOutput 1873 var follows []*PGFollow 1874 var balances []*PGCreatorCoinBalance 1875 var likes []*PGLike 1876 var posts []*PGPost 1877 var lowercaseUsernames []string 1878 1879 for _, txn := range desoBlock.Txns { 1880 // Preload all the inputs 1881 for _, txInput := range txn.TxInputs { 1882 output := &PGTransactionOutput{ 1883 OutputHash: &txInput.TxID, 1884 OutputIndex: txInput.Index, 1885 Spent: false, 1886 } 1887 outputs = append(outputs, output) 1888 } 1889 1890 if txn.TxnMeta.GetTxnType() == TxnTypeFollow { 1891 txnMeta := txn.TxnMeta.(*FollowMetadata) 1892 follow := &PGFollow{ 1893 FollowerPKID: bav.GetPKIDForPublicKey(txn.PublicKey).PKID.NewPKID(), 1894 FollowedPKID: bav.GetPKIDForPublicKey(txnMeta.FollowedPublicKey).PKID.NewPKID(), 1895 } 1896 follows = append(follows, follow) 1897 1898 // We cache the follow as not present and then fill them in later 1899 followerKey := MakeFollowKey(follow.FollowerPKID, follow.FollowedPKID) 1900 bav.FollowKeyToFollowEntry[followerKey] = nil 1901 } else if txn.TxnMeta.GetTxnType() == TxnTypeCreatorCoin { 1902 txnMeta := txn.TxnMeta.(*CreatorCoinMetadataa) 1903 1904 // Fetch the buyer's balance entry 1905 balance := &PGCreatorCoinBalance{ 1906 HolderPKID: bav.GetPKIDForPublicKey(txn.PublicKey).PKID.NewPKID(), 1907 CreatorPKID: bav.GetPKIDForPublicKey(txnMeta.ProfilePublicKey).PKID.NewPKID(), 1908 } 1909 balances = append(balances, balance) 1910 1911 // We cache the balances as not present and then fill them in later 1912 balanceEntryKey := MakeCreatorCoinBalanceKey(balance.HolderPKID, balance.CreatorPKID) 1913 bav.HODLerPKIDCreatorPKIDToBalanceEntry[balanceEntryKey] = nil 1914 1915 // Fetch the creator's balance entry if they're not buying their own coin 1916 if !reflect.DeepEqual(txn.PublicKey, txnMeta.ProfilePublicKey) { 1917 balance = &PGCreatorCoinBalance{ 1918 HolderPKID: bav.GetPKIDForPublicKey(txnMeta.ProfilePublicKey).PKID.NewPKID(), 1919 CreatorPKID: bav.GetPKIDForPublicKey(txnMeta.ProfilePublicKey).PKID.NewPKID(), 1920 } 1921 balances = append(balances, balance) 1922 1923 // We cache the balances as not present and then fill them in later 1924 balanceEntryKey = MakeCreatorCoinBalanceKey(balance.HolderPKID, balance.CreatorPKID) 1925 bav.HODLerPKIDCreatorPKIDToBalanceEntry[balanceEntryKey] = nil 1926 } 1927 } else if txn.TxnMeta.GetTxnType() == TxnTypeLike { 1928 txnMeta := txn.TxnMeta.(*LikeMetadata) 1929 like := &PGLike{ 1930 LikerPublicKey: txn.PublicKey, 1931 LikedPostHash: txnMeta.LikedPostHash.NewBlockHash(), 1932 } 1933 likes = append(likes, like) 1934 1935 // We cache the likes as not present and then fill them in later 1936 likeKey := MakeLikeKey(like.LikerPublicKey, *like.LikedPostHash) 1937 bav.LikeKeyToLikeEntry[likeKey] = nil 1938 1939 post := &PGPost{ 1940 PostHash: txnMeta.LikedPostHash.NewBlockHash(), 1941 } 1942 posts = append(posts, post) 1943 1944 // We cache the posts as not present and then fill them in later 1945 bav.PostHashToPostEntry[*post.PostHash] = nil 1946 } else if txn.TxnMeta.GetTxnType() == TxnTypeSubmitPost { 1947 txnMeta := txn.TxnMeta.(*SubmitPostMetadata) 1948 1949 var postHash *BlockHash 1950 if len(txnMeta.PostHashToModify) != 0 { 1951 postHash = NewBlockHash(txnMeta.PostHashToModify) 1952 } else { 1953 postHash = txn.Hash() 1954 } 1955 1956 posts = append(posts, &PGPost{ 1957 PostHash: postHash, 1958 }) 1959 1960 // We cache the posts as not present and then fill them in later 1961 bav.PostHashToPostEntry[*postHash] = nil 1962 1963 // TODO: Preload parent, grandparent, and reposted posts 1964 } else if txn.TxnMeta.GetTxnType() == TxnTypeUpdateProfile { 1965 txnMeta := txn.TxnMeta.(*UpdateProfileMetadata) 1966 if len(txnMeta.NewUsername) == 0 { 1967 continue 1968 } 1969 1970 lowercaseUsernames = append(lowercaseUsernames, strings.ToLower(string(txnMeta.NewUsername))) 1971 1972 // We cache the profiles as not present and then fill them in later 1973 bav.ProfileUsernameToProfileEntry[MakeUsernameMapKey(txnMeta.NewUsername)] = nil 1974 } 1975 } 1976 1977 if len(outputs) > 0 { 1978 //foundOutputs := bav.Postgres.GetOutputs(outputs) 1979 //for _, output := range foundOutputs { 1980 // err := bav._setUtxoMappings(output.NewUtxoEntry()) 1981 // if err != nil { 1982 // return err 1983 // } 1984 //} 1985 } 1986 1987 if len(follows) > 0 { 1988 foundFollows := bav.Postgres.GetFollows(follows) 1989 for _, follow := range foundFollows { 1990 followEntry := follow.NewFollowEntry() 1991 bav._setFollowEntryMappings(followEntry) 1992 } 1993 } 1994 1995 if len(balances) > 0 { 1996 foundBalances := bav.Postgres.GetCreatorCoinBalances(balances) 1997 for _, balance := range foundBalances { 1998 balanceEntry := balance.NewBalanceEntry() 1999 bav._setBalanceEntryMappings(balanceEntry) 2000 } 2001 } 2002 2003 if len(likes) > 0 { 2004 foundLikes := bav.Postgres.GetLikes(likes) 2005 for _, like := range foundLikes { 2006 likeEntry := like.NewLikeEntry() 2007 bav._setLikeEntryMappings(likeEntry) 2008 } 2009 } 2010 2011 if len(posts) > 0 { 2012 foundPosts := bav.Postgres.GetPosts(posts) 2013 for _, post := range foundPosts { 2014 bav.setPostMappings(post) 2015 } 2016 } 2017 2018 if len(lowercaseUsernames) > 0 { 2019 foundProfiles := bav.Postgres.GetProfilesForUsername(lowercaseUsernames) 2020 for _, profile := range foundProfiles { 2021 bav.setProfileMappings(profile) 2022 } 2023 } 2024 2025 return nil 2026 } 2027 2028 // GetUnspentUtxoEntrysForPublicKey returns the UtxoEntrys corresponding to the 2029 // passed-in public key that are currently unspent. It does this while factoring 2030 // in any transactions that have already been connected to it. This is useful, 2031 // as an example, when one whats to see what UtxoEntrys are available for spending 2032 // after factoring in (i.e. connecting) all of the transactions currently in the 2033 // mempool that are related to this public key. 2034 // 2035 // At a high level, this function allows one to get the utxos that are the union of: 2036 // - utxos in the db 2037 // - utxos in the view from previously-connected transactions 2038 func (bav *UtxoView) GetUnspentUtxoEntrysForPublicKey(pkBytes []byte) ([]*UtxoEntry, error) { 2039 // Fetch the relevant utxos for this public key from the db. We do this because 2040 // the db could contain utxos that are not currently loaded into the view. 2041 var utxoEntriesForPublicKey []*UtxoEntry 2042 var err error 2043 if bav.Postgres != nil { 2044 utxoEntriesForPublicKey = bav.Postgres.GetUtxoEntriesForPublicKey(pkBytes) 2045 } else { 2046 utxoEntriesForPublicKey, err = DbGetUtxosForPubKey(pkBytes, bav.Handle) 2047 } 2048 if err != nil { 2049 return nil, errors.Wrapf(err, "UtxoView.GetUnspentUtxoEntrysForPublicKey: Problem fetching "+ 2050 "utxos for public key %s", PkToString(pkBytes, bav.Params)) 2051 } 2052 2053 // Load all the utxos associated with this public key into 2054 // the view. This makes it so that the view can enumerate all of the utxoEntries 2055 // known for this public key. To put it another way, it allows the view to 2056 // contain the union of: 2057 // - utxos in the db 2058 // - utxos in the view from previously-connected transactions 2059 for _, utxoEntry := range utxoEntriesForPublicKey { 2060 bav.GetUtxoEntryForUtxoKey(utxoEntry.UtxoKey) 2061 } 2062 2063 // Now that all of the utxos for this key have been loaded, filter the 2064 // ones for this public key and return them. 2065 utxoEntriesToReturn := []*UtxoEntry{} 2066 for utxoKeyTmp, utxoEntry := range bav.UtxoKeyToUtxoEntry { 2067 // Make a copy of the iterator since it might change from underneath us 2068 // if we take its pointer. 2069 utxoKey := utxoKeyTmp 2070 utxoEntry.UtxoKey = &utxoKey 2071 if !utxoEntry.isSpent && reflect.DeepEqual(utxoEntry.PublicKey, pkBytes) { 2072 utxoEntriesToReturn = append(utxoEntriesToReturn, utxoEntry) 2073 } 2074 } 2075 2076 return utxoEntriesToReturn, nil 2077 } 2078 2079 func (bav *UtxoView) GetSpendableDeSoBalanceNanosForPublicKey(pkBytes []byte, 2080 tipHeight uint32) (_spendableBalance uint64, _err error) { 2081 // In order to get the spendable balance, we need to account for any immature block rewards. 2082 // We get these by starting at the chain tip and iterating backwards until we have collected 2083 // all of the immature block rewards for this public key. 2084 nextBlockHash := bav.TipHash 2085 numImmatureBlocks := uint32(bav.Params.BlockRewardMaturity / bav.Params.TimeBetweenBlocks) 2086 immatureBlockRewards := uint64(0) 2087 2088 if bav.Postgres != nil { 2089 // TODO: Filter out immature block rewards in postgres. UtxoType needs to be set correctly when importing blocks 2090 //outputs := bav.Postgres.GetBlockRewardsForPublicKey(NewPublicKey(pkBytes), tipHeight-numImmatureBlocks, tipHeight) 2091 //for _, output := range outputs { 2092 // immatureBlockRewards += output.AmountNanos 2093 //} 2094 } else { 2095 for ii := uint64(1); ii < uint64(numImmatureBlocks); ii++ { 2096 // Don't look up the genesis block since it isn't in the DB. 2097 if GenesisBlockHashHex == nextBlockHash.String() { 2098 break 2099 } 2100 2101 blockNode := GetHeightHashToNodeInfo(bav.Handle, tipHeight, nextBlockHash, false) 2102 if blockNode == nil { 2103 return uint64(0), fmt.Errorf( 2104 "GetSpendableDeSoBalanceNanosForPublicKey: Problem getting block for blockhash %s", 2105 nextBlockHash.String()) 2106 } 2107 blockRewardForPK, err := DbGetBlockRewardForPublicKeyBlockHash(bav.Handle, pkBytes, nextBlockHash) 2108 if err != nil { 2109 return uint64(0), errors.Wrapf( 2110 err, "GetSpendableDeSoBalanceNanosForPublicKey: Problem getting block reward for "+ 2111 "public key %s blockhash %s", PkToString(pkBytes, bav.Params), nextBlockHash.String()) 2112 } 2113 immatureBlockRewards += blockRewardForPK 2114 if blockNode.Parent != nil { 2115 nextBlockHash = blockNode.Parent.Hash 2116 } else { 2117 nextBlockHash = GenesisBlockHash 2118 } 2119 } 2120 } 2121 2122 balanceNanos, err := bav.GetDeSoBalanceNanosForPublicKey(pkBytes) 2123 if err != nil { 2124 return uint64(0), errors.Wrap(err, "GetSpendableUtxosForPublicKey: ") 2125 } 2126 // Sanity check that the balanceNanos >= immatureBlockRewards to prevent underflow. 2127 if balanceNanos < immatureBlockRewards { 2128 return uint64(0), fmt.Errorf( 2129 "GetSpendableUtxosForPublicKey: balance underflow (%d,%d)", balanceNanos, immatureBlockRewards) 2130 } 2131 return balanceNanos - immatureBlockRewards, nil 2132 }