コード例 #1
0
ファイル: tickets.go プロジェクト: decred/dcrd
// ExistsExpiredTicket returns whether or not a ticket was ever expired from
// the perspective of this stake node.
func (sn *Node) ExistsExpiredTicket(ticket chainhash.Hash) bool {
	v := sn.missedTickets.Get(tickettreap.Key(ticket))
	if v != nil && v.Expired {
		return true
	}
	v = sn.revokedTickets.Get(tickettreap.Key(ticket))
	if v != nil && v.Expired {
		return true
	}

	return false
}
コード例 #2
0
ファイル: chainio.go プロジェクト: decred/dcrd
// DbLoadAllTickets loads all the live tickets from the database into a treap.
func DbLoadAllTickets(dbTx database.Tx, ticketBucket []byte) (*tickettreap.Immutable, error) {
	meta := dbTx.Metadata()
	bucket := meta.Bucket(ticketBucket)

	treap := tickettreap.NewImmutable()
	err := bucket.ForEach(func(k []byte, v []byte) error {
		if len(v) < 5 {
			return ticketDBError(ErrLoadAllTickets, fmt.Sprintf("short "+
				"read for ticket key %x when loading tickets", k))
		}

		h, err := chainhash.NewHash(k)
		if err != nil {
			return err
		}
		treapKey := tickettreap.Key(*h)
		missed, revoked, spent, expired := undoBitFlagsFromByte(v[4])
		treapValue := &tickettreap.Value{
			Height:  dbnamespace.ByteOrder.Uint32(v[0:4]),
			Missed:  missed,
			Revoked: revoked,
			Spent:   spent,
			Expired: expired,
		}

		treap = treap.Put(treapKey, treapValue)
		return nil
	})
	if err != nil {
		return nil, ticketDBError(ErrLoadAllTickets, fmt.Sprintf("failed to "+
			"load all tickets for the bucket %s", string(ticketBucket)))
	}

	return treap, nil
}
コード例 #3
0
ファイル: lottery_test.go プロジェクト: decred/dcrd
func TestFetchWinnersErrors(t *testing.T) {
	treap := new(tickettreap.Immutable)
	for i := 0; i < 0xff; i++ {
		h := chainhash.HashFuncH([]byte{byte(i)})
		v := &tickettreap.Value{
			Height:  uint32(i),
			Missed:  i%2 == 0,
			Revoked: i%2 != 0,
			Spent:   i%2 == 0,
			Expired: i%2 != 0,
		}
		treap = treap.Put(tickettreap.Key(h), v)
	}

	// No indexes.
	_, err := fetchWinners(nil, treap)
	if err == nil {
		t.Errorf("Expected nil slice error")
	}

	// No treap.
	_, err = fetchWinners([]int{1, 2, 3, 4, -1}, nil)
	if err == nil {
		t.Errorf("Expected nil treap error")
	}

	// Bad index too small.
	_, err = fetchWinners([]int{1, 2, 3, 4, -1}, treap)
	if err == nil {
		t.Errorf("Expected index too small error")
	}

	// Bad index too big.
	_, err = fetchWinners([]int{1, 2, 3, 4, 256}, treap)
	if err == nil {
		t.Errorf("Expected index too big error")
	}
}
コード例 #4
0
ファイル: chainio_test.go プロジェクト: decred/dcrd
// TestLiveDatabase tests various functions that require a live database.
func TestLiveDatabase(t *testing.T) {
	// Create a new database to store the accepted stake node data into.
	dbName := "ffldb_ticketdb_test"
	dbPath := filepath.Join(testDbRoot, dbName)
	_ = os.RemoveAll(dbPath)
	testDb, err := database.Create(testDbType, dbPath, chaincfg.SimNetParams.Net)
	if err != nil {
		t.Fatalf("error creating db: %v", err)
	}

	// Setup a teardown.
	defer os.RemoveAll(dbPath)
	defer os.RemoveAll(testDbRoot)
	defer testDb.Close()

	// Initialize the database, then try to read the version.
	err = testDb.Update(func(dbTx database.Tx) error {
		return DbCreate(dbTx)
	})
	if err != nil {
		t.Fatalf("%v", err.Error())
	}

	var dbi *DatabaseInfo
	err = testDb.View(func(dbTx database.Tx) error {
		dbi, err = DbFetchDatabaseInfo(dbTx)
		if err != nil {
			return err
		}

		return nil
	})
	if err != nil {
		t.Fatalf("%v", err.Error())
	}
	if dbi.Version != currentDatabaseVersion {
		t.Fatalf("bad version after reading from DB; want %v, got %v",
			currentDatabaseVersion, dbi.Version)
	}

	// Test storing arbitrary ticket treaps.
	ticketMap := make(map[tickettreap.Key]*tickettreap.Value)
	tickets := make([]chainhash.Hash, 5)
	for i := 0; i < 4; i++ {
		h := chainhash.HashFuncH(bytes.Repeat([]byte{0x01}, i))
		ticketMap[tickettreap.Key(h)] = &tickettreap.Value{
			Height:  12345 + uint32(i),
			Missed:  i%2 == 0,
			Revoked: i%2 != 0,
			Spent:   i%2 == 0,
			Expired: i%2 != 0,
		}
		tickets[i] = h
	}

	err = testDb.Update(func(dbTx database.Tx) error {
		for k, v := range ticketMap {
			h := chainhash.Hash(k)
			err = DbPutTicket(dbTx, dbnamespace.LiveTicketsBucketName, &h,
				v.Height, v.Missed, v.Revoked, v.Spent, v.Expired)
			if err != nil {
				return err
			}
		}

		return nil
	})
	if err != nil {
		t.Fatalf("%v", err.Error())
	}

	var treap *tickettreap.Immutable
	ticketMap2 := make(map[tickettreap.Key]*tickettreap.Value)
	err = testDb.View(func(dbTx database.Tx) error {
		treap, err = DbLoadAllTickets(dbTx, dbnamespace.LiveTicketsBucketName)
		if err != nil {
			return err
		}

		return nil
	})
	if err != nil {
		t.Fatalf("%v", err.Error())
	}
	treap.ForEach(func(k tickettreap.Key, v *tickettreap.Value) bool {
		ticketMap2[k] = v

		return true
	})

	if !reflect.DeepEqual(ticketMap, ticketMap2) {
		t.Fatalf("not same ticket maps")
	}
}
コード例 #5
0
ファイル: tickets.go プロジェクト: decred/dcrd
// disconnectNode disconnects a stake node from itself and returns the state of
// the parent node.  The database transaction should be included if the
// UndoTicketDataSlice or tickets are nil in order to look up the undo data or
// tickets from the database.
func disconnectNode(node *Node, parentHeader wire.BlockHeader, parentUtds UndoTicketDataSlice, parentTickets []chainhash.Hash, dbTx database.Tx) (*Node, error) {
	// Edge case for the parent being the genesis block.
	if node.height == 1 {
		return genesisNode(node.params), nil
	}

	if node == nil {
		return nil, fmt.Errorf("missing stake node pointer input when " +
			"disconnecting")
	}

	// The undo ticket slice is normally stored in memory for the most
	// recent blocks and the sidechain, but it may be the case that it
	// is missing because it's in the mainchain and very old (thus
	// outside the node cache).  In this case, restore this data from
	// disk.
	if parentUtds == nil || parentTickets == nil {
		if dbTx == nil {
			return nil, stakeRuleError(ErrMissingDatabaseTx, "needed to "+
				"look up undo data in the database, but no dbtx passed")
		}

		var err error
		parentUtds, err = ticketdb.DbFetchBlockUndoData(dbTx, node.height-1)
		if err != nil {
			return nil, err
		}

		parentTickets, err = ticketdb.DbFetchNewTickets(dbTx, node.height-1)
		if err != nil {
			return nil, err
		}
	}

	restoredNode := &Node{
		height:               node.height - 1,
		liveTickets:          node.liveTickets,
		missedTickets:        node.missedTickets,
		revokedTickets:       node.revokedTickets,
		databaseUndoUpdate:   parentUtds,
		databaseBlockTickets: parentTickets,
		nextWinners:          make([]chainhash.Hash, 0),
		params:               node.params,
	}

	// Iterate through the block undo data and write all database
	// changes to the respective treap, reversing all the changes
	// added when the child block was added to the chain.
	stateBuffer := make([]byte, 0,
		(node.params.TicketsPerBlock+1)*chainhash.HashSize)
	for _, undo := range node.databaseUndoUpdate {
		var err error
		k := tickettreap.Key(undo.TicketHash)
		v := &tickettreap.Value{
			Height:  undo.TicketHeight,
			Missed:  undo.Missed,
			Revoked: undo.Revoked,
			Spent:   undo.Spent,
			Expired: undo.Expired,
		}

		switch {
		// All flags are unset; this is a newly added ticket.
		// Remove it from the list of live tickets.
		case !undo.Missed && !undo.Revoked && !undo.Spent:
			restoredNode.liveTickets, err =
				safeDelete(restoredNode.liveTickets, k)
			if err != nil {
				return nil, err
			}

		// The ticket was missed and revoked.  It needs to
		// be moved from the revoked ticket treap to the
		// missed ticket treap.
		case undo.Missed && undo.Revoked:
			v.Revoked = false
			restoredNode.revokedTickets, err =
				safeDelete(restoredNode.revokedTickets, k)
			if err != nil {
				return nil, err
			}
			restoredNode.missedTickets, err =
				safePut(restoredNode.missedTickets, k, v)
			if err != nil {
				return nil, err
			}

		// The ticket was missed and was previously live.
		// Remove it from the missed tickets bucket and
		// move it to the live tickets bucket.
		case undo.Missed && !undo.Revoked:
			// Expired tickets could never have been
			// winners.
			if !undo.Expired {
				restoredNode.nextWinners = append(restoredNode.nextWinners,
					undo.TicketHash)
				stateBuffer = append(stateBuffer,
					undo.TicketHash[:]...)
			} else {
				v.Expired = false
			}
			v.Missed = false
			restoredNode.missedTickets, err =
				safeDelete(restoredNode.missedTickets, k)
			if err != nil {
				return nil, err
			}
			restoredNode.liveTickets, err =
				safePut(restoredNode.liveTickets, k, v)
			if err != nil {
				return nil, err
			}

		// The ticket was spent.  Reinsert it into the live
		// tickets treap and add it to the list of next
		// winners.
		case undo.Spent:
			v.Spent = false
			restoredNode.nextWinners = append(restoredNode.nextWinners,
				undo.TicketHash)
			stateBuffer = append(stateBuffer, undo.TicketHash[:]...)
			restoredNode.liveTickets, err =
				safePut(restoredNode.liveTickets, k, v)
			if err != nil {
				return nil, err
			}

		default:
			return nil, stakeRuleError(ErrMemoryCorruption,
				"unknown ticket state in undo data")
		}
	}

	if node.height >= uint32(node.params.StakeValidationHeight) {
		phB, err := parentHeader.Bytes()
		if err != nil {
			return nil, err
		}
		prng := NewHash256PRNG(phB)
		_, err = findTicketIdxs(int64(restoredNode.liveTickets.Len()),
			int(node.params.TicketsPerBlock), prng)
		if err != nil {
			return nil, err
		}
		lastHash := prng.StateHash()
		stateBuffer = append(stateBuffer, lastHash[:]...)
		copy(restoredNode.finalState[:], chainhash.HashFuncB(stateBuffer)[0:6])
	}

	return restoredNode, nil
}
コード例 #6
0
ファイル: tickets.go プロジェクト: decred/dcrd
// connectNode connects a child to a parent stake node, returning the
// modified stake node for the child.  It is important to keep in mind that
// the argument node is the parent node, and that the child stake node is
// returned after subsequent modification of the parent node's immutable
// data.
func connectNode(node *Node, header wire.BlockHeader, ticketsSpentInBlock, revokedTickets, newTickets []chainhash.Hash) (*Node, error) {
	if node == nil {
		return nil, fmt.Errorf("missing stake node pointer input when connecting")
	}

	connectedNode := &Node{
		height:               node.height + 1,
		liveTickets:          node.liveTickets,
		missedTickets:        node.missedTickets,
		revokedTickets:       node.revokedTickets,
		databaseUndoUpdate:   make(UndoTicketDataSlice, 0),
		databaseBlockTickets: newTickets,
		nextWinners:          make([]chainhash.Hash, 0),
		params:               node.params,
	}

	// We only have to deal with voted related issues and expiry after
	// StakeEnabledHeight.
	var err error
	if connectedNode.height >= uint32(connectedNode.params.StakeEnabledHeight) {
		// Basic sanity check.
		for i := range ticketsSpentInBlock {
			if !hashInSlice(ticketsSpentInBlock[i], node.nextWinners) {
				return nil, stakeRuleError(ErrUnknownTicketSpent,
					fmt.Sprintf("unknown ticket %v spent in block",
						ticketsSpentInBlock[i]))
			}
		}

		// Iterate through all possible winners and construct the undo data,
		// updating the live and missed ticket treaps as necessary.  We need
		// to copy the value here so we don't modify it in the previous treap.
		for _, ticket := range node.nextWinners {
			k := tickettreap.Key(ticket)
			v, err := safeGet(connectedNode.liveTickets, k)
			if err != nil {
				return nil, err
			}

			// If it's spent in this block, mark it as being spent.  Otherwise,
			// it was missed.  Spent tickets are dropped from the live ticket
			// bucket, while missed tickets are pushed to the missed ticket
			// bucket.  Because we already know from the above check that the
			// ticket should still be in the live tickets treap, we probably
			// do not have to use the safe delete functions, but do so anyway
			// just to be safe.
			if hashInSlice(ticket, ticketsSpentInBlock) {
				v.Spent = true
				v.Missed = false
				connectedNode.liveTickets, err =
					safeDelete(connectedNode.liveTickets, k)
				if err != nil {
					return nil, err
				}
			} else {
				v.Spent = false
				v.Missed = true
				connectedNode.liveTickets, err =
					safeDelete(connectedNode.liveTickets, k)
				if err != nil {
					return nil, err
				}
				connectedNode.missedTickets, err =
					safePut(connectedNode.missedTickets, k, v)
				if err != nil {
					return nil, err
				}
			}

			connectedNode.databaseUndoUpdate =
				append(connectedNode.databaseUndoUpdate, ticketdb.UndoTicketData{
					TicketHash:   ticket,
					TicketHeight: v.Height,
					Missed:       v.Missed,
					Revoked:      v.Revoked,
					Spent:        v.Spent,
					Expired:      v.Expired,
				})
		}

		// Find the expiring tickets and drop them as well.  We already know what
		// the winners are from the cached information in the previous block, so
		// no drop the results of that here.
		toExpireHeight := uint32(0)
		if connectedNode.height > uint32(connectedNode.params.TicketExpiry) {
			toExpireHeight = connectedNode.height -
				uint32(connectedNode.params.TicketExpiry)
		}
		expired := fetchExpired(toExpireHeight, connectedNode.liveTickets)
		for _, treapKey := range expired {
			v, err := safeGet(connectedNode.liveTickets, *treapKey)
			if err != nil {
				return nil, err
			}
			v.Missed = true
			v.Expired = true
			connectedNode.liveTickets, err =
				safeDelete(connectedNode.liveTickets, *treapKey)
			if err != nil {
				return nil, err
			}
			connectedNode.missedTickets, err =
				safePut(connectedNode.missedTickets, *treapKey, v)
			if err != nil {
				return nil, err
			}

			ticketHash := chainhash.Hash(*treapKey)
			connectedNode.databaseUndoUpdate =
				append(connectedNode.databaseUndoUpdate, ticketdb.UndoTicketData{
					TicketHash:   ticketHash,
					TicketHeight: v.Height,
					Missed:       v.Missed,
					Revoked:      v.Revoked,
					Spent:        v.Spent,
					Expired:      v.Expired,
				})
		}

		// Process all the revocations, moving them from the missed to the
		// revoked treap and recording them in the undo data.
		for _, revokedTicket := range revokedTickets {
			v, err := safeGet(connectedNode.missedTickets,
				tickettreap.Key(revokedTicket))
			if err != nil {
				return nil, err
			}
			v.Revoked = true
			connectedNode.missedTickets, err =
				safeDelete(connectedNode.missedTickets,
					tickettreap.Key(revokedTicket))
			if err != nil {
				return nil, err
			}
			connectedNode.revokedTickets, err =
				safePut(connectedNode.revokedTickets,
					tickettreap.Key(revokedTicket), v)
			if err != nil {
				return nil, err
			}

			connectedNode.databaseUndoUpdate =
				append(connectedNode.databaseUndoUpdate, ticketdb.UndoTicketData{
					TicketHash:   revokedTicket,
					TicketHeight: v.Height,
					Missed:       v.Missed,
					Revoked:      v.Revoked,
					Spent:        v.Spent,
					Expired:      v.Expired,
				})
		}
	}

	// Add all the new tickets.
	for _, newTicket := range newTickets {
		k := tickettreap.Key(newTicket)
		v := &tickettreap.Value{
			Height:  connectedNode.height,
			Missed:  false,
			Revoked: false,
			Spent:   false,
			Expired: false,
		}
		connectedNode.liveTickets, err =
			safePut(connectedNode.liveTickets, k, v)
		if err != nil {
			return nil, err
		}

		connectedNode.databaseUndoUpdate =
			append(connectedNode.databaseUndoUpdate, ticketdb.UndoTicketData{
				TicketHash:   newTicket,
				TicketHeight: v.Height,
				Missed:       v.Missed,
				Revoked:      v.Revoked,
				Spent:        v.Spent,
				Expired:      v.Expired,
			})
	}

	// The first block voted on is at StakeEnabledHeight, so begin calculating
	// winners at the block before StakeEnabledHeight.
	if connectedNode.height >=
		uint32(connectedNode.params.StakeValidationHeight-1) {
		// Find the next set of winners.
		hB, err := header.Bytes()
		if err != nil {
			return nil, err
		}
		prng := NewHash256PRNG(hB)
		idxs, err := findTicketIdxs(int64(connectedNode.liveTickets.Len()),
			int(connectedNode.params.TicketsPerBlock), prng)
		if err != nil {
			return nil, err
		}

		stateBuffer := make([]byte, 0,
			(connectedNode.params.TicketsPerBlock+1)*chainhash.HashSize)
		nextWinnersKeys, err := fetchWinners(idxs, connectedNode.liveTickets)
		if err != nil {
			return nil, err
		}

		for _, treapKey := range nextWinnersKeys {
			ticketHash := chainhash.Hash(*treapKey)
			connectedNode.nextWinners = append(connectedNode.nextWinners,
				ticketHash)
			stateBuffer = append(stateBuffer, ticketHash[:]...)
		}
		lastHash := prng.StateHash()
		stateBuffer = append(stateBuffer, lastHash[:]...)
		copy(connectedNode.finalState[:], chainhash.HashFuncB(stateBuffer)[0:6])
	}

	return connectedNode, nil
}
コード例 #7
0
ファイル: tickets.go プロジェクト: decred/dcrd
// ExistsRevokedTicket returns whether or not a ticket exists in the revoked
// ticket treap for this stake node.
func (sn *Node) ExistsRevokedTicket(ticket chainhash.Hash) bool {
	return sn.revokedTickets.Has(tickettreap.Key(ticket))
}
コード例 #8
0
ファイル: tickets.go プロジェクト: decred/dcrd
// ExistsMissedTicket returns whether or not a ticket exists in the missed
// ticket treap for this stake node.
func (sn *Node) ExistsMissedTicket(ticket chainhash.Hash) bool {
	return sn.missedTickets.Has(tickettreap.Key(ticket))
}
コード例 #9
0
ファイル: tickets.go プロジェクト: decred/dcrd
// ExistsLiveTicket returns whether or not a ticket exists in the live ticket
// treap for this stake node.
func (sn *Node) ExistsLiveTicket(ticket chainhash.Hash) bool {
	return sn.liveTickets.Has(tickettreap.Key(ticket))
}
コード例 #10
0
ファイル: tickets_test.go プロジェクト: decred/dcrd
func TestTicketDBGeneral(t *testing.T) {
	// Declare some useful variables.
	testBCHeight := int64(168)
	filename := filepath.Join("..", "/../blockchain/testdata", "blocks0to168.bz2")
	fi, err := os.Open(filename)
	bcStream := bzip2.NewReader(fi)
	defer fi.Close()

	// Create a buffer of the read file.
	bcBuf := new(bytes.Buffer)
	bcBuf.ReadFrom(bcStream)

	// Create decoder from the buffer and a map to store the data.
	bcDecoder := gob.NewDecoder(bcBuf)
	testBlockchainBytes := make(map[int64][]byte)

	// Decode the blockchain into the map.
	if err := bcDecoder.Decode(&testBlockchainBytes); err != nil {
		t.Errorf("error decoding test blockchain")
	}
	testBlockchain := make(map[int64]*dcrutil.Block, len(testBlockchainBytes))
	for k, v := range testBlockchainBytes {
		bl, err := dcrutil.NewBlockFromBytes(v)
		if err != nil {
			t.Fatalf("couldn't decode block")
		}

		testBlockchain[k] = bl
	}

	// Create a new database to store the accepted stake node data into.
	dbName := "ffldb_staketest"
	dbPath := filepath.Join(testDbRoot, dbName)
	_ = os.RemoveAll(dbPath)
	testDb, err := database.Create(testDbType, dbPath, simNetParams.Net)
	if err != nil {
		t.Fatalf("error creating db: %v", err)
	}

	// Setup a teardown.
	defer os.RemoveAll(dbPath)
	defer os.RemoveAll(testDbRoot)
	defer testDb.Close()

	// Load the genesis block and begin testing exported functions.
	var bestNode *Node
	err = testDb.Update(func(dbTx database.Tx) error {
		var errLocal error
		bestNode, errLocal = InitDatabaseState(dbTx, simNetParams)
		if errLocal != nil {
			return errLocal
		}

		return nil
	})
	if err != nil {
		t.Fatalf(err.Error())
	}

	// Cache all of our nodes so that we can check them when we start
	// disconnecting and going backwards through the blockchain.
	nodesForward := make([]*Node, testBCHeight+1)
	loadedNodesForward := make([]*Node, testBCHeight+1)
	nodesForward[0] = bestNode
	loadedNodesForward[0] = bestNode
	err = testDb.Update(func(dbTx database.Tx) error {
		for i := int64(1); i <= testBCHeight; i++ {
			block := testBlockchain[i]
			ticketsToAdd := make([]chainhash.Hash, 0)
			if i >= simNetParams.StakeEnabledHeight {
				matureHeight := (i - int64(simNetParams.TicketMaturity))
				ticketsToAdd = ticketsInBlock(testBlockchain[matureHeight])
			}
			header := block.MsgBlock().Header
			if int(header.PoolSize) != len(bestNode.LiveTickets()) {
				t.Errorf("bad number of live tickets: want %v, got %v",
					header.PoolSize, len(bestNode.LiveTickets()))
			}
			if header.FinalState != bestNode.FinalState() {
				t.Errorf("bad final state: want %x, got %x",
					header.FinalState, bestNode.FinalState())
			}

			// In memory addition test.
			bestNode, err = bestNode.ConnectNode(header,
				ticketsSpentInBlock(block), revokedTicketsInBlock(block),
				ticketsToAdd)
			if err != nil {
				return fmt.Errorf("couldn't connect node: %v", err.Error())
			}

			// Write the new node to db.
			nodesForward[i] = bestNode
			blockSha := block.Sha()
			err := WriteConnectedBestNode(dbTx, bestNode, *blockSha)
			if err != nil {
				return fmt.Errorf("failure writing the best node: %v",
					err.Error())
			}

			// Reload the node from DB and make sure it's the same.
			blockHash := block.Sha()
			loadedNode, err := LoadBestNode(dbTx, bestNode.Height(),
				*blockHash, header, simNetParams)
			if err != nil {
				return fmt.Errorf("failed to load the best node: %v",
					err.Error())
			}
			err = nodesEqual(loadedNode, bestNode)
			if err != nil {
				return fmt.Errorf("loaded best node was not same as "+
					"in memory best node: %v", err.Error())
			}
			loadedNodesForward[i] = loadedNode
		}

		return nil
	})
	if err != nil {
		t.Fatalf(err.Error())
	}

	nodesBackward := make([]*Node, testBCHeight+1)
	nodesBackward[testBCHeight] = bestNode
	for i := testBCHeight; i >= int64(1); i-- {
		parentBlock := testBlockchain[i-1]
		ticketsToAdd := make([]chainhash.Hash, 0)
		if i >= simNetParams.StakeEnabledHeight {
			matureHeight := (i - 1 - int64(simNetParams.TicketMaturity))
			ticketsToAdd = ticketsInBlock(testBlockchain[matureHeight])
		}
		header := parentBlock.MsgBlock().Header
		blockUndoData := nodesForward[i-1].UndoData()
		formerBestNode := bestNode

		// In memory disconnection test.
		bestNode, err = bestNode.DisconnectNode(header, blockUndoData,
			ticketsToAdd, nil)
		if err != nil {
			t.Fatalf(err.Error())
		}

		err = nodesEqual(bestNode, nodesForward[i-1])
		if err != nil {
			t.Errorf("non-equiv stake nodes at height %v: %v", i-1, err.Error())
		}

		// Try again using the database instead of the in memory
		// data to disconnect the node, too.
		var bestNodeUsingDB *Node
		err = testDb.View(func(dbTx database.Tx) error {
			// Negative test.
			bestNodeUsingDB, err = formerBestNode.DisconnectNode(header, nil,
				nil, nil)
			if err == nil && formerBestNode.height > 1 {
				return fmt.Errorf("expected error when no in memory data " +
					"or dbtx is passed")
			}

			bestNodeUsingDB, err = formerBestNode.DisconnectNode(header, nil,
				nil, dbTx)
			if err != nil {
				return err
			}

			return nil
		})
		if err != nil {
			t.Errorf("couldn't disconnect using the database: %v",
				err.Error())
		}
		err = nodesEqual(bestNode, bestNodeUsingDB)
		if err != nil {
			t.Errorf("non-equiv stake nodes using db when disconnecting: %v",
				err.Error())
		}

		// Write the new best node to the database.
		nodesBackward[i-1] = bestNode
		err = testDb.Update(func(dbTx database.Tx) error {
			nodesForward[i] = bestNode
			parentBlockSha := parentBlock.Sha()
			err := WriteDisconnectedBestNode(dbTx, bestNode,
				*parentBlockSha, formerBestNode.UndoData())
			if err != nil {
				return fmt.Errorf("failure writing the best node: %v",
					err.Error())
			}

			return nil
		})
		if err != nil {
			t.Errorf("%s", err.Error())
		}

		// Check the best node against the loaded best node from
		// the database after.
		err = testDb.View(func(dbTx database.Tx) error {
			parentBlockHash := parentBlock.Sha()
			loadedNode, err := LoadBestNode(dbTx, bestNode.Height(),
				*parentBlockHash, header, simNetParams)
			if err != nil {
				return fmt.Errorf("failed to load the best node: %v",
					err.Error())
			}
			err = nodesEqual(loadedNode, bestNode)
			if err != nil {
				return fmt.Errorf("loaded best node was not same as "+
					"in memory best node: %v", err.Error())
			}
			err = nodesEqual(loadedNode, loadedNodesForward[i-1])
			if err != nil {
				return fmt.Errorf("loaded best node was not same as "+
					"previously cached node: %v", err.Error())
			}

			return nil
		})
		if err != nil {
			t.Errorf("%s", err.Error())
		}
	}

	// Unit testing the in-memory implementation negatively.
	b161 := testBlockchain[161]
	b162 := testBlockchain[162]
	n162Test := copyNode(nodesForward[162])

	// No node.
	_, err = connectNode(nil, b162.MsgBlock().Header,
		n162Test.SpentByBlock(), revokedTicketsInBlock(b162),
		n162Test.NewTickets())
	if err == nil {
		t.Errorf("expect error for no node")
	}

	// Best node missing ticket in live ticket bucket to spend.
	n161Copy := copyNode(nodesForward[161])
	n161Copy.liveTickets.Delete(tickettreap.Key(n162Test.SpentByBlock()[0]))
	_, err = n161Copy.ConnectNode(b162.MsgBlock().Header,
		n162Test.SpentByBlock(), revokedTicketsInBlock(b162),
		n162Test.NewTickets())
	if err == nil || err.(RuleError).GetCode() != ErrMissingTicket {
		t.Errorf("unexpected wrong or no error for "+
			"Best node missing ticket in live ticket bucket to spend: %v", err)
	}

	// Duplicate best winners.
	n161Copy = copyNode(nodesForward[161])
	n162Copy := copyNode(nodesForward[162])
	n161Copy.nextWinners[0] = n161Copy.nextWinners[1]
	spentInBlock := n162Copy.SpentByBlock()
	spentInBlock[0] = spentInBlock[1]
	_, err = n161Copy.ConnectNode(b162.MsgBlock().Header,
		spentInBlock, revokedTicketsInBlock(b162),
		n162Test.NewTickets())
	if err == nil || err.(RuleError).GetCode() != ErrMissingTicket {
		t.Errorf("unexpected wrong or no error for "+
			"Best node missing ticket in live ticket bucket to spend: %v", err)
	}

	// Test for corrupted spentInBlock.
	someHash := chainhash.HashFuncH([]byte{0x00})
	spentInBlock = n162Test.SpentByBlock()
	spentInBlock[4] = someHash
	_, err = nodesForward[161].ConnectNode(b162.MsgBlock().Header,
		spentInBlock, revokedTicketsInBlock(b162), n162Test.NewTickets())
	if err == nil || err.(RuleError).GetCode() != ErrUnknownTicketSpent {
		t.Errorf("unexpected wrong or no error for "+
			"Test for corrupted spentInBlock: %v", err)
	}

	// Corrupt winners.
	n161Copy = copyNode(nodesForward[161])
	n161Copy.nextWinners[4] = someHash
	_, err = n161Copy.ConnectNode(b162.MsgBlock().Header,
		spentInBlock, revokedTicketsInBlock(b162), n162Test.NewTickets())
	if err == nil || err.(RuleError).GetCode() != ErrMissingTicket {
		t.Errorf("unexpected wrong or no error for "+
			"Corrupt winners: %v", err)
	}

	// Unknown missed ticket.
	n162Copy = copyNode(nodesForward[162])
	spentInBlock = n162Copy.SpentByBlock()
	_, err = nodesForward[161].ConnectNode(b162.MsgBlock().Header,
		spentInBlock, append(revokedTicketsInBlock(b162), someHash),
		n162Copy.NewTickets())
	if err == nil || err.(RuleError).GetCode() != ErrMissingTicket {
		t.Errorf("unexpected wrong or no error for "+
			"Unknown missed ticket: %v", err)
	}

	// Insert a duplicate new ticket.
	spentInBlock = n162Test.SpentByBlock()
	newTicketsDup := []chainhash.Hash{someHash, someHash}
	_, err = nodesForward[161].ConnectNode(b162.MsgBlock().Header,
		spentInBlock, revokedTicketsInBlock(b162), newTicketsDup)
	if err == nil || err.(RuleError).GetCode() != ErrDuplicateTicket {
		t.Errorf("unexpected wrong or no error for "+
			"Insert a duplicate new ticket: %v", err)
	}

	// Impossible undo data for disconnecting.
	n161Copy = copyNode(nodesForward[161])
	n162Copy = copyNode(nodesForward[162])
	n162Copy.databaseUndoUpdate[0].Expired = false
	n162Copy.databaseUndoUpdate[0].Missed = false
	n162Copy.databaseUndoUpdate[0].Spent = false
	n162Copy.databaseUndoUpdate[0].Revoked = true
	_, err = n162Copy.DisconnectNode(b161.MsgBlock().Header,
		n161Copy.UndoData(), n161Copy.NewTickets(), nil)
	if err == nil {
		t.Errorf("unexpected wrong or no error for "+
			"Impossible undo data for disconnecting: %v", err)
	}

	// Missing undo data for disconnecting.
	n161Copy = copyNode(nodesForward[161])
	n162Copy = copyNode(nodesForward[162])
	n162Copy.databaseUndoUpdate = n162Copy.databaseUndoUpdate[0:3]
	_, err = n162Copy.DisconnectNode(b161.MsgBlock().Header,
		n161Copy.UndoData(), n161Copy.NewTickets(), nil)
	if err == nil {
		t.Errorf("unexpected wrong or no error for "+
			"Missing undo data for disconnecting: %v", err)
	}

	// Unknown undo data hash when disconnecting (missing).
	n161Copy = copyNode(nodesForward[161])
	n162Copy = copyNode(nodesForward[162])
	n162Copy.databaseUndoUpdate[0].TicketHash = someHash
	n162Copy.databaseUndoUpdate[0].Expired = false
	n162Copy.databaseUndoUpdate[0].Missed = true
	n162Copy.databaseUndoUpdate[0].Spent = false
	n162Copy.databaseUndoUpdate[0].Revoked = false
	_, err = n162Copy.DisconnectNode(b161.MsgBlock().Header,
		n161Copy.UndoData(), n161Copy.NewTickets(), nil)
	if err == nil || err.(RuleError).GetCode() != ErrMissingTicket {
		t.Errorf("unexpected wrong or no error for "+
			"Unknown undo data for disconnecting (missing): %v", err)
	}

	// Unknown undo data hash when disconnecting (revoked).
	n161Copy = copyNode(nodesForward[161])
	n162Copy = copyNode(nodesForward[162])
	n162Copy.databaseUndoUpdate[0].TicketHash = someHash
	n162Copy.databaseUndoUpdate[0].Expired = false
	n162Copy.databaseUndoUpdate[0].Missed = true
	n162Copy.databaseUndoUpdate[0].Spent = false
	n162Copy.databaseUndoUpdate[0].Revoked = true
	_, err = n162Copy.DisconnectNode(b161.MsgBlock().Header,
		n161Copy.UndoData(), n161Copy.NewTickets(), nil)
	if err == nil || err.(RuleError).GetCode() != ErrMissingTicket {
		t.Errorf("unexpected wrong or no error for "+
			"Unknown undo data for disconnecting (revoked): %v", err)
	}
}