func (w *Wallet) compressWallet(ctx context.Context, op errors.Op, maxNumIns int, account uint32, changeAddr stdaddr.Address) (*chainhash.Hash, error) {
	defer w.lockedOutpointMu.Unlock()
	w.lockedOutpointMu.Lock()
	var hash *chainhash.Hash
	err := walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error {
		var err error
		hash, err = w.compressWalletInternal(ctx, op, dbtx, maxNumIns, account, changeAddr)
		return err
	})
	if err != nil {
		return nil, errors.E(op, err)
	}
	return hash, nil
}
compressWallet compresses all the utxos in a wallet into a single change
address. For use when it becomes dusty.
package wallet
import (
	"context"
	"crypto/rand"
	"crypto/tls"
	"encoding/binary"
	"net"
	"sort"
	"time"
	"decred.org/cspp"
	"decred.org/cspp/coinjoin"
	"decred.org/dcrwallet/v2/errors"
	"decred.org/dcrwallet/v2/wallet/txauthor"
	"decred.org/dcrwallet/v2/wallet/txrules"
	"decred.org/dcrwallet/v2/wallet/txsizes"
	"decred.org/dcrwallet/v2/wallet/udb"
	"decred.org/dcrwallet/v2/wallet/walletdb"
	"github.com/decred/dcrd/blockchain/stake/v4"
	blockchain "github.com/decred/dcrd/blockchain/standalone/v2"
	"github.com/decred/dcrd/chaincfg/chainhash"
	"github.com/decred/dcrd/chaincfg/v3"
	"github.com/decred/dcrd/dcrec"
	"github.com/decred/dcrd/dcrutil/v4"
	"github.com/decred/dcrd/txscript/v4"
	"github.com/decred/dcrd/txscript/v4/sign"
	"github.com/decred/dcrd/txscript/v4/stdaddr"
	"github.com/decred/dcrd/wire"
)
const (
	revocationFeeLimit = 1 << 14
	maxStandardTxSize = 100000
	sanityVerifyFlags = txscript.ScriptDiscourageUpgradableNops |
		txscript.ScriptVerifyCleanStack |
		txscript.ScriptVerifyCheckLockTimeVerify |
		txscript.ScriptVerifyCheckSequenceVerify |
		txscript.ScriptVerifyTreasury
)
type Input struct {
	OutPoint wire.OutPoint
	PrevOut  wire.TxOut
}
type OutputSelectionAlgorithm uint
const (
	OutputSelectionAlgorithmDefault = iota
	OutputSelectionAlgorithmAll
)
func (w *Wallet) NewUnsignedTransaction(ctx context.Context, outputs []*wire.TxOut,
	relayFeePerKb dcrutil.Amount, account uint32, minConf int32,
	algo OutputSelectionAlgorithm, changeSource txauthor.ChangeSource, inputSource txauthor.InputSource) (*txauthor.AuthoredTx, error) {
	const op errors.Op = "wallet.NewUnsignedTransaction"
	ignoreInput := func(op *wire.OutPoint) bool {
		_, ok := w.lockedOutpoints[outpoint{op.Hash, op.Index}]
		return ok
	}
	defer w.lockedOutpointMu.Unlock()
	w.lockedOutpointMu.Lock()
	var authoredTx *txauthor.AuthoredTx
	var changeSourceUpdates []func(walletdb.ReadWriteTx) error
	err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error {
		addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
		txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
		_, tipHeight := w.txStore.MainChainTip(dbtx)
		if account != udb.ImportedAddrAccount {
			lastAcct, err := w.manager.LastAccount(addrmgrNs)
			if err != nil {
				return err
			}
			if account > lastAcct {
				return errors.E(errors.NotExist, "missing account")
			}
		}
		if inputSource == nil {
			sourceImpl := w.txStore.MakeInputSource(txmgrNs, addrmgrNs, account,
				minConf, tipHeight, ignoreInput)
			switch algo {
			case OutputSelectionAlgorithmDefault:
				inputSource = randomInputSource(sourceImpl.SelectInputs)
			case OutputSelectionAlgorithmAll:
				inputSource = func(dcrutil.Amount) (*txauthor.InputDetail, error) {
					inputDetail, err := sourceImpl.SelectInputs(dcrutil.MaxAmount)
					if errors.Is(err, errors.InsufficientBalance) {
						err = nil
					}
					return inputDetail, err
				}
			default:
				return errors.E(errors.Invalid,
					errors.Errorf("unknown output selection algorithm %v", algo))
			}
		}
		if changeSource == nil {
			changeSource = &p2PKHChangeSource{
				persist: w.deferPersistReturnedChild(ctx, &changeSourceUpdates),
				account: account,
				wallet:  w,
				ctx:     context.Background(),
			}
		}
		var err error
		authoredTx, err = txauthor.NewUnsignedTransaction(outputs, relayFeePerKb,
			inputSource, changeSource, w.chainParams.MaxTxSize)
		if err != nil {
			return err
		}
		return nil
	})
	if err != nil {
		return nil, errors.E(op, err)
	}
	if len(changeSourceUpdates) != 0 {
		err := walletdb.Update(ctx, w.db, func(tx walletdb.ReadWriteTx) error {
			for _, up := range changeSourceUpdates {
				err := up(tx)
				if err != nil {
					return err
				}
			}
			return nil
		})
		if err != nil {
			return nil, errors.E(op, err)
		}
	}
	return authoredTx, nil
}
type secretSource struct {
	*udb.Manager
	addrmgrNs walletdb.ReadBucket
	doneFuncs []func()
}
func (s *secretSource) GetKey(addr stdaddr.Address) ([]byte, dcrec.SignatureType, bool, error) {
	privKey, done, err := s.Manager.PrivateKey(s.addrmgrNs, addr)
	if err != nil {
		return nil, 0, false, err
	}
	s.doneFuncs = append(s.doneFuncs, done)
	return privKey.Serialize(), dcrec.STEcdsaSecp256k1, true, nil
}
func (s *secretSource) GetScript(addr stdaddr.Address) ([]byte, error) {
	return s.Manager.RedeemScript(s.addrmgrNs, addr)
}
type CreatedTx struct {
	MsgTx       *wire.MsgTx
	ChangeAddr  stdaddr.Address
	ChangeIndex int  
	Fee         dcrutil.Amount
}
func (w *Wallet) insertIntoTxMgr(dbtx walletdb.ReadWriteTx, msgTx *wire.MsgTx) (*udb.TxRecord, error) {
	rec, err := udb.NewTxRecordFromMsgTx(msgTx, time.Now())
	if err != nil {
		return nil, err
	}
	err = w.txStore.InsertMemPoolTx(dbtx, rec)
	if err != nil {
		return nil, err
	}
	return rec, nil
}
func (w *Wallet) insertCreditsIntoTxMgr(op errors.Op, dbtx walletdb.ReadWriteTx, msgTx *wire.MsgTx, rec *udb.TxRecord) error {
	addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey)
	for i, output := range msgTx.TxOut {
		_, addrs, _, err := txscript.ExtractPkScriptAddrs(output.Version,
			output.PkScript, w.chainParams, true)  
		if err != nil {
			continue
		}
		for _, addr := range addrs {
			ma, err := w.manager.Address(addrmgrNs, addr)
			if err == nil {
				err = w.txStore.AddCredit(dbtx, rec, nil,
					uint32(i), ma.Internal(), ma.Account())
				if err != nil {
					return errors.E(op, err)
				}
				err = w.markUsedAddress(op, dbtx, ma)
				if err != nil {
					return err
				}
				log.Debugf("Marked address %v used", addr)
				continue
			}
			if !errors.Is(err, errors.NotExist) {
				return errors.E(op, err)
			}
		}
	}
	return nil
}
func (w *Wallet) insertMultisigOutIntoTxMgr(dbtx walletdb.ReadWriteTx, msgTx *wire.MsgTx, index uint32) error {
	rec, err := udb.NewTxRecordFromMsgTx(msgTx, time.Now())
	if err != nil {
		return err
	}
	return w.txStore.AddMultisigOut(dbtx, rec, nil, index)
}
func (w *Wallet) checkHighFees(totalInput dcrutil.Amount, tx *wire.MsgTx) error {
	if w.allowHighFees {
		return nil
	}
	if txrules.PaysHighFees(totalInput, tx) {
		return errors.E(errors.Policy, "high fee")
	}
	return nil
}
func (w *Wallet) publishAndWatch(ctx context.Context, op errors.Op, n NetworkBackend, tx *wire.MsgTx,
	watch []wire.OutPoint) error {
	if n == nil {
		var err error
		n, err = w.NetworkBackend()
		if err != nil {
			return errors.E(op, err)
		}
	}
	err := n.PublishTransactions(ctx, tx)
	if err != nil {
		hash := tx.TxHash()
		log.Errorf("Abandoning transaction %v which failed to publish", &hash)
		if err := w.AbandonTransaction(ctx, &hash); err != nil {
			log.Errorf("Cannot abandon %v: %v", &hash, err)
		}
		return errors.E(op, err)
	}
	_, err = w.watchHDAddrs(ctx, false, n)
	if err != nil {
		log.Errorf("Failed to watch for future address usage after publishing "+
			"transaction: %v", err)
	}
	if len(watch) > 0 {
		err := n.LoadTxFilter(ctx, false, nil, watch)
		if err != nil {
			log.Errorf("Failed to watch outpoints: %v", err)
		}
	}
	return nil
}
type authorTx struct {
	outputs            []*wire.TxOut
	account            uint32
	changeAccount      uint32
	minconf            int32
	randomizeChangeIdx bool
	txFee              dcrutil.Amount
	dontSignTx         bool
	isTreasury         bool
	atx                 *txauthor.AuthoredTx
	changeSourceUpdates []func(walletdb.ReadWriteTx) error
	watch               []wire.OutPoint
}
func (w *Wallet) authorTx(ctx context.Context, op errors.Op, a *authorTx) error {
	var unlockOutpoints []*wire.OutPoint
	defer func() {
		for _, op := range unlockOutpoints {
			delete(w.lockedOutpoints, outpoint{op.Hash, op.Index})
		}
		w.lockedOutpointMu.Unlock()
	}()
	ignoreInput := func(op *wire.OutPoint) bool {
		_, ok := w.lockedOutpoints[outpoint{op.Hash, op.Index}]
		return ok
	}
	w.lockedOutpointMu.Lock()
	var atx *txauthor.AuthoredTx
	var changeSourceUpdates []func(walletdb.ReadWriteTx) error
	err := walletdb.View(ctx, w.db, func(dbtx walletdb.ReadTx) error {
		addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey)
		txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey)
		_, tipHeight := w.txStore.MainChainTip(dbtx)
		inputSource := w.txStore.MakeInputSource(txmgrNs, addrmgrNs,
			a.account, a.minconf, tipHeight, ignoreInput)
		var changeSource txauthor.ChangeSource
		if a.isTreasury {
			changeSource = &p2PKHTreasuryChangeSource{
				persist: w.deferPersistReturnedChild(ctx,
					&changeSourceUpdates),
				account: a.changeAccount,
				wallet:  w,
				ctx:     ctx,
			}
		} else {
			changeSource = &p2PKHChangeSource{
				persist: w.deferPersistReturnedChild(ctx,
					&changeSourceUpdates),
				account:   a.changeAccount,
				wallet:    w,
				ctx:       ctx,
				gapPolicy: gapPolicyWrap,
			}
		}
		var err error
		atx, err = txauthor.NewUnsignedTransaction(a.outputs, a.txFee,
			randomInputSource(inputSource.SelectInputs), changeSource,
			w.chainParams.MaxTxSize)
		if err != nil {
			return err
		}
		for _, in := range atx.Tx.TxIn {
			prev := &in.PreviousOutPoint
			w.lockedOutpoints[outpoint{prev.Hash, prev.Index}] = struct{}{}
			unlockOutpoints = append(unlockOutpoints, prev)
		}
		if atx.ChangeIndex >= 0 && a.randomizeChangeIdx {
			atx.RandomizeChangePosition()
		}
		if a.isTreasury {
			if atx.Tx.Version != wire.TxVersion {
				return errors.E(op, "violated assumption: "+
					"expected unsigned tx to be version 1")
			}
			atx.Tx.Version = wire.TxVersionTreasury
		}
		if !a.dontSignTx {
			secrets := &secretSource{Manager: w.manager, addrmgrNs: addrmgrNs}
			err = atx.AddAllInputScripts(secrets)
			for _, done := range secrets.doneFuncs {
				done()
			}
		}
		return err
	})
	if err != nil {
		return errors.E(op, err)
	}
	if atx.ChangeIndex >= 0 && a.account == udb.ImportedAddrAccount {
		changeAmount := dcrutil.Amount(atx.Tx.TxOut[atx.ChangeIndex].Value)
		log.Warnf("Spend from imported account produced change: moving"+
			" %v from imported account into default account.", changeAmount)
	}
	err = w.checkHighFees(atx.TotalInput, atx.Tx)
	if err != nil {
		return errors.E(op, err)
	}
	if !a.dontSignTx {
		err = validateMsgTx(op, atx.Tx, atx.PrevScripts)
		if err != nil {
			return errors.E(op, err)
		}
	}
	a.atx = atx
	a.changeSourceUpdates = changeSourceUpdates
	return nil
}
func (w *Wallet) recordAuthoredTx(ctx context.Context, op errors.Op, a *authorTx) error {
	rec, err := udb.NewTxRecordFromMsgTx(a.atx.Tx, time.Now())
	if err != nil {
		return errors.E(op, err)
	}
	w.lockedOutpointMu.Lock()
	defer w.lockedOutpointMu.Unlock()
	var watch []wire.OutPoint
	err = walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error {
		for _, up := range a.changeSourceUpdates {
			err := up(dbtx)
			if err != nil {
				return err
			}
		}
		var err error
		watch, err = w.processTransactionRecord(ctx, dbtx, rec, nil, nil)
		return err
	})
	if err != nil {
		return errors.E(op, err)
	}
	a.watch = watch
	return nil
}
func (w *Wallet) txToMultisig(ctx context.Context, op errors.Op, account uint32, amount dcrutil.Amount, pubkeys [][]byte,
	nRequired int8, minconf int32) (*CreatedTx, stdaddr.Address, []byte, error) {
	defer w.lockedOutpointMu.Unlock()
	w.lockedOutpointMu.Lock()
	var created *CreatedTx
	var addr stdaddr.Address
	var msScript []byte
	err := walletdb.Update(ctx, w.db, func(dbtx walletdb.ReadWriteTx) error {
		var err error
		created, addr, msScript, err = w.txToMultisigInternal(ctx, op, dbtx,
			account, amount, pubkeys, nRequired, minconf)
		return err
	})
	if err != nil {
		return nil, nil, nil, errors.E(op, err)
	}
	return created, addr, msScript, nil
}
func (w *Wallet) txToMultisigInternal(ctx context.Context, op errors.Op, dbtx walletdb.ReadWriteTx, account uint32, amount dcrutil.Amount,
	pubkeys [][]byte, nRequired int8, minconf int32) (*CreatedTx, stdaddr.Address, []byte, error) {
	addrmgrNs := dbtx.ReadWriteBucket(waddrmgrNamespaceKey)
	txToMultisigError := func(err error) (*CreatedTx, stdaddr.Address, []byte, error) {
		return nil, nil, nil, err
	}
	n, err := w.NetworkBackend()
	if err != nil {
		return txToMultisigError(err)
	}
	_, topHeight := w.txStore.MainChainTip(dbtx)
	var feeEstForTx dcrutil.Amount
	switch w.chainParams.Net {
	case wire.MainNet:
		feeEstForTx = 5e7
	case 0x48e7a065:  
		feeEstForTx = 5e7
	case wire.TestNet3:
		feeEstForTx = 5e7
	default:
		feeEstForTx = 3e4
	}
	amountRequired := amount + feeEstForTx
	eligible, err := w.findEligibleOutputsAmount(dbtx, account, minconf,
		amountRequired, topHeight)
	if err != nil {
		return txToMultisigError(errors.E(op, err))
	}
	if eligible == nil {
		return txToMultisigError(errors.E(op, "not enough funds to send to multisig address"))
	}
	for i := range eligible {
		op := &eligible[i].OutPoint
		w.lockedOutpoints[outpoint{op.Hash, op.Index}] = struct{}{}
	}
	defer func() {
		for i := range eligible {
			op := &eligible[i].OutPoint
			delete(w.lockedOutpoints, outpoint{op.Hash, op.Index})
		}
	}()
	msgtx := wire.NewMsgTx()
	scriptSizes := make([]int, 0, len(eligible))
	forSigning := make([]Input, 0, len(eligible))
	totalInput := dcrutil.Amount(0)
	for _, e := range eligible {
		txIn := wire.NewTxIn(&e.OutPoint, e.PrevOut.Value, nil)
		msgtx.AddTxIn(txIn)
		totalInput += dcrutil.Amount(e.PrevOut.Value)
		forSigning = append(forSigning, e)
		scriptSizes = append(scriptSizes, txsizes.RedeemP2SHSigScriptSize)
	}
	msScript, err := txscript.MultiSigScript(int(nRequired), pubkeys...)
	if err != nil {
		return txToMultisigError(errors.E(op, err))
	}
	_, err = w.manager.ImportScript(addrmgrNs, msScript)
	if err != nil {
		if !errors.Is(err, errors.Exist) {
			return txToMultisigError(errors.E(op, err))
		}
	}
	scAddr, err := stdaddr.NewAddressScriptHashV0(msScript, w.chainParams)
	if err != nil {
		return txToMultisigError(errors.E(op, err))
	}
	vers, p2shScript := scAddr.PaymentScript()
	txOut := &wire.TxOut{
		Value:    int64(amount),
		PkScript: p2shScript,
		Version:  vers,
	}
	msgtx.AddTxOut(txOut)
	changeSize := 0
	if totalInput > amount+feeEstForTx {
		changeSize = txsizes.P2PKHPkScriptSize
	}
	feeSize := txsizes.EstimateSerializeSize(scriptSizes, msgtx.TxOut, changeSize)
	feeEst := txrules.FeeForSerializeSize(w.RelayFee(), feeSize)
	if totalInput < amount+feeEst {
		return txToMultisigError(errors.E(op, errors.InsufficientBalance))
	}
	if totalInput > amount+feeEst {
		changeSource := p2PKHChangeSource{
			persist: w.persistReturnedChild(ctx, dbtx),
			account: account,
			wallet:  w,
			ctx:     ctx,
		}
		pkScript, vers, err := changeSource.Script()
		if err != nil {
			return txToMultisigError(err)
		}
		change := totalInput - (amount + feeEst)
		msgtx.AddTxOut(&wire.TxOut{
			Value:    int64(change),
			Version:  vers,
			PkScript: pkScript,
		})
	}
	err = w.signP2PKHMsgTx(msgtx, forSigning, addrmgrNs)
	if err != nil {
		return txToMultisigError(errors.E(op, err))
	}
	err = w.checkHighFees(totalInput, msgtx)
	if err != nil {
		return txToMultisigError(errors.E(op, err))
	}
	err = n.PublishTransactions(ctx, msgtx)
	if err != nil {
		return txToMultisigError(errors.E(op, err))
	}
	err = n.LoadTxFilter(ctx, false, []stdaddr.Address{scAddr}, nil)
	if err != nil {
		return txToMultisigError(errors.E(op, err))
	}
	err = w.insertMultisigOutIntoTxMgr(dbtx, msgtx, 0)
	if err != nil {
		return txToMultisigError(errors.E(op, err))
	}
	created := &CreatedTx{
		MsgTx:       msgtx,
		ChangeAddr:  nil,
		ChangeIndex: -1,
	}
	return created, scAddr, msScript, nil
}
func validateMsgTx(op errors.Op, tx *wire.MsgTx, prevScripts [][]byte) error {
	for i, prevScript := range prevScripts {
		vm, err := txscript.NewEngine(prevScript, tx, i,
			sanityVerifyFlags, 0, nil)
		if err != nil {
			return errors.E(op, err)
		}
		err = vm.Execute()
		if err != nil {
			prevOut := &tx.TxIn[i].PreviousOutPoint
			sigScript := tx.TxIn[i].SignatureScript
			log.Errorf("Script validation failed (outpoint %v pkscript %x sigscript %x): %v",
				prevOut, prevScript, sigScript, err)
			return errors.E(op, errors.ScriptFailure, err)
		}
	}
	return nil
}
func creditScripts(credits []Input) [][]byte {
	scripts := make([][]byte, 0, len(credits))
	for _, c := range credits {
		scripts = append(scripts, c.PrevOut.PkScript)
	}
	return scripts
}