From 852789ba0366ab62e904c1b183c477271b293bec Mon Sep 17 00:00:00 2001 From: Ononiwu Maureen <59079323+Chinwendu20@users.noreply.github.com> Date: Fri, 17 May 2024 11:59:51 +0100 Subject: [PATCH 1/3] mod: add fn package Signed-off-by: Ononiwu Maureen <59079323+Chinwendu20@users.noreply.github.com> --- go.mod | 4 +++- go.sum | 8 ++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index d9abbc76e4..bb3926bc90 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,8 @@ require ( github.com/jrick/logrotate v1.0.0 github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf github.com/lightninglabs/neutrino v0.16.0 - github.com/lightninglabs/neutrino/cache v1.1.1 + github.com/lightninglabs/neutrino/cache v1.1.2 + github.com/lightningnetwork/lnd/fn v1.0.6 github.com/lightningnetwork/lnd/ticker v1.0.0 github.com/lightningnetwork/lnd/tlv v1.0.2 github.com/stretchr/testify v1.8.4 @@ -43,6 +44,7 @@ require ( github.com/rogpeppe/go-internal v1.9.0 // indirect github.com/stretchr/objx v0.5.0 // indirect go.etcd.io/bbolt v1.3.7 // indirect + golang.org/x/exp v0.0.0-20231226003508-02704c960a9b // indirect golang.org/x/sys v0.8.0 // indirect golang.org/x/text v0.9.0 // indirect google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect diff --git a/go.sum b/go.sum index 3abc568806..d8b6948df8 100644 --- a/go.sum +++ b/go.sum @@ -95,10 +95,12 @@ github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf h1:HZKvJUHlcXI github.com/lightninglabs/gozmq v0.0.0-20191113021534-d20a764486bf/go.mod h1:vxmQPeIQxPf6Jf9rM8R+B4rKBqLA2AjttNxkFBL2Plk= github.com/lightninglabs/neutrino v0.16.0 h1:YNTQG32fPR/Zg0vvJVI65OBH8l3U18LSXXtX91hx0q0= github.com/lightninglabs/neutrino v0.16.0/go.mod h1:x3OmY2wsA18+Kc3TSV2QpSUewOCiscw2mKpXgZv2kZk= -github.com/lightninglabs/neutrino/cache v1.1.1 h1:TllWOSlkABhpgbWJfzsrdUaDH2fBy/54VSIB4vVqV8M= -github.com/lightninglabs/neutrino/cache v1.1.1/go.mod h1:XJNcgdOw1LQnanGjw8Vj44CvguYA25IMKjWFZczwZuo= +github.com/lightninglabs/neutrino/cache v1.1.2 h1:C9DY/DAPaPxbFC+xNNEI/z1SJY9GS3shmlu5hIQ798g= +github.com/lightninglabs/neutrino/cache v1.1.2/go.mod h1:XJNcgdOw1LQnanGjw8Vj44CvguYA25IMKjWFZczwZuo= github.com/lightningnetwork/lnd/clock v1.0.1 h1:QQod8+m3KgqHdvVMV+2DRNNZS1GRFir8mHZYA+Z2hFo= github.com/lightningnetwork/lnd/clock v1.0.1/go.mod h1:KnQudQ6w0IAMZi1SgvecLZQZ43ra2vpDNj7H/aasemg= +github.com/lightningnetwork/lnd/fn v1.0.6 h1:LOeKEPUMxXcu3wax6zxz7GzNFxSLGMUM6hbHLkujd1s= +github.com/lightningnetwork/lnd/fn v1.0.6/go.mod h1:P027+0CyELd92H9gnReUkGGAqbFA1HwjHWdfaDFD51U= github.com/lightningnetwork/lnd/queue v1.0.1 h1:jzJKcTy3Nj5lQrooJ3aaw9Lau3I0IwvQR5sqtjdv2R0= github.com/lightningnetwork/lnd/queue v1.0.1/go.mod h1:vaQwexir73flPW43Mrm7JOgJHmcEFBWWSl9HlyASoms= github.com/lightningnetwork/lnd/ticker v1.0.0 h1:S1b60TEGoTtCe2A0yeB+ecoj/kkS4qpwh6l+AkQEZwU= @@ -137,6 +139,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.7.0 h1:AvwMYaRytfdeVt3u6mLaxYtErKYjxA2OXjJ1HHq6t3A= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/exp v0.0.0-20231226003508-02704c960a9b h1:kLiC65FbiHWFAOu+lxwNPujcsl8VYyTYYEZnsOO1WK4= +golang.org/x/exp v0.0.0-20231226003508-02704c960a9b/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI= golang.org/x/net v0.0.0-20180719180050-a680a1efc54d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= From a32b9038f1ea8b2d98b524c8292ad4a7b6ecb976 Mon Sep 17 00:00:00 2001 From: Ononiwu Maureen <59079323+Chinwendu20@users.noreply.github.com> Date: Mon, 13 May 2024 21:11:18 +0100 Subject: [PATCH 2/3] wallet: select utxos should not contain duplicates Signed-off-by: Ononiwu Maureen <59079323+Chinwendu20@users.noreply.github.com> --- wallet/createtx.go | 6 ++ wallet/createtx_test.go | 124 +++++++++++++++++++++++++++++++--------- 2 files changed, 102 insertions(+), 28 deletions(-) diff --git a/wallet/createtx.go b/wallet/createtx.go index 9c2b061984..27ded5c7ad 100644 --- a/wallet/createtx.go +++ b/wallet/createtx.go @@ -20,6 +20,7 @@ import ( "github.com/btcsuite/btcwallet/wallet/txsizes" "github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/btcwallet/wtxmgr" + "github.com/lightningnetwork/lnd/fn" ) func makeInputSource(eligible []Coin) txauthor.InputSource { @@ -179,6 +180,11 @@ func (w *Wallet) txToOutputs(outputs []*wire.TxOut, var inputSource txauthor.InputSource if len(selectedUtxos) > 0 { + dedupUtxos := fn.NewSet(selectedUtxos...) + if len(dedupUtxos) != len(selectedUtxos) { + return errors.New("selected UTXOs contain " + + "duplicate values") + } eligibleByOutpoint := make( map[wire.OutPoint]wtxmgr.Credit, ) diff --git a/wallet/createtx_test.go b/wallet/createtx_test.go index c0badf35d7..f224b4a43c 100644 --- a/wallet/createtx_test.go +++ b/wallet/createtx_test.go @@ -449,42 +449,110 @@ func TestSelectUtxosTxoToOutpoint(t *testing.T) { require.Len(t, unspent, 4, "expected 4 unspent "+ "utxos") - selectUtxos := []wire.OutPoint{ + tCases := []struct { + name string + selectUtxos []wire.OutPoint + errString string + }{ { - Hash: incomingTx.TxHash(), - Index: 1, + name: "Duplicate utxo values", + selectUtxos: []wire.OutPoint{ + { + Hash: incomingTx.TxHash(), + Index: 1, + }, + { + Hash: incomingTx.TxHash(), + Index: 1, + }, + }, + errString: "selected UTXOs contain duplicate values", }, { - Hash: incomingTx.TxHash(), - Index: 2, + name: "all selected utxos not eligible for spending", + selectUtxos: []wire.OutPoint{ + { + Hash: chainhash.Hash([32]byte{1}), + Index: 1, + }, + { + Hash: chainhash.Hash([32]byte{3}), + Index: 1, + }, + }, + errString: "selected outpoint not eligible for " + + "spending", + }, + { + name: "some select utxos not eligible for spending", + selectUtxos: []wire.OutPoint{ + { + Hash: chainhash.Hash([32]byte{1}), + Index: 1, + }, + { + Hash: incomingTx.TxHash(), + Index: 1, + }, + }, + errString: "selected outpoint not eligible for " + + "spending", + }, + { + name: "select utxo, no duplicates and all eligible " + + "for spending", + selectUtxos: []wire.OutPoint{ + { + Hash: incomingTx.TxHash(), + Index: 1, + }, + { + Hash: incomingTx.TxHash(), + Index: 2, + }, + }, }, } - // Test by sending 200_000. - targetTxOut := &wire.TxOut{ - Value: 200_000, - PkScript: p2trScript, - } - tx1, err := w.txToOutputs( - []*wire.TxOut{targetTxOut}, nil, nil, 0, 1, 1000, - CoinSelectionLargest, true, selectUtxos, alwaysAllowUtxo, - ) - require.NoError(t, err) + for _, tc := range tCases { + t.Run(tc.name, func(t *testing.T) { + // Test by sending 200_000. + targetTxOut := &wire.TxOut{ + Value: 200_000, + PkScript: p2trScript, + } + tx1, err := w.txToOutputs( + []*wire.TxOut{targetTxOut}, nil, nil, 0, 1, + 1000, CoinSelectionLargest, true, + tc.selectUtxos, alwaysAllowUtxo, + ) + if tc.errString != "" { + require.ErrorContains(t, err, tc.errString) + require.Nil(t, tx1) - // We expect all and only our select utxos to be input in this - // transaction. - require.Len(t, tx1.Tx.TxIn, len(selectUtxos)) + return + } - lookupSelectUtxos := make(map[wire.OutPoint]struct{}) - for _, utxo := range selectUtxos { - lookupSelectUtxos[utxo] = struct{}{} - } + require.NoError(t, err) + require.NotNil(t, tx1) - for _, tx := range tx1.Tx.TxIn { - _, ok := lookupSelectUtxos[tx.PreviousOutPoint] - require.True(t, ok, "unexpected outpoint in txin") - } + // We expect all and only our select utxos to be input + // in this transaction. + require.Len(t, tx1.Tx.TxIn, len(tc.selectUtxos)) - // Expect two outputs, change and the actual payment to the address. - require.Len(t, tx1.Tx.TxOut, 2) + lookupSelectUtxos := make(map[wire.OutPoint]struct{}) + for _, utxo := range tc.selectUtxos { + lookupSelectUtxos[utxo] = struct{}{} + } + + for _, tx := range tx1.Tx.TxIn { + _, ok := lookupSelectUtxos[tx.PreviousOutPoint] + require.True(t, ok) + } + + // Expect two outputs, change and the actual payment to + // the address. + require.Len(t, tx1.Tx.TxOut, 2) + }) + } } From 15e80268e6689fa6fe5aab0be7197aa255f33b50 Mon Sep 17 00:00:00 2001 From: Ononiwu Maureen <59079323+Chinwendu20@users.noreply.github.com> Date: Wed, 17 Jul 2024 20:56:48 +0100 Subject: [PATCH 3/3] wallet: Set birthday block and rescan importPublicKey Signed-off-by: Ononiwu Maureen <59079323+Chinwendu20@users.noreply.github.com> --- wallet/import.go | 61 ++++++++++++++++++++++++++++++++++++++----- wallet/import_test.go | 2 +- 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/wallet/import.go b/wallet/import.go index 9f37e93d90..60276c49ee 100644 --- a/wallet/import.go +++ b/wallet/import.go @@ -381,7 +381,7 @@ func (w *Wallet) ImportAccountDryRun(name string, // intend to not support importing BIP-44 keys into the wallet using the legacy // pay-to-pubkey-hash (P2PKH) scheme. func (w *Wallet) ImportPublicKey(pubKey *btcec.PublicKey, - addrType waddrmgr.AddressType) error { + addrType waddrmgr.AddressType, bs *waddrmgr.BlockStamp, rescan bool) error { // Determine what key scope the public key should belong to and import // it into the key scope's default imported account. @@ -410,7 +410,31 @@ func (w *Wallet) ImportPublicKey(pubKey *btcec.PublicKey, err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) addr, err = scopedKeyManager.ImportPublicKey(ns, pubKey, nil) - return err + if err != nil { + return err + } + + // We'll only update our birthday with the new one if it is + // before our current one. Otherwise, if we do, we can + // potentially miss detecting relevant chain events that + // occurred between them while rescanning. + birthdayBlock, _, err := w.Manager.BirthdayBlock(ns) + if err != nil { + return err + } + if bs.Height >= birthdayBlock.Height { + return nil + } + + err = w.Manager.SetBirthday(ns, bs.Timestamp) + if err != nil { + return err + } + + // To ensure this birthday block is correct, we'll mark it as + // unverified to prompt a sanity check at the next restart to + // ensure it is correct as it was provided by the caller. + return w.Manager.SetBirthdayBlock(ns, *bs, false) }) if err != nil { return err @@ -418,10 +442,35 @@ func (w *Wallet) ImportPublicKey(pubKey *btcec.PublicKey, log.Infof("Imported address %v", addr.Address()) - err = w.chainClient.NotifyReceived([]btcutil.Address{addr.Address()}) - if err != nil { - return fmt.Errorf("unable to subscribe for address "+ - "notifications: %w", err) + // Rescan blockchain for transactions with txout scripts paying to the + // imported address. + if rescan { + log.Infof("Rescanning oooooo 09") + job := &RescanJob{ + Addrs: []btcutil.Address{addr.Address()}, + OutPoints: nil, + BlockStamp: *bs, + } + + // Submit rescan job and log when the import has completed. + // Do not block on finishing the rescan. The rescan success + // or failure is logged elsewhere, and the channel is not + // required to be read, so discard the return value. + errChan := w.SubmitRescan(job) + + err = <-errChan + if err != nil { + return err + } + + } else { + err = w.chainClient.NotifyReceived( + []btcutil.Address{addr.Address()}, + ) + if err != nil { + return fmt.Errorf("unable to subscribe for address "+ + "notifications: %w", err) + } } return nil diff --git a/wallet/import_test.go b/wallet/import_test.go index c48dcc5002..5940b75dd7 100644 --- a/wallet/import_test.go +++ b/wallet/import_test.go @@ -204,7 +204,7 @@ func testImportAccount(t *testing.T, w *Wallet, tc *testCase, watchOnly bool, require.NoError(t, err) require.Equal(t, tc.expectedScope, acct2.KeyScope) - err = w.ImportPublicKey(acct3ExternalPub, tc.addrType) + err = w.ImportPublicKey(acct3ExternalPub, tc.addrType, nil, false) require.NoError(t, err) // If the wallet is watch only, there is no default account and our