// WriteDisconnectedBestNode writes the newly connected best node to the database // under an atomic database transaction, performing all the necessary writes to // reverse the contents of the database buckets for live, missed, and revoked // tickets. It does so by using the parent's block undo data to restore the // original state in the database. It also drops new ticket and reversion data // for any nodes that have a height higher than this one after. func WriteDisconnectedBestNode(dbTx database.Tx, node *Node, hash chainhash.Hash, childUndoData UndoTicketDataSlice) error { // Load the last best node and check to see if its height is above the // current node. If it is, drop all reversion data above this incoming // node. formerBest, err := ticketdb.DbFetchBestState(dbTx) if err != nil { return err } if formerBest.Height > node.height { for i := formerBest.Height; i > node.height; i-- { err := ticketdb.DbDropBlockUndoData(dbTx, i) if err != nil { return err } err = ticketdb.DbDropNewTickets(dbTx, i) if err != nil { return err } } } // Iterate through the block undo data and write all database // changes to the respective on-disk map, reversing all the // changes added when the child block was added to the block // chain. for _, undo := range childUndoData { var err error 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: err = ticketdb.DbDeleteTicket(dbTx, dbnamespace.LiveTicketsBucketName, &undo.TicketHash) if err != nil { return 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: err = ticketdb.DbDeleteTicket(dbTx, dbnamespace.RevokedTicketsBucketName, &undo.TicketHash) if err != nil { return err } err = ticketdb.DbPutTicket(dbTx, dbnamespace.MissedTicketsBucketName, &undo.TicketHash, undo.TicketHeight, undo.Missed, false, undo.Spent, undo.Expired) if err != nil { return err } // The ticket was missed and was previously live. // Remove it from the missed tickets bucket and // move it to the live tickets bucket. We don't // know if it was expired or not, so just set that // flag to false. case undo.Missed && !undo.Revoked: err = ticketdb.DbDeleteTicket(dbTx, dbnamespace.MissedTicketsBucketName, &undo.TicketHash) if err != nil { return err } err = ticketdb.DbPutTicket(dbTx, dbnamespace.LiveTicketsBucketName, &undo.TicketHash, undo.TicketHeight, false, undo.Revoked, undo.Spent, false) if err != nil { return err } // The ticket was spent. Reinsert it into the live // tickets treap. case undo.Spent: err = ticketdb.DbPutTicket(dbTx, dbnamespace.LiveTicketsBucketName, &undo.TicketHash, undo.TicketHeight, undo.Missed, undo.Revoked, false, undo.Expired) if err != nil { return err } default: return stakeRuleError(ErrMemoryCorruption, "unknown ticket state in undo data") } } // Write the new block undo and new tickets data to the // database for the given height, potentially overwriting // an old entry with the new data. err = ticketdb.DbPutBlockUndoData(dbTx, node.height, node.databaseUndoUpdate) if err != nil { return err } err = ticketdb.DbPutNewTickets(dbTx, node.height, node.databaseBlockTickets) if err != nil { return err } // Write the new best state to the database. nextWinners := make([]chainhash.Hash, int(node.params.TicketsPerBlock)) if node.height >= uint32(node.params.StakeValidationHeight-1) { for i := range nextWinners { nextWinners[i] = node.nextWinners[i] } } err = ticketdb.DbPutBestState(dbTx, ticketdb.BestChainState{ Hash: hash, Height: node.height, Live: uint32(node.liveTickets.Len()), Missed: uint64(node.missedTickets.Len()), Revoked: uint64(node.revokedTickets.Len()), PerBlock: node.params.TicketsPerBlock, NextWinners: nextWinners, }) if err != nil { return err } return nil }
// WriteConnectedBestNode writes the newly connected best node to the database // under an atomic database transaction, performing all the necessary writes to // the database buckets for live, missed, and revoked tickets. func WriteConnectedBestNode(dbTx database.Tx, node *Node, hash chainhash.Hash) error { // Iterate through the block undo data and write all database // changes to the respective on-disk map. for _, undo := range node.databaseUndoUpdate { var err error switch { // All flags are unset; this is a newly added ticket. // Insert it into the live ticket database. case !undo.Missed && !undo.Revoked && !undo.Spent: err = ticketdb.DbPutTicket(dbTx, dbnamespace.LiveTicketsBucketName, &undo.TicketHash, undo.TicketHeight, undo.Missed, undo.Revoked, undo.Spent, undo.Expired) if err != nil { return err } // The ticket was missed and revoked. It needs to // be moved from the missed ticket bucket to the // revoked ticket bucket. case undo.Missed && undo.Revoked: err = ticketdb.DbDeleteTicket(dbTx, dbnamespace.MissedTicketsBucketName, &undo.TicketHash) if err != nil { return err } err = ticketdb.DbPutTicket(dbTx, dbnamespace.RevokedTicketsBucketName, &undo.TicketHash, undo.TicketHeight, undo.Missed, undo.Revoked, undo.Spent, undo.Expired) if err != nil { return err } // The ticket was missed and was previously live. // Move it from the live ticket bucket to the missed // ticket bucket. case undo.Missed && !undo.Revoked: err = ticketdb.DbDeleteTicket(dbTx, dbnamespace.LiveTicketsBucketName, &undo.TicketHash) if err != nil { return err } err = ticketdb.DbPutTicket(dbTx, dbnamespace.MissedTicketsBucketName, &undo.TicketHash, undo.TicketHeight, true, undo.Revoked, undo.Spent, undo.Expired) if err != nil { return err } // The ticket was spent. Remove it from the live // ticket bucket. case undo.Spent: err = ticketdb.DbDeleteTicket(dbTx, dbnamespace.LiveTicketsBucketName, &undo.TicketHash) if err != nil { return err } default: return stakeRuleError(ErrMemoryCorruption, "unknown ticket state in undo data") } } // Write the new block undo and new tickets data to the // database for the given height, potentially overwriting // an old entry with the new data. err := ticketdb.DbPutBlockUndoData(dbTx, node.height, node.databaseUndoUpdate) if err != nil { return err } err = ticketdb.DbPutNewTickets(dbTx, node.height, node.databaseBlockTickets) if err != nil { return err } // Write the new best state to the database. nextWinners := make([]chainhash.Hash, int(node.params.TicketsPerBlock)) if node.height >= uint32(node.params.StakeValidationHeight-1) { for i := range nextWinners { nextWinners[i] = node.nextWinners[i] } } err = ticketdb.DbPutBestState(dbTx, ticketdb.BestChainState{ Hash: hash, Height: node.height, Live: uint32(node.liveTickets.Len()), Missed: uint64(node.missedTickets.Len()), Revoked: uint64(node.revokedTickets.Len()), PerBlock: node.params.TicketsPerBlock, NextWinners: nextWinners, }) if err != nil { return err } return nil }