diff --git a/go.mod b/go.mod index 3fc5dbd477f12ea2df332810bad1d193bbc8205e..015e2823f021a6c53041edfe4e00aa49616055e8 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/golang/protobuf v1.3.2 // indirect github.com/google/open-location-code/go v0.0.0-20190903173953-119bc96a3a51 github.com/gorilla/websocket v1.4.1 - github.com/iotaledger/hive.go v0.0.0-20191205213014-6bee840fa69a + github.com/iotaledger/hive.go v0.0.0-20191208004610-567900b261bd github.com/iotaledger/iota.go v1.0.0-beta.9 github.com/kr/text v0.1.0 github.com/labstack/echo v3.3.10+incompatible diff --git a/go.sum b/go.sum index 6a2724a85c9dc8576ebcdb169a10efee47df2e2d..21013aaa10a237c3ee55af178ab8f640c205cbfb 100644 --- a/go.sum +++ b/go.sum @@ -96,6 +96,10 @@ github.com/iotaledger/hive.go v0.0.0-20191205211001-eafba1daa31e h1:7F9mSTDfFwMl github.com/iotaledger/hive.go v0.0.0-20191205211001-eafba1daa31e/go.mod h1:7iqun29a1x0lymTrn0UJ3Z/yy0sUzUpoOZ1OYMrYN20= github.com/iotaledger/hive.go v0.0.0-20191205213014-6bee840fa69a h1:mI4GR5wcI5G6vjumuI2KH9r0mf5me3i6mWhDMtkKR04= github.com/iotaledger/hive.go v0.0.0-20191205213014-6bee840fa69a/go.mod h1:7iqun29a1x0lymTrn0UJ3Z/yy0sUzUpoOZ1OYMrYN20= +github.com/iotaledger/hive.go v0.0.0-20191206001944-418a9127c1eb h1:iMYyaySL2gEjOcPRzWZYCn4+vXkanq1b9Pr9fKSzdrI= +github.com/iotaledger/hive.go v0.0.0-20191206001944-418a9127c1eb/go.mod h1:7iqun29a1x0lymTrn0UJ3Z/yy0sUzUpoOZ1OYMrYN20= +github.com/iotaledger/hive.go v0.0.0-20191208004610-567900b261bd h1:hh8iusLOBylWNHeJNiZv6atCbO7vzjCLlg53pNAMkFk= +github.com/iotaledger/hive.go v0.0.0-20191208004610-567900b261bd/go.mod h1:7iqun29a1x0lymTrn0UJ3Z/yy0sUzUpoOZ1OYMrYN20= github.com/iotaledger/iota.go v1.0.0-beta.7 h1:OaUNahPvOdQz2nKcgeAfcUdxlEDlEV3xwLIkwzZ1B/U= github.com/iotaledger/iota.go v1.0.0-beta.7/go.mod h1:dMps6iMVU1pf5NDYNKIw4tRsPeC8W3ZWjOvYHOO1PMg= github.com/iotaledger/iota.go v1.0.0-beta.9 h1:c654s9pkdhMBkABUvWg+6k91MEBbdtmZXP1xDfQpajg= diff --git a/packages/ledgerstate/conflict_id.go b/packages/ledgerstate/conflict_id.go index 0d084ca047931540bac891e7c29ce09b774864a4..fce0559c6d5acfd114a7400f31ed252a933352d9 100644 --- a/packages/ledgerstate/conflict_id.go +++ b/packages/ledgerstate/conflict_id.go @@ -9,17 +9,17 @@ import ( type ConflictId [conflictSetIdLength]byte -func NewConflictSetId(conflictBytes ...interface{}) (result ConflictId) { +func NewConflictId(conflictBytes ...interface{}) (result ConflictId) { switch len(conflictBytes) { case 2: transferHash, ok := conflictBytes[0].(TransferHash) if !ok { - panic("expected first parameter of NewConflictSetId to be a TransferHash") + panic("expected first parameter of NewConflictId to be a TransferHash") } addressHash, ok := conflictBytes[0].(TransferHash) if !ok { - panic("expected second parameter of NewConflictSetId to be a AddressHash") + panic("expected second parameter of NewConflictId to be a AddressHash") } fullConflictSetIdentifier := make([]byte, transferHashLength+addressHashLength) @@ -29,7 +29,7 @@ func NewConflictSetId(conflictBytes ...interface{}) (result ConflictId) { result = blake2b.Sum256(fullConflictSetIdentifier) case 1: default: - panic("invalid parameter count when calling NewConflictSetId") + panic("invalid parameter count when calling NewConflictId") } return diff --git a/packages/ledgerstate/ledgerstate.go b/packages/ledgerstate/ledgerstate.go index 83ff9dc53cdc973fb7a9a532959fa66d4437664a..0af177c674f4ce1fcf7023d91ba981fcbaeb3119 100644 --- a/packages/ledgerstate/ledgerstate.go +++ b/packages/ledgerstate/ledgerstate.go @@ -152,9 +152,7 @@ func (ledgerState *LedgerState) BookTransfer(transfer *Transfer) (err error) { return } - if !targetReality.PersistenceEnabled() { - targetReality.Persist() - } + targetReality.Persist() }) return diff --git a/packages/ledgerstate/ledgerstate_test.go b/packages/ledgerstate/ledgerstate_test.go index fc5efc9e7dfa7b13990ac30e29e74e1364a496de..61ad15e871a030a7192bf777a6dcd84975a2e79b 100644 --- a/packages/ledgerstate/ledgerstate_test.go +++ b/packages/ledgerstate/ledgerstate_test.go @@ -233,7 +233,7 @@ func TestAggregateAggregatedRealities(t *testing.T) { multiSpend(ledgerState, 1, aggregatedOutputs0[0], aggregatedOutputs1[0]) multiSpend(ledgerState, 1, aggregatedOutputs0[1], aggregatedOutputs2[0]) - time.Sleep(1000 * time.Millisecond) + time.Sleep(2000 * time.Millisecond) objectstorage.WaitForWritesToFlush() @@ -247,7 +247,7 @@ func TestAggregateAggregatedRealities(t *testing.T) { multiSpend(ledgerState, 2, outputs0[0], outputs1[0]) - time.Sleep(1000 * time.Millisecond) + time.Sleep(2000 * time.Millisecond) objectstorage.WaitForWritesToFlush() diff --git a/packages/ledgerstate/outputs.png b/packages/ledgerstate/outputs.png index 6f2378aae6fd63d8cc32cebf1de4b0d53e3657d6..b746b5ce8c27e57e55376cd41b8a903a8c5fb5c6 100644 Binary files a/packages/ledgerstate/outputs.png and b/packages/ledgerstate/outputs.png differ diff --git a/packages/ledgerstate/outputs1.png b/packages/ledgerstate/outputs1.png index 78638b4ac0d547722553ecba91080eb482cfdfac..fbb292ef06d60e9e6ddf1bf269990516a389fcb7 100644 Binary files a/packages/ledgerstate/outputs1.png and b/packages/ledgerstate/outputs1.png differ diff --git a/packages/ledgerstate/outputs2.png b/packages/ledgerstate/outputs2.png index 4e5fb6fc2eb50722536f8cebf3237379c06830c8..60319a10e56e7846b93dd8889d864649f4514ba3 100644 Binary files a/packages/ledgerstate/outputs2.png and b/packages/ledgerstate/outputs2.png differ diff --git a/packages/ledgerstate/realities2.png b/packages/ledgerstate/realities2.png index a6fb2ce1c43c24eb3059ea3a8c840a5b846fa769..08f4b00d7bd979f5503fbfcbc0c2a62e636ab170 100644 Binary files a/packages/ledgerstate/realities2.png and b/packages/ledgerstate/realities2.png differ diff --git a/packages/ledgerstate/reality.go b/packages/ledgerstate/reality.go index afbbcb50b59c0b9b33fa60bfe47f174b1781b6d3..b163d718083c2985e0dbf50a3e4180cc2212e6f5 100644 --- a/packages/ledgerstate/reality.go +++ b/packages/ledgerstate/reality.go @@ -4,9 +4,9 @@ import ( "sync" "sync/atomic" - "github.com/iotaledger/goshimmer/packages/errors" "github.com/iotaledger/goshimmer/packages/stringify" + "github.com/iotaledger/goshimmer/packages/errors" "github.com/iotaledger/hive.go/objectstorage" ) @@ -72,19 +72,69 @@ func (reality *Reality) AddParentReality(realityId RealityId) { } } +// Utility function that replaces the parent of a reality. +// Since IO is the most expensive part of the ledger state, we only update the parents and mark the reality as modified +// if either the oldRealityId exists or the newRealityId does not exist. +func (reality *Reality) replaceParentReality(oldRealityId RealityId, newRealityId RealityId) { + reality.parentRealityIdsMutex.RLock() + if _, oldRealityIdExist := reality.parentRealityIds[oldRealityId]; oldRealityIdExist { + reality.parentRealityIdsMutex.RUnlock() + + reality.parentRealityIdsMutex.Lock() + if _, oldRealityIdExist := reality.parentRealityIds[oldRealityId]; oldRealityIdExist { + delete(reality.parentRealityIds, oldRealityId) + + if _, newRealityIdExist := reality.parentRealityIds[newRealityId]; !newRealityIdExist { + reality.parentRealityIds[newRealityId] = void + } + + reality.SetModified() + } else { + if _, newRealityIdExist := reality.parentRealityIds[newRealityId]; !newRealityIdExist { + reality.parentRealityIds[newRealityId] = void + + reality.SetModified() + } + } + reality.parentRealityIdsMutex.Unlock() + } else { + if _, newRealityIdExist := reality.parentRealityIds[newRealityId]; !newRealityIdExist { + reality.parentRealityIdsMutex.RUnlock() + + reality.parentRealityIdsMutex.Lock() + if _, newRealityIdExist := reality.parentRealityIds[newRealityId]; !newRealityIdExist { + reality.parentRealityIds[newRealityId] = void + + reality.SetModified() + } + reality.parentRealityIdsMutex.Unlock() + } else { + reality.parentRealityIdsMutex.RUnlock() + } + } +} + // Returns the amount of TransferOutputs in this Reality. func (reality *Reality) GetTransferOutputCount() uint32 { return atomic.LoadUint32(&(reality.transferOutputCount)) } // Increases (and returns) the amount of TransferOutputs in this Reality. -func (reality *Reality) IncreaseTransferOutputCount() uint32 { - return atomic.AddUint32(&(reality.transferOutputCount), 1) +func (reality *Reality) IncreaseTransferOutputCount() (transferOutputCount uint32) { + transferOutputCount = atomic.AddUint32(&(reality.transferOutputCount), 1) + + reality.SetModified() + + return } // Decreases (and returns) the amount of TransferOutputs in this Reality. -func (reality *Reality) DecreaseTransferOutputCount() uint32 { - return atomic.AddUint32(&(reality.transferOutputCount), ^uint32(0)) +func (reality *Reality) DecreaseTransferOutputCount() (transferOutputCount uint32) { + transferOutputCount = atomic.AddUint32(&(reality.transferOutputCount), ^uint32(0)) + + reality.SetModified() + + return } // Returns true, if this reality is an "aggregated reality" that combines multiple other realities. @@ -136,8 +186,8 @@ func (reality *Reality) GetParentRealities() (parentRealities map[RealityId]*obj return } -// Returns a map of all parent realities that are conflicting. Aggregated realities are "transparent". They have to be -// "released" manually when they are not needed anymore. +// Returns a map of all parent realities that are not aggregated (aggregated realities are "transparent"). They have to +// be "released" manually when they are not needed anymore. func (reality *Reality) GetParentConflictRealities() map[RealityId]*objectstorage.CachedObject { if !reality.IsAggregated() { return reality.GetParentRealities() @@ -150,24 +200,8 @@ func (reality *Reality) GetParentConflictRealities() map[RealityId]*objectstorag } } -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// - -func (reality *Reality) collectParentConflictRealities(parentConflictRealities map[RealityId]*objectstorage.CachedObject) { - for realityId, cachedParentReality := range reality.GetParentRealities() { - parentReality := cachedParentReality.Get().(*Reality) - - if !parentReality.IsAggregated() { - parentConflictRealities[realityId] = cachedParentReality - } else { - parentReality.collectParentConflictRealities(parentConflictRealities) - - cachedParentReality.Release() - } - } -} - -// [DONE] Returns a map of all ancestor realities (up till the MAIN_REALITY). They have to manually be "released" when -// they are not needed anymore. +// Returns a map of all ancestor realities (up till the MAIN_REALITY). They have to manually be "released" when they are +// not needed anymore. func (reality *Reality) GetAncestorRealities() (result map[RealityId]*objectstorage.CachedObject) { result = make(map[RealityId]*objectstorage.CachedObject, 1) @@ -182,18 +216,25 @@ func (reality *Reality) GetAncestorRealities() (result map[RealityId]*objectstor return } -// [DONE] Registers the conflict set in the Reality. +// Registers the conflict set in the Reality. func (reality *Reality) AddConflict(conflictSetId ConflictId) { - reality.conflictIdsMutex.Lock() + reality.conflictIdsMutex.RLock() if _, exists := reality.conflictIds[conflictSetId]; !exists { - reality.conflictIds[conflictSetId] = void + reality.conflictIdsMutex.RUnlock() - reality.SetModified() + reality.conflictIdsMutex.Lock() + if _, exists := reality.conflictIds[conflictSetId]; !exists { + reality.conflictIds[conflictSetId] = void + + reality.SetModified() + } + reality.conflictIdsMutex.Unlock() + } else { + reality.conflictIdsMutex.RUnlock() } - reality.conflictIdsMutex.Unlock() } -// [DONE] Creates a new sub Reality and "stores" it. It has to manually be "released" when it is not needed anymore. +// Creates a new sub Reality and "stores" it. It has to manually be "released" when it is not needed anymore. func (reality *Reality) CreateReality(id RealityId) *objectstorage.CachedObject { newReality := newReality(id, reality.id) newReality.ledgerState = reality.ledgerState @@ -201,12 +242,34 @@ func (reality *Reality) CreateReality(id RealityId) *objectstorage.CachedObject return reality.ledgerState.realities.Store(newReality) } +// Books a transfer into this reality (wrapper for the private bookTransfer function). func (reality *Reality) BookTransfer(transfer *Transfer) (err error) { err = reality.bookTransfer(transfer.GetHash(), reality.ledgerState.getTransferInputs(transfer), transfer.GetOutputs()) return } +// Creates a string representation of this Reality. +func (reality *Reality) String() (result string) { + reality.parentRealityIdsMutex.RLock() + parentRealities := make([]string, len(reality.parentRealityIds)) + i := 0 + for parentRealityId := range reality.parentRealityIds { + parentRealities[i] = parentRealityId.String() + + i++ + } + reality.parentRealityIdsMutex.RUnlock() + + result = stringify.Struct("Reality", + stringify.StructField("id", reality.GetId().String()), + stringify.StructField("parentRealities", parentRealities), + ) + + return +} + +// Books a transfer into this reality (contains the dispatcher for the actual tasks). func (reality *Reality) bookTransfer(transferHash TransferHash, inputs objectstorage.CachedObjects, outputs map[AddressHash][]*ColoredBalance) (err error) { if err = reality.verifyTransfer(inputs, outputs); err != nil { return @@ -217,31 +280,8 @@ func (reality *Reality) bookTransfer(transferHash TransferHash, inputs objectsto return } - if len(conflicts) >= 1 { - targetRealityId := transferHash.ToRealityId() - - reality.CreateReality(targetRealityId).Consume(func(object objectstorage.StorableObject) { - targetReality := object.(*Reality) - - for _, cachedConflictSet := range conflicts { - conflictSet := cachedConflictSet.Get().(*Conflict) - - conflictSet.AddReality(targetRealityId) - targetReality.AddConflict(conflictSet.GetId()) - } - - for addressHash, coloredBalances := range outputs { - if err = targetReality.bookTransferOutput(NewTransferOutput(reality.ledgerState, emptyRealityId, transferHash, addressHash, coloredBalances...)); err != nil { - return - } - } - }) - } else { - for addressHash, coloredBalances := range outputs { - if err = reality.bookTransferOutput(NewTransferOutput(reality.ledgerState, emptyRealityId, transferHash, addressHash, coloredBalances...)); err != nil { - return - } - } + if err = reality.createTransferOutputs(transferHash, outputs, conflicts); err != nil { + return } conflicts.Release() @@ -250,7 +290,7 @@ func (reality *Reality) bookTransfer(transferHash TransferHash, inputs objectsto return } -// Verifies the transfer and checks if it is valid (spends existing funds + the net balance is 0). +// Internal utility function that verifies the transfer and checks if it is valid (inputs exist + the net balance is 0). func (reality *Reality) verifyTransfer(inputs []*objectstorage.CachedObject, outputs map[AddressHash][]*ColoredBalance) error { totalColoredBalances := make(map[Color]uint64) @@ -289,7 +329,8 @@ func (reality *Reality) verifyTransfer(inputs []*objectstorage.CachedObject, out return nil } -// Marks the consumed inputs as spent and returns the corresponding Conflict if the inputs have been consumed before. +// Internal utility function that marks the consumed inputs as spent and returns the corresponding conflicts if the +// inputs have been consumed before. func (reality *Reality) consumeInputs(inputs objectstorage.CachedObjects, transferHash TransferHash, outputs map[AddressHash][]*ColoredBalance) (conflicts objectstorage.CachedObjects, err error) { conflicts = make(objectstorage.CachedObjects, 0) @@ -301,7 +342,7 @@ func (reality *Reality) consumeInputs(inputs objectstorage.CachedObjects, transf return } else if consumersToElevate != nil { - if conflict, conflictErr := reality.retrieveConflictForConflictingInput(consumedInput, consumersToElevate); conflictErr != nil { + if conflict, conflictErr := reality.processConflictingInput(consumedInput, consumersToElevate); conflictErr != nil { err = conflictErr return @@ -314,18 +355,73 @@ func (reality *Reality) consumeInputs(inputs objectstorage.CachedObjects, transf return } -func (reality *Reality) retrieveConflictForConflictingInput(input *TransferOutput, consumersToElevate map[TransferHash][]AddressHash) (conflict *objectstorage.CachedObject, err error) { - conflictSetId := NewConflictSetId(input.GetTransferHash(), input.GetAddressHash()) +// Private utility function that creates the transfer outputs in the ledger. +// +// If the inputs have been used before and we consequently have a non-empty list of conflicts, we first create a new +// reality for the inputs and then book the transfer outputs into the correct reality. +func (reality *Reality) createTransferOutputs(transferHash TransferHash, outputs map[AddressHash][]*ColoredBalance, conflicts objectstorage.CachedObjects) (err error) { + if len(conflicts) >= 1 { + targetRealityId := transferHash.ToRealityId() + + reality.CreateReality(targetRealityId).Consume(func(object objectstorage.StorableObject) { + targetReality := object.(*Reality) + + for _, cachedConflictSet := range conflicts { + conflictSet := cachedConflictSet.Get().(*Conflict) + + conflictSet.AddReality(targetRealityId) + targetReality.AddConflict(conflictSet.GetId()) + } + + for addressHash, coloredBalances := range outputs { + if err = targetReality.bookTransferOutput(NewTransferOutput(reality.ledgerState, emptyRealityId, transferHash, addressHash, coloredBalances...)); err != nil { + return + } + } + }) + } else { + for addressHash, coloredBalances := range outputs { + if err = reality.bookTransferOutput(NewTransferOutput(reality.ledgerState, emptyRealityId, transferHash, addressHash, coloredBalances...)); err != nil { + return + } + } + } + + return +} + +// Utility function that collects all non-aggregated parent realities. It is used by GetParentConflictRealities and +// prevents us from having to allocate multiple maps during recursion. +func (reality *Reality) collectParentConflictRealities(parentConflictRealities map[RealityId]*objectstorage.CachedObject) { + for realityId, cachedParentReality := range reality.GetParentRealities() { + parentReality := cachedParentReality.Get().(*Reality) + + if !parentReality.IsAggregated() { + parentConflictRealities[realityId] = cachedParentReality + } else { + parentReality.collectParentConflictRealities(parentConflictRealities) + + cachedParentReality.Release() + } + } +} + +// Utility function that processes a conflicting input by retrieving the corresponding conflict. +// If there is a non-empty list of consumers to elevate, we elevate them. +func (reality *Reality) processConflictingInput(input *TransferOutput, consumersToElevate map[TransferHash][]AddressHash) (conflict *objectstorage.CachedObject, err error) { + conflictId := NewConflictId(input.GetTransferHash(), input.GetAddressHash()) if len(consumersToElevate) >= 1 { - newConflict := newConflictSet(conflictSetId) + newConflict := newConflictSet(conflictId) newConflict.ledgerState = reality.ledgerState conflict = reality.ledgerState.conflictSets.Store(newConflict) - err = reality.createRealityForConsumerOfConflictingInput(consumersToElevate, conflict.Get().(*Conflict)) + err = reality.createRealityForPreviouslyUnconflictingConsumers(consumersToElevate, conflict.Get().(*Conflict)) } else { - if conflict, err = reality.ledgerState.conflictSets.Load(conflictSetId[:]); err == nil { + if conflict, err = reality.ledgerState.conflictSets.Load(conflictId[:]); err != nil { + return + } else { conflict.Get().(*Conflict).ledgerState = reality.ledgerState } } @@ -334,14 +430,13 @@ func (reality *Reality) retrieveConflictForConflictingInput(input *TransferOutpu } // Creates a Reality for the consumers of the conflicting inputs and registers it as part of the corresponding Conflict. -func (reality *Reality) createRealityForConsumerOfConflictingInput(consumersOfConflictingInput map[TransferHash][]AddressHash, conflict *Conflict) (err error) { +func (reality *Reality) createRealityForPreviouslyUnconflictingConsumers(consumersOfConflictingInput map[TransferHash][]AddressHash, conflict *Conflict) (err error) { for transferHash, addressHashes := range consumersOfConflictingInput { - var elevatedRealityId = transferHash.ToRealityId() - var realityIsNew bool - var cachedElevatedReality *objectstorage.CachedObject + elevatedRealityId := transferHash.ToRealityId() // Retrieve the Reality for this Transfer or create one if no Reality exists, yet. - if cachedElevatedReality, err = reality.ledgerState.realities.ComputeIfAbsent(elevatedRealityId[:], func(key []byte) (object objectstorage.StorableObject, e error) { + var realityIsNew bool + if cachedElevatedReality, realityErr := reality.ledgerState.realities.ComputeIfAbsent(elevatedRealityId[:], func(key []byte) (object objectstorage.StorableObject, e error) { newReality := newReality(elevatedRealityId, reality.id) newReality.ledgerState = reality.ledgerState @@ -351,7 +446,9 @@ func (reality *Reality) createRealityForConsumerOfConflictingInput(consumersOfCo realityIsNew = true return newReality, nil - }); err == nil { + }); realityErr != nil { + err = realityErr + } else { cachedElevatedReality.Consume(func(object objectstorage.StorableObject) { elevatedReality := object.(*Reality) @@ -377,40 +474,39 @@ func (reality *Reality) createRealityForConsumerOfConflictingInput(consumersOfCo return } +// Private utility function that elevates a transfer output to the given reality. func (reality *Reality) elevateTransferOutput(transferOutputReference *TransferOutputReference, newReality *Reality) (err error) { - cachedTransferOutputToElevate := reality.ledgerState.GetTransferOutput(transferOutputReference) - if !cachedTransferOutputToElevate.Exists() { - return errors.New("could not find TransferOutput to elevate") - } - - cachedTransferOutputToElevate.Consume(func(object objectstorage.StorableObject) { - transferOutputToElevate := object.(*TransferOutput) - - transferOutputToElevate.SetModified() + if cachedTransferOutputToElevate := reality.ledgerState.GetTransferOutput(transferOutputReference); !cachedTransferOutputToElevate.Exists() { + err = errors.New("could not find TransferOutput to elevate") + } else { + cachedTransferOutputToElevate.Consume(func(object objectstorage.StorableObject) { + transferOutputToElevate := object.(*TransferOutput) - if transferOutputToElevate.GetRealityId() == reality.id { - err = reality.elevateTransferOutputOfCurrentReality(transferOutputToElevate, newReality) - } else { - reality.ledgerState.GetReality(transferOutputToElevate.GetRealityId()).Consume(func(nestedReality objectstorage.StorableObject) { - err = nestedReality.(*Reality).elevateTransferOutputOfNestedReality(transferOutputToElevate, reality.id, newReality.id) - }) - } - }) + if currentTransferOutputRealityId := transferOutputToElevate.GetRealityId(); currentTransferOutputRealityId == reality.GetId() { + err = reality.elevateTransferOutputOfCurrentReality(transferOutputToElevate, newReality) + } else if cachedNestedReality := reality.ledgerState.GetReality(currentTransferOutputRealityId); !cachedNestedReality.Exists() { + err = errors.New("could not find nested reality to elevate TransferOutput") + } else { + cachedNestedReality.Consume(func(nestedReality objectstorage.StorableObject) { + err = nestedReality.(*Reality).elevateTransferOutputOfNestedReality(transferOutputToElevate, reality.GetId(), newReality.GetId()) + }) + } + }) + } return } +// Private utility function that elevates the transfer output from the current reality to the new reality. func (reality *Reality) elevateTransferOutputOfCurrentReality(transferOutput *TransferOutput, newReality *Reality) (err error) { - if err = newReality.bookTransferOutput(transferOutput); err != nil { - return - } - - for transferHash, addresses := range transferOutput.GetConsumers() { - for _, addressHash := range addresses { - if elevateErr := reality.elevateTransferOutput(NewTransferOutputReference(transferHash, addressHash), newReality); elevateErr != nil { - err = elevateErr + if err = newReality.bookTransferOutput(transferOutput); err == nil { + for transferHash, addresses := range transferOutput.GetConsumers() { + for _, addressHash := range addresses { + if elevateErr := reality.elevateTransferOutput(NewTransferOutputReference(transferHash, addressHash), newReality); elevateErr != nil { + err = elevateErr - return + return + } } } } @@ -418,24 +514,19 @@ func (reality *Reality) elevateTransferOutputOfCurrentReality(transferOutput *Tr return } +// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// + func (reality *Reality) elevateTransferOutputOfNestedReality(transferOutput *TransferOutput, oldParentRealityId RealityId, newParentRealityId RealityId) (err error) { if !reality.IsAggregated() { - reality.parentRealityIdsMutex.Lock() - reality.parentRealityIds.Remove(oldParentRealityId).Add(newParentRealityId) - reality.parentRealityIdsMutex.Unlock() + reality.replaceParentReality(oldParentRealityId, newParentRealityId) + } else { + reality.ledgerState.AggregateRealities(reality.GetParentRealityIds().Remove(oldParentRealityId).Add(newParentRealityId).ToList()...).Consume(func(newAggregatedReality objectstorage.StorableObject) { + newAggregatedReality.Persist() - return + err = reality.elevateTransferOutputOfCurrentReality(transferOutput, newAggregatedReality.(*Reality)) + }) } - newParentRealities := reality.GetParentRealityIds().Remove(oldParentRealityId).Add(newParentRealityId).ToList() - - reality.ledgerState.AggregateRealities(newParentRealities...).Consume(func(object objectstorage.StorableObject) { - object.Persist() - object.SetModified() - - err = reality.elevateTransferOutputOfCurrentReality(transferOutput, object.(*Reality)) - }) - return } @@ -464,7 +555,7 @@ func (reality *Reality) bookTransferOutput(transferOutput *TransferOutput) (err reality.ledgerState.GetReality(transferOutputRealityId).Consume(func(object objectstorage.StorableObject) { // decrease transferOutputCount and remove reality if it is empty if object.(*Reality).DecreaseTransferOutputCount() == 0 { - //reality.ledgerState.realities.Delete(transferOutputRealityId[:]) + // delete reality if empty } }) @@ -483,24 +574,3 @@ func (reality *Reality) bookTransferOutput(transferOutput *TransferOutput) (err return } - -func (reality *Reality) String() (result string) { - reality.parentRealityIdsMutex.RLock() - - parentRealities := make([]string, len(reality.parentRealityIds)) - i := 0 - for parentRealityId := range reality.parentRealityIds { - parentRealities[i] = parentRealityId.String() - - i++ - } - - result = stringify.Struct("Reality", - stringify.StructField("id", reality.id.String()), - stringify.StructField("parentRealities", parentRealities), - ) - - reality.parentRealityIdsMutex.RUnlock() - - return -} diff --git a/packages/ledgerstate/transfer_output.go b/packages/ledgerstate/transfer_output.go index a783ef161ff7857872f8c592bf7439f14d4f2cbb..b0a99c3125ebc9e549bbc2c7bf6689f792bced86 100644 --- a/packages/ledgerstate/transfer_output.go +++ b/packages/ledgerstate/transfer_output.go @@ -57,9 +57,20 @@ func (transferOutput *TransferOutput) GetAddressHash() (addressHash AddressHash) } func (transferOutput *TransferOutput) SetRealityId(realityId RealityId) { - transferOutput.realityIdMutex.Lock() - transferOutput.realityId = realityId - transferOutput.realityIdMutex.Unlock() + transferOutput.realityIdMutex.RLock() + if transferOutput.realityId != realityId { + transferOutput.realityIdMutex.RUnlock() + + transferOutput.realityIdMutex.Lock() + if transferOutput.realityId != realityId { + transferOutput.realityId = realityId + + transferOutput.SetModified() + } + transferOutput.realityIdMutex.Unlock() + } else { + transferOutput.realityIdMutex.RUnlock() + } } func (transferOutput *TransferOutput) GetBalances() []*ColoredBalance {