diff --git a/dapps/valuetransfers/packages/branchmanager/branchmanager.go b/dapps/valuetransfers/packages/branchmanager/branchmanager.go index 32093550960071f155715c04bb67d4fdc4082a5d..e2984d6851b6d18acea98adca37f6256a2351837 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 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 444d80ac73e9b3c70c995306e13214bd4cc9edd5..c19fa3530f86aa5d0d7dd60887d579fbe572d824 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 b4de9e257d1587cbb35d616a9271e8628b63d25b..d7430ee6145dd497815f3c66f76f732cb3da4550 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 d60a949420a3fb5f13c3014fe38ad95e13b6713f..11ce6e61e28a5c4010da0d102367f57707b796aa 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 c8a63f48fe5b365c0ffd61b4c84304a702103879..f584138ae0b5d02e4241414be9836b15780b1a84 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 af40906b1d34b7c789de251975a4476e7e9a08e7..f7fe65d00edf33d3d87ed3d1a672a383b049522b 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 8b1a8b9d3643d5446bba4477ea0b104df23f28a7..3ad8aa7533d362e049ece4ba2c5e19a68ac19906 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)) })