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