func (s *Store) TicketDetails(ns walletdb.ReadBucket, txDetails *TxDetails) (*TicketDetails, error) {
	var ticketDetails = &TicketDetails{}
	if !stake.IsSStx(&txDetails.MsgTx) {
		return nil, nil
	}
	ticketDetails.Ticket = txDetails
	var spenderHash = chainhash.Hash{}
	_, credVal := existsCredit(ns, &txDetails.Hash, 0, &txDetails.Block.Block)
	if credVal != nil {
		if extractRawCreditIsSpent(credVal) {
			debKey := extractRawCreditSpenderDebitKey(credVal)
			debHash := extractRawDebitHash(debKey)
			copy(spenderHash[:], debHash)
		}
	} else {
		opKey := canonicalOutPoint(&txDetails.Hash, 0)
		spenderVal := existsRawUnminedInput(ns, opKey)
		if spenderVal != nil {
			copy(spenderHash[:], spenderVal)
		}
	}
	spenderDetails, err := s.TxDetails(ns, &spenderHash)
	if (err != nil) && (!errors.Is(err, errors.NotExist)) {
		return nil, err
	}
	ticketDetails.Spender = spenderDetails
	return ticketDetails, nil
}
TicketDetails looks up all recorded details regarding a ticket with some
hash.

Not finding a ticket with this hash is not an error.  In this case,
a nil TicketDetiails is returned.
package udb
import (
	"bytes"
	"context"
	"decred.org/dcrwallet/v2/errors"
	"decred.org/dcrwallet/v2/wallet/walletdb"
	"github.com/decred/dcrd/blockchain/stake/v4"
	"github.com/decred/dcrd/chaincfg/chainhash"
	"github.com/decred/dcrd/dcrutil/v4"
	"github.com/decred/dcrd/wire"
)
type CreditRecord struct {
	Index      uint32
	Amount     dcrutil.Amount
	Spent      bool
	Change     bool
	OpCode     uint8
	IsCoinbase bool
	HasExpiry  bool
}
type DebitRecord struct {
	Amount dcrutil.Amount
	Index  uint32
}
type TxDetails struct {
	TxRecord
	Block   BlockMeta
	Credits []CreditRecord
	Debits  []DebitRecord
}
func (t *TxDetails) Height() int32 {
	return t.Block.Block.Height
}
func (s *Store) minedTxDetails(ns walletdb.ReadBucket, txHash *chainhash.Hash, recKey, recVal []byte) (*TxDetails, error) {
	var details TxDetails
	err := readRawTxRecord(txHash, recVal, &details.TxRecord)
	if err != nil {
		return nil, err
	}
	err = readRawTxRecordBlock(recKey, &details.Block.Block)
	if err != nil {
		return nil, err
	}
	details.Block.Time, err = fetchBlockTime(ns, details.Block.Height)
	if err != nil {
		return nil, err
	}
	credIter := makeReadCreditIterator(ns, recKey, DBVersion)
	for credIter.next() {
		if int(credIter.elem.Index) >= len(details.MsgTx.TxOut) {
			credIter.close()
			return nil, errors.E(errors.IO, "saved credit index exceeds number of outputs")
		}
		if !credIter.elem.Spent {
			k := canonicalOutPoint(txHash, credIter.elem.Index)
			spent := existsRawUnminedInput(ns, k) != nil
			credIter.elem.Spent = spent
		}
		details.Credits = append(details.Credits, credIter.elem)
	}
	credIter.close()
	if credIter.err != nil {
		return nil, credIter.err
	}
	debIter := makeReadDebitIterator(ns, recKey)
	defer debIter.close()
	for debIter.next() {
		if int(debIter.elem.Index) >= len(details.MsgTx.TxIn) {
			return nil, errors.E(errors.IO, "saved debit index exceeds number of inputs")
		}
		details.Debits = append(details.Debits, debIter.elem)
	}
	return &details, debIter.err
}
func (s *Store) unminedTxDetails(ns walletdb.ReadBucket, txHash *chainhash.Hash, v []byte) (*TxDetails, error) {
	details := TxDetails{
		Block: BlockMeta{Block: Block{Height: -1}},
	}
	err := readRawTxRecord(txHash, v, &details.TxRecord)
	if err != nil {
		return nil, err
	}
	it := makeReadUnminedCreditIterator(ns, txHash, DBVersion)
	defer it.close()
	for it.next() {
		if int(it.elem.Index) >= len(details.MsgTx.TxOut) {
			return nil, errors.E(errors.IO, errors.Errorf("credit output index %d does not exist for tx %v", it.elem.Index, txHash))
		}
		it.elem.Spent = existsRawUnminedInput(ns, it.ck) != nil
		details.Credits = append(details.Credits, it.elem)
	}
	if it.err != nil {
		return nil, it.err
	}
	for i, output := range details.MsgTx.TxIn {
		opKey := canonicalOutPoint(&output.PreviousOutPoint.Hash,
			output.PreviousOutPoint.Index)
		credKey := existsRawUnspent(ns, opKey)
		if credKey != nil {
			v := existsRawCredit(ns, credKey)
			amount, err := fetchRawCreditAmount(v)
			if err != nil {
				return nil, err
			}
			details.Debits = append(details.Debits, DebitRecord{
				Amount: amount,
				Index:  uint32(i),
			})
			continue
		}
		v := existsRawUnminedCredit(ns, opKey)
		if v == nil {
			continue
		}
		amount, err := fetchRawCreditAmount(v)
		if err != nil {
			return nil, err
		}
		details.Debits = append(details.Debits, DebitRecord{
			Amount: amount,
			Index:  uint32(i),
		})
	}
	return &details, nil
}
func (s *Store) TxDetails(ns walletdb.ReadBucket, txHash *chainhash.Hash) (*TxDetails, error) {
	v := existsRawUnmined(ns, txHash[:])
	if v != nil {
		return s.unminedTxDetails(ns, txHash, v)
	}
	k, v := latestTxRecord(ns, txHash[:])
	if v == nil {
		return nil, errors.E(errors.NotExist)
	}
	return s.minedTxDetails(ns, txHash, k, v)
}
type TicketDetails struct {
	Ticket  *TxDetails
	Spender *TxDetails
}