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