mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-03-30 15:08:33 +00:00
[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:
parent
2068ac299a
commit
904f2cf2e3
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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 {
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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 {
|
||||
|
@ -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.
|
||||
|
@ -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
891
blockdag/phanom_test.go
Normal 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
105
blockdag/phantom.go
Normal 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)
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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.
|
||||
|
Loading…
x
Reference in New Issue
Block a user