diff --git a/blockdag/accept.go b/blockdag/accept.go index 300afc375..cc4f18f89 100644 --- a/blockdag/accept.go +++ b/blockdag/accept.go @@ -59,7 +59,7 @@ func (b *BlockDAG) maybeAcceptBlock(block *btcutil.Block, flags BehaviorFlags) e // if the block ultimately gets connected to the main chain, it starts out // on a side chain. blockHeader := &block.MsgBlock().Header - newNode := newBlockNode(blockHeader, parents) + newNode := newBlockNode(blockHeader, parents, b.dagParams.K) newNode.status = statusDataStored b.index.AddNode(newNode) diff --git a/blockdag/blockheap.go b/blockdag/blockheap.go index 87617465e..1e89f7211 100644 --- a/blockdag/blockheap.go +++ b/blockdag/blockheap.go @@ -1,6 +1,10 @@ package blockdag -import "container/heap" +import ( + "container/heap" + + "github.com/daglabs/btcd/dagconfig/daghash" +) // baseHeap is an implementation for heap.Interface that sorts blocks by their height type baseHeap []*blockNode @@ -22,7 +26,7 @@ func (h *baseHeap) Pop() interface{} { func (h baseHeap) Less(i, j int) bool { if h[i].height == h[j].height { - return HashToBig(&h[i].hash).Cmp(HashToBig(&h[j].hash)) > 0 + return daghash.HashToBig(&h[i].hash).Cmp(daghash.HashToBig(&h[j].hash)) > 0 } return h[i].height > h[j].height diff --git a/blockdag/blockheap_test.go b/blockdag/blockheap_test.go index dbd075e43..f57dd435c 100644 --- a/blockdag/blockheap_test.go +++ b/blockdag/blockheap_test.go @@ -2,19 +2,20 @@ package blockdag import ( "testing" - "github.com/daglabs/btcd/dagconfig/daghash" + "github.com/daglabs/btcd/dagconfig" + "github.com/daglabs/btcd/dagconfig/daghash" ) // TestBlockHeap tests pushing, popping, and determining the length of the heap. func TestBlockHeap(t *testing.T) { block0Header := dagconfig.MainNetParams.GenesisBlock.Header - block0 := newBlockNode(&block0Header, newSet()) + block0 := newBlockNode(&block0Header, newSet(), dagconfig.MainNetParams.K) block100000Header := Block100000.Header - block100000 := newBlockNode(&block100000Header, setFromSlice(block0)) + block100000 := newBlockNode(&block100000Header, setFromSlice(block0), dagconfig.MainNetParams.K) - block0smallHash := newBlockNode(&block0Header, newSet()) + block0smallHash := newBlockNode(&block0Header, newSet(), dagconfig.MainNetParams.K) block0smallHash.hash = daghash.Hash{} tests := []struct { diff --git a/blockdag/blockindex.go b/blockdag/blockindex.go index 88592736d..4a561ae9c 100644 --- a/blockdag/blockindex.go +++ b/blockdag/blockindex.go @@ -5,6 +5,7 @@ package blockdag import ( + "fmt" "math/big" "sort" "sync" @@ -85,10 +86,10 @@ type blockNode struct { diffChild *blockNode // blues are all blue blocks in this block's worldview that are in its selected parent anticone - blues blockSet + blues []*blockNode - // blueScore is the count of all the blue blocks in this block's past (including itself) - blueScore int64 + // blueScore is the count of all the blue blocks in this block's past + blueScore uint64 // utxoDiff is the UTXO of the block represented as a diff to the virtual block utxoDiff UtxoViewpoint @@ -124,10 +125,11 @@ type blockNode struct { // calculating the height and workSum from the respective fields on the first parent. // This function is NOT safe for concurrent access. It must only be called when // initially creating a node. -func initBlockNode(node *blockNode, blockHeader *wire.BlockHeader, parents blockSet) { +func initBlockNode(node *blockNode, blockHeader *wire.BlockHeader, parents blockSet, phantomK uint32) { *node = blockNode{ hash: blockHeader.BlockHash(), parents: parents, + children: make(blockSet), workSum: CalcWork(blockHeader.Bits), version: blockHeader.Version, bits: blockHeader.Bits, @@ -136,18 +138,35 @@ func initBlockNode(node *blockNode, blockHeader *wire.BlockHeader, parents block merkleRoot: blockHeader.MerkleRoot, } if len(parents) > 0 { - node.selectedParent = parents.first() - node.height = node.selectedParent.height + 1 + addNodeAsChildToParents(node) + node.blues, node.selectedParent, node.blueScore = phantom(node, phantomK) + node.height = calculateNodeHeight(node) node.workSum = node.workSum.Add(node.selectedParent.workSum, node.workSum) } } +func addNodeAsChildToParents(node *blockNode) { + for _, parent := range node.parents { + parent.children.add(node) + } +} + +func calculateNodeHeight(node *blockNode) int32 { + var maxHeight int32 + for _, parent := range node.parents { + if maxHeight < parent.height { + maxHeight = parent.height + } + } + return maxHeight + 1 +} + // newBlockNode returns a new block node for the given block header and parent // nodes, calculating the height and workSum from the respective fields on the // parent. This function is NOT safe for concurrent access. -func newBlockNode(blockHeader *wire.BlockHeader, parents blockSet) *blockNode { +func newBlockNode(blockHeader *wire.BlockHeader, parents blockSet, phantomK uint32) *blockNode { var node blockNode - initBlockNode(&node, blockHeader, parents) + initBlockNode(&node, blockHeader, parents, phantomK) return &node } @@ -235,12 +254,17 @@ func (node *blockNode) CalcPastMedianTime() time.Time { } func (node *blockNode) PrevHashes() []daghash.Hash { - prevHashes := make([]daghash.Hash, len(node.parents)) - for _, parent := range node.parents { - prevHashes = append(prevHashes, parent.hash) - } + return node.parents.hashes() +} - return prevHashes +// isGenesis returns if the current block is the genesis block +func (node *blockNode) isGenesis() bool { + return len(node.parents) == 0 +} + +// String returns a string that contains the block hash and height. +func (node blockNode) String() string { + return fmt.Sprintf("%s (%d)", node.hash, node.height) } // blockIndex provides facilities for keeping track of an in-memory index of the diff --git a/blockdag/blockset.go b/blockdag/blockset.go index 2b614f052..561d5f3dd 100644 --- a/blockdag/blockset.go +++ b/blockdag/blockset.go @@ -1,6 +1,7 @@ package blockdag import ( + "sort" "strings" "github.com/daglabs/btcd/dagconfig/daghash" @@ -23,17 +24,6 @@ func setFromSlice(blocks ...*blockNode) blockSet { return set } -// toSlice converts a set of blocks into a slice -func (bs blockSet) toSlice() []*blockNode { - slice := []*blockNode{} - - for _, block := range bs { - slice = append(slice, block) - } - - return slice -} - // add adds a block to this BlockSet func (bs blockSet) add(block *blockNode) { bs[block.hash] = block @@ -118,7 +108,9 @@ func (bs blockSet) hashes() []daghash.Hash { for hash := range bs { hashes = append(hashes, hash) } - + sort.Slice(hashes, func(i, j int) bool { + return daghash.Less(&hashes[i], &hashes[j]) + }) return hashes } @@ -132,9 +124,20 @@ func (bs blockSet) first() *blockNode { } func (bs blockSet) String() string { - ids := []string{} - for hash := range bs { - ids = append(ids, hash.String()) + nodeStrs := make([]string, 0, len(bs)) + for _, node := range bs { + nodeStrs = append(nodeStrs, node.String()) } - return strings.Join(ids, ",") + return strings.Join(nodeStrs, ",") +} + +// anyChildInSet returns true iff any child of block is contained within this set +func (bs blockSet) anyChildInSet(block *blockNode) bool { + for _, child := range block.children { + if bs.contains(child) { + return true + } + } + + return false } diff --git a/blockdag/common_test.go b/blockdag/common_test.go index b05e8e572..a8e82da0f 100644 --- a/blockdag/common_test.go +++ b/blockdag/common_test.go @@ -343,14 +343,14 @@ func (b *BlockDAG) TstSetCoinbaseMaturity(maturity uint16) { b.dagParams.CoinbaseMaturity = maturity } -// newFakeDag returns a chain that is usable for syntetic tests. It is +// newTestDAG returns a DAG that is usable for syntetic tests. It is // important to note that this chain has no database associated with it, so // it is not usable with all functions and the tests must take care when making // use of it. -func newFakeDAG(params *dagconfig.Params) *BlockDAG { +func newTestDAG(params *dagconfig.Params) *BlockDAG { // Create a genesis block node and block index index populated with it // for use when creating the fake chain below. - node := newBlockNode(¶ms.GenesisBlock.Header, newSet()) + node := newBlockNode(¶ms.GenesisBlock.Header, newSet(), params.K) index := newBlockIndex(nil, params) index.AddNode(node) @@ -370,15 +370,15 @@ func newFakeDAG(params *dagconfig.Params) *BlockDAG { } } -// newFakeNode creates a block node connected to the passed parent with the +// newTestNode creates a block node connected to the passed parent with the // provided fields populated and fake values for the other fields. -func newFakeNode(parent *blockNode, blockVersion int32, bits uint32, timestamp time.Time) *blockNode { +func newTestNode(parents blockSet, blockVersion int32, bits uint32, timestamp time.Time, phantomK uint32) *blockNode { // Make up a header and create a block node from it. header := &wire.BlockHeader{ Version: blockVersion, - PrevBlocks: []daghash.Hash{parent.hash}, // TODO: (Stas) This is wrong. Modified only to satisfy compilation. + PrevBlocks: parents.hashes(), Bits: bits, Timestamp: timestamp, } - return newBlockNode(header, setFromSlice(parent)) // TODO: (Stas) This is wrong. Modified only to satisfy compilation. + return newBlockNode(header, parents, phantomK) } diff --git a/blockdag/dag_test.go b/blockdag/dag_test.go index db5c868c9..d6a663632 100644 --- a/blockdag/dag_test.go +++ b/blockdag/dag_test.go @@ -121,13 +121,13 @@ func TestCalcSequenceLock(t *testing.T) { blockVersion := int32(0x20000000) // Generate enough synthetic blocks for the rest of the test - chain := newFakeDAG(netParams) + chain := newTestDAG(netParams) node := chain.dag.SelectedTip() blockTime := node.Header().Timestamp numBlocksToGenerate := uint32(5) for i := uint32(0); i < numBlocksToGenerate; i++ { blockTime = blockTime.Add(time.Second) - node = newFakeNode(node, blockVersion, 0, blockTime) + node = newTestNode(setFromSlice(node), blockVersion, 0, blockTime, netParams.K) chain.index.AddNode(node) chain.dag.SetTip(node) } @@ -448,7 +448,7 @@ func TestLocateInventory(t *testing.T) { // genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18 // \-> 16a -> 17a tip := tstTip - dag := newFakeDAG(&dagconfig.MainNetParams) + dag := newTestDAG(&dagconfig.MainNetParams) branch0Nodes := chainedNodes(setFromSlice(dag.dag.Genesis()), 18) branch1Nodes := chainedNodes(setFromSlice(branch0Nodes[14]), 2) for _, node := range branch0Nodes { @@ -788,20 +788,20 @@ func TestHeightToHashRange(t *testing.T) { // genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18 // \-> 16a -> 17a -> 18a (unvalidated) tip := tstTip - chain := newFakeDAG(&dagconfig.MainNetParams) - branch0Nodes := chainedNodes(setFromSlice(chain.dag.Genesis()), 18) + blockDAG := newTestDAG(&dagconfig.MainNetParams) + branch0Nodes := chainedNodes(setFromSlice(blockDAG.dag.Genesis()), 18) branch1Nodes := chainedNodes(setFromSlice(branch0Nodes[14]), 3) for _, node := range branch0Nodes { - chain.index.SetStatusFlags(node, statusValid) - chain.index.AddNode(node) + blockDAG.index.SetStatusFlags(node, statusValid) + blockDAG.index.AddNode(node) } for _, node := range branch1Nodes { if node.height < 18 { - chain.index.SetStatusFlags(node, statusValid) + blockDAG.index.SetStatusFlags(node, statusValid) } - chain.index.AddNode(node) + blockDAG.index.AddNode(node) } - chain.dag.SetTip(tip(branch0Nodes)) + blockDAG.dag.SetTip(tip(branch0Nodes)) tests := []struct { name string @@ -856,7 +856,7 @@ func TestHeightToHashRange(t *testing.T) { }, } for _, test := range tests { - hashes, err := chain.HeightToHashRange(test.startHeight, &test.endHash, + hashes, err := blockDAG.HeightToHashRange(test.startHeight, &test.endHash, test.maxResults) if err != nil { if !test.expectError { @@ -880,7 +880,7 @@ func TestIntervalBlockHashes(t *testing.T) { // genesis -> 1 -> 2 -> ... -> 15 -> 16 -> 17 -> 18 // \-> 16a -> 17a -> 18a (unvalidated) tip := tstTip - chain := newFakeDAG(&dagconfig.MainNetParams) + chain := newTestDAG(&dagconfig.MainNetParams) branch0Nodes := chainedNodes(setFromSlice(chain.dag.Genesis()), 18) branch1Nodes := chainedNodes(setFromSlice(branch0Nodes[14]), 3) for _, node := range branch0Nodes { diff --git a/blockdag/dagio.go b/blockdag/dagio.go index ed4fc9ae3..560e6ada9 100644 --- a/blockdag/dagio.go +++ b/blockdag/dagio.go @@ -11,11 +11,12 @@ import ( "sync" "time" + "encoding/json" + "github.com/daglabs/btcd/dagconfig/daghash" "github.com/daglabs/btcd/database" "github.com/daglabs/btcd/wire" "github.com/daglabs/btcutil" - "encoding/json" ) const ( @@ -838,7 +839,7 @@ func (b *BlockDAG) createDAGState() error { genesisBlock := btcutil.NewBlock(b.dagParams.GenesisBlock) genesisBlock.SetHeight(0) header := &genesisBlock.MsgBlock().Header - node := newBlockNode(header, nil) + node := newBlockNode(header, nil, b.dagParams.K) node.status = statusDataStored | statusValid b.dag.SetTip(node) @@ -1025,7 +1026,7 @@ func (b *BlockDAG) initDAGState() error { // Initialize the block node for the block, connect it, // and add it to the block index. node := &blockNodes[i] - initBlockNode(node, header, setFromSlice(parent)) // TODO: (Stas) This is wrong. Modified only to satisfy compilation. + initBlockNode(node, header, setFromSlice(parent), b.dagParams.K) // TODO: (Stas) This is wrong. Modified only to satisfy compilation. node.status = status b.index.addNode(node) diff --git a/blockdag/dagview_test.go b/blockdag/dagview_test.go index 2e8c81038..d232101f8 100644 --- a/blockdag/dagview_test.go +++ b/blockdag/dagview_test.go @@ -5,11 +5,11 @@ package blockdag import ( - "fmt" "math/rand" "reflect" "testing" + "github.com/daglabs/btcd/dagconfig" "github.com/daglabs/btcd/wire" ) @@ -28,17 +28,12 @@ func chainedNodes(parents blockSet, numNodes int) []*blockNode { // synthetic tests to work. header := wire.BlockHeader{Nonce: testNoncePrng.Uint32()} header.PrevBlocks = tips.hashes() - nodes[i] = newBlockNode(&header, tips) + nodes[i] = newBlockNode(&header, tips, dagconfig.SimNetParams.K) tips = setFromSlice(nodes[i]) } return nodes } -// String returns the block node as a human-readable name. -func (node blockNode) String() string { - return fmt.Sprintf("%s(%d)", node.hash, node.height) -} - // tstTip is a convenience function to grab the tip of a chain of block nodes // created via chainedNodes. func tstTip(nodes []*blockNode) *blockNode { diff --git a/blockdag/difficulty.go b/blockdag/difficulty.go index 3a9ca2e6c..6bc92a727 100644 --- a/blockdag/difficulty.go +++ b/blockdag/difficulty.go @@ -7,8 +7,6 @@ package blockdag import ( "math/big" "time" - - "github.com/daglabs/btcd/dagconfig/daghash" ) var ( @@ -21,20 +19,6 @@ var ( oneLsh256 = new(big.Int).Lsh(bigOne, 256) ) -// HashToBig converts a daghash.Hash into a big.Int that can be used to -// perform math comparisons. -func HashToBig(hash *daghash.Hash) *big.Int { - // A Hash is in little-endian, but the big package wants the bytes in - // big-endian, so reverse them. - buf := *hash - blen := len(buf) - for i := 0; i < blen/2; i++ { - buf[i], buf[blen-1-i] = buf[blen-1-i], buf[i] - } - - return new(big.Int).SetBytes(buf[:]) -} - // CompactToBig converts a compact representation of a whole number N to an // unsigned 32-bit number. The representation is similar to IEEE754 floating // point numbers. diff --git a/blockdag/fullblocktests/generate.go b/blockdag/fullblocktests/generate.go index e98b9d618..48e4549cc 100644 --- a/blockdag/fullblocktests/generate.go +++ b/blockdag/fullblocktests/generate.go @@ -342,7 +342,7 @@ func solveBlock(header *wire.BlockHeader) bool { default: hdr.Nonce = i hash := hdr.BlockHash() - if blockdag.HashToBig(&hash).Cmp( + if daghash.HashToBig(&hash).Cmp( targetDifficulty) <= 0 { results <- sbResult{true, i} @@ -1444,7 +1444,7 @@ func Generate(includeLargeReorg bool) (tests [][]TestInstance, err error) { // a uint256 is higher than the limit. b46.Header.Nonce++ blockHash := b46.BlockHash() - hashNum := blockdag.HashToBig(&blockHash) + hashNum := daghash.HashToBig(&blockHash) if hashNum.Cmp(g.params.PowLimit) >= 0 { break } diff --git a/blockdag/phanom_test.go b/blockdag/phanom_test.go new file mode 100644 index 000000000..5a6cdab1f --- /dev/null +++ b/blockdag/phanom_test.go @@ -0,0 +1,891 @@ +package blockdag + +import ( + "fmt" + "reflect" + "sort" + "testing" + "time" + + "github.com/daglabs/btcd/dagconfig/daghash" + + "github.com/daglabs/btcd/dagconfig" +) + +type testBlockData struct { + parents []string + id string //id is a virtual entity that is used only for tests so we can define relations between blocks without knowing their hash + expectedScore uint64 + expectedSelectedParent string + expectedBlues []string +} + +type hashIDPair struct { + hash *daghash.Hash + id string +} + +//TestPhantom iterate over several dag simulations, and checks +//that the blue score, blue set and selected parent of each +//block calculated as expected +func TestPhantom(t *testing.T) { + netParams := dagconfig.SimNetParams + + blockVersion := int32(0x20000000) + + tests := []struct { + k uint32 + dagData []*testBlockData + virtualBlockID string + expectedReds []string + }{ + { + //Block hash order:DEBHICAKGJF + k: 1, + virtualBlockID: "K", + expectedReds: []string{"D"}, + dagData: []*testBlockData{ + { + parents: []string{"A"}, + id: "B", + expectedScore: 1, + expectedSelectedParent: "A", + expectedBlues: []string{"A"}, + }, + { + parents: []string{"A"}, + id: "C", + expectedScore: 1, + expectedSelectedParent: "A", + expectedBlues: []string{"A"}, + }, + { + parents: []string{"B"}, + id: "D", + expectedScore: 2, + expectedSelectedParent: "B", + expectedBlues: []string{"B"}, + }, + { + parents: []string{"B"}, + id: "E", + expectedScore: 2, + expectedSelectedParent: "B", + expectedBlues: []string{"B"}, + }, + { + parents: []string{"C"}, + id: "F", + expectedScore: 2, + expectedSelectedParent: "C", + expectedBlues: []string{"C"}, + }, + { + parents: []string{"C", "D"}, + id: "G", + expectedScore: 4, + expectedSelectedParent: "C", + expectedBlues: []string{"D", "B", "C"}, + }, + { + parents: []string{"C", "E"}, + id: "H", + expectedScore: 4, + expectedSelectedParent: "C", + expectedBlues: []string{"E", "B", "C"}, + }, + { + parents: []string{"E", "G"}, + id: "I", + expectedScore: 5, + expectedSelectedParent: "G", + expectedBlues: []string{"G"}, + }, + { + parents: []string{"F"}, + id: "J", + expectedScore: 3, + expectedSelectedParent: "F", + expectedBlues: []string{"F"}, + }, + { + parents: []string{"H", "I", "J"}, + id: "K", + expectedScore: 9, + expectedSelectedParent: "H", + expectedBlues: []string{"I", "G", "J", "F", "H"}, + }, + }, + }, + { + //block hash order:DQKRLHOEBSIGUJNPCMTAFV + k: 2, + virtualBlockID: "V", + expectedReds: []string{"D", "J", "P"}, + dagData: []*testBlockData{ + { + parents: []string{"A"}, + id: "B", + expectedScore: 1, + expectedSelectedParent: "A", + expectedBlues: []string{"A"}, + }, + { + parents: []string{"A"}, + id: "C", + expectedScore: 1, + expectedSelectedParent: "A", + expectedBlues: []string{"A"}, + }, + { + parents: []string{"B"}, + id: "D", + expectedScore: 2, + expectedSelectedParent: "B", + expectedBlues: []string{"B"}, + }, + { + parents: []string{"B"}, + id: "E", + expectedScore: 2, + expectedSelectedParent: "B", + expectedBlues: []string{"B"}, + }, + { + parents: []string{"C"}, + id: "F", + expectedScore: 2, + expectedSelectedParent: "C", + expectedBlues: []string{"C"}, + }, + { + parents: []string{"C"}, + id: "G", + expectedScore: 2, + expectedSelectedParent: "C", + expectedBlues: []string{"C"}, + }, + { + parents: []string{"G"}, + id: "H", + expectedScore: 3, + expectedSelectedParent: "G", + expectedBlues: []string{"G"}, + }, + { + parents: []string{"E"}, + id: "I", + expectedScore: 3, + expectedSelectedParent: "E", + expectedBlues: []string{"E"}, + }, + { + parents: []string{"E"}, + id: "J", + expectedScore: 3, + expectedSelectedParent: "E", + expectedBlues: []string{"E"}, + }, + { + parents: []string{"I"}, + id: "K", + expectedScore: 4, + expectedSelectedParent: "I", + expectedBlues: []string{"I"}, + }, + { + parents: []string{"K", "H"}, + id: "L", + expectedScore: 5, + expectedSelectedParent: "K", + expectedBlues: []string{"K"}, + }, + { + parents: []string{"F", "L"}, + id: "M", + expectedScore: 10, + expectedSelectedParent: "F", + expectedBlues: []string{"L", "K", "H", "I", "E", "G", "B", "F"}, + }, + { + parents: []string{"G", "K"}, + id: "N", + expectedScore: 7, + expectedSelectedParent: "G", + expectedBlues: []string{"K", "I", "E", "B", "G"}, + }, + { + parents: []string{"J", "N"}, + id: "O", + expectedScore: 8, + expectedSelectedParent: "N", + expectedBlues: []string{"N"}, + }, + { + parents: []string{"D"}, + id: "P", + expectedScore: 3, + expectedSelectedParent: "D", + expectedBlues: []string{"D"}, + }, + { + parents: []string{"O", "P"}, + id: "Q", + expectedScore: 10, + expectedSelectedParent: "P", + expectedBlues: []string{"O", "N", "K", "I", "J", "E", "P"}, + }, + { + parents: []string{"L", "Q"}, + id: "R", + expectedScore: 11, + expectedSelectedParent: "Q", + expectedBlues: []string{"Q"}, + }, + { + parents: []string{"M", "R"}, + id: "S", + expectedScore: 15, + expectedSelectedParent: "M", + expectedBlues: []string{"R", "Q", "O", "N", "M"}, + }, + { + parents: []string{"H", "F"}, + id: "T", + expectedScore: 5, + expectedSelectedParent: "F", + expectedBlues: []string{"H", "G", "F"}, + }, + { + parents: []string{"M", "T"}, + id: "U", + expectedScore: 12, + expectedSelectedParent: "M", + expectedBlues: []string{"T", "M"}, + }, + { + parents: []string{"S", "U"}, + id: "V", + expectedScore: 18, + expectedSelectedParent: "S", + expectedBlues: []string{"U", "T", "S"}, + }, + }, + }, + { + //Block hash order:NRSHBUXJTFGPDVCKEQIOWLMA + k: 1, + virtualBlockID: "X", + expectedReds: []string{"D", "F", "G", "H", "J", "K", "L", "N", "O", "Q", "R", "S", "U", "V"}, + dagData: []*testBlockData{ + { + parents: []string{"A"}, + id: "B", + expectedScore: 1, + expectedSelectedParent: "A", + expectedBlues: []string{"A"}, + }, + { + parents: []string{"A"}, + id: "C", + expectedScore: 1, + expectedSelectedParent: "A", + expectedBlues: []string{"A"}, + }, + { + parents: []string{"A"}, + id: "D", + expectedScore: 1, + expectedSelectedParent: "A", + expectedBlues: []string{"A"}, + }, + { + parents: []string{"A"}, + id: "E", + expectedScore: 1, + expectedSelectedParent: "A", + expectedBlues: []string{"A"}, + }, + { + parents: []string{"B"}, + id: "F", + expectedScore: 2, + expectedSelectedParent: "B", + expectedBlues: []string{"B"}, + }, + { + parents: []string{"B"}, + id: "G", + expectedScore: 2, + expectedSelectedParent: "B", + expectedBlues: []string{"B"}, + }, + { + parents: []string{"C"}, + id: "H", + expectedScore: 2, + expectedSelectedParent: "C", + expectedBlues: []string{"C"}, + }, + { + parents: []string{"C"}, + id: "I", + expectedScore: 2, + expectedSelectedParent: "C", + expectedBlues: []string{"C"}, + }, + { + parents: []string{"B"}, + id: "J", + expectedScore: 2, + expectedSelectedParent: "B", + expectedBlues: []string{"B"}, + }, + { + parents: []string{"D"}, + id: "K", + expectedScore: 2, + expectedSelectedParent: "D", + expectedBlues: []string{"D"}, + }, + { + parents: []string{"D"}, + id: "L", + expectedScore: 2, + expectedSelectedParent: "D", + expectedBlues: []string{"D"}, + }, + { + parents: []string{"E"}, + id: "M", + expectedScore: 2, + expectedSelectedParent: "E", + expectedBlues: []string{"E"}, + }, + { + parents: []string{"E"}, + id: "N", + expectedScore: 2, + expectedSelectedParent: "E", + expectedBlues: []string{"E"}, + }, + { + parents: []string{"F", "G", "J"}, + id: "O", + expectedScore: 5, + expectedSelectedParent: "G", + expectedBlues: []string{"J", "F", "G"}, + }, + { + parents: []string{"B", "M", "I"}, + id: "P", + expectedScore: 6, + expectedSelectedParent: "B", + expectedBlues: []string{"I", "M", "C", "E", "B"}, + }, + { + parents: []string{"K", "E"}, + id: "Q", + expectedScore: 4, + expectedSelectedParent: "E", + expectedBlues: []string{"K", "D", "E"}, + }, + { + parents: []string{"L", "N"}, + id: "R", + expectedScore: 3, + expectedSelectedParent: "L", + expectedBlues: []string{"L"}, + }, + { + parents: []string{"I", "Q"}, + id: "S", + expectedScore: 5, + expectedSelectedParent: "Q", + expectedBlues: []string{"Q"}, + }, + { + parents: []string{"K", "P"}, + id: "T", + expectedScore: 7, + expectedSelectedParent: "P", + expectedBlues: []string{"P"}, + }, + { + parents: []string{"K", "L"}, + id: "U", + expectedScore: 4, + expectedSelectedParent: "L", + expectedBlues: []string{"K", "L"}, + }, + { + parents: []string{"U", "R"}, + id: "V", + expectedScore: 6, + expectedSelectedParent: "U", + expectedBlues: []string{"R", "U"}, + }, + { + parents: []string{"S", "U", "T"}, + id: "W", + expectedScore: 8, + expectedSelectedParent: "T", + expectedBlues: []string{"T"}, + }, + { + parents: []string{"V", "W", "H"}, + id: "X", + expectedScore: 9, + expectedSelectedParent: "W", + expectedBlues: []string{"W"}, + }, + }, + }, + { + //Secret mining attack: The attacker is mining + //blocks B,C,D,E,F,G,T in secret without propagating + //them, so all blocks except T should be red, because + //they don't follow the rules of PHANTOM that require + //you to point to all the parents that you know, and + //propagate your block as soon as it's mined + + //Block hash order: HRTGMKQBXDWSICYFONUPLEAJZ + k: 1, + virtualBlockID: "Y", + expectedReds: []string{"B", "C", "D", "E", "F", "G"}, + dagData: []*testBlockData{ + { + parents: []string{"A"}, + id: "B", + expectedScore: 1, + expectedSelectedParent: "A", + expectedBlues: []string{"A"}, + }, + { + parents: []string{"B"}, + id: "C", + expectedScore: 2, + expectedSelectedParent: "B", + expectedBlues: []string{"B"}, + }, + { + parents: []string{"C"}, + id: "D", + expectedScore: 3, + expectedSelectedParent: "C", + expectedBlues: []string{"C"}, + }, + { + parents: []string{"D"}, + id: "E", + expectedScore: 4, + expectedSelectedParent: "D", + expectedBlues: []string{"D"}, + }, + { + parents: []string{"E"}, + id: "F", + expectedScore: 5, + expectedSelectedParent: "E", + expectedBlues: []string{"E"}, + }, + { + parents: []string{"F"}, + id: "G", + expectedScore: 6, + expectedSelectedParent: "F", + expectedBlues: []string{"F"}, + }, + { + parents: []string{"A"}, + id: "H", + expectedScore: 1, + expectedSelectedParent: "A", + expectedBlues: []string{"A"}, + }, + { + parents: []string{"A"}, + id: "I", + expectedScore: 1, + expectedSelectedParent: "A", + expectedBlues: []string{"A"}, + }, + { + parents: []string{"H", "I"}, + id: "J", + expectedScore: 3, + expectedSelectedParent: "I", + expectedBlues: []string{"H", "I"}, + }, + { + parents: []string{"H", "I"}, + id: "K", + expectedScore: 3, + expectedSelectedParent: "I", + expectedBlues: []string{"H", "I"}, + }, + { + parents: []string{"I"}, + id: "L", + expectedScore: 2, + expectedSelectedParent: "I", + expectedBlues: []string{"I"}, + }, + { + parents: []string{"J", "K", "L"}, + id: "M", + expectedScore: 6, + expectedSelectedParent: "J", + expectedBlues: []string{"K", "L", "J"}, + }, + { + parents: []string{"J", "K", "L"}, + id: "N", + expectedScore: 6, + expectedSelectedParent: "J", + expectedBlues: []string{"K", "L", "J"}, + }, + { + parents: []string{"N", "M"}, + id: "O", + expectedScore: 8, + expectedSelectedParent: "N", + expectedBlues: []string{"M", "N"}, + }, + { + parents: []string{"N", "M"}, + id: "P", + expectedScore: 8, + expectedSelectedParent: "N", + expectedBlues: []string{"M", "N"}, + }, + { + parents: []string{"N", "M"}, + id: "Q", + expectedScore: 8, + expectedSelectedParent: "N", + expectedBlues: []string{"M", "N"}, + }, + { + parents: []string{"O", "P", "Q"}, + id: "R", + expectedScore: 11, + expectedSelectedParent: "P", + expectedBlues: []string{"Q", "O", "P"}, + }, + { + parents: []string{"O", "P", "Q"}, + id: "S", + expectedScore: 11, + expectedSelectedParent: "P", + expectedBlues: []string{"Q", "O", "P"}, + }, + { + parents: []string{"G", "S", "R"}, + id: "T", + expectedScore: 13, + expectedSelectedParent: "S", + expectedBlues: []string{"R", "S"}, + }, + { + parents: []string{"S", "R"}, + id: "U", + expectedScore: 13, + expectedSelectedParent: "S", + expectedBlues: []string{"R", "S"}, + }, + { + parents: []string{"T", "U"}, + id: "V", + expectedScore: 15, + expectedSelectedParent: "U", + expectedBlues: []string{"T", "U"}, + }, + { + parents: []string{"T", "U"}, + id: "W", + expectedScore: 15, + expectedSelectedParent: "U", + expectedBlues: []string{"T", "U"}, + }, + { + parents: []string{"T", "U"}, + id: "X", + expectedScore: 15, + expectedSelectedParent: "U", + expectedBlues: []string{"T", "U"}, + }, + { + parents: []string{"V", "W", "X"}, + id: "Y", + expectedScore: 18, + expectedSelectedParent: "X", + expectedBlues: []string{"W", "V", "X"}, + }, + }, + }, + { + //Censorship mining attack: The attacker is mining blocks B,C,D,E,F,G in secret without propagating them, + //so all blocks except B should be red, because they don't follow the rules of + //PHANTOM that require you to point to all the parents that you know + + //Block hash order:WZHOGBJMDSICRUYKTFQLEAPXN + k: 1, + virtualBlockID: "Y", + expectedReds: []string{"C", "D", "E", "F", "G"}, + dagData: []*testBlockData{ + { + parents: []string{"A"}, + id: "B", + expectedScore: 1, + expectedSelectedParent: "A", + expectedBlues: []string{"A"}, + }, + { + parents: []string{"B"}, + id: "C", + expectedScore: 2, + expectedSelectedParent: "B", + expectedBlues: []string{"B"}, + }, + { + parents: []string{"C"}, + id: "D", + expectedScore: 3, + expectedSelectedParent: "C", + expectedBlues: []string{"C"}, + }, + { + parents: []string{"D"}, + id: "E", + expectedScore: 4, + expectedSelectedParent: "D", + expectedBlues: []string{"D"}, + }, + { + parents: []string{"E"}, + id: "F", + expectedScore: 5, + expectedSelectedParent: "E", + expectedBlues: []string{"E"}, + }, + { + parents: []string{"F"}, + id: "G", + expectedScore: 6, + expectedSelectedParent: "F", + expectedBlues: []string{"F"}, + }, + { + parents: []string{"A"}, + id: "H", + expectedScore: 1, + expectedSelectedParent: "A", + expectedBlues: []string{"A"}, + }, + { + parents: []string{"A"}, + id: "I", + expectedScore: 1, + expectedSelectedParent: "A", + expectedBlues: []string{"A"}, + }, + { + parents: []string{"H", "I", "B"}, + id: "J", + expectedScore: 4, + expectedSelectedParent: "I", + expectedBlues: []string{"H", "B", "I"}, + }, + { + parents: []string{"H", "I", "B"}, + id: "K", + expectedScore: 4, + expectedSelectedParent: "I", + expectedBlues: []string{"H", "B", "I"}, + }, + { + parents: []string{"I"}, + id: "L", + expectedScore: 2, + expectedSelectedParent: "I", + expectedBlues: []string{"I"}, + }, + { + parents: []string{"J", "K", "L", "C"}, + id: "M", + expectedScore: 7, + expectedSelectedParent: "K", + expectedBlues: []string{"J", "L", "K"}, + }, + { + parents: []string{"J", "K", "L", "C"}, + id: "N", + expectedScore: 7, + expectedSelectedParent: "K", + expectedBlues: []string{"J", "L", "K"}, + }, + { + parents: []string{"N", "M", "D"}, + id: "O", + expectedScore: 9, + expectedSelectedParent: "N", + expectedBlues: []string{"M", "N"}, + }, + { + parents: []string{"N", "M", "D"}, + id: "P", + expectedScore: 9, + expectedSelectedParent: "N", + expectedBlues: []string{"M", "N"}, + }, + { + parents: []string{"N", "M", "D"}, + id: "Q", + expectedScore: 9, + expectedSelectedParent: "N", + expectedBlues: []string{"M", "N"}, + }, + { + parents: []string{"O", "P", "Q", "E"}, + id: "R", + expectedScore: 12, + expectedSelectedParent: "P", + expectedBlues: []string{"O", "Q", "P"}, + }, + { + parents: []string{"O", "P", "Q", "E"}, + id: "S", + expectedScore: 12, + expectedSelectedParent: "P", + expectedBlues: []string{"O", "Q", "P"}, + }, + { + parents: []string{"G", "S", "R"}, + id: "T", + expectedScore: 14, + expectedSelectedParent: "R", + expectedBlues: []string{"S", "R"}, + }, + { + parents: []string{"S", "R", "F"}, + id: "U", + expectedScore: 14, + expectedSelectedParent: "R", + expectedBlues: []string{"S", "R"}, + }, + { + parents: []string{"T", "U"}, + id: "V", + expectedScore: 16, + expectedSelectedParent: "T", + expectedBlues: []string{"U", "T"}, + }, + { + parents: []string{"T", "U"}, + id: "W", + expectedScore: 16, + expectedSelectedParent: "T", + expectedBlues: []string{"U", "T"}, + }, + { + parents: []string{"T", "U"}, + id: "X", + expectedScore: 16, + expectedSelectedParent: "T", + expectedBlues: []string{"U", "T"}, + }, + { + parents: []string{"V", "W", "X"}, + id: "Y", + expectedScore: 19, + expectedSelectedParent: "W", + expectedBlues: []string{"V", "X", "W"}, + }, + }, + }, + } + + for i, test := range tests { + netParams.K = test.k + // Generate enough synthetic blocks for the rest of the test + blockDAG := newTestDAG(&netParams) + genesisNode := blockDAG.dag.SelectedTip() + blockTime := genesisNode.Header().Timestamp + blockByIDMap := make(map[string]*blockNode) + idByBlockMap := make(map[*blockNode]string) + blockByIDMap["A"] = genesisNode + idByBlockMap[genesisNode] = "A" + + for _, blockData := range test.dagData { + blockTime = blockTime.Add(time.Second) + parents := blockSet{} + for _, parentID := range blockData.parents { + parent := blockByIDMap[parentID] + parents.add(parent) + } + node := newTestNode(parents, blockVersion, 0, blockTime, test.k) + + blockDAG.index.AddNode(node) + blockByIDMap[blockData.id] = node + idByBlockMap[node] = blockData.id + + bluesIDs := make([]string, 0, len(node.blues)) + for _, blue := range node.blues { + bluesIDs = append(bluesIDs, idByBlockMap[blue]) + } + selectedParentID := idByBlockMap[node.selectedParent] + fullDataStr := fmt.Sprintf("blues: %v, selectedParent: %v, score: %v", + bluesIDs, selectedParentID, node.blueScore) + if blockData.expectedScore != node.blueScore { + t.Errorf("Test %d: Block %v expected to have score %v but got %v (fulldata: %v)", + i, blockData.id, blockData.expectedScore, node.blueScore, fullDataStr) + } + if blockData.expectedSelectedParent != selectedParentID { + t.Errorf("Test %d: Block %v expected to have selected parent %v but got %v (fulldata: %v)", + i, blockData.id, blockData.expectedSelectedParent, selectedParentID, fullDataStr) + } + if !reflect.DeepEqual(blockData.expectedBlues, bluesIDs) { + t.Errorf("Test %d: Block %v expected to have blues %v but got %v (fulldata: %v)", + i, blockData.id, blockData.expectedBlues, bluesIDs, fullDataStr) + } + } + + reds := make(map[string]bool) + + for id := range blockByIDMap { + reds[id] = true + } + + for tip := blockByIDMap[test.virtualBlockID]; tip.selectedParent != nil; tip = tip.selectedParent { + tipID := idByBlockMap[tip] + delete(reds, tipID) + for _, blue := range tip.blues { + blueID := idByBlockMap[blue] + delete(reds, blueID) + } + } + if !checkReds(test.expectedReds, reds) { + redsIDs := make([]string, 0, len(reds)) + for id := range reds { + redsIDs = append(redsIDs, id) + } + sort.Strings(redsIDs) + sort.Strings(test.expectedReds) + t.Errorf("Test %d: Expected reds %v but got %v", i, test.expectedReds, redsIDs) + } + + } +} + +func checkReds(expectedReds []string, reds map[string]bool) bool { + if len(expectedReds) != len(reds) { + return false + } + for _, redID := range expectedReds { + if !reds[redID] { + return false + } + } + return true +} diff --git a/blockdag/phantom.go b/blockdag/phantom.go new file mode 100644 index 000000000..ba7af226d --- /dev/null +++ b/blockdag/phantom.go @@ -0,0 +1,105 @@ +package blockdag + +import ( + "github.com/daglabs/btcd/dagconfig/daghash" +) + +// phantom calculates and returns the block's blue set, selected parent and blue score. +// Chain start is determined by going down the DAG through the selected path +// (follow the selected parent of each block) k + 1 steps. +// The blue set of a block are all blue blocks in its past. +// To optimize memory usage, for each block we are storing only the blue blocks in +// its selected parent's anticone that are in the future of the chain start +// as well as the selected parent itself - the rest of the +// blue set can be restored by traversing the selected parent chain and combining +// the .blues of all blocks in it. +// The blue score is the total number of blocks in this block's blue set +// of the selected parent. (the blue score of the genesis block is defined as 0) +// The selected parent is chosen by determining which block's parent will give this block the highest blue score. +func phantom(block *blockNode, k uint32) (blues []*blockNode, selectedParent *blockNode, score uint64) { + bestScore := uint64(0) + var bestParent *blockNode + var bestBlues []*blockNode + var bestHash *daghash.Hash + for _, parent := range block.parents { + chainStart := digToChainStart(parent, k) + candidates := blueCandidates(chainStart) + blues := traverseCandidates(block, candidates, parent) + score := uint64(len(blues)) + parent.blueScore + + if score > bestScore || (score == bestScore && (bestHash == nil || daghash.Less(bestHash, &parent.hash))) { + bestScore = score + bestBlues = blues + bestParent = parent + bestHash = &parent.hash + } + } + + return bestBlues, bestParent, bestScore +} + +// digToChainStart digs through the selected path and returns the block in depth k+1 +func digToChainStart(parent *blockNode, k uint32) *blockNode { + current := parent + + for i := uint32(0); i < k; i++ { + if current.isGenesis() { + break + } + current = current.selectedParent + } + + return current +} + +func blueCandidates(chainStart *blockNode) blockSet { + candidates := newSet() + candidates.add(chainStart) + + queue := []*blockNode{chainStart} + for len(queue) > 0 { + var current *blockNode + current, queue = queue[0], queue[1:] + + children := current.children + for _, child := range children { + if !candidates.contains(child) { + candidates.add(child) + queue = append(queue, child) + } + } + } + + return candidates +} + +//traverseCandidates returns all the blocks that are in the future of the chain start and in the anticone of the selected parent +func traverseCandidates(newBlock *blockNode, candidates blockSet, selectedParent *blockNode) []*blockNode { + blues := []*blockNode{} + selectedParentPast := newSet() + queue := NewHeap() + visited := newSet() + + for _, parent := range newBlock.parents { + queue.Push(parent) + } + + for queue.Len() > 0 { + current := queue.Pop() + if candidates.contains(current) { + if current == selectedParent || selectedParentPast.anyChildInSet(current) { + selectedParentPast.add(current) + } else { + blues = append(blues, current) + } + for _, parent := range current.parents { + if !visited.contains(parent) { + visited.add(parent) + queue.Push(parent) + } + } + } + } + + return append(blues, selectedParent) +} diff --git a/blockdag/validate.go b/blockdag/validate.go index 1c9c8a8b1..e7b51acf2 100644 --- a/blockdag/validate.go +++ b/blockdag/validate.go @@ -333,7 +333,7 @@ func checkProofOfWork(header *wire.BlockHeader, powLimit *big.Int, flags Behavio if flags&BFNoPoWCheck != BFNoPoWCheck { // The block hash must be less than the claimed target. hash := header.BlockHash() - hashNum := HashToBig(&hash) + hashNum := daghash.HashToBig(&hash) if hashNum.Cmp(target) > 0 { str := fmt.Sprintf("block hash of %064x is higher than "+ "expected max of %064x", hashNum, target) @@ -1202,6 +1202,6 @@ func (b *BlockDAG) CheckConnectBlockTemplate(block *btcutil.Block) error { // is not needed and thus extra work can be avoided. view := NewUtxoViewpoint() view.SetTips(tips) - newNode := newBlockNode(&header, b.dag.Tips()) + newNode := newBlockNode(&header, b.dag.Tips(), b.dagParams.K) return b.checkConnectBlock(newNode, block, view, nil) } diff --git a/dagconfig/daghash/hash.go b/dagconfig/daghash/hash.go index 2cba65501..fa35c88b8 100644 --- a/dagconfig/daghash/hash.go +++ b/dagconfig/daghash/hash.go @@ -8,6 +8,7 @@ package daghash import ( "encoding/hex" "fmt" + "math/big" ) // HashSize of array used to store hashes. See Hash. @@ -156,3 +157,32 @@ func Decode(dst *Hash, src string) error { return nil } + +// HashToBig converts a daghash.Hash into a big.Int that can be used to +// perform math comparisons. +func HashToBig(hash *Hash) *big.Int { + // A Hash is in little-endian, but the big package wants the bytes in + // big-endian, so reverse them. + buf := *hash + blen := len(buf) + for i := 0; i < blen/2; i++ { + buf[i], buf[blen-1-i] = buf[blen-1-i], buf[i] + } + + return new(big.Int).SetBytes(buf[:]) +} + +// Cmp compares hash and target and returns: +// +// -1 if hash < target +// 0 if hash == target +// +1 if hash > target +// +func (hash *Hash) Cmp(target *Hash) int { + return HashToBig(hash).Cmp(HashToBig(target)) +} + +//Less returns true iff hash b is less than hash a +func Less(a *Hash, b *Hash) bool { + return a.Cmp(b) > 0 +} diff --git a/dagconfig/params.go b/dagconfig/params.go index 3e95882a1..b9f0d2f4b 100644 --- a/dagconfig/params.go +++ b/dagconfig/params.go @@ -41,6 +41,8 @@ var ( simNetPowLimit = new(big.Int).Sub(new(big.Int).Lsh(bigOne, 255), bigOne) ) +const phantomK = 10 + // Checkpoint identifies a known good point in the block chain. Using // checkpoints allows a few optimizations for old blocks during initial download // and also prevents forks from old blocks. @@ -149,6 +151,8 @@ func (prefix Bech32Prefix) String() string { // used by Bitcoin applications to differentiate networks as well as addresses // and keys for one network from those intended for use on another network. type Params struct { + K uint32 + // Name defines a human-readable identifier for the network. Name string @@ -259,6 +263,7 @@ type Params struct { // MainNetParams defines the network parameters for the main Bitcoin network. var MainNetParams = Params{ + K: phantomK, Name: "mainnet", Net: wire.MainNet, DefaultPort: "8333", @@ -345,6 +350,7 @@ var MainNetParams = Params{ // Bitcoin network. Not to be confused with the test Bitcoin network (version // 3), this network is sometimes simply called "testnet". var RegressionNetParams = Params{ + K: phantomK, Name: "regtest", Net: wire.TestNet, DefaultPort: "18444", @@ -405,6 +411,7 @@ var RegressionNetParams = Params{ // (version 3). Not to be confused with the regression test network, this // network is sometimes simply called "testnet". var TestNet3Params = Params{ + K: phantomK, Name: "testnet3", Net: wire.TestNet3, DefaultPort: "18333", @@ -486,6 +493,7 @@ var TestNet3Params = Params{ // following normal discovery rules. This is important as otherwise it would // just turn into another public testnet. var SimNetParams = Params{ + K: phantomK, Name: "simnet", Net: wire.SimNet, DefaultPort: "18555", diff --git a/integration/rpctest/blockgen.go b/integration/rpctest/blockgen.go index 5b98daf5f..b2aef938d 100644 --- a/integration/rpctest/blockgen.go +++ b/integration/rpctest/blockgen.go @@ -44,7 +44,7 @@ func solveBlock(header *wire.BlockHeader, targetDifficulty *big.Int) bool { default: hdr.Nonce = i hash := hdr.BlockHash() - if blockdag.HashToBig(&hash).Cmp(targetDifficulty) <= 0 { + if daghash.HashToBig(&hash).Cmp(targetDifficulty) <= 0 { select { case results <- sbResult{true, i}: return diff --git a/mining/cpuminer/cpuminer.go b/mining/cpuminer/cpuminer.go index 8fdc0e6f6..73c8007c9 100644 --- a/mining/cpuminer/cpuminer.go +++ b/mining/cpuminer/cpuminer.go @@ -278,7 +278,7 @@ func (m *CPUMiner) solveBlock(msgBlock *wire.MsgBlock, blockHeight int32, // The block is solved when the new block hash is less // than the target difficulty. Yay! - if blockdag.HashToBig(&hash).Cmp(targetDifficulty) <= 0 { + if daghash.HashToBig(&hash).Cmp(targetDifficulty) <= 0 { m.updateHashes <- hashesCompleted return true } diff --git a/wire/blockheader.go b/wire/blockheader.go index 4e9141e57..5fd74a8ae 100644 --- a/wire/blockheader.go +++ b/wire/blockheader.go @@ -43,8 +43,7 @@ type BlockHeader struct { // Merkle tree reference to hash of all transactions for the block. MerkleRoot daghash.Hash - // Time the block was created. This is, unfortunately, encoded as a - // uint32 on the wire and therefore is limited to 2106. + // Time the block was created. Timestamp time.Time // Difficulty target for the block.