mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-10-14 00:59:33 +00:00

* [NOD-540] Implement reachability (#545) * [NOD-540] Begin implementing reachability. * [NOD-540] Finish implementing reachability. * [NOD-540] Implement TestIsFutureBlock. * [NOD-540] Implement TestInsertFutureBlock. * [NOD-540] Add comments. * [NOD-540] Add comment for interval in blockNode. * [NOD-540] Updated comments over insertFutureBlock and isFutureBlock. * [NOD-540] Implement interval splitting methods. * [NOD-540] Begin implementing tree manipulation in blockNode. * [NOD-540] Implement countSubtreesUp. * [NOD-540] Add a comment explaining an impossible condition. * [NOD-540] Implement applyIntervalDown. * [NOD-540] Moved the reachability tree stuff into reachability.go. * [NOD-540] Add some comments. * [NOD-540] Add more comments, implement isInPast. * [NOD-540] Fix comments. * [NOD-540] Implement TestSplitFraction. * [NOD-540] Implement TestSplitExact. * [NOD-540] Implement TestSplit. * [NOD-540] Add comments to structs. * [NOD-540] Implement TestAddTreeChild. * [NOD-540] Fix a comment. * [NOD-540] Rename isInPast to isAncestorOf. * [NOD-540] Rename futureBlocks to futureCoveringSet. * [NOD-540] Rename isFutureBlock to isInFuture. * [NOD-540] move reachabilityInterval to the top of reachability.go. * [NOD-540] Change "s.t." to "such that" in a comment. * [NOD-540] Fix indentation. * [NOD-540] Fix a potential bug involving float inaccuracy. * [NOD-540] Wrote a more descriptive error message. * [NOD-540] Fix error messsage. * [NOD-540] Fix the recursive countSubtreesUp. * [NOD-540] Rename countSubtreesUp to countSubtrees and applyIntervalDown to propagateInterval. * [NOD-540] Implement updating reachability for a valid new block. * [NOD-540] Implement a disk storage for reachability data. * [NOD-540] Fix not all tree nodes being written to the database. * [NOD-540] Implement serialization for reachabilityData. * [NOD-540] Implement some deserialization for reachabilityData. * [NOD-540] Implement restoring the reachabilityStore on node restart. * [NOD-540] Made interval and remainingInterval pointers. * [NOD-540] Rename setTreeInterval to setInterval. * [NOD-540] Rename reindexTreeIntervals to reindexIntervals and fixed the comment above it. * [NOD-540] Expand the comment above reindexIntervals. * [NOD-540] Fix comment above countSubtrees. * [NOD-540] Fix comment above countSubtrees some more. * [NOD-540] Fix comment above split. * [NOD-540] Fix comment above isAncestorOf. * [NOD-540] Fix comment above reachabilityTreeNode. * [NOD-540] Fix weird condition in addTreeChild. * [NOD-540] Rename addTreeChild to addChild. * [NOD-540] Fix weird condition in splitFraction. * [NOD-540] Reverse the lines in reachabilityTreeNode.String(). * [NOD-540] Renamed f to fraction and x to size. * [NOD-540] Fix comment above bisect. * [NOD-540] Implement rtn.isAncestorOf(). * [NOD-540] Use treeNode isAncestorOf instead of treeInterval isAncestorOf. * [NOD-540] Use newReachabilityInterval instead of struct initialization. * [NOD-540] Make reachabilityTreeNode.String() use strings.Join. * [NOD-540] Use sync.RWMutex instead of locks.PriorityMutex. * [NOD-540] Rename thisTreeNode to newTreeNode. * [NOD-540] Rename setTreeNode to addTreeNode. * [NOD-540] Extracted selectedParentAnticone to a separate function. * [NOD-540] Rename node to this. * [NOD-540] Move updateReachability and isAncestorOf from dag.go to reachability.go. * [NOD-540] Add whitespace after multiline function signatures in reachability.go. * [NOD-540] Make splitFraction return an error on empty interval. * [NOD-540] Add a comment about rounding to splitFraction. * [NOD-540] Replace sneaky tabs with spaces. * [NOD-540] Rename split to splitExponential. * [NOD-540] Extract exponentialFractions to a separate function. * [NOD-540] Rename bisect to findIndex. * [NOD-540] Add call to reachabilityStore.clearDirtyEntries at the end of saveChangesFromBlock. * [NOD-540] Explain the dirty hack in reachabilityStore.init(). * [NOD-540] Split the function signature for deserializeReachabilityData to two lines. * [NOD-540] Add a comment about float precision loss to exponentialFractions. * [NOD-540] Corrected a comment about float precision loss to exponentialFractions. * [NOD-540] Fixed a comment about float precision loss to exponentialFractions some more. * [NOD-540] Added further comments above futureCoveringBlockSet. * [NOD-540] Rename addTreeNode to setTreeNode. * [NOD-540] Rename splitExponential to splitWithExponentialBias. * [NOD-540] Fix object references in reachabilityData deserialization (#563) * [NOD-540] Fix broken references in deserialization. * [NOD-540] Fix broken references in futureCoveringSet deserialization. Also add comments. * [NOD-540] Don't deserialize on the first pass in reachabilityStore.init(). * [NOD-540] Remove redundant assignment to loaded[hash]. * [NOD-540] Use NewHash instead of SetBytes. Rename data to destination. * [NOD-540] Preallocate futureCoveringSet. * [NOD-541] Implement GHOSTDAG (#560) * [NOD-541] Implement GHOSTDAG * [NOD-541] Replace the old PHANTOM variant with GHOSTDAG * [NOD-541] Move dag.updateReachability to the top of dag.applyDAGChanges to update reachability before the virtual block is updated * [NOD-541] Fix blueAnticoneSize * [NOD-541] Initialize node.bluesAnticoneSizes * [NOD-541] Fix pastUTXO and applyBlueBlocks blues order * [NOD-541] Add serialization logic to node.bluesAnticoneSizes * [NOD-541] Fix GHOSTDAG to not count the new block and the blue candidates anticone, add selected parent to blues, and save to node.bluesAnticoneSizes properly * [NOD-541] Fix test names in inner strings * [NOD-541] Writing TestGHOSTDAG * [NOD-541] In blueAnticoneSize change node->current * [NOD-541] name ghostdag return values * [NOD-541] fix ghostdag to return slice * [NOD-541] Split k-cluster violation rules * [NOD-541] Add missing space * [NOD-541] Add comment to ghostdag * [NOD-541] In selectedParentAnticone rename past->selectedParentPast * [NOD-541] Fix misrefernces to TestChainUpdates * [NOD-541] Fix ghostdag comment * [NOD-541] Make PrepareBlockForTest in blockdag package * [NOD-541] Make PrepareBlockForTest in blockdag package * [NOD-541] Assign to selectedParentAnticone[i] instead of appending * [NOD-541] Remove redundant forceTransactions arguments from PrepareBlockForTEST * [NOD-541] Add non-selected parents to anticoneHeap * [NOD-541] add test for ghostdag * [NOD-541] Add comments * [NOD-541] Use adjusted time for initializing blockNode * [NOD-541] Rename isAncestorOf -> isAncestorOfBlueCandidate * [NOD-541] Remove params from PrepareBlockForTest * [NOD-541] Fix TestChainHeight * [NOD-541] Remove recursive lock * [NOD-541] Fix TestTxIndexConnectBlock * [NOD-541] Fix TestBlueBlockWindow * [NOD-541] Put prepareAndProcessBlock in common_test.go * [NOD-541] Fix TestConfirmations * [NOD-541] Fix TestAcceptingBlock * [NOD-541] Fix TestDifficulty * [NOD-541] Fix TestVirtualBlock * [NOD-541] Fix TestSelectedPath * [NOD-541] Fix TestChainUpdates * [NOD-541] Shorten TestDifficulty test time * [NOD-541] Make PrepareBlockForTest use minimal valid block time * [NOD-541] Remove TODO comment * [NOD-541] Move blockdag related mining functions to mining.go * [NOD-541] Use NextBlockCoinbaseTransaction instead of NextBlockCoinbaseTransactionNoLock in NextCoinbaseFromAddress * [NOD-541] Remove useMinimalTime from BlockForMining * [NOD-541] Make MedianAdjustedTime a *BlockDAG method * [NOD-541] Fix ghostdag to use anticone slice instead of heap * [NOD-541] Fix NewBlockTemplate locks * [NOD-541] Fix ghostdag comments * [NOD-541] Convert MedianAdjustedTime to NextBlockTime * [NOD-541] Fix ghostdag comment * [NOD-541] Fix TestGHOSTDAG comment * [NOD-541] Add comment before sanity check * [NOD-541] Explicitly initialize .blues in ghostdag * [NOD-541] Rename *blockNode.lessThan to *blockNode.less * [NOD-541] Remove redundant check if block != chainBlock * [NOD-541] Fix comment * [NOD-541] Fix comment * [NOD-497] Add comment; General refactoring * [NOD-497] General refactoring. * [NOD-497] Use isAncestor of the tree rather than the node * [NOD-497] Remove reachability mutex lock as it is redundant (dag lock is held so no need); General refactoring. * [NOD-497] Update comment * [NOD-497] Undo test blocktimestamp * [NOD-497] Update comments; Use BlockNode.less for blockset; * [NOD-497] Change processBlock to return boolean and not the delay duration (merge conflict) * [NOD-497] Undo change for bluest to use less; Change blocknode less to use daghash.Less Co-authored-by: stasatdaglabs <39559713+stasatdaglabs@users.noreply.github.com> Co-authored-by: Dan Aharoni <dereeno@protonmail.com>
283 lines
9.7 KiB
Go
283 lines
9.7 KiB
Go
// Copyright (c) 2017 The btcsuite developers
|
|
// Use of this source code is governed by an ISC
|
|
// license that can be found in the LICENSE file.
|
|
|
|
package blockdag
|
|
|
|
import (
|
|
"github.com/kaspanet/kaspad/dagconfig"
|
|
"github.com/kaspanet/kaspad/util"
|
|
"reflect"
|
|
"testing"
|
|
)
|
|
|
|
func buildNode(t *testing.T, dag *BlockDAG, parents blockSet) *blockNode {
|
|
block, err := PrepareBlockForTest(dag, parents.hashes(), nil)
|
|
if err != nil {
|
|
t.Fatalf("error in PrepareBlockForTest: %s", err)
|
|
}
|
|
utilBlock := util.NewBlock(block)
|
|
isOrphan, isDelayed, err := dag.ProcessBlock(utilBlock, BFNoPoWCheck)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error in ProcessBlock: %s", err)
|
|
}
|
|
if isDelayed {
|
|
t.Fatalf("block is too far in the future")
|
|
}
|
|
if isOrphan {
|
|
t.Fatalf("block was unexpectedly orphan")
|
|
}
|
|
return nodeByMsgBlock(t, dag, block)
|
|
}
|
|
|
|
// TestVirtualBlock ensures that VirtualBlock works as expected.
|
|
func TestVirtualBlock(t *testing.T) {
|
|
// Create a new database and DAG instance to run tests against.
|
|
params := dagconfig.SimNetParams
|
|
params.K = 1
|
|
dag, teardownFunc, err := DAGSetup("TestVirtualBlock", Config{
|
|
DAGParams: ¶ms,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("TestVirtualBlock: Failed to setup DAG instance: %s", err)
|
|
}
|
|
defer teardownFunc()
|
|
|
|
resetExtraNonceForTest()
|
|
|
|
// Create a DAG as follows:
|
|
// 0 <- 1 <- 2
|
|
// \
|
|
// <- 3 <- 5
|
|
// \ X
|
|
// <- 4 <- 6
|
|
node0 := dag.genesis
|
|
node1 := buildNode(t, dag, setFromSlice(node0))
|
|
node2 := buildNode(t, dag, setFromSlice(node1))
|
|
node3 := buildNode(t, dag, setFromSlice(node0))
|
|
node4 := buildNode(t, dag, setFromSlice(node0))
|
|
node5 := buildNode(t, dag, setFromSlice(node3, node4))
|
|
node6 := buildNode(t, dag, setFromSlice(node3, node4))
|
|
|
|
// Given an empty VirtualBlock, each of the following test cases will:
|
|
// Set its tips to tipsToSet
|
|
// Add to it all the tips in tipsToAdd, one after the other
|
|
// Call .Tips() on it and compare the result to expectedTips
|
|
// Call .selectedTip() on it and compare the result to expectedSelectedParent
|
|
tests := []struct {
|
|
name string
|
|
tipsToSet []*blockNode
|
|
tipsToAdd []*blockNode
|
|
expectedTips blockSet
|
|
expectedSelectedParent *blockNode
|
|
}{
|
|
{
|
|
name: "empty virtual",
|
|
tipsToSet: []*blockNode{},
|
|
tipsToAdd: []*blockNode{},
|
|
expectedTips: newSet(),
|
|
expectedSelectedParent: nil,
|
|
},
|
|
{
|
|
name: "virtual with genesis tip",
|
|
tipsToSet: []*blockNode{node0},
|
|
tipsToAdd: []*blockNode{},
|
|
expectedTips: setFromSlice(node0),
|
|
expectedSelectedParent: node0,
|
|
},
|
|
{
|
|
name: "virtual with genesis tip, add child of genesis",
|
|
tipsToSet: []*blockNode{node0},
|
|
tipsToAdd: []*blockNode{node1},
|
|
expectedTips: setFromSlice(node1),
|
|
expectedSelectedParent: node1,
|
|
},
|
|
{
|
|
name: "empty virtual, add a full DAG",
|
|
tipsToSet: []*blockNode{},
|
|
tipsToAdd: []*blockNode{node0, node1, node2, node3, node4, node5, node6},
|
|
expectedTips: setFromSlice(node2, node5, node6),
|
|
expectedSelectedParent: node5,
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
// Create an empty VirtualBlock
|
|
virtual := newVirtualBlock(dag, nil)
|
|
|
|
// Set the tips. This will be the initial state
|
|
virtual.SetTips(setFromSlice(test.tipsToSet...))
|
|
|
|
// Add all blockNodes in tipsToAdd in order
|
|
for _, tipToAdd := range test.tipsToAdd {
|
|
addNodeAsChildToParents(tipToAdd)
|
|
virtual.AddTip(tipToAdd)
|
|
}
|
|
|
|
// Ensure that the virtual block's tips are now equal to expectedTips
|
|
resultTips := virtual.tips()
|
|
if !reflect.DeepEqual(resultTips, test.expectedTips) {
|
|
t.Errorf("unexpected tips in test \"%s\". "+
|
|
"Expected: %v, got: %v.", test.name, test.expectedTips, resultTips)
|
|
}
|
|
|
|
// Ensure that the virtual block's selectedParent is now equal to expectedSelectedParent
|
|
resultSelectedTip := virtual.selectedParent
|
|
if !reflect.DeepEqual(resultSelectedTip, test.expectedSelectedParent) {
|
|
t.Errorf("unexpected selected tip in test \"%s\". "+
|
|
"Expected: %v, got: %v.", test.name, test.expectedSelectedParent, resultSelectedTip)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestSelectedPath(t *testing.T) {
|
|
// Create a new database and DAG instance to run tests against.
|
|
params := dagconfig.SimNetParams
|
|
params.K = 1
|
|
dag, teardownFunc, err := DAGSetup("TestSelectedPath", Config{
|
|
DAGParams: ¶ms,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("TestSelectedPath: Failed to setup DAG instance: %s", err)
|
|
}
|
|
defer teardownFunc()
|
|
|
|
// Create an empty VirtualBlock
|
|
virtual := newVirtualBlock(dag, nil)
|
|
|
|
tip := dag.genesis
|
|
virtual.AddTip(tip)
|
|
initialPath := setFromSlice(tip)
|
|
for i := 0; i < 5; i++ {
|
|
tip = buildNode(t, dag, setFromSlice(tip))
|
|
initialPath.add(tip)
|
|
virtual.AddTip(tip)
|
|
}
|
|
initialTip := tip
|
|
|
|
firstPath := initialPath.clone()
|
|
for i := 0; i < 5; i++ {
|
|
tip = buildNode(t, dag, setFromSlice(tip))
|
|
firstPath.add(tip)
|
|
virtual.AddTip(tip)
|
|
}
|
|
// For now we don't have any DAG, just chain, the selected path should include all the blocks on the chain.
|
|
if !reflect.DeepEqual(virtual.selectedParentChainSet, firstPath) {
|
|
t.Fatalf("TestSelectedPath: selectedPathSet doesn't include the expected values. got %v, want %v", virtual.selectedParent, firstPath)
|
|
}
|
|
// We expect that selectedParentChainSlice should have all the blocks we've added so far
|
|
wantLen := 11
|
|
gotLen := len(virtual.selectedParentChainSlice)
|
|
if wantLen != gotLen {
|
|
t.Fatalf("TestSelectedPath: selectedParentChainSlice doesn't have the expected length. got %d, want %d", gotLen, wantLen)
|
|
}
|
|
|
|
secondPath := initialPath.clone()
|
|
tip = initialTip
|
|
for i := 0; i < 100; i++ {
|
|
tip = buildNode(t, dag, setFromSlice(tip))
|
|
secondPath.add(tip)
|
|
virtual.AddTip(tip)
|
|
}
|
|
// Because we added a chain that is much longer than the previous chain, the selected path should be re-organized.
|
|
if !reflect.DeepEqual(virtual.selectedParentChainSet, secondPath) {
|
|
t.Fatalf("TestSelectedPath: selectedPathSet didn't handle the re-org as expected. got %v, want %v", virtual.selectedParent, firstPath)
|
|
}
|
|
// We expect that selectedParentChainSlice should have all the blocks we've added so far except the old chain
|
|
wantLen = 106
|
|
gotLen = len(virtual.selectedParentChainSlice)
|
|
if wantLen != gotLen {
|
|
t.Fatalf("TestSelectedPath: selectedParentChainSlice doesn't have"+
|
|
"the expected length, possibly because it didn't handle the re-org as expected. got %d, want %d", gotLen, wantLen)
|
|
}
|
|
|
|
tip = initialTip
|
|
for i := 0; i < 3; i++ {
|
|
tip = buildNode(t, dag, setFromSlice(tip))
|
|
virtual.AddTip(tip)
|
|
}
|
|
// Because we added a very short chain, the selected path should not be affected.
|
|
if !reflect.DeepEqual(virtual.selectedParentChainSet, secondPath) {
|
|
t.Fatalf("TestSelectedPath: selectedPathSet did an unexpected re-org. got %v, want %v", virtual.selectedParent, firstPath)
|
|
}
|
|
// We expect that selectedParentChainSlice not to change
|
|
wantLen = 106
|
|
gotLen = len(virtual.selectedParentChainSlice)
|
|
if wantLen != gotLen {
|
|
t.Fatalf("TestSelectedPath: selectedParentChainSlice doesn't"+
|
|
"have the expected length, possibly due to unexpected did an unexpected re-org. got %d, want %d", gotLen, wantLen)
|
|
}
|
|
|
|
// We call updateSelectedParentSet manually without updating the tips, to check if it panics
|
|
virtual2 := newVirtualBlock(dag, nil)
|
|
defer func() {
|
|
if r := recover(); r == nil {
|
|
t.Fatalf("updateSelectedParentSet didn't panic")
|
|
}
|
|
}()
|
|
virtual2.updateSelectedParentSet(buildNode(t, dag, setFromSlice()))
|
|
}
|
|
|
|
func TestChainUpdates(t *testing.T) {
|
|
// Create a new database and DAG instance to run tests against.
|
|
params := dagconfig.SimNetParams
|
|
params.K = 1
|
|
dag, teardownFunc, err := DAGSetup("TestChainUpdates", Config{
|
|
DAGParams: ¶ms,
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("TestChainUpdates: Failed to setup DAG instance: %s", err)
|
|
}
|
|
defer teardownFunc()
|
|
|
|
genesis := dag.genesis
|
|
|
|
// Create a chain to be removed
|
|
var toBeRemovedNodes []*blockNode
|
|
toBeRemovedTip := genesis
|
|
for i := 0; i < 5; i++ {
|
|
toBeRemovedTip = buildNode(t, dag, setFromSlice(toBeRemovedTip))
|
|
toBeRemovedNodes = append(toBeRemovedNodes, toBeRemovedTip)
|
|
}
|
|
|
|
// Create a VirtualBlock with the toBeRemoved chain
|
|
virtual := newVirtualBlock(dag, setFromSlice(toBeRemovedNodes...))
|
|
|
|
// Create a chain to be added
|
|
var toBeAddedNodes []*blockNode
|
|
toBeAddedTip := genesis
|
|
for i := 0; i < 8; i++ {
|
|
toBeAddedTip = buildNode(t, dag, setFromSlice(toBeAddedTip))
|
|
toBeAddedNodes = append(toBeAddedNodes, toBeAddedTip)
|
|
}
|
|
|
|
// Set the virtual tip to be the tip of the toBeAdded chain
|
|
chainUpdates := virtual.setTips(setFromSlice(toBeAddedTip))
|
|
|
|
// Make sure that the removed blocks are as expected (in reverse order)
|
|
if len(chainUpdates.removedChainBlockHashes) != len(toBeRemovedNodes) {
|
|
t.Fatalf("TestChainUpdates: wrong removed amount. "+
|
|
"Got: %d, want: %d", len(chainUpdates.removedChainBlockHashes), len(toBeRemovedNodes))
|
|
}
|
|
for i, removedHash := range chainUpdates.removedChainBlockHashes {
|
|
correspondingRemovedNode := toBeRemovedNodes[len(toBeRemovedNodes)-1-i]
|
|
if !removedHash.IsEqual(correspondingRemovedNode.hash) {
|
|
t.Fatalf("TestChainUpdates: wrong removed hash. "+
|
|
"Got: %s, want: %s", removedHash, correspondingRemovedNode.hash)
|
|
}
|
|
}
|
|
|
|
// Make sure that the added blocks are as expected (in forward order)
|
|
if len(chainUpdates.addedChainBlockHashes) != len(toBeAddedNodes) {
|
|
t.Fatalf("TestChainUpdates: wrong added amount. "+
|
|
"Got: %d, want: %d", len(chainUpdates.removedChainBlockHashes), len(toBeAddedNodes))
|
|
}
|
|
for i, addedHash := range chainUpdates.addedChainBlockHashes {
|
|
correspondingAddedNode := toBeAddedNodes[i]
|
|
if !addedHash.IsEqual(correspondingAddedNode.hash) {
|
|
t.Fatalf("TestChainUpdates: wrong added hash. "+
|
|
"Got: %s, want: %s", addedHash, correspondingAddedNode.hash)
|
|
}
|
|
}
|
|
}
|