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(&notifications).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  }