[DEV-72] Write Blues()

* [DEV-62] add phantom constructs to blocknode

* [DEV-62] add phantom constructs to blocknode

* [DEV-72] write blues()

* [DEV-72] write blues()

* [DEV-72] write blues()

* [DEV-62] add comments to new phantom constructs in blocknode

* Fixed dbIndexConnectBlock. (#33)

* Fixed dbIndexConnectBlock.

* Removed redundant check in storeFilter.

* Created a new method to BlockHeader: IsGenesis.

* [DEV-71] Implement BlockHeap (#35)

* [DEV-71] Implemented BlockHeap.

* [DEV-71] Removed irrelevant comment.

* [DEV-71] Renamed variables in Pop() and split Less() to multiple lines.

* [DEV-72] write blues()

* [DEV-72] write blues()

* [DEV-72] write blues()

* [DEV-72] write blues tests

* [DEV-72] write blues tests

* [DEV-72] remove relevant past

* [DEV-72] write blues tests

* [DEV-72] write blues tests

* [DEV-72] write blues tests

* [DEV-72] write functions to order blockSet by hash and write blue tests

* [DEV-72] add secret mining and censorship attack tests

* [DEV-72] remove prints

* [DEV-72] remove K from dagconfig.Params

* [DEV-72] remove K from dagconfig.Params

* [DEV-72] change blueScore to uint64

* [DEV-72] block V was missing, so renamed w -> v, x -> w etc

* [DEV-72] use node.String instead of %v

* [DEV-72] block V was missing, so renamed w -> v, x -> w etc

* [DEV-72] add K to dagconfig.Params, and add expected reds to all phantom tests

* [DEV-72] set K=10 and add comments to phantom and phantom tests

* [DEV-72] fix formatting and add comments to TestPhantom

* [DEV-72] fix grammar
This commit is contained in:
Ori Newman 2018-08-02 16:34:40 +03:00 committed by stasatdaglabs
parent 2068ac299a
commit 904f2cf2e3
19 changed files with 1134 additions and 89 deletions

View File

@ -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)

View File

@ -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

View File

@ -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 {

View File

@ -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

View File

@ -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
}

View File

@ -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(&params.GenesisBlock.Header, newSet())
node := newBlockNode(&params.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)
}

View File

@ -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 {

View File

@ -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)

View File

@ -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 {

View File

@ -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.

View File

@ -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
}

891
blockdag/phanom_test.go Normal file
View File

@ -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
}

105
blockdag/phantom.go Normal file
View File

@ -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)
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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",

View File

@ -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

View File

@ -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
}

View File

@ -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.