diff --git a/dapps/valuetransfers/packages/branchmanager/branch.go b/dapps/valuetransfers/packages/branchmanager/branch.go index 09e862bc2c3cfb74dc08e3d9f63e2e310610543f..2fde95d8db13560881628288fad10082864f4038 100644 --- a/dapps/valuetransfers/packages/branchmanager/branch.go +++ b/dapps/valuetransfers/packages/branchmanager/branch.go @@ -214,7 +214,6 @@ func (branch *Branch) setPreferred(preferred bool) (modified bool) { branch.preferred = preferred branch.SetModified() modified = true - return } @@ -246,8 +245,7 @@ func (branch *Branch) setLiked(liked bool) (modified bool) { branch.liked = liked branch.SetModified() modified = true - - return branch.liked + return } // Finalized returns true if the branch has been marked as finalized. diff --git a/dapps/valuetransfers/packages/branchmanager/branchmanager.go b/dapps/valuetransfers/packages/branchmanager/branchmanager.go index 97100a8cbb11db52960d89ea9cbd95a1dc0f505d..9e1bafbe97952ab1c3228106e335aea2ec27db50 100644 --- a/dapps/valuetransfers/packages/branchmanager/branchmanager.go +++ b/dapps/valuetransfers/packages/branchmanager/branchmanager.go @@ -251,6 +251,8 @@ func (branchManager *BranchManager) BranchesConflicting(branchIds ...BranchID) ( } if conflictMember.BranchID() == currentBranchID { + cachedConflictMember.Release() + continue } @@ -392,7 +394,7 @@ func (branchManager *BranchManager) setBranchPreferred(cachedBranch *CachedBranc if !preferred { if modified = branch.setPreferred(false); modified { branchManager.Events.BranchUnpreferred.Trigger(cachedBranch) - + branchManager.propagatePreferredChangesToAggregatedChildBranches(branch.ID()) branchManager.propagateDislikeToFutureCone(cachedBranch.Retain()) } @@ -417,12 +419,63 @@ func (branchManager *BranchManager) setBranchPreferred(cachedBranch *CachedBranc } branchManager.Events.BranchPreferred.Trigger(cachedBranch) - + branchManager.propagatePreferredChangesToAggregatedChildBranches(branch.ID()) err = branchManager.propagateLike(cachedBranch.Retain()) return } +// propagatePreferredChangesToAggregatedChildBranches recursively updates the preferred flag of all descendants of the +// given Branch. +func (branchManager *BranchManager) propagatePreferredChangesToAggregatedChildBranches(parentBranchID BranchID) { + // initialize stack with children of the passed in parent Branch + branchStack := list.New() + branchManager.ChildBranches(parentBranchID).Consume(func(childBranchReference *ChildBranch) { + branchStack.PushBack(childBranchReference.ChildID()) + }) + + // iterate through child branches and propagate changes + for branchStack.Len() >= 1 { + // retrieve first element from the stack + currentEntry := branchStack.Front() + currentChildBranchID := currentEntry.Value.(BranchID) + branchStack.Remove(currentEntry) + + // load branch from storage + cachedAggregatedChildBranch := branchManager.Branch(currentChildBranchID) + + // only process branches that could be loaded and that are aggregated branches + if aggregatedChildBranch := cachedAggregatedChildBranch.Unwrap(); aggregatedChildBranch != nil && aggregatedChildBranch.IsAggregated() { + // determine if all parents are liked + allParentsPreferred := true + for _, parentID := range aggregatedChildBranch.ParentBranches() { + if allParentsPreferred { + allParentsPreferred = allParentsPreferred && branchManager.Branch(parentID).Consume(func(parentBranch *Branch) { + allParentsPreferred = allParentsPreferred && parentBranch.Preferred() + }) + } + } + + // trigger events and check children if the branch was updated + if aggregatedChildBranch.setPreferred(allParentsPreferred) { + if allParentsPreferred { + branchManager.Events.BranchPreferred.Trigger(cachedAggregatedChildBranch) + } else { + branchManager.Events.BranchUnpreferred.Trigger(cachedAggregatedChildBranch) + } + + // schedule checks for children if preferred flag was updated + branchManager.ChildBranches(currentChildBranchID).Consume(func(childBranchReference *ChildBranch) { + branchStack.PushBack(childBranchReference.ChildID()) + }) + } + } + + // release the branch when we are done + cachedAggregatedChildBranch.Release() + } +} + func (branchManager *BranchManager) setBranchLiked(cachedBranch *CachedBranch, liked bool) (modified bool, err error) { defer cachedBranch.Release() branch := cachedBranch.Unwrap() @@ -611,7 +664,7 @@ func (branchManager *BranchManager) determineAggregatedBranchDetails(deepestComm continue } - if branch.IsAggregated() { + if !branch.IsAggregated() { aggregatedBranchConflictParents[branchID] = cachedBranch continue @@ -781,8 +834,6 @@ func (branchManager *BranchManager) findDeepestCommonDescendants(branches ...Bra cachedAggregatedBranch.Release() result[branchID] = cachedBranch - - return nil } } diff --git a/dapps/valuetransfers/packages/branchmanager/branchmanager_test.go b/dapps/valuetransfers/packages/branchmanager/branchmanager_test.go index 776e620199c2b3363702c7972f57c0622f476f68..e5a148f6efaa36c55bf160bb5e8d2a11a5f2702b 100644 --- a/dapps/valuetransfers/packages/branchmanager/branchmanager_test.go +++ b/dapps/valuetransfers/packages/branchmanager/branchmanager_test.go @@ -1,23 +1,1052 @@ package branchmanager import ( - "fmt" "testing" - "github.com/iotaledger/goshimmer/dapps/valuetransfers/packages/transaction" "github.com/iotaledger/hive.go/kvstore/mapdb" + "github.com/stretchr/testify/assert" ) -func TestSomething(t *testing.T) { +type sampleTree struct { + cachedBranches [17]*CachedBranch + branches [17]*Branch + numID map[BranchID]int +} + +func (st *sampleTree) Release() { + for i := range st.cachedBranches { + if st.cachedBranches[i] == nil { + continue + } + st.cachedBranches[i].Release() + } +} + +// ./img/sample_tree.png +func createSampleTree(branchManager *BranchManager) *sampleTree { + st := &sampleTree{ + numID: map[BranchID]int{}, + } + cachedMasterBranch := branchManager.Branch(MasterBranchID) + st.cachedBranches[1] = cachedMasterBranch + st.branches[1] = cachedMasterBranch.Unwrap() + st.numID[st.branches[1].ID()] = 1 + + cachedBranch2, _ := branchManager.Fork(BranchID{2}, []BranchID{MasterBranchID}, []ConflictID{{0}}) + branch2 := cachedBranch2.Unwrap() + st.branches[2] = branch2 + st.cachedBranches[2] = cachedBranch2 + st.numID[st.branches[2].ID()] = 2 + + cachedBranch3, _ := branchManager.Fork(BranchID{3}, []BranchID{MasterBranchID}, []ConflictID{{0}}) + branch3 := cachedBranch3.Unwrap() + st.branches[3] = branch3 + st.cachedBranches[3] = cachedBranch3 + st.numID[st.branches[3].ID()] = 3 + + cachedBranch4, _ := branchManager.Fork(BranchID{4}, []BranchID{branch2.ID()}, []ConflictID{{1}}) + branch4 := cachedBranch4.Unwrap() + st.branches[4] = branch4 + st.cachedBranches[4] = cachedBranch4 + st.numID[st.branches[4].ID()] = 4 + + cachedBranch5, _ := branchManager.Fork(BranchID{5}, []BranchID{branch2.ID()}, []ConflictID{{1}}) + branch5 := cachedBranch5.Unwrap() + st.branches[5] = branch5 + st.cachedBranches[5] = cachedBranch5 + st.numID[st.branches[5].ID()] = 5 + + cachedBranch6, _ := branchManager.Fork(BranchID{6}, []BranchID{MasterBranchID}, []ConflictID{{2}}) + branch6 := cachedBranch6.Unwrap() + st.branches[6] = branch6 + st.cachedBranches[6] = cachedBranch6 + st.numID[st.branches[6].ID()] = 6 + + cachedBranch7, _ := branchManager.Fork(BranchID{7}, []BranchID{MasterBranchID}, []ConflictID{{2}}) + branch7 := cachedBranch7.Unwrap() + st.branches[7] = branch7 + st.cachedBranches[7] = cachedBranch7 + st.numID[st.branches[7].ID()] = 7 + + cachedBranch8, _ := branchManager.Fork(BranchID{8}, []BranchID{MasterBranchID}, []ConflictID{{3}}) + branch8 := cachedBranch8.Unwrap() + st.branches[8] = branch8 + st.cachedBranches[8] = cachedBranch8 + st.numID[st.branches[8].ID()] = 8 + + cachedBranch9, _ := branchManager.Fork(BranchID{9}, []BranchID{MasterBranchID}, []ConflictID{{3}}) + branch9 := cachedBranch9.Unwrap() + st.branches[9] = branch9 + st.cachedBranches[9] = cachedBranch9 + st.numID[st.branches[9].ID()] = 9 + + cachedAggrBranch10, err := branchManager.AggregateBranches(branch3.ID(), branch6.ID()) + if err != nil { + panic(err) + } + aggrBranch10 := cachedAggrBranch10.Unwrap() + st.branches[10] = aggrBranch10 + st.cachedBranches[10] = cachedAggrBranch10 + st.numID[st.branches[10].ID()] = 10 + + cachedAggrBranch11, err := branchManager.AggregateBranches(branch6.ID(), branch8.ID()) + if err != nil { + panic(err) + } + aggrBranch11 := cachedAggrBranch11.Unwrap() + st.branches[11] = aggrBranch11 + st.cachedBranches[11] = cachedAggrBranch11 + st.numID[st.branches[11].ID()] = 11 + + cachedBranch12, _ := branchManager.Fork(BranchID{12}, []BranchID{aggrBranch10.ID()}, []ConflictID{{4}}) + branch12 := cachedBranch12.Unwrap() + st.branches[12] = branch12 + st.cachedBranches[12] = cachedBranch12 + st.numID[st.branches[12].ID()] = 12 + + cachedBranch13, _ := branchManager.Fork(BranchID{13}, []BranchID{aggrBranch10.ID()}, []ConflictID{{4}}) + branch13 := cachedBranch13.Unwrap() + st.branches[13] = branch13 + st.cachedBranches[13] = cachedBranch13 + st.numID[st.branches[13].ID()] = 13 + + cachedBranch14, _ := branchManager.Fork(BranchID{14}, []BranchID{aggrBranch11.ID()}, []ConflictID{{5}}) + branch14 := cachedBranch14.Unwrap() + st.branches[14] = branch14 + st.cachedBranches[14] = cachedBranch14 + st.numID[st.branches[14].ID()] = 14 + + cachedBranch15, _ := branchManager.Fork(BranchID{15}, []BranchID{aggrBranch11.ID()}, []ConflictID{{5}}) + branch15 := cachedBranch15.Unwrap() + st.branches[15] = branch15 + st.cachedBranches[15] = cachedBranch15 + st.numID[st.branches[15].ID()] = 15 + + cachedAggrBranch16, err := branchManager.AggregateBranches(branch13.ID(), branch14.ID()) + if err != nil { + panic(err) + } + aggrBranch16 := cachedAggrBranch16.Unwrap() + st.branches[16] = aggrBranch16 + st.cachedBranches[16] = cachedAggrBranch16 + st.numID[st.branches[16].ID()] = 16 + return st +} + +func TestGetAncestorBranches(t *testing.T) { + branchManager := New(mapdb.NewMapDB()) + + st := createSampleTree(branchManager) + defer st.Release() + + { + masterBranch := branchManager.Branch(MasterBranchID) + assert.NotNil(t, masterBranch) + ancestorBranches, err := branchManager.getAncestorBranches(masterBranch.Unwrap()) + assert.NoError(t, err) + + expectedAncestors := map[BranchID]struct{}{} + actualAncestors := map[BranchID]struct{}{} + ancestorBranches.Consume(func(branch *Branch) { + actualAncestors[branch.ID()] = struct{}{} + }) + assert.Equal(t, expectedAncestors, actualAncestors) + } + + { + ancestorBranches, err := branchManager.getAncestorBranches(st.branches[4]) + assert.NoError(t, err) + + expectedAncestors := map[BranchID]struct{}{ + st.branches[2].ID(): {}, MasterBranchID: {}, + } + actualAncestors := map[BranchID]struct{}{} + ancestorBranches.Consume(func(branch *Branch) { + actualAncestors[branch.ID()] = struct{}{} + }) + assert.Equal(t, expectedAncestors, actualAncestors) + } + + { + ancestorBranches, err := branchManager.getAncestorBranches(st.branches[16]) + assert.NoError(t, err) + + expectedAncestors := map[BranchID]struct{}{ + st.branches[13].ID(): {}, st.branches[14].ID(): {}, + st.branches[10].ID(): {}, st.branches[11].ID(): {}, + st.branches[3].ID(): {}, st.branches[6].ID(): {}, st.branches[8].ID(): {}, + MasterBranchID: {}, + } + actualAncestors := map[BranchID]struct{}{} + ancestorBranches.Consume(func(branch *Branch) { + actualAncestors[branch.ID()] = struct{}{} + }) + assert.Equal(t, expectedAncestors, actualAncestors) + } + + { + ancestorBranches, err := branchManager.getAncestorBranches(st.branches[12]) + assert.NoError(t, err) + + expectedAncestors := map[BranchID]struct{}{ + st.branches[10].ID(): {}, st.branches[3].ID(): {}, + st.branches[6].ID(): {}, MasterBranchID: {}, + } + actualAncestors := map[BranchID]struct{}{} + ancestorBranches.Consume(func(branch *Branch) { + actualAncestors[branch.ID()] = struct{}{} + }) + assert.Equal(t, expectedAncestors, actualAncestors) + } + + { + ancestorBranches, err := branchManager.getAncestorBranches(st.branches[11]) + assert.NoError(t, err) + + expectedAncestors := map[BranchID]struct{}{ + st.branches[8].ID(): {}, st.branches[6].ID(): {}, MasterBranchID: {}, + } + actualAncestors := map[BranchID]struct{}{} + ancestorBranches.Consume(func(branch *Branch) { + actualAncestors[branch.ID()] = struct{}{} + }) + assert.Equal(t, expectedAncestors, actualAncestors) + } +} + +func TestIsAncestorOfBranch(t *testing.T) { + branchManager := New(mapdb.NewMapDB()) + + st := createSampleTree(branchManager) + defer st.Release() + + type testcase struct { + ancestor *Branch + descendent *Branch + is bool + } + + cases := []testcase{ + {st.branches[2], st.branches[4], true}, + {st.branches[4], st.branches[2], false}, + {st.branches[3], st.branches[4], false}, + {st.branches[3], st.branches[12], true}, + {st.branches[3], st.branches[10], true}, + {st.branches[3], st.branches[16], true}, + {st.branches[1], st.branches[16], true}, + {st.branches[11], st.branches[16], true}, + {st.branches[9], st.branches[16], false}, + {st.branches[6], st.branches[15], true}, + } + + for _, testCase := range cases { + isAncestor, err := branchManager.branchIsAncestorOfBranch(testCase.ancestor, testCase.descendent) + assert.NoError(t, err) + if testCase.is { + assert.True(t, isAncestor, "branch %d is an ancestor of branch %d", st.numID[testCase.ancestor.ID()], st.numID[testCase.descendent.ID()]) + continue + } + assert.False(t, isAncestor, "branch %d is not an ancestor of branch %d", st.numID[testCase.ancestor.ID()], st.numID[testCase.descendent.ID()]) + } +} + +func TestFindDeepestCommonDescendants(t *testing.T) { + branchManager := New(mapdb.NewMapDB()) + + st := createSampleTree(branchManager) + defer st.Release() + + { + deepestCommonDescendants, err := branchManager.findDeepestCommonDescendants(MasterBranchID, st.branches[2].ID()) + assert.NoError(t, err) + + expectedDeepestCommonDescendants := map[BranchID]struct{}{ + st.branches[2].ID(): {}, + } + actualDeepestCommonDescendants := map[BranchID]struct{}{} + deepestCommonDescendants.Consume(func(branch *Branch) { + actualDeepestCommonDescendants[branch.ID()] = struct{}{} + }) + assert.Equal(t, expectedDeepestCommonDescendants, actualDeepestCommonDescendants) + } + + { + deepestCommonDescendants, err := branchManager.findDeepestCommonDescendants(st.branches[2].ID(), st.branches[3].ID()) + assert.NoError(t, err) + + expectedDeepestCommonDescendants := map[BranchID]struct{}{ + st.branches[2].ID(): {}, st.branches[3].ID(): {}, + } + actualDeepestCommonDescendants := map[BranchID]struct{}{} + deepestCommonDescendants.Consume(func(branch *Branch) { + actualDeepestCommonDescendants[branch.ID()] = struct{}{} + }) + assert.Equal(t, expectedDeepestCommonDescendants, actualDeepestCommonDescendants) + } + + { + deepestCommonDescendants, err := branchManager.findDeepestCommonDescendants(st.branches[2].ID(), st.branches[4].ID(), st.branches[5].ID()) + assert.NoError(t, err) + + expectedDeepestCommonDescendants := map[BranchID]struct{}{ + st.branches[4].ID(): {}, st.branches[5].ID(): {}, + } + actualDeepestCommonDescendants := map[BranchID]struct{}{} + deepestCommonDescendants.Consume(func(branch *Branch) { + actualDeepestCommonDescendants[branch.ID()] = struct{}{} + }) + assert.Equal(t, expectedDeepestCommonDescendants, actualDeepestCommonDescendants) + } + + // breaks: should only be aggr. branch 11 which consists out of branch 6 and 8 + { + deepestCommonDescendants, err := branchManager.findDeepestCommonDescendants( + st.branches[6].ID(), st.branches[8].ID(), st.branches[11].ID()) + assert.NoError(t, err) + + expectedDeepestCommonDescendants := map[BranchID]struct{}{ + st.branches[11].ID(): {}, + } + actualDeepestCommonDescendants := map[BranchID]struct{}{} + deepestCommonDescendants.Consume(func(branch *Branch) { + actualDeepestCommonDescendants[branch.ID()] = struct{}{} + }) + assert.Equal(t, expectedDeepestCommonDescendants, actualDeepestCommonDescendants) + } + + // breaks: thinks that branch 13 is one of the deepest common descendants + { + deepestCommonDescendants, err := branchManager.findDeepestCommonDescendants( + st.branches[6].ID(), st.branches[8].ID(), st.branches[10].ID(), st.branches[11].ID(), + st.branches[12].ID(), st.branches[15].ID(), st.branches[14].ID(), st.branches[13].ID(), + st.branches[16].ID()) + assert.NoError(t, err) + + expectedDeepestCommonDescendants := map[BranchID]struct{}{ + st.branches[12].ID(): {}, st.branches[15].ID(): {}, st.branches[16].ID(): {}, + } + actualDeepestCommonDescendants := map[BranchID]struct{}{} + deepestCommonDescendants.Consume(func(branch *Branch) { + actualDeepestCommonDescendants[branch.ID()] = struct{}{} + }) + assert.Equal(t, expectedDeepestCommonDescendants, actualDeepestCommonDescendants) + } +} + +func TestCollectClosestConflictAncestors(t *testing.T) { + branchManager := New(mapdb.NewMapDB()) + + st := createSampleTree(branchManager) + defer st.Release() + + { + aggregatedBranchConflictParents := make(CachedBranches) + err := branchManager.collectClosestConflictAncestors(st.branches[10], aggregatedBranchConflictParents) + assert.NoError(t, err) + + expectedClosestConflictAncestors := map[BranchID]struct{}{ + st.branches[3].ID(): {}, st.branches[6].ID(): {}, + } + actualClosestConflictAncestors := map[BranchID]struct{}{} + aggregatedBranchConflictParents.Consume(func(branch *Branch) { + actualClosestConflictAncestors[branch.ID()] = struct{}{} + }) + assert.Equal(t, expectedClosestConflictAncestors, actualClosestConflictAncestors) + } + + { + aggregatedBranchConflictParents := make(CachedBranches) + err := branchManager.collectClosestConflictAncestors(st.branches[13], aggregatedBranchConflictParents) + assert.NoError(t, err) + + expectedClosestConflictAncestors := map[BranchID]struct{}{ + st.branches[3].ID(): {}, st.branches[6].ID(): {}, + } + actualClosestConflictAncestors := map[BranchID]struct{}{} + aggregatedBranchConflictParents.Consume(func(branch *Branch) { + actualClosestConflictAncestors[branch.ID()] = struct{}{} + }) + assert.Equal(t, expectedClosestConflictAncestors, actualClosestConflictAncestors) + } + + { + aggregatedBranchConflictParents := make(CachedBranches) + err := branchManager.collectClosestConflictAncestors(st.branches[14], aggregatedBranchConflictParents) + assert.NoError(t, err) + + expectedClosestConflictAncestors := map[BranchID]struct{}{ + st.branches[8].ID(): {}, st.branches[6].ID(): {}, + } + actualClosestConflictAncestors := map[BranchID]struct{}{} + aggregatedBranchConflictParents.Consume(func(branch *Branch) { + actualClosestConflictAncestors[branch.ID()] = struct{}{} + }) + assert.Equal(t, expectedClosestConflictAncestors, actualClosestConflictAncestors) + } + + { + aggregatedBranchConflictParents := make(CachedBranches) + err := branchManager.collectClosestConflictAncestors(st.branches[16], aggregatedBranchConflictParents) + assert.NoError(t, err) + + expectedClosestConflictAncestors := map[BranchID]struct{}{ + st.branches[13].ID(): {}, st.branches[14].ID(): {}, + } + actualClosestConflictAncestors := map[BranchID]struct{}{} + aggregatedBranchConflictParents.Consume(func(branch *Branch) { + actualClosestConflictAncestors[branch.ID()] = struct{}{} + }) + assert.Equal(t, expectedClosestConflictAncestors, actualClosestConflictAncestors) + } + + { + // lets check whether an aggregated branch out of branch 2 and aggr. branch 11 + // resolves to the same ID as when the actual parents (6 and 8) of aggr. branch 11 + // are used in conjunction with branch 2. + parentsBranches := CachedBranches{ + st.branches[2].ID(): st.cachedBranches[2].Retain(), + st.branches[6].ID(): st.cachedBranches[6].Retain(), + st.branches[8].ID(): st.cachedBranches[8].Retain(), + } + expectedAggrBranchID := branchManager.generateAggregatedBranchID(parentsBranches) + + cachedAggrBranch17, err := branchManager.AggregateBranches(st.branches[2].ID(), st.branches[11].ID()) + assert.NoError(t, err) + assert.Equal(t, expectedAggrBranchID, cachedAggrBranch17.Unwrap().ID()) + cachedAggrBranch17.Release() + } +} + +func TestBranchManager_ConflictMembers(t *testing.T) { + branchManager := New(mapdb.NewMapDB()) + + cachedBranch2, _ := branchManager.Fork(BranchID{2}, []BranchID{MasterBranchID}, []ConflictID{{0}}) + defer cachedBranch2.Release() + branch2 := cachedBranch2.Unwrap() + + cachedBranch3, _ := branchManager.Fork(BranchID{3}, []BranchID{MasterBranchID}, []ConflictID{{0}}) + defer cachedBranch3.Release() + branch3 := cachedBranch3.Unwrap() + + // assert conflict members + expectedConflictMembers := map[BranchID]struct{}{ + branch2.ID(): {}, branch3.ID(): {}, + } + actualConflictMembers := map[BranchID]struct{}{} + branchManager.ConflictMembers(ConflictID{0}).Consume(func(conflictMember *ConflictMember) { + actualConflictMembers[conflictMember.BranchID()] = struct{}{} + }) + assert.Equal(t, expectedConflictMembers, actualConflictMembers) + + // add branch 4 + cachedBranch4, _ := branchManager.Fork(BranchID{4}, []BranchID{MasterBranchID}, []ConflictID{{0}}) + defer cachedBranch4.Release() + branch4 := cachedBranch4.Unwrap() + + // branch 4 should now also be part of the conflict set + expectedConflictMembers = map[BranchID]struct{}{ + branch2.ID(): {}, branch3.ID(): {}, branch4.ID(): {}, + } + actualConflictMembers = map[BranchID]struct{}{} + branchManager.ConflictMembers(ConflictID{0}).Consume(func(conflictMember *ConflictMember) { + actualConflictMembers[conflictMember.BranchID()] = struct{}{} + }) + assert.Equal(t, expectedConflictMembers, actualConflictMembers) +} + +// ./img/testelevation.png +func TestBranchManager_ElevateConflictBranch(t *testing.T) { branchManager := New(mapdb.NewMapDB()) - cachedBranch1, _ := branchManager.Fork(BranchID{2}, []BranchID{MasterBranchID}, []ConflictID{transaction.OutputID{4}}) - defer cachedBranch1.Release() - _ = cachedBranch1.Unwrap() + cachedBranch2, _ := branchManager.Fork(BranchID{2}, []BranchID{MasterBranchID}, []ConflictID{{0}}) + defer cachedBranch2.Release() + branch2 := cachedBranch2.Unwrap() + + cachedBranch3, _ := branchManager.Fork(BranchID{3}, []BranchID{MasterBranchID}, []ConflictID{{0}}) + defer cachedBranch3.Release() + branch3 := cachedBranch3.Unwrap() + + cachedBranch4, _ := branchManager.Fork(BranchID{4}, []BranchID{branch2.ID()}, []ConflictID{{1}}) + defer cachedBranch4.Release() + branch4 := cachedBranch4.Unwrap() + + cachedBranch5, _ := branchManager.Fork(BranchID{5}, []BranchID{branch2.ID()}, []ConflictID{{1}}) + defer cachedBranch5.Release() + branch5 := cachedBranch5.Unwrap() + + cachedBranch6, _ := branchManager.Fork(BranchID{6}, []BranchID{branch4.ID()}, []ConflictID{{2}}) + defer cachedBranch6.Release() + branch6 := cachedBranch6.Unwrap() + + cachedBranch7, _ := branchManager.Fork(BranchID{7}, []BranchID{branch4.ID()}, []ConflictID{{2}}) + defer cachedBranch7.Release() + branch7 := cachedBranch7.Unwrap() + + // lets assume the conflict between 2 and 3 is resolved and therefore we elevate + // branches 4 and 5 to the master branch + isConflictBranch, modified, err := branchManager.ElevateConflictBranch(branch4.ID(), MasterBranchID) + assert.NoError(t, err, "branch 4 should have been elevated to the master branch") + assert.True(t, isConflictBranch, "branch 4 should have been a conflict branch") + assert.True(t, modified, "branch 4 should have been modified") + assert.Equal(t, MasterBranchID, branch4.ParentBranches()[0], "branch 4's parent should now be the master branch") + + isConflictBranch, modified, err = branchManager.ElevateConflictBranch(branch5.ID(), MasterBranchID) + assert.NoError(t, err, "branch 5 should have been elevated to the master branch") + assert.True(t, isConflictBranch, "branch 5 should have been a conflict branch") + assert.True(t, modified, "branch 5 should have been modified") + assert.Equal(t, MasterBranchID, branch5.ParentBranches()[0], "branch 5's parent should now be the master branch") + + // check whether the child branches are what we expect them to be of the master branch + expectedMasterChildBranches := map[BranchID]struct{}{ + branch2.ID(): {}, branch3.ID(): {}, + branch4.ID(): {}, branch5.ID(): {}, + } + actualMasterChildBranches := map[BranchID]struct{}{} + branchManager.ChildBranches(MasterBranchID).Consume(func(childBranch *ChildBranch) { + actualMasterChildBranches[childBranch.ChildID()] = struct{}{} + }) + assert.Equal(t, expectedMasterChildBranches, actualMasterChildBranches) - cachedBranch2, _ := branchManager.Fork(BranchID{3}, []BranchID{MasterBranchID}, []ConflictID{transaction.OutputID{4}}) + // check that 4 and 5 no longer are children of branch 2 + expectedBranch2ChildBranches := map[BranchID]struct{}{} + actualBranch2ChildBranches := map[BranchID]struct{}{} + branchManager.ChildBranches(branch2.ID()).Consume(func(childBranch *ChildBranch) { + actualBranch2ChildBranches[childBranch.ChildID()] = struct{}{} + }) + assert.Equal(t, expectedBranch2ChildBranches, actualBranch2ChildBranches) + + // lets assume the conflict between 4 and 5 is resolved and therefore we elevate + // branches 6 and 7 to the master branch + isConflictBranch, modified, err = branchManager.ElevateConflictBranch(branch6.ID(), MasterBranchID) + assert.NoError(t, err, "branch 6 should have been elevated to the master branch") + assert.True(t, isConflictBranch, "branch 6 should have been a conflict branch") + assert.True(t, modified, "branch 6 should have been modified") + assert.Equal(t, MasterBranchID, branch6.ParentBranches()[0], "branch 6's parent should now be the master branch") + + isConflictBranch, modified, err = branchManager.ElevateConflictBranch(branch7.ID(), MasterBranchID) + assert.NoError(t, err, "branch 7 should have been elevated to the master branch") + assert.True(t, isConflictBranch, "branch 7 should have been a conflict branch") + assert.True(t, modified, "branch 7 should have been modified") + assert.Equal(t, MasterBranchID, branch7.ParentBranches()[0], "branch 7's parent should now be the master branch") + + // check whether the child branches are what we expect them to be of the master branch + expectedMasterChildBranches = map[BranchID]struct{}{ + branch2.ID(): {}, branch3.ID(): {}, + branch4.ID(): {}, branch5.ID(): {}, + branch6.ID(): {}, branch7.ID(): {}, + } + actualMasterChildBranches = map[BranchID]struct{}{} + branchManager.ChildBranches(MasterBranchID).Consume(func(childBranch *ChildBranch) { + actualMasterChildBranches[childBranch.ChildID()] = struct{}{} + }) + assert.Equal(t, expectedMasterChildBranches, actualMasterChildBranches) + + // check that 6 and 7 no longer are children of branch 4 + expectedBranch4ChildBranches := map[BranchID]struct{}{} + actualBranch4ChildBranches := map[BranchID]struct{}{} + branchManager.ChildBranches(branch4.ID()).Consume(func(childBranch *ChildBranch) { + actualBranch4ChildBranches[childBranch.ChildID()] = struct{}{} + }) + assert.Equal(t, expectedBranch4ChildBranches, actualBranch4ChildBranches) + + // TODO: branches are never deleted? +} + +// ./img/testconflictdetection.png +func TestBranchManager_BranchesConflicting(t *testing.T) { + branchManager := New(mapdb.NewMapDB()) + + cachedBranch2, _ := branchManager.Fork(BranchID{2}, []BranchID{MasterBranchID}, []ConflictID{{0}}) defer cachedBranch2.Release() branch2 := cachedBranch2.Unwrap() - fmt.Println(branchManager.BranchesConflicting(MasterBranchID, branch2.ID())) + cachedBranch3, _ := branchManager.Fork(BranchID{3}, []BranchID{MasterBranchID}, []ConflictID{{0}}) + defer cachedBranch3.Release() + branch3 := cachedBranch3.Unwrap() + + { + conflicting, err := branchManager.BranchesConflicting(MasterBranchID, branch2.ID()) + assert.NoError(t, err) + assert.False(t, conflicting, "branch 2 should not be conflicting with master branch") + + conflicting, err = branchManager.BranchesConflicting(MasterBranchID, branch2.ID()) + assert.NoError(t, err) + assert.False(t, conflicting, "branch 3 should not be conflicting with master branch") + + conflicting, err = branchManager.BranchesConflicting(branch2.ID(), branch3.ID()) + assert.NoError(t, err) + assert.True(t, conflicting, "branch 2 & 3 should be conflicting with each other") + } + + // spawn of branch 4 and 5 from branch 2 + cachedBranch4, _ := branchManager.Fork(BranchID{4}, []BranchID{branch2.ID()}, []ConflictID{{1}}) + defer cachedBranch4.Release() + branch4 := cachedBranch4.Unwrap() + + cachedBranch5, _ := branchManager.Fork(BranchID{5}, []BranchID{branch2.ID()}, []ConflictID{{1}}) + defer cachedBranch5.Release() + branch5 := cachedBranch5.Unwrap() + + { + conflicting, err := branchManager.BranchesConflicting(MasterBranchID, branch4.ID()) + assert.NoError(t, err) + assert.False(t, conflicting, "branch 4 should not be conflicting with master branch") + + conflicting, err = branchManager.BranchesConflicting(branch3.ID(), branch4.ID()) + assert.NoError(t, err) + assert.True(t, conflicting, "branch 3 & 4 should be conflicting with each other") + + conflicting, err = branchManager.BranchesConflicting(MasterBranchID, branch5.ID()) + assert.NoError(t, err) + assert.False(t, conflicting, "branch 5 should not be conflicting with master branch") + + conflicting, err = branchManager.BranchesConflicting(branch3.ID(), branch5.ID()) + assert.NoError(t, err) + assert.True(t, conflicting, "branch 3 & 5 should be conflicting with each other") + + // since both consume the same output + conflicting, err = branchManager.BranchesConflicting(branch4.ID(), branch5.ID()) + assert.NoError(t, err) + assert.True(t, conflicting, "branch 4 & 5 should be conflicting with each other") + } + + // branch 6, 7 are on the same level as 2 and 3 but are not part of that conflict set + cachedBranch6, _ := branchManager.Fork(BranchID{6}, []BranchID{MasterBranchID}, []ConflictID{{2}}) + defer cachedBranch6.Release() + branch6 := cachedBranch6.Unwrap() + + cachedBranch7, _ := branchManager.Fork(BranchID{7}, []BranchID{MasterBranchID}, []ConflictID{{2}}) + defer cachedBranch7.Release() + branch7 := cachedBranch7.Unwrap() + + { + conflicting, err := branchManager.BranchesConflicting(branch2.ID(), branch6.ID()) + assert.NoError(t, err) + assert.False(t, conflicting, "branch 6 should not be conflicting with branch 2") + + conflicting, err = branchManager.BranchesConflicting(branch3.ID(), branch6.ID()) + assert.NoError(t, err) + assert.False(t, conflicting, "branch 6 should not be conflicting with branch 3") + + conflicting, err = branchManager.BranchesConflicting(branch2.ID(), branch7.ID()) + assert.NoError(t, err) + assert.False(t, conflicting, "branch 7 should not be conflicting with branch 2") + + conflicting, err = branchManager.BranchesConflicting(branch3.ID(), branch7.ID()) + assert.NoError(t, err) + assert.False(t, conflicting, "branch 7 should not be conflicting with branch 3") + + conflicting, err = branchManager.BranchesConflicting(branch6.ID(), branch7.ID()) + assert.NoError(t, err) + assert.True(t, conflicting, "branch 6 & 7 should be conflicting with each other") + + conflicting, err = branchManager.BranchesConflicting(branch4.ID(), branch6.ID()) + assert.NoError(t, err) + assert.False(t, conflicting, "branch 6 should not be conflicting with branch 4") + + conflicting, err = branchManager.BranchesConflicting(branch5.ID(), branch6.ID()) + assert.NoError(t, err) + assert.False(t, conflicting, "branch 6 should not be conflicting with branch 5") + + conflicting, err = branchManager.BranchesConflicting(branch4.ID(), branch7.ID()) + assert.NoError(t, err) + assert.False(t, conflicting, "branch 7 should not be conflicting with branch 4") + + conflicting, err = branchManager.BranchesConflicting(branch5.ID(), branch7.ID()) + assert.NoError(t, err) + assert.False(t, conflicting, "branch 7 should not be conflicting with branch 5") + } + + // aggregated branch out of branch 4 (child of branch 2) and branch 6 + cachedAggrBranch8, aggrBranchErr := branchManager.AggregateBranches(branch4.ID(), branch6.ID()) + assert.NoError(t, aggrBranchErr) + defer cachedAggrBranch8.Release() + aggrBranch8 := cachedAggrBranch8.Unwrap() + + { + conflicting, err := branchManager.BranchesConflicting(aggrBranch8.ID(), MasterBranchID) + assert.NoError(t, err) + assert.False(t, conflicting, "aggr. branch 8 should not be conflicting with master branch") + + conflicting, err = branchManager.BranchesConflicting(aggrBranch8.ID(), branch2.ID()) + assert.NoError(t, err) + assert.False(t, conflicting, "aggr. branch 8 should not be conflicting with branch 2") + + // conflicting since branch 2 and branch 3 are + conflicting, err = branchManager.BranchesConflicting(aggrBranch8.ID(), branch3.ID()) + assert.NoError(t, err) + assert.True(t, conflicting, "aggr. branch 8 & branch 3 should be conflicting with each other") + + // conflicting since branch 4 and branch 5 are + conflicting, err = branchManager.BranchesConflicting(aggrBranch8.ID(), branch5.ID()) + assert.NoError(t, err) + assert.True(t, conflicting, "aggr. branch 8 & branch 5 should be conflicting with each other") + + // conflicting since branch 6 and branch 7 are + conflicting, err = branchManager.BranchesConflicting(aggrBranch8.ID(), branch7.ID()) + assert.NoError(t, err) + assert.True(t, conflicting, "aggr. branch 8 & branch 7 should be conflicting with each other") + } + + // aggregated branch out of aggr. branch 8 and branch 7: + // should fail since branch 6 & 7 are conflicting + _, aggrBranchErr = branchManager.AggregateBranches(aggrBranch8.ID(), branch7.ID()) + assert.Error(t, aggrBranchErr, "can't aggregate branches aggr. branch 8 & conflict branch 7") + + // aggregated branch out of branch 5 (child of branch 2) and branch 7 + cachedAggrBranch9, aggrBranchErr := branchManager.AggregateBranches(branch5.ID(), branch7.ID()) + assert.NoError(t, aggrBranchErr) + defer cachedAggrBranch9.Release() + aggrBranch9 := cachedAggrBranch9.Unwrap() + + assert.NotEqual(t, aggrBranch8.ID().String(), aggrBranch9.ID().String(), "aggr. branches 8 & 9 should have different IDs") + + { + conflicting, err := branchManager.BranchesConflicting(aggrBranch9.ID(), MasterBranchID) + assert.NoError(t, err) + assert.False(t, conflicting, "aggr. branch 9 should not be conflicting with master branch") + + // aggr. branch 8 and 9 should be conflicting, since 4 & 5 and 6 & 7 are + conflicting, err = branchManager.BranchesConflicting(aggrBranch8.ID(), aggrBranch9.ID()) + assert.NoError(t, err) + assert.True(t, conflicting, "aggr. branch 8 & branch 9 should be conflicting with each other") + + // conflicting since branch 3 & 2 are + conflicting, err = branchManager.BranchesConflicting(branch3.ID(), aggrBranch9.ID()) + assert.NoError(t, err) + assert.True(t, conflicting, "aggr. branch 9 & branch 3 should be conflicting with each other") + } + + // aggregated branch out of branch 3 and branch 6 + cachedAggrBranch10, aggrBranchErr := branchManager.AggregateBranches(branch3.ID(), branch6.ID()) + assert.NoError(t, aggrBranchErr) + defer cachedAggrBranch10.Release() + aggrBranch10 := cachedAggrBranch10.Unwrap() + + { + conflicting, err := branchManager.BranchesConflicting(aggrBranch10.ID(), MasterBranchID) + assert.NoError(t, err) + assert.False(t, conflicting, "aggr. branch 10 should not be conflicting with master branch") + + // aggr. branch 8 and 10 should be conflicting, since 2 & 3 are + conflicting, err = branchManager.BranchesConflicting(aggrBranch8.ID(), aggrBranch10.ID()) + assert.NoError(t, err) + assert.True(t, conflicting, "aggr. branch 8 & branch 10 should be conflicting with each other") + + // aggr. branch 9 and 10 should be conflicting, since 2 & 3 and 6 & 7 are + conflicting, err = branchManager.BranchesConflicting(aggrBranch9.ID(), aggrBranch10.ID()) + assert.NoError(t, err) + assert.True(t, conflicting, "aggr. branch 9 & branch 10 should be conflicting with each other") + } + + // branch 11, 12 are on the same level as 2 & 3 and 6 & 7 but are not part of either conflict set + cachedBranch11, _ := branchManager.Fork(BranchID{11}, []BranchID{MasterBranchID}, []ConflictID{{3}}) + defer cachedBranch11.Release() + branch11 := cachedBranch11.Unwrap() + + cachedBranch12, _ := branchManager.Fork(BranchID{12}, []BranchID{MasterBranchID}, []ConflictID{{3}}) + defer cachedBranch12.Release() + branch12 := cachedBranch12.Unwrap() + + { + conflicting, err := branchManager.BranchesConflicting(MasterBranchID, branch11.ID()) + assert.NoError(t, err) + assert.False(t, conflicting, "branch 11 should not be conflicting with master branch") + + conflicting, err = branchManager.BranchesConflicting(MasterBranchID, branch12.ID()) + assert.NoError(t, err) + assert.False(t, conflicting, "branch 12 should not be conflicting with master branch") + + conflicting, err = branchManager.BranchesConflicting(branch11.ID(), branch12.ID()) + assert.NoError(t, err) + assert.True(t, conflicting, "branch 11 & 12 should be conflicting with each other") + } + + // aggr. branch 13 out of branch 6 and 11 + cachedAggrBranch13, aggrBranchErr := branchManager.AggregateBranches(branch6.ID(), branch11.ID()) + assert.NoError(t, aggrBranchErr) + defer cachedAggrBranch13.Release() + aggrBranch13 := cachedAggrBranch13.Unwrap() + + { + conflicting, err := branchManager.BranchesConflicting(aggrBranch13.ID(), aggrBranch9.ID()) + assert.NoError(t, err) + assert.True(t, conflicting, "aggr. branch 9 & 13 should be conflicting with each other") + + conflicting, err = branchManager.BranchesConflicting(aggrBranch13.ID(), aggrBranch8.ID()) + assert.NoError(t, err) + assert.False(t, conflicting, "aggr. branch 8 & 13 should not be conflicting with each other") + + conflicting, err = branchManager.BranchesConflicting(aggrBranch13.ID(), aggrBranch10.ID()) + assert.NoError(t, err) + assert.False(t, conflicting, "aggr. branch 10 & 13 should not be conflicting with each other") + } + + // aggr. branch 14 out of aggr. branch 10 and 13 + cachedAggrBranch14, aggrBranchErr := branchManager.AggregateBranches(aggrBranch10.ID(), aggrBranch13.ID()) + assert.NoError(t, aggrBranchErr) + defer cachedAggrBranch14.Release() + aggrBranch14 := cachedAggrBranch14.Unwrap() + + { + // aggr. branch 9 has parent branch 7 which conflicts with ancestor branch 6 of aggr. branch 14 + conflicting, err := branchManager.BranchesConflicting(aggrBranch14.ID(), aggrBranch9.ID()) + assert.NoError(t, err) + assert.True(t, conflicting, "aggr. branch 14 & 9 should be conflicting with each other") + + // aggr. branch has ancestor branch 2 which conflicts with ancestor branch 3 of aggr. branch 14 + conflicting, err = branchManager.BranchesConflicting(aggrBranch14.ID(), aggrBranch8.ID()) + assert.NoError(t, err) + assert.True(t, conflicting, "aggr. branch 14 & 8 should be conflicting with each other") + } + + // aggr. branch 15 out of branch 2, 7 and 12 + cachedAggrBranch15, aggrBranchErr := branchManager.AggregateBranches(branch2.ID(), branch7.ID(), branch12.ID()) + assert.NoError(t, aggrBranchErr) + defer cachedAggrBranch15.Release() + aggrBranch15 := cachedAggrBranch15.Unwrap() + + { + // aggr. branch 13 has parent branches 11 & 6 which conflicts which conflicts with ancestor branches 12 & 7 of aggr. branch 15 + conflicting, err := branchManager.BranchesConflicting(aggrBranch15.ID(), aggrBranch13.ID()) + assert.NoError(t, err) + assert.True(t, conflicting, "aggr. branch 15 & 13 should be conflicting with each other") + + // aggr. branch 10 has parent branches 3 & 6 which conflicts with ancestor branches 2 & 7 of aggr. branch 15 + conflicting, err = branchManager.BranchesConflicting(aggrBranch15.ID(), aggrBranch10.ID()) + assert.NoError(t, err) + assert.True(t, conflicting, "aggr. branch 15 & 10 should be conflicting with each other") + + // aggr. branch 8 has parent branch 6 which conflicts with ancestor branch 7 of aggr. branch 15 + conflicting, err = branchManager.BranchesConflicting(aggrBranch15.ID(), aggrBranch8.ID()) + assert.NoError(t, err) + assert.True(t, conflicting, "aggr. branch 15 & 8 should be conflicting with each other") + } + + // aggr. branch 16 out of aggr. branches 15 and 9 + cachedAggrBranch16, aggrBranchErr := branchManager.AggregateBranches(aggrBranch15.ID(), aggrBranch9.ID()) + assert.NoError(t, aggrBranchErr) + defer cachedAggrBranch16.Release() + aggrBranch16 := cachedAggrBranch16.Unwrap() + + { + // sanity check + conflicting, err := branchManager.BranchesConflicting(aggrBranch16.ID(), aggrBranch9.ID()) + assert.NoError(t, err) + assert.False(t, conflicting, "aggr. branch 16 & 9 should not be conflicting with each other") + + // sanity check + conflicting, err = branchManager.BranchesConflicting(aggrBranch16.ID(), branch7.ID()) + assert.NoError(t, err) + assert.False(t, conflicting, "aggr. branch 16 & 9 should not be conflicting with each other") + + conflicting, err = branchManager.BranchesConflicting(aggrBranch16.ID(), aggrBranch13.ID()) + assert.NoError(t, err) + assert.True(t, conflicting, "aggr. branch 16 & 13 should be conflicting with each other") + + conflicting, err = branchManager.BranchesConflicting(aggrBranch16.ID(), aggrBranch14.ID()) + assert.NoError(t, err) + assert.True(t, conflicting, "aggr. branch 16 & 14 should be conflicting with each other") + + conflicting, err = branchManager.BranchesConflicting(aggrBranch16.ID(), aggrBranch8.ID()) + assert.NoError(t, err) + assert.True(t, conflicting, "aggr. branch 16 & 8 should be conflicting with each other") + } + +} + +func TestBranchManager_SetBranchPreferred(t *testing.T) { + branchManager := New(mapdb.NewMapDB()) + + cachedBranch2, _ := branchManager.Fork(BranchID{2}, []BranchID{MasterBranchID}, []ConflictID{{0}}) + defer cachedBranch2.Release() + branch2 := cachedBranch2.Unwrap() + + cachedBranch3, _ := branchManager.Fork(BranchID{3}, []BranchID{MasterBranchID}, []ConflictID{{0}}) + defer cachedBranch3.Release() + branch3 := cachedBranch3.Unwrap() + + assert.False(t, branch2.Preferred(), "branch 2 should not be preferred") + assert.False(t, branch2.Liked(), "branch 2 should not be liked") + assert.False(t, branch3.Preferred(), "branch 3 should not be preferred") + assert.False(t, branch3.Liked(), "branch 3 should not be liked") + + cachedBranch4, _ := branchManager.Fork(BranchID{4}, []BranchID{branch2.ID()}, []ConflictID{{1}}) + defer cachedBranch4.Release() + branch4 := cachedBranch4.Unwrap() + + cachedBranch5, _ := branchManager.Fork(BranchID{5}, []BranchID{branch2.ID()}, []ConflictID{{1}}) + defer cachedBranch5.Release() + branch5 := cachedBranch5.Unwrap() + + // lets assume branch 4 is preferred since its underlying transaction was longer + // solid than the avg. network delay before the conflicting transaction which created + // the conflict set was received + modified, err := branchManager.SetBranchPreferred(branch4.ID(), true) + assert.NoError(t, err) + assert.True(t, modified) + + assert.True(t, branch4.Preferred(), "branch 4 should be preferred") + // is not liked because its parents aren't liked, respectively branch 2 + assert.False(t, branch4.Liked(), "branch 4 should not be liked") + assert.False(t, branch5.Preferred(), "branch 5 should not be preferred") + assert.False(t, branch5.Liked(), "branch 5 should not be liked") + + // now branch 2 becomes preferred via FPC, this causes branch 2 to be liked (since + // the master branch is liked) and its liked state propagates to branch 4 (but not branch 5) + modified, err = branchManager.SetBranchPreferred(branch2.ID(), true) + assert.NoError(t, err) + assert.True(t, modified) + + assert.True(t, branch2.Liked(), "branch 2 should be liked") + assert.True(t, branch2.Preferred(), "branch 2 should be preferred") + assert.True(t, branch4.Liked(), "branch 4 should be liked") + assert.True(t, branch4.Preferred(), "branch 4 should still be preferred") + assert.False(t, branch5.Liked(), "branch 5 should not be liked") + assert.False(t, branch5.Preferred(), "branch 5 should not be preferred") + + // now the network decides that branch 5 is preferred (via FPC), thus branch 4 should lose its + // preferred and liked state and branch 5 should instead become preferred and liked + modified, err = branchManager.SetBranchPreferred(branch5.ID(), true) + assert.NoError(t, err) + assert.True(t, modified) + + // sanity check for branch 2 state + assert.True(t, branch2.Liked(), "branch 2 should be liked") + assert.True(t, branch2.Preferred(), "branch 2 should be preferred") + + // check that branch 4 is disliked and not preferred + assert.False(t, branch4.Liked(), "branch 4 should be disliked") + assert.False(t, branch4.Preferred(), "branch 4 should not be preferred") + assert.True(t, branch5.Liked(), "branch 5 should be liked") + assert.True(t, branch5.Preferred(), "branch 5 should be preferred") +} + +func TestBranchManager_SetBranchPreferred2(t *testing.T) { + branchManager := New(mapdb.NewMapDB()) + + cachedBranch2, _ := branchManager.Fork(BranchID{2}, []BranchID{MasterBranchID}, []ConflictID{{0}}) + defer cachedBranch2.Release() + branch2 := cachedBranch2.Unwrap() + + cachedBranch3, _ := branchManager.Fork(BranchID{3}, []BranchID{MasterBranchID}, []ConflictID{{0}}) + defer cachedBranch3.Release() + branch3 := cachedBranch3.Unwrap() + + cachedBranch4, _ := branchManager.Fork(BranchID{4}, []BranchID{branch2.ID()}, []ConflictID{{1}}) + defer cachedBranch4.Release() + branch4 := cachedBranch4.Unwrap() + + cachedBranch5, _ := branchManager.Fork(BranchID{5}, []BranchID{branch2.ID()}, []ConflictID{{1}}) + defer cachedBranch5.Release() + branch5 := cachedBranch5.Unwrap() + + cachedBranch6, _ := branchManager.Fork(BranchID{6}, []BranchID{MasterBranchID}, []ConflictID{{2}}) + defer cachedBranch6.Release() + branch6 := cachedBranch6.Unwrap() + + cachedBranch7, _ := branchManager.Fork(BranchID{7}, []BranchID{MasterBranchID}, []ConflictID{{2}}) + defer cachedBranch7.Release() + branch7 := cachedBranch7.Unwrap() + + // assume branch 2 preferred since solid longer than avg. network delay + modified, err := branchManager.SetBranchPreferred(branch2.ID(), true) + assert.NoError(t, err) + assert.True(t, modified) + + // assume branch 6 preferred since solid longer than avg. network delay + modified, err = branchManager.SetBranchPreferred(branch6.ID(), true) + assert.NoError(t, err) + assert.True(t, modified) + + { + assert.True(t, branch2.Liked(), "branch 2 should be liked") + assert.True(t, branch2.Preferred(), "branch 2 should be preferred") + assert.False(t, branch3.Liked(), "branch 3 should not be liked") + assert.False(t, branch3.Preferred(), "branch 3 should not be preferred") + assert.False(t, branch4.Liked(), "branch 4 should not be liked") + assert.False(t, branch4.Preferred(), "branch 4 should not be preferred") + assert.False(t, branch5.Liked(), "branch 5 should not be liked") + assert.False(t, branch5.Preferred(), "branch 5 should not be preferred") + + assert.True(t, branch6.Liked(), "branch 6 should be liked") + assert.True(t, branch6.Preferred(), "branch 6 should be preferred") + assert.False(t, branch7.Liked(), "branch 7 should not be liked") + assert.False(t, branch7.Preferred(), "branch 7 should not be preferred") + } + + // throw some aggregated branches into the mix + cachedAggrBranch8, err := branchManager.AggregateBranches(branch4.ID(), branch6.ID()) + assert.NoError(t, err) + defer cachedAggrBranch8.Release() + aggrBranch8 := cachedAggrBranch8.Unwrap() + + // should not be preferred because only 6 is is preferred but not 4 + assert.False(t, aggrBranch8.Liked(), "aggr. branch 8 should not be liked") + assert.False(t, aggrBranch8.Preferred(), "aggr. branch 8 should not be preferred") + + cachedAggrBranch9, err := branchManager.AggregateBranches(branch5.ID(), branch7.ID()) + assert.NoError(t, err) + defer cachedAggrBranch9.Release() + aggrBranch9 := cachedAggrBranch9.Unwrap() + + // branch 5 and 7 are neither liked or preferred + assert.False(t, aggrBranch9.Liked(), "aggr. branch 9 should not be liked") + assert.False(t, aggrBranch9.Preferred(), "aggr. branch 9 should not be preferred") + + // should not be preferred because only 6 is is preferred but not 3 + cachedAggrBranch10, err := branchManager.AggregateBranches(branch3.ID(), branch6.ID()) + assert.NoError(t, err) + defer cachedAggrBranch10.Release() + aggrBranch10 := cachedAggrBranch10.Unwrap() + + assert.False(t, aggrBranch10.Liked(), "aggr. branch 10 should not be liked") + assert.False(t, aggrBranch10.Preferred(), "aggr. branch 10 should not be preferred") + + // spawn off conflict branch 11 and 12 + cachedBranch11, _ := branchManager.Fork(BranchID{11}, []BranchID{aggrBranch8.ID()}, []ConflictID{{3}}) + defer cachedBranch11.Release() + branch11 := cachedBranch11.Unwrap() + + assert.False(t, branch11.Liked(), "aggr. branch 11 should not be liked") + assert.False(t, branch11.Preferred(), "aggr. branch 11 should not be preferred") + + cachedBranch12, _ := branchManager.Fork(BranchID{12}, []BranchID{aggrBranch8.ID()}, []ConflictID{{3}}) + defer cachedBranch12.Release() + branch12 := cachedBranch12.Unwrap() + + assert.False(t, branch12.Liked(), "aggr. branch 12 should not be liked") + assert.False(t, branch12.Preferred(), "aggr. branch 12 should not be preferred") + + cachedAggrBranch13, err := branchManager.AggregateBranches(branch4.ID(), branch12.ID()) + assert.NoError(t, err) + defer cachedAggrBranch13.Release() + aggrBranch13 := cachedAggrBranch13.Unwrap() + + assert.False(t, aggrBranch13.Liked(), "aggr. branch 13 should not be liked") + assert.False(t, aggrBranch13.Preferred(), "aggr. branch 13 should not be preferred") + + // now lets assume FPC finalized on branch 2, 6 and 4 to be preferred. + // branches 2 and 6 are already preferred but 4 is newly preferred. Branch 4 therefore + // should also become liked, since branch 2 of which it spawns off is liked too. + + // simulate branch 3 being not preferred from FPC vote + modified, err = branchManager.SetBranchPreferred(branch3.ID(), false) + assert.NoError(t, err) + assert.False(t, modified) + // simulate branch 7 being not preferred from FPC vote + modified, err = branchManager.SetBranchPreferred(branch7.ID(), false) + assert.NoError(t, err) + assert.False(t, modified) + // simulate branch 4 being preferred by FPC vote + modified, err = branchManager.SetBranchPreferred(branch4.ID(), true) + assert.NoError(t, err) + assert.True(t, modified) + assert.True(t, branch4.Liked(), "branch 4 should be liked") + assert.True(t, branch4.Preferred(), "branch 4 should be preferred") + + // this should cause aggr. branch 8 to also be preferred and liked, since branch 6 and 4 + // of which it spawns off are. + assert.True(t, aggrBranch8.Liked(), "aggr. branch 8 should be liked") + assert.True(t, aggrBranch8.Preferred(), "aggr. branch 8 should be preferred") } diff --git a/dapps/valuetransfers/packages/branchmanager/imgs/sample_tree.png b/dapps/valuetransfers/packages/branchmanager/imgs/sample_tree.png new file mode 100644 index 0000000000000000000000000000000000000000..88218b1b8b7aa7b675e57c33c9e3f318a2022ffc Binary files /dev/null and b/dapps/valuetransfers/packages/branchmanager/imgs/sample_tree.png differ diff --git a/dapps/valuetransfers/packages/branchmanager/imgs/testconflictdetection.PNG b/dapps/valuetransfers/packages/branchmanager/imgs/testconflictdetection.PNG new file mode 100644 index 0000000000000000000000000000000000000000..9da013e5b4caa42306c2c856386f7ee4a06bc167 Binary files /dev/null and b/dapps/valuetransfers/packages/branchmanager/imgs/testconflictdetection.PNG differ diff --git a/dapps/valuetransfers/packages/branchmanager/imgs/testelevation.PNG b/dapps/valuetransfers/packages/branchmanager/imgs/testelevation.PNG new file mode 100644 index 0000000000000000000000000000000000000000..b54ecfc5098dfcee316ff693b5cfa8bebc834d56 Binary files /dev/null and b/dapps/valuetransfers/packages/branchmanager/imgs/testelevation.PNG differ diff --git a/dapps/valuetransfers/packages/branchmanager/imgs/testlikepropagation.PNG b/dapps/valuetransfers/packages/branchmanager/imgs/testlikepropagation.PNG new file mode 100644 index 0000000000000000000000000000000000000000..c8aef98a87366018d16778561828080256c7b58d Binary files /dev/null and b/dapps/valuetransfers/packages/branchmanager/imgs/testlikepropagation.PNG differ