diff --git a/wallet/createtx.go b/wallet/createtx.go index d534ae96d3..9c2b061984 100644 --- a/wallet/createtx.go +++ b/wallet/createtx.go @@ -140,7 +140,9 @@ func (w *Wallet) txToOutputs(outputs []*wire.TxOut, coinSelectKeyScope, changeKeyScope *waddrmgr.KeyScope, account uint32, minconf int32, feeSatPerKb btcutil.Amount, strategy CoinSelectionStrategy, dryRun bool, - selectedUtxos []wire.OutPoint) (*txauthor.AuthoredTx, error) { + selectedUtxos []wire.OutPoint, + allowUtxo func(utxo wtxmgr.Credit) bool) ( + *txauthor.AuthoredTx, error) { chainClient, err := w.requireChainClient() if err != nil { @@ -168,7 +170,8 @@ func (w *Wallet) txToOutputs(outputs []*wire.TxOut, } eligible, err := w.findEligibleOutputs( - dbtx, coinSelectKeyScope, account, minconf, bs, + dbtx, coinSelectKeyScope, account, minconf, + bs, allowUtxo, ) if err != nil { return err @@ -322,7 +325,8 @@ func (w *Wallet) txToOutputs(outputs []*wire.TxOut, func (w *Wallet) findEligibleOutputs(dbtx walletdb.ReadTx, keyScope *waddrmgr.KeyScope, account uint32, minconf int32, - bs *waddrmgr.BlockStamp) ([]wtxmgr.Credit, error) { + bs *waddrmgr.BlockStamp, + allowUtxo func(utxo wtxmgr.Credit) bool) ([]wtxmgr.Credit, error) { addrmgrNs := dbtx.ReadBucket(waddrmgrNamespaceKey) txmgrNs := dbtx.ReadBucket(wtxmgrNamespaceKey) @@ -341,6 +345,13 @@ func (w *Wallet) findEligibleOutputs(dbtx walletdb.ReadTx, for i := range unspent { output := &unspent[i] + // Restrict the selected utxos if a filter function is provided. + if allowUtxo != nil && + !allowUtxo(*output) { + + continue + } + // Only include this output if it meets the required number of // confirmations. Coinbase transactions must have reached // maturity before their outputs may be spent. diff --git a/wallet/createtx_test.go b/wallet/createtx_test.go index bb351a1fc4..c0badf35d7 100644 --- a/wallet/createtx_test.go +++ b/wallet/createtx_test.go @@ -28,6 +28,8 @@ var ( "02a4", ) testBlockHeight int32 = 276425 + + alwaysAllowUtxo = func(utxo wtxmgr.Credit) bool { return true } ) // TestTxToOutput checks that no new address is added to he database if we @@ -79,7 +81,7 @@ func TestTxToOutputsDryRun(t *testing.T) { // database us not inflated. dryRunTx, err := w.txToOutputs( txOuts, nil, nil, 0, 1, 1000, CoinSelectionLargest, true, - nil, + nil, alwaysAllowUtxo, ) if err != nil { t.Fatalf("unable to author tx: %v", err) @@ -97,7 +99,7 @@ func TestTxToOutputsDryRun(t *testing.T) { dryRunTx2, err := w.txToOutputs( txOuts, nil, nil, 0, 1, 1000, CoinSelectionLargest, true, - nil, + nil, alwaysAllowUtxo, ) if err != nil { t.Fatalf("unable to author tx: %v", err) @@ -133,7 +135,7 @@ func TestTxToOutputsDryRun(t *testing.T) { // to the database. tx, err := w.txToOutputs( txOuts, nil, nil, 0, 1, 1000, CoinSelectionLargest, false, - nil, + nil, alwaysAllowUtxo, ) if err != nil { t.Fatalf("unable to author tx: %v", err) @@ -283,7 +285,7 @@ func TestTxToOutputsRandom(t *testing.T) { createTx := func() *txauthor.AuthoredTx { tx, err := w.txToOutputs( txOuts, nil, nil, 0, 1, feeSatPerKb, - CoinSelectionRandom, true, nil, + CoinSelectionRandom, true, nil, alwaysAllowUtxo, ) require.NoError(t, err) return tx @@ -355,7 +357,7 @@ func TestCreateSimpleCustomChange(t *testing.T) { } tx1, err := w.txToOutputs( []*wire.TxOut{targetTxOut}, nil, nil, 0, 1, 1000, - CoinSelectionLargest, true, nil, + CoinSelectionLargest, true, nil, alwaysAllowUtxo, ) require.NoError(t, err) @@ -381,7 +383,7 @@ func TestCreateSimpleCustomChange(t *testing.T) { tx2, err := w.txToOutputs( []*wire.TxOut{targetTxOut}, &waddrmgr.KeyScopeBIP0086, &waddrmgr.KeyScopeBIP0084, 0, 1, 1000, CoinSelectionLargest, - true, nil, + true, nil, alwaysAllowUtxo, ) require.NoError(t, err) @@ -465,7 +467,7 @@ func TestSelectUtxosTxoToOutpoint(t *testing.T) { } tx1, err := w.txToOutputs( []*wire.TxOut{targetTxOut}, nil, nil, 0, 1, 1000, - CoinSelectionLargest, true, selectUtxos, + CoinSelectionLargest, true, selectUtxos, alwaysAllowUtxo, ) require.NoError(t, err) diff --git a/wallet/txauthor/author.go b/wallet/txauthor/author.go index 2ac83eab40..7966e40a17 100644 --- a/wallet/txauthor/author.go +++ b/wallet/txauthor/author.go @@ -7,6 +7,7 @@ package txauthor import ( "errors" + "fmt" "github.com/btcsuite/btcd/btcutil" "github.com/btcsuite/btcd/chaincfg" @@ -104,6 +105,7 @@ func NewUnsignedTransaction(outputs []*wire.TxOut, feeRatePerKb btcutil.Amount, if err != nil { return nil, err } + fmt.Println("FEE", inputAmount, targetAmount, targetFee) if inputAmount < targetAmount+targetFee { return nil, insufficientFundsError{} } diff --git a/wallet/wallet.go b/wallet/wallet.go index 7356348cc1..17dda9cbd4 100644 --- a/wallet/wallet.go +++ b/wallet/wallet.go @@ -1201,6 +1201,7 @@ type ( dryRun bool resp chan createTxResponse selectUtxos []wire.OutPoint + allowUtxo func(wtxmgr.Credit) bool } createTxResponse struct { tx *txauthor.AuthoredTx @@ -1239,9 +1240,10 @@ out: } tx, err := w.txToOutputs( - txr.outputs, txr.coinSelectKeyScope, txr.changeKeyScope, - txr.account, txr.minconf, txr.feeSatPerKB, - txr.coinSelectionStrategy, txr.dryRun, txr.selectUtxos, + txr.outputs, txr.coinSelectKeyScope, + txr.changeKeyScope, txr.account, txr.minconf, + txr.feeSatPerKB, txr.coinSelectionStrategy, + txr.dryRun, txr.selectUtxos, txr.allowUtxo, ) release() @@ -1259,6 +1261,7 @@ out: type txCreateOptions struct { changeKeyScope *waddrmgr.KeyScope selectUtxos []wire.OutPoint + allowUtxo func(wtxmgr.Credit) bool } // TxCreateOption is a set of optional arguments to modify the tx creation @@ -1289,6 +1292,15 @@ func WithCustomSelectUtxos(utxos []wire.OutPoint) TxCreateOption { } } +// WithFilterUtxo is used to restrict the selection of the internal wallet +// inputs by further external conditions. Utxos which pass the filter are +// considered when creating the transaction. +func WithUtxoFilter(allowUtxo func(utxo wtxmgr.Credit) bool) TxCreateOption { + return func(opts *txCreateOptions) { + opts.allowUtxo = allowUtxo + } +} + // CreateSimpleTx creates a new signed transaction spending unspent outputs with // at least minconf confirmations spending to any number of address/amount // pairs. Only unspent outputs belonging to the given key scope and account will @@ -1333,6 +1345,7 @@ func (w *Wallet) CreateSimpleTx(coinSelectKeyScope *waddrmgr.KeyScope, dryRun: dryRun, resp: make(chan createTxResponse), selectUtxos: opts.selectUtxos, + allowUtxo: opts.allowUtxo, } w.createTxRequests <- req resp := <-req.resp diff --git a/wtxmgr/db.go b/wtxmgr/db.go index 9bb7986d02..93a2f93538 100644 --- a/wtxmgr/db.go +++ b/wtxmgr/db.go @@ -394,6 +394,9 @@ func valueTxRecord(rec *TxRecord) ([]byte, error) { copy(v[8:], rec.SerializedTx) } byteOrder.PutUint64(v, uint64(rec.Received.Unix())) + + // Add the unstable flag here via TLV records. + return v, nil } @@ -434,6 +437,9 @@ func readRawTxRecord(txHash *chainhash.Hash, v []byte, rec *TxRecord) error { bucketTxRecords, txHash) return storeError(ErrData, str, err) } + + // Fetch the tlv record of the unstable record. + return nil } @@ -680,21 +686,21 @@ func deleteRawCredit(ns walletdb.ReadWriteBucket, k []byte) error { // // Example usage: // -// prefix := keyTxRecord(txHash, block) -// it := makeCreditIterator(ns, prefix) -// for it.next() { -// // Use it.elem -// // If necessary, read additional details from it.ck, it.cv -// } -// if it.err != nil { -// // Handle error -// } +// prefix := keyTxRecord(txHash, block) +// it := makeCreditIterator(ns, prefix) +// for it.next() { +// // Use it.elem +// // If necessary, read additional details from it.ck, it.cv +// } +// if it.err != nil { +// // Handle error +// } // // The elem's Spent field is not set to true if the credit is spent by an // unmined transaction. To check for this case: // -// k := canonicalOutPoint(&txHash, it.elem.Index) -// it.elem.Spent = existsRawUnminedInput(ns, k) != nil +// k := canonicalOutPoint(&txHash, it.elem.Index) +// it.elem.Spent = existsRawUnminedInput(ns, k) != nil type creditIterator struct { c walletdb.ReadWriteCursor // Set to nil after final iteration prefix []byte @@ -920,15 +926,15 @@ func deleteRawDebit(ns walletdb.ReadWriteBucket, k []byte) error { // // Example usage: // -// prefix := keyTxRecord(txHash, block) -// it := makeDebitIterator(ns, prefix) -// for it.next() { -// // Use it.elem -// // If necessary, read additional details from it.ck, it.cv -// } -// if it.err != nil { -// // Handle error -// } +// prefix := keyTxRecord(txHash, block) +// it := makeDebitIterator(ns, prefix) +// for it.next() { +// // Use it.elem +// // If necessary, read additional details from it.ck, it.cv +// } +// if it.err != nil { +// // Handle error +// } type debitIterator struct { c walletdb.ReadWriteCursor // Set to nil after final iteration prefix []byte @@ -1097,22 +1103,22 @@ func deleteRawUnminedCredit(ns walletdb.ReadWriteBucket, k []byte) error { // unminedCreditIterator allows for cursor iteration over all credits, in order, // from a single unmined transaction. // -// Example usage: +// Example usage: // -// it := makeUnminedCreditIterator(ns, txHash) -// for it.next() { -// // Use it.elem, it.ck and it.cv -// // Optionally, use it.delete() to remove this k/v pair -// } -// if it.err != nil { -// // Handle error -// } +// it := makeUnminedCreditIterator(ns, txHash) +// for it.next() { +// // Use it.elem, it.ck and it.cv +// // Optionally, use it.delete() to remove this k/v pair +// } +// if it.err != nil { +// // Handle error +// } // // The spentness of the credit is not looked up for performance reasons (because // for unspent credits, it requires another lookup in another bucket). If this // is needed, it may be checked like this: // -// spent := existsRawUnminedInput(ns, it.ck) != nil +// spent := existsRawUnminedInput(ns, it.ck) != nil type unminedCreditIterator struct { c walletdb.ReadWriteCursor prefix []byte