// 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 }
// LoadBestNode is used when the blockchain is initialized, to get the initial // stake node from the database bucket. The blockchain must pass the height // and the blockHash to confirm that the ticket database is on the same // location in the blockchain as the blockchain itself. This function also // checks to ensure that the database has not failed the upgrade process and // reports the current version. Upgrades are also handled by this function, // when they become applicable. func LoadBestNode(dbTx database.Tx, height uint32, blockHash chainhash.Hash, header wire.BlockHeader, params *chaincfg.Params) (*Node, error) { info, err := ticketdb.DbFetchDatabaseInfo(dbTx) if err != nil { return nil, err } // Compare the tip and make sure it matches. state, err := ticketdb.DbFetchBestState(dbTx) if err != nil { return nil, err } if state.Hash != blockHash || state.Height != height { return nil, stakeRuleError(ErrDatabaseCorrupt, "best state corruption") } // Restore the best node treaps form the database. node := new(Node) node.height = height node.params = params node.liveTickets, err = ticketdb.DbLoadAllTickets(dbTx, dbnamespace.LiveTicketsBucketName) if err != nil { return nil, err } if node.liveTickets.Len() != int(state.Live) { return nil, stakeRuleError(ErrDatabaseCorrupt, fmt.Sprintf("live tickets corruption (got "+ "%v in state but loaded %v)", int(state.Live), node.liveTickets.Len())) } node.missedTickets, err = ticketdb.DbLoadAllTickets(dbTx, dbnamespace.MissedTicketsBucketName) if err != nil { return nil, err } if node.missedTickets.Len() != int(state.Missed) { return nil, stakeRuleError(ErrDatabaseCorrupt, fmt.Sprintf("missed tickets corruption (got "+ "%v in state but loaded %v)", int(state.Missed), node.missedTickets.Len())) } node.revokedTickets, err = ticketdb.DbLoadAllTickets(dbTx, dbnamespace.RevokedTicketsBucketName) if err != nil { return nil, err } if node.revokedTickets.Len() != int(state.Revoked) { return nil, stakeRuleError(ErrDatabaseCorrupt, fmt.Sprintf("revoked tickets corruption (got "+ "%v in state but loaded %v)", int(state.Revoked), node.revokedTickets.Len())) } // Restore the node undo and new tickets data. node.databaseUndoUpdate, err = ticketdb.DbFetchBlockUndoData(dbTx, height) if err != nil { return nil, err } node.databaseBlockTickets, err = ticketdb.DbFetchNewTickets(dbTx, height) if err != nil { return nil, err } // Restore the next winners for the node. node.nextWinners = make([]chainhash.Hash, 0) if node.height >= uint32(node.params.StakeValidationHeight-1) { node.nextWinners = make([]chainhash.Hash, len(state.NextWinners)) for i := range state.NextWinners { node.nextWinners[i] = state.NextWinners[i] } // Calculate the final state from the block header. stateBuffer := make([]byte, 0, (node.params.TicketsPerBlock+1)*chainhash.HashSize) for _, ticketHash := range node.nextWinners { stateBuffer = append(stateBuffer, ticketHash[:]...) } hB, err := header.Bytes() if err != nil { return nil, err } prng := NewHash256PRNG(hB) _, err = findTicketIdxs(int64(node.liveTickets.Len()), int(node.params.TicketsPerBlock), prng) if err != nil { return nil, err } lastHash := prng.StateHash() stateBuffer = append(stateBuffer, lastHash[:]...) copy(node.finalState[:], chainhash.HashFuncB(stateBuffer)[0:6]) } log.Infof("Stake database version %v loaded", info.Version) return node, nil }