github.com/mit-dci/lit@v0.0.0-20221102210550-8c3d3b49f2ce/wallit/signsend.go (about)

     1  package wallit
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"sort"
     7  
     8  	"github.com/mit-dci/lit/logging"
     9  
    10  	"github.com/mit-dci/lit/btcutil/chaincfg/chainhash"
    11  	"github.com/mit-dci/lit/btcutil/txscript"
    12  	"github.com/mit-dci/lit/btcutil/txsort"
    13  	"github.com/mit-dci/lit/consts"
    14  	"github.com/mit-dci/lit/lnutil"
    15  	"github.com/mit-dci/lit/portxo"
    16  	"github.com/mit-dci/lit/wire"
    17  )
    18  
    19  // Build a tx, kindof like with SendCoins, but don't sign or broadcast.
    20  // Segwit inputs only.  Freeze the utxos used so the tx can be signed and broadcast
    21  // later.  Use only segwit utxos.  Return the txid, and indexes of where the txouts
    22  // in the argument slice ended up in the final tx.
    23  // Bunch of redundancy with SendMany, maybe move that to a shared function...
    24  //NOTE this does not support multiple txouts with identical pkscripts in one tx.
    25  // The code would be trivial; it's not supported on purpose.  Use unique pkscripts.
    26  func (w *Wallit) MaybeSend(txos []*wire.TxOut, ow bool) ([]*wire.OutPoint, error) {
    27  	var err error
    28  	var totalSend int64
    29  	dustCutoff := consts.DustCutoff // below this amount, just give to miners
    30  
    31  	feePerByte := w.FeeRate
    32  
    33  	// make an initial txo copy so we can find where the outputs end up in final tx
    34  
    35  	initTxos := make([]*wire.TxOut, len(txos))
    36  
    37  	// change output (if needed)
    38  	var changeOut *wire.TxOut
    39  
    40  	finalOutPoints := make([]*wire.OutPoint, len(txos))
    41  	copy(initTxos, txos)
    42  
    43  	var outputByteSize int64
    44  	// check for negative...?
    45  	for _, txo := range txos {
    46  		totalSend += txo.Value
    47  		outputByteSize += 8 + int64(len(txo.PkScript))
    48  	}
    49  
    50  	// start access to utxos
    51  	w.FreezeMutex.Lock()
    52  	defer w.FreezeMutex.Unlock()
    53  
    54  	// get inputs for this tx.  Only segwit if needed
    55  	utxos, overshoot, err :=
    56  		w.PickUtxos(totalSend, outputByteSize, feePerByte, ow)
    57  	if err != nil {
    58  		return nil, err
    59  	}
    60  
    61  	logging.Infof("MaybeSend has overshoot %d, %d inputs\n", overshoot, len(utxos))
    62  
    63  	// changeOutSize is the extra vsize that a change output would add
    64  	changeOutFee := 30 * feePerByte
    65  
    66  	// add a change output if we have enough extra to do so
    67  	if overshoot > dustCutoff+changeOutFee {
    68  		changeOut, err = w.NewChangeOut(overshoot - changeOutFee)
    69  		if err != nil {
    70  			return nil, err
    71  		}
    72  	}
    73  
    74  	// build frozen tx for later broadcast
    75  	fTx := new(FrozenTx)
    76  	fTx.Ins = utxos
    77  	fTx.Outs = txos
    78  	fTx.ChangeOut = changeOut
    79  
    80  	if changeOut != nil {
    81  		txos = append(txos, changeOut)
    82  	}
    83  
    84  	// BuildDontSign gets the txid.  Also sorts txin, txout slices in place
    85  	tx, err := w.BuildDontSign(utxos, txos)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  
    90  	// after building, store the locktime and txid
    91  	fTx.Nlock = tx.LockTime
    92  	fTx.Txid = tx.TxHash()
    93  
    94  	for _, utxo := range utxos {
    95  		w.FreezeSet[utxo.Op] = fTx
    96  	}
    97  
    98  	// figure out where outputs ended up after adding the change output and sorting
    99  	for i, initTxo := range initTxos {
   100  		for j, finalTxo := range tx.TxOut {
   101  			// If pkscripts match, this is where it ended up.
   102  			// if you're sending different amounts to the same address, this
   103  			// might not work!  Don't re-use addresses!
   104  			if bytes.Equal(initTxo.PkScript, finalTxo.PkScript) {
   105  				finalOutPoints[i] = wire.NewOutPoint(&fTx.Txid, uint32(j))
   106  			}
   107  		}
   108  	}
   109  
   110  	return finalOutPoints, nil
   111  }
   112  
   113  // Sign and broadcast a tx previously built with MaybeSend.  This clears the freeze
   114  // on the utxos but they're not utxos anymore anyway.
   115  func (w *Wallit) ReallySend(txid *chainhash.Hash) error {
   116  	logging.Infof("Reallysend %s\n", txid.String())
   117  	// start frozen set access
   118  	w.FreezeMutex.Lock()
   119  	defer w.FreezeMutex.Unlock()
   120  	// get the transaction
   121  	frozenTx, err := w.FindFreezeTx(txid)
   122  	if err != nil {
   123  		return err
   124  	}
   125  	// delete inputs from frozen set (they're gone anyway, but just to clean it up)
   126  	for _, txin := range frozenTx.Ins {
   127  		logging.Infof("\t remove %s from frozen outpoints\n", txin.Op.String())
   128  		delete(w.FreezeSet, txin.Op)
   129  	}
   130  
   131  	allOuts := frozenTx.Outs
   132  
   133  	if frozenTx.ChangeOut != nil {
   134  		allOuts = append(frozenTx.Outs, frozenTx.ChangeOut)
   135  	}
   136  
   137  	tx, err := w.BuildAndSign(frozenTx.Ins, allOuts, frozenTx.Nlock)
   138  	if err != nil {
   139  		return err
   140  	}
   141  
   142  	return w.NewOutgoingTx(tx)
   143  }
   144  
   145  // Cancel the hold on a tx previously built with MaybeSend.  Clears freeze on
   146  // utxos so they can be used somewhere else.
   147  func (w *Wallit) NahDontSend(txid *chainhash.Hash) error {
   148  	logging.Infof("Nahdontsend %s\n", txid.String())
   149  	// start frozen set access
   150  	w.FreezeMutex.Lock()
   151  	defer w.FreezeMutex.Unlock()
   152  	// get the transaction
   153  	frozenTx, err := w.FindFreezeTx(txid)
   154  	if err != nil {
   155  		return err
   156  	}
   157  	// go through all its inputs, and remove those outpoints from the frozen set
   158  	for _, txin := range frozenTx.Ins {
   159  		logging.Infof("\t remove %s from frozen outpoints\n", txin.Op.String())
   160  		delete(w.FreezeSet, txin.Op)
   161  	}
   162  	return nil
   163  }
   164  
   165  // FindFreezeTx looks through the frozen map to find a tx.  Error if it can't find it
   166  func (w *Wallit) FindFreezeTx(txid *chainhash.Hash) (*FrozenTx, error) {
   167  	for op := range w.FreezeSet {
   168  		frozenTxid := w.FreezeSet[op].Txid
   169  		if frozenTxid.IsEqual(txid) {
   170  			return w.FreezeSet[op], nil
   171  		}
   172  	}
   173  	return nil, fmt.Errorf("couldn't find %s in frozen set", txid.String())
   174  }
   175  
   176  // GrabAll makes first-party justice txs.
   177  func (w *Wallit) GrabAll() error {
   178  	// no args, look through all utxos
   179  	utxos, err := w.GetAllUtxos()
   180  	if err != nil {
   181  		return err
   182  	}
   183  
   184  	// currently grabs only confirmed txs.
   185  	nothin := true
   186  	for _, u := range utxos {
   187  		if u.Seq == 1 && u.Height > 0 { // grabbable
   188  			logging.Infof("found %s to grab!\n", u.String())
   189  			adr160, err := w.NewAdr160()
   190  			if err != nil {
   191  				return err
   192  			}
   193  
   194  			outScript := lnutil.DirectWPKHScriptFromPKH(adr160)
   195  
   196  			tx, err := w.SendOne(*u, outScript)
   197  			if err != nil {
   198  				return err
   199  			}
   200  			err = w.NewOutgoingTx(tx)
   201  			if err != nil {
   202  				return err
   203  			}
   204  			nothin = false
   205  		}
   206  	}
   207  	if nothin {
   208  		logging.Infof("Nothing to grab\n")
   209  	}
   210  	return nil
   211  }
   212  
   213  // Directly send out a tx.  For things that plug in to the uspv wallet.
   214  func (w *Wallit) DirectSendTx(tx *wire.MsgTx) error {
   215  	// don't ingest, just push out
   216  	return w.Hook.PushTx(tx)
   217  }
   218  
   219  // NewOutgoingTx runs a tx though the db first, then sends it out to the network.
   220  func (w *Wallit) NewOutgoingTx(tx *wire.MsgTx) error {
   221  	_, err := w.Ingest(tx, 0) // our own tx; don't keep track of false positives
   222  	if err != nil {
   223  		return err
   224  	}
   225  	return w.Hook.PushTx(tx)
   226  }
   227  
   228  // PickUtxos Picks Utxos for spending.  Tell it how much money you want.
   229  // It returns a tx-sortable utxoslice, and the overshoot amount.  Also errors.
   230  // if "ow" is true, only gives witness utxos (for channel funding)
   231  // The overshoot amount is *after* fees, so can be used directly for a
   232  // change output.
   233  func (w *Wallit) PickUtxos(
   234  	amtWanted, outputByteSize, feePerByte int64,
   235  	ow bool) (portxo.TxoSliceByBip69, int64, error) {
   236  
   237  	curHeight, err := w.GetDBSyncHeight()
   238  	if err != nil {
   239  		return nil, 0, err
   240  	}
   241  
   242  	var allUtxos portxo.TxoSliceByAmt
   243  	allUtxos, err = w.GetAllUtxos()
   244  	if err != nil {
   245  		return nil, 0, err
   246  	}
   247  
   248  	// remove frozen utxos from allUtxo slice.  Iterate backwards / trailing delete
   249  	for i := len(allUtxos) - 1; i >= 0; i-- {
   250  		_, frozen := w.FreezeSet[allUtxos[i].Op]
   251  		if frozen {
   252  			// faster than append, and we're sorting a few lines later anyway
   253  			allUtxos[i] = allUtxos[len(allUtxos)-1] // redundant if at last index
   254  			allUtxos = allUtxos[:len(allUtxos)-1]   // trim last element
   255  		}
   256  	}
   257  
   258  	// start with utxos sorted by value and pop off utxos which are greater
   259  	// than the send amount... as long as the next 2 are greater.
   260  	// simple / straightforward coin selection optimization, which tends to make
   261  	// 2 in 2 out
   262  
   263  	// smallest and unconfirmed last (because it's reversed)
   264  	sort.Sort(sort.Reverse(allUtxos))
   265  
   266  	// guessing that txs won't be more than 10K here...
   267  	maxFeeGuess := feePerByte * consts.MaxTxCount
   268  
   269  	// first pass of removing candidate utxos; if the next one is bigger than
   270  	// we need, remove the top one.
   271  	for len(allUtxos) > 1 &&
   272  		allUtxos[1].Value > amtWanted+maxFeeGuess &&
   273  		allUtxos[1].Height > 100 &&
   274  		!(ow && allUtxos[1].Mode&portxo.FlagTxoWitness == 0) {
   275  		allUtxos = allUtxos[1:]
   276  	}
   277  
   278  	// if we've got 2 or more confirmed utxos, and the next one is
   279  	// more than enough, pop off the first one.
   280  	// Note that there are probably all sorts of edge cases where this will
   281  	// result in not being able to send money when you should be able to.
   282  	// Thus the handwavey "maxFeeGuess"
   283  	for len(allUtxos) > 2 &&
   284  		allUtxos[2].Height > 100 && // since sorted, don't need to check [1]
   285  		allUtxos[1].Mature(curHeight) &&
   286  		allUtxos[2].Mature(curHeight) &&
   287  		allUtxos[1].Value+allUtxos[2].Value > amtWanted+maxFeeGuess &&
   288  		!(ow && allUtxos[2].Mode&portxo.FlagTxoWitness == 0) &&
   289  		!(ow && allUtxos[1].Mode&portxo.FlagTxoWitness == 0) {
   290  		logging.Infof("remaining utxo list, in order:\n")
   291  		for _, u := range allUtxos {
   292  			logging.Infof("\t h: %d amt: %d\n", u.Height, u.Value)
   293  		}
   294  		allUtxos = allUtxos[1:]
   295  	}
   296  
   297  	// coin selection is super complex, and we can definitely do a lot better
   298  	// here!
   299  	// TODO: anyone who wants to: implement more advanced coin selection algo
   300  
   301  	// rSlice is the return slice of the utxos which are going into the tx
   302  	var rSlice portxo.TxoSliceByBip69
   303  	// add utxos until we've had enough
   304  	remaining := amtWanted // remaining is how much is needed on input side
   305  	for _, utxo := range allUtxos {
   306  		// skip unconfirmed.  Or de-prioritize? Some option for this...
   307  		//		if utxo.AtHeight == 0 {
   308  		//			continue
   309  		//		}
   310  		if !utxo.Mature(curHeight) {
   311  			continue // skip immature or unconfirmed time-locked sh outputs
   312  		}
   313  		if ow && utxo.Mode&portxo.FlagTxoWitness == 0 {
   314  			continue // skip non-witness
   315  		}
   316  		// why are 0-value outputs a thing..?
   317  		if utxo.Value < 1 {
   318  			continue
   319  		}
   320  		// yeah, lets add this utxo!
   321  		rSlice = append(rSlice, utxo)
   322  		remaining -= utxo.Value
   323  		// if remaining is positive, don't bother checking fee yet.
   324  		// if remaining is negative, calculate needed fee
   325  		if remaining <= 0 {
   326  			fee := EstFee(rSlice, outputByteSize, feePerByte)
   327  			// subtract fee from returned overshoot.
   328  			// (remaining is negative here)
   329  			remaining += fee
   330  
   331  			// done adding utxos if remaining below negative est fee
   332  			if remaining < -fee {
   333  				break
   334  			}
   335  		}
   336  	}
   337  
   338  	if remaining > 0 {
   339  		return nil, 0, fmt.Errorf("wanted %d but %d available.",
   340  			amtWanted, amtWanted-remaining)
   341  	}
   342  
   343  	sort.Sort(rSlice) // send sorted.  This is probably redundant?
   344  	return rSlice, -remaining, nil
   345  }
   346  
   347  // SendOne is for the sweep function, and doesn't do change.
   348  // Probably can get rid of this for real txs.
   349  func (w *Wallit) SendOne(u portxo.PorTxo, outScript []byte) (*wire.MsgTx, error) {
   350  
   351  	w.FreezeMutex.Lock()
   352  	defer w.FreezeMutex.Unlock()
   353  	_, frozen := w.FreezeSet[u.Op]
   354  	if frozen {
   355  		return nil, fmt.Errorf("%s is frozen, can't spend", u.Op.String())
   356  	}
   357  
   358  	curHeight, err := w.GetDBSyncHeight()
   359  	if err != nil {
   360  		return nil, err
   361  	}
   362  
   363  	if u.Seq > 1 &&
   364  		(u.Height < 100 || u.Height+int32(u.Seq) > curHeight) {
   365  		// skip immature or unconfirmed time-locked sh outputs
   366  		return nil, fmt.Errorf("Can't spend, immature")
   367  	}
   368  	// fixed fee
   369  	fee := w.FeeRate * 200
   370  
   371  	sendAmt := u.Value - fee
   372  
   373  	// make user specified txout and add to tx
   374  	txout := wire.NewTxOut(sendAmt, outScript)
   375  
   376  	return w.BuildAndSign(
   377  		[]*portxo.PorTxo{&u}, []*wire.TxOut{txout}, uint32(w.CurrentHeight()))
   378  }
   379  
   380  // Builds tx from inputs and outputs, returns tx.  Sorts.  Doesn't sign.
   381  func (w *Wallit) BuildDontSign(
   382  	utxos []*portxo.PorTxo, txos []*wire.TxOut) (*wire.MsgTx, error) {
   383  
   384  	// make the tx
   385  	tx := wire.NewMsgTx()
   386  	// set version 2, for op_csv
   387  	tx.Version = 2
   388  	// set the time, the way core does.
   389  	tx.LockTime = uint32(w.CurrentHeight())
   390  
   391  	// add all the txouts
   392  	for _, txo := range txos {
   393  		tx.AddTxOut(txo)
   394  	}
   395  	// add all the txins
   396  	for i, u := range utxos {
   397  		tx.AddTxIn(wire.NewTxIn(&u.Op, nil, nil))
   398  		// set sequence field if it's in the portxo
   399  		if u.Seq > 1 {
   400  			tx.TxIn[i].Sequence = u.Seq
   401  		}
   402  	}
   403  	// sort in place before signing
   404  	txsort.InPlaceSort(tx)
   405  	return tx, nil
   406  }
   407  
   408  // SignMyInputs finds the inputs in a transaction that came from our own wallet, and signs them with our private keys.
   409  // Will modify the transaction in place, but will ignore inputs that we can't sign and leave them unsigned.
   410  func (w *Wallit) SignMyInputs(tx *wire.MsgTx) error {
   411  
   412  	// generate tx-wide hashCache for segwit stuff
   413  	// might not be needed (non-witness) but make it anyway
   414  	hCache := txscript.NewTxSigHashes(tx)
   415  	// make the stashes for signatures / witnesses
   416  	sigStash := make([][]byte, len(tx.TxIn))
   417  	witStash := make([][][]byte, len(tx.TxIn))
   418  
   419  	var allUtxos portxo.TxoSliceByAmt
   420  	allUtxos, err := w.GetAllUtxos()
   421  	if err != nil {
   422  		return err
   423  	}
   424  
   425  	for i := range tx.TxIn {
   426  		var utxo *portxo.PorTxo
   427  		for j := range allUtxos {
   428  			if allUtxos[j].Op.Hash.IsEqual(&tx.TxIn[i].PreviousOutPoint.Hash) && allUtxos[j].Op.Index == tx.TxIn[i].PreviousOutPoint.Index {
   429  				utxo = allUtxos[j]
   430  				break
   431  			}
   432  		}
   433  
   434  		if utxo == nil {
   435  			// Not my input, or at least i don't have it in my DB
   436  			continue
   437  		}
   438  
   439  		// get key
   440  		priv := w.PathPrivkey(utxo.KeyGen)
   441  		logging.Infof("signing with privkey pub %x\n", priv.PubKey().SerializeCompressed())
   442  
   443  		if priv == nil {
   444  			return fmt.Errorf("SignMyInputs: nil privkey")
   445  		}
   446  
   447  		// sign into stash.  3 possibilities:  legacy PKH, WPKH, WSH
   448  		if utxo.Mode == portxo.TxoP2PKHComp { // legacy PKH
   449  			sigStash[i], err = txscript.SignatureScript(tx, i,
   450  				utxo.PkScript, txscript.SigHashAll, priv, true)
   451  			if err != nil {
   452  				return err
   453  			}
   454  		}
   455  		if utxo.Mode == portxo.TxoP2WPKHComp { // witness PKH
   456  			witStash[i], err = txscript.WitnessScript(tx, hCache, i,
   457  				utxo.Value, utxo.PkScript, txscript.SigHashAll, priv, true)
   458  			if err != nil {
   459  				return err
   460  			}
   461  		}
   462  		if utxo.Mode == portxo.TxoP2WSHComp { // witness script hash
   463  			sig, err := txscript.RawTxInWitnessSignature(tx, hCache, i,
   464  				utxo.Value, utxo.PkScript, txscript.SigHashAll, priv)
   465  			if err != nil {
   466  				return err
   467  			}
   468  			// witness stack has the signature, items, then the previous full script
   469  			witStash[i] = make([][]byte, 2+len(utxo.PreSigStack))
   470  
   471  			// sig comes first (pushed to stack last)
   472  			witStash[i][0] = sig
   473  
   474  			// after stack comes PostSigStack items
   475  			for j, element := range utxo.PreSigStack {
   476  				witStash[i][j+1] = element
   477  			}
   478  
   479  			// last stack item is the pkscript
   480  			witStash[i][len(witStash[i])-1] = utxo.PkScript
   481  		}
   482  
   483  	}
   484  	// swap sigs into sigScripts in txins
   485  	for i, txin := range tx.TxIn {
   486  		if sigStash[i] != nil {
   487  			txin.SignatureScript = sigStash[i]
   488  		}
   489  		if witStash[i] != nil {
   490  			txin.Witness = witStash[i]
   491  			txin.SignatureScript = nil
   492  		}
   493  	}
   494  
   495  	return nil
   496  }
   497  
   498  // BuildAndSign builds a tx from a slice of utxos and txOuts.
   499  // It then signs all the inputs and returns the tx.  Should
   500  // pretty much always work for any inputs.
   501  func (w *Wallit) BuildAndSign(
   502  	utxos []*portxo.PorTxo, txos []*wire.TxOut, nlt uint32) (*wire.MsgTx, error) {
   503  
   504  	if len(utxos) == 0 || len(txos) == 0 {
   505  		return nil, fmt.Errorf("BuildAndSign args no utxos or txos")
   506  	}
   507  	// sort input utxos first.
   508  	sort.Sort(portxo.TxoSliceByBip69(utxos))
   509  
   510  	// make the tx
   511  	tx := wire.NewMsgTx()
   512  
   513  	// always make version 2 txs
   514  	tx.Version = 2
   515  	tx.LockTime = nlt
   516  	// add all the txouts, direct from the argument slice
   517  	for _, txo := range txos {
   518  		if txo == nil || txo.PkScript == nil || txo.Value == 0 {
   519  			return nil, fmt.Errorf("BuildAndSign arg invalid txo")
   520  		}
   521  		tx.AddTxOut(txo)
   522  	}
   523  	// add all the txins, first refenecing the prev outPoints
   524  	for i, u := range utxos {
   525  		tx.AddTxIn(wire.NewTxIn(&u.Op, nil, nil))
   526  		// set sequence field if it's in the portxo
   527  		if u.Seq > 1 {
   528  			tx.TxIn[i].Sequence = u.Seq
   529  		}
   530  	}
   531  	// sort txouts in place before signing.  txins are already sorted from above
   532  	txsort.InPlaceSort(tx)
   533  
   534  	w.SignMyInputs(tx)
   535  
   536  	logging.Infof("tx: %s", TxToString(tx))
   537  	return tx, nil
   538  }
   539  
   540  // EstFee gives a fee estimate based on an input / output set and a sat/Byte target.
   541  // It guesses the final tx size based on:
   542  // Txouts: 8 bytes + pkscript length
   543  // Total guess on the p2wsh one, see if that's accurate
   544  func EstFee(txins []*portxo.PorTxo, outputByteSize, spB int64) int64 {
   545  	size := int64(40)      // around 40 bytes for a change output and nlock time
   546  	size += outputByteSize // add the output size
   547  
   548  	// iterate through txins, guessing size based on mode
   549  	for _, txin := range txins {
   550  		if txin == nil { // silently ignore nil txins; somebody else's problem
   551  			continue
   552  		}
   553  		size += txin.EstSize()
   554  	}
   555  
   556  	logging.Infof("%d spB, est vsize %d, fee %d\n", spB, size, size*spB)
   557  	return size * spB
   558  }