From c6bce7fada5accfe3e4b900edc9e08d15c0b96a1 Mon Sep 17 00:00:00 2001
From: Hans Moog <3293976+hmoog@users.noreply.github.com>
Date: Thu, 11 Jun 2020 13:48:40 +0200
Subject: [PATCH] Feat: Propagate inclusion state to transactions for faster
 lookup (#461)

* Fix: fixed some bugs

* Feat: added propagation to inclusion states to tx and its outputs

* Feat: finished the propagation down to the tx and its outputs

Co-authored-by: Hans Moog <hm@mkjc.net>
---
 .../packages/branchmanager/branchmanager.go   |   4 +-
 .../valuetransfers/packages/consensus/fcob.go |   4 +
 .../valuetransfers/packages/tangle/events.go  |  12 ++
 .../valuetransfers/packages/tangle/output.go  | 102 +++++++++++++++
 .../packages/tangle/payloadmetadata.go        |   2 +-
 .../valuetransfers/packages/tangle/tangle.go  | 116 +++++++++++++++++-
 .../packages/tangle/transactionmetadata.go    | 116 +++++++++++++++++-
 .../packages/transaction/transaction.go       |   2 +-
 8 files changed, 352 insertions(+), 6 deletions(-)

diff --git a/dapps/valuetransfers/packages/branchmanager/branchmanager.go b/dapps/valuetransfers/packages/branchmanager/branchmanager.go
index 32093550..e2984d68 100644
--- a/dapps/valuetransfers/packages/branchmanager/branchmanager.go
+++ b/dapps/valuetransfers/packages/branchmanager/branchmanager.go
@@ -409,7 +409,7 @@ func (branchManager *BranchManager) propagateRejectedToChildBranches(cachedBranc
 		branchStack.Remove(currentStackElement)
 
 		currentBranch := currentCachedBranch.Unwrap()
-		if currentBranch == nil || !currentBranch.setRejected(false) {
+		if currentBranch == nil || !currentBranch.setRejected(true) {
 			currentCachedBranch.Release()
 
 			continue
@@ -498,6 +498,8 @@ func (branchManager *BranchManager) init() {
 	cachedBranch.Consume(func(branch *Branch) {
 		branch.setPreferred(true)
 		branch.setLiked(true)
+		branch.setFinalized(true)
+		branch.setConfirmed(true)
 	})
 }
 
diff --git a/dapps/valuetransfers/packages/consensus/fcob.go b/dapps/valuetransfers/packages/consensus/fcob.go
index 00722a7d..d5b86548 100644
--- a/dapps/valuetransfers/packages/consensus/fcob.go
+++ b/dapps/valuetransfers/packages/consensus/fcob.go
@@ -52,6 +52,10 @@ func (fcob *FCOB) ProcessVoteResult(id string, opinion vote.Opinion) {
 	if _, err := fcob.tangle.SetTransactionPreferred(transactionID, opinion == vote.Like); err != nil {
 		fcob.Events.Error.Trigger(err)
 	}
+
+	if _, err := fcob.tangle.SetTransactionFinalized(transactionID); err != nil {
+		fcob.Events.Error.Trigger(err)
+	}
 }
 
 // onTransactionBooked analyzes the transaction that was booked by the Tangle and initiates the FCOB rules if it is not
diff --git a/dapps/valuetransfers/packages/tangle/events.go b/dapps/valuetransfers/packages/tangle/events.go
index 444d80ac..c19fa353 100644
--- a/dapps/valuetransfers/packages/tangle/events.go
+++ b/dapps/valuetransfers/packages/tangle/events.go
@@ -31,6 +31,14 @@ type Events struct {
 
 	TransactionUnpreferred *events.Event
 
+	TransactionLiked *events.Event
+
+	TransactionDisliked *events.Event
+
+	TransactionConfirmed *events.Event
+
+	TransactionRejected *events.Event
+
 	TransactionFinalized *events.Event
 
 	// Fork gets triggered when a previously un-conflicting transaction get's some of its inputs double spend, so that a
@@ -71,7 +79,11 @@ func newEvents() *Events {
 		TransactionBooked:      events.NewEvent(transactionBookedEvent),
 		TransactionPreferred:   events.NewEvent(cachedTransactionEvent),
 		TransactionUnpreferred: events.NewEvent(cachedTransactionEvent),
+		TransactionLiked:       events.NewEvent(cachedTransactionEvent),
+		TransactionDisliked:    events.NewEvent(cachedTransactionEvent),
 		TransactionFinalized:   events.NewEvent(cachedTransactionEvent),
+		TransactionConfirmed:   events.NewEvent(cachedTransactionEvent),
+		TransactionRejected:    events.NewEvent(cachedTransactionEvent),
 		Fork:                   events.NewEvent(forkEvent),
 		Error:                  events.NewEvent(events.ErrorCaller),
 	}
diff --git a/dapps/valuetransfers/packages/tangle/output.go b/dapps/valuetransfers/packages/tangle/output.go
index b4de9e25..d7430ee6 100644
--- a/dapps/valuetransfers/packages/tangle/output.go
+++ b/dapps/valuetransfers/packages/tangle/output.go
@@ -26,12 +26,18 @@ type Output struct {
 	solidificationTime time.Time
 	firstConsumer      transaction.ID
 	consumerCount      int
+	liked              bool
+	confirmed          bool
+	rejected           bool
 	balances           []*balance.Balance
 
 	branchIDMutex           sync.RWMutex
 	solidMutex              sync.RWMutex
 	solidificationTimeMutex sync.RWMutex
 	consumerMutex           sync.RWMutex
+	likedMutex              sync.RWMutex
+	confirmedMutex          sync.RWMutex
+	rejectedMutex           sync.RWMutex
 
 	objectstorage.StorableObjectFlags
 	storageKey []byte
@@ -227,6 +233,102 @@ func (output *Output) ConsumerCount() int {
 	return output.consumerCount
 }
 
+// Liked returns true if the Output was marked as liked.
+func (output *Output) Liked() bool {
+	output.likedMutex.RLock()
+	defer output.likedMutex.RUnlock()
+
+	return output.liked
+}
+
+// setLiked modifies the liked flag of the given Output. It returns true if the value has been updated.
+func (output *Output) setLiked(liked bool) (modified bool) {
+	output.likedMutex.RLock()
+	if output.liked == liked {
+		output.likedMutex.RUnlock()
+
+		return
+	}
+
+	output.likedMutex.RUnlock()
+	output.likedMutex.Lock()
+	defer output.likedMutex.Unlock()
+
+	if output.liked == liked {
+		return
+	}
+
+	output.liked = liked
+	output.SetModified()
+	modified = true
+
+	return
+}
+
+// Confirmed returns true if the Output was marked as confirmed.
+func (output *Output) Confirmed() bool {
+	output.confirmedMutex.RLock()
+	defer output.confirmedMutex.RUnlock()
+
+	return output.confirmed
+}
+
+// setConfirmed modifies the confirmed flag of the given Output. It returns true if the value has been updated.
+func (output *Output) setConfirmed(confirmed bool) (modified bool) {
+	output.confirmedMutex.RLock()
+	if output.confirmed == confirmed {
+		output.confirmedMutex.RUnlock()
+
+		return
+	}
+
+	output.confirmedMutex.RUnlock()
+	output.confirmedMutex.Lock()
+	defer output.confirmedMutex.Unlock()
+
+	if output.confirmed == confirmed {
+		return
+	}
+
+	output.confirmed = confirmed
+	output.SetModified()
+	modified = true
+
+	return
+}
+
+// Rejected returns true if the Output was marked as confirmed.
+func (output *Output) Rejected() bool {
+	output.rejectedMutex.RLock()
+	defer output.rejectedMutex.RUnlock()
+
+	return output.rejected
+}
+
+// setRejected modifies the rejected flag of the given Output. It returns true if the value has been updated.
+func (output *Output) setRejected(rejected bool) (modified bool) {
+	output.rejectedMutex.RLock()
+	if output.rejected == rejected {
+		output.rejectedMutex.RUnlock()
+
+		return
+	}
+
+	output.rejectedMutex.RUnlock()
+	output.rejectedMutex.Lock()
+	defer output.rejectedMutex.Unlock()
+
+	if output.rejected == rejected {
+		return
+	}
+
+	output.rejected = rejected
+	output.SetModified()
+	modified = true
+
+	return
+}
+
 // Balances returns the colored balances (color + balance) that this output contains.
 func (output *Output) Balances() []*balance.Balance {
 	return output.balances
diff --git a/dapps/valuetransfers/packages/tangle/payloadmetadata.go b/dapps/valuetransfers/packages/tangle/payloadmetadata.go
index d60a9494..11ce6e61 100644
--- a/dapps/valuetransfers/packages/tangle/payloadmetadata.go
+++ b/dapps/valuetransfers/packages/tangle/payloadmetadata.go
@@ -355,7 +355,7 @@ func (cachedPayloadMetadata *CachedPayloadMetadata) Retain() *CachedPayloadMetad
 }
 
 // Consume wraps the underlying method to return the correctly typed objects in the callback.
-func (cachedPayloadMetadata *CachedPayloadMetadata) Consume(consumer func(payload *PayloadMetadata)) bool {
+func (cachedPayloadMetadata *CachedPayloadMetadata) Consume(consumer func(payloadMetadata *PayloadMetadata)) bool {
 	return cachedPayloadMetadata.CachedObject.Consume(func(object objectstorage.StorableObject) {
 		consumer(object.(*PayloadMetadata))
 	})
diff --git a/dapps/valuetransfers/packages/tangle/tangle.go b/dapps/valuetransfers/packages/tangle/tangle.go
index c8a63f48..f584138a 100644
--- a/dapps/valuetransfers/packages/tangle/tangle.go
+++ b/dapps/valuetransfers/packages/tangle/tangle.go
@@ -148,7 +148,7 @@ func (tangle *Tangle) ValuePayloadsConfirmed(payloadIDs ...payload.ID) (confirme
 		}
 
 		payloadMetadataFound := tangle.PayloadMetadata(payloadID).Consume(func(payloadMetadata *PayloadMetadata) {
-			confirmed = payloadMetadata.Liked()
+			confirmed = payloadMetadata.Confirmed()
 		})
 
 		if !payloadMetadataFound || !confirmed {
@@ -555,6 +555,11 @@ func (tangle *Tangle) setTransactionFinalized(transactionID transaction.ID, even
 			// trigger the corresponding event
 			tangle.Events.TransactionFinalized.Trigger(cachedTransaction, cachedTransactionMetadata)
 
+			// propagate the rejected flag
+			if !metadata.Preferred() {
+				tangle.propagateRejectedToTransactions(metadata.ID())
+			}
+
 			// propagate changes to value tangle and branch DAG if we were called from the tangle
 			// Note: if the update was triggered by a change in the branch DAG then we do not propagate the confirmed
 			//       and rejected changes yet as those require the branch to be liked before (we instead do it in the
@@ -579,6 +584,59 @@ func (tangle *Tangle) setTransactionFinalized(transactionID transaction.ID, even
 	return
 }
 
+// propagateRejectedToTransactions propagates the rejected flag to a transaction, its outputs and to its consumers.
+func (tangle *Tangle) propagateRejectedToTransactions(transactionID transaction.ID) {
+	// initialize stack with first transaction
+	rejectedPropagationStack := list.New()
+	rejectedPropagationStack.PushBack(transactionID)
+
+	// keep track of the added transactions so we don't add them multiple times
+	addedTransaction := make(map[transaction.ID]types.Empty)
+
+	// work through stack
+	for rejectedPropagationStack.Len() >= 1 {
+		// pop the first element from the stack
+		firstElement := rejectedPropagationStack.Front()
+		rejectedPropagationStack.Remove(firstElement)
+		currentTransactionID := firstElement.Value.(transaction.ID)
+
+		cachedTransactionMetadata := tangle.TransactionMetadata(currentTransactionID)
+		cachedTransactionMetadata.Consume(func(metadata *TransactionMetadata) {
+			if !metadata.setRejected(true) {
+				return
+			}
+
+			cachedTransaction := tangle.Transaction(currentTransactionID)
+			cachedTransaction.Consume(func(tx *transaction.Transaction) {
+				// process all outputs
+				tx.Outputs().ForEach(func(address address.Address, balances []*balance.Balance) bool {
+					outputID := transaction.NewOutputID(address, currentTransactionID)
+
+					// mark the output to be rejected
+					tangle.TransactionOutput(outputID).Consume(func(output *Output) {
+						output.setRejected(true)
+					})
+
+					// queue consumers to also be rejected
+					tangle.Consumers(outputID).Consume(func(consumer *Consumer) {
+						if _, transactionAdded := addedTransaction[consumer.TransactionID()]; transactionAdded {
+							return
+						}
+						addedTransaction[consumer.TransactionID()] = types.Void
+
+						rejectedPropagationStack.PushBack(consumer.TransactionID())
+					})
+
+					return true
+				})
+
+				// trigger event
+				tangle.Events.TransactionRejected.Trigger(cachedTransaction, cachedTransactionMetadata)
+			})
+		})
+	}
+}
+
 // TODO: WRITE COMMENT
 func (tangle *Tangle) propagateValuePayloadConfirmedRejectedUpdates(transactionID transaction.ID) {
 	// initiate stack with the attachments of the passed in transaction
@@ -621,10 +679,24 @@ func (tangle *Tangle) propagateValuePayloadConfirmedRejectedUpdateStackEntry(pro
 			return
 		}
 
+		// trigger payload event
 		tangle.Events.PayloadConfirmed.Trigger(propagationStackEntry.CachedPayload, propagationStackEntry.CachedPayloadMetadata)
+
+		// propagate confirmed status to transaction and its outputs
+		if currentTransactionMetadata.setConfirmed(true) {
+			currentTransaction.Outputs().ForEach(func(address address.Address, balances []*balance.Balance) bool {
+				tangle.TransactionOutput(transaction.NewOutputID(address, currentTransaction.ID())).Consume(func(output *Output) {
+					output.setConfirmed(true)
+				})
+
+				return true
+			})
+
+			tangle.Events.TransactionConfirmed.Trigger(propagationStackEntry.CachedTransaction, propagationStackEntry.CachedTransactionMetadata)
+		}
 	case false:
 		// abort if the payload has been marked as disliked before
-		if !currentTransactionMetadata.Finalized() || !currentPayloadMetadata.setRejected(true) {
+		if !currentPayloadMetadata.setRejected(true) {
 			return
 		}
 
@@ -732,7 +804,22 @@ func (tangle *Tangle) processValuePayloadLikedUpdateStackEntry(propagationStack
 			return
 		}
 
+		// trigger payload event
 		tangle.Events.PayloadLiked.Trigger(propagationStackEntry.CachedPayload, propagationStackEntry.CachedPayloadMetadata)
+
+		// propagate liked to transaction and its outputs
+		if currentTransactionMetadata.setLiked(true) {
+			currentTransaction.Outputs().ForEach(func(address address.Address, balances []*balance.Balance) bool {
+				tangle.TransactionOutput(transaction.NewOutputID(address, currentTransaction.ID())).Consume(func(output *Output) {
+					output.setLiked(true)
+				})
+
+				return true
+			})
+
+			// trigger event
+			tangle.Events.TransactionLiked.Trigger(propagationStackEntry.CachedTransaction, propagationStackEntry.CachedTransactionMetadata)
+		}
 	case false:
 		// abort if the payload has been marked as disliked before
 		if !currentPayloadMetadata.setLiked(liked) {
@@ -740,6 +827,31 @@ func (tangle *Tangle) processValuePayloadLikedUpdateStackEntry(propagationStack
 		}
 
 		tangle.Events.PayloadDisliked.Trigger(propagationStackEntry.CachedPayload, propagationStackEntry.CachedPayloadMetadata)
+
+		// look if we still have any liked attachments of this transaction
+		likedAttachmentFound := false
+		tangle.Attachments(currentTransaction.ID()).Consume(func(attachment *Attachment) {
+			tangle.PayloadMetadata(attachment.PayloadID()).Consume(func(payloadMetadata *PayloadMetadata) {
+				likedAttachmentFound = likedAttachmentFound || payloadMetadata.Liked()
+			})
+		})
+
+		// if there are no other liked attachments of this transaction then also set it to disliked
+		if !likedAttachmentFound {
+			// propagate disliked to transaction and its outputs
+			if currentTransactionMetadata.setLiked(false) {
+				currentTransaction.Outputs().ForEach(func(address address.Address, balances []*balance.Balance) bool {
+					tangle.TransactionOutput(transaction.NewOutputID(address, currentTransaction.ID())).Consume(func(output *Output) {
+						output.setLiked(false)
+					})
+
+					return true
+				})
+
+				// trigger event
+				tangle.Events.TransactionDisliked.Trigger(propagationStackEntry.CachedTransaction, propagationStackEntry.CachedTransactionMetadata)
+			}
+		}
 	}
 
 	// schedule checks of approvers and consumers
diff --git a/dapps/valuetransfers/packages/tangle/transactionmetadata.go b/dapps/valuetransfers/packages/tangle/transactionmetadata.go
index af40906b..f7fe65d0 100644
--- a/dapps/valuetransfers/packages/tangle/transactionmetadata.go
+++ b/dapps/valuetransfers/packages/tangle/transactionmetadata.go
@@ -22,6 +22,9 @@ type TransactionMetadata struct {
 	solid              bool
 	preferred          bool
 	finalized          bool
+	liked              bool
+	confirmed          bool
+	rejected           bool
 	solidificationTime time.Time
 	finalizationTime   time.Time
 
@@ -29,6 +32,9 @@ type TransactionMetadata struct {
 	solidMutex              sync.RWMutex
 	preferredMutex          sync.RWMutex
 	finalizedMutex          sync.RWMutex
+	likedMutex              sync.RWMutex
+	confirmedMutex          sync.RWMutex
+	rejectedMutex           sync.RWMutex
 	solidificationTimeMutex sync.RWMutex
 }
 
@@ -244,6 +250,102 @@ func (transactionMetadata *TransactionMetadata) Finalized() bool {
 	return transactionMetadata.finalized
 }
 
+// Liked returns true if the Transaction was marked as liked.
+func (transactionMetadata *TransactionMetadata) Liked() bool {
+	transactionMetadata.likedMutex.RLock()
+	defer transactionMetadata.likedMutex.RUnlock()
+
+	return transactionMetadata.liked
+}
+
+// setLiked modifies the liked flag of the given Transaction. It returns true if the value has been updated.
+func (transactionMetadata *TransactionMetadata) setLiked(liked bool) (modified bool) {
+	transactionMetadata.likedMutex.RLock()
+	if transactionMetadata.liked == liked {
+		transactionMetadata.likedMutex.RUnlock()
+
+		return
+	}
+
+	transactionMetadata.likedMutex.RUnlock()
+	transactionMetadata.likedMutex.Lock()
+	defer transactionMetadata.likedMutex.Unlock()
+
+	if transactionMetadata.liked == liked {
+		return
+	}
+
+	transactionMetadata.liked = liked
+	transactionMetadata.SetModified()
+	modified = true
+
+	return
+}
+
+// Confirmed returns true if the Transaction was marked as confirmed.
+func (transactionMetadata *TransactionMetadata) Confirmed() bool {
+	transactionMetadata.confirmedMutex.RLock()
+	defer transactionMetadata.confirmedMutex.RUnlock()
+
+	return transactionMetadata.confirmed
+}
+
+// setConfirmed modifies the confirmed flag of the given Transaction. It returns true if the value has been updated.
+func (transactionMetadata *TransactionMetadata) setConfirmed(confirmed bool) (modified bool) {
+	transactionMetadata.confirmedMutex.RLock()
+	if transactionMetadata.confirmed == confirmed {
+		transactionMetadata.confirmedMutex.RUnlock()
+
+		return
+	}
+
+	transactionMetadata.confirmedMutex.RUnlock()
+	transactionMetadata.confirmedMutex.Lock()
+	defer transactionMetadata.confirmedMutex.Unlock()
+
+	if transactionMetadata.confirmed == confirmed {
+		return
+	}
+
+	transactionMetadata.confirmed = confirmed
+	transactionMetadata.SetModified()
+	modified = true
+
+	return
+}
+
+// Rejected returns true if the Transaction was marked as confirmed.
+func (transactionMetadata *TransactionMetadata) Rejected() bool {
+	transactionMetadata.rejectedMutex.RLock()
+	defer transactionMetadata.rejectedMutex.RUnlock()
+
+	return transactionMetadata.rejected
+}
+
+// setRejected modifies the rejected flag of the given Transaction. It returns true if the value has been updated.
+func (transactionMetadata *TransactionMetadata) setRejected(rejected bool) (modified bool) {
+	transactionMetadata.rejectedMutex.RLock()
+	if transactionMetadata.rejected == rejected {
+		transactionMetadata.rejectedMutex.RUnlock()
+
+		return
+	}
+
+	transactionMetadata.rejectedMutex.RUnlock()
+	transactionMetadata.rejectedMutex.Lock()
+	defer transactionMetadata.rejectedMutex.Unlock()
+
+	if transactionMetadata.rejected == rejected {
+		return
+	}
+
+	transactionMetadata.rejected = rejected
+	transactionMetadata.SetModified()
+	modified = true
+
+	return
+}
+
 // FinalizationTime returns the time when this transaction was finalized.
 func (transactionMetadata *TransactionMetadata) FinalizationTime() time.Time {
 	transactionMetadata.finalizedMutex.RLock()
@@ -262,13 +364,16 @@ func (transactionMetadata *TransactionMetadata) SoldificationTime() time.Time {
 
 // Bytes marshals the TransactionMetadata object into a sequence of bytes.
 func (transactionMetadata *TransactionMetadata) Bytes() []byte {
-	return marshalutil.New(branchmanager.BranchIDLength + 2*marshalutil.TIME_SIZE + 3*marshalutil.BOOL_SIZE).
+	return marshalutil.New(branchmanager.BranchIDLength + 2*marshalutil.TIME_SIZE + 6*marshalutil.BOOL_SIZE).
 		WriteBytes(transactionMetadata.BranchID().Bytes()).
 		WriteTime(transactionMetadata.SoldificationTime()).
 		WriteTime(transactionMetadata.FinalizationTime()).
 		WriteBool(transactionMetadata.Solid()).
 		WriteBool(transactionMetadata.Preferred()).
 		WriteBool(transactionMetadata.Finalized()).
+		WriteBool(transactionMetadata.Liked()).
+		WriteBool(transactionMetadata.Confirmed()).
+		WriteBool(transactionMetadata.Rejected()).
 		Bytes()
 }
 
@@ -320,6 +425,15 @@ func (transactionMetadata *TransactionMetadata) UnmarshalObjectStorageValue(data
 	if transactionMetadata.finalized, err = marshalUtil.ReadBool(); err != nil {
 		return
 	}
+	if transactionMetadata.liked, err = marshalUtil.ReadBool(); err != nil {
+		return
+	}
+	if transactionMetadata.confirmed, err = marshalUtil.ReadBool(); err != nil {
+		return
+	}
+	if transactionMetadata.rejected, err = marshalUtil.ReadBool(); err != nil {
+		return
+	}
 	consumedBytes = marshalUtil.ReadOffset()
 
 	return
diff --git a/dapps/valuetransfers/packages/transaction/transaction.go b/dapps/valuetransfers/packages/transaction/transaction.go
index 8b1a8b9d..3ad8aa75 100644
--- a/dapps/valuetransfers/packages/transaction/transaction.go
+++ b/dapps/valuetransfers/packages/transaction/transaction.go
@@ -436,7 +436,7 @@ func (cachedTransaction *CachedTransaction) Retain() *CachedTransaction {
 
 // Consume  overrides the underlying method to use a CachedTransaction object instead of a generic CachedObject in the
 // consumer).
-func (cachedTransaction *CachedTransaction) Consume(consumer func(metadata *Transaction)) bool {
+func (cachedTransaction *CachedTransaction) Consume(consumer func(tx *Transaction)) bool {
 	return cachedTransaction.CachedObject.Consume(func(object objectstorage.StorableObject) {
 		consumer(object.(*Transaction))
 	})
-- 
GitLab