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
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
})
|
||||
}
|
||||
|
@ -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]]"),
|
||||
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]]"),
|
||||
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)
|
||||
|
@ -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",
|
||||
}
|
||||
|
||||
|
@ -55,6 +55,7 @@ func TestErrorCodeStringer(t *testing.T) {
|
||||
{ErrInvalidAncestorBlock, "ErrInvalidAncestorBlock"},
|
||||
{ErrParentBlockNotCurrentTips, "ErrParentBlockNotCurrentTips"},
|
||||
{ErrWithDiff, "ErrWithDiff"},
|
||||
{ErrFinality, "ErrFinality"},
|
||||
{ErrTransactionsNotSorted, "ErrTransactionsNotSorted"},
|
||||
{0xffff, "Unknown ErrorCode (65535)"},
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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)
|
||||
|
Loading…
x
Reference in New Issue
Block a user