diff --git a/packages/ledgerstate/ledgerstate.go b/packages/ledgerstate/ledgerstate.go index caf22788e6857bbf8dd24f3f317a8343e3b7de1a..e941bfab368a6ad7220165e4b7077fa01f57a994 100644 --- a/packages/ledgerstate/ledgerstate.go +++ b/packages/ledgerstate/ledgerstate.go @@ -1,9 +1,6 @@ package ledgerstate import ( - "io/ioutil" - "os" - "os/exec" "reflect" "sort" "strconv" @@ -234,68 +231,6 @@ func (ledgerState *LedgerState) GenerateRealityVisualization(pngFilename string) return graphviz.RenderPNG(graph, pngFilename) } -func (ledgerState *LedgerState) GenerateVisualization() error { - di := dot.NewGraph(dot.Directed) - - realityGraphs := make(map[RealityId]*dot.Graph) - - getRealityGraph := func(realityId RealityId) *dot.Graph { - realityGraph, exists := realityGraphs[realityId] - if !exists { - realityGraph = di.Subgraph("REALITY: "+strings.Trim(realityId.String(), "\x00"), dot.ClusterOption{}) - realityGraph.Attr("style", "filled") - realityGraph.Attr("color", "lightgrey") - realityGraph.Attr("fillcolor", "lightgrey") - - realityGraphs[realityId] = realityGraph - } - - return realityGraph - } - - var drawTransferOutput func(transferOutput *TransferOutput) dot.Node - drawTransferOutput = func(transferOutput *TransferOutput) dot.Node { - transferOutputNode := getRealityGraph(transferOutput.GetRealityId()).Node(transferOutput.GetTransferHash().String() + "\n" + transferOutput.GetAddressHash().String()) - transferOutputNode.Attr("style", "filled") - transferOutputNode.Attr("color", "white") - transferOutputNode.Attr("fillcolor", "white") - - for transferHash, addresses := range transferOutput.GetConsumers() { - for _, addressHash := range addresses { - ledgerState.GetTransferOutput(NewTransferOutputReference(transferHash, addressHash)).Consume(func(object objectstorage.StorableObject) { - transferOutputNode.Edge(drawTransferOutput(object.(*TransferOutput))) - }) - } - } - - return transferOutputNode - } - - ledgerState.ForEachTransferOutput(func(object *objectstorage.CachedObject) bool { - object.Consume(func(object objectstorage.StorableObject) { - drawTransferOutput(object.(*TransferOutput)) - }) - - return true - }, MAIN_REALITY_ID) - - d1 := []byte(di.String()) - if err := ioutil.WriteFile("_tmp.dot", d1, 0755); err != nil { - return err - } - - _, err := exec.Command("dot", "-Tpng", "_tmp.dot", "-o", "viz.png").Output() - if err != nil { - return err - } - - if err := os.Remove("_tmp.dot"); err != nil { - return err - } - - return nil -} - func (ledgerState *LedgerState) AggregateRealities(realityIds ...RealityId) *objectstorage.CachedObject { switch len(realityIds) { case 0: diff --git a/packages/ledgerstate/ledgerstate_test.go b/packages/ledgerstate/ledgerstate_test.go index 3ede10579c54fcf560b224a631e50a65c469b4f1..6c36a10338f778cd05f74d2028581108792a8bef 100644 --- a/packages/ledgerstate/ledgerstate_test.go +++ b/packages/ledgerstate/ledgerstate_test.go @@ -8,13 +8,11 @@ import ( "github.com/iotaledger/hive.go/parameter" - "github.com/iotaledger/goshimmer/packages/utils" - "github.com/iotaledger/hive.go/objectstorage" ) var ( - iota = NewColor("IOTA") + iota_ = NewColor("IOTA") eth = NewColor("ETH") transferHash1 = NewTransferHash("TRANSFER1") transferHash2 = NewTransferHash("TRANSFER2") @@ -63,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, 1337), NewColoredBalance(iota_, 1338), ) ledgerState.CreateReality(pendingReality) @@ -71,11 +69,11 @@ func Test(t *testing.T) { transfer := NewTransfer(transferHash2).AddInput( NewTransferOutputReference(transferHash1, addressHash1), ).AddOutput( - addressHash3, NewColoredBalance(iota, 338), + addressHash3, NewColoredBalance(iota_, 338), ).AddOutput( addressHash3, NewColoredBalance(eth, 337), ).AddOutput( - addressHash4, NewColoredBalance(iota, 1000), + addressHash4, NewColoredBalance(iota_, 1000), ).AddOutput( addressHash4, NewColoredBalance(eth, 1000), ) @@ -87,11 +85,11 @@ func Test(t *testing.T) { if err := ledgerState.BookTransfer(NewTransfer(transferHash3).AddInput( NewTransferOutputReference(transferHash1, addressHash1), ).AddOutput( - addressHash3, NewColoredBalance(iota, 338), + addressHash3, NewColoredBalance(iota_, 338), ).AddOutput( addressHash3, NewColoredBalance(eth, 337), ).AddOutput( - addressHash4, NewColoredBalance(iota, 1000), + addressHash4, NewColoredBalance(iota_, 1000), ).AddOutput( addressHash4, NewColoredBalance(eth, 1000), )); err != nil { @@ -111,12 +109,20 @@ func Test(t *testing.T) { }) } +var transferHashCounter = 0 + func generateRandomTransferHash() TransferHash { - return NewTransferHash(utils.RandomString(32)) + transferHashCounter++ + + return NewTransferHash("TRANSFER" + strconv.Itoa(transferHashCounter)) } +var addressHashCounter = 0 + func generateRandomAddressHash() AddressHash { - return NewAddressHash(utils.RandomString(32)) + addressHashCounter++ + + return NewAddressHash("ADDRESS" + strconv.Itoa(addressHashCounter)) } func initializeLedgerStateWithBalances(numberOfBalances int) (ledgerState *LedgerState, result []*TransferOutputReference) { @@ -126,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_, 1337)) result = append(result, NewTransferOutputReference(transferHash, addressHash)) } @@ -147,7 +153,27 @@ func spend(ledgerState *LedgerState, transferOutputReferences ...*TransferOutput addressHash := generateRandomAddressHash() transfer := NewTransfer(transferHash).AddOutput( - addressHash, NewColoredBalance(iota, uint64(len(transferOutputReferences))*1337), + addressHash, NewColoredBalance(iota_, uint64(len(transferOutputReferences))*1337), + ) + for _, transferOutputReference := range transferOutputReferences { + transfer.AddInput(transferOutputReference) + } + + if err := ledgerState.BookTransfer(transfer); err != nil { + panic(err) + } + + result = NewTransferOutputReference(transferHash, addressHash) + + return +} + +func spend2(ledgerState *LedgerState, transferOutputReferences ...*TransferOutputReference) (result *TransferOutputReference) { + transferHash := generateRandomTransferHash() + addressHash := generateRandomAddressHash() + + transfer := NewTransfer(transferHash).AddOutput( + addressHash, NewColoredBalance(iota_, uint64(len(transferOutputReferences))*2*1337), ) for _, transferOutputReference := range transferOutputReferences { transfer.AddInput(transferOutputReference) @@ -163,21 +189,29 @@ func spend(ledgerState *LedgerState, transferOutputReferences ...*TransferOutput } func TestElevateAggregatedReality(t *testing.T) { - ledgerState, transferOutputs := initializeLedgerStateWithBalances(2) + ledgerState, transferOutputs := initializeLedgerStateWithBalances(3) // create 2 double spends doubleSpentOutputs1 := doubleSpend(ledgerState, transferOutputs[0]) doubleSpentOutputs2 := doubleSpend(ledgerState, transferOutputs[1]) + normalSpend := spend(ledgerState, transferOutputs[2]) + _ = doubleSpend(ledgerState, normalSpend) // send funds from one of the double spends further spentInput := spend(ledgerState, doubleSpentOutputs1[1]) // aggregate further sent funds with other reality - spend(ledgerState, spentInput, doubleSpentOutputs2[0]) + outputOfAggregatedReality := spend(ledgerState, spentInput, doubleSpentOutputs2[0]) // double spend further spend to elevate aggregated reality spend(ledgerState, doubleSpentOutputs1[1]) + // double spend funds of aggregated reality + spend(ledgerState, spentInput, doubleSpentOutputs2[0]) + + // spend funds of conflict in aggregated reality further + spend2(ledgerState, outputOfAggregatedReality) + time.Sleep(1000 * time.Millisecond) objectstorage.WaitForWritesToFlush() @@ -186,25 +220,21 @@ func TestElevateAggregatedReality(t *testing.T) { t.Error(err) } - ledgerState.ForEachTransferOutput(func(object *objectstorage.CachedObject) bool { - object.Consume(func(object objectstorage.StorableObject) { - fmt.Println(object.(*TransferOutput)) - }) - - return true - }, MAIN_REALITY_ID) + if err := NewVisualizer(ledgerState).RenderTransferOutputs("outputs.png"); err != nil { + t.Error(err) + } } func TestElevate(t *testing.T) { ledgerState := NewLedgerState("testLedger").Prune().AddTransferOutput( - transferHash1, addressHash1, NewColoredBalance(eth, 1337), NewColoredBalance(iota, 1338), + transferHash1, addressHash1, NewColoredBalance(eth, 1337), NewColoredBalance(iota_, 1338), ) // create first legit spend if err := ledgerState.BookTransfer(NewTransfer(transferHash2).AddInput( NewTransferOutputReference(transferHash1, addressHash1), ).AddOutput( - addressHash2, NewColoredBalance(iota, 1338), + addressHash2, NewColoredBalance(iota_, 1338), ).AddOutput( addressHash2, NewColoredBalance(eth, 1337), )); err != nil { @@ -215,7 +245,7 @@ func TestElevate(t *testing.T) { if err := ledgerState.BookTransfer(NewTransfer(transferHash3).AddInput( NewTransferOutputReference(transferHash2, addressHash2), ).AddOutput( - addressHash4, NewColoredBalance(iota, 1338), + addressHash4, NewColoredBalance(iota_, 1338), ).AddOutput( addressHash4, NewColoredBalance(eth, 1337), )); err != nil { @@ -225,7 +255,7 @@ func TestElevate(t *testing.T) { if err := ledgerState.BookTransfer(NewTransfer(transferHash4).AddInput( NewTransferOutputReference(transferHash2, addressHash2), ).AddOutput( - addressHash4, NewColoredBalance(iota, 1338), + addressHash4, NewColoredBalance(iota_, 1338), ).AddOutput( addressHash4, NewColoredBalance(eth, 1337), )); err != nil { @@ -238,7 +268,7 @@ func TestElevate(t *testing.T) { ).AddInput( NewTransferOutputReference(transferHash4, addressHash4), ).AddOutput( - addressHash6, NewColoredBalance(iota, 2676), + addressHash6, NewColoredBalance(iota_, 2676), ).AddOutput( addressHash6, NewColoredBalance(eth, 2674), )); err != nil { @@ -249,7 +279,7 @@ func TestElevate(t *testing.T) { if err := ledgerState.BookTransfer(NewTransfer(transferHash5).AddInput( NewTransferOutputReference(transferHash1, addressHash1), ).AddOutput( - addressHash5, NewColoredBalance(iota, 1338), + addressHash5, NewColoredBalance(iota_, 1338), ).AddOutput( addressHash5, NewColoredBalance(eth, 1337), )); err != nil { diff --git a/packages/ledgerstate/outputs.png b/packages/ledgerstate/outputs.png new file mode 100644 index 0000000000000000000000000000000000000000..3c9f53b9f5faaac17c0e4f188387bc0bdc5b8232 Binary files /dev/null and b/packages/ledgerstate/outputs.png differ diff --git a/packages/ledgerstate/realities.png b/packages/ledgerstate/realities.png index ee04a70dc73d14b3b2f6fb0e66d329a0c21c9f19..789f1c48bb59962df9ec5af99bcfb7410b387968 100644 Binary files a/packages/ledgerstate/realities.png and b/packages/ledgerstate/realities.png differ diff --git a/packages/ledgerstate/reality.go b/packages/ledgerstate/reality.go index 3645f10697a9699a661c835e263234772b8e6516..5be5e02ac4cc1c0f79c1ab55a1501e0d2ced86c8 100644 --- a/packages/ledgerstate/reality.go +++ b/packages/ledgerstate/reality.go @@ -144,7 +144,7 @@ func (reality *Reality) GetAncestorRealities() (result map[RealityId]*objectstor } // [DONE] Registers the conflict set in the Reality. -func (reality *Reality) AddConflictSet(conflictSetId ConflictId) { +func (reality *Reality) AddConflict(conflictSetId ConflictId) { reality.conflictIdsMutex.Lock() reality.conflictIds[conflictSetId] = void reality.conflictIdsMutex.Unlock() @@ -164,44 +164,47 @@ func (reality *Reality) BookTransfer(transfer *Transfer) (err error) { return } -func (reality *Reality) bookTransfer(transferHash TransferHash, inputs objectstorage.CachedObjects, outputs map[AddressHash][]*ColoredBalance) error { - if err := reality.verifyTransfer(inputs, outputs); err != nil { - return err +func (reality *Reality) bookTransfer(transferHash TransferHash, inputs objectstorage.CachedObjects, outputs map[AddressHash][]*ColoredBalance) (err error) { + if err = reality.verifyTransfer(inputs, outputs); err != nil { + return } - conflictSets, err := reality.consumeInputs(inputs, transferHash, outputs) + conflicts, err := reality.consumeInputs(inputs, transferHash, outputs) if err != nil { - return err + return } - if len(conflictSets) >= 1 { - var targetRealityId RealityId - copy(targetRealityId[:], transferHash[:]) + if len(conflicts) >= 1 { + targetRealityId := transferHash.ToRealityId() reality.CreateReality(targetRealityId).Consume(func(object objectstorage.StorableObject) { targetReality := object.(*Reality) - for _, cachedConflictSet := range conflictSets { + for _, cachedConflictSet := range conflicts { conflictSet := cachedConflictSet.Get().(*Conflict) conflictSet.AddReality(targetRealityId) - targetReality.AddConflictSet(conflictSet.GetId()) + targetReality.AddConflict(conflictSet.GetId()) } for addressHash, coloredBalances := range outputs { - targetReality.bookTransferOutput(NewTransferOutput(reality.ledgerState, emptyRealityId, transferHash, addressHash, coloredBalances...)) + if err = targetReality.bookTransferOutput(NewTransferOutput(reality.ledgerState, emptyRealityId, transferHash, addressHash, coloredBalances...)); err != nil { + return + } } }) } else { for addressHash, coloredBalances := range outputs { - reality.bookTransferOutput(NewTransferOutput(reality.ledgerState, emptyRealityId, transferHash, addressHash, coloredBalances...)) + if err = reality.bookTransferOutput(NewTransferOutput(reality.ledgerState, emptyRealityId, transferHash, addressHash, coloredBalances...)); err != nil { + return + } } } - conflictSets.Release() + conflicts.Release() inputs.Release() - return nil + return } // Verifies the transfer and checks if it is valid (spends existing funds + the net balance is 0). @@ -213,12 +216,12 @@ func (reality *Reality) verifyTransfer(inputs []*objectstorage.CachedObject, out return errors.New("missing input in transfer") } - transferOutput := cachedInput.Get().(*TransferOutput) - if !reality.DescendsFrom(transferOutput.GetRealityId()) { + input := cachedInput.Get().(*TransferOutput) + if !reality.DescendsFrom(input.GetRealityId()) { return errors.New("the referenced funds do not exist in this reality") } - for _, balance := range transferOutput.GetBalances() { + for _, balance := range input.GetBalances() { totalColoredBalances[balance.GetColor()] += balance.GetValue() } } @@ -243,24 +246,24 @@ func (reality *Reality) verifyTransfer(inputs []*objectstorage.CachedObject, out return nil } -// Marks the consumed inputs as spent and returns the corresponding ConflictSets if the inputs have been consumed before. -func (reality *Reality) consumeInputs(inputs objectstorage.CachedObjects, transferHash TransferHash, outputs map[AddressHash][]*ColoredBalance) (conflictSets objectstorage.CachedObjects, err error) { - conflictSets = make(objectstorage.CachedObjects, 0) +// Marks the consumed inputs as spent and returns the corresponding Conflict 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) for _, input := range inputs { - consumedTransferOutput := input.Get().(*TransferOutput) + consumedInput := input.Get().(*TransferOutput) - if consumersToElevate, consumeErr := consumedTransferOutput.addConsumer(transferHash, outputs); consumeErr != nil { + if consumersToElevate, consumeErr := consumedInput.addConsumer(transferHash, outputs); consumeErr != nil { err = consumeErr return } else if consumersToElevate != nil { - if conflictSet, conflictErr := reality.retrieveConflictSetForConflictingInput(consumedTransferOutput, consumersToElevate); conflictErr != nil { + if conflict, conflictErr := reality.retrieveConflictForConflictingInput(consumedInput, consumersToElevate); conflictErr != nil { err = conflictErr return } else { - conflictSets = append(conflictSets, conflictSet) + conflicts = append(conflicts, conflict) } } @@ -270,50 +273,61 @@ func (reality *Reality) consumeInputs(inputs objectstorage.CachedObjects, transf return } -func (reality *Reality) retrieveConflictSetForConflictingInput(input *TransferOutput, consumersToElevate map[TransferHash][]AddressHash) (conflictSet *objectstorage.CachedObject, err error) { +func (reality *Reality) retrieveConflictForConflictingInput(input *TransferOutput, consumersToElevate map[TransferHash][]AddressHash) (conflict *objectstorage.CachedObject, err error) { conflictSetId := NewConflictSetId(input.GetTransferHash(), input.GetAddressHash()) if len(consumersToElevate) >= 1 { - newConflictSet := newConflictSet(conflictSetId) - newConflictSet.ledgerState = reality.ledgerState + newConflict := newConflictSet(conflictSetId) + newConflict.ledgerState = reality.ledgerState - conflictSet = reality.ledgerState.conflictSets.Store(newConflictSet) + conflict = reality.ledgerState.conflictSets.Store(newConflict) - err = reality.createRealityForConflictingConsumers(consumersToElevate, conflictSet.Get().(*Conflict)) - if err != nil { - return - } + err = reality.createRealityForConsumerOfConflictingInput(consumersToElevate, conflict.Get().(*Conflict)) } else { - conflictSet, err = reality.ledgerState.conflictSets.Load(conflictSetId[:]) - if err != nil { - return + if conflict, err = reality.ledgerState.conflictSets.Load(conflictSetId[:]); err == nil { + conflict.Get().(*Conflict).ledgerState = reality.ledgerState } - conflictSet.Get().(*Conflict).ledgerState = reality.ledgerState } return } -func (reality *Reality) createRealityForConflictingConsumers(conflictingConsumers map[TransferHash][]AddressHash, conflictSet *Conflict) (err error) { - for transferHash, addressHashes := range conflictingConsumers { - // determine RealityId - elevatedRealityId := transferHash.ToRealityId() - - // create new reality for every Transfer - reality.CreateReality(elevatedRealityId).Consume(func(object objectstorage.StorableObject) { - elevatedReality := object.(*Reality) - - // register Reality <-> Conflict - conflictSet.AddReality(elevatedRealityId) - elevatedReality.AddConflictSet(conflictSet.GetId()) - - // elevate TransferOutputs to the new Reality - for _, addressHash := range addressHashes { - if err = reality.elevateTransferOutput(NewTransferOutputReference(transferHash, addressHash), elevatedReality); err != nil { - return +// 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) { + for transferHash, addressHashes := range consumersOfConflictingInput { + var elevatedRealityId = transferHash.ToRealityId() + var realityIsNew bool + var cachedElevatedReality *objectstorage.CachedObject + + // 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) { + newReality := newReality(elevatedRealityId, reality.id) + newReality.ledgerState = reality.ledgerState + + realityIsNew = true + + return newReality, nil + }); err == nil { + cachedElevatedReality.Store().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 + // association to all corresponding Conflicts. (Note: A Reality can be part of multiple Conflicts if the + // Transfer that is associated to this Reality consumes multiple inputs. + conflict.AddReality(elevatedRealityId) + elevatedReality.AddConflict(conflict.GetId()) + + // A transaction can consume multiple inputs. We only elevate the consumers of a Reality once (when the + // Reality is created the first time). + if realityIsNew { + for _, addressHash := range addressHashes { + if err = reality.elevateTransferOutput(NewTransferOutputReference(transferHash, addressHash), elevatedReality); err != nil { + return + } + } } - } - }) + }) + } } return diff --git a/packages/ledgerstate/visualizer.go b/packages/ledgerstate/visualizer.go new file mode 100644 index 0000000000000000000000000000000000000000..53c700a7017371550f76ad0922cc011a2928a273 --- /dev/null +++ b/packages/ledgerstate/visualizer.go @@ -0,0 +1,182 @@ +package ledgerstate + +import ( + "strings" + + "github.com/iotaledger/goshimmer/packages/graphviz" + + "github.com/emicklei/dot" + "github.com/iotaledger/hive.go/objectstorage" +) + +type transferOutputId [transferHashLength + addressHashLength]byte + +type Visualizer struct { + ledgerState *LedgerState + graph *dot.Graph + realitySubGraphs map[RealityId]*dot.Graph + transferOutputNodes map[transferOutputId]dot.Node +} + +func NewVisualizer(ledgerState *LedgerState) *Visualizer { + return &Visualizer{ + ledgerState: ledgerState, + } +} + +func (visualizer *Visualizer) RenderTransferOutputs(pngFileName string) error { + visualizer.reset() + + visualizer.graph.Attr("ranksep", "1.0 equally") + visualizer.graph.Attr("compound", "true") + + visualizer.ledgerState.ForEachTransferOutput(func(object *objectstorage.CachedObject) bool { + object.Consume(func(object objectstorage.StorableObject) { + visualizer.drawTransferOutput(object.(*TransferOutput)) + }) + + return true + }, MAIN_REALITY_ID) + + return graphviz.RenderPNG(visualizer.graph, pngFileName) +} + +func (visualizer *Visualizer) reset() *Visualizer { + visualizer.graph = dot.NewGraph(dot.Directed) + visualizer.realitySubGraphs = make(map[RealityId]*dot.Graph) + visualizer.transferOutputNodes = make(map[transferOutputId]dot.Node) + + return visualizer +} + +func (visualizer *Visualizer) drawTransferOutput(transferOutput *TransferOutput) dot.Node { + transferOutputIdentifier := visualizer.generateTransferOutputId(transferOutput) + transferOutputNode, transferOutputDrawn := visualizer.transferOutputNodes[transferOutputIdentifier] + + if !transferOutputDrawn { + transferOutputNode = visualizer.getRealitySubGraph(transferOutput.GetRealityId()).Node("OUTPUT: " + strings.Trim(transferOutput.GetTransferHash().String(), "\x00") + " / " + strings.Trim(transferOutput.GetAddressHash().String(), "\x00")) + + visualizer.styleTransferOutputNode(transferOutputNode) + + for transferHash, addresses := range transferOutput.GetConsumers() { + for _, addressHash := range addresses { + visualizer.ledgerState.GetTransferOutput(NewTransferOutputReference(transferHash, addressHash)).Consume(func(object objectstorage.StorableObject) { + transferOutputNode.Edge(visualizer.drawTransferOutput(object.(*TransferOutput))) + }) + } + } + + visualizer.transferOutputNodes[transferOutputIdentifier] = transferOutputNode + } + + return transferOutputNode +} + +func (visualizer *Visualizer) generateTransferOutputId(transferOutput *TransferOutput) (result transferOutputId) { + transferHash := transferOutput.GetTransferHash() + addressHash := transferOutput.GetAddressHash() + + copy(result[:], transferHash[:]) + copy(result[transferHashLength:], addressHash[:]) + + return +} + +func (Visualizer *Visualizer) styleTransferOutputNode(transferOutputNode dot.Node) { + transferOutputNode.Attr("fontname", "helvetica") + transferOutputNode.Attr("fontsize", "11") + transferOutputNode.Attr("style", "filled") + transferOutputNode.Attr("shape", "box") + transferOutputNode.Attr("color", "#6C8EBF") + transferOutputNode.Attr("fillcolor", "white") +} + +func (visualizer *Visualizer) getRealitySubGraph(realityId RealityId) *dot.Graph { + realityGraph, exists := visualizer.realitySubGraphs[realityId] + if !exists { + visualizer.ledgerState.GetReality(realityId).Consume(func(object objectstorage.StorableObject) { + reality := object.(*Reality) + + parentRealities := reality.GetParentRealityIds() + switch true { + case len(parentRealities) > 1: + realityGraph = visualizer.getRealitySubGraph(MAIN_REALITY_ID).Subgraph(visualizer.generateRealityName(parentRealities.ToList()...), dot.ClusterOption{}) + + visualizer.styleRealitySubGraph(realityGraph, realityTypeAggregated) + + //dummyNode := realityGraph.Node(ledgerState.generateRealityName(parentRealities.ToList()...) + "_dummy") + //dummyNode.Attr("shape", "point") + //dummyNode.Attr("style", "invis") + //dummyNode.Attr("peripheries", "0") + //dummyNode.Attr("height", "0") + //dummyNode.Attr("width", "0") + case len(parentRealities) == 1: + for parentRealityId := range parentRealities { + realityGraph = visualizer.getRealitySubGraph(parentRealityId).Subgraph(visualizer.generateRealityName(realityId), dot.ClusterOption{}) + + visualizer.styleRealitySubGraph(realityGraph, realityTypeDefault) + } + default: + realityGraph = visualizer.graph.Subgraph(visualizer.generateRealityName(realityId), dot.ClusterOption{}) + + visualizer.styleRealitySubGraph(realityGraph, realityTypeMain) + } + }) + + visualizer.realitySubGraphs[realityId] = realityGraph + } + + return realityGraph +} + +func (visualizer *Visualizer) styleRealitySubGraph(realitySubGraph *dot.Graph, realityType realityType) { + realitySubGraph.Attr("fontname", "helvetica") + realitySubGraph.Attr("fontsize", "11") + realitySubGraph.Attr("style", "filled") + realitySubGraph.Attr("nodesep", "0") + + switch realityType { + case realityTypeAggregated: + realitySubGraph.Attr("color", "#9673A6") + realitySubGraph.Attr("fillcolor", "#E1D5E7") + case realityTypeMain: + realitySubGraph.Attr("color", "#D6B656") + realitySubGraph.Attr("fillcolor", "#FFF2CC") + case realityTypeDefault: + realitySubGraph.Attr("color", "#6C8EBF") + realitySubGraph.Attr("fillcolor", "#DAE8FC") + } +} + +func (visualizer *Visualizer) generateRealityName(realityIds ...RealityId) string { + if len(realityIds) > 1 { + result := "AGGREGATED REALITY [ " + + realityIdCount := len(realityIds) + for id, realityId := range realityIds { + if id == realityIdCount-1 { + result += strings.Trim(realityId.String(), "\x00") + } else { + result += strings.Trim(realityId.String(), "\x00") + " + " + } + } + + result += " ]" + + return result + } else { + if realityIds[0] == MAIN_REALITY_ID { + return strings.Trim(realityIds[0].String(), "\x00") + } + + return "REALITY [ " + strings.Trim(realityIds[0].String(), "\x00") + " ]" + } +} + +type realityType int + +const ( + realityTypeAggregated = iota + realityTypeMain + realityTypeDefault +)