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