diff --git a/go.mod b/go.mod index 03c0c6c0e99dc8945ee1de7a536638a0119f2ff8..3fc5dbd477f12ea2df332810bad1d193bbc8205e 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-20191202111738-357cee7a1c37 + github.com/iotaledger/hive.go v0.0.0-20191205213014-6bee840fa69a 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 d9027fa98ffeaacc105cd871773ffe8c1b967746..6a2724a85c9dc8576ebcdb169a10efee47df2e2d 100644 --- a/go.sum +++ b/go.sum @@ -92,6 +92,10 @@ github.com/iotaledger/hive.go v0.0.0-20191125112048-8b1784dd1bce h1:QchbydsqgH7b github.com/iotaledger/hive.go v0.0.0-20191125112048-8b1784dd1bce/go.mod h1:1Thhlil4lHzuy53EVvmEbEvWBFY0Tasp4kCBfxBCPIk= github.com/iotaledger/hive.go v0.0.0-20191202111738-357cee7a1c37 h1:Vex6W5Oae7xXvVmnCrl7J4o+PCx0FW3paMzXxQDr8H4= github.com/iotaledger/hive.go v0.0.0-20191202111738-357cee7a1c37/go.mod h1:1Thhlil4lHzuy53EVvmEbEvWBFY0Tasp4kCBfxBCPIk= +github.com/iotaledger/hive.go v0.0.0-20191205211001-eafba1daa31e h1:7F9mSTDfFwMlEwkllo2yoc0p2iolrvL0I6RgMcL4h4M= +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/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.go b/packages/ledgerstate/conflict.go index a65dcf570a4f437680a63a591e2626250ac15d46..4ae4e0f750c4f928c10614edc3bfd90c3a1fab33 100644 --- a/packages/ledgerstate/conflict.go +++ b/packages/ledgerstate/conflict.go @@ -11,6 +11,8 @@ import ( ) type Conflict struct { + objectstorage.StorableObjectFlags + id ConflictId members map[RealityId]empty diff --git a/packages/ledgerstate/ledgerstate.go b/packages/ledgerstate/ledgerstate.go index e941bfab368a6ad7220165e4b7077fa01f57a994..83ff9dc53cdc973fb7a9a532959fa66d4437664a 100644 --- a/packages/ledgerstate/ledgerstate.go +++ b/packages/ledgerstate/ledgerstate.go @@ -142,21 +142,22 @@ func (ledgerState *LedgerState) GetReality(id RealityId) *objectstorage.CachedOb } } -func (ledgerState *LedgerState) BookTransfer(transfer *Transfer) error { +func (ledgerState *LedgerState) BookTransfer(transfer *Transfer) (err error) { inputs := ledgerState.getTransferInputs(transfer) - targetReality := ledgerState.getTargetReality(inputs) - defer targetReality.Release() + ledgerState.getTargetReality(inputs).Consume(func(object objectstorage.StorableObject) { + targetReality := object.(*Reality) - if bookingErr := targetReality.Get().(*Reality).bookTransfer(transfer.GetHash(), inputs, transfer.GetOutputs()); bookingErr != nil { - return bookingErr - } + if err = targetReality.bookTransfer(transfer.GetHash(), inputs, transfer.GetOutputs()); err != nil { + return + } - if !targetReality.IsStored() { - targetReality.Store() - } + if !targetReality.PersistenceEnabled() { + targetReality.Persist() + } + }) - return nil + return } func (ledgerState *LedgerState) GenerateRealityVisualization(pngFilename string) error { @@ -299,12 +300,49 @@ func (ledgerState *LedgerState) AggregateRealities(realityIds ...RealityId) *obj } } - sortedRealityIds := ledgerState.sortRealityIds(aggregatedRealities) + parentConflictRealities := make(map[RealityId]*objectstorage.CachedObject) + aggregatedRealityParentIds := make([]RealityId, len(aggregatedRealities)) + + counter := 0 + for aggregatedRealityId, cachedAggregatedReality := range aggregatedRealities { + aggregatedRealityParentIds[counter] = aggregatedRealityId + counter++ + + aggregatedReality := cachedAggregatedReality.Get().(*Reality) + if !aggregatedReality.IsAggregated() { + parentConflictRealities[aggregatedRealityId] = cachedAggregatedReality + } else { + aggregatedReality.collectParentConflictRealities(parentConflictRealities) + + cachedAggregatedReality.Release() + } + } + + aggregatedRealityId := ledgerState.generateAggregatedRealityId(ledgerState.sortRealityIds(parentConflictRealities)) + + newAggregatedRealityCreated := false + if cachedAggregatedReality, err := ledgerState.realities.ComputeIfAbsent(aggregatedRealityId[:], func(key []byte) (object objectstorage.StorableObject, e error) { + aggregatedReality := newReality(aggregatedRealityId, aggregatedRealityParentIds...) + aggregatedReality.ledgerState = ledgerState + + aggregatedReality.SetModified() + + newAggregatedRealityCreated = true - aggregatedReality := newReality(ledgerState.generateAggregatedRealityId(sortedRealityIds), sortedRealityIds...) - aggregatedReality.ledgerState = ledgerState + return aggregatedReality, nil + }); err != nil { + panic(err) + } else { + if !newAggregatedRealityCreated { + aggregatedReality := cachedAggregatedReality.Get().(*Reality) - return ledgerState.realities.Prepare(aggregatedReality) + for _, realityId := range aggregatedRealityParentIds { + aggregatedReality.AddParentReality(realityId) + } + } + + return cachedAggregatedReality + } } } diff --git a/packages/ledgerstate/ledgerstate_test.go b/packages/ledgerstate/ledgerstate_test.go index 4727db2de3e83282fb1f3ed6f789213f94b9c9b3..fc5efc9e7dfa7b13990ac30e29e74e1364a496de 100644 --- a/packages/ledgerstate/ledgerstate_test.go +++ b/packages/ledgerstate/ledgerstate_test.go @@ -37,7 +37,7 @@ func init() { func Benchmark(b *testing.B) { ledgerState := NewLedgerState("testLedger").Prune().AddTransferOutput( - transferHash1, addressHash1, NewColoredBalance(eth, 1337), + transferHash1, addressHash1, NewColoredBalance(eth, 1024), ) b.ResetTimer() @@ -50,7 +50,7 @@ func Benchmark(b *testing.B) { if err := ledgerState.BookTransfer(NewTransfer(newTransferHash).AddInput( NewTransferOutputReference(lastTransferHash, addressHash1), ).AddOutput( - addressHash1, NewColoredBalance(eth, 1337), + addressHash1, NewColoredBalance(eth, 1024), )); err != nil { b.Error(err) } @@ -61,7 +61,7 @@ func Benchmark(b *testing.B) { func Test(t *testing.T) { ledgerState := NewLedgerState("testLedger").Prune().AddTransferOutput( - transferHash1, addressHash1, NewColoredBalance(eth, 1337), NewColoredBalance(iota_, 1338), + transferHash1, addressHash1, NewColoredBalance(eth, 1024), NewColoredBalance(iota_, 1338), ) ledgerState.CreateReality(pendingReality) @@ -132,7 +132,7 @@ func initializeLedgerStateWithBalances(numberOfBalances int) (ledgerState *Ledge transferHash := generateRandomTransferHash() addressHash := generateRandomAddressHash() - ledgerState.AddTransferOutput(transferHash, addressHash, NewColoredBalance(iota_, 1337)) + ledgerState.AddTransferOutput(transferHash, addressHash, NewColoredBalance(iota_, 1024)) result = append(result, NewTransferOutputReference(transferHash, addressHash)) } @@ -179,6 +179,87 @@ func spend(ledgerState *LedgerState, transferOutputReferences ...*TransferOutput return } +func multiSpend(ledgerState *LedgerState, outputCount int, transferOutputReferences ...*TransferOutputReference) (result []*TransferOutputReference) { + transferHash := generateRandomTransferHash() + + transfer := NewTransfer(transferHash) + + totalInputBalance := uint64(0) + for _, transferOutputReference := range transferOutputReferences { + ledgerState.GetTransferOutput(transferOutputReference).Consume(func(object objectstorage.StorableObject) { + transferOutput := object.(*TransferOutput) + + for _, coloredBalance := range transferOutput.GetBalances() { + totalInputBalance += coloredBalance.GetValue() + } + }) + + transfer.AddInput(transferOutputReference) + } + + for i := 0; i < outputCount; i++ { + addressHash := generateRandomAddressHash() + + transfer.AddOutput( + addressHash, NewColoredBalance(iota_, totalInputBalance/uint64(outputCount)), + ) + + result = append(result, NewTransferOutputReference(transferHash, addressHash)) + } + + if err := ledgerState.BookTransfer(transfer); err != nil { + panic(err) + } + + return +} + +func TestAggregateAggregatedRealities(t *testing.T) { + ledgerState, transferOutputs := initializeLedgerStateWithBalances(3) + + multiSpend(ledgerState, 1, transferOutputs[0]) + outputs0 := multiSpend(ledgerState, 2, multiSpend(ledgerState, 1, transferOutputs[0])[0]) + + multiSpend(ledgerState, 1, transferOutputs[1]) + outputs1 := multiSpend(ledgerState, 2, multiSpend(ledgerState, 1, transferOutputs[1])[0]) + + multiSpend(ledgerState, 1, transferOutputs[2]) + outputs2 := multiSpend(ledgerState, 2, multiSpend(ledgerState, 1, transferOutputs[2])[0]) + + aggregatedOutputs0 := multiSpend(ledgerState, 2, outputs0[0], outputs1[0]) + aggregatedOutputs1 := multiSpend(ledgerState, 2, outputs1[1], outputs2[1]) + aggregatedOutputs2 := multiSpend(ledgerState, 2, outputs0[1], outputs2[0]) + + multiSpend(ledgerState, 1, aggregatedOutputs0[0], aggregatedOutputs1[0]) + multiSpend(ledgerState, 1, aggregatedOutputs0[1], aggregatedOutputs2[0]) + + time.Sleep(1000 * time.Millisecond) + + objectstorage.WaitForWritesToFlush() + + if err := ledgerState.GenerateRealityVisualization("realities1.png"); err != nil { + t.Error(err) + } + + if err := NewVisualizer(ledgerState).RenderTransferOutputs("outputs1.png"); err != nil { + t.Error(err) + } + + multiSpend(ledgerState, 2, outputs0[0], outputs1[0]) + + time.Sleep(1000 * time.Millisecond) + + objectstorage.WaitForWritesToFlush() + + if err := ledgerState.GenerateRealityVisualization("realities2.png"); err != nil { + t.Error(err) + } + + if err := NewVisualizer(ledgerState).RenderTransferOutputs("outputs2.png"); err != nil { + t.Error(err) + } +} + func TestElevateAggregatedReality(t *testing.T) { ledgerState, transferOutputs := initializeLedgerStateWithBalances(3) @@ -221,7 +302,7 @@ func TestElevateAggregatedReality(t *testing.T) { func TestElevate(t *testing.T) { ledgerState := NewLedgerState("testLedger").Prune().AddTransferOutput( - transferHash1, addressHash1, NewColoredBalance(eth, 1337), NewColoredBalance(iota_, 1338), + transferHash1, addressHash1, NewColoredBalance(eth, 1024), NewColoredBalance(iota_, 1338), ) // create first legit spend @@ -230,7 +311,7 @@ func TestElevate(t *testing.T) { ).AddOutput( addressHash2, NewColoredBalance(iota_, 1338), ).AddOutput( - addressHash2, NewColoredBalance(eth, 1337), + addressHash2, NewColoredBalance(eth, 1024), )); err != nil { t.Error(err) } @@ -241,7 +322,7 @@ func TestElevate(t *testing.T) { ).AddOutput( addressHash4, NewColoredBalance(iota_, 1338), ).AddOutput( - addressHash4, NewColoredBalance(eth, 1337), + addressHash4, NewColoredBalance(eth, 1024), )); err != nil { t.Error(err) } @@ -251,7 +332,7 @@ func TestElevate(t *testing.T) { ).AddOutput( addressHash4, NewColoredBalance(iota_, 1338), ).AddOutput( - addressHash4, NewColoredBalance(eth, 1337), + addressHash4, NewColoredBalance(eth, 1024), )); err != nil { t.Error(err) } @@ -275,7 +356,7 @@ func TestElevate(t *testing.T) { ).AddOutput( addressHash5, NewColoredBalance(iota_, 1338), ).AddOutput( - addressHash5, NewColoredBalance(eth, 1337), + addressHash5, NewColoredBalance(eth, 1024), )); err != nil { t.Error(err) } diff --git a/packages/ledgerstate/outputs.png b/packages/ledgerstate/outputs.png index d681a69339839d9272253fc74b47ed3d313af085..6f2378aae6fd63d8cc32cebf1de4b0d53e3657d6 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 new file mode 100644 index 0000000000000000000000000000000000000000..78638b4ac0d547722553ecba91080eb482cfdfac Binary files /dev/null and b/packages/ledgerstate/outputs1.png differ diff --git a/packages/ledgerstate/outputs2.png b/packages/ledgerstate/outputs2.png new file mode 100644 index 0000000000000000000000000000000000000000..4e5fb6fc2eb50722536f8cebf3237379c06830c8 Binary files /dev/null and b/packages/ledgerstate/outputs2.png differ diff --git a/packages/ledgerstate/realities.png b/packages/ledgerstate/realities.png index 081531cd0633c8945012907f4023596c4c9cc3b8..bb6453828ee02fc9679bf7b5ca9488e12feb7cbd 100644 Binary files a/packages/ledgerstate/realities.png and b/packages/ledgerstate/realities.png differ diff --git a/packages/ledgerstate/realities1.png b/packages/ledgerstate/realities1.png new file mode 100644 index 0000000000000000000000000000000000000000..73be1b7f5e854ce26112b3021b8a03c534dff7f9 Binary files /dev/null and b/packages/ledgerstate/realities1.png differ diff --git a/packages/ledgerstate/realities2.png b/packages/ledgerstate/realities2.png new file mode 100644 index 0000000000000000000000000000000000000000..a6fb2ce1c43c24eb3059ea3a8c840a5b846fa769 Binary files /dev/null and b/packages/ledgerstate/realities2.png differ diff --git a/packages/ledgerstate/reality.go b/packages/ledgerstate/reality.go index 62a15f7c3802a003c105ec8ca977359e011b8f1a..afbbcb50b59c0b9b33fa60bfe47f174b1781b6d3 100644 --- a/packages/ledgerstate/reality.go +++ b/packages/ledgerstate/reality.go @@ -11,6 +11,8 @@ import ( ) type Reality struct { + objectstorage.StorableObjectFlags + id RealityId parentRealityIds RealityIdSet parentRealityIdsMutex sync.RWMutex @@ -52,11 +54,22 @@ func (reality *Reality) GetParentRealityIds() (realityIdSet RealityIdSet) { return } -// Sets the set of RealityIds that are the parents of this Reality. -func (reality *Reality) SetParentRealityIds(parentRealityIds RealityIdSet) { - reality.parentRealityIdsMutex.Lock() - reality.parentRealityIds = parentRealityIds - reality.parentRealityIdsMutex.Unlock() +// Adds a new parent Reality to this Reality (it is used for aggregating aggregated Realities). +func (reality *Reality) AddParentReality(realityId RealityId) { + reality.parentRealityIdsMutex.RLock() + if _, exists := reality.parentRealityIds[realityId]; !exists { + reality.parentRealityIdsMutex.RUnlock() + + reality.parentRealityIdsMutex.Lock() + if _, exists := reality.parentRealityIds[realityId]; !exists { + reality.parentRealityIds[realityId] = void + + reality.SetModified() + } + reality.parentRealityIdsMutex.Unlock() + } else { + reality.parentRealityIdsMutex.RUnlock() + } } // Returns the amount of TransferOutputs in this Reality. @@ -102,15 +115,12 @@ func (reality *Reality) DescendsFrom(realityId RealityId) bool { } } -// endregion /////////////////////////////////////////////////////////////////////////////////////////////////////////// - -// [DONE] Returns a map of all parent realities (one level). They have to manually be "released" when they are not -// needed anymore. -func (reality *Reality) GetParentRealities() map[RealityId]*objectstorage.CachedObject { - parentRealities := make(map[RealityId]*objectstorage.CachedObject) +// Returns a map of all parent realities (one level). They have to be "released" manually when they are not needed +// anymore. +func (reality *Reality) GetParentRealities() (parentRealities map[RealityId]*objectstorage.CachedObject) { + parentRealities = make(map[RealityId]*objectstorage.CachedObject) reality.parentRealityIdsMutex.RLock() - for parentRealityId := range reality.parentRealityIds { loadedParentReality := reality.ledgerState.GetReality(parentRealityId) if !loadedParentReality.Exists() { @@ -121,13 +131,13 @@ func (reality *Reality) GetParentRealities() map[RealityId]*objectstorage.Cached parentRealities[loadedParentReality.Get().(*Reality).id] = loadedParentReality } - reality.parentRealityIdsMutex.RUnlock() - return parentRealities + return } -// Returns +// 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. func (reality *Reality) GetParentConflictRealities() map[RealityId]*objectstorage.CachedObject { if !reality.IsAggregated() { return reality.GetParentRealities() @@ -140,6 +150,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) @@ -173,7 +185,11 @@ func (reality *Reality) GetAncestorRealities() (result map[RealityId]*objectstor // [DONE] Registers the conflict set in the Reality. func (reality *Reality) AddConflict(conflictSetId ConflictId) { reality.conflictIdsMutex.Lock() - reality.conflictIds[conflictSetId] = void + if _, exists := reality.conflictIds[conflictSetId]; !exists { + reality.conflictIds[conflictSetId] = void + + reality.SetModified() + } reality.conflictIdsMutex.Unlock() } @@ -293,8 +309,6 @@ func (reality *Reality) consumeInputs(inputs objectstorage.CachedObjects, transf conflicts = append(conflicts, conflict) } } - - input.Store() } return @@ -331,11 +345,14 @@ func (reality *Reality) createRealityForConsumerOfConflictingInput(consumersOfCo newReality := newReality(elevatedRealityId, reality.id) newReality.ledgerState = reality.ledgerState + newReality.Persist() + newReality.SetModified() + realityIsNew = true return newReality, nil }); err == nil { - cachedElevatedReality.Store().Consume(func(object objectstorage.StorableObject) { + cachedElevatedReality.Consume(func(object objectstorage.StorableObject) { elevatedReality := object.(*Reality) // We register every Conflict with the Reality (independent if it is "new" or not), to reflect its @@ -369,6 +386,8 @@ func (reality *Reality) elevateTransferOutput(transferOutputReference *TransferO cachedTransferOutputToElevate.Consume(func(object objectstorage.StorableObject) { transferOutputToElevate := object.(*TransferOutput) + transferOutputToElevate.SetModified() + if transferOutputToElevate.GetRealityId() == reality.id { err = reality.elevateTransferOutputOfCurrentReality(transferOutputToElevate, newReality) } else { @@ -410,7 +429,10 @@ func (reality *Reality) elevateTransferOutputOfNestedReality(transferOutput *Tra newParentRealities := reality.GetParentRealityIds().Remove(oldParentRealityId).Add(newParentRealityId).ToList() - reality.ledgerState.AggregateRealities(newParentRealities...).Store().Consume(func(object objectstorage.StorableObject) { + reality.ledgerState.AggregateRealities(newParentRealities...).Consume(func(object objectstorage.StorableObject) { + object.Persist() + object.SetModified() + err = reality.elevateTransferOutputOfCurrentReality(transferOutput, object.(*Reality)) }) @@ -442,11 +464,13 @@ 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[:]) + //reality.ledgerState.realities.Delete(transferOutputRealityId[:]) } }) - oldTransferOutputBooking.Delete().Release() + oldTransferOutputBooking.Consume(func(transferOutputBooking objectstorage.StorableObject) { + transferOutputBooking.Delete() + }) } } diff --git a/packages/ledgerstate/transfer_output.go b/packages/ledgerstate/transfer_output.go index 9545d2e0b07f378b56abdbd75f1e3026e5955d11..a783ef161ff7857872f8c592bf7439f14d4f2cbb 100644 --- a/packages/ledgerstate/transfer_output.go +++ b/packages/ledgerstate/transfer_output.go @@ -9,6 +9,8 @@ import ( ) type TransferOutput struct { + objectstorage.StorableObjectFlags + transferHash TransferHash addressHash AddressHash balances []*ColoredBalance @@ -109,6 +111,8 @@ func (transferOutput *TransferOutput) addConsumer(consumer TransferHash, outputs transferOutput.consumers[consumer] = consumers transferOutput.consumersMutex.Unlock() + + transferOutput.SetModified() } return @@ -125,7 +129,9 @@ func (transferOutput *TransferOutput) markAsSpent() error { } else { transferOutput.ledgerState.storeTransferOutputBooking(newTransferOutputBooking(transferOutput.GetRealityId(), transferOutput.addressHash, true, transferOutput.transferHash)).Release() - oldTransferOutputBooking.Delete().Release() + oldTransferOutputBooking.Consume(func(transferOutputBooking objectstorage.StorableObject) { + transferOutputBooking.Delete() + }) } transferOutput.bookingMutex.Unlock() diff --git a/packages/ledgerstate/transfer_output_booking.go b/packages/ledgerstate/transfer_output_booking.go index 9e5d1b20f8c72427f37f5dad225913f60cb9842d..c60fde9a6816f56d2efd09d2f633dc5885e0c0d1 100644 --- a/packages/ledgerstate/transfer_output_booking.go +++ b/packages/ledgerstate/transfer_output_booking.go @@ -9,6 +9,8 @@ import ( // region struct + constructor + public api //////////////////////////////////////////////////////////////////////////// type TransferOutputBooking struct { + objectstorage.StorableObjectFlags + realityId RealityId addressHash AddressHash spent bool