// 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 }
// 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 }
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") } }
// 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") } }
// 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 }
// 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 }
// 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)) }
// 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)) }
// 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)) }
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) } }