github.com/deso-protocol/core@v1.2.9/lib/notifier.go (about) 1 package lib 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "github.com/dgraph-io/badger/v3" 7 "github.com/gernest/mention" 8 "github.com/go-pg/pg/v10" 9 "reflect" 10 "strings" 11 "time" 12 13 "github.com/golang/glog" 14 ) 15 16 type Notifier struct { 17 coreChain *Blockchain 18 postgres *Postgres 19 20 // Shortcut to postgres.db 21 db *pg.DB 22 23 // Shortcut to coreChain.db 24 badger *badger.DB 25 } 26 27 func NewNotifier(coreChain *Blockchain, postgres *Postgres) *Notifier { 28 return &Notifier{ 29 coreChain: coreChain, 30 postgres: postgres, 31 db: postgres.db, 32 badger: coreChain.db, 33 } 34 } 35 36 func (notifier *Notifier) Update() error { 37 // Fetch all the blocks we haven't processed notifications for in groups of 10,000 38 var blocks []*PGBlock 39 err := notifier.db.Model(&blocks).Where("notified = false").Limit(10_000).Select() 40 if err != nil { 41 return err 42 } 43 44 for _, block := range blocks { 45 var notifications []*PGNotification 46 var transactions []*PGTransaction 47 err = notifier.db.Model(&transactions).Where("block_hash = ?", block.Hash). 48 Relation("Outputs").Relation("PGMetadataLike").Relation("PGMetadataFollow"). 49 Relation("PGMetadataCreatorCoin").Relation("PGMetadataCreatorCoinTransfer"). 50 Relation("PGMetadataSubmitPost").Select() 51 // TODO: Add NFTs 52 if err != nil { 53 return err 54 } 55 56 glog.Infof("Notifier: Found %d transactions in block %v at height %d", len(transactions), block.Hash, block.Height) 57 58 for _, transaction := range transactions { 59 if transaction.Type == TxnTypeBasicTransfer { 60 extraData := transaction.ExtraData 61 for _, output := range transaction.Outputs { 62 if !reflect.DeepEqual(output.PublicKey, transaction.PublicKey) { 63 notification := &PGNotification{ 64 TransactionHash: transaction.Hash, 65 Mined: true, 66 ToUser: output.PublicKey, 67 FromUser: transaction.PublicKey, 68 Type: NotificationSendDESO, 69 Amount: output.AmountNanos, 70 Timestamp: block.Timestamp, 71 } 72 diamondLevelBytes, hasDiamondLevel := extraData[DiamondLevelKey] 73 diamondPostBytes, hasDiamondPost := extraData[DiamondPostHashKey] 74 if hasDiamondLevel && hasDiamondPost { 75 diamondLevel, bytesRead := Varint(diamondLevelBytes) 76 if bytesRead > 0 { 77 notification.Type = NotificationDESODiamond 78 notification.Amount = uint64(diamondLevel) 79 notification.PostHash = &BlockHash{} 80 copy(notification.PostHash[:], diamondPostBytes) 81 } 82 } 83 notifications = append(notifications, notification) 84 } 85 } 86 } else if transaction.Type == TxnTypeLike { 87 postHash := transaction.MetadataLike.LikedPostHash 88 post := DBGetPostEntryByPostHash(notifier.badger, postHash) 89 if post != nil { 90 notifications = append(notifications, &PGNotification{ 91 TransactionHash: transaction.Hash, 92 Mined: true, 93 ToUser: post.PosterPublicKey, 94 FromUser: transaction.PublicKey, 95 Type: NotificationLike, 96 PostHash: postHash, 97 Timestamp: block.Timestamp, 98 }) 99 } 100 } else if transaction.Type == TxnTypeFollow { 101 if !transaction.MetadataFollow.IsUnfollow { 102 notifications = append(notifications, &PGNotification{ 103 TransactionHash: transaction.Hash, 104 Mined: true, 105 ToUser: transaction.MetadataFollow.FollowedPublicKey, 106 FromUser: transaction.PublicKey, 107 Type: NotificationFollow, 108 Timestamp: block.Timestamp, 109 }) 110 } 111 } else if transaction.Type == TxnTypeCreatorCoin { 112 meta := transaction.MetadataCreatorCoin 113 if meta.OperationType == CreatorCoinOperationTypeBuy { 114 notifications = append(notifications, &PGNotification{ 115 TransactionHash: transaction.Hash, 116 Mined: true, 117 ToUser: meta.ProfilePublicKey, 118 FromUser: transaction.PublicKey, 119 Type: NotificationCoinPurchase, 120 Amount: meta.DeSoToSellNanos, 121 Timestamp: block.Timestamp, 122 }) 123 } 124 } else if transaction.Type == TxnTypeCreatorCoinTransfer { 125 meta := transaction.MetadataCreatorCoinTransfer 126 extraData := transaction.ExtraData 127 notification := &PGNotification{ 128 TransactionHash: transaction.Hash, 129 Mined: true, 130 ToUser: meta.ReceiverPublicKey, 131 FromUser: transaction.PublicKey, 132 OtherUser: meta.ProfilePublicKey, 133 Timestamp: block.Timestamp, 134 } 135 136 diamondLevelBytes, hasDiamondLevel := extraData[DiamondLevelKey] 137 diamondPostBytes, hasDiamondPost := extraData[DiamondPostHashKey] 138 if hasDiamondLevel && hasDiamondPost { 139 diamondLevel, bytesRead := Varint(diamondLevelBytes) 140 if bytesRead > 0 { 141 notification.Type = NotificationCoinDiamond 142 notification.Amount = uint64(diamondLevel) 143 notification.PostHash = &BlockHash{} 144 copy(notification.PostHash[:], diamondPostBytes) 145 } 146 } 147 148 // If we failed to extract diamond metadata record it as a normal transfer 149 if notification.Type == NotificationUnknown { 150 notification.Type = NotificationCoinTransfer 151 notification.Amount = meta.CreatorCoinToTransferNanos 152 } 153 154 notifications = append(notifications, notification) 155 } else if transaction.Type == TxnTypeSubmitPost { 156 meta := transaction.MetadataSubmitPost 157 158 // Process replies 159 if len(meta.ParentStakeID) == HashSizeBytes { 160 postEntry := DBGetPostEntryByPostHash(notifier.badger, meta.ParentStakeID) 161 if postEntry != nil { 162 notifications = append(notifications, &PGNotification{ 163 TransactionHash: transaction.Hash, 164 Mined: true, 165 ToUser: postEntry.PosterPublicKey, 166 FromUser: transaction.PublicKey, 167 Type: NotificationPostReply, 168 PostHash: meta.ParentStakeID, 169 Timestamp: block.Timestamp, 170 }) 171 } 172 } 173 174 // Process mentions 175 bodyObj := &DeSoBodySchema{} 176 if err := json.Unmarshal(meta.Body, &bodyObj); err == nil { 177 terminators := []rune(" ,.\n&*()-+~'\"[]{}") 178 dollarTagsFound := mention.GetTagsAsUniqueStrings('$', string(bodyObj.Body), terminators...) 179 atTagsFound := mention.GetTagsAsUniqueStrings('@', string(bodyObj.Body), terminators...) 180 181 tagsFound := append(dollarTagsFound, atTagsFound...) 182 for _, tag := range tagsFound { 183 184 profileFound := DBGetProfileEntryForUsername(notifier.badger, []byte(strings.ToLower(strings.Trim(tag, ",.\n&*()-+~'\"[]{}!?^%#")))) 185 // Don't worry about tags that don't line up to a profile. 186 if profileFound == nil { 187 continue 188 } 189 190 notifications = append(notifications, &PGNotification{ 191 TransactionHash: transaction.Hash, 192 Mined: true, 193 ToUser: profileFound.PublicKey, 194 FromUser: transaction.PublicKey, 195 Type: NotificationPostMention, 196 PostHash: meta.PostHashToModify, 197 Timestamp: block.Timestamp, 198 }) 199 } 200 } 201 202 // Process reposts 203 if postBytes, isRepost := transaction.ExtraData[RepostedPostHash]; isRepost { 204 postHash := &BlockHash{} 205 copy(postHash[:], postBytes) 206 post := DBGetPostEntryByPostHash(notifier.badger, postHash) 207 if post != nil { 208 notifications = append(notifications, &PGNotification{ 209 TransactionHash: transaction.Hash, 210 Mined: true, 211 ToUser: post.PosterPublicKey, 212 FromUser: transaction.PublicKey, 213 Type: NotificationPostRepost, 214 PostHash: postHash, 215 Timestamp: block.Timestamp, 216 }) 217 } 218 } 219 } 220 } 221 222 // Insert the new notifications if we created any 223 if len(notifications) > 0 { 224 _, err = notifier.db.Model(¬ifications).OnConflict("DO NOTHING").Returning("NULL").Insert() 225 if err != nil { 226 return err 227 } 228 } 229 230 // Mark the block as notified 231 block.Notified = true 232 _, err = notifier.db.Model(block).WherePK().Column("notified").Returning("NULL").Update() 233 if err != nil { 234 return err 235 } 236 } 237 238 return nil 239 } 240 241 func (notifier *Notifier) notifyBasicTransfers() { 242 243 } 244 245 func (notifier *Notifier) Start() { 246 glog.Info("Notifier: Starting update thread") 247 248 // Run a loop to continuously process notifications 249 go func() { 250 for { 251 //if notifier.coreChain.ChainState() == SyncStateFullyCurrent { 252 // // If the node is fully synced, then try an update. 253 // err := notifier.Update() 254 // if err != nil { 255 // glog.Error(fmt.Errorf("Notifier: Problem running update: %v", err)) 256 // } 257 //} else { 258 // glog.V(1).Infof("Notifier: Waiting for node to sync before updating") 259 //} 260 261 err := notifier.Update() 262 if err != nil { 263 glog.Error(fmt.Errorf("Notifier: Problem running update: %v", err)) 264 } 265 time.Sleep(1 * time.Second) 266 } 267 }() 268 269 }