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