func putUnspentCredit(ns walletdb.ReadWriteBucket, cred *credit, scrType scriptType,
	scrLoc uint32, scrLen uint32, account uint32, dbVersion uint32) error {
	k := keyCredit(&cred.outPoint.Hash, cred.outPoint.Index, &cred.block)
	v := valueUnspentCredit(cred, scrType, scrLoc, scrLen, account, dbVersion)
	return putRawCredit(ns, k, v)
}
putUnspentCredit puts a credit record for an unspent credit.  It may only be
used when the credit is already know to be unspent, or spent by an
unconfirmed transaction.
package udb
import (
	"bytes"
	"encoding/binary"
	"runtime/debug"
	"time"
	"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/chaincfg/v3"
	"github.com/decred/dcrd/crypto/ripemd160"
	"github.com/decred/dcrd/dcrutil/v4"
	"github.com/decred/dcrd/txscript/v4"
	"github.com/decred/dcrd/wire"
)
const (
	accountExistsMask = uint8(0x80)
)
type scriptType uint8
const (
	scriptTypeNonexisting = iota
	scriptTypeUnspecified
	scriptTypeP2PKH
	scriptTypeP2PK
	scriptTypeP2PKHAlt
	scriptTypeP2PKAlt
	scriptTypeP2SH
	scriptTypeSP2PKH
	scriptTypeSP2SH
)
const (
	scriptLocNotStored = 0
)
var byteOrder = binary.BigEndian
var _ [32]byte = chainhash.Hash{}
var (
	bucketBlocks                  = []byte("b")
	bucketHeaders                 = []byte("h")
	bucketTxRecords               = []byte("t")
	bucketCredits                 = []byte("c")
	bucketUnspent                 = []byte("u")
	bucketDebits                  = []byte("d")
	bucketUnmined                 = []byte("m")
	bucketUnpublished             = []byte("up")
	bucketUnminedCredits          = []byte("mc")
	bucketUnminedInputs           = []byte("mi")
	bucketTickets                 = []byte("tix")
	bucketScripts                 = []byte("sc")  
	bucketMultisig                = []byte("ms")
	bucketMultisigUsp             = []byte("mu")
	bucketStakeInvalidatedCredits = []byte("ic")
	bucketStakeInvalidatedDebits  = []byte("id")
	bucketCFilters                = []byte("cf")
	bucketTicketCommitments       = []byte("cmt")
	bucketTicketCommitmentsUsp    = []byte("cmu")
)
var (
	rootCreateDate   = []byte("date")
	rootVersion      = []byte("vers")
	rootMinedBalance = []byte("bal")
	rootTipBlock     = []byte("tip")
	rootHaveCFilters = []byte("havecfilters")
	rootLastTxsBlock = []byte("lasttxsblock")
	rootVSPHostIndex = []byte("vsphostindex")
)
func fetchMinedBalance(ns walletdb.ReadBucket) (dcrutil.Amount, error) {
	v := ns.Get(rootMinedBalance)
	if len(v) != 8 {
		return 0, errors.E(errors.IO, errors.Errorf("mined balance len %d", len(v)))
	}
	return dcrutil.Amount(byteOrder.Uint64(v)), nil
}
func putMinedBalance(ns walletdb.ReadWriteBucket, amt dcrutil.Amount) error {
	v := make([]byte, 8)
	byteOrder.PutUint64(v, uint64(amt))
	err := ns.Put(rootMinedBalance, v)
	if err != nil {
		return errors.E(errors.IO, err)
	}
	return nil
}
func canonicalOutPoint(txHash *chainhash.Hash, index uint32) []byte {
	k := make([]byte, 36)
	copy(k, txHash[:])
	byteOrder.PutUint32(k[32:36], index)
	return k
}
func readCanonicalOutPoint(k []byte, op *wire.OutPoint) error {
	if len(k) < 36 {
		return errors.E(errors.IO, errors.Errorf("outpoint len %d", len(k)))
	}
	copy(op.Hash[:], k)
	op.Index = byteOrder.Uint32(k[32:36])
	return nil
}
func keyBlockRecord(height int32) []byte {
	k := make([]byte, 4)
	byteOrder.PutUint32(k, uint32(height))
	return k
}
func valueBlockRecordEmptyFromHeader(blockHash []byte, header []byte) []byte {
	v := make([]byte, 47)
	copy(v, blockHash[:])
	byteOrder.PutUint64(v[32:40], uint64(extractBlockHeaderUnixTime(header[:])))
	byteOrder.PutUint16(v[40:42], extractBlockHeaderVoteBits(header[:]))
	byteOrder.PutUint32(v[43:47], 0)
	return v
}
func valueBlockRecordStakeValidated(v []byte) []byte {
	newv := make([]byte, len(v))
	copy(newv, v[:42])
	copy(newv[43:], v[43:])
	return newv
}
func valueBlockRecordStakeInvalidated(v []byte) []byte {
	newv := make([]byte, len(v))
	copy(newv, v[:42])
	newv[42] = 1
	copy(newv[43:], v[43:])
	return newv
}
func appendRawBlockRecord(v []byte, txHash *chainhash.Hash) ([]byte, error) {
	if len(v) < 47 {
		return nil, errors.E(errors.IO, errors.Errorf("block record len %d", len(v)))
	}
	newv := append(v[:len(v):len(v)], txHash[:]...)
	n := byteOrder.Uint32(newv[43:47])
	byteOrder.PutUint32(newv[43:47], n+1)
	return newv, nil
}
func putRawBlockRecord(ns walletdb.ReadWriteBucket, k, v []byte) error {
	err := ns.NestedReadWriteBucket(bucketBlocks).Put(k, v)
	if err != nil {
		return errors.E(errors.IO, err)
	}
	return nil
}
func fetchBlockTime(ns walletdb.ReadBucket, height int32) (time.Time, error) {
	k := keyBlockRecord(height)
	v := ns.NestedReadBucket(bucketBlocks).Get(k)
	if len(v) < 47 {
		return time.Time{}, errors.E(errors.IO, errors.Errorf("block record len %d", len(v)))
	}
	return time.Unix(int64(byteOrder.Uint64(v[32:40])), 0), nil
}
func fetchBlockRecord(ns walletdb.ReadBucket, height int32) (*blockRecord, error) {
	br := &blockRecord{}
	k := keyBlockRecord(height)
	v := ns.NestedReadBucket(bucketBlocks).Get(k)
	err := readRawBlockRecord(k, v, br)
	return br, err
}
func existsBlockRecord(ns walletdb.ReadBucket, height int32) (k, v []byte) {
	k = keyBlockRecord(height)
	v = ns.NestedReadBucket(bucketBlocks).Get(k)
	return
}
func readRawBlockRecord(k, v []byte, block *blockRecord) error {
	if len(k) < 4 {
		return errors.E(errors.IO, errors.Errorf("block key len %d", len(k)))
	}
	if len(v) < 47 {
		return errors.E(errors.IO, errors.Errorf("block record len %d", len(k)))
	}
	numTransactions := int(byteOrder.Uint32(v[43:47]))
	expectedLen := 47 + chainhash.HashSize*numTransactions
	if len(v) < expectedLen {
		return errors.E(errors.IO, errors.Errorf("%d tx block record len %d",
			numTransactions, len(v)))
	}
	block.Height = int32(byteOrder.Uint32(k))
	copy(block.Hash[:], v)
	block.Time = time.Unix(int64(byteOrder.Uint64(v[32:40])), 0)
	block.VoteBits = byteOrder.Uint16(v[40:42])
	block.transactions = make([]chainhash.Hash, numTransactions)
	off := 47
	for i := range block.transactions {
		copy(block.transactions[i][:], v[off:])
		off += chainhash.HashSize
	}
	return nil
}
func extractRawBlockRecordHash(v []byte) []byte {
	return v[:32]
}
func extractRawBlockRecordStakeInvalid(v []byte) bool {
	return v[42] != 0
}
type blockIterator struct {
	c    walletdb.ReadWriteCursor
	seek []byte
	ck   []byte
	cv   []byte
	elem blockRecord
	err  error
}
func makeReadBlockIterator(ns walletdb.ReadBucket, height int32) blockIterator {
	seek := make([]byte, 4)
	byteOrder.PutUint32(seek, uint32(height))
	c := ns.NestedReadBucket(bucketBlocks).ReadCursor()
	return blockIterator{c: readCursor{c}, seek: seek}
}
func makeReverseBlockIterator(ns walletdb.ReadWriteBucket) blockIterator {
	seek := make([]byte, 4)
	byteOrder.PutUint32(seek, ^uint32(0))
	c := ns.NestedReadWriteBucket(bucketBlocks).ReadWriteCursor()
	return blockIterator{c: c, seek: seek}
}
func (it *blockIterator) next() bool {
	if it.c == nil {
		return false
	}
	if it.ck == nil {
		it.ck, it.cv = it.c.Seek(it.seek)
	} else {
		it.ck, it.cv = it.c.Next()
	}
	if it.ck == nil {
		it.c.Close()
		it.c = nil
		return false
	}
	err := readRawBlockRecord(it.ck, it.cv, &it.elem)
	if err != nil {
		it.c = nil
		it.err = err
		return false
	}
	return true
}
func (it *blockIterator) prev() bool {
	if it.c == nil {
		return false
	}
	if it.ck == nil {
		it.ck, it.cv = it.c.Seek(it.seek)
		if !bytes.HasPrefix(it.ck, it.seek) {
			it.ck, it.cv = it.c.Prev()
		}
	} else {
		it.ck, it.cv = it.c.Prev()
	}
	if it.ck == nil {
		it.c.Close()
		it.c = nil
		return false
	}
	err := readRawBlockRecord(it.ck, it.cv, &it.elem)
	if err != nil {
		it.c.Close()
		it.c = nil
		it.err = err
		return false
	}
	return true
}
func (it *blockIterator) close() {
	if it.c == nil {
		return
	}
	it.c.Close()
}
func (it *blockIterator) reposition(height int32) {
	it.c.Seek(keyBlockRecord(height))
}
func deleteBlockRecord(ns walletdb.ReadWriteBucket, height int32) error {
	k := keyBlockRecord(height)
	return ns.NestedReadWriteBucket(bucketBlocks).Delete(k)
}
func keyBlockHeader(blockHash *chainhash.Hash) []byte { return blockHash[:] }
func putRawBlockHeader(ns walletdb.ReadWriteBucket, k, v []byte) error {
	err := ns.NestedReadWriteBucket(bucketHeaders).Put(k, v)
	if err != nil {
		return errors.E(errors.IO, err)
	}
	return nil
}
func fetchRawBlockHeader(ns walletdb.ReadBucket, k []byte) ([]byte, error) {
	v := ns.NestedReadBucket(bucketHeaders).Get(k)
	if v == nil {
		return nil, errors.E(errors.NotExist, "block header")
	}
	vcopy := make([]byte, len(v))
	copy(vcopy, v)
	return vcopy, nil
}
func existsBlockHeader(ns walletdb.ReadBucket, k []byte) []byte {
	return ns.NestedReadBucket(bucketHeaders).Get(k)
}
func keyTxRecord(txHash *chainhash.Hash, block *Block) []byte {
	k := make([]byte, 68)
	copy(k, txHash[:])
	byteOrder.PutUint32(k[32:36], uint32(block.Height))
	copy(k[36:68], block.Hash[:])
	return k
}
func valueTxRecord(rec *TxRecord) ([]byte, error) {
	var v []byte
	if rec.SerializedTx == nil {
		txSize := rec.MsgTx.SerializeSize()
		v = make([]byte, 8, 8+txSize)
		err := rec.MsgTx.Serialize(bytes.NewBuffer(v[8:]))
		if err != nil {
			return nil, errors.E(errors.Invalid, err)
		}
		v = v[:cap(v)]
	} else {
		v = make([]byte, 8+len(rec.SerializedTx))
		copy(v[8:], rec.SerializedTx)
	}
	byteOrder.PutUint64(v, uint64(rec.Received.Unix()))
	return v, nil
}
func putTxRecord(ns walletdb.ReadWriteBucket, rec *TxRecord, block *Block) error {
	k := keyTxRecord(&rec.Hash, block)
	v, err := valueTxRecord(rec)
	if err != nil {
		return err
	}
	err = ns.NestedReadWriteBucket(bucketTxRecords).Put(k, v)
	if err != nil {
		return errors.E(errors.IO, err)
	}
	return nil
}
func putRawTxRecord(ns walletdb.ReadWriteBucket, k, v []byte) error {
	err := ns.NestedReadWriteBucket(bucketTxRecords).Put(k, v)
	if err != nil {
		return errors.E(errors.IO, err)
	}
	return nil
}
func readRawTxRecordMsgTx(txHash *chainhash.Hash, v []byte, msgTx *wire.MsgTx) error {
	if len(v) < 8 {
		return errors.E(errors.IO, errors.Errorf("tx record len %d", len(v)))
	}
	err := msgTx.Deserialize(bytes.NewReader(v[8:]))
	if err != nil {
		return errors.E(errors.IO, err)
	}
	return nil
}
func readRawTxRecord(txHash *chainhash.Hash, v []byte, rec *TxRecord) error {
	if len(v) < 8 {
		return errors.E(errors.IO, errors.Errorf("tx record len %d", len(v)))
	}
	rec.Hash = *txHash
	rec.Received = time.Unix(int64(byteOrder.Uint64(v)), 0)
	err := rec.MsgTx.Deserialize(bytes.NewReader(v[8:]))
	if err != nil {
		return errors.E(errors.IO, err)
	}
	rec.TxType = stake.DetermineTxType(&rec.MsgTx, true, false)
	return nil
}
func readRawTxRecordHash(k []byte, hash *chainhash.Hash) error {
	if len(k) < 68 {
		debug.PrintStack()
		return errors.E(errors.IO, errors.Errorf("tx record key len %d", len(k)))
	}
	copy(hash[:], k[:32])
	return nil
}
func readRawTxRecordBlockHeight(k []byte, height *int32) error {
	if len(k) < 68 {
		debug.PrintStack()
		return errors.E(errors.IO, errors.Errorf("tx record key len %d", len(k)))
	}
	*height = int32(byteOrder.Uint32(k[32:36]))
	return nil
}
func readRawTxRecordBlock(k []byte, block *Block) error {
	if len(k) < 68 {
		debug.PrintStack()
		return errors.E(errors.IO, errors.Errorf("tx record key len %d", len(k)))
	}
	block.Height = int32(byteOrder.Uint32(k[32:36]))
	copy(block.Hash[:], k[36:68])
	return nil
}
func fetchRawTxRecordPkScript(k, v []byte, index uint32, scrLoc uint32, scrLen uint32) ([]byte, error) {
	var pkScript []byte
	var txHash chainhash.Hash
	err := readRawTxRecordHash(k, &txHash)
	if err != nil {
		return nil, err
	}
	if scrLoc == scriptLocNotStored {
		var rec TxRecord
		err = readRawTxRecord(&txHash, v, &rec)
		if err != nil {
			return nil, err
		}
		if int(index) >= len(rec.MsgTx.TxOut) {
			return nil, errors.E(errors.IO, "missing transaction output for credit index")
		}
		pkScript = rec.MsgTx.TxOut[index].PkScript
	} else {
		scrLocInt := int(scrLoc) + 8
		scrLenInt := int(scrLen)
		if scrLocInt > len(v)-1 || scrLocInt+scrLenInt > len(v) {
			return nil, errors.E(errors.IO, errors.Errorf(
				"invalid script offset %d for tx %v", scrLocInt, &txHash))
		}
		pkScript = make([]byte, scrLenInt)
		copy(pkScript, v[scrLocInt:scrLocInt+scrLenInt])
	}
	return pkScript, nil
}
func fetchRawTxRecordReceived(v []byte) time.Time {
	return time.Unix(int64(byteOrder.Uint64(v)), 0)
}
func existsTxRecord(ns walletdb.ReadBucket, txHash *chainhash.Hash, block *Block) (k, v []byte) {
	k = keyTxRecord(txHash, block)
	v = ns.NestedReadBucket(bucketTxRecords).Get(k)
	return
}
func existsRawTxRecord(ns walletdb.ReadBucket, k []byte) (v []byte) {
	return ns.NestedReadBucket(bucketTxRecords).Get(k)
}
func deleteTxRecord(ns walletdb.ReadWriteBucket, txHash *chainhash.Hash, block *Block) error {
	k := keyTxRecord(txHash, block)
	err := ns.NestedReadWriteBucket(bucketTxRecords).Delete(k)
	if err != nil {
		return errors.E(errors.IO, err)
	}
	return nil
}
func latestTxRecord(ns walletdb.ReadBucket, txHash []byte) (k, v []byte) {
	c := ns.NestedReadBucket(bucketTxRecords).ReadCursor()
	ck, cv := c.Seek(txHash)
	var lastKey, lastVal []byte
	for bytes.HasPrefix(ck, txHash) {
		lastKey, lastVal = ck, cv
		ck, cv = c.Next()
	}
	c.Close()
	return lastKey, lastVal
}
const (
	creditKeySize = 72
	creditValueSize = 94
)
func keyCredit(txHash *chainhash.Hash, index uint32, block *Block) []byte {
	k := make([]byte, creditKeySize)
	copy(k, txHash[:])
	byteOrder.PutUint32(k[32:36], uint32(block.Height))
	copy(k[36:68], block.Hash[:])
	byteOrder.PutUint32(k[68:72], index)
	return k
}
func condenseOpCode(opCode uint8) byte {
	switch {
	case opCode == txscript.OP_TGEN:
		return 0x1c
	default:
		return (opCode - 0xb9) << 2
	}
}
func expandOpCode(opCodeFlag byte) uint8 {
	mask := byte(0x1c)
	switch {
	case opCodeFlag&mask == 0x1c:
		return txscript.OP_TGEN
	default:
		return uint8(((opCodeFlag >> 2) & 0x07) + 0xb9)
	}
}
func valueUnspentCredit(cred *credit, scrType scriptType, scrLoc uint32,
	scrLen uint32, account uint32, dbVersion uint32) []byte {
	v := make([]byte, creditValueSize)
	byteOrder.PutUint64(v, uint64(cred.amount))
	v[8] = condenseOpCode(cred.opCode)
	if cred.change {
		v[8] |= 1 << 1
	}
	if cred.isCoinbase {
		v[8] |= 1 << 5
	}
	if cred.hasExpiry {
		switch {
		case dbVersion >= hasExpiryFixedVersion:
			v[8] |= 1 << 6
		case dbVersion >= hasExpiryVersion:
			v[8] |= 1 << 4
		}
	}
	v[81] = byte(scrType)
	v[81] |= accountExistsMask
	byteOrder.PutUint32(v[82:86], scrLoc)
	byteOrder.PutUint32(v[86:90], scrLen)
	byteOrder.PutUint32(v[90:94], account)
	return v
}
func putRawCredit(ns walletdb.ReadWriteBucket, k, v []byte) error {
	err := ns.NestedReadWriteBucket(bucketCredits).Put(k, v)
	if err != nil {
		return errors.E(errors.IO, err)
	}
	return nil
}