[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:
Ori Newman 2019-01-03 13:22:05 +02:00 committed by Mike Zak
parent f0f79b1fcc
commit c0aafdf7e1
13 changed files with 303 additions and 86 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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: &params,
})
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")
}
}

View File

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

View File

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

View File

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

View File

@ -55,6 +55,7 @@ func TestErrorCodeStringer(t *testing.T) {
{ErrInvalidAncestorBlock, "ErrInvalidAncestorBlock"},
{ErrParentBlockNotCurrentTips, "ErrParentBlockNotCurrentTips"},
{ErrWithDiff, "ErrWithDiff"},
{ErrFinality, "ErrFinality"},
{ErrTransactionsNotSorted, "ErrTransactionsNotSorted"},
{0xffff, "Unknown ErrorCode (65535)"},
}

View File

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

View File

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

View File

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

View File

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