diff --git a/blockdag/blockindex.go b/blockdag/blockindex.go index e84e2f54f..0a7f9c603 100644 --- a/blockdag/blockindex.go +++ b/blockdag/blockindex.go @@ -5,242 +5,13 @@ package blockdag import ( - "fmt" - "math/big" - "sort" "sync" - "time" "github.com/daglabs/btcd/dagconfig" "github.com/daglabs/btcd/dagconfig/daghash" "github.com/daglabs/btcd/database" - "github.com/daglabs/btcd/wire" ) -// blockStatus is a bit field representing the validation state of the block. -type blockStatus byte - -const ( - // statusDataStored indicates that the block's payload is stored on disk. - statusDataStored blockStatus = 1 << iota - - // statusValid indicates that the block has been fully validated. - statusValid - - // statusValidateFailed indicates that the block has failed validation. - statusValidateFailed - - // statusInvalidAncestor indicates that one of the block's ancestors has - // has failed validation, thus the block is also invalid. - statusInvalidAncestor - - // statusNone indicates that the block has no validation state flags set. - // - // NOTE: This must be defined last in order to avoid influencing iota. - statusNone blockStatus = 0 -) - -// KnownValid returns whether the block is known to be valid. This will return -// false for a valid block that has not been fully validated yet. -func (status blockStatus) KnownValid() bool { - return status&statusValid != 0 -} - -// KnownInvalid returns whether the block is known to be invalid. This may be -// because the block itself failed validation or any of its ancestors is -// invalid. This will return false for invalid blocks that have not been proven -// invalid yet. -func (status blockStatus) KnownInvalid() bool { - return status&(statusValidateFailed|statusInvalidAncestor) != 0 -} - -// blockNode represents a block within the block DAG. The DAG is stored into -// the block database. -type blockNode struct { - // NOTE: Additions, deletions, or modifications to the order of the - // definitions in this struct should not be changed without considering - // how it affects alignment on 64-bit platforms. The current order is - // specifically crafted to result in minimal padding. There will be - // hundreds of thousands of these in memory, so a few extra bytes of - // padding adds up. - - // parents is the parent blocks for this node. - parents blockSet - - // selectedParent is the selected parent for this node. - // The selected parent is the parent that if chosen will maximize the blue score of this block - selectedParent *blockNode - - // children are all the blocks that refer to this block as a parent - children blockSet - - // blues are all blue blocks in this block's worldview that are in its selected parent anticone - blues []*blockNode - - // blueScore is the count of all the blue blocks in this block's past - blueScore uint64 - - // diff is the UTXO representation of the block - // A block's UTXO is reconstituted by applying diffWith on every block in the chain of diffChildren - // from the virtual block down to the block. See diffChild - diff *UTXODiff - - // diffChild is the child that diff will be built from. See diff - diffChild *blockNode - - // hash is the double sha 256 of the block. - hash daghash.Hash - - // workSum is the total amount of work in the DAG up to and including - // this node. - workSum *big.Int - - // height is the position in the block DAG. - height int32 - - // Some fields from block headers to aid in best chain selection and - // reconstructing headers from memory. These must be treated as - // immutable and are intentionally ordered to avoid padding on 64-bit - // platforms. - version int32 - bits uint32 - nonce uint64 - timestamp int64 - merkleRoot daghash.Hash - - // status is a bitfield representing the validation state of the block. The - // status field, unlike the other fields, may be written to and so should - // only be accessed using the concurrent-safe NodeStatus method on - // blockIndex once the node has been added to the global index. - status blockStatus -} - -// initBlockNode initializes a block node from the given header and parent nodes, -// 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, phantomK uint32) { - *node = blockNode{ - parents: parents, - children: make(blockSet), - workSum: big.NewInt(0), - timestamp: time.Now().Unix(), - } - - if blockHeader != nil { - node.hash = blockHeader.BlockHash() - node.workSum = CalcWork(blockHeader.Bits) - node.version = blockHeader.Version - node.bits = blockHeader.Bits - node.nonce = blockHeader.Nonce - node.timestamp = blockHeader.Timestamp.Unix() - node.merkleRoot = blockHeader.MerkleRoot - } - - if len(parents) > 0 { - node.blues, node.selectedParent, node.blueScore = phantom(node, phantomK) - node.height = calculateNodeHeight(node) - node.workSum = node.workSum.Add(node.selectedParent.workSum, node.workSum) - } -} - -func calculateNodeHeight(node *blockNode) int32 { - return node.parents.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, phantomK uint32) *blockNode { - var node blockNode - initBlockNode(&node, blockHeader, parents, phantomK) - return &node -} - -// Header constructs a block header from the node and returns it. -// -// This function is safe for concurrent access. -func (node *blockNode) Header() *wire.BlockHeader { - // No lock is needed because all accessed fields are immutable. - return &wire.BlockHeader{ - Version: node.version, - ParentHashes: node.ParentHashes(), - MerkleRoot: node.merkleRoot, - Timestamp: time.Unix(node.timestamp, 0), - Bits: node.bits, - Nonce: node.nonce, - } -} - -// Ancestor returns the ancestor block node at the provided height by following -// the chain backwards from this node. The returned block will be nil when a -// height is requested that is after the height of the passed node or is less -// than zero. -// -// This function is safe for concurrent access. -func (node *blockNode) Ancestor(height int32) *blockNode { - if height < 0 || height > node.height { - return nil - } - - n := node - for ; n != nil && n.height != height; n = n.selectedParent { - // Intentionally left blank - } - - return n -} - -// RelativeAncestor returns the ancestor block node a relative 'distance' blocks -// before this node. This is equivalent to calling Ancestor with the node's -// height minus provided distance. -// -// This function is safe for concurrent access. -func (node *blockNode) RelativeAncestor(distance int32) *blockNode { - return node.Ancestor(node.height - distance) -} - -// CalcPastMedianTime calculates the median time of the previous few blocks -// prior to, and including, the block node. -// -// This function is safe for concurrent access. -func (node *blockNode) CalcPastMedianTime() time.Time { - // Create a slice of the previous few block timestamps used to calculate - // the median per the number defined by the constant medianTimeBlocks. - // If there aren't enough blocks yet - pad remaining with genesis block's timestamp. - timestamps := make([]int64, medianTimeBlocks) - iterNode := node - for i := 0; i < medianTimeBlocks; i++ { - timestamps[i] = iterNode.timestamp - - if !iterNode.isGenesis() { - iterNode = iterNode.selectedParent - } - } - - sort.Sort(timeSorter(timestamps)) - - // Note: This works when medianTimeBlockCount is an odd number. - // If it is to be changed to an even number - must take avarage of two middle values - // Since medianTimeBlockCount is a constant, we can skip the odd/even check - medianTimestamp := timestamps[medianTimeBlocks/2] - return time.Unix(medianTimestamp, 0) -} - -func (node *blockNode) ParentHashes() []daghash.Hash { - return node.parents.hashes() -} - -// 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 // block chain. Although the name block chain suggests a single chain of // blocks, it is actually a tree-shaped structure where any node can have diff --git a/blockdag/blocknode.go b/blockdag/blocknode.go new file mode 100644 index 000000000..a7c5bab28 --- /dev/null +++ b/blockdag/blocknode.go @@ -0,0 +1,250 @@ +// Copyright (c) 2015-2017 The btcsuite developers +// Use of this source code is governed by an ISC +// license that can be found in the LICENSE file. + +package blockdag + +import ( + "fmt" + "math/big" + "sort" + "time" + + "github.com/daglabs/btcd/dagconfig/daghash" + "github.com/daglabs/btcd/wire" +) + +// blockStatus is a bit field representing the validation state of the block. +type blockStatus byte + +const ( + // statusDataStored indicates that the block's payload is stored on disk. + statusDataStored blockStatus = 1 << iota + + // statusValid indicates that the block has been fully validated. + statusValid + + // statusValidateFailed indicates that the block has failed validation. + statusValidateFailed + + // statusInvalidAncestor indicates that one of the block's ancestors has + // has failed validation, thus the block is also invalid. + statusInvalidAncestor + + // statusNone indicates that the block has no validation state flags set. + // + // NOTE: This must be defined last in order to avoid influencing iota. + statusNone blockStatus = 0 +) + +// KnownValid returns whether the block is known to be valid. This will return +// false for a valid block that has not been fully validated yet. +func (status blockStatus) KnownValid() bool { + return status&statusValid != 0 +} + +// KnownInvalid returns whether the block is known to be invalid. This may be +// because the block itself failed validation or any of its ancestors is +// invalid. This will return false for invalid blocks that have not been proven +// invalid yet. +func (status blockStatus) KnownInvalid() bool { + return status&(statusValidateFailed|statusInvalidAncestor) != 0 +} + +// blockNode represents a block within the block DAG. The DAG is stored into +// the block database. +type blockNode struct { + // NOTE: Additions, deletions, or modifications to the order of the + // definitions in this struct should not be changed without considering + // how it affects alignment on 64-bit platforms. The current order is + // specifically crafted to result in minimal padding. There will be + // hundreds of thousands of these in memory, so a few extra bytes of + // padding adds up. + + // parents is the parent blocks for this node. + parents blockSet + + // selectedParent is the selected parent for this node. + // The selected parent is the parent that if chosen will maximize the blue score of this block + selectedParent *blockNode + + // children are all the blocks that refer to this block as a parent + children blockSet + + // blues are all blue blocks in this block's worldview that are in its selected parent anticone + blues []*blockNode + + // blueScore is the count of all the blue blocks in this block's past + blueScore uint64 + + // diff is the UTXO representation of the block + // A block's UTXO is reconstituted by applying diffWith on every block in the chain of diffChildren + // from the virtual block down to the block. See diffChild + diff *UTXODiff + + // diffChild is the child that diff will be built from. See diff + diffChild *blockNode + + // hash is the double sha 256 of the block. + hash daghash.Hash + + // workSum is the total amount of work in the DAG up to and including + // this node. + workSum *big.Int + + // height is the position in the block DAG. + height int32 + + // chainHeight is the number of hops you need to go down the selected parent chain in order to get to the genesis block. + chainHeight uint32 + + // Some fields from block headers to aid in best chain selection and + // reconstructing headers from memory. These must be treated as + // immutable and are intentionally ordered to avoid padding on 64-bit + // platforms. + version int32 + bits uint32 + nonce uint64 + timestamp int64 + merkleRoot daghash.Hash + + // status is a bitfield representing the validation state of the block. The + // status field, unlike the other fields, may be written to and so should + // only be accessed using the concurrent-safe NodeStatus method on + // blockIndex once the node has been added to the global index. + status blockStatus +} + +// initBlockNode initializes a block node from the given header and parent nodes, +// 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, phantomK uint32) { + *node = blockNode{ + parents: parents, + children: make(blockSet), + workSum: big.NewInt(0), + timestamp: time.Now().Unix(), + } + + if blockHeader != nil { + node.hash = blockHeader.BlockHash() + node.workSum = CalcWork(blockHeader.Bits) + node.version = blockHeader.Version + node.bits = blockHeader.Bits + node.nonce = blockHeader.Nonce + node.timestamp = blockHeader.Timestamp.Unix() + node.merkleRoot = blockHeader.MerkleRoot + } + + if len(parents) > 0 { + node.blues, node.selectedParent, node.blueScore = phantom(node, phantomK) + node.height = calculateNodeHeight(node) + node.chainHeight = calculateChainHeight(node) + node.workSum = node.workSum.Add(node.selectedParent.workSum, node.workSum) + } +} + +func calculateNodeHeight(node *blockNode) int32 { + return node.parents.maxHeight() + 1 +} + +func calculateChainHeight(node *blockNode) uint32 { + if node.isGenesis() { + return 0 + } + return node.selectedParent.chainHeight + 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, phantomK uint32) *blockNode { + var node blockNode + initBlockNode(&node, blockHeader, parents, phantomK) + return &node +} + +// Header constructs a block header from the node and returns it. +// +// This function is safe for concurrent access. +func (node *blockNode) Header() *wire.BlockHeader { + // No lock is needed because all accessed fields are immutable. + return &wire.BlockHeader{ + Version: node.version, + ParentHashes: node.ParentHashes(), + MerkleRoot: node.merkleRoot, + Timestamp: time.Unix(node.timestamp, 0), + Bits: node.bits, + Nonce: node.nonce, + } +} + +// Ancestor returns the ancestor block node at the provided height by following +// the chain backwards from this node. The returned block will be nil when a +// height is requested that is after the height of the passed node or is less +// than zero. +// +// This function is safe for concurrent access. +func (node *blockNode) Ancestor(height int32) *blockNode { + if height < 0 || height > node.height { + return nil + } + + n := node + for ; n != nil && n.height != height; n = n.selectedParent { + // Intentionally left blank + } + + return n +} + +// RelativeAncestor returns the ancestor block node a relative 'distance' blocks +// before this node. This is equivalent to calling Ancestor with the node's +// height minus provided distance. +// +// This function is safe for concurrent access. +func (node *blockNode) RelativeAncestor(distance int32) *blockNode { + return node.Ancestor(node.height - distance) +} + +// CalcPastMedianTime calculates the median time of the previous few blocks +// prior to, and including, the block node. +// +// This function is safe for concurrent access. +func (node *blockNode) CalcPastMedianTime() time.Time { + // Create a slice of the previous few block timestamps used to calculate + // the median per the number defined by the constant medianTimeBlocks. + // If there aren't enough blocks yet - pad remaining with genesis block's timestamp. + timestamps := make([]int64, medianTimeBlocks) + iterNode := node + for i := 0; i < medianTimeBlocks; i++ { + timestamps[i] = iterNode.timestamp + + if !iterNode.isGenesis() { + iterNode = iterNode.selectedParent + } + } + + sort.Sort(timeSorter(timestamps)) + + // Note: This works when medianTimeBlockCount is an odd number. + // If it is to be changed to an even number - must take avarage of two middle values + // Since medianTimeBlockCount is a constant, we can skip the odd/even check + medianTimestamp := timestamps[medianTimeBlocks/2] + return time.Unix(medianTimestamp, 0) +} + +func (node *blockNode) ParentHashes() []daghash.Hash { + return node.parents.hashes() +} + +// 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) +} diff --git a/blockdag/blocknode_test.go b/blockdag/blocknode_test.go new file mode 100644 index 000000000..d84361717 --- /dev/null +++ b/blockdag/blocknode_test.go @@ -0,0 +1,88 @@ +package blockdag + +import ( + "testing" +) + +func TestChainHeight(t *testing.T) { + phantomK := uint32(2) + buildNode := buildNodeGenerator(phantomK) + buildWithChildren := func(parents blockSet) *blockNode { + node := buildNode(parents) + addNodeAsChildToParents(node) + return node + } + + node0 := buildWithChildren(setFromSlice()) + node1 := buildWithChildren(setFromSlice(node0)) + node2 := buildWithChildren(setFromSlice(node0)) + node3 := buildWithChildren(setFromSlice(node0)) + node4 := buildWithChildren(setFromSlice(node1, node2, node3)) + node5 := buildWithChildren(setFromSlice(node1, node2, node3)) + node6 := buildWithChildren(setFromSlice(node1, node2, node3)) + node7 := buildWithChildren(setFromSlice(node0)) + node8 := buildWithChildren(setFromSlice(node7)) + node9 := buildWithChildren(setFromSlice(node8)) + node10 := buildWithChildren(setFromSlice(node9, node6)) + + // Because nodes 7 & 8 were mined secretly, node10's selected + // parent will be node6, although node9 is higher. So in this + // case, node10.height and node10.chainHeight will be different + + tests := []struct { + node *blockNode + expectedChainHeight uint32 + }{ + { + node: node0, + expectedChainHeight: 0, + }, + { + node: node1, + expectedChainHeight: 1, + }, + { + node: node2, + expectedChainHeight: 1, + }, + { + node: node3, + expectedChainHeight: 1, + }, + { + node: node4, + expectedChainHeight: 2, + }, + { + node: node5, + expectedChainHeight: 2, + }, + { + node: node6, + expectedChainHeight: 2, + }, + { + node: node7, + expectedChainHeight: 1, + }, + { + node: node8, + expectedChainHeight: 2, + }, + { + node: node9, + expectedChainHeight: 3, + }, + { + node: node10, + expectedChainHeight: 3, + }, + } + + for _, test := range tests { + if test.node.chainHeight != test.expectedChainHeight { + t.Errorf("block %v expected chain height %v but got %v", test.node, test.expectedChainHeight, test.node.chainHeight) + } + } + +} diff --git a/blockdag/common_test.go b/blockdag/common_test.go index d6394686c..339d993d1 100644 --- a/blockdag/common_test.go +++ b/blockdag/common_test.go @@ -199,3 +199,16 @@ func addNodeAsChildToParents(node *blockNode) { parent.children.add(node) } } + +func buildNodeGenerator(phantomK uint32) func(parents blockSet) *blockNode { + // For the purposes of these tests, we'll create blockNodes whose hashes are a + // series of numbers from 0 to n. + hashCounter := byte(0) + return func(parents blockSet) *blockNode { + block := newBlockNode(nil, parents, phantomK) + block.hash = daghash.Hash{hashCounter} + hashCounter++ + + return block + } +} diff --git a/blockdag/virtualblock_test.go b/blockdag/virtualblock_test.go index 4b00048db..545994cc9 100644 --- a/blockdag/virtualblock_test.go +++ b/blockdag/virtualblock_test.go @@ -7,23 +7,8 @@ package blockdag import ( "reflect" "testing" - - "github.com/daglabs/btcd/dagconfig/daghash" ) -func buildNodeGenerator(phantomK uint32) func(parents blockSet) *blockNode { - // For the purposes of these tests, we'll create blockNodes whose hashes are a - // series of numbers from 0 to n. - hashCounter := byte(0) - return func(parents blockSet) *blockNode { - block := newBlockNode(nil, parents, phantomK) - block.hash = daghash.Hash{hashCounter} - hashCounter++ - - return block - } -} - // TestVirtualBlock ensures that VirtualBlock works as expected. func TestVirtualBlock(t *testing.T) { phantomK := uint32(1)