diff --git a/dapps/valuetransfers/packages/consensus/fcob.go b/dapps/valuetransfers/packages/consensus/fcob.go index 00722a7d17f03225e10dd4adc537a4608e429ea8..d5b86548b57dc8c2eeb850a6ab5d7aa6cd564e44 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 f872cea9d170b5c26f218366b35c76ad094c1648..73c81755e4ea7a3774af3a4fd9fb89eb6248a3cb 100644 --- a/dapps/valuetransfers/packages/tangle/events.go +++ b/dapps/valuetransfers/packages/tangle/events.go @@ -37,6 +37,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 @@ -80,7 +88,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 118f56a1ede50d71ee6906077b9ff5ef313ebed4..faad3a5ca3db3f1d8ea9858477307a2413a8eb4b 100644 --- a/dapps/valuetransfers/packages/tangle/output.go +++ b/dapps/valuetransfers/packages/tangle/output.go @@ -25,12 +25,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 @@ -226,6 +232,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/tangle.go b/dapps/valuetransfers/packages/tangle/tangle.go index 349b05a8a837c8771765cfb21ef9a6381c8f46ff..9390b4b960a508e5e074a2b293cd52d19ffc6be7 100644 --- a/dapps/valuetransfers/packages/tangle/tangle.go +++ b/dapps/valuetransfers/packages/tangle/tangle.go @@ -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,7 +679,21 @@ 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 !currentPayloadMetadata.setRejected(true) { @@ -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 34d941c8dce31403ccd907c7c003370ecdead69f..01b161613ae30fcb8d4a98e2e337c3a9723f370e 100644 --- a/dapps/valuetransfers/packages/tangle/transactionmetadata.go +++ b/dapps/valuetransfers/packages/tangle/transactionmetadata.go @@ -21,6 +21,9 @@ type TransactionMetadata struct { solid bool preferred bool finalized bool + liked bool + confirmed bool + rejected bool solidificationTime time.Time finalizationTime time.Time @@ -28,6 +31,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 } @@ -243,6 +249,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() @@ -261,13 +363,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() } @@ -319,6 +424,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