mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-07-03 11:22:30 +00:00
[DEV-303] Implement block finality (#139)
* [DEV-303] Implement block finality * [DEV-303] Add finality tests * [DEV-303] Make finality tests use maybeAcceptBlock * [DEV-303] Only check finality rules if we are fastAdd mode * [DEV-303] replace hasBiggerFinalityScoreThan checks with .finalityScore()
This commit is contained in:
parent
f0f79b1fcc
commit
c0aafdf7e1
@ -244,6 +244,10 @@ func (node *blockNode) isGenesis() bool {
|
|||||||
return len(node.parents) == 0
|
return len(node.parents) == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (node *blockNode) finalityScore() uint64 {
|
||||||
|
return node.blueScore / finalityInterval
|
||||||
|
}
|
||||||
|
|
||||||
// String returns a string that contains the block hash and height.
|
// String returns a string that contains the block hash and height.
|
||||||
func (node blockNode) String() string {
|
func (node blockNode) String() string {
|
||||||
return fmt.Sprintf("%s (%d)", node.hash, node.height)
|
return fmt.Sprintf("%s (%d)", node.hash, node.height)
|
||||||
|
@ -6,24 +6,19 @@ import (
|
|||||||
|
|
||||||
func TestChainHeight(t *testing.T) {
|
func TestChainHeight(t *testing.T) {
|
||||||
phantomK := uint32(2)
|
phantomK := uint32(2)
|
||||||
buildNode := buildNodeGenerator(phantomK)
|
buildNode := buildNodeGenerator(phantomK, true)
|
||||||
buildWithChildren := func(parents blockSet) *blockNode {
|
|
||||||
node := buildNode(parents)
|
|
||||||
addNodeAsChildToParents(node)
|
|
||||||
return node
|
|
||||||
}
|
|
||||||
|
|
||||||
node0 := buildWithChildren(setFromSlice())
|
node0 := buildNode(setFromSlice())
|
||||||
node1 := buildWithChildren(setFromSlice(node0))
|
node1 := buildNode(setFromSlice(node0))
|
||||||
node2 := buildWithChildren(setFromSlice(node0))
|
node2 := buildNode(setFromSlice(node0))
|
||||||
node3 := buildWithChildren(setFromSlice(node0))
|
node3 := buildNode(setFromSlice(node0))
|
||||||
node4 := buildWithChildren(setFromSlice(node1, node2, node3))
|
node4 := buildNode(setFromSlice(node1, node2, node3))
|
||||||
node5 := buildWithChildren(setFromSlice(node1, node2, node3))
|
node5 := buildNode(setFromSlice(node1, node2, node3))
|
||||||
node6 := buildWithChildren(setFromSlice(node1, node2, node3))
|
node6 := buildNode(setFromSlice(node1, node2, node3))
|
||||||
node7 := buildWithChildren(setFromSlice(node0))
|
node7 := buildNode(setFromSlice(node0))
|
||||||
node8 := buildWithChildren(setFromSlice(node7))
|
node8 := buildNode(setFromSlice(node7))
|
||||||
node9 := buildWithChildren(setFromSlice(node8))
|
node9 := buildNode(setFromSlice(node8))
|
||||||
node10 := buildWithChildren(setFromSlice(node9, node6))
|
node10 := buildNode(setFromSlice(node9, node6))
|
||||||
|
|
||||||
// Because nodes 7 & 8 were mined secretly, node10's selected
|
// Because nodes 7 & 8 were mined secretly, node10's selected
|
||||||
// parent will be node6, although node9 is higher. So in this
|
// parent will be node6, although node9 is higher. So in this
|
||||||
|
@ -200,15 +200,23 @@ func addNodeAsChildToParents(node *blockNode) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func buildNodeGenerator(phantomK uint32) func(parents blockSet) *blockNode {
|
func buildNodeGenerator(phantomK uint32, withChildren bool) func(parents blockSet) *blockNode {
|
||||||
// For the purposes of these tests, we'll create blockNodes whose hashes are a
|
// For the purposes of these tests, we'll create blockNodes whose hashes are a
|
||||||
// series of numbers from 0 to n.
|
// series of numbers from 1 to 255.
|
||||||
hashCounter := byte(0)
|
hashCounter := byte(1)
|
||||||
return func(parents blockSet) *blockNode {
|
buildNode := func(parents blockSet) *blockNode {
|
||||||
block := newBlockNode(nil, parents, phantomK)
|
block := newBlockNode(nil, parents, phantomK)
|
||||||
block.hash = daghash.Hash{hashCounter}
|
block.hash = daghash.Hash{hashCounter}
|
||||||
hashCounter++
|
hashCounter++
|
||||||
|
|
||||||
return block
|
return block
|
||||||
}
|
}
|
||||||
|
if withChildren {
|
||||||
|
return func(parents blockSet) *blockNode {
|
||||||
|
node := buildNode(parents)
|
||||||
|
addNodeAsChildToParents(node)
|
||||||
|
return node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return buildNode
|
||||||
}
|
}
|
||||||
|
@ -23,6 +23,8 @@ const (
|
|||||||
// maxOrphanBlocks is the maximum number of orphan blocks that can be
|
// maxOrphanBlocks is the maximum number of orphan blocks that can be
|
||||||
// queued.
|
// queued.
|
||||||
maxOrphanBlocks = 100
|
maxOrphanBlocks = 100
|
||||||
|
|
||||||
|
finalityInterval = 100
|
||||||
)
|
)
|
||||||
|
|
||||||
// BlockLocator is used to help locate a specific block. The algorithm for
|
// BlockLocator is used to help locate a specific block. The algorithm for
|
||||||
@ -138,6 +140,8 @@ type BlockDAG struct {
|
|||||||
// certain blockchain events.
|
// certain blockchain events.
|
||||||
notificationsLock sync.RWMutex
|
notificationsLock sync.RWMutex
|
||||||
notifications []NotificationCallback
|
notifications []NotificationCallback
|
||||||
|
|
||||||
|
lastFinalityPoint *blockNode
|
||||||
}
|
}
|
||||||
|
|
||||||
// HaveBlock returns whether or not the DAG instance has the block represented
|
// HaveBlock returns whether or not the DAG instance has the block represented
|
||||||
@ -503,12 +507,26 @@ func (dag *BlockDAG) connectBlock(node *blockNode, block *util.Block, fastAdd bo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var finalityPointCandidate *blockNode
|
||||||
|
|
||||||
|
if !fastAdd {
|
||||||
|
var err error
|
||||||
|
finalityPointCandidate, err = dag.checkFinalityRulesAndGetFinalityPointCandidate(node)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add the node to the virtual and update the UTXO set of the DAG.
|
// Add the node to the virtual and update the UTXO set of the DAG.
|
||||||
utxoDiff, acceptedTxsData, err := dag.applyUTXOChanges(node, block, fastAdd)
|
utxoDiff, acceptedTxsData, err := dag.applyUTXOChanges(node, block, fastAdd)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if finalityPointCandidate != nil {
|
||||||
|
dag.lastFinalityPoint = finalityPointCandidate
|
||||||
|
}
|
||||||
|
|
||||||
// Write any block status changes to DB before updating the DAG state.
|
// Write any block status changes to DB before updating the DAG state.
|
||||||
err = dag.index.flushToDB()
|
err = dag.index.flushToDB()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -518,7 +536,11 @@ func (dag *BlockDAG) connectBlock(node *blockNode, block *util.Block, fastAdd bo
|
|||||||
// Atomically insert info into the database.
|
// Atomically insert info into the database.
|
||||||
err = dag.db.Update(func(dbTx database.Tx) error {
|
err = dag.db.Update(func(dbTx database.Tx) error {
|
||||||
// Update best block state.
|
// Update best block state.
|
||||||
err := dbPutDAGTipHashes(dbTx, dag.TipHashes())
|
state := &dagState{
|
||||||
|
TipHashes: dag.TipHashes(),
|
||||||
|
LastFinalityPoint: dag.lastFinalityPoint.hash,
|
||||||
|
}
|
||||||
|
err := dbPutDAGState(dbTx, state)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -563,6 +585,27 @@ func (dag *BlockDAG) connectBlock(node *blockNode, block *util.Block, fastAdd bo
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (dag *BlockDAG) checkFinalityRulesAndGetFinalityPointCandidate(node *blockNode) (*blockNode, error) {
|
||||||
|
var finalityPointCandidate *blockNode
|
||||||
|
finalityErr := ruleError(ErrFinality, "The last finality point is not in the selected chain of this block")
|
||||||
|
|
||||||
|
if node.blueScore <= dag.lastFinalityPoint.blueScore {
|
||||||
|
return nil, finalityErr
|
||||||
|
}
|
||||||
|
|
||||||
|
shouldFindFinalityPointCandidate := node.finalityScore() > dag.lastFinalityPoint.finalityScore()
|
||||||
|
|
||||||
|
for currentNode := node.selectedParent; currentNode != dag.lastFinalityPoint; currentNode = currentNode.selectedParent {
|
||||||
|
if currentNode.blueScore <= dag.lastFinalityPoint.blueScore {
|
||||||
|
return nil, finalityErr
|
||||||
|
}
|
||||||
|
if shouldFindFinalityPointCandidate && currentNode.finalityScore() > currentNode.selectedParent.finalityScore() {
|
||||||
|
finalityPointCandidate = currentNode
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return finalityPointCandidate, nil
|
||||||
|
}
|
||||||
|
|
||||||
// applyUTXOChanges does the following:
|
// applyUTXOChanges does the following:
|
||||||
// 1. Verifies that each transaction within the new block could spend an existing UTXO.
|
// 1. Verifies that each transaction within the new block could spend an existing UTXO.
|
||||||
// 2. Connects each of the new block's parents to the block.
|
// 2. Connects each of the new block's parents to the block.
|
||||||
@ -1528,7 +1571,7 @@ func New(config *Config) (*BlockDAG, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Initialize the chain state from the passed database. When the db
|
// Initialize the chain state from the passed database. When the db
|
||||||
// does not yet contain any chain state, both it and the chain state
|
// does not yet contain any DAG state, both it and the DAG state
|
||||||
// will be initialized to contain only the genesis block.
|
// will be initialized to contain only the genesis block.
|
||||||
if err := dag.initDAGState(); err != nil {
|
if err := dag.initDAGState(); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
@ -40,16 +40,16 @@ func TestBlockCount(t *testing.T) {
|
|||||||
blocks = append(blocks, blockTmp...)
|
blocks = append(blocks, blockTmp...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new database and chain instance to run tests against.
|
// Create a new database and DAG instance to run tests against.
|
||||||
dag, teardownFunc, err := DAGSetup("haveblock", Config{
|
dag, teardownFunc, err := DAGSetup("haveblock", Config{
|
||||||
DAGParams: &dagconfig.SimNetParams,
|
DAGParams: &dagconfig.SimNetParams,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to setup chain instance: %v", err)
|
t.Fatalf("Failed to setup DAG instance: %v", err)
|
||||||
}
|
}
|
||||||
defer teardownFunc()
|
defer teardownFunc()
|
||||||
|
|
||||||
// Since we're not dealing with the real block chain, set the coinbase
|
// Since we're not dealing with the real block DAG, set the coinbase
|
||||||
// maturity to 1.
|
// maturity to 1.
|
||||||
dag.TstSetCoinbaseMaturity(1)
|
dag.TstSetCoinbaseMaturity(1)
|
||||||
|
|
||||||
@ -849,3 +849,132 @@ func testErrorThroughPatching(t *testing.T, expectedErrorMessage string, targetF
|
|||||||
"Want: %s, got: %s", expectedErrorMessage, err)
|
"Want: %s, got: %s", expectedErrorMessage, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestFinality checks that the finality mechanism works as expected.
|
||||||
|
// This is how the flow goes:
|
||||||
|
// 1) We build a chain of finalityInterval blocks and call its tip altChainTip.
|
||||||
|
// 2) We build another chain (let's call it mainChain) of 2 * finalityInterval
|
||||||
|
// blocks, which points to genesis, and then we check that the block in that
|
||||||
|
// chain with height of finalityInterval is marked as finality point (This is
|
||||||
|
// very predictable, because the blue score of each new block in a chain is the
|
||||||
|
// parents plus one).
|
||||||
|
// 3) We make a new child to block with height (2 * finalityInterval - 1)
|
||||||
|
// in mainChain, and we check that connecting it to the DAG
|
||||||
|
// doesn't affect the last finality point.
|
||||||
|
// 4) We make a block that points to genesis, and check that it
|
||||||
|
// gets rejected because its blue score is lower then the last finality
|
||||||
|
// point.
|
||||||
|
// 5) We make a block that points to altChainTip, and check that it
|
||||||
|
// gets rejected because it doesn't have the last finality point in
|
||||||
|
// its selected parent chain.
|
||||||
|
func TestFinality(t *testing.T) {
|
||||||
|
params := dagconfig.SimNetParams
|
||||||
|
params.K = 1
|
||||||
|
dag, teardownFunc, err := DAGSetup("TestFinality", Config{
|
||||||
|
DAGParams: ¶ms,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Failed to setup DAG instance: %v", err)
|
||||||
|
}
|
||||||
|
defer teardownFunc()
|
||||||
|
blockTime := time.Unix(dag.genesis.timestamp, 0)
|
||||||
|
extraNonce := int64(0)
|
||||||
|
buildNodeToDag := func(parents blockSet) (*blockNode, error) {
|
||||||
|
// We need to change the blockTime to keep all block hashes unique
|
||||||
|
blockTime = blockTime.Add(time.Second)
|
||||||
|
|
||||||
|
// We need to change the extraNonce to keep coinbase hashes unique
|
||||||
|
extraNonce++
|
||||||
|
|
||||||
|
bh := &wire.BlockHeader{
|
||||||
|
Version: 1,
|
||||||
|
Bits: dag.genesis.bits,
|
||||||
|
ParentHashes: parents.hashes(),
|
||||||
|
Timestamp: blockTime,
|
||||||
|
}
|
||||||
|
msgBlock := wire.NewMsgBlock(bh)
|
||||||
|
blockHeight := parents.maxHeight() + 1
|
||||||
|
coinbaseTx, err := createCoinbaseTx(blockHeight, 1, extraNonce, dag.dagParams)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
msgBlock.AddTransaction(coinbaseTx)
|
||||||
|
block := util.NewBlock(msgBlock)
|
||||||
|
|
||||||
|
dag.dagLock.Lock()
|
||||||
|
defer dag.dagLock.Unlock()
|
||||||
|
|
||||||
|
err = dag.maybeAcceptBlock(block, BFNone)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return dag.index.LookupNode(block.Hash()), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
currentNode := dag.genesis
|
||||||
|
|
||||||
|
// First we build a chain of finalityInterval blocks for future use
|
||||||
|
for currentNode.blueScore < finalityInterval {
|
||||||
|
currentNode, err = buildNodeToDag(setFromSlice(currentNode))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TestFinality: buildNodeToDag unexpectedly returned an error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
altChainTip := currentNode
|
||||||
|
|
||||||
|
// Now we build a new chain of 2 * finalityInterval blocks, pointed to genesis, and
|
||||||
|
// we expect the block with height 1 * finalityInterval to be the last finality point
|
||||||
|
currentNode = dag.genesis
|
||||||
|
for currentNode.blueScore < finalityInterval {
|
||||||
|
currentNode, err = buildNodeToDag(setFromSlice(currentNode))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TestFinality: buildNodeToDag unexpectedly returned an error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expectedFinalityPoint := currentNode
|
||||||
|
|
||||||
|
for currentNode.blueScore < 2*finalityInterval {
|
||||||
|
currentNode, err = buildNodeToDag(setFromSlice(currentNode))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TestFinality: buildNodeToDag unexpectedly returned an error: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if dag.lastFinalityPoint != expectedFinalityPoint {
|
||||||
|
t.Errorf("TestFinality: dag.lastFinalityPoint expected to be %v but got %v", expectedFinalityPoint, dag.lastFinalityPoint)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here we check that even if we create a parallel tip (a new tip with
|
||||||
|
// the same parents as the current one) with the same blue score as the
|
||||||
|
// current tip, it still won't affect the last finality point.
|
||||||
|
_, err = buildNodeToDag(setFromSlice(currentNode.selectedParent))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TestFinality: buildNodeToDag unexpectedly returned an error: %v", err)
|
||||||
|
}
|
||||||
|
if dag.lastFinalityPoint != expectedFinalityPoint {
|
||||||
|
t.Errorf("TestFinality: dag.lastFinalityPoint was unexpectly changed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here we check that a block with lower blue score than the last finality
|
||||||
|
// point will get rejected
|
||||||
|
nodeWithLowBlueScore, err := buildNodeToDag(setFromSlice(dag.genesis))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TestFinality: buildNodeToDag unexpectedly returned an error: %v", err)
|
||||||
|
}
|
||||||
|
if !dag.index.NodeStatus(nodeWithLowBlueScore).KnownInvalid() {
|
||||||
|
t.Errorf("TestFinality: nodeWithLowBlueScore was expected to be invalid, but got valid instead")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Here we check that a block that doesn't have the last finality point in
|
||||||
|
// its selected parent chain will get rejected
|
||||||
|
nodeWithFinalityPointInAnticone, err := buildNodeToDag(setFromSlice(altChainTip))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("TestFinality: buildNodeToDag unexpectedly returned an error: %v", err)
|
||||||
|
}
|
||||||
|
if !dag.index.NodeStatus(nodeWithFinalityPointInAnticone).KnownInvalid() {
|
||||||
|
t.Errorf("TestFinality: invalidNode was expected to be invalid, but got valid instead")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -41,9 +41,9 @@ var (
|
|||||||
// the block height -> block hash index.
|
// the block height -> block hash index.
|
||||||
heightIndexBucketName = []byte("heightidx")
|
heightIndexBucketName = []byte("heightidx")
|
||||||
|
|
||||||
// dagTipHashesKeyName is the name of the db key used to store the DAG
|
// dagStateKeyName is the name of the db key used to store the DAG
|
||||||
// tip hashes.
|
// tip hashes.
|
||||||
dagTipHashesKeyName = []byte("dagtiphashes")
|
dagStateKeyName = []byte("dagstate")
|
||||||
|
|
||||||
// utxoSetVersionKeyName is the name of the db key used to store the
|
// utxoSetVersionKeyName is the name of the db key used to store the
|
||||||
// version of the utxo set currently in the database.
|
// version of the utxo set currently in the database.
|
||||||
@ -697,38 +697,43 @@ func dbFetchHeightByHash(dbTx database.Tx, hash *daghash.Hash) (int32, error) {
|
|||||||
return int32(byteOrder.Uint32(serializedHeight)), nil
|
return int32(byteOrder.Uint32(serializedHeight)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// serializeDAGTipHashes returns the serialization of the DAG tip hashes.
|
type dagState struct {
|
||||||
// This is data to be stored in the DAG tip hashes bucket.
|
TipHashes []daghash.Hash
|
||||||
func serializeDAGTipHashes(tipHashes []daghash.Hash) ([]byte, error) {
|
LastFinalityPoint daghash.Hash
|
||||||
return json.Marshal(tipHashes)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// deserializeDAGTipHashes deserializes the passed serialized DAG tip hashes.
|
// serializeDAGState returns the serialization of the DAG state.
|
||||||
// This is data stored in the DAG tip hashes bucket and is updated after
|
// This is data to be stored in the DAG state bucket.
|
||||||
|
func serializeDAGState(state *dagState) ([]byte, error) {
|
||||||
|
return json.Marshal(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
// deserializeDAGState deserializes the passed serialized DAG state.
|
||||||
|
// This is data stored in the DAG state bucket and is updated after
|
||||||
// every block is connected to the DAG.
|
// every block is connected to the DAG.
|
||||||
func deserializeDAGTipHashes(serializedData []byte) ([]daghash.Hash, error) {
|
func deserializeDAGState(serializedData []byte) (*dagState, error) {
|
||||||
var tipHashes []daghash.Hash
|
var state *dagState
|
||||||
err := json.Unmarshal(serializedData, &tipHashes)
|
err := json.Unmarshal(serializedData, &state)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, database.Error{
|
return nil, database.Error{
|
||||||
ErrorCode: database.ErrCorruption,
|
ErrorCode: database.ErrCorruption,
|
||||||
Description: "corrupt DAG tip hashes",
|
Description: "corrupt DAG state",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return tipHashes, nil
|
return state, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// dbPutDAGTipHashes uses an existing database transaction to store the latest
|
// dbPutDAGState uses an existing database transaction to store the latest
|
||||||
// tip hashes of the DAG.
|
// tip hashes of the DAG.
|
||||||
func dbPutDAGTipHashes(dbTx database.Tx, tipHashes []daghash.Hash) error {
|
func dbPutDAGState(dbTx database.Tx, state *dagState) error {
|
||||||
serializedData, err := serializeDAGTipHashes(tipHashes)
|
serializedData, err := serializeDAGState(state)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return dbTx.Metadata().Put(dagTipHashesKeyName, serializedData)
|
return dbTx.Metadata().Put(dagStateKeyName, serializedData)
|
||||||
}
|
}
|
||||||
|
|
||||||
// createDAGState initializes both the database and the DAG state to the
|
// createDAGState initializes both the database and the DAG state to the
|
||||||
@ -758,6 +763,9 @@ func (dag *BlockDAG) createDAGState() error {
|
|||||||
// Add the new node to the index which is used for faster lookups.
|
// Add the new node to the index which is used for faster lookups.
|
||||||
dag.index.addNode(node)
|
dag.index.addNode(node)
|
||||||
|
|
||||||
|
// Initiate the last finality point to the genesis block
|
||||||
|
dag.lastFinalityPoint = node
|
||||||
|
|
||||||
// Create the initial the database chain state including creating the
|
// Create the initial the database chain state including creating the
|
||||||
// necessary index buckets and inserting the genesis block.
|
// necessary index buckets and inserting the genesis block.
|
||||||
err := dag.db.Update(func(dbTx database.Tx) error {
|
err := dag.db.Update(func(dbTx database.Tx) error {
|
||||||
@ -810,8 +818,12 @@ func (dag *BlockDAG) createDAGState() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Store the current DAG tip hashes into the database.
|
// Store the current DAG state into the database.
|
||||||
err = dbPutDAGTipHashes(dbTx, dag.TipHashes())
|
state := &dagState{
|
||||||
|
TipHashes: dag.TipHashes(),
|
||||||
|
LastFinalityPoint: *genesisBlock.Hash(),
|
||||||
|
}
|
||||||
|
err = dbPutDAGState(dbTx, state)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -830,7 +842,7 @@ func (dag *BlockDAG) initDAGState() error {
|
|||||||
// everything from scratch or upgrade certain buckets.
|
// everything from scratch or upgrade certain buckets.
|
||||||
var initialized bool
|
var initialized bool
|
||||||
err := dag.db.View(func(dbTx database.Tx) error {
|
err := dag.db.View(func(dbTx database.Tx) error {
|
||||||
initialized = dbTx.Metadata().Get(dagTipHashesKeyName) != nil
|
initialized = dbTx.Metadata().Get(dagStateKeyName) != nil
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -849,9 +861,9 @@ func (dag *BlockDAG) initDAGState() error {
|
|||||||
// When it doesn't exist, it means the database hasn't been
|
// When it doesn't exist, it means the database hasn't been
|
||||||
// initialized for use with the DAG yet, so break out now to allow
|
// initialized for use with the DAG yet, so break out now to allow
|
||||||
// that to happen under a writable database transaction.
|
// that to happen under a writable database transaction.
|
||||||
serializedData := dbTx.Metadata().Get(dagTipHashesKeyName)
|
serializedData := dbTx.Metadata().Get(dagStateKeyName)
|
||||||
log.Tracef("Serialized DAG tip hashes: %x", serializedData)
|
log.Tracef("Serialized DAG tip hashes: %x", serializedData)
|
||||||
tipHashes, err := deserializeDAGTipHashes(serializedData)
|
state, err := deserializeDAGState(serializedData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -977,16 +989,19 @@ func (dag *BlockDAG) initDAGState() error {
|
|||||||
|
|
||||||
// Apply the stored tips to the virtual block.
|
// Apply the stored tips to the virtual block.
|
||||||
tips := newSet()
|
tips := newSet()
|
||||||
for _, tipHash := range tipHashes {
|
for _, tipHash := range state.TipHashes {
|
||||||
tip := dag.index.LookupNode(&tipHash)
|
tip := dag.index.LookupNode(&tipHash)
|
||||||
if tip == nil {
|
if tip == nil {
|
||||||
return AssertError(fmt.Sprintf("initDAGState: cannot find "+
|
return AssertError(fmt.Sprintf("initDAGState: cannot find "+
|
||||||
"DAG tip %s in block index", tipHashes))
|
"DAG tip %s in block index", state.TipHashes))
|
||||||
}
|
}
|
||||||
tips.add(tip)
|
tips.add(tip)
|
||||||
}
|
}
|
||||||
dag.virtual.SetTips(tips)
|
dag.virtual.SetTips(tips)
|
||||||
|
|
||||||
|
// Set the last finality point
|
||||||
|
dag.lastFinalityPoint = dag.index.LookupNode(&state.LastFinalityPoint)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -532,64 +532,70 @@ func TestUtxoEntryDeserializeErrors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestDAGTipHashesSerialization ensures serializing and deserializing the
|
// TestDAGStateSerialization ensures serializing and deserializing the
|
||||||
// DAG tip hashes works as expected.
|
// DAG state works as expected.
|
||||||
func TestDAGTipHashesSerialization(t *testing.T) {
|
func TestDAGStateSerialization(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
tipHashes []daghash.Hash
|
state *dagState
|
||||||
serialized []byte
|
serialized []byte
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "genesis",
|
name: "genesis",
|
||||||
tipHashes: []daghash.Hash{*newHashFromStr("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")},
|
state: &dagState{
|
||||||
serialized: []byte("[[111,226,140,10,182,241,179,114,193,166,162,70,174,99,247,79,147,30,131,101,225,90,8,156,104,214,25,0,0,0,0,0]]"),
|
LastFinalityPoint: *newHashFromStr("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"),
|
||||||
|
TipHashes: []daghash.Hash{*newHashFromStr("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")},
|
||||||
|
},
|
||||||
|
serialized: []byte("{\"TipHashes\":[[111,226,140,10,182,241,179,114,193,166,162,70,174,99,247,79,147,30,131,101,225,90,8,156,104,214,25,0,0,0,0,0]],\"LastFinalityPoint\":[111,226,140,10,182,241,179,114,193,166,162,70,174,99,247,79,147,30,131,101,225,90,8,156,104,214,25,0,0,0,0,0]}"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "block 1",
|
name: "block 1",
|
||||||
tipHashes: []daghash.Hash{*newHashFromStr("00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048")},
|
state: &dagState{
|
||||||
serialized: []byte("[[72,96,235,24,191,27,22,32,227,126,148,144,252,138,66,117,20,65,111,215,81,89,171,134,104,142,154,131,0,0,0,0]]"),
|
LastFinalityPoint: *newHashFromStr("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"),
|
||||||
|
TipHashes: []daghash.Hash{*newHashFromStr("00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048")},
|
||||||
|
},
|
||||||
|
serialized: []byte("{\"TipHashes\":[[72,96,235,24,191,27,22,32,227,126,148,144,252,138,66,117,20,65,111,215,81,89,171,134,104,142,154,131,0,0,0,0]],\"LastFinalityPoint\":[111,226,140,10,182,241,179,114,193,166,162,70,174,99,247,79,147,30,131,101,225,90,8,156,104,214,25,0,0,0,0,0]}"),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for i, test := range tests {
|
for i, test := range tests {
|
||||||
gotBytes, err := serializeDAGTipHashes(test.tipHashes)
|
gotBytes, err := serializeDAGState(test.state)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("serializeDAGTipHashes #%d (%s) "+
|
t.Errorf("serializeDAGState #%d (%s) "+
|
||||||
"unexpected error: %v", i, test.name, err)
|
"unexpected error: %v", i, test.name, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the tipHashes serializes to the expected value.
|
// Ensure the dagState serializes to the expected value.
|
||||||
if !bytes.Equal(gotBytes, test.serialized) {
|
if !bytes.Equal(gotBytes, test.serialized) {
|
||||||
t.Errorf("serializeDAGTipHashes #%d (%s): mismatched "+
|
t.Errorf("serializeDAGState #%d (%s): mismatched "+
|
||||||
"bytes - got %s, want %s", i, test.name,
|
"bytes - got %s, want %s", i, test.name,
|
||||||
string(gotBytes), string(test.serialized))
|
string(gotBytes), string(test.serialized))
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the serialized bytes are decoded back to the expected
|
// Ensure the serialized bytes are decoded back to the expected
|
||||||
// tipHashes.
|
// dagState.
|
||||||
tipHashes, err := deserializeDAGTipHashes(test.serialized)
|
state, err := deserializeDAGState(test.serialized)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("deserializeDAGTipHashes #%d (%s) "+
|
t.Errorf("deserializeDAGState #%d (%s) "+
|
||||||
"unexpected error: %v", i, test.name, err)
|
"unexpected error: %v", i, test.name, err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(tipHashes, test.tipHashes) {
|
if !reflect.DeepEqual(state, test.state) {
|
||||||
t.Errorf("deserializeDAGTipHashes #%d (%s) "+
|
t.Errorf("deserializeDAGState #%d (%s) "+
|
||||||
"mismatched tipHashes - got %v, want %v", i,
|
"mismatched state - got %v, want %v", i,
|
||||||
test.name, tipHashes, test.tipHashes)
|
test.name, state, test.state)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestDAGTipHashesDeserializeErrors performs negative tests against
|
// TestDAGStateDeserializeErrors performs negative tests against
|
||||||
// deserializing the DAG tip hashes to ensure error paths work as expected.
|
// deserializing the DAG state to ensure error paths work as expected.
|
||||||
func TestDAGTipHashesDeserializeErrors(t *testing.T) {
|
func TestDAGStateDeserializeErrors(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
@ -611,9 +617,9 @@ func TestDAGTipHashesDeserializeErrors(t *testing.T) {
|
|||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
// Ensure the expected error type and code is returned.
|
// Ensure the expected error type and code is returned.
|
||||||
_, err := deserializeDAGTipHashes(test.serialized)
|
_, err := deserializeDAGState(test.serialized)
|
||||||
if reflect.TypeOf(err) != reflect.TypeOf(test.errType) {
|
if reflect.TypeOf(err) != reflect.TypeOf(test.errType) {
|
||||||
t.Errorf("deserializeDAGTipHashes (%s): expected "+
|
t.Errorf("deserializeDAGState (%s): expected "+
|
||||||
"error type does not match - got %T, want %T",
|
"error type does not match - got %T, want %T",
|
||||||
test.name, err, test.errType)
|
test.name, err, test.errType)
|
||||||
continue
|
continue
|
||||||
@ -621,7 +627,7 @@ func TestDAGTipHashesDeserializeErrors(t *testing.T) {
|
|||||||
if derr, ok := err.(database.Error); ok {
|
if derr, ok := err.(database.Error); ok {
|
||||||
tderr := test.errType.(database.Error)
|
tderr := test.errType.(database.Error)
|
||||||
if derr.ErrorCode != tderr.ErrorCode {
|
if derr.ErrorCode != tderr.ErrorCode {
|
||||||
t.Errorf("deserializeDAGTipHashes (%s): "+
|
t.Errorf("deserializeDAGState (%s): "+
|
||||||
"wrong error code got: %v, want: %v",
|
"wrong error code got: %v, want: %v",
|
||||||
test.name, derr.ErrorCode,
|
test.name, derr.ErrorCode,
|
||||||
tderr.ErrorCode)
|
tderr.ErrorCode)
|
||||||
|
@ -209,6 +209,8 @@ const (
|
|||||||
// ErrWithDiff indicates that there was an error with UTXOSet.WithDiff
|
// ErrWithDiff indicates that there was an error with UTXOSet.WithDiff
|
||||||
ErrWithDiff
|
ErrWithDiff
|
||||||
|
|
||||||
|
// ErrFinality indicates that a block doesn't adhere to the finality rules
|
||||||
|
ErrFinality
|
||||||
// ErrTransactionsNotSorted indicates that transactions in block are not
|
// ErrTransactionsNotSorted indicates that transactions in block are not
|
||||||
// sorted by subnetwork
|
// sorted by subnetwork
|
||||||
ErrTransactionsNotSorted
|
ErrTransactionsNotSorted
|
||||||
@ -256,6 +258,7 @@ var errorCodeStrings = map[ErrorCode]string{
|
|||||||
ErrInvalidAncestorBlock: "ErrInvalidAncestorBlock",
|
ErrInvalidAncestorBlock: "ErrInvalidAncestorBlock",
|
||||||
ErrParentBlockNotCurrentTips: "ErrParentBlockNotCurrentTips",
|
ErrParentBlockNotCurrentTips: "ErrParentBlockNotCurrentTips",
|
||||||
ErrWithDiff: "ErrWithDiff",
|
ErrWithDiff: "ErrWithDiff",
|
||||||
|
ErrFinality: "ErrFinality",
|
||||||
ErrTransactionsNotSorted: "ErrTransactionsNotSorted",
|
ErrTransactionsNotSorted: "ErrTransactionsNotSorted",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -55,6 +55,7 @@ func TestErrorCodeStringer(t *testing.T) {
|
|||||||
{ErrInvalidAncestorBlock, "ErrInvalidAncestorBlock"},
|
{ErrInvalidAncestorBlock, "ErrInvalidAncestorBlock"},
|
||||||
{ErrParentBlockNotCurrentTips, "ErrParentBlockNotCurrentTips"},
|
{ErrParentBlockNotCurrentTips, "ErrParentBlockNotCurrentTips"},
|
||||||
{ErrWithDiff, "ErrWithDiff"},
|
{ErrWithDiff, "ErrWithDiff"},
|
||||||
|
{ErrFinality, "ErrFinality"},
|
||||||
{ErrTransactionsNotSorted, "ErrTransactionsNotSorted"},
|
{ErrTransactionsNotSorted, "ErrTransactionsNotSorted"},
|
||||||
{0xffff, "Unknown ErrorCode (65535)"},
|
{0xffff, "Unknown ErrorCode (65535)"},
|
||||||
}
|
}
|
||||||
|
@ -84,10 +84,24 @@ func traverseCandidates(newBlock *blockNode, candidates blockSet, selectedParent
|
|||||||
queue.Push(parent)
|
queue.Push(parent)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO (Ori): This is temporary until children bug is fixed. After it we should return it to `selectedParentPast.anyChildInSet(current)`
|
||||||
|
isInSelectedParentPast := func(block *blockNode) bool {
|
||||||
|
if selectedParent.parents.contains(block) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
for _, child := range block.children {
|
||||||
|
if selectedParentPast.contains(child) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
for queue.Len() > 0 {
|
for queue.Len() > 0 {
|
||||||
current := queue.pop()
|
current := queue.pop()
|
||||||
if candidates.contains(current) {
|
if candidates.contains(current) {
|
||||||
if current == selectedParent || selectedParentPast.anyChildInSet(current) {
|
if current == selectedParent || isInSelectedParentPast(current) {
|
||||||
selectedParentPast.add(current)
|
selectedParentPast.add(current)
|
||||||
} else {
|
} else {
|
||||||
blues = append(blues, current)
|
blues = append(blues, current)
|
||||||
|
@ -184,7 +184,7 @@ func (d *UTXODiff) diffFrom(other *UTXODiff) (*UTXODiff, error) {
|
|||||||
result.toRemove.add(outPoint, utxoEntry)
|
result.toRemove.add(outPoint, utxoEntry)
|
||||||
}
|
}
|
||||||
if other.toRemove.contains(outPoint) {
|
if other.toRemove.contains(outPoint) {
|
||||||
return nil, errors.New("diffFrom: transaction both in d.toAdd and in other.toRemove")
|
return nil, fmt.Errorf("diffFrom: outpoint %v both in d.toAdd and in other.toRemove", outPoint)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -803,9 +803,8 @@ func TestDiffUTXOSet_addTx(t *testing.T) {
|
|||||||
// outputs paying an appropriate subsidy based on the passed block height to the
|
// outputs paying an appropriate subsidy based on the passed block height to the
|
||||||
// address associated with the harness. It automatically uses a standard
|
// address associated with the harness. It automatically uses a standard
|
||||||
// signature script that starts with the block height
|
// signature script that starts with the block height
|
||||||
func createCoinbaseTx(blockHeight int32, numOutputs uint32) (*wire.MsgTx, error) {
|
func createCoinbaseTx(blockHeight int32, numOutputs uint32, extraNonce int64, params *dagconfig.Params) (*wire.MsgTx, error) {
|
||||||
// Create standard coinbase script.
|
// Create standard coinbase script.
|
||||||
extraNonce := int64(0)
|
|
||||||
coinbaseScript, err := txscript.NewScriptBuilder().
|
coinbaseScript, err := txscript.NewScriptBuilder().
|
||||||
AddInt64(int64(blockHeight)).AddInt64(extraNonce).Script()
|
AddInt64(int64(blockHeight)).AddInt64(extraNonce).Script()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -821,7 +820,7 @@ func createCoinbaseTx(blockHeight int32, numOutputs uint32) (*wire.MsgTx, error)
|
|||||||
SignatureScript: coinbaseScript,
|
SignatureScript: coinbaseScript,
|
||||||
Sequence: wire.MaxTxInSequenceNum,
|
Sequence: wire.MaxTxInSequenceNum,
|
||||||
})
|
})
|
||||||
totalInput := CalcBlockSubsidy(blockHeight, &dagconfig.MainNetParams)
|
totalInput := CalcBlockSubsidy(blockHeight, params)
|
||||||
amountPerOutput := totalInput / uint64(numOutputs)
|
amountPerOutput := totalInput / uint64(numOutputs)
|
||||||
remainder := totalInput - amountPerOutput*uint64(numOutputs)
|
remainder := totalInput - amountPerOutput*uint64(numOutputs)
|
||||||
for i := uint32(0); i < numOutputs; i++ {
|
for i := uint32(0); i < numOutputs; i++ {
|
||||||
@ -858,7 +857,7 @@ func TestApplyUTXOChanges(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
cbTx, err := createCoinbaseTx(1, 1)
|
cbTx, err := createCoinbaseTx(1, 1, 0, dag.dagParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("createCoinbaseTx: %v", err)
|
t.Errorf("createCoinbaseTx: %v", err)
|
||||||
}
|
}
|
||||||
@ -925,7 +924,7 @@ func TestDiffFromTx(t *testing.T) {
|
|||||||
fus := &FullUTXOSet{
|
fus := &FullUTXOSet{
|
||||||
utxoCollection: utxoCollection{},
|
utxoCollection: utxoCollection{},
|
||||||
}
|
}
|
||||||
cbTx, err := createCoinbaseTx(1, 1)
|
cbTx, err := createCoinbaseTx(1, 1, 0, &dagconfig.SimNetParams)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Errorf("createCoinbaseTx: %v", err)
|
t.Errorf("createCoinbaseTx: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -12,7 +12,7 @@ import (
|
|||||||
// TestVirtualBlock ensures that VirtualBlock works as expected.
|
// TestVirtualBlock ensures that VirtualBlock works as expected.
|
||||||
func TestVirtualBlock(t *testing.T) {
|
func TestVirtualBlock(t *testing.T) {
|
||||||
phantomK := uint32(1)
|
phantomK := uint32(1)
|
||||||
buildNode := buildNodeGenerator(phantomK)
|
buildNode := buildNodeGenerator(phantomK, false)
|
||||||
|
|
||||||
// Create a DAG as follows:
|
// Create a DAG as follows:
|
||||||
// 0 <- 1 <- 2
|
// 0 <- 1 <- 2
|
||||||
@ -101,7 +101,7 @@ func TestVirtualBlock(t *testing.T) {
|
|||||||
|
|
||||||
func TestSelectedPath(t *testing.T) {
|
func TestSelectedPath(t *testing.T) {
|
||||||
phantomK := uint32(1)
|
phantomK := uint32(1)
|
||||||
buildNode := buildNodeGenerator(phantomK)
|
buildNode := buildNodeGenerator(phantomK, false)
|
||||||
|
|
||||||
// Create an empty VirtualBlock
|
// Create an empty VirtualBlock
|
||||||
virtual := newVirtualBlock(nil, phantomK)
|
virtual := newVirtualBlock(nil, phantomK)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user