github.com/deso-protocol/core@v1.2.9/lib/postgres.go (about) 1 package lib 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "github.com/dgraph-io/badger/v3" 7 "github.com/go-pg/pg/v10" 8 "github.com/go-pg/pg/v10/orm" 9 "github.com/golang/glog" 10 "strings" 11 ) 12 13 type Postgres struct { 14 db *pg.DB 15 } 16 17 func NewPostgres(db *pg.DB) *Postgres { 18 // Uncomment to print all queries. 19 //db.AddQueryHook(pgdebug.DebugHook{ 20 // Verbose: true, 21 //}) 22 23 return &Postgres{ 24 db: db, 25 } 26 } 27 28 // LogSelect is a helpful utility when developing or debugging queries. Simply call 29 // LogSelect(query) instead of query.Select() to get a log of the raw query. 30 func LogSelect(query *orm.Query) error { 31 selectQuery := orm.NewSelectQuery(query) 32 fmter := orm.NewFormatter().WithModel(selectQuery) 33 queryStr, _ := selectQuery.AppendQuery(fmter, nil) 34 glog.Info(string(queryStr)) 35 return query.Select() 36 } 37 38 const ( 39 MAIN_CHAIN = "main" 40 ) 41 42 // 43 // Tables 44 // 45 // The current schema is the sum of all the migrations in the migrate folder. Eventually we should 46 // export the current schema as new instances of the chain shouldn't be running every single migration. 47 // 48 // For information about the `pg:"..."` annotations, see: https://pg.uptrace.dev/models/ 49 // 50 // Common annotations include: 51 // - Custom primary key: `pg:",pk"` 52 // - Don't store 0 or false as NULL: `pg:",use_zero"` 53 // 54 // When we can, we use unique fields (or combinations of unique fields) as the primary keys on the models. 55 // This lets us use the WherePK() query while also minimizing columns and indicies on disk. 56 // 57 // Table names are defined so the relation is obvious even though go-pg can create them for us automatically. 58 // 59 // Column names are automatically created by go-pg. For example, a field named TipHash maps to tip_hash. 60 // 61 62 type PGChain struct { 63 tableName struct{} `pg:"pg_chains"` 64 65 Name string `pg:",pk"` 66 TipHash *BlockHash `pg:",type:bytea"` 67 } 68 69 // PGBlock represents BlockNode and MsgDeSoHeader 70 type PGBlock struct { 71 tableName struct{} `pg:"pg_blocks"` 72 73 // BlockNode and MsgDeSoHeader 74 Hash *BlockHash `pg:",pk,type:bytea"` 75 ParentHash *BlockHash `pg:",type:bytea"` 76 Height uint64 `pg:",use_zero"` 77 78 // BlockNode 79 DifficultyTarget *BlockHash `pg:",type:bytea"` 80 CumWork *BlockHash `pg:",type:bytea"` 81 Status BlockStatus `pg:",use_zero"` // TODO: Refactor 82 83 // MsgDeSoHeader 84 TxMerkleRoot *BlockHash `pg:",type:bytea"` 85 Version uint32 `pg:",use_zero"` 86 Timestamp uint64 `pg:",use_zero"` 87 Nonce uint64 `pg:",use_zero"` 88 ExtraNonce uint64 `pg:",use_zero"` 89 90 // Notifications 91 Notified bool `pg:",use_zero"` 92 } 93 94 // PGTransaction represents MsgDeSoTxn 95 type PGTransaction struct { 96 tableName struct{} `pg:"pg_transactions"` 97 98 Hash *BlockHash `pg:",pk,type:bytea"` 99 BlockHash *BlockHash `pg:",type:bytea"` 100 Type TxnType `pg:",use_zero"` 101 PublicKey []byte `pg:",type:bytea"` 102 ExtraData map[string][]byte 103 R *BlockHash `pg:",type:bytea"` 104 S *BlockHash `pg:",type:bytea"` 105 106 // Relationships 107 Outputs []*PGTransactionOutput `pg:"rel:has-many,join_fk:output_hash"` 108 MetadataBlockReward *PGMetadataBlockReward `pg:"rel:belongs-to,join_fk:transaction_hash"` 109 MetadataBitcoinExchange *PGMetadataBitcoinExchange `pg:"rel:belongs-to,join_fk:transaction_hash"` 110 MetadataPrivateMessage *PGMetadataPrivateMessage `pg:"rel:belongs-to,join_fk:transaction_hash"` 111 MetadataSubmitPost *PGMetadataSubmitPost `pg:"rel:belongs-to,join_fk:transaction_hash"` 112 MetadataUpdateExchangeRate *PGMetadataUpdateExchangeRate `pg:"rel:belongs-to,join_fk:transaction_hash"` 113 MetadataUpdateProfile *PGMetadataUpdateProfile `pg:"rel:belongs-to,join_fk:transaction_hash"` 114 MetadataFollow *PGMetadataFollow `pg:"rel:belongs-to,join_fk:transaction_hash"` 115 MetadataLike *PGMetadataLike `pg:"rel:belongs-to,join_fk:transaction_hash"` 116 MetadataCreatorCoin *PGMetadataCreatorCoin `pg:"rel:belongs-to,join_fk:transaction_hash"` 117 MetadataCreatorCoinTransfer *PGMetadataCreatorCoinTransfer `pg:"rel:belongs-to,join_fk:transaction_hash"` 118 MetadataSwapIdentity *PGMetadataSwapIdentity `pg:"rel:belongs-to,join_fk:transaction_hash"` 119 MetadataCreateNFT *PGMetadataCreateNFT `pg:"rel:belongs-to,join_fk:transaction_hash"` 120 MetadataUpdateNFT *PGMetadataUpdateNFT `pg:"rel:belongs-to,join_fk:transaction_hash"` 121 MetadataAcceptNFTBid *PGMetadataAcceptNFTBid `pg:"rel:belongs-to,join_fk:transaction_hash"` 122 MetadataNFTBid *PGMetadataNFTBid `pg:"rel:belongs-to,join_fk:transaction_hash"` 123 MetadataNFTTransfer *PGMetadataNFTTransfer `pg:"rel:belongs-to,join_fk:transaction_hash"` 124 MetadataAcceptNFTTransfer *PGMetadataAcceptNFTTransfer `pg:"rel:belongs-to,join_fk:transaction_hash"` 125 MetadataBurnNFT *PGMetadataBurnNFT `pg:"rel:belongs-to,join_fk:transaction_hash"` 126 MetadataDerivedKey *PGMetadataDerivedKey `pg:"rel:belongs-to,join_fk:transaction_hash"` 127 } 128 129 // PGTransactionOutput represents DeSoOutput, DeSoInput, and UtxoEntry 130 type PGTransactionOutput struct { 131 tableName struct{} `pg:"pg_transaction_outputs"` 132 133 OutputHash *BlockHash `pg:",pk"` 134 OutputIndex uint32 `pg:",pk,use_zero"` 135 OutputType UtxoType `pg:",use_zero"` 136 Height uint32 `pg:",use_zero"` 137 PublicKey []byte 138 AmountNanos uint64 `pg:",use_zero"` 139 Spent bool `pg:",use_zero"` 140 InputHash *BlockHash 141 InputIndex uint32 `pg:",pk,use_zero"` 142 } 143 144 func (utxo *PGTransactionOutput) NewUtxoEntry() *UtxoEntry { 145 return &UtxoEntry{ 146 PublicKey: utxo.PublicKey, 147 AmountNanos: utxo.AmountNanos, 148 BlockHeight: utxo.Height, 149 UtxoType: utxo.OutputType, 150 isSpent: utxo.Spent, 151 UtxoKey: &UtxoKey{ 152 TxID: *utxo.OutputHash, 153 Index: utxo.OutputIndex, 154 }, 155 } 156 } 157 158 // PGMetadataBlockReward represents BlockRewardMetadataa 159 type PGMetadataBlockReward struct { 160 tableName struct{} `pg:"pg_metadata_block_rewards"` 161 162 TransactionHash *BlockHash `pg:",pk,type:bytea"` 163 ExtraData []byte `pg:",type:bytea"` 164 } 165 166 // PGMetadataBitcoinExchange represents BitcoinExchangeMetadata 167 type PGMetadataBitcoinExchange struct { 168 tableName struct{} `pg:"pg_metadata_bitcoin_exchanges"` 169 170 TransactionHash *BlockHash `pg:",pk,type:bytea"` 171 BitcoinBlockHash *BlockHash `pg:",type:bytea"` 172 BitcoinMerkleRoot *BlockHash `pg:",type:bytea"` 173 // Not storing BitcoinTransaction *wire.MsgTx 174 // Not storing BitcoinMerkleProof []*merkletree.ProofPart 175 } 176 177 // PGMetadataPrivateMessage represents PrivateMessageMetadata 178 type PGMetadataPrivateMessage struct { 179 tableName struct{} `pg:"pg_metadata_private_messages"` 180 181 TransactionHash *BlockHash `pg:",pk,type:bytea"` 182 RecipientPublicKey []byte `pg:",type:bytea"` 183 EncryptedText []byte `pg:",type:bytea"` 184 TimestampNanos uint64 185 } 186 187 // PGMetadataSubmitPost represents SubmitPostMetadata 188 type PGMetadataSubmitPost struct { 189 tableName struct{} `pg:"pg_metadata_submit_posts"` 190 191 TransactionHash *BlockHash `pg:",pk,type:bytea"` 192 PostHashToModify *BlockHash `pg:",type:bytea"` 193 ParentStakeID *BlockHash `pg:",type:bytea"` 194 Body []byte `pg:",type:bytea"` 195 TimestampNanos uint64 196 IsHidden bool `pg:",use_zero"` 197 } 198 199 // PGMetadataUpdateExchangeRate represents UpdateBitcoinUSDExchangeRateMetadataa 200 type PGMetadataUpdateExchangeRate struct { 201 tableName struct{} `pg:"pg_metadata_update_exchange_rates"` 202 203 TransactionHash *BlockHash `pg:",pk,type:bytea"` 204 USDCentsPerBitcoin uint64 `pg:",use_zero"` 205 } 206 207 // PGMetadataUpdateProfile represents UpdateProfileMetadata 208 type PGMetadataUpdateProfile struct { 209 tableName struct{} `pg:"pg_metadata_update_profiles"` 210 211 TransactionHash *BlockHash `pg:",pk,type:bytea"` 212 ProfilePublicKey []byte `pg:",type:bytea"` 213 NewUsername []byte `pg:",type:bytea"` 214 NewDescription []byte `pg:",type:bytea"` 215 NewProfilePic []byte `pg:",type:bytea"` 216 NewCreatorBasisPoints uint64 `pg:",use_zero"` 217 } 218 219 // PGMetadataFollow represents FollowMetadata 220 type PGMetadataFollow struct { 221 tableName struct{} `pg:"pg_metadata_follows"` 222 223 TransactionHash *BlockHash `pg:",pk,type:bytea"` 224 FollowedPublicKey []byte `pg:",type:bytea"` 225 IsUnfollow bool `pg:",use_zero"` 226 } 227 228 // PGMetadataLike represents LikeMetadata 229 type PGMetadataLike struct { 230 tableName struct{} `pg:"pg_metadata_likes"` 231 232 TransactionHash *BlockHash `pg:",pk,type:bytea"` 233 LikedPostHash *BlockHash `pg:",type:bytea"` 234 IsUnlike bool `pg:",use_zero"` 235 } 236 237 // PGMetadataCreatorCoin represents CreatorCoinMetadataa 238 type PGMetadataCreatorCoin struct { 239 tableName struct{} `pg:"pg_metadata_creator_coins"` 240 241 TransactionHash *BlockHash `pg:",pk,type:bytea"` 242 ProfilePublicKey []byte `pg:",type:bytea"` 243 OperationType CreatorCoinOperationType `pg:",use_zero"` 244 DeSoToSellNanos uint64 `pg:",use_zero"` 245 CreatorCoinToSellNanos uint64 `pg:",use_zero"` 246 DeSoToAddNanos uint64 `pg:",use_zero"` 247 MinDeSoExpectedNanos uint64 `pg:",use_zero"` 248 MinCreatorCoinExpectedNanos uint64 `pg:",use_zero"` 249 } 250 251 // PGMetadataCreatorCoinTransfer represents CreatorCoinTransferMetadataa 252 type PGMetadataCreatorCoinTransfer struct { 253 tableName struct{} `pg:"pg_metadata_creator_coin_transfers"` 254 255 TransactionHash *BlockHash `pg:",pk,type:bytea"` 256 ProfilePublicKey []byte `pg:",type:bytea"` 257 CreatorCoinToTransferNanos uint64 `pg:",use_zero"` 258 ReceiverPublicKey []byte `pg:",type:bytea"` 259 } 260 261 // PGMetadataSwapIdentity represents SwapIdentityMetadataa 262 type PGMetadataSwapIdentity struct { 263 tableName struct{} `pg:"pg_metadata_swap_identities"` 264 265 TransactionHash *BlockHash `pg:",pk,type:bytea"` 266 FromPublicKey []byte `pg:",type:bytea"` 267 ToPublicKey []byte `pg:",type:bytea"` 268 } 269 270 // PGMetadataCreateNFT represents CreateNFTMetadata 271 type PGMetadataCreateNFT struct { 272 tableName struct{} `pg:"pg_metadata_create_nfts"` 273 274 TransactionHash *BlockHash `pg:",pk,type:bytea"` 275 NFTPostHash *BlockHash `pg:",type:bytea"` 276 NumCopies uint64 `pg:",use_zero"` 277 HasUnlockable bool `pg:",use_zero"` 278 IsForSale bool `pg:",use_zero"` 279 MinBidAmountNanos uint64 `pg:",use_zero"` 280 CreatorRoyaltyBasisPoints uint64 `pg:",use_zero"` 281 CoinRoyaltyBasisPoints uint64 `pg:",use_zero"` 282 } 283 284 // PGMetadataUpdateNFT represents UpdateNFTMetadata 285 type PGMetadataUpdateNFT struct { 286 tableName struct{} `pg:"pg_metadata_update_nfts"` 287 288 TransactionHash *BlockHash `pg:",pk,type:bytea"` 289 NFTPostHash *BlockHash `pg:",type:bytea"` 290 SerialNumber uint64 `pg:",use_zero"` 291 IsForSale bool `pg:",use_zero"` 292 MinBidAmountNanos uint64 `pg:",use_zero"` 293 } 294 295 // PGMetadataAcceptNFTBid represents AcceptNFTBidMetadata 296 type PGMetadataAcceptNFTBid struct { 297 tableName struct{} `pg:"pg_metadata_accept_nft_bids"` 298 299 TransactionHash *BlockHash `pg:",pk,type:bytea"` 300 NFTPostHash *BlockHash `pg:",type:bytea"` 301 SerialNumber uint64 `pg:",use_zero"` 302 BidderPKID *PKID `pg:",type:bytea"` 303 BidAmountNanos uint64 `pg:",use_zero"` 304 UnlockableText []byte `pg:",type:bytea"` 305 BidderInputs []*PGMetadataBidInput `pg:"rel:has-many,join_fk:transaction_hash"` 306 } 307 308 type PGMetadataBidInput struct { 309 tableName struct{} `pg:"pg_metadata_bid_inputs"` 310 311 TransactionHash *BlockHash `pg:",pk,type:bytea"` 312 InputHash *BlockHash `pg:",pk,type:bytea"` 313 InputIndex uint32 `pg:",pk,use_zero"` 314 } 315 316 // PGMetadataNFTBid represents NFTBidMetadata 317 type PGMetadataNFTBid struct { 318 tableName struct{} `pg:"pg_metadata_nft_bids"` 319 320 TransactionHash *BlockHash `pg:",pk,type:bytea"` 321 NFTPostHash *BlockHash `pg:",type:bytea"` 322 SerialNumber uint64 `pg:",use_zero"` 323 BidAmountNanos uint64 `pg:",use_zero"` 324 } 325 326 // PGMetadataNFTTransfer represents NFTTransferMetadata 327 type PGMetadataNFTTransfer struct { 328 tableName struct{} `pg:"pg_metadata_nft_transfer"` 329 330 TransactionHash *BlockHash `pg:",pk,type:bytea"` 331 NFTPostHash *BlockHash `pg:",pk,type:bytea"` 332 SerialNumber uint64 `pg:",use_zero"` 333 ReceiverPublicKey []byte `pg:",pk,type:bytea"` 334 UnlockableText []byte `pg:",type:bytea"` 335 } 336 337 // PGMetadataAcceptNFTTransfer represents AcceptNFTTransferMetadata 338 type PGMetadataAcceptNFTTransfer struct { 339 tableName struct{} `pg:"pg_metadata_accept_nft_transfer"` 340 341 TransactionHash *BlockHash `pg:",pk,type:bytea"` 342 NFTPostHash *BlockHash `pg:",pk,type:bytea"` 343 SerialNumber uint64 `pg:",use_zero"` 344 } 345 346 // PGMetadataBurnNFT represents BurnNFTMetadata 347 type PGMetadataBurnNFT struct { 348 tableName struct{} `pg:"pg_metadata_burn_nft"` 349 350 TransactionHash *BlockHash `pg:",pk,type:bytea"` 351 NFTPostHash *BlockHash `pg:",pk,type:bytea"` 352 SerialNumber uint64 `pg:",use_zero"` 353 } 354 355 // PGMetadataDerivedKey represents AuthorizeDerivedKeyMetadata 356 type PGMetadataDerivedKey struct { 357 tableName struct{} `pg:"pg_metadata_derived_keys"` 358 359 TransactionHash *BlockHash `pg:",pk,type:bytea"` 360 DerivedPublicKey PublicKey `pg:",type:bytea"` 361 ExpirationBlock uint64 `pg:",use_zero"` 362 OperationType AuthorizeDerivedKeyOperationType `pg:",use_zero"` 363 AccessSignature []byte `pg:",type:bytea"` 364 } 365 366 type PGNotification struct { 367 tableName struct{} `pg:"pg_notifications"` 368 369 TransactionHash *BlockHash `pg:",pk,type:bytea"` 370 Mined bool `pg:",use_zero"` 371 ToUser []byte `pg:",type:bytea"` 372 FromUser []byte `pg:",type:bytea"` 373 OtherUser []byte `pg:",type:bytea"` 374 Type NotificationType `pg:",use_zero"` 375 Amount uint64 `pg:",use_zero"` 376 PostHash *BlockHash `pg:",type:bytea"` 377 Timestamp uint64 `pg:",use_zero"` 378 } 379 380 type NotificationType uint8 381 382 const ( 383 NotificationUnknown NotificationType = iota 384 NotificationSendDESO 385 NotificationLike 386 NotificationFollow 387 NotificationCoinPurchase 388 NotificationCoinTransfer 389 NotificationCoinDiamond 390 NotificationPostMention 391 NotificationPostReply 392 NotificationPostRepost 393 NotificationDESODiamond 394 ) 395 396 type PGProfile struct { 397 tableName struct{} `pg:"pg_profiles"` 398 399 PKID *PKID `pg:",pk,type:bytea"` 400 PublicKey *PublicKey `pg:",type:bytea"` 401 Username string 402 Description string 403 ProfilePic []byte 404 CreatorBasisPoints uint64 405 DeSoLockedNanos uint64 406 NumberOfHolders uint64 407 CoinsInCirculationNanos uint64 408 CoinWatermarkNanos uint64 409 } 410 411 func (profile *PGProfile) Empty() bool { 412 return profile.Username == "" 413 } 414 415 type PGPost struct { 416 tableName struct{} `pg:"pg_posts"` 417 418 PostHash *BlockHash `pg:",pk,type:bytea"` 419 PosterPublicKey []byte 420 ParentPostHash *BlockHash `pg:",type:bytea"` 421 Body string 422 RepostedPostHash *BlockHash `pg:",type:bytea"` 423 QuotedRepost bool `pg:",use_zero"` 424 Timestamp uint64 `pg:",use_zero"` 425 Hidden bool `pg:",use_zero"` 426 LikeCount uint64 `pg:",use_zero"` 427 RepostCount uint64 `pg:",use_zero"` 428 QuoteRepostCount uint64 `pg:",use_zero"` 429 DiamondCount uint64 `pg:",use_zero"` 430 CommentCount uint64 `pg:",use_zero"` 431 Pinned bool `pg:",use_zero"` 432 NFT bool `pg:",use_zero"` 433 NumNFTCopies uint64 `pg:",use_zero"` 434 NumNFTCopiesForSale uint64 `pg:",use_zero"` 435 NumNFTCopiesBurned uint64 `pg:",use_zero"` 436 Unlockable bool `pg:",use_zero"` 437 CreatorRoyaltyBasisPoints uint64 `pg:",use_zero"` 438 CoinRoyaltyBasisPoints uint64 `pg:",use_zero"` 439 ExtraData map[string][]byte 440 } 441 442 func (post *PGPost) NewPostEntry() *PostEntry { 443 postEntry := &PostEntry{ 444 PostHash: post.PostHash, 445 PosterPublicKey: post.PosterPublicKey, 446 Body: []byte(post.Body), 447 RepostedPostHash: post.RepostedPostHash, 448 IsQuotedRepost: post.QuotedRepost, 449 TimestampNanos: post.Timestamp, 450 IsHidden: post.Hidden, 451 LikeCount: post.LikeCount, 452 RepostCount: post.RepostCount, 453 QuoteRepostCount: post.QuoteRepostCount, 454 DiamondCount: post.DiamondCount, 455 CommentCount: post.CommentCount, 456 IsPinned: post.Pinned, 457 IsNFT: post.NFT, 458 NumNFTCopies: post.NumNFTCopies, 459 NumNFTCopiesForSale: post.NumNFTCopiesForSale, 460 NumNFTCopiesBurned: post.NumNFTCopiesBurned, 461 HasUnlockable: post.Unlockable, 462 NFTRoyaltyToCoinBasisPoints: post.CoinRoyaltyBasisPoints, 463 NFTRoyaltyToCreatorBasisPoints: post.CreatorRoyaltyBasisPoints, 464 PostExtraData: post.ExtraData, 465 } 466 467 if post.ParentPostHash != nil { 468 postEntry.ParentStakeID = post.ParentPostHash.ToBytes() 469 } 470 471 return postEntry 472 } 473 474 // HasMedia is inefficient and needs to be moved to a column in the Posts table 475 func (post *PGPost) HasMedia() bool { 476 bodyJSONObj := DeSoBodySchema{} 477 err := json.Unmarshal([]byte(post.Body), &bodyJSONObj) 478 // Return true if body json can be parsed and ImageUrls or VideoURLs is not nil/non-empty or EmbedVideoUrl is not nil/non-empty 479 return (err == nil && len(bodyJSONObj.ImageURLs) > 0 || len(bodyJSONObj.VideoURLs) > 0) || len(post.ExtraData["EmbedVideoURL"]) > 0 480 } 481 482 type PGLike struct { 483 tableName struct{} `pg:"pg_likes"` 484 485 LikerPublicKey []byte `pg:",pk,type:bytea"` 486 LikedPostHash *BlockHash `pg:",pk,type:bytea"` 487 } 488 489 func (like *PGLike) NewLikeEntry() *LikeEntry { 490 return &LikeEntry{ 491 LikerPubKey: like.LikerPublicKey, 492 LikedPostHash: like.LikedPostHash, 493 } 494 } 495 496 type PGFollow struct { 497 tableName struct{} `pg:"pg_follows"` 498 499 FollowerPKID *PKID `pg:",pk,type:bytea"` 500 FollowedPKID *PKID `pg:",pk,type:bytea"` 501 } 502 503 func (follow *PGFollow) NewFollowEntry() *FollowEntry { 504 return &FollowEntry{ 505 FollowerPKID: follow.FollowerPKID, 506 FollowedPKID: follow.FollowedPKID, 507 } 508 } 509 510 type PGDiamond struct { 511 tableName struct{} `pg:"pg_diamonds"` 512 513 SenderPKID *PKID `pg:",pk,type:bytea"` 514 ReceiverPKID *PKID `pg:",pk,type:bytea"` 515 DiamondPostHash *BlockHash `pg:",pk,type:bytea"` 516 DiamondLevel uint8 517 } 518 519 // TODO: This doesn't need to be a table. Just add sender to PGMetadataPrivateMessage? 520 // The only reason we might not want to do this is if we end up pruning Metadata tables. 521 type PGMessage struct { 522 tableName struct{} `pg:"pg_messages"` 523 524 MessageHash *BlockHash `pg:",pk,type:bytea"` 525 SenderPublicKey []byte 526 RecipientPublicKey []byte 527 EncryptedText []byte 528 TimestampNanos uint64 529 // TODO: Version 530 531 // Used to track deletions in the UtxoView 532 isDeleted bool 533 } 534 535 type PGCreatorCoinBalance struct { 536 tableName struct{} `pg:"pg_creator_coin_balances"` 537 538 HolderPKID *PKID `pg:",pk,type:bytea"` 539 CreatorPKID *PKID `pg:",pk,type:bytea"` 540 BalanceNanos uint64 541 HasPurchased bool 542 } 543 544 func (balance *PGCreatorCoinBalance) NewBalanceEntry() *BalanceEntry { 545 return &BalanceEntry{ 546 HODLerPKID: balance.HolderPKID, 547 CreatorPKID: balance.CreatorPKID, 548 BalanceNanos: balance.BalanceNanos, 549 HasPurchased: balance.HasPurchased, 550 } 551 } 552 553 // PGBalance represents PublicKeyToDeSoBalanceNanos 554 type PGBalance struct { 555 tableName struct{} `pg:"pg_balances"` 556 557 PublicKey *PublicKey `pg:",pk,type:bytea"` 558 BalanceNanos uint64 `pg:",use_zero"` 559 } 560 561 // PGGlobalParams represents GlobalParamsEntry 562 type PGGlobalParams struct { 563 tableName struct{} `pg:"pg_global_params"` 564 565 ID uint64 566 567 USDCentsPerBitcoin uint64 `pg:",use_zero"` 568 CreateProfileFeeNanos uint64 `pg:",use_zero"` 569 CreateNFTFeeNanos uint64 `pg:",use_zero"` 570 MaxCopiesPerNFT uint64 `pg:",use_zero"` 571 MinNetworkFeeNanosPerKB uint64 `pg:",use_zero"` 572 } 573 574 type PGRepost struct { 575 tableName struct{} `pg:"pg_reposts"` 576 577 ReposterPublickey *PublicKey `pg:",pk,type:bytea"` 578 RepostedPostHash *BlockHash `pg:",pk,type:bytea"` 579 RepostPostHash *BlockHash `pg:",type:bytea"` 580 581 // Whether or not this entry is deleted in the view. 582 isDeleted bool 583 } 584 585 // PGForbiddenKey represents ForbiddenPubKeyEntry 586 type PGForbiddenKey struct { 587 tableName struct{} `pg:"pg_forbidden_keys"` 588 589 PublicKey *PublicKey `pg:",pk,type:bytea"` 590 } 591 592 // PGNFT represents NFTEntry 593 type PGNFT struct { 594 tableName struct{} `pg:"pg_nfts"` 595 596 NFTPostHash *BlockHash `pg:",pk,type:bytea"` 597 SerialNumber uint64 `pg:",pk"` 598 599 // This is needed to decrypt unlockable text. 600 LastOwnerPKID *PKID `pg:",type:bytea"` 601 OwnerPKID *PKID `pg:",type:bytea"` 602 ForSale bool `pg:",use_zero"` 603 MinBidAmountNanos uint64 `pg:",use_zero"` 604 UnlockableText string 605 LastAcceptedBidAmountNanos uint64 `pg:",use_zero"` 606 IsPending bool `pg:",use_zero"` 607 } 608 609 func (nft *PGNFT) NewNFTEntry() *NFTEntry { 610 return &NFTEntry{ 611 LastOwnerPKID: nft.LastOwnerPKID, 612 OwnerPKID: nft.OwnerPKID, 613 NFTPostHash: nft.NFTPostHash, 614 SerialNumber: nft.SerialNumber, 615 IsForSale: nft.ForSale, 616 MinBidAmountNanos: nft.MinBidAmountNanos, 617 UnlockableText: []byte(nft.UnlockableText), 618 LastAcceptedBidAmountNanos: nft.LastAcceptedBidAmountNanos, 619 IsPending: nft.IsPending, 620 } 621 } 622 623 // PGNFTBid represents NFTBidEntry 624 type PGNFTBid struct { 625 tableName struct{} `pg:"pg_nft_bids"` 626 627 BidderPKID *PKID `pg:",pk,type:bytea"` 628 NFTPostHash *BlockHash `pg:",pk,type:bytea"` 629 SerialNumber uint64 `pg:",pk,use_zero"` 630 BidAmountNanos uint64 `pg:",use_zero"` 631 Accepted bool `pg:",use_zero"` 632 } 633 634 func (bid *PGNFTBid) NewNFTBidEntry() *NFTBidEntry { 635 return &NFTBidEntry{ 636 BidderPKID: bid.BidderPKID, 637 NFTPostHash: bid.NFTPostHash, 638 SerialNumber: bid.SerialNumber, 639 BidAmountNanos: bid.BidAmountNanos, 640 } 641 } 642 643 // PGDerivedKey represents DerivedKeyEntry 644 type PGDerivedKey struct { 645 tableName struct{} `pg:"pg_derived_keys"` 646 647 OwnerPublicKey PublicKey `pg:",pk,type:bytea"` 648 DerivedPublicKey PublicKey `pg:",pk,type:bytea"` 649 ExpirationBlock uint64 `pg:",use_zero"` 650 OperationType AuthorizeDerivedKeyOperationType `pg:",use_zero"` 651 } 652 653 func (key *PGDerivedKey) NewDerivedKeyEntry() *DerivedKeyEntry { 654 return &DerivedKeyEntry{ 655 OwnerPublicKey: key.OwnerPublicKey, 656 DerivedPublicKey: key.DerivedPublicKey, 657 ExpirationBlock: key.ExpirationBlock, 658 OperationType: key.OperationType, 659 } 660 } 661 662 // 663 // Blockchain and Transactions 664 // 665 666 func (postgres *Postgres) UpsertBlock(blockNode *BlockNode) error { 667 return postgres.db.RunInTransaction(postgres.db.Context(), func(tx *pg.Tx) error { 668 return postgres.UpsertBlockTx(tx, blockNode) 669 }) 670 } 671 672 func (postgres *Postgres) UpsertBlockTx(tx *pg.Tx, blockNode *BlockNode) error { 673 block := &PGBlock{ 674 Hash: blockNode.Hash, 675 Height: blockNode.Header.Height, 676 677 DifficultyTarget: blockNode.DifficultyTarget, 678 CumWork: BigintToHash(blockNode.CumWork), 679 Status: blockNode.Status, 680 681 TxMerkleRoot: blockNode.Header.TransactionMerkleRoot, 682 Version: blockNode.Header.Version, 683 Timestamp: blockNode.Header.TstampSecs, 684 Nonce: blockNode.Header.Nonce, 685 ExtraNonce: blockNode.Header.ExtraNonce, 686 } 687 688 // The genesis block has a nil parent 689 if blockNode.Parent != nil { 690 block.ParentHash = blockNode.Parent.Hash 691 } 692 693 _, err := tx.Model(block).WherePK().OnConflict("(hash) DO UPDATE").Insert() 694 return err 695 } 696 697 // GetBlockIndex gets all the PGBlocks and creates a map of BlockHash to BlockNode as needed by blockchain.go 698 func (postgres *Postgres) GetBlockIndex() (map[BlockHash]*BlockNode, error) { 699 var blocks []PGBlock 700 err := postgres.db.Model(&blocks).Select() 701 if err != nil { 702 return nil, err 703 } 704 705 blockMap := make(map[BlockHash]*BlockNode) 706 for _, block := range blocks { 707 blockMap[*block.Hash] = &BlockNode{ 708 Hash: block.Hash, 709 Height: uint32(block.Height), 710 DifficultyTarget: block.DifficultyTarget, 711 CumWork: HashToBigint(block.CumWork), 712 Header: &MsgDeSoHeader{ 713 Version: block.Version, 714 PrevBlockHash: block.ParentHash, 715 TransactionMerkleRoot: block.TxMerkleRoot, 716 TstampSecs: block.Timestamp, 717 Height: block.Height, 718 Nonce: block.Nonce, 719 ExtraNonce: block.ExtraNonce, 720 }, 721 Status: block.Status, 722 } 723 } 724 725 // Setup parent pointers 726 for _, blockNode := range blockMap { 727 // Genesis block has nil parent 728 parentHash := blockNode.Header.PrevBlockHash 729 if parentHash != nil { 730 blockNode.Parent = blockMap[*parentHash] 731 } 732 } 733 734 return blockMap, nil 735 } 736 737 // GetChain returns the current chain by name. Postgres only supports MAIN_CHAIN for now but will eventually 738 // support multiple chains. A chain is defined by its Name and TipHash. 739 func (postgres *Postgres) GetChain(name string) *PGChain { 740 chain := &PGChain{ 741 Name: name, 742 } 743 744 err := postgres.db.Model(chain).First() 745 if err != nil { 746 return nil 747 } 748 749 return chain 750 } 751 752 func (postgres *Postgres) UpsertChain(name string, tipHash *BlockHash) error { 753 return postgres.db.RunInTransaction(postgres.db.Context(), func(tx *pg.Tx) error { 754 return postgres.UpsertChainTx(tx, name, tipHash) 755 }) 756 } 757 758 func (postgres *Postgres) UpsertChainTx(tx *pg.Tx, name string, tipHash *BlockHash) error { 759 bestChain := &PGChain{ 760 TipHash: tipHash, 761 Name: name, 762 } 763 764 _, err := tx.Model(bestChain).WherePK().OnConflict("(name) DO UPDATE").Insert() 765 return err 766 } 767 768 // InsertTransactionsTx inserts all the transactions from a block in a bulk query 769 func (postgres *Postgres) InsertTransactionsTx(tx *pg.Tx, desoTxns []*MsgDeSoTxn, blockNode *BlockNode) error { 770 var transactions []*PGTransaction 771 var transactionOutputs []*PGTransactionOutput 772 var transactionInputs []*PGTransactionOutput 773 774 var metadataBlockRewards []*PGMetadataBlockReward 775 var metadataBitcoinExchanges []*PGMetadataBitcoinExchange 776 var metadataPrivateMessages []*PGMetadataPrivateMessage 777 var metadataSubmitPosts []*PGMetadataSubmitPost 778 var metadataUpdateProfiles []*PGMetadataUpdateProfile 779 var metadataExchangeRates []*PGMetadataUpdateExchangeRate 780 var metadataFollows []*PGMetadataFollow 781 var metadataLikes []*PGMetadataLike 782 var metadataCreatorCoins []*PGMetadataCreatorCoin 783 var metadataSwapIdentities []*PGMetadataSwapIdentity 784 var metadataCreatorCoinTransfers []*PGMetadataCreatorCoinTransfer 785 var metadataCreateNFTs []*PGMetadataCreateNFT 786 var metadataUpdateNFTs []*PGMetadataUpdateNFT 787 var metadataAcceptNFTBids []*PGMetadataAcceptNFTBid 788 var metadataBidInputs []*PGMetadataBidInput 789 var metadataNFTBids []*PGMetadataNFTBid 790 var metadataNFTTransfer []*PGMetadataNFTTransfer 791 var metadataAcceptNFTTransfer []*PGMetadataAcceptNFTTransfer 792 var metadataBurnNFT []*PGMetadataBurnNFT 793 var metadataDerivedKey []*PGMetadataDerivedKey 794 795 blockHash := blockNode.Hash 796 797 // Iterate over all the transactions and build the arrays of data to insert 798 for _, txn := range desoTxns { 799 txnHash := txn.Hash() 800 transaction := &PGTransaction{ 801 Hash: txnHash, 802 BlockHash: blockHash, 803 Type: txn.TxnMeta.GetTxnType(), 804 PublicKey: txn.PublicKey, 805 ExtraData: txn.ExtraData, 806 } 807 808 if txn.Signature != nil { 809 transaction.R = BigintToHash(txn.Signature.R) 810 transaction.S = BigintToHash(txn.Signature.S) 811 } 812 813 transactions = append(transactions, transaction) 814 815 for ii, input := range txn.TxInputs { 816 transactionInputs = append(transactionInputs, &PGTransactionOutput{ 817 OutputHash: &input.TxID, 818 OutputIndex: input.Index, 819 Height: blockNode.Height, 820 InputHash: txnHash, 821 InputIndex: uint32(ii), 822 Spent: true, 823 }) 824 } 825 826 for ii, output := range txn.TxOutputs { 827 transactionOutputs = append(transactionOutputs, &PGTransactionOutput{ 828 OutputHash: txnHash, 829 OutputIndex: uint32(ii), 830 OutputType: 0, // TODO 831 PublicKey: output.PublicKey, 832 AmountNanos: output.AmountNanos, 833 }) 834 } 835 836 if txn.TxnMeta.GetTxnType() == TxnTypeBlockReward { 837 txMeta := txn.TxnMeta.(*BlockRewardMetadataa) 838 metadataBlockRewards = append(metadataBlockRewards, &PGMetadataBlockReward{ 839 TransactionHash: txnHash, 840 ExtraData: txMeta.ExtraData, 841 }) 842 } else if txn.TxnMeta.GetTxnType() == TxnTypeBasicTransfer { 843 // No extra metadata needed 844 } else if txn.TxnMeta.GetTxnType() == TxnTypeBitcoinExchange { 845 txMeta := txn.TxnMeta.(*BitcoinExchangeMetadata) 846 metadataBitcoinExchanges = append(metadataBitcoinExchanges, &PGMetadataBitcoinExchange{ 847 TransactionHash: txnHash, 848 BitcoinBlockHash: txMeta.BitcoinBlockHash, 849 BitcoinMerkleRoot: txMeta.BitcoinMerkleRoot, 850 }) 851 } else if txn.TxnMeta.GetTxnType() == TxnTypePrivateMessage { 852 txMeta := txn.TxnMeta.(*PrivateMessageMetadata) 853 metadataPrivateMessages = append(metadataPrivateMessages, &PGMetadataPrivateMessage{ 854 TransactionHash: txnHash, 855 RecipientPublicKey: txMeta.RecipientPublicKey, 856 EncryptedText: txMeta.EncryptedText, 857 TimestampNanos: txMeta.TimestampNanos, 858 }) 859 } else if txn.TxnMeta.GetTxnType() == TxnTypeSubmitPost { 860 txMeta := txn.TxnMeta.(*SubmitPostMetadata) 861 862 postHashToModify := &BlockHash{} 863 parentStakeId := &BlockHash{} 864 copy(postHashToModify[:], txMeta.PostHashToModify) 865 copy(parentStakeId[:], txMeta.ParentStakeID) 866 867 metadataSubmitPosts = append(metadataSubmitPosts, &PGMetadataSubmitPost{ 868 TransactionHash: txnHash, 869 PostHashToModify: postHashToModify, 870 ParentStakeID: parentStakeId, 871 Body: txMeta.Body, 872 TimestampNanos: txMeta.TimestampNanos, 873 IsHidden: txMeta.IsHidden, 874 }) 875 } else if txn.TxnMeta.GetTxnType() == TxnTypeUpdateProfile { 876 txMeta := txn.TxnMeta.(*UpdateProfileMetadata) 877 metadataUpdateProfiles = append(metadataUpdateProfiles, &PGMetadataUpdateProfile{ 878 TransactionHash: txnHash, 879 ProfilePublicKey: txMeta.ProfilePublicKey, 880 NewUsername: txMeta.NewUsername, 881 NewProfilePic: txMeta.NewProfilePic, 882 NewCreatorBasisPoints: txMeta.NewCreatorBasisPoints, 883 }) 884 } else if txn.TxnMeta.GetTxnType() == TxnTypeUpdateBitcoinUSDExchangeRate { 885 txMeta := txn.TxnMeta.(*UpdateBitcoinUSDExchangeRateMetadataa) 886 metadataExchangeRates = append(metadataExchangeRates, &PGMetadataUpdateExchangeRate{ 887 TransactionHash: txnHash, 888 USDCentsPerBitcoin: txMeta.USDCentsPerBitcoin, 889 }) 890 } else if txn.TxnMeta.GetTxnType() == TxnTypeFollow { 891 txMeta := txn.TxnMeta.(*FollowMetadata) 892 metadataFollows = append(metadataFollows, &PGMetadataFollow{ 893 TransactionHash: txnHash, 894 FollowedPublicKey: txMeta.FollowedPublicKey, 895 IsUnfollow: txMeta.IsUnfollow, 896 }) 897 } else if txn.TxnMeta.GetTxnType() == TxnTypeLike { 898 txMeta := txn.TxnMeta.(*LikeMetadata) 899 metadataLikes = append(metadataLikes, &PGMetadataLike{ 900 TransactionHash: txnHash, 901 LikedPostHash: txMeta.LikedPostHash, 902 IsUnlike: txMeta.IsUnlike, 903 }) 904 } else if txn.TxnMeta.GetTxnType() == TxnTypeCreatorCoin { 905 txMeta := txn.TxnMeta.(*CreatorCoinMetadataa) 906 metadataCreatorCoins = append(metadataCreatorCoins, &PGMetadataCreatorCoin{ 907 TransactionHash: txnHash, 908 ProfilePublicKey: txMeta.ProfilePublicKey, 909 OperationType: txMeta.OperationType, 910 DeSoToSellNanos: txMeta.DeSoToSellNanos, 911 CreatorCoinToSellNanos: txMeta.CreatorCoinToSellNanos, 912 DeSoToAddNanos: txMeta.DeSoToAddNanos, 913 MinDeSoExpectedNanos: txMeta.MinDeSoExpectedNanos, 914 MinCreatorCoinExpectedNanos: txMeta.MinCreatorCoinExpectedNanos, 915 }) 916 } else if txn.TxnMeta.GetTxnType() == TxnTypeSwapIdentity { 917 txMeta := txn.TxnMeta.(*SwapIdentityMetadataa) 918 metadataSwapIdentities = append(metadataSwapIdentities, &PGMetadataSwapIdentity{ 919 TransactionHash: txnHash, 920 FromPublicKey: txMeta.FromPublicKey, 921 ToPublicKey: txMeta.ToPublicKey, 922 }) 923 } else if txn.TxnMeta.GetTxnType() == TxnTypeUpdateGlobalParams { 924 // No extra metadata needed, it's all in ExtraData 925 } else if txn.TxnMeta.GetTxnType() == TxnTypeCreatorCoinTransfer { 926 txMeta := txn.TxnMeta.(*CreatorCoinTransferMetadataa) 927 metadataCreatorCoinTransfers = append(metadataCreatorCoinTransfers, &PGMetadataCreatorCoinTransfer{ 928 TransactionHash: txnHash, 929 ProfilePublicKey: txMeta.ProfilePublicKey, 930 CreatorCoinToTransferNanos: txMeta.CreatorCoinToTransferNanos, 931 ReceiverPublicKey: txMeta.ReceiverPublicKey, 932 }) 933 } else if txn.TxnMeta.GetTxnType() == TxnTypeCreateNFT { 934 txMeta := txn.TxnMeta.(*CreateNFTMetadata) 935 metadataCreateNFTs = append(metadataCreateNFTs, &PGMetadataCreateNFT{ 936 TransactionHash: txnHash, 937 NFTPostHash: txMeta.NFTPostHash, 938 NumCopies: txMeta.NumCopies, 939 HasUnlockable: txMeta.HasUnlockable, 940 IsForSale: txMeta.IsForSale, 941 MinBidAmountNanos: txMeta.MinBidAmountNanos, 942 CreatorRoyaltyBasisPoints: txMeta.NFTRoyaltyToCreatorBasisPoints, 943 CoinRoyaltyBasisPoints: txMeta.NFTRoyaltyToCoinBasisPoints, 944 }) 945 } else if txn.TxnMeta.GetTxnType() == TxnTypeUpdateNFT { 946 txMeta := txn.TxnMeta.(*UpdateNFTMetadata) 947 metadataUpdateNFTs = append(metadataUpdateNFTs, &PGMetadataUpdateNFT{ 948 TransactionHash: txnHash, 949 NFTPostHash: txMeta.NFTPostHash, 950 SerialNumber: txMeta.SerialNumber, 951 IsForSale: txMeta.IsForSale, 952 MinBidAmountNanos: txMeta.MinBidAmountNanos, 953 }) 954 } else if txn.TxnMeta.GetTxnType() == TxnTypeAcceptNFTBid { 955 txMeta := txn.TxnMeta.(*AcceptNFTBidMetadata) 956 metadataAcceptNFTBids = append(metadataAcceptNFTBids, &PGMetadataAcceptNFTBid{ 957 TransactionHash: txnHash, 958 NFTPostHash: txMeta.NFTPostHash, 959 SerialNumber: txMeta.SerialNumber, 960 BidderPKID: txMeta.BidderPKID, 961 BidAmountNanos: txMeta.BidAmountNanos, 962 UnlockableText: txMeta.UnlockableText, 963 }) 964 965 for _, input := range txMeta.BidderInputs { 966 metadataBidInputs = append(metadataBidInputs, &PGMetadataBidInput{ 967 TransactionHash: txnHash, 968 InputHash: input.TxID.NewBlockHash(), 969 InputIndex: input.Index, 970 }) 971 } 972 } else if txn.TxnMeta.GetTxnType() == TxnTypeNFTBid { 973 txMeta := txn.TxnMeta.(*NFTBidMetadata) 974 metadataNFTBids = append(metadataNFTBids, &PGMetadataNFTBid{ 975 TransactionHash: txnHash, 976 NFTPostHash: txMeta.NFTPostHash, 977 SerialNumber: txMeta.SerialNumber, 978 BidAmountNanos: txMeta.BidAmountNanos, 979 }) 980 } else if txn.TxnMeta.GetTxnType() == TxnTypeNFTTransfer { 981 txMeta := txn.TxnMeta.(*NFTTransferMetadata) 982 metadataNFTTransfer = append(metadataNFTTransfer, &PGMetadataNFTTransfer{ 983 TransactionHash: txnHash, 984 NFTPostHash: txMeta.NFTPostHash, 985 SerialNumber: txMeta.SerialNumber, 986 ReceiverPublicKey: txMeta.ReceiverPublicKey, 987 UnlockableText: txMeta.UnlockableText, 988 }) 989 } else if txn.TxnMeta.GetTxnType() == TxnTypeAcceptNFTTransfer { 990 txMeta := txn.TxnMeta.(*AcceptNFTTransferMetadata) 991 metadataAcceptNFTTransfer = append(metadataAcceptNFTTransfer, &PGMetadataAcceptNFTTransfer{ 992 TransactionHash: txnHash, 993 NFTPostHash: txMeta.NFTPostHash, 994 SerialNumber: txMeta.SerialNumber, 995 }) 996 } else if txn.TxnMeta.GetTxnType() == TxnTypeBurnNFT { 997 txMeta := txn.TxnMeta.(*BurnNFTMetadata) 998 metadataBurnNFT = append(metadataBurnNFT, &PGMetadataBurnNFT{ 999 TransactionHash: txnHash, 1000 NFTPostHash: txMeta.NFTPostHash, 1001 SerialNumber: txMeta.SerialNumber, 1002 }) 1003 } else if txn.TxnMeta.GetTxnType() == TxnTypeAuthorizeDerivedKey { 1004 txMeta := txn.TxnMeta.(*AuthorizeDerivedKeyMetadata) 1005 metadataDerivedKey = append(metadataDerivedKey, &PGMetadataDerivedKey{ 1006 TransactionHash: txnHash, 1007 DerivedPublicKey: *NewPublicKey(txMeta.DerivedPublicKey), 1008 ExpirationBlock: txMeta.ExpirationBlock, 1009 OperationType: txMeta.OperationType, 1010 AccessSignature: txMeta.AccessSignature, 1011 }) 1012 } else { 1013 return fmt.Errorf("InsertTransactionTx: Unimplemented txn type %v", txn.TxnMeta.GetTxnType().String()) 1014 } 1015 } 1016 1017 // Insert the block and all of its data in bulk 1018 1019 if len(transactions) > 0 { 1020 if _, err := tx.Model(&transactions).Returning("NULL").Insert(); err != nil { 1021 return err 1022 } 1023 } 1024 1025 if len(transactionOutputs) > 0 { 1026 if _, err := tx.Model(&transactionOutputs).Returning("NULL").OnConflict("(output_hash, output_index) DO UPDATE").Insert(); err != nil { 1027 return err 1028 } 1029 } 1030 1031 if len(transactionInputs) > 0 { 1032 if _, err := tx.Model(&transactionInputs).WherePK().Column("input_hash", "input_index", "spent").Update(); err != nil { 1033 return err 1034 } 1035 } 1036 1037 if len(metadataBlockRewards) > 0 { 1038 if _, err := tx.Model(&metadataBlockRewards).Returning("NULL").Insert(); err != nil { 1039 return err 1040 } 1041 } 1042 1043 if len(metadataBitcoinExchanges) > 0 { 1044 if _, err := tx.Model(&metadataBitcoinExchanges).Returning("NULL").Insert(); err != nil { 1045 return err 1046 } 1047 } 1048 1049 if len(metadataPrivateMessages) > 0 { 1050 if _, err := tx.Model(&metadataPrivateMessages).Returning("NULL").Insert(); err != nil { 1051 return err 1052 } 1053 } 1054 1055 if len(metadataSubmitPosts) > 0 { 1056 if _, err := tx.Model(&metadataSubmitPosts).Returning("NULL").Insert(); err != nil { 1057 return err 1058 } 1059 } 1060 1061 if len(metadataUpdateProfiles) > 0 { 1062 if _, err := tx.Model(&metadataUpdateProfiles).Returning("NULL").Insert(); err != nil { 1063 return err 1064 } 1065 } 1066 1067 if len(metadataExchangeRates) > 0 { 1068 if _, err := tx.Model(&metadataExchangeRates).Returning("NULL").Insert(); err != nil { 1069 return err 1070 } 1071 } 1072 1073 if len(metadataFollows) > 0 { 1074 if _, err := tx.Model(&metadataFollows).Returning("NULL").Insert(); err != nil { 1075 return err 1076 } 1077 } 1078 1079 if len(metadataLikes) > 0 { 1080 if _, err := tx.Model(&metadataLikes).Returning("NULL").Insert(); err != nil { 1081 return err 1082 } 1083 } 1084 1085 if len(metadataCreatorCoins) > 0 { 1086 if _, err := tx.Model(&metadataCreatorCoins).Returning("NULL").Insert(); err != nil { 1087 return err 1088 } 1089 } 1090 1091 if len(metadataSwapIdentities) > 0 { 1092 if _, err := tx.Model(&metadataSwapIdentities).Returning("NULL").Insert(); err != nil { 1093 return err 1094 } 1095 } 1096 1097 if len(metadataCreatorCoinTransfers) > 0 { 1098 if _, err := tx.Model(&metadataCreatorCoinTransfers).Returning("NULL").Insert(); err != nil { 1099 return err 1100 } 1101 } 1102 1103 if len(metadataCreateNFTs) > 0 { 1104 if _, err := tx.Model(&metadataCreateNFTs).Returning("NULL").Insert(); err != nil { 1105 return err 1106 } 1107 } 1108 1109 if len(metadataUpdateNFTs) > 0 { 1110 if _, err := tx.Model(&metadataUpdateNFTs).Returning("NULL").Insert(); err != nil { 1111 return err 1112 } 1113 } 1114 1115 if len(metadataAcceptNFTBids) > 0 { 1116 if _, err := tx.Model(&metadataAcceptNFTBids).Returning("NULL").Insert(); err != nil { 1117 return err 1118 } 1119 } 1120 1121 if len(metadataBidInputs) > 0 { 1122 if _, err := tx.Model(&metadataBidInputs).Returning("NULL").Insert(); err != nil { 1123 return err 1124 } 1125 } 1126 1127 if len(metadataNFTBids) > 0 { 1128 if _, err := tx.Model(&metadataNFTBids).Returning("NULL").Insert(); err != nil { 1129 return err 1130 } 1131 } 1132 1133 if len(metadataNFTTransfer) > 0 { 1134 if _, err := tx.Model(&metadataNFTTransfer).Returning("NULL").Insert(); err != nil { 1135 return err 1136 } 1137 } 1138 1139 if len(metadataAcceptNFTTransfer) > 0 { 1140 if _, err := tx.Model(&metadataAcceptNFTTransfer).Returning("NULL").Insert(); err != nil { 1141 return err 1142 } 1143 } 1144 1145 if len(metadataBurnNFT) > 0 { 1146 if _, err := tx.Model(&metadataBurnNFT).Returning("NULL").Insert(); err != nil { 1147 return err 1148 } 1149 } 1150 1151 if len(metadataDerivedKey) > 0 { 1152 if _, err := tx.Model(&metadataDerivedKey).Returning("NULL").Insert(); err != nil { 1153 return err 1154 } 1155 } 1156 1157 return nil 1158 } 1159 1160 func (postgres *Postgres) UpsertBlockAndTransactions(blockNode *BlockNode, desoBlock *MsgDeSoBlock) error { 1161 return postgres.db.RunInTransaction(postgres.db.Context(), func(tx *pg.Tx) error { 1162 err := postgres.UpsertBlockTx(tx, blockNode) 1163 if err != nil { 1164 return err 1165 } 1166 1167 blockHash := blockNode.Hash 1168 err = postgres.UpsertChainTx(tx, MAIN_CHAIN, blockHash) 1169 if err != nil { 1170 return err 1171 } 1172 1173 err = postgres.InsertTransactionsTx(tx, desoBlock.Txns, blockNode) 1174 if err != nil { 1175 return err 1176 } 1177 1178 return nil 1179 }) 1180 } 1181 1182 // 1183 // BlockView Flushing 1184 // 1185 1186 func (postgres *Postgres) FlushView(view *UtxoView) error { 1187 return postgres.db.RunInTransaction(postgres.db.Context(), func(tx *pg.Tx) error { 1188 if err := postgres.flushUtxos(tx, view); err != nil { 1189 return err 1190 } 1191 if err := postgres.flushProfiles(tx, view); err != nil { 1192 return err 1193 } 1194 if err := postgres.flushPosts(tx, view); err != nil { 1195 return err 1196 } 1197 if err := postgres.flushLikes(tx, view); err != nil { 1198 return err 1199 } 1200 if err := postgres.flushFollows(tx, view); err != nil { 1201 return err 1202 } 1203 if err := postgres.flushDiamonds(tx, view); err != nil { 1204 return err 1205 } 1206 if err := postgres.flushMessages(tx, view); err != nil { 1207 return err 1208 } 1209 if err := postgres.flushCreatorCoinBalances(tx, view); err != nil { 1210 return err 1211 } 1212 if err := postgres.flushBalances(tx, view); err != nil { 1213 return err 1214 } 1215 if err := postgres.flushForbiddenKeys(tx, view); err != nil { 1216 return err 1217 } 1218 if err := postgres.flushNFTs(tx, view); err != nil { 1219 return err 1220 } 1221 if err := postgres.flushNFTBids(tx, view); err != nil { 1222 return err 1223 } 1224 if err := postgres.flushDerivedKeys(tx, view); err != nil { 1225 return err 1226 } 1227 1228 return nil 1229 }) 1230 } 1231 1232 func (postgres *Postgres) flushUtxos(tx *pg.Tx, view *UtxoView) error { 1233 var outputs []*PGTransactionOutput 1234 for utxoKeyIter, utxoEntry := range view.UtxoKeyToUtxoEntry { 1235 // Making a copy of the iterator is required 1236 utxoKey := utxoKeyIter 1237 outputs = append(outputs, &PGTransactionOutput{ 1238 OutputHash: &utxoKey.TxID, 1239 OutputIndex: utxoKey.Index, 1240 OutputType: utxoEntry.UtxoType, 1241 PublicKey: utxoEntry.PublicKey, 1242 AmountNanos: utxoEntry.AmountNanos, 1243 Spent: utxoEntry.isSpent, 1244 }) 1245 } 1246 1247 _, err := tx.Model(&outputs).WherePK().OnConflict("(output_hash, output_index) DO UPDATE").Insert() 1248 if err != nil { 1249 return err 1250 } 1251 1252 return nil 1253 } 1254 1255 func (postgres *Postgres) flushProfiles(tx *pg.Tx, view *UtxoView) error { 1256 var insertProfiles []*PGProfile 1257 var deleteProfiles []*PGProfile 1258 for _, pkidEntry := range view.PublicKeyToPKIDEntry { 1259 pkid := pkidEntry.PKID 1260 1261 profile := &PGProfile{ 1262 PKID: pkid, 1263 PublicKey: NewPublicKey(pkidEntry.PublicKey), 1264 } 1265 1266 profileEntry := view.ProfilePKIDToProfileEntry[*pkid] 1267 if profileEntry != nil { 1268 profile.Username = string(profileEntry.Username) 1269 profile.Description = string(profileEntry.Description) 1270 profile.ProfilePic = profileEntry.ProfilePic 1271 profile.CreatorBasisPoints = profileEntry.CreatorBasisPoints 1272 profile.DeSoLockedNanos = profileEntry.DeSoLockedNanos 1273 profile.NumberOfHolders = profileEntry.NumberOfHolders 1274 profile.CoinsInCirculationNanos = profileEntry.CoinsInCirculationNanos 1275 profile.CoinWatermarkNanos = profileEntry.CoinWatermarkNanos 1276 } 1277 1278 if pkidEntry.isDeleted { 1279 deleteProfiles = append(deleteProfiles, profile) 1280 } else { 1281 insertProfiles = append(insertProfiles, profile) 1282 } 1283 } 1284 1285 if len(insertProfiles) > 0 { 1286 _, err := tx.Model(&insertProfiles).WherePK().OnConflict("(pkid) DO UPDATE").Returning("NULL").Insert() 1287 if err != nil { 1288 return err 1289 } 1290 } 1291 1292 if len(deleteProfiles) > 0 { 1293 _, err := tx.Model(&deleteProfiles).Returning("NULL").Delete() 1294 if err != nil { 1295 return err 1296 } 1297 } 1298 1299 return nil 1300 } 1301 1302 func (postgres *Postgres) flushPosts(tx *pg.Tx, view *UtxoView) error { 1303 var insertPosts []*PGPost 1304 var deletePosts []*PGPost 1305 for _, postEntry := range view.PostHashToPostEntry { 1306 if postEntry == nil { 1307 continue 1308 } 1309 1310 post := &PGPost{ 1311 PostHash: postEntry.PostHash, 1312 PosterPublicKey: postEntry.PosterPublicKey, 1313 Body: string(postEntry.Body), 1314 RepostedPostHash: postEntry.RepostedPostHash, 1315 QuotedRepost: postEntry.IsQuotedRepost, 1316 Timestamp: postEntry.TimestampNanos, 1317 Hidden: postEntry.IsHidden, 1318 LikeCount: postEntry.LikeCount, 1319 RepostCount: postEntry.RepostCount, 1320 QuoteRepostCount: postEntry.QuoteRepostCount, 1321 DiamondCount: postEntry.DiamondCount, 1322 CommentCount: postEntry.CommentCount, 1323 Pinned: postEntry.IsPinned, 1324 NFT: postEntry.IsNFT, 1325 NumNFTCopies: postEntry.NumNFTCopies, 1326 NumNFTCopiesForSale: postEntry.NumNFTCopiesForSale, 1327 NumNFTCopiesBurned: postEntry.NumNFTCopiesBurned, 1328 Unlockable: postEntry.HasUnlockable, 1329 CreatorRoyaltyBasisPoints: postEntry.NFTRoyaltyToCreatorBasisPoints, 1330 CoinRoyaltyBasisPoints: postEntry.NFTRoyaltyToCoinBasisPoints, 1331 ExtraData: postEntry.PostExtraData, 1332 } 1333 1334 if len(postEntry.ParentStakeID) > 0 { 1335 post.ParentPostHash = NewBlockHash(postEntry.ParentStakeID) 1336 } 1337 1338 if postEntry.isDeleted { 1339 deletePosts = append(deletePosts, post) 1340 } else { 1341 insertPosts = append(insertPosts, post) 1342 } 1343 } 1344 1345 if len(insertPosts) > 0 { 1346 _, err := tx.Model(&insertPosts).WherePK().OnConflict("(post_hash) DO UPDATE").Returning("NULL").Insert() 1347 if err != nil { 1348 return err 1349 } 1350 } 1351 1352 if len(deletePosts) > 0 { 1353 _, err := tx.Model(&deletePosts).Returning("NULL").Delete() 1354 if err != nil { 1355 return err 1356 } 1357 } 1358 1359 return nil 1360 } 1361 1362 func (postgres *Postgres) flushLikes(tx *pg.Tx, view *UtxoView) error { 1363 var insertLikes []*PGLike 1364 var deleteLikes []*PGLike 1365 for _, likeEntry := range view.LikeKeyToLikeEntry { 1366 if likeEntry == nil { 1367 continue 1368 } 1369 1370 like := &PGLike{ 1371 LikerPublicKey: likeEntry.LikerPubKey, 1372 LikedPostHash: likeEntry.LikedPostHash, 1373 } 1374 1375 if likeEntry.isDeleted { 1376 deleteLikes = append(deleteLikes, like) 1377 } else { 1378 insertLikes = append(insertLikes, like) 1379 } 1380 } 1381 1382 if len(insertLikes) > 0 { 1383 _, err := tx.Model(&insertLikes).WherePK().OnConflict("DO NOTHING").Returning("NULL").Insert() 1384 if err != nil { 1385 return err 1386 } 1387 } 1388 1389 if len(deleteLikes) > 0 { 1390 _, err := tx.Model(&deleteLikes).Returning("NULL").Delete() 1391 if err != nil { 1392 return err 1393 } 1394 } 1395 1396 return nil 1397 } 1398 1399 func (postgres *Postgres) flushFollows(tx *pg.Tx, view *UtxoView) error { 1400 var insertFollows []*PGFollow 1401 var deleteFollows []*PGFollow 1402 for _, followEntry := range view.FollowKeyToFollowEntry { 1403 if followEntry == nil { 1404 continue 1405 } 1406 1407 follow := &PGFollow{ 1408 FollowerPKID: followEntry.FollowerPKID, 1409 FollowedPKID: followEntry.FollowedPKID, 1410 } 1411 1412 if followEntry.isDeleted { 1413 deleteFollows = append(deleteFollows, follow) 1414 } else { 1415 insertFollows = append(insertFollows, follow) 1416 } 1417 } 1418 1419 if len(insertFollows) > 0 { 1420 _, err := tx.Model(&insertFollows).WherePK().OnConflict("DO NOTHING").Returning("NULL").Insert() 1421 if err != nil { 1422 return err 1423 } 1424 } 1425 1426 if len(deleteFollows) > 0 { 1427 _, err := tx.Model(&deleteFollows).Returning("NULL").Delete() 1428 if err != nil { 1429 return err 1430 } 1431 } 1432 1433 return nil 1434 } 1435 1436 func (postgres *Postgres) flushDiamonds(tx *pg.Tx, view *UtxoView) error { 1437 var insertDiamonds []*PGDiamond 1438 var deleteDiamonds []*PGDiamond 1439 for _, diamondEntry := range view.DiamondKeyToDiamondEntry { 1440 diamond := &PGDiamond{ 1441 SenderPKID: diamondEntry.SenderPKID, 1442 ReceiverPKID: diamondEntry.ReceiverPKID, 1443 DiamondPostHash: diamondEntry.DiamondPostHash, 1444 DiamondLevel: uint8(diamondEntry.DiamondLevel), 1445 } 1446 1447 if diamondEntry.isDeleted { 1448 deleteDiamonds = append(deleteDiamonds, diamond) 1449 } else { 1450 insertDiamonds = append(insertDiamonds, diamond) 1451 } 1452 } 1453 1454 if len(insertDiamonds) > 0 { 1455 _, err := tx.Model(&insertDiamonds).WherePK().OnConflict("(sender_pkid, receiver_pkid, diamond_post_hash) DO UPDATE").Returning("NULL").Insert() 1456 if err != nil { 1457 return err 1458 } 1459 } 1460 1461 if len(deleteDiamonds) > 0 { 1462 _, err := tx.Model(&deleteDiamonds).Returning("NULL").Delete() 1463 if err != nil { 1464 return err 1465 } 1466 } 1467 1468 return nil 1469 } 1470 1471 func (postgres *Postgres) flushMessages(tx *pg.Tx, view *UtxoView) error { 1472 var insertMessages []*PGMessage 1473 var deleteMessages []*PGMessage 1474 for _, message := range view.MessageMap { 1475 if message.isDeleted { 1476 deleteMessages = append(deleteMessages, message) 1477 } else { 1478 insertMessages = append(insertMessages, message) 1479 } 1480 } 1481 1482 if len(insertMessages) > 0 { 1483 // TODO: There should never be a conflict here. Should we raise an error? 1484 _, err := tx.Model(&insertMessages).WherePK().OnConflict("(message_hash) DO NOTHING").Returning("NULL").Insert() 1485 if err != nil { 1486 return err 1487 } 1488 } 1489 1490 if len(deleteMessages) > 0 { 1491 _, err := tx.Model(&deleteMessages).Returning("NULL").Delete() 1492 if err != nil { 1493 return err 1494 } 1495 } 1496 1497 return nil 1498 } 1499 1500 func (postgres *Postgres) flushCreatorCoinBalances(tx *pg.Tx, view *UtxoView) error { 1501 var insertBalances []*PGCreatorCoinBalance 1502 var deleteBalances []*PGCreatorCoinBalance 1503 for _, balanceEntry := range view.HODLerPKIDCreatorPKIDToBalanceEntry { 1504 if balanceEntry == nil { 1505 continue 1506 } 1507 1508 balance := &PGCreatorCoinBalance{ 1509 HolderPKID: balanceEntry.HODLerPKID, 1510 CreatorPKID: balanceEntry.CreatorPKID, 1511 BalanceNanos: balanceEntry.BalanceNanos, 1512 HasPurchased: balanceEntry.HasPurchased, 1513 } 1514 1515 if balanceEntry.isDeleted { 1516 deleteBalances = append(deleteBalances, balance) 1517 } else { 1518 insertBalances = append(insertBalances, balance) 1519 } 1520 } 1521 1522 if len(insertBalances) > 0 { 1523 _, err := tx.Model(&insertBalances).WherePK().OnConflict("(holder_pkid, creator_pkid) DO UPDATE").Returning("NULL").Insert() 1524 if err != nil { 1525 return err 1526 } 1527 } 1528 1529 if len(deleteBalances) > 0 { 1530 _, err := tx.Model(&deleteBalances).Returning("NULL").Delete() 1531 if err != nil { 1532 return err 1533 } 1534 } 1535 1536 return nil 1537 } 1538 1539 func (postgres *Postgres) flushBalances(tx *pg.Tx, view *UtxoView) error { 1540 var balances []*PGBalance 1541 for pubKeyIter, balanceNanos := range view.PublicKeyToDeSoBalanceNanos { 1542 // Make a copy of the iterator since it might change from under us. 1543 pubKey := pubKeyIter[:] 1544 1545 balance := &PGBalance{ 1546 PublicKey: NewPublicKey(pubKey), 1547 BalanceNanos: balanceNanos, 1548 } 1549 1550 balances = append(balances, balance) 1551 } 1552 1553 if len(balances) > 0 { 1554 _, err := tx.Model(&balances).WherePK().OnConflict("(public_key) DO UPDATE").Returning("NULL").Insert() 1555 if err != nil { 1556 return err 1557 } 1558 } 1559 1560 return nil 1561 } 1562 1563 func (postgres *Postgres) flushForbiddenKeys(tx *pg.Tx, view *UtxoView) error { 1564 var insertKeys []*PGForbiddenKey 1565 var deleteKeys []*PGForbiddenKey 1566 for _, keyEntry := range view.ForbiddenPubKeyToForbiddenPubKeyEntry { 1567 balance := &PGForbiddenKey{ 1568 PublicKey: NewPublicKey(keyEntry.PubKey), 1569 } 1570 1571 if keyEntry.isDeleted { 1572 deleteKeys = append(deleteKeys, balance) 1573 } else { 1574 insertKeys = append(insertKeys, balance) 1575 } 1576 } 1577 1578 if len(insertKeys) > 0 { 1579 _, err := tx.Model(&insertKeys).WherePK().OnConflict("(public_key) DO UPDATE").Returning("NULL").Insert() 1580 if err != nil { 1581 return err 1582 } 1583 } 1584 1585 if len(deleteKeys) > 0 { 1586 _, err := tx.Model(&deleteKeys).Returning("NULL").Delete() 1587 if err != nil { 1588 return err 1589 } 1590 } 1591 1592 return nil 1593 } 1594 1595 func (postgres *Postgres) flushNFTs(tx *pg.Tx, view *UtxoView) error { 1596 var insertNFTs []*PGNFT 1597 var deleteNFTs []*PGNFT 1598 for _, nftEntry := range view.NFTKeyToNFTEntry { 1599 nft := &PGNFT{ 1600 NFTPostHash: nftEntry.NFTPostHash, 1601 SerialNumber: nftEntry.SerialNumber, 1602 LastOwnerPKID: nftEntry.LastOwnerPKID, 1603 OwnerPKID: nftEntry.OwnerPKID, 1604 ForSale: nftEntry.IsForSale, 1605 MinBidAmountNanos: nftEntry.MinBidAmountNanos, 1606 UnlockableText: string(nftEntry.UnlockableText), 1607 LastAcceptedBidAmountNanos: nftEntry.LastAcceptedBidAmountNanos, 1608 IsPending: nftEntry.IsPending, 1609 } 1610 1611 if nftEntry.isDeleted { 1612 deleteNFTs = append(deleteNFTs, nft) 1613 } else { 1614 insertNFTs = append(insertNFTs, nft) 1615 } 1616 } 1617 1618 if len(insertNFTs) > 0 { 1619 _, err := tx.Model(&insertNFTs).WherePK().OnConflict("(nft_post_hash, serial_number) DO UPDATE").Returning("NULL").Insert() 1620 if err != nil { 1621 return err 1622 } 1623 } 1624 1625 if len(deleteNFTs) > 0 { 1626 _, err := tx.Model(&deleteNFTs).Returning("NULL").Delete() 1627 if err != nil { 1628 return err 1629 } 1630 } 1631 1632 return nil 1633 } 1634 1635 func (postgres *Postgres) flushNFTBids(tx *pg.Tx, view *UtxoView) error { 1636 var insertBids []*PGNFTBid 1637 var deleteBids []*PGNFTBid 1638 for _, bidEntry := range view.NFTBidKeyToNFTBidEntry { 1639 nft := &PGNFTBid{ 1640 BidderPKID: bidEntry.BidderPKID, 1641 NFTPostHash: bidEntry.NFTPostHash, 1642 SerialNumber: bidEntry.SerialNumber, 1643 BidAmountNanos: bidEntry.BidAmountNanos, 1644 // TODO: Change how accepted bid logic works in consensus 1645 Accepted: false, 1646 } 1647 1648 if bidEntry.isDeleted { 1649 deleteBids = append(deleteBids, nft) 1650 } else { 1651 insertBids = append(insertBids, nft) 1652 } 1653 } 1654 1655 if len(insertBids) > 0 { 1656 _, err := tx.Model(&insertBids).WherePK().OnConflict("(nft_post_hash, bidder_pkid, serial_number) DO UPDATE").Returning("NULL").Insert() 1657 if err != nil { 1658 return err 1659 } 1660 } 1661 1662 if len(deleteBids) > 0 { 1663 _, err := tx.Model(&deleteBids).Returning("NULL").Delete() 1664 if err != nil { 1665 return err 1666 } 1667 } 1668 1669 return nil 1670 } 1671 1672 func (postgres *Postgres) flushDerivedKeys(tx *pg.Tx, view *UtxoView) error { 1673 var insertKeys []*PGDerivedKey 1674 var deleteKeys []*PGDerivedKey 1675 for _, keyEntry := range view.DerivedKeyToDerivedEntry { 1676 key := &PGDerivedKey{ 1677 OwnerPublicKey: keyEntry.OwnerPublicKey, 1678 DerivedPublicKey: keyEntry.DerivedPublicKey, 1679 ExpirationBlock: keyEntry.ExpirationBlock, 1680 OperationType: keyEntry.OperationType, 1681 } 1682 1683 if keyEntry.isDeleted { 1684 deleteKeys = append(deleteKeys, key) 1685 } else { 1686 insertKeys = append(insertKeys, key) 1687 } 1688 } 1689 1690 if len(insertKeys) > 0 { 1691 _, err := tx.Model(&insertKeys).WherePK().OnConflict("(owner_public_key, derived_public_key) DO UPDATE").Returning("NULL").Insert() 1692 if err != nil { 1693 return err 1694 } 1695 } 1696 1697 if len(deleteKeys) > 0 { 1698 _, err := tx.Model(&deleteKeys).Returning("NULL").Delete() 1699 if err != nil { 1700 return err 1701 } 1702 } 1703 1704 return nil 1705 } 1706 1707 // 1708 // UTXOS 1709 // 1710 1711 func (postgres *Postgres) GetUtxoEntryForUtxoKey(utxoKey *UtxoKey) *UtxoEntry { 1712 utxo := &PGTransactionOutput{ 1713 OutputHash: &utxoKey.TxID, 1714 OutputIndex: utxoKey.Index, 1715 Spent: false, 1716 } 1717 1718 err := postgres.db.Model(utxo).WherePK().Select() 1719 if err != nil { 1720 return nil 1721 } 1722 1723 return utxo.NewUtxoEntry() 1724 } 1725 1726 func (postgres *Postgres) GetUtxoEntriesForPublicKey(publicKey []byte) []*UtxoEntry { 1727 var transactionOutputs []*PGTransactionOutput 1728 err := postgres.db.Model(&transactionOutputs).Where("public_key = ?", publicKey).Select() 1729 if err != nil { 1730 return nil 1731 } 1732 1733 var utxoEntries []*UtxoEntry 1734 for _, utxo := range transactionOutputs { 1735 utxoEntries = append(utxoEntries, utxo.NewUtxoEntry()) 1736 } 1737 1738 return utxoEntries 1739 } 1740 1741 func (postgres *Postgres) GetOutputs(outputs []*PGTransactionOutput) []*PGTransactionOutput { 1742 err := postgres.db.Model(&outputs).WherePK().Select() 1743 if err != nil { 1744 return nil 1745 } 1746 return outputs 1747 } 1748 1749 func (postgres *Postgres) GetBlockRewardsForPublicKey(publicKey *PublicKey, startHeight uint32, endHeight uint32) []*PGTransactionOutput { 1750 var transactionOutputs []*PGTransactionOutput 1751 err := postgres.db.Model(&transactionOutputs).Where("public_key = ?", publicKey). 1752 Where("height >= ?", startHeight).Where("height <= ?", endHeight).Select() 1753 if err != nil { 1754 return nil 1755 } 1756 return transactionOutputs 1757 } 1758 1759 // 1760 // Profiles 1761 // 1762 1763 func (postgres *Postgres) GetProfileForUsername(nonLowercaseUsername string) *PGProfile { 1764 var profile PGProfile 1765 err := postgres.db.Model(&profile).Where("LOWER(username) = ?", strings.ToLower(nonLowercaseUsername)).First() 1766 if err != nil { 1767 return nil 1768 } 1769 return &profile 1770 } 1771 1772 func (postgres *Postgres) GetProfileForPublicKey(publicKey []byte) *PGProfile { 1773 var profile PGProfile 1774 err := postgres.db.Model(&profile).Where("public_key = ?", publicKey).First() 1775 if err != nil { 1776 return nil 1777 } 1778 return &profile 1779 } 1780 1781 func (postgres *Postgres) GetProfile(pkid PKID) *PGProfile { 1782 var profile PGProfile 1783 err := postgres.db.Model(&profile).Where("pkid = ?", pkid).First() 1784 if err != nil { 1785 return nil 1786 } 1787 return &profile 1788 } 1789 1790 func (postgres *Postgres) GetProfilesForPublicKeys(publicKeys []*PublicKey) []*PGProfile { 1791 var profiles []*PGProfile 1792 err := postgres.db.Model(&profiles).WhereIn("public_key IN (?)", publicKeys).Select() 1793 if err != nil { 1794 return nil 1795 } 1796 return profiles 1797 } 1798 1799 func (postgres *Postgres) GetProfilesByCoinValue(startLockedNanos uint64, limit int) []*PGProfile { 1800 var profiles []*PGProfile 1801 err := postgres.db.Model(&profiles).Where("deso_locked_nanos < ?", startLockedNanos). 1802 OrderExpr("deso_locked_nanos DESC").Limit(limit).Select() 1803 if err != nil { 1804 return nil 1805 } 1806 return profiles 1807 } 1808 1809 func (postgres *Postgres) GetProfilesForUsernamePrefixByCoinValue(usernamePrefix string, limit int) []*PGProfile { 1810 var profiles []*PGProfile 1811 err := postgres.db.Model(&profiles).Where("username ILIKE ?", fmt.Sprintf("%s%%", usernamePrefix)). 1812 Where("deso_locked_nanos >= 0").OrderExpr("deso_locked_nanos DESC").Limit(limit).Select() 1813 if err != nil { 1814 return nil 1815 } 1816 return profiles 1817 } 1818 1819 func (postgres *Postgres) GetProfilesForUsername(usernames []string) []*PGProfile { 1820 var profiles []*PGProfile 1821 err := postgres.db.Model(&profiles).Where("LOWER(username) IN (?)", usernames).Select() 1822 if err != nil { 1823 return nil 1824 } 1825 return profiles 1826 } 1827 1828 // 1829 // Posts 1830 // 1831 1832 func (postgres *Postgres) GetPost(postHash *BlockHash) *PGPost { 1833 var post PGPost 1834 err := postgres.db.Model(&post).Where("post_hash = ?", postHash).First() 1835 if err != nil { 1836 return nil 1837 } 1838 return &post 1839 } 1840 1841 func (postgres *Postgres) GetPosts(posts []*PGPost) []*PGPost { 1842 err := postgres.db.Model(&posts).WherePK().Select() 1843 if err != nil { 1844 return nil 1845 } 1846 return posts 1847 } 1848 1849 func (postgres *Postgres) GetPostsForPublicKey(publicKey []byte, startTime uint64, limit uint64) []*PGPost { 1850 var posts []*PGPost 1851 err := postgres.db.Model(&posts). 1852 Where("poster_public_key = ?", publicKey).Where("timestamp < ?", startTime). 1853 Where("hidden IS NULL").Where("parent_post_hash IS NULL"). 1854 OrderExpr("timestamp DESC").Limit(int(limit)).Select() 1855 if err != nil { 1856 return nil 1857 } 1858 return posts 1859 } 1860 1861 // 1862 // Comments 1863 // 1864 1865 // TODO: Pagination 1866 func (postgres *Postgres) GetComments(parentPostHash *BlockHash) []*PGPost { 1867 var posts []*PGPost 1868 err := postgres.db.Model(&posts).Where("parent_post_hash = ?", parentPostHash).Select() 1869 if err != nil { 1870 return nil 1871 } 1872 return posts 1873 } 1874 1875 func (postgres *Postgres) GetMessage(messageHash *BlockHash) *PGMessage { 1876 var message PGMessage 1877 err := postgres.db.Model(&message).Where("message_hash = ?", messageHash).First() 1878 if err != nil { 1879 return nil 1880 } 1881 return &message 1882 } 1883 1884 // 1885 // LIKES 1886 // 1887 1888 func (postgres *Postgres) GetLike(likerPublicKey []byte, likedPostHash *BlockHash) *PGLike { 1889 like := PGLike{ 1890 LikerPublicKey: likerPublicKey, 1891 LikedPostHash: likedPostHash, 1892 } 1893 err := postgres.db.Model(&like).WherePK().First() 1894 if err != nil { 1895 return nil 1896 } 1897 return &like 1898 } 1899 1900 func (postgres *Postgres) GetLikes(likes []*PGLike) []*PGLike { 1901 err := postgres.db.Model(&likes).WherePK().Select() 1902 if err != nil { 1903 return nil 1904 } 1905 return likes 1906 } 1907 1908 func (postgres *Postgres) GetLikesForPost(postHash *BlockHash) []*PGLike { 1909 var likes []*PGLike 1910 err := postgres.db.Model(&likes).Where("liked_post_hash = ?", postHash).Select() 1911 if err != nil { 1912 return nil 1913 } 1914 return likes 1915 } 1916 1917 // 1918 // Follows 1919 // 1920 1921 func (postgres *Postgres) GetFollow(followerPkid *PKID, followedPkid *PKID) *PGFollow { 1922 follow := PGFollow{ 1923 FollowerPKID: followerPkid, 1924 FollowedPKID: followedPkid, 1925 } 1926 err := postgres.db.Model(&follow).WherePK().First() 1927 if err != nil { 1928 return nil 1929 } 1930 return &follow 1931 } 1932 1933 func (postgres *Postgres) GetFollows(follows []*PGFollow) []*PGFollow { 1934 err := postgres.db.Model(&follows).WherePK().Select() 1935 if err != nil { 1936 return nil 1937 } 1938 return follows 1939 } 1940 1941 func (postgres *Postgres) GetFollowing(pkid *PKID) []*PGFollow { 1942 var follows []*PGFollow 1943 err := postgres.db.Model(&follows).Where("follower_pkid = ?", pkid).Select() 1944 if err != nil { 1945 return nil 1946 } 1947 return follows 1948 } 1949 1950 func (postgres *Postgres) GetFollowers(pkid *PKID) []*PGFollow { 1951 var follows []*PGFollow 1952 err := postgres.db.Model(&follows).Where("followed_pkid = ?", pkid).Select() 1953 if err != nil { 1954 return nil 1955 } 1956 return follows 1957 } 1958 1959 func (postgres *Postgres) GetDiamond(senderPkid *PKID, receiverPkid *PKID, postHash *BlockHash) *PGDiamond { 1960 diamond := PGDiamond{ 1961 SenderPKID: senderPkid, 1962 ReceiverPKID: receiverPkid, 1963 DiamondPostHash: postHash, 1964 } 1965 err := postgres.db.Model(&diamond).WherePK().First() 1966 if err != nil { 1967 return nil 1968 } 1969 return &diamond 1970 } 1971 1972 // 1973 // Creator Coins 1974 // 1975 1976 func (postgres *Postgres) GetCreatorCoinBalances(balances []*PGCreatorCoinBalance) []*PGCreatorCoinBalance { 1977 err := postgres.db.Model(&balances).WherePK().Select() 1978 if err != nil { 1979 return nil 1980 } 1981 return balances 1982 } 1983 1984 func (postgres *Postgres) GetCreatorCoinBalance(holderPkid *PKID, creatorPkid *PKID) *PGCreatorCoinBalance { 1985 balance := PGCreatorCoinBalance{ 1986 HolderPKID: holderPkid, 1987 CreatorPKID: creatorPkid, 1988 } 1989 err := postgres.db.Model(&balance).WherePK().First() 1990 if err != nil { 1991 return nil 1992 } 1993 return &balance 1994 } 1995 1996 func (postgres *Postgres) GetHoldings(pkid *PKID) []*PGCreatorCoinBalance { 1997 var holdings []*PGCreatorCoinBalance 1998 err := postgres.db.Model(&holdings).Where("holder_pkid = ?", pkid).Select() 1999 if err != nil { 2000 return nil 2001 } 2002 return holdings 2003 } 2004 2005 func (postgres *Postgres) GetHolders(pkid *PKID) []*PGCreatorCoinBalance { 2006 var holdings []*PGCreatorCoinBalance 2007 err := postgres.db.Model(&holdings).Where("creator_pkid = ?", pkid).Select() 2008 if err != nil { 2009 return nil 2010 } 2011 return holdings 2012 } 2013 2014 // 2015 // NFTS 2016 // 2017 2018 func (postgres *Postgres) GetNFT(nftPostHash *BlockHash, serialNumber uint64) *PGNFT { 2019 nft := PGNFT{ 2020 NFTPostHash: nftPostHash, 2021 SerialNumber: serialNumber, 2022 } 2023 err := postgres.db.Model(&nft).WherePK().First() 2024 if err != nil { 2025 return nil 2026 } 2027 return &nft 2028 } 2029 2030 func (postgres *Postgres) GetNFTsForPostHash(nftPostHash *BlockHash) []*PGNFT { 2031 var nfts []*PGNFT 2032 err := postgres.db.Model(&nfts).Where("nft_post_hash = ?", nftPostHash).Select() 2033 if err != nil { 2034 return nil 2035 } 2036 return nfts 2037 } 2038 2039 func (postgres *Postgres) GetNFTsForPKID(pkid *PKID) []*PGNFT { 2040 var nfts []*PGNFT 2041 err := postgres.db.Model(&nfts).Where("owner_pkid = ?", pkid).Select() 2042 if err != nil { 2043 return nil 2044 } 2045 return nfts 2046 } 2047 2048 func (postgres *Postgres) GetNFTBidsForPKID(pkid *PKID) []*PGNFTBid { 2049 var nftBids []*PGNFTBid 2050 err := postgres.db.Model(&nftBids).Where("bidder_pkid = ?", pkid).Select() 2051 if err != nil { 2052 return nil 2053 } 2054 return nftBids 2055 } 2056 2057 func (postgres *Postgres) GetNFTBidsForSerial(nftPostHash *BlockHash, serialNumber uint64) []*PGNFTBid { 2058 var nftBids []*PGNFTBid 2059 err := postgres.db.Model(&nftBids).Where("nft_post_hash = ?", nftPostHash). 2060 Where("serial_number = ?", serialNumber).Select() 2061 if err != nil { 2062 return nil 2063 } 2064 return nftBids 2065 } 2066 2067 func (postgres *Postgres) GetNFTBid(nftPostHash *BlockHash, bidderPKID *PKID, serialNumber uint64) *PGNFTBid { 2068 bid := PGNFTBid{ 2069 NFTPostHash: nftPostHash, 2070 BidderPKID: bidderPKID, 2071 SerialNumber: serialNumber, 2072 } 2073 err := postgres.db.Model(&bid).WherePK().First() 2074 if err != nil { 2075 return nil 2076 } 2077 return &bid 2078 } 2079 2080 // 2081 // Derived Keys 2082 // 2083 2084 func (postgres *Postgres) GetDerivedKey(ownerPublicKey *PublicKey, derivedPublicKey *PublicKey) *PGDerivedKey { 2085 key := PGDerivedKey{ 2086 OwnerPublicKey: *ownerPublicKey, 2087 DerivedPublicKey: *derivedPublicKey, 2088 } 2089 err := postgres.db.Model(&key).WherePK().First() 2090 if err != nil { 2091 return nil 2092 } 2093 return &key 2094 } 2095 2096 func (postgres *Postgres) GetAllDerivedKeysForOwner(ownerPublicKey *PublicKey) []*PGDerivedKey { 2097 var keys []*PGDerivedKey 2098 err := postgres.db.Model(&keys).Where("owner_public_key = ?", *ownerPublicKey).Select() 2099 if err != nil { 2100 return nil 2101 } 2102 return keys 2103 } 2104 2105 // 2106 // Balances 2107 // 2108 2109 func (postgres *Postgres) GetBalance(publicKey *PublicKey) uint64 { 2110 balance := PGBalance{ 2111 PublicKey: publicKey, 2112 } 2113 err := postgres.db.Model(&balance).WherePK().First() 2114 if err != nil { 2115 return 0 2116 } 2117 return balance.BalanceNanos 2118 } 2119 2120 // 2121 // PGChain Init 2122 // 2123 2124 func (postgres *Postgres) InitGenesisBlock(params *DeSoParams, db *badger.DB) error { 2125 // Construct a node for the genesis block. Its height is zero and it has no parents. Its difficulty should be 2126 // set to the initial difficulty specified in the parameters and it should be assumed to be 2127 // valid and stored by the end of this function. 2128 genesisBlock := params.GenesisBlock 2129 diffTarget := MustDecodeHexBlockHash(params.MinDifficultyTargetHex) 2130 blockHash := MustDecodeHexBlockHash(params.GenesisBlockHashHex) 2131 genesisNode := NewBlockNode( 2132 nil, 2133 blockHash, 2134 0, 2135 diffTarget, 2136 BytesToBigint(ExpectedWorkForBlockHash(diffTarget)[:]), 2137 genesisBlock.Header, 2138 StatusHeaderValidated|StatusBlockProcessed|StatusBlockStored|StatusBlockValidated, 2139 ) 2140 2141 // Create the chain 2142 err := postgres.UpsertChain("main", blockHash) 2143 if err != nil { 2144 return fmt.Errorf("InitGenesisBlock: Error upserting chain: %v", err) 2145 } 2146 2147 // Set the fields in the db to reflect the current state of our chain. 2148 // 2149 // Set the best hash to the genesis block in the db since its the only node 2150 // we're currently aware of. Set it for both the header chain and the block 2151 // chain. 2152 err = postgres.UpsertBlock(genesisNode) 2153 if err != nil { 2154 return fmt.Errorf("InitGenesisBlock: Error upserting block: %v", err) 2155 } 2156 2157 for index, txOutput := range params.SeedBalances { 2158 _, err := postgres.db.Model(&PGTransactionOutput{ 2159 OutputHash: &BlockHash{}, 2160 OutputIndex: uint32(index), 2161 OutputType: UtxoTypeOutput, 2162 AmountNanos: txOutput.AmountNanos, 2163 PublicKey: txOutput.PublicKey, 2164 }).Returning("NULL").Insert() 2165 if err != nil { 2166 return err 2167 } 2168 } 2169 2170 return nil 2171 } 2172 2173 // 2174 // API 2175 // 2176 2177 func (postgres *Postgres) GetNotifications(publicKey string) ([]*PGNotification, error) { 2178 keyBytes, _, _ := Base58CheckDecode(publicKey) 2179 2180 var notifications []*PGNotification 2181 err := postgres.db.Model(¬ifications).Where("to_user = ?", keyBytes).Order("timestamp desc").Limit(100).Select() 2182 if err != nil { 2183 return nil, err 2184 } 2185 2186 return notifications, nil 2187 }