diff --git a/blockdag/blocknode.go b/blockdag/blocknode.go index a7c5bab28..a4f5a4671 100644 --- a/blockdag/blocknode.go +++ b/blockdag/blocknode.go @@ -244,6 +244,10 @@ func (node *blockNode) isGenesis() bool { 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. 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 index d84361717..a92cef1a7 100644 --- a/blockdag/blocknode_test.go +++ b/blockdag/blocknode_test.go @@ -6,24 +6,19 @@ import ( func TestChainHeight(t *testing.T) { phantomK := uint32(2) - buildNode := buildNodeGenerator(phantomK) - buildWithChildren := func(parents blockSet) *blockNode { - node := buildNode(parents) - addNodeAsChildToParents(node) - return node - } + buildNode := buildNodeGenerator(phantomK, true) - 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)) + node0 := buildNode(setFromSlice()) + node1 := buildNode(setFromSlice(node0)) + node2 := buildNode(setFromSlice(node0)) + node3 := buildNode(setFromSlice(node0)) + node4 := buildNode(setFromSlice(node1, node2, node3)) + node5 := buildNode(setFromSlice(node1, node2, node3)) + node6 := buildNode(setFromSlice(node1, node2, node3)) + node7 := buildNode(setFromSlice(node0)) + node8 := buildNode(setFromSlice(node7)) + node9 := buildNode(setFromSlice(node8)) + node10 := buildNode(setFromSlice(node9, node6)) // Because nodes 7 & 8 were mined secretly, node10's selected // parent will be node6, although node9 is higher. So in this diff --git a/blockdag/common_test.go b/blockdag/common_test.go index 339d993d1..3941d9b1e 100644 --- a/blockdag/common_test.go +++ b/blockdag/common_test.go @@ -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 - // series of numbers from 0 to n. - hashCounter := byte(0) - return func(parents blockSet) *blockNode { + // series of numbers from 1 to 255. + hashCounter := byte(1) + buildNode := func(parents blockSet) *blockNode { block := newBlockNode(nil, parents, phantomK) block.hash = daghash.Hash{hashCounter} hashCounter++ return block } + if withChildren { + return func(parents blockSet) *blockNode { + node := buildNode(parents) + addNodeAsChildToParents(node) + return node + } + } + return buildNode } diff --git a/blockdag/dag.go b/blockdag/dag.go index 4a27d9fa4..910e152df 100644 --- a/blockdag/dag.go +++ b/blockdag/dag.go @@ -23,6 +23,8 @@ const ( // maxOrphanBlocks is the maximum number of orphan blocks that can be // queued. maxOrphanBlocks = 100 + + finalityInterval = 100 ) // BlockLocator is used to help locate a specific block. The algorithm for @@ -138,6 +140,8 @@ type BlockDAG struct { // certain blockchain events. notificationsLock sync.RWMutex notifications []NotificationCallback + + lastFinalityPoint *blockNode } // 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. utxoDiff, acceptedTxsData, err := dag.applyUTXOChanges(node, block, fastAdd) if err != nil { return err } + if finalityPointCandidate != nil { + dag.lastFinalityPoint = finalityPointCandidate + } + // Write any block status changes to DB before updating the DAG state. err = dag.index.flushToDB() if err != nil { @@ -518,7 +536,11 @@ func (dag *BlockDAG) connectBlock(node *blockNode, block *util.Block, fastAdd bo // Atomically insert info into the database. err = dag.db.Update(func(dbTx database.Tx) error { // 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 { return err } @@ -563,6 +585,27 @@ func (dag *BlockDAG) connectBlock(node *blockNode, block *util.Block, fastAdd bo 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: // 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. @@ -1528,7 +1571,7 @@ func New(config *Config) (*BlockDAG, error) { } // 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. if err := dag.initDAGState(); err != nil { return nil, err diff --git a/blockdag/dag_test.go b/blockdag/dag_test.go index d36d8b301..8629c607b 100644 --- a/blockdag/dag_test.go +++ b/blockdag/dag_test.go @@ -40,16 +40,16 @@ func TestBlockCount(t *testing.T) { 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{ DAGParams: &dagconfig.SimNetParams, }) if err != nil { - t.Fatalf("Failed to setup chain instance: %v", err) + t.Fatalf("Failed to setup DAG instance: %v", err) } 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. dag.TstSetCoinbaseMaturity(1) @@ -849,3 +849,132 @@ func testErrorThroughPatching(t *testing.T, expectedErrorMessage string, targetF "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") + } +} diff --git a/blockdag/dagio.go b/blockdag/dagio.go index 0a3949dfe..492eeec59 100644 --- a/blockdag/dagio.go +++ b/blockdag/dagio.go @@ -41,9 +41,9 @@ var ( // the block height -> block hash index. 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. - dagTipHashesKeyName = []byte("dagtiphashes") + dagStateKeyName = []byte("dagstate") // utxoSetVersionKeyName is the name of the db key used to store the // 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 } -// serializeDAGTipHashes returns the serialization of the DAG tip hashes. -// This is data to be stored in the DAG tip hashes bucket. -func serializeDAGTipHashes(tipHashes []daghash.Hash) ([]byte, error) { - return json.Marshal(tipHashes) +type dagState struct { + TipHashes []daghash.Hash + LastFinalityPoint daghash.Hash } -// deserializeDAGTipHashes deserializes the passed serialized DAG tip hashes. -// This is data stored in the DAG tip hashes bucket and is updated after +// serializeDAGState returns the serialization of the DAG state. +// 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. -func deserializeDAGTipHashes(serializedData []byte) ([]daghash.Hash, error) { - var tipHashes []daghash.Hash - err := json.Unmarshal(serializedData, &tipHashes) +func deserializeDAGState(serializedData []byte) (*dagState, error) { + var state *dagState + err := json.Unmarshal(serializedData, &state) if err != nil { return nil, database.Error{ 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. -func dbPutDAGTipHashes(dbTx database.Tx, tipHashes []daghash.Hash) error { - serializedData, err := serializeDAGTipHashes(tipHashes) +func dbPutDAGState(dbTx database.Tx, state *dagState) error { + serializedData, err := serializeDAGState(state) if err != nil { 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 @@ -758,6 +763,9 @@ func (dag *BlockDAG) createDAGState() error { // Add the new node to the index which is used for faster lookups. 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 // necessary index buckets and inserting the genesis block. err := dag.db.Update(func(dbTx database.Tx) error { @@ -810,8 +818,12 @@ func (dag *BlockDAG) createDAGState() error { return err } - // Store the current DAG tip hashes into the database. - err = dbPutDAGTipHashes(dbTx, dag.TipHashes()) + // Store the current DAG state into the database. + state := &dagState{ + TipHashes: dag.TipHashes(), + LastFinalityPoint: *genesisBlock.Hash(), + } + err = dbPutDAGState(dbTx, state) if err != nil { return err } @@ -830,7 +842,7 @@ func (dag *BlockDAG) initDAGState() error { // everything from scratch or upgrade certain buckets. var initialized bool err := dag.db.View(func(dbTx database.Tx) error { - initialized = dbTx.Metadata().Get(dagTipHashesKeyName) != nil + initialized = dbTx.Metadata().Get(dagStateKeyName) != nil return 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 // initialized for use with the DAG yet, so break out now to allow // 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) - tipHashes, err := deserializeDAGTipHashes(serializedData) + state, err := deserializeDAGState(serializedData) if err != nil { return err } @@ -977,16 +989,19 @@ func (dag *BlockDAG) initDAGState() error { // Apply the stored tips to the virtual block. tips := newSet() - for _, tipHash := range tipHashes { + for _, tipHash := range state.TipHashes { tip := dag.index.LookupNode(&tipHash) if tip == nil { 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) } dag.virtual.SetTips(tips) + // Set the last finality point + dag.lastFinalityPoint = dag.index.LookupNode(&state.LastFinalityPoint) + return nil }) } diff --git a/blockdag/dagio_test.go b/blockdag/dagio_test.go index cb0c14957..1f8b7e31a 100644 --- a/blockdag/dagio_test.go +++ b/blockdag/dagio_test.go @@ -532,64 +532,70 @@ func TestUtxoEntryDeserializeErrors(t *testing.T) { } } -// TestDAGTipHashesSerialization ensures serializing and deserializing the -// DAG tip hashes works as expected. -func TestDAGTipHashesSerialization(t *testing.T) { +// TestDAGStateSerialization ensures serializing and deserializing the +// DAG state works as expected. +func TestDAGStateSerialization(t *testing.T) { t.Parallel() tests := []struct { name string - tipHashes []daghash.Hash + state *dagState serialized []byte }{ { - name: "genesis", - tipHashes: []daghash.Hash{*newHashFromStr("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f")}, - 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]]"), + name: "genesis", + state: &dagState{ + 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", - tipHashes: []daghash.Hash{*newHashFromStr("00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048")}, - 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]]"), + name: "block 1", + state: &dagState{ + 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 { - gotBytes, err := serializeDAGTipHashes(test.tipHashes) + gotBytes, err := serializeDAGState(test.state) if err != nil { - t.Errorf("serializeDAGTipHashes #%d (%s) "+ + t.Errorf("serializeDAGState #%d (%s) "+ "unexpected error: %v", i, test.name, err) continue } - // Ensure the tipHashes serializes to the expected value. + // Ensure the dagState serializes to the expected value. 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, string(gotBytes), string(test.serialized)) continue } // Ensure the serialized bytes are decoded back to the expected - // tipHashes. - tipHashes, err := deserializeDAGTipHashes(test.serialized) + // dagState. + state, err := deserializeDAGState(test.serialized) if err != nil { - t.Errorf("deserializeDAGTipHashes #%d (%s) "+ + t.Errorf("deserializeDAGState #%d (%s) "+ "unexpected error: %v", i, test.name, err) continue } - if !reflect.DeepEqual(tipHashes, test.tipHashes) { - t.Errorf("deserializeDAGTipHashes #%d (%s) "+ - "mismatched tipHashes - got %v, want %v", i, - test.name, tipHashes, test.tipHashes) + if !reflect.DeepEqual(state, test.state) { + t.Errorf("deserializeDAGState #%d (%s) "+ + "mismatched state - got %v, want %v", i, + test.name, state, test.state) continue } } } -// TestDAGTipHashesDeserializeErrors performs negative tests against -// deserializing the DAG tip hashes to ensure error paths work as expected. -func TestDAGTipHashesDeserializeErrors(t *testing.T) { +// TestDAGStateDeserializeErrors performs negative tests against +// deserializing the DAG state to ensure error paths work as expected. +func TestDAGStateDeserializeErrors(t *testing.T) { t.Parallel() tests := []struct { @@ -611,9 +617,9 @@ func TestDAGTipHashesDeserializeErrors(t *testing.T) { for _, test := range tests { // 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) { - t.Errorf("deserializeDAGTipHashes (%s): expected "+ + t.Errorf("deserializeDAGState (%s): expected "+ "error type does not match - got %T, want %T", test.name, err, test.errType) continue @@ -621,7 +627,7 @@ func TestDAGTipHashesDeserializeErrors(t *testing.T) { if derr, ok := err.(database.Error); ok { tderr := test.errType.(database.Error) if derr.ErrorCode != tderr.ErrorCode { - t.Errorf("deserializeDAGTipHashes (%s): "+ + t.Errorf("deserializeDAGState (%s): "+ "wrong error code got: %v, want: %v", test.name, derr.ErrorCode, tderr.ErrorCode) diff --git a/blockdag/error.go b/blockdag/error.go index 4ad68b232..71eb9b491 100644 --- a/blockdag/error.go +++ b/blockdag/error.go @@ -209,6 +209,8 @@ const ( // ErrWithDiff indicates that there was an error with UTXOSet.WithDiff ErrWithDiff + // ErrFinality indicates that a block doesn't adhere to the finality rules + ErrFinality // ErrTransactionsNotSorted indicates that transactions in block are not // sorted by subnetwork ErrTransactionsNotSorted @@ -256,6 +258,7 @@ var errorCodeStrings = map[ErrorCode]string{ ErrInvalidAncestorBlock: "ErrInvalidAncestorBlock", ErrParentBlockNotCurrentTips: "ErrParentBlockNotCurrentTips", ErrWithDiff: "ErrWithDiff", + ErrFinality: "ErrFinality", ErrTransactionsNotSorted: "ErrTransactionsNotSorted", } diff --git a/blockdag/error_test.go b/blockdag/error_test.go index 02ab93c8d..0e0efe02a 100644 --- a/blockdag/error_test.go +++ b/blockdag/error_test.go @@ -55,6 +55,7 @@ func TestErrorCodeStringer(t *testing.T) { {ErrInvalidAncestorBlock, "ErrInvalidAncestorBlock"}, {ErrParentBlockNotCurrentTips, "ErrParentBlockNotCurrentTips"}, {ErrWithDiff, "ErrWithDiff"}, + {ErrFinality, "ErrFinality"}, {ErrTransactionsNotSorted, "ErrTransactionsNotSorted"}, {0xffff, "Unknown ErrorCode (65535)"}, } diff --git a/blockdag/phantom.go b/blockdag/phantom.go index 453d90352..d02b6a3c5 100644 --- a/blockdag/phantom.go +++ b/blockdag/phantom.go @@ -84,10 +84,24 @@ func traverseCandidates(newBlock *blockNode, candidates blockSet, selectedParent 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 { current := queue.pop() if candidates.contains(current) { - if current == selectedParent || selectedParentPast.anyChildInSet(current) { + if current == selectedParent || isInSelectedParentPast(current) { selectedParentPast.add(current) } else { blues = append(blues, current) diff --git a/blockdag/utxoset.go b/blockdag/utxoset.go index 80d736346..d794aa1bc 100644 --- a/blockdag/utxoset.go +++ b/blockdag/utxoset.go @@ -184,7 +184,7 @@ func (d *UTXODiff) diffFrom(other *UTXODiff) (*UTXODiff, error) { result.toRemove.add(outPoint, utxoEntry) } 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) } } diff --git a/blockdag/utxoset_test.go b/blockdag/utxoset_test.go index 0c12d291b..098876701 100644 --- a/blockdag/utxoset_test.go +++ b/blockdag/utxoset_test.go @@ -803,9 +803,8 @@ func TestDiffUTXOSet_addTx(t *testing.T) { // outputs paying an appropriate subsidy based on the passed block height to the // address associated with the harness. It automatically uses a standard // 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. - extraNonce := int64(0) coinbaseScript, err := txscript.NewScriptBuilder(). AddInt64(int64(blockHeight)).AddInt64(extraNonce).Script() if err != nil { @@ -821,7 +820,7 @@ func createCoinbaseTx(blockHeight int32, numOutputs uint32) (*wire.MsgTx, error) SignatureScript: coinbaseScript, Sequence: wire.MaxTxInSequenceNum, }) - totalInput := CalcBlockSubsidy(blockHeight, &dagconfig.MainNetParams) + totalInput := CalcBlockSubsidy(blockHeight, params) amountPerOutput := totalInput / uint64(numOutputs) remainder := totalInput - amountPerOutput*uint64(numOutputs) 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 { t.Errorf("createCoinbaseTx: %v", err) } @@ -925,7 +924,7 @@ func TestDiffFromTx(t *testing.T) { fus := &FullUTXOSet{ utxoCollection: utxoCollection{}, } - cbTx, err := createCoinbaseTx(1, 1) + cbTx, err := createCoinbaseTx(1, 1, 0, &dagconfig.SimNetParams) if err != nil { t.Errorf("createCoinbaseTx: %v", err) } diff --git a/blockdag/virtualblock_test.go b/blockdag/virtualblock_test.go index 545994cc9..de6b93d71 100644 --- a/blockdag/virtualblock_test.go +++ b/blockdag/virtualblock_test.go @@ -12,7 +12,7 @@ import ( // TestVirtualBlock ensures that VirtualBlock works as expected. func TestVirtualBlock(t *testing.T) { phantomK := uint32(1) - buildNode := buildNodeGenerator(phantomK) + buildNode := buildNodeGenerator(phantomK, false) // Create a DAG as follows: // 0 <- 1 <- 2 @@ -101,7 +101,7 @@ func TestVirtualBlock(t *testing.T) { func TestSelectedPath(t *testing.T) { phantomK := uint32(1) - buildNode := buildNodeGenerator(phantomK) + buildNode := buildNodeGenerator(phantomK, false) // Create an empty VirtualBlock virtual := newVirtualBlock(nil, phantomK)