diff --git a/dapps/valuetransfers/packages/tangle/output.go b/dapps/valuetransfers/packages/tangle/output.go index faad3a5ca3db3f1d8ea9858477307a2413a8eb4b..5d19ec51f12cd50a29abc7de98e279a8b8d5cb8b 100644 --- a/dapps/valuetransfers/packages/tangle/output.go +++ b/dapps/valuetransfers/packages/tangle/output.go @@ -25,6 +25,8 @@ type Output struct { solidificationTime time.Time firstConsumer transaction.ID consumerCount int + preferred bool + finalized bool liked bool confirmed bool rejected bool @@ -34,6 +36,8 @@ type Output struct { solidMutex sync.RWMutex solidificationTimeMutex sync.RWMutex consumerMutex sync.RWMutex + preferredMutex sync.RWMutex + finalizedMutex sync.RWMutex likedMutex sync.RWMutex confirmedMutex sync.RWMutex rejectedMutex sync.RWMutex @@ -140,8 +144,8 @@ func (output *Output) BranchID() branchmanager.BranchID { return output.branchID } -// SetBranchID is the setter for the property that indicates in which ledger state branch the output is booked. -func (output *Output) SetBranchID(branchID branchmanager.BranchID) (modified bool) { +// setBranchID is the setter for the property that indicates in which ledger state branch the output is booked. +func (output *Output) setBranchID(branchID branchmanager.BranchID) (modified bool) { output.branchIDMutex.RLock() if output.branchID == branchID { output.branchIDMutex.RUnlock() @@ -232,6 +236,73 @@ func (output *Output) ConsumerCount() int { return output.consumerCount } +// Preferred returns true if the output belongs to a preferred transaction. +func (output *Output) Preferred() (result bool) { + output.preferredMutex.RLock() + defer output.preferredMutex.RUnlock() + + return output.preferred +} + +// setPreferred updates the preferred flag of the output. It is defined as a private setter because updating the +// preferred flag causes changes in other outputs and branches as well. This means that we need additional logic +// in the tangle. To update the preferred flag of a output, we need to use Tangle.SetTransactionPreferred(bool). +func (output *Output) setPreferred(preferred bool) (modified bool) { + output.preferredMutex.RLock() + if output.preferred == preferred { + output.preferredMutex.RUnlock() + + return + } + + output.preferredMutex.RUnlock() + output.preferredMutex.Lock() + defer output.preferredMutex.Unlock() + + if output.preferred == preferred { + return + } + + output.preferred = preferred + output.SetModified() + modified = true + + return +} + +// setFinalized allows us to set the finalized flag on the outputs. Finalized outputs will not be forked when +// a conflict arrives later. +func (output *Output) setFinalized(finalized bool) (modified bool) { + output.finalizedMutex.RLock() + if output.finalized == finalized { + output.finalizedMutex.RUnlock() + + return + } + + output.finalizedMutex.RUnlock() + output.finalizedMutex.Lock() + defer output.finalizedMutex.Unlock() + + if output.finalized == finalized { + return + } + + output.finalized = finalized + output.SetModified() + modified = true + + return +} + +// Finalized returns true, if the decision if this output is preferred or not has been finalized by consensus already. +func (output *Output) Finalized() bool { + output.finalizedMutex.RLock() + defer output.finalizedMutex.RUnlock() + + return output.finalized +} + // Liked returns true if the Output was marked as liked. func (output *Output) Liked() bool { output.likedMutex.RLock() @@ -357,12 +428,17 @@ func (output *Output) ObjectStorageValue() []byte { balanceCount := len(output.balances) // initialize helper - marshalUtil := marshalutil.New(branchmanager.BranchIDLength + marshalutil.BOOL_SIZE + marshalutil.TIME_SIZE + transaction.IDLength + marshalutil.UINT32_SIZE + marshalutil.UINT32_SIZE + balanceCount*balance.Length) + marshalUtil := marshalutil.New(branchmanager.BranchIDLength + 6*marshalutil.BOOL_SIZE + marshalutil.TIME_SIZE + transaction.IDLength + marshalutil.UINT32_SIZE + marshalutil.UINT32_SIZE + balanceCount*balance.Length) marshalUtil.WriteBytes(output.branchID.Bytes()) marshalUtil.WriteBool(output.solid) marshalUtil.WriteTime(output.solidificationTime) marshalUtil.WriteBytes(output.firstConsumer.Bytes()) marshalUtil.WriteUint32(uint32(output.consumerCount)) + marshalUtil.WriteBool(output.Preferred()) + marshalUtil.WriteBool(output.Finalized()) + marshalUtil.WriteBool(output.Liked()) + marshalUtil.WriteBool(output.Confirmed()) + marshalUtil.WriteBool(output.Rejected()) marshalUtil.WriteUint32(uint32(balanceCount)) for _, balanceToMarshal := range output.balances { marshalUtil.WriteBytes(balanceToMarshal.Bytes()) @@ -391,6 +467,21 @@ func (output *Output) UnmarshalObjectStorageValue(data []byte) (consumedBytes in if err != nil { return } + if output.preferred, err = marshalUtil.ReadBool(); err != nil { + return + } + if output.finalized, err = marshalUtil.ReadBool(); err != nil { + return + } + if output.liked, err = marshalUtil.ReadBool(); err != nil { + return + } + if output.confirmed, err = marshalUtil.ReadBool(); err != nil { + return + } + if output.rejected, err = marshalUtil.ReadBool(); err != nil { + return + } output.consumerCount = int(consumerCount) balanceCount, err := marshalUtil.ReadUint32() if err != nil { diff --git a/dapps/valuetransfers/packages/tangle/payloadmetadata.go b/dapps/valuetransfers/packages/tangle/payloadmetadata.go index 11ce6e61e28a5c4010da0d102367f57707b796aa..6c65c2ac5e1fa48f66dc64380021bef63525cd89 100644 --- a/dapps/valuetransfers/packages/tangle/payloadmetadata.go +++ b/dapps/valuetransfers/packages/tangle/payloadmetadata.go @@ -249,8 +249,8 @@ func (payloadMetadata *PayloadMetadata) BranchID() branchmanager.BranchID { return payloadMetadata.branchID } -// SetBranchID is the setter for the BranchID that the corresponding Payload is booked into. -func (payloadMetadata *PayloadMetadata) SetBranchID(branchID branchmanager.BranchID) (modified bool) { +// setBranchID is the setter for the BranchID that the corresponding Payload is booked into. +func (payloadMetadata *PayloadMetadata) setBranchID(branchID branchmanager.BranchID) (modified bool) { payloadMetadata.branchIDMutex.RLock() if branchID == payloadMetadata.branchID { payloadMetadata.branchIDMutex.RUnlock() diff --git a/dapps/valuetransfers/packages/tangle/tangle.go b/dapps/valuetransfers/packages/tangle/tangle.go index cdfca6eaf489685b427b10120737ad92acf5fe73..0566e3ac463578d839b2b3c7047b9a8975199227 100644 --- a/dapps/valuetransfers/packages/tangle/tangle.go +++ b/dapps/valuetransfers/packages/tangle/tangle.go @@ -170,7 +170,7 @@ func (tangle *Tangle) LoadSnapshot(snapshot map[transaction.ID]map[address.Addre for outputAddress, balances := range addressBalances { input := NewOutput(outputAddress, transactionID, branchmanager.MasterBranchID, balances) input.setSolid(true) - input.SetBranchID(branchmanager.MasterBranchID) + input.setBranchID(branchmanager.MasterBranchID) // store output and abort if the snapshot has already been loaded earlier (output exists in the database) cachedOutput, stored := tangle.outputStorage.StoreIfAbsent(input) @@ -543,10 +543,21 @@ func (tangle *Tangle) setTransactionFinalized(transactionID transaction.ID, even cachedTransactionMetadata := tangle.TransactionMetadata(transactionID) cachedTransactionMetadata.Consume(func(metadata *TransactionMetadata) { // update the finalized flag of the transaction - modified = metadata.SetFinalized(true) + modified = metadata.setFinalized(true) // only propagate the changes if the flag was modified if modified { + // set outputs to be finalized as well + tangle.Transaction(transactionID).Consume(func(tx *transaction.Transaction) { + tx.Outputs().ForEach(func(address address.Address, balances []*balance.Balance) bool { + tangle.TransactionOutput(transaction.NewOutputID(address, transactionID)).Consume(func(output *Output) { + output.setFinalized(true) + }) + + return true + }) + }) + // retrieve transaction from the database (for the events) cachedTransaction := tangle.Transaction(transactionID) defer cachedTransaction.Release() @@ -608,22 +619,36 @@ func (tangle *Tangle) propagateRejectedToTransactions(transactionID transaction. cachedTransactionMetadata := tangle.TransactionMetadata(currentTransactionID) cachedTransactionMetadata.Consume(func(metadata *TransactionMetadata) { - if !metadata.setRejected(true) { - return - } - metadata.setPreferred(false) + cachedTransaction := tangle.Transaction(currentTransactionID) + cachedTransaction.Consume(func(tx *transaction.Transaction) { + if !metadata.setRejected(true) { + return + } - // if the transaction is not finalized, yet then we set it to finalized - if !metadata.Finalized() { - if _, err := tangle.setTransactionFinalized(metadata.ID(), EventSourceTangle); err != nil { - tangle.Events.Error.Trigger(err) + if metadata.setPreferred(false) { + // set outputs to be not preferred as well + tangle.Transaction(currentTransactionID).Consume(func(tx *transaction.Transaction) { + tx.Outputs().ForEach(func(address address.Address, balances []*balance.Balance) bool { + tangle.TransactionOutput(transaction.NewOutputID(address, currentTransactionID)).Consume(func(output *Output) { + output.setPreferred(false) + }) - return + return true + }) + }) + + tangle.Events.TransactionUnpreferred.Trigger(cachedTransaction, cachedTransactionMetadata) + } + + // if the transaction is not finalized, yet then we set it to finalized + if !metadata.Finalized() { + if _, err := tangle.setTransactionFinalized(metadata.ID(), EventSourceTangle); err != nil { + tangle.Events.Error.Trigger(err) + + 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) @@ -740,6 +765,17 @@ func (tangle *Tangle) setTransactionPreferred(transactionID transaction.ID, pref // only do something if the flag was modified if modified { + // update outputs as well + tangle.Transaction(transactionID).Consume(func(tx *transaction.Transaction) { + tx.Outputs().ForEach(func(address address.Address, balances []*balance.Balance) bool { + tangle.TransactionOutput(transaction.NewOutputID(address, transactionID)).Consume(func(output *Output) { + output.setPreferred(preferred) + }) + + return true + }) + }) + // retrieve transaction from the database (for the events) cachedTransaction := tangle.Transaction(transactionID) defer cachedTransaction.Release() @@ -1219,7 +1255,7 @@ func (tangle *Tangle) bookTransaction(cachedTransaction *transaction.CachedTrans } // abort if transaction was marked as solid before - if !transactionMetadata.SetSolid(true) { + if !transactionMetadata.setSolid(true) { return } @@ -1230,6 +1266,7 @@ func (tangle *Tangle) bookTransaction(cachedTransaction *transaction.CachedTrans conflictingInputs := make([]transaction.OutputID, 0) conflictingInputsOfFirstConsumers := make(map[transaction.ID][]transaction.OutputID) + finalizedConflictingSpenderFound := false if !transactionToBook.Inputs().ForEach(func(outputID transaction.OutputID) bool { cachedOutput := tangle.TransactionOutput(outputID) defer cachedOutput.Release() @@ -1260,6 +1297,17 @@ func (tangle *Tangle) bookTransaction(cachedTransaction *transaction.CachedTrans conflictingInputsOfFirstConsumers[firstConsumerID] = append(conflictingInputsOfFirstConsumers[firstConsumerID], outputID) } + // check if any of the consumers were finalized already + if !finalizedConflictingSpenderFound { + tangle.Consumers(outputID).Consume(func(consumer *Consumer) { + if !finalizedConflictingSpenderFound { + tangle.TransactionMetadata(consumer.TransactionID()).Consume(func(metadata *TransactionMetadata) { + finalizedConflictingSpenderFound = metadata.Preferred() && metadata.Finalized() + }) + } + }) + } + // mark input as conflicting conflictingInputs = append(conflictingInputs, outputID) @@ -1295,7 +1343,7 @@ func (tangle *Tangle) bookTransaction(cachedTransaction *transaction.CachedTrans } // book transaction into target branch - transactionMetadata.SetBranchID(targetBranch.ID()) + transactionMetadata.setBranchID(targetBranch.ID()) // create color for newly minted coins mintedColor, _, err := balance.ColorFromBytes(transactionToBook.ID().Bytes()) @@ -1323,16 +1371,19 @@ func (tangle *Tangle) bookTransaction(cachedTransaction *transaction.CachedTrans return true }) - // fork the conflicting transactions into their own branch - for consumerID, conflictingInputs := range conflictingInputsOfFirstConsumers { - _, decisionFinalized, forkedErr := tangle.Fork(consumerID, conflictingInputs) - if forkedErr != nil { - err = forkedErr + // fork the conflicting transactions into their own branch if a decision is still pending + decisionPending = !finalizedConflictingSpenderFound + if decisionPending { + for consumerID, conflictingInputs := range conflictingInputsOfFirstConsumers { + _, decisionFinalized, forkedErr := tangle.Fork(consumerID, conflictingInputs) + if forkedErr != nil { + err = forkedErr - return - } + return + } - decisionPending = decisionPending || !decisionFinalized + decisionPending = decisionPending || !decisionFinalized + } } transactionBooked = true @@ -1379,7 +1430,7 @@ func (tangle *Tangle) bookPayload(cachedPayload *payload.CachedPayload, cachedPa return } - payloadBooked = valueObjectMetadata.SetBranchID(aggregatedBranch.ID()) + payloadBooked = valueObjectMetadata.setBranchID(aggregatedBranch.ID()) return } @@ -1712,7 +1763,7 @@ func (tangle *Tangle) moveTransactionToBranch(cachedTransaction *transaction.Cac } // abort if we did not modify the branch of the transaction - if !currentTransactionMetadata.SetBranchID(targetBranch.ID()) { + if !currentTransactionMetadata.setBranchID(targetBranch.ID()) { return nil } @@ -1737,7 +1788,7 @@ func (tangle *Tangle) moveTransactionToBranch(cachedTransaction *transaction.Cac } // abort if the output was moved already - if !output.SetBranchID(targetBranch.ID()) { + if !output.setBranchID(targetBranch.ID()) { return true } @@ -1813,7 +1864,7 @@ func (tangle *Tangle) updateBranchOfValuePayloadsAttachingTransaction(transactio // try to update the metadata of the payload and queue its approvers cachedAggregatedBranch.Consume(func(branch *branchmanager.Branch) { tangle.PayloadMetadata(currentPayload.ID()).Consume(func(payloadMetadata *PayloadMetadata) { - if !payloadMetadata.SetBranchID(branch.ID()) { + if !payloadMetadata.setBranchID(branch.ID()) { return } diff --git a/dapps/valuetransfers/packages/tangle/tangle_test.go b/dapps/valuetransfers/packages/tangle/tangle_test.go index dab6589cc4d360d0fa88d0651f74a08000749a05..0849dc3ff9a58ea3f837bb8b455b941a5878b99b 100644 --- a/dapps/valuetransfers/packages/tangle/tangle_test.go +++ b/dapps/valuetransfers/packages/tangle/tangle_test.go @@ -59,7 +59,7 @@ func TestBookTransaction(t *testing.T) { cachedTransaction, cachedTransactionMetadata, _, _ := tangle.storeTransactionModels(valueObject) transactionMetadata := cachedTransactionMetadata.Unwrap() - transactionMetadata.SetSolid(true) + transactionMetadata.setSolid(true) transactionBooked, decisionPending, err := tangle.bookTransaction(cachedTransaction, cachedTransactionMetadata) require.NoError(t, err) @@ -106,7 +106,7 @@ func TestBookTransaction(t *testing.T) { transactionBooked, decisionPending, err := tangle.bookTransaction(cachedTransaction, cachedTransactionMetadata) require.NoError(t, err) assert.True(t, transactionBooked, "transactionBooked") - assert.False(t, decisionPending, "decisionPending") + assert.True(t, decisionPending, "decisionPending") // assert that branchID is the same as the MasterBranchID assert.Equal(t, branchmanager.MasterBranchID, txMetadata.BranchID()) @@ -257,7 +257,7 @@ func TestFork(t *testing.T) { _, cachedTransactionMetadata, _, _ := tangle.storeTransactionModels(valueObject) txMetadata := cachedTransactionMetadata.Unwrap() - txMetadata.SetFinalized(true) + txMetadata.setFinalized(true) forked, finalized, err := tangle.Fork(tx.ID(), []transaction.OutputID{}) require.NoError(t, err) @@ -334,12 +334,12 @@ func TestBookPayload(t *testing.T) { cachedPayload, cachedMetadata, _ := tangle.storePayload(valueObject) metadata := cachedMetadata.Unwrap() - metadata.SetBranchID(branchmanager.BranchID{1}) - metadata.SetBranchID(branchmanager.BranchID{2}) + metadata.setBranchID(branchmanager.BranchID{1}) + metadata.setBranchID(branchmanager.BranchID{2}) _, cachedTransactionMetadata, _, _ := tangle.storeTransactionModels(valueObject) txMetadata := cachedTransactionMetadata.Unwrap() - txMetadata.SetBranchID(branchmanager.BranchID{1}) + txMetadata.setBranchID(branchmanager.BranchID{1}) payloadBooked, err := tangle.bookPayload(cachedPayload.Retain(), cachedMetadata.Retain(), cachedTransactionMetadata.Retain()) defer func() { @@ -359,12 +359,12 @@ func TestBookPayload(t *testing.T) { cachedPayload, cachedMetadata, _ := tangle.storePayload(valueObject) metadata := cachedMetadata.Unwrap() - metadata.SetBranchID(branchmanager.BranchID{1}) - metadata.SetBranchID(branchmanager.BranchID{1}) + metadata.setBranchID(branchmanager.BranchID{1}) + metadata.setBranchID(branchmanager.BranchID{1}) _, cachedTransactionMetadata, _, _ := tangle.storeTransactionModels(valueObject) txMetadata := cachedTransactionMetadata.Unwrap() - txMetadata.SetBranchID(branchmanager.BranchID{1}) + txMetadata.setBranchID(branchmanager.BranchID{1}) payloadBooked, err := tangle.bookPayload(cachedPayload.Retain(), cachedMetadata.Retain(), cachedTransactionMetadata.Retain()) defer func() { @@ -1045,8 +1045,8 @@ func TestCheckTransactionSolidity(t *testing.T) { tangle := New(mapdb.NewMapDB()) tx := createDummyTransaction() txMetadata := NewTransactionMetadata(tx.ID()) - txMetadata.SetSolid(true) - txMetadata.SetBranchID(branchmanager.MasterBranchID) + txMetadata.setSolid(true) + txMetadata.setBranchID(branchmanager.MasterBranchID) solid, consumedBranches, err := tangle.checkTransactionSolidity(tx, txMetadata) assert.True(t, solid) @@ -1216,7 +1216,7 @@ func TestPayloadBranchID(t *testing.T) { expectedBranchID := branchmanager.BranchID{1} cachedMetadata.Consume(func(metadata *PayloadMetadata) { metadata.setSolid(true) - metadata.SetBranchID(expectedBranchID) + metadata.setBranchID(expectedBranchID) }) branchID := tangle.payloadBranchID(valueObject.ID()) @@ -1245,7 +1245,7 @@ func TestCheckPayloadSolidity(t *testing.T) { valueObject := payload.New(payload.GenesisID, payload.GenesisID, createDummyTransaction()) metadata := NewPayloadMetadata(valueObject.ID()) metadata.setSolid(true) - metadata.SetBranchID(branchmanager.MasterBranchID) + metadata.setBranchID(branchmanager.MasterBranchID) transactionBranches := []branchmanager.BranchID{branchmanager.MasterBranchID} solid, err := tangle.payloadBecameNewlySolid(valueObject, metadata, transactionBranches) @@ -1268,7 +1268,7 @@ func TestCheckPayloadSolidity(t *testing.T) { { setParent := func(payloadMetadata *PayloadMetadata) { payloadMetadata.setSolid(true) - payloadMetadata.SetBranchID(branchmanager.MasterBranchID) + payloadMetadata.setBranchID(branchmanager.MasterBranchID) } valueObject := payload.New(storeParentPayloadWithMetadataFunc(t, tangle, setParent), storeParentPayloadWithMetadataFunc(t, tangle, setParent), createDummyTransaction()) @@ -1304,11 +1304,11 @@ func TestCheckPayloadSolidity(t *testing.T) { defer cachedBranch3.Release() setParent1 := func(payloadMetadata *PayloadMetadata) { payloadMetadata.setSolid(true) - payloadMetadata.SetBranchID(branchmanager.BranchID{2}) + payloadMetadata.setBranchID(branchmanager.BranchID{2}) } setParent2 := func(payloadMetadata *PayloadMetadata) { payloadMetadata.setSolid(true) - payloadMetadata.SetBranchID(branchmanager.BranchID{3}) + payloadMetadata.setBranchID(branchmanager.BranchID{3}) } valueObject := payload.New(storeParentPayloadWithMetadataFunc(t, tangle, setParent1), storeParentPayloadWithMetadataFunc(t, tangle, setParent2), createDummyTransaction()) @@ -1329,11 +1329,11 @@ func TestCheckPayloadSolidity(t *testing.T) { defer cachedBranch3.Release() setParent1 := func(payloadMetadata *PayloadMetadata) { payloadMetadata.setSolid(true) - payloadMetadata.SetBranchID(branchmanager.MasterBranchID) + payloadMetadata.setBranchID(branchmanager.MasterBranchID) } setParent2 := func(payloadMetadata *PayloadMetadata) { payloadMetadata.setSolid(true) - payloadMetadata.SetBranchID(branchmanager.BranchID{3}) + payloadMetadata.setBranchID(branchmanager.BranchID{3}) } valueObject := payload.New(storeParentPayloadWithMetadataFunc(t, tangle, setParent1), storeParentPayloadWithMetadataFunc(t, tangle, setParent2), createDummyTransaction()) diff --git a/dapps/valuetransfers/packages/tangle/transactionmetadata.go b/dapps/valuetransfers/packages/tangle/transactionmetadata.go index 01b161613ae30fcb8d4a98e2e337c3a9723f370e..106075bafe490af586673c960e379a8b8430a4dc 100644 --- a/dapps/valuetransfers/packages/tangle/transactionmetadata.go +++ b/dapps/valuetransfers/packages/tangle/transactionmetadata.go @@ -113,8 +113,8 @@ func (transactionMetadata *TransactionMetadata) BranchID() branchmanager.BranchI return transactionMetadata.branchID } -// SetBranchID is the setter for the branch id. It returns true if the value of the flag has been updated. -func (transactionMetadata *TransactionMetadata) SetBranchID(branchID branchmanager.BranchID) (modified bool) { +// setBranchID is the setter for the branch id. It returns true if the value of the flag has been updated. +func (transactionMetadata *TransactionMetadata) setBranchID(branchID branchmanager.BranchID) (modified bool) { transactionMetadata.branchIDMutex.RLock() if transactionMetadata.branchID == branchID { transactionMetadata.branchIDMutex.RUnlock() @@ -150,9 +150,9 @@ func (transactionMetadata *TransactionMetadata) Solid() (result bool) { return } -// SetSolid marks a Transaction as either solid or not solid. +// setSolid marks a Transaction as either solid or not solid. // It returns true if the solid flag was changes and automatically updates the solidificationTime as well. -func (transactionMetadata *TransactionMetadata) SetSolid(solid bool) (modified bool) { +func (transactionMetadata *TransactionMetadata) setSolid(solid bool) (modified bool) { transactionMetadata.solidMutex.RLock() if transactionMetadata.solid != solid { transactionMetadata.solidMutex.RUnlock() @@ -213,9 +213,9 @@ func (transactionMetadata *TransactionMetadata) setPreferred(preferred bool) (mo return } -// SetFinalized allows us to set the finalized flag on the transactions. Finalized transactions will not be forked when +// setFinalized allows us to set the finalized flag on the transactions. Finalized transactions will not be forked when // a conflict arrives later. -func (transactionMetadata *TransactionMetadata) SetFinalized(finalized bool) (modified bool) { +func (transactionMetadata *TransactionMetadata) setFinalized(finalized bool) (modified bool) { transactionMetadata.finalizedMutex.RLock() if transactionMetadata.finalized == finalized { transactionMetadata.finalizedMutex.RUnlock()