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

     1  package qln
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"time"
     7  
     8  	"github.com/mit-dci/lit/btcutil/chaincfg/chainhash"
     9  	"github.com/mit-dci/lit/btcutil/txscript"
    10  	"github.com/mit-dci/lit/crypto/fastsha256"
    11  	"github.com/mit-dci/lit/crypto/koblitz"
    12  	"github.com/mit-dci/lit/lnutil"
    13  	"github.com/mit-dci/lit/logging"
    14  	"github.com/mit-dci/lit/portxo"
    15  	"github.com/mit-dci/lit/sig64"
    16  	"github.com/mit-dci/lit/wire"
    17  )
    18  
    19  /* CloseChannel --- cooperative close
    20  This is the simplified close which sends to the same outputs as a break tx,
    21  just with no timeouts.
    22  
    23  Users might want a more advanced close function which allows multiple outputs.
    24  They can exchange txouts and sigs.  That could be "fancyClose", but this is
    25  just close, so only a signature is sent by the initiator, and the receiver
    26  doesn't reply, as the channel is closed.
    27  
    28  */
    29  
    30  // CoopClose requests a cooperative close of the channel
    31  func (nd *LitNode) CoopClose(q *Qchan) error {
    32  
    33  	nd.RemoteMtx.Lock()
    34  	_, ok := nd.RemoteCons[q.Peer()]
    35  	nd.RemoteMtx.Unlock()
    36  	if !ok {
    37  		return fmt.Errorf("not connected to peer %d ", q.Peer())
    38  	}
    39  
    40  	if q.CloseData.Closed {
    41  		return fmt.Errorf("can't close (%d,%d): already closed",
    42  			q.KeyGen.Step[3]&0x7fffffff, q.KeyGen.Step[4]&0x7fffffff)
    43  	}
    44  
    45  	for _, h := range q.State.HTLCs {
    46  		if !h.Cleared {
    47  			return fmt.Errorf("can't close (%d,%d): there are uncleared HTLCs",
    48  				q.KeyGen.Step[3]&0x7fffffff, q.KeyGen.Step[4]&0x7fffffff)
    49  		}
    50  	}
    51  
    52  	tx, err := q.SimpleCloseTx()
    53  	if err != nil {
    54  		return err
    55  	}
    56  
    57  	sig, err := nd.SignSimpleClose(q, tx)
    58  	if err != nil {
    59  		return err
    60  	}
    61  
    62  	// Save something, just so the UI marks it as closed, and
    63  	// we don't accept payments on this channel anymore.
    64  
    65  	// save channel state as closed.  We know the txid... even though that
    66  	// txid may not actually happen.
    67  	nd.RemoteMtx.Lock()
    68  	q.LastUpdate = uint64(time.Now().UnixNano() / 1000)
    69  	q.CloseData.Closed = true
    70  	q.CloseData.CloseTxid = tx.TxHash()
    71  	nd.RemoteMtx.Unlock()
    72  	err = nd.SaveQchanUtxoData(q)
    73  	if err != nil {
    74  		return err
    75  	}
    76  
    77  	var signature [64]byte
    78  	copy(signature[:], sig[:])
    79  
    80  	// Save something to db... TODO
    81  	// Should save something, just so the UI marks it as closed, and
    82  	// we don't accept payments on this channel anymore.
    83  
    84  	outMsg := lnutil.NewCloseReqMsg(q.Peer(), q.Op, signature)
    85  	nd.tmpSendLitMsg(outMsg)
    86  
    87  	return nil
    88  }
    89  
    90  // CloseReqHandler takes in a close request from a remote host, signs and
    91  // responds with a close response.  Obviously later there will be some judgment
    92  // over what to do, but for now it just signs whatever it's requested to.
    93  
    94  func (nd *LitNode) CloseReqHandler(msg lnutil.CloseReqMsg) {
    95  	opArr := lnutil.OutPointToBytes(msg.Outpoint)
    96  
    97  	// get channel
    98  	q, err := nd.GetQchan(opArr)
    99  	if err != nil {
   100  		logging.Errorf("CloseReqHandler GetQchan err %s", err.Error())
   101  		return
   102  	}
   103  
   104  	if nd.SubWallet[q.Coin()] == nil {
   105  		logging.Errorf("Not connected to coin type %d\n", q.Coin())
   106  	}
   107  
   108  	for _, h := range q.State.HTLCs {
   109  		if !h.Cleared {
   110  			logging.Errorf("can't close (%d,%d): there are uncleared HTLCs",
   111  				q.KeyGen.Step[3]&0x7fffffff, q.KeyGen.Step[4]&0x7fffffff)
   112  			return
   113  		}
   114  	}
   115  
   116  	// verify their sig?  should do that before signing our side just to be safe
   117  	// TODO -- yeah we need to verify their sig
   118  
   119  	// build close tx
   120  	tx, err := q.SimpleCloseTx()
   121  	if err != nil {
   122  		logging.Errorf("CloseReqHandler SimpleCloseTx err %s", err.Error())
   123  		return
   124  	}
   125  
   126  	hCache := txscript.NewTxSigHashes(tx)
   127  
   128  	pre, _, err := lnutil.FundTxScript(q.MyPub, q.TheirPub)
   129  	if err != nil {
   130  		logging.Errorf("CloseReqHandler Sig err %s", err.Error())
   131  		return
   132  	}
   133  
   134  	parsed, err := txscript.ParseScript(pre)
   135  	if err != nil {
   136  		logging.Errorf("CloseReqHandler Sig err %s", err.Error())
   137  		return
   138  	}
   139  	// always sighash all
   140  	hash := txscript.CalcWitnessSignatureHash(
   141  		parsed, hCache, txscript.SigHashAll, tx, 0, q.Value)
   142  
   143  	theirBigSig := sig64.SigDecompress(msg.Signature)
   144  
   145  	// sig is pre-truncated; last byte for sighashtype is always sighashAll
   146  	pSig, err := koblitz.ParseDERSignature(theirBigSig, koblitz.S256())
   147  	if err != nil {
   148  		logging.Errorf("CloseReqHandler Sig err %s", err.Error())
   149  		return
   150  	}
   151  	theirPubKey, err := koblitz.ParsePubKey(q.TheirPub[:], koblitz.S256())
   152  	if err != nil {
   153  		logging.Errorf("CloseReqHandler Sig err %s", err.Error())
   154  		return
   155  	}
   156  
   157  	worked := pSig.Verify(hash, theirPubKey)
   158  	if !worked {
   159  		logging.Errorf("CloseReqHandler Sig err invalid signature on close tx %s", err.Error())
   160  		return
   161  	}
   162  
   163  	// sign close
   164  	mySig, err := nd.SignSimpleClose(q, tx)
   165  	if err != nil {
   166  		logging.Errorf("CloseReqHandler SignSimpleClose err %s", err.Error())
   167  		return
   168  	}
   169  
   170  	myBigSig := sig64.SigDecompress(mySig)
   171  
   172  	// put the sighash all byte on the end of both signatures
   173  	myBigSig = append(myBigSig, byte(txscript.SigHashAll))
   174  	theirBigSig = append(theirBigSig, byte(txscript.SigHashAll))
   175  
   176  	pre, swap, err := lnutil.FundTxScript(q.MyPub, q.TheirPub)
   177  	if err != nil {
   178  		logging.Errorf("CloseReqHandler FundTxScript err %s", err.Error())
   179  		return
   180  	}
   181  
   182  	// swap if needed
   183  	if swap {
   184  		tx.TxIn[0].Witness = SpendMultiSigWitStack(pre, theirBigSig, myBigSig)
   185  	} else {
   186  		tx.TxIn[0].Witness = SpendMultiSigWitStack(pre, myBigSig, theirBigSig)
   187  	}
   188  	logging.Info(lnutil.TxToString(tx))
   189  
   190  	// save channel state to db as closed.
   191  	nd.RemoteMtx.Lock()
   192  	q.LastUpdate = uint64(time.Now().UnixNano() / 1000)
   193  	q.CloseData.Closed = true
   194  	q.CloseData.CloseTxid = tx.TxHash()
   195  	nd.RemoteMtx.Unlock()
   196  	err = nd.SaveQchanUtxoData(q)
   197  	if err != nil {
   198  		logging.Errorf("CloseReqHandler SaveQchanUtxoData err %s", err.Error())
   199  		return
   200  	}
   201  
   202  	// broadcast
   203  	err = nd.SubWallet[q.Coin()].PushTx(tx)
   204  	if err != nil {
   205  		logging.Errorf("CloseReqHandler NewOutgoingTx err %s", err.Error())
   206  		return
   207  	}
   208  
   209  	peerIdx := q.Peer()
   210  	peer := nd.PeerMan.GetPeerByIdx(int32(peerIdx))
   211  
   212  	// Broadcast that we've closed a channel
   213  	closed := ChannelStateUpdateEvent{
   214  		Action:   "closed",
   215  		ChanIdx:  q.Idx(),
   216  		State:    q.State,
   217  		TheirPub: peer.GetPubkey(),
   218  		CoinType: q.Coin(),
   219  	}
   220  
   221  	if succeed, err := nd.Events.Publish(closed); err != nil {
   222  		logging.Errorf("ClosedHandler publish err %s", err)
   223  		return
   224  	} else if !succeed {
   225  		logging.Errorf("ClosedHandler publish did not succeed")
   226  		return
   227  	}
   228  
   229  	return
   230  }
   231  
   232  func (q *Qchan) GetHtlcTxosWithElkPointsAndRevPub(tx *wire.MsgTx, mine bool, theirElkPoint, myElkPoint, revPub [33]byte) ([]*wire.TxOut, []uint32, error) {
   233  	htlcOutsInTx := make([]*wire.TxOut, 0)
   234  	htlcOutIndexesInTx := make([]uint32, 0)
   235  	htlcOuts := make([]*wire.TxOut, 0)
   236  	for _, h := range q.State.HTLCs {
   237  		txOut, err := q.GenHTLCOutWithElkPointsAndRevPub(h, mine, theirElkPoint, myElkPoint, revPub)
   238  		if err != nil {
   239  			return nil, nil, err
   240  		}
   241  		htlcOuts = append(htlcOuts, txOut)
   242  	}
   243  
   244  	for i, out := range tx.TxOut {
   245  		htlcOut := false
   246  		for _, hOut := range htlcOuts {
   247  			if out.Value == hOut.Value && bytes.Equal(out.PkScript, hOut.PkScript) {
   248  				// This is an HTLC output
   249  				logging.Info("Found HTLC output at index %d", i)
   250  				htlcOut = true
   251  				break
   252  			}
   253  		}
   254  		if htlcOut {
   255  			htlcOutsInTx = append(htlcOutsInTx, out)
   256  			htlcOutIndexesInTx = append(htlcOutIndexesInTx, uint32(i))
   257  		}
   258  	}
   259  
   260  	return htlcOutsInTx, htlcOutIndexesInTx, nil
   261  }
   262  
   263  func (q *Qchan) GetHtlcTxos(tx *wire.MsgTx, mine bool) ([]*wire.TxOut, []uint32, error) {
   264  	revPub, _, _, err := q.GetKeysFromState(mine)
   265  	if err != nil {
   266  		return nil, nil, err
   267  	}
   268  
   269  	curElk, err := q.ElkPoint(false, q.State.StateIdx)
   270  	if err != nil {
   271  		return nil, nil, err
   272  	}
   273  
   274  	return q.GetHtlcTxosWithElkPointsAndRevPub(tx, mine, q.State.ElkPoint, curElk, revPub)
   275  }
   276  
   277  // GetCloseTxos takes in a tx and sets the QcloseTXO fields based on the tx.
   278  // It also returns the spendable (u)txos generated by the close.
   279  // TODO way too long.  Need to split up.
   280  // TODO problem with collisions, and insufficiently high elk receiver...?
   281  func (q *Qchan) GetCloseTxos(tx *wire.MsgTx) ([]portxo.PorTxo, error) {
   282  	if tx == nil {
   283  		return nil, fmt.Errorf("IngesGetCloseTxostCloseTx: nil tx")
   284  	}
   285  	txid := tx.TxHash()
   286  	// double check -- does this tx actually close the channel?
   287  	if !(len(tx.TxIn) == 1 && lnutil.OutPointsEqual(tx.TxIn[0].PreviousOutPoint, q.Op)) {
   288  		return nil, fmt.Errorf("tx %s doesn't spend channel outpoint %s",
   289  			txid.String(), q.Op.String())
   290  	}
   291  	var shIdx, pkhIdx uint32
   292  	var pkhIsMine bool
   293  	cTxos := make([]portxo.PorTxo, 1)
   294  	myPKHPkSript := lnutil.DirectWPKHScript(q.MyRefundPub)
   295  
   296  	htlcOutsInTx, htlcOutIndexesInTx, err := q.GetHtlcTxos(tx, false)
   297  	if err != nil {
   298  		return nil, err
   299  	}
   300  	htlcOutsInOurTx, htlcOutIndexesInOurTx, err := q.GetHtlcTxos(tx, true)
   301  	if err != nil {
   302  		return nil, err
   303  	}
   304  	htlcOutsInTx = append(htlcOutsInTx, htlcOutsInOurTx...)
   305  	htlcOutIndexesInTx = append(htlcOutIndexesInTx, htlcOutIndexesInOurTx...)
   306  
   307  	shIdx = 999 // set high here to detect if there's no SH output
   308  	// Classify outputs. If output is an HTLC, do nothing, since there is a
   309  	// separate function for that
   310  	for i, out := range tx.TxOut {
   311  		if len(out.PkScript) == 34 {
   312  			htlcOut := false
   313  			for _, idx := range htlcOutIndexesInTx {
   314  				if uint32(i) == idx {
   315  					htlcOut = true
   316  					break
   317  				}
   318  			}
   319  
   320  			// There should be only one other script output other than HTLCs which
   321  			// is the closing script with timelock
   322  			if !htlcOut {
   323  				shIdx = uint32(i)
   324  			}
   325  		} else if bytes.Equal(myPKHPkSript, out.PkScript) {
   326  			pkhIdx = uint32(i)
   327  			pkhIsMine = true
   328  		}
   329  	}
   330  
   331  	// if pkh is mine, grab it.
   332  	if pkhIsMine {
   333  		logging.Info("got PKH output [%d] from channel close", pkhIdx)
   334  		var pkhTxo portxo.PorTxo // create new utxo and copy into it
   335  
   336  		pkhTxo.Op.Hash = txid
   337  		pkhTxo.Op.Index = pkhIdx
   338  		pkhTxo.Height = q.CloseData.CloseHeight
   339  		// keypath same, use different
   340  		pkhTxo.KeyGen = q.KeyGen
   341  		// same keygen as underlying channel, but use is refund
   342  		pkhTxo.KeyGen.Step[2] = UseChannelRefund
   343  
   344  		pkhTxo.Mode = portxo.TxoP2WPKHComp
   345  		pkhTxo.Value = tx.TxOut[pkhIdx].Value
   346  		// PKH, could omit this
   347  		pkhTxo.PkScript = tx.TxOut[pkhIdx].PkScript
   348  		cTxos[0] = pkhTxo
   349  	}
   350  
   351  	// get state hint based on pkh match.  If pkh is mine, that's their TX & hint.
   352  	// if there's no PKH output for me, the TX is mine, so use my hint.
   353  	var comNum uint64
   354  	if pkhIsMine {
   355  		comNum = GetStateIdxFromTx(tx, q.GetChanHint(false))
   356  	} else {
   357  		comNum = GetStateIdxFromTx(tx, q.GetChanHint(true))
   358  	}
   359  	if comNum > q.State.StateIdx { // future state, uhoh.  Crash for now.
   360  		logging.Info("indicated state %d but we know up to %d",
   361  			comNum, q.State.StateIdx)
   362  		return cTxos, nil
   363  	}
   364  
   365  	// if we didn't get the pkh, and the comNum is current, we get the SH output.
   366  	// also we probably closed ourselves.  Regular timeout
   367  	if !pkhIsMine && shIdx < 999 && comNum != 0 && comNum == q.State.StateIdx {
   368  		theirElkPoint, err := q.ElkPoint(false, comNum)
   369  		if err != nil {
   370  			return nil, err
   371  		}
   372  
   373  		// build script to store in porTxo, make pubkeys
   374  		timeoutPub := lnutil.AddPubsEZ(q.MyHAKDBase, theirElkPoint)
   375  		revokePub := lnutil.CombinePubs(q.TheirHAKDBase, theirElkPoint)
   376  
   377  		script := lnutil.CommitScript(revokePub, timeoutPub, q.Delay)
   378  		// script check.  redundant / just in case
   379  		genSH := fastsha256.Sum256(script)
   380  		if !bytes.Equal(genSH[:], tx.TxOut[shIdx].PkScript[2:34]) {
   381  			logging.Warnf("got different observed and generated SH scripts.\n")
   382  			logging.Warnf("in %s:%d, see %x\n", txid, shIdx, tx.TxOut[shIdx].PkScript)
   383  			logging.Warnf("generated %x \n", genSH)
   384  			logging.Warnf("revokable pub %x\ntimeout pub %x\n", revokePub, timeoutPub)
   385  		}
   386  
   387  		// create the ScriptHash, timeout portxo.
   388  		var shTxo portxo.PorTxo // create new utxo and copy into it
   389  		// use txidx's elkrem as it may not be most recent
   390  		elk, err := q.ElkSnd.AtIndex(comNum)
   391  		if err != nil {
   392  			return nil, err
   393  		}
   394  		// keypath is the same, except for use
   395  		shTxo.KeyGen = q.KeyGen
   396  
   397  		shTxo.Op.Hash = txid
   398  		shTxo.Op.Index = shIdx
   399  		shTxo.Height = q.CloseData.CloseHeight
   400  
   401  		shTxo.KeyGen.Step[2] = UseChannelHAKDBase
   402  
   403  		elkpoint := lnutil.ElkPointFromHash(elk)
   404  		addhash := chainhash.DoubleHashH(append(elkpoint[:], q.MyHAKDBase[:]...))
   405  
   406  		shTxo.PrivKey = addhash
   407  
   408  		shTxo.Mode = portxo.TxoP2WSHComp
   409  		shTxo.Value = tx.TxOut[shIdx].Value
   410  		shTxo.Seq = uint32(q.Delay)
   411  		shTxo.PreSigStack = make([][]byte, 1) // revoke SH has one presig item
   412  		shTxo.PreSigStack[0] = nil            // and that item is a nil (timeout)
   413  
   414  		shTxo.PkScript = script
   415  		cTxos[0] = shTxo
   416  	}
   417  
   418  	// if we got the pkh, and the comNum is too old, we can get the SH.  Justice.
   419  	if pkhIsMine && comNum != 0 && comNum < q.State.StateIdx {
   420  		logging.Info("Executing Justice!")
   421  
   422  		// ---------- revoked SH is mine
   423  		// invalid previous state, can be grabbed!
   424  		// make MY elk points
   425  		myElkPoint, err := q.ElkPoint(true, comNum)
   426  		if err != nil {
   427  			return nil, err
   428  		}
   429  
   430  		theirElkPoint, err := q.ElkPoint(false, comNum)
   431  		if err != nil {
   432  			return nil, err
   433  		}
   434  
   435  		timeoutPub := lnutil.AddPubsEZ(q.TheirHAKDBase, myElkPoint)
   436  		revokePub := lnutil.CombinePubs(q.MyHAKDBase, myElkPoint)
   437  		script := lnutil.CommitScript(revokePub, timeoutPub, q.Delay)
   438  
   439  		htlcOutsInTx, htlcOutIndexesInTx, err := q.GetHtlcTxosWithElkPointsAndRevPub(tx, false, myElkPoint, theirElkPoint, revokePub)
   440  		if err != nil {
   441  			return nil, err
   442  		}
   443  
   444  		// Do this search again with the HTLC TXOs using the correct elkpoints
   445  		// There's probably a better solution for this.
   446  
   447  		shIdx = 999 // set high here to detect if there's no SH output
   448  		// Classify outputs. If output is an HTLC, do nothing, since there is a
   449  		// separate function for that
   450  		for i, out := range tx.TxOut {
   451  			if len(out.PkScript) == 34 {
   452  				htlcOut := false
   453  				for _, idx := range htlcOutIndexesInTx {
   454  					if uint32(i) == idx {
   455  						htlcOut = true
   456  						break
   457  					}
   458  				}
   459  
   460  				// There should be only one other script output other than HTLCs which
   461  				// is the closing script with timelock
   462  				if !htlcOut {
   463  					shIdx = uint32(i)
   464  				}
   465  			}
   466  		}
   467  
   468  		logging.Info("P2SH output from channel (non-HTLC output) is at %d", shIdx)
   469  
   470  		// script check
   471  		wshScript := lnutil.P2WSHify(script)
   472  		if !bytes.Equal(wshScript[:], tx.TxOut[shIdx].PkScript) {
   473  			logging.Warnf("got different observed and generated SH scripts.\n")
   474  			logging.Warnf("in %s:%d, see %x\n", txid, shIdx, tx.TxOut[shIdx].PkScript)
   475  			logging.Warnf("generated %x \n", wshScript)
   476  			logging.Warnf("revokable pub %x\ntimeout pub %x\n", revokePub, timeoutPub)
   477  		}
   478  
   479  		// myElkHashR added to HAKD private key
   480  		elk, err := q.ElkRcv.AtIndex(comNum)
   481  		if err != nil {
   482  			return nil, err
   483  		}
   484  
   485  		var shTxo portxo.PorTxo // create new utxo and copy into it
   486  		shTxo.KeyGen = q.KeyGen
   487  		shTxo.Op.Hash = txid
   488  		shTxo.Op.Index = shIdx
   489  		shTxo.Height = q.CloseData.CloseHeight
   490  
   491  		shTxo.KeyGen.Step[2] = UseChannelHAKDBase
   492  
   493  		shTxo.PrivKey = lnutil.ElkScalar(elk)
   494  
   495  		// just return the elkScalar and let
   496  		// something modify it before export due to the seq=1 flag.
   497  
   498  		shTxo.PkScript = script
   499  		shTxo.Value = tx.TxOut[shIdx].Value
   500  		shTxo.Mode = portxo.TxoP2WSHComp
   501  		shTxo.Seq = 1                         // 1 means grab immediately
   502  		shTxo.PreSigStack = make([][]byte, 1) // timeout SH has one presig item
   503  		shTxo.PreSigStack[0] = []byte{0x01}   // and that item is a 1 (justice)
   504  		cTxos = append(cTxos, shTxo)
   505  
   506  		logging.Info("There are %d HTLC Outs to do justice on in this transaction\n", len(htlcOutsInTx))
   507  		// Also grab HTLCs. They are mine now too :)
   508  		for i, txo := range htlcOutsInTx {
   509  			logging.Info("Executing Justice on HTLC TXO!")
   510  
   511  			// script check
   512  			htlcScript, err := q.GenHTLCScriptWithElkPointsAndRevPub(q.State.HTLCs[i], false, theirElkPoint, myElkPoint, revokePub)
   513  			if err != nil {
   514  				return nil, err
   515  			}
   516  			wshHTLCScript := lnutil.P2WSHify(htlcScript)
   517  
   518  			if !bytes.Equal(wshHTLCScript[:], txo.PkScript) {
   519  				logging.Warnf("got different observed and generated HTLC scripts.\n")
   520  				logging.Warnf("in %s:%d, see %x\n", txid, htlcOutIndexesInTx[i], txo.PkScript)
   521  				logging.Warnf("generated %x \n", wshHTLCScript)
   522  				logging.Warnf("revokable pub %x\ntimeout pub %x\n", revokePub, timeoutPub)
   523  			}
   524  
   525  			var htlcTxo portxo.PorTxo // create new utxo and copy into it
   526  			htlcTxo.KeyGen = q.KeyGen
   527  			htlcTxo.Op.Hash = txid
   528  			htlcTxo.Op.Index = htlcOutIndexesInTx[i]
   529  			htlcTxo.Height = q.CloseData.CloseHeight
   530  
   531  			htlcTxo.KeyGen.Step[2] = UseChannelHAKDBase
   532  
   533  			htlcTxo.PrivKey = lnutil.ElkScalar(elk)
   534  
   535  			// just return the elkScalar and let
   536  			// something modify it before export due to the seq=1 flag.
   537  
   538  			htlcTxo.PkScript = script
   539  			htlcTxo.Value = txo.Value
   540  			htlcTxo.Mode = portxo.TxoP2WSHComp
   541  			htlcTxo.Seq = 1                         // 1 means grab immediately
   542  			htlcTxo.PreSigStack = make([][]byte, 1) // timeout SH has one presig item
   543  			htlcTxo.PreSigStack[0] = []byte{0x01}   // and that item is a 1 (justice)
   544  			cTxos = append(cTxos, htlcTxo)
   545  		}
   546  	}
   547  	logging.Info("Returning [%d] cTxos", len(cTxos))
   548  	return cTxos, nil
   549  }