From b92abc00b5ad1385fb4d745fe6445e9bdb21f0cf Mon Sep 17 00:00:00 2001 From: akekeke Date: Tue, 30 Oct 2018 19:21:23 +0900 Subject: [PATCH 01/28] init port of code from dlcdemo --- internal/wallet/preparetx.go | 51 ++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 internal/wallet/preparetx.go diff --git a/internal/wallet/preparetx.go b/internal/wallet/preparetx.go new file mode 100644 index 0000000..09fd348 --- /dev/null +++ b/internal/wallet/preparetx.go @@ -0,0 +1,51 @@ +package wallet + +import ( + "fmt" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" + _ "github.com/btcsuite/btcwallet/walletdb/bdb" // blank import for bolt db driver + "github.com/dgarage/dlc-demo/src/dlc" +) + +// FundTx adds inputs to a transaction until amount. +func (w *Wallet) FundTx(tx *wire.MsgTx, amount, efee int64) error { + list, err := w.rpc.ListUnspent() + if err != nil { + return err + } + outs := []*wire.OutPoint{} + total := int64(0) + addfee := int64(0) + for _, utxo := range list { + txid, _ := chainhash.NewHashFromStr(utxo.TxID) + outs = append(outs, wire.NewOutPoint(txid, utxo.Vout)) + a, _ := btcutil.NewAmount(utxo.Amount) + total += int64(a) + addfee = int64(len(outs)) * dlc.DlcTxInSize * efee + if amount+addfee <= total { + if amount+addfee == total { + break + } + addfee += dlc.DlcTxOutSize * efee + if amount+addfee <= total { + break + } + } + } + if amount+addfee > total { + return fmt.Errorf("short of bitcoin") + } + for _, out := range outs { + tx.AddTxIn(wire.NewTxIn(out, nil, nil)) + } + if amount+addfee == total { + return nil + } + change := total - (amount + addfee) + pkScript := w.P2WPKHpkScript(w.GetFakePublicKey()) + tx.AddTxOut(wire.NewTxOut(change, pkScript)) + return nil +} From d5cf21f7202dd1c29f08f2e8914035967043c784 Mon Sep 17 00:00:00 2001 From: akekeke Date: Tue, 30 Oct 2018 19:41:26 +0900 Subject: [PATCH 02/28] WIP ported btcwallet code --- internal/wallet/preparetx.go | 108 +++++++++++++++++++++++------------ 1 file changed, 70 insertions(+), 38 deletions(-) diff --git a/internal/wallet/preparetx.go b/internal/wallet/preparetx.go index 09fd348..bca5d11 100644 --- a/internal/wallet/preparetx.go +++ b/internal/wallet/preparetx.go @@ -1,51 +1,83 @@ package wallet import ( - "fmt" + "sort" - "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcwallet/wallet/txauthor" _ "github.com/btcsuite/btcwallet/walletdb/bdb" // blank import for bolt db driver - "github.com/dgarage/dlc-demo/src/dlc" + "github.com/btcsuite/btcwallet/wtxmgr" ) +// byAmount defines the methods needed to satisify sort.Interface to +// sort credits by their output amount. +type byAmount []wtxmgr.Credit + +func (s byAmount) Len() int { return len(s) } +func (s byAmount) Less(i, j int) bool { return s[i].Amount < s[j].Amount } +func (s byAmount) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + // FundTx adds inputs to a transaction until amount. -func (w *Wallet) FundTx(tx *wire.MsgTx, amount, efee int64) error { - list, err := w.rpc.ListUnspent() - if err != nil { - return err - } - outs := []*wire.OutPoint{} - total := int64(0) - addfee := int64(0) - for _, utxo := range list { - txid, _ := chainhash.NewHashFromStr(utxo.TxID) - outs = append(outs, wire.NewOutPoint(txid, utxo.Vout)) - a, _ := btcutil.NewAmount(utxo.Amount) - total += int64(a) - addfee = int64(len(outs)) * dlc.DlcTxInSize * efee - if amount+addfee <= total { - if amount+addfee == total { - break - } - addfee += dlc.DlcTxOutSize * efee - if amount+addfee <= total { - break - } +func (w *Wallet) FundTx(eligible []wtxmgr.Credit) txauthor.InputSource { + + // outs := []*wire.OutPoint{} + // total := int64(0) + // addfee := int64(0) + // for _, utxo := range list { + // txid, _ := chainhash.NewHashFromStr(utxo.TxID) + // outs = append(outs, wire.NewOutPoint(txid, utxo.Vout)) + // a, _ := btcutil.NewAmount(utxo.Amount) + // total += int64(a) + // addfee = int64(len(outs)) * dlc.DlcTxInSize * efee + // if amount+addfee <= total { + // if amount+addfee == total { + // break + // } + // addfee += dlc.DlcTxOutSize * efee + // if amount+addfee <= total { + // break + // } + // } + // } + // if amount+addfee > total { + // return fmt.Errorf("short of bitcoin") + // } + // for _, out := range outs { + // tx.AddTxIn(wire.NewTxIn(out, nil, nil)) + // } + // if amount+addfee == total { + // return nil + // } + // change := total - (amount + addfee) + // pkScript := w.P2WPKHpkScript(w.GetFakePublicKey()) + // tx.AddTxOut(wire.NewTxOut(change, pkScript)) + // return nil + + // Pick largest outputs first. This is only done for compatibility with + // previous tx creation code, not because it's a good idea. + sort.Sort(sort.Reverse(byAmount(eligible))) + + // Current inputs and their total value. These are closed over by the + // returned input source and reused across multiple calls. + currentTotal := btcutil.Amount(0) + currentInputs := make([]*wire.TxIn, 0, len(eligible)) + currentScripts := make([][]byte, 0, len(eligible)) + currentInputValues := make([]btcutil.Amount, 0, len(eligible)) + + return func(target btcutil.Amount) (btcutil.Amount, []*wire.TxIn, + []btcutil.Amount, [][]byte, error) { + + for currentTotal < target && len(eligible) != 0 { + nextCredit := &eligible[0] + eligible = eligible[1:] + nextInput := wire.NewTxIn(&nextCredit.OutPoint, nil, nil) + currentTotal += nextCredit.Amount + currentInputs = append(currentInputs, nextInput) + currentScripts = append(currentScripts, nextCredit.PkScript) + currentInputValues = append(currentInputValues, nextCredit.Amount) } + return currentTotal, currentInputs, currentInputValues, currentScripts, nil } - if amount+addfee > total { - return fmt.Errorf("short of bitcoin") - } - for _, out := range outs { - tx.AddTxIn(wire.NewTxIn(out, nil, nil)) - } - if amount+addfee == total { - return nil - } - change := total - (amount + addfee) - pkScript := w.P2WPKHpkScript(w.GetFakePublicKey()) - tx.AddTxOut(wire.NewTxOut(change, pkScript)) - return nil + } From dbff7219a30f0322f5dbebe371aaabc3e442f921 Mon Sep 17 00:00:00 2001 From: akekeke Date: Wed, 31 Oct 2018 13:46:32 +0900 Subject: [PATCH 03/28] added Open func, added txstore to wallet --- Gopkg.lock | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Gopkg.lock b/Gopkg.lock index 4a0e796..5149301 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -42,12 +42,20 @@ [[projects]] branch = "master" +<<<<<<< HEAD digest = "1:83aa3de05caeb9faec1b32476a35418897a023543036eadb0bfc48db64664eb0" +======= + digest = "1:546eccde2b4056c2106e2a3dcf27736b2f1f7882db25fda9a3ddf4a33ace90d0" +>>>>>>> added Open func, added txstore to wallet name = "github.com/btcsuite/btcwallet" packages = [ + "internal/helpers", "internal/zero", "snacl", "waddrmgr", + "wallet/internal/txsizes", + "wallet/txauthor", + "wallet/txrules", "walletdb", "walletdb/bdb", "wtxmgr", @@ -143,9 +151,11 @@ "github.com/btcsuite/btcd/chaincfg/chainhash", "github.com/btcsuite/btcd/rpcclient", "github.com/btcsuite/btcd/txscript", + "github.com/btcsuite/btcd/wire", "github.com/btcsuite/btcutil", "github.com/btcsuite/btcutil/hdkeychain", "github.com/btcsuite/btcwallet/waddrmgr", + "github.com/btcsuite/btcwallet/wallet/txauthor", "github.com/btcsuite/btcwallet/walletdb", "github.com/btcsuite/btcwallet/walletdb/bdb", "github.com/btcsuite/btcwallet/wtxmgr", From d45c01a94f7cbb9c083a07a9e870624655b47ca5 Mon Sep 17 00:00:00 2001 From: akekeke Date: Wed, 31 Oct 2018 15:19:52 +0900 Subject: [PATCH 04/28] refactored wallet init, seperated wallet and db creation --- internal/wallet/wallet.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/internal/wallet/wallet.go b/internal/wallet/wallet.go index 9ec8bbe..c58c699 100644 --- a/internal/wallet/wallet.go +++ b/internal/wallet/wallet.go @@ -49,7 +49,11 @@ type wallet struct { // TODO: separate db creation and Manager creation // TODO: create loader script for wallet init func CreateWallet(params *chaincfg.Params, seed, pubPass, privPass []byte, +<<<<<<< HEAD dbFilePath, walletName string) (Wallet, error) { +======= + dbFilePath, walletName string) (*Wallet, error) { +>>>>>>> refactored wallet init, seperated wallet and db creation // TODO: add prompts for dbDirPath, walletDBname // Create a new db at specified path dbDirPath := filepath.Join(dbFilePath, params.Name) From aebabaf712045ecf3a00358803c217addeb95689 Mon Sep 17 00:00:00 2001 From: akekeke Date: Thu, 1 Nov 2018 12:55:20 +0900 Subject: [PATCH 05/28] added listunspent func --- internal/wallet/preparetx.go | 83 ------------------------------------ internal/wallet/utxo_test.go | 31 ++++++++++++++ 2 files changed, 31 insertions(+), 83 deletions(-) delete mode 100644 internal/wallet/preparetx.go create mode 100644 internal/wallet/utxo_test.go diff --git a/internal/wallet/preparetx.go b/internal/wallet/preparetx.go deleted file mode 100644 index bca5d11..0000000 --- a/internal/wallet/preparetx.go +++ /dev/null @@ -1,83 +0,0 @@ -package wallet - -import ( - "sort" - - "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcwallet/wallet/txauthor" - _ "github.com/btcsuite/btcwallet/walletdb/bdb" // blank import for bolt db driver - "github.com/btcsuite/btcwallet/wtxmgr" -) - -// byAmount defines the methods needed to satisify sort.Interface to -// sort credits by their output amount. -type byAmount []wtxmgr.Credit - -func (s byAmount) Len() int { return len(s) } -func (s byAmount) Less(i, j int) bool { return s[i].Amount < s[j].Amount } -func (s byAmount) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -// FundTx adds inputs to a transaction until amount. -func (w *Wallet) FundTx(eligible []wtxmgr.Credit) txauthor.InputSource { - - // outs := []*wire.OutPoint{} - // total := int64(0) - // addfee := int64(0) - // for _, utxo := range list { - // txid, _ := chainhash.NewHashFromStr(utxo.TxID) - // outs = append(outs, wire.NewOutPoint(txid, utxo.Vout)) - // a, _ := btcutil.NewAmount(utxo.Amount) - // total += int64(a) - // addfee = int64(len(outs)) * dlc.DlcTxInSize * efee - // if amount+addfee <= total { - // if amount+addfee == total { - // break - // } - // addfee += dlc.DlcTxOutSize * efee - // if amount+addfee <= total { - // break - // } - // } - // } - // if amount+addfee > total { - // return fmt.Errorf("short of bitcoin") - // } - // for _, out := range outs { - // tx.AddTxIn(wire.NewTxIn(out, nil, nil)) - // } - // if amount+addfee == total { - // return nil - // } - // change := total - (amount + addfee) - // pkScript := w.P2WPKHpkScript(w.GetFakePublicKey()) - // tx.AddTxOut(wire.NewTxOut(change, pkScript)) - // return nil - - // Pick largest outputs first. This is only done for compatibility with - // previous tx creation code, not because it's a good idea. - sort.Sort(sort.Reverse(byAmount(eligible))) - - // Current inputs and their total value. These are closed over by the - // returned input source and reused across multiple calls. - currentTotal := btcutil.Amount(0) - currentInputs := make([]*wire.TxIn, 0, len(eligible)) - currentScripts := make([][]byte, 0, len(eligible)) - currentInputValues := make([]btcutil.Amount, 0, len(eligible)) - - return func(target btcutil.Amount) (btcutil.Amount, []*wire.TxIn, - []btcutil.Amount, [][]byte, error) { - - for currentTotal < target && len(eligible) != 0 { - nextCredit := &eligible[0] - eligible = eligible[1:] - nextInput := wire.NewTxIn(&nextCredit.OutPoint, nil, nil) - currentTotal += nextCredit.Amount - currentInputs = append(currentInputs, nextInput) - currentScripts = append(currentScripts, nextCredit.PkScript) - currentInputValues = append(currentInputValues, nextCredit.Amount) - } - return currentTotal, currentInputs, currentInputValues, currentScripts, nil - } - -} diff --git a/internal/wallet/utxo_test.go b/internal/wallet/utxo_test.go new file mode 100644 index 0000000..5dbce86 --- /dev/null +++ b/internal/wallet/utxo_test.go @@ -0,0 +1,31 @@ +package wallet + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestListUnspent(t *testing.T) { + // dbFilePath := "./testdb" + + // Create a temporary directory for testing. + dirName, err := ioutil.TempDir("", "managertest") + if err != nil { + t.Fatalf("Failed to create db temp dir: %v", err) + } + + wallet, err := CreateWallet(¶ms, seed, pubPassphrase, privPassphrase, + dirName, walletName) + + assert.Nil(t, err) + assert.NotNil(t, wallet.publicPassphrase) + assert.NotNil(t, wallet.params) + assert.NotNil(t, wallet.Manager) + assert.NotNil(t, wallet.TxStore) + + // delete created db + _ = os.RemoveAll(dirName) +} From 9d0f19fed3fa6c234bb292d64ca4ec1dd5041856 Mon Sep 17 00:00:00 2001 From: akekeke Date: Thu, 1 Nov 2018 13:09:43 +0900 Subject: [PATCH 06/28] fixed lint issues --- internal/wallet/utxo_test.go | 25 ++----------------------- 1 file changed, 2 insertions(+), 23 deletions(-) diff --git a/internal/wallet/utxo_test.go b/internal/wallet/utxo_test.go index 5dbce86..90ea980 100644 --- a/internal/wallet/utxo_test.go +++ b/internal/wallet/utxo_test.go @@ -1,31 +1,10 @@ package wallet import ( - "io/ioutil" - "os" "testing" - - "github.com/stretchr/testify/assert" ) func TestListUnspent(t *testing.T) { - // dbFilePath := "./testdb" - - // Create a temporary directory for testing. - dirName, err := ioutil.TempDir("", "managertest") - if err != nil { - t.Fatalf("Failed to create db temp dir: %v", err) - } - - wallet, err := CreateWallet(¶ms, seed, pubPassphrase, privPassphrase, - dirName, walletName) - - assert.Nil(t, err) - assert.NotNil(t, wallet.publicPassphrase) - assert.NotNil(t, wallet.params) - assert.NotNil(t, wallet.Manager) - assert.NotNil(t, wallet.TxStore) - - // delete created db - _ = os.RemoveAll(dirName) + // I'm not sure how to test this yet... + // just pray that it works lol } From c948351bcbe26fd3a2611fdf6d5976577e578a47 Mon Sep 17 00:00:00 2001 From: akekeke Date: Thu, 1 Nov 2018 13:49:19 +0900 Subject: [PATCH 07/28] added comments and fixed lint --- internal/wallet/utxo_test.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/wallet/utxo_test.go b/internal/wallet/utxo_test.go index 90ea980..33cc5c8 100644 --- a/internal/wallet/utxo_test.go +++ b/internal/wallet/utxo_test.go @@ -4,6 +4,12 @@ import ( "testing" ) +// Test setup? +// create wallet +// mine regtest coins? +// ListUnspent() to check if we can see the mined coins? +// TestListUnspent() will also need to check different types of scripts + func TestListUnspent(t *testing.T) { // I'm not sure how to test this yet... // just pray that it works lol From 0e63022904431c47766dc843c7cd6a3f16b2e009 Mon Sep 17 00:00:00 2001 From: akekeke Date: Thu, 1 Nov 2018 13:57:42 +0900 Subject: [PATCH 08/28] fixed one xommment --- internal/wallet/utxo_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/wallet/utxo_test.go b/internal/wallet/utxo_test.go index 33cc5c8..ac135ed 100644 --- a/internal/wallet/utxo_test.go +++ b/internal/wallet/utxo_test.go @@ -8,8 +8,8 @@ import ( // create wallet // mine regtest coins? // ListUnspent() to check if we can see the mined coins? -// TestListUnspent() will also need to check different types of scripts +// TestListUnspent() will also need to check different types of scripts func TestListUnspent(t *testing.T) { // I'm not sure how to test this yet... // just pray that it works lol From 860a88207746b1012344c0f619b4f705fe1bbb11 Mon Sep 17 00:00:00 2001 From: akekeke Date: Tue, 30 Oct 2018 19:21:23 +0900 Subject: [PATCH 09/28] init port of code from dlcdemo --- internal/wallet/preparetx.go | 51 ++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 internal/wallet/preparetx.go diff --git a/internal/wallet/preparetx.go b/internal/wallet/preparetx.go new file mode 100644 index 0000000..09fd348 --- /dev/null +++ b/internal/wallet/preparetx.go @@ -0,0 +1,51 @@ +package wallet + +import ( + "fmt" + + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" + _ "github.com/btcsuite/btcwallet/walletdb/bdb" // blank import for bolt db driver + "github.com/dgarage/dlc-demo/src/dlc" +) + +// FundTx adds inputs to a transaction until amount. +func (w *Wallet) FundTx(tx *wire.MsgTx, amount, efee int64) error { + list, err := w.rpc.ListUnspent() + if err != nil { + return err + } + outs := []*wire.OutPoint{} + total := int64(0) + addfee := int64(0) + for _, utxo := range list { + txid, _ := chainhash.NewHashFromStr(utxo.TxID) + outs = append(outs, wire.NewOutPoint(txid, utxo.Vout)) + a, _ := btcutil.NewAmount(utxo.Amount) + total += int64(a) + addfee = int64(len(outs)) * dlc.DlcTxInSize * efee + if amount+addfee <= total { + if amount+addfee == total { + break + } + addfee += dlc.DlcTxOutSize * efee + if amount+addfee <= total { + break + } + } + } + if amount+addfee > total { + return fmt.Errorf("short of bitcoin") + } + for _, out := range outs { + tx.AddTxIn(wire.NewTxIn(out, nil, nil)) + } + if amount+addfee == total { + return nil + } + change := total - (amount + addfee) + pkScript := w.P2WPKHpkScript(w.GetFakePublicKey()) + tx.AddTxOut(wire.NewTxOut(change, pkScript)) + return nil +} From 4eb72f8f9f944360629b8a3d59a08106cd3acae2 Mon Sep 17 00:00:00 2001 From: akekeke Date: Tue, 30 Oct 2018 19:41:26 +0900 Subject: [PATCH 10/28] WIP ported btcwallet code --- internal/wallet/preparetx.go | 108 +++++++++++++++++++++++------------ 1 file changed, 70 insertions(+), 38 deletions(-) diff --git a/internal/wallet/preparetx.go b/internal/wallet/preparetx.go index 09fd348..bca5d11 100644 --- a/internal/wallet/preparetx.go +++ b/internal/wallet/preparetx.go @@ -1,51 +1,83 @@ package wallet import ( - "fmt" + "sort" - "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcwallet/wallet/txauthor" _ "github.com/btcsuite/btcwallet/walletdb/bdb" // blank import for bolt db driver - "github.com/dgarage/dlc-demo/src/dlc" + "github.com/btcsuite/btcwallet/wtxmgr" ) +// byAmount defines the methods needed to satisify sort.Interface to +// sort credits by their output amount. +type byAmount []wtxmgr.Credit + +func (s byAmount) Len() int { return len(s) } +func (s byAmount) Less(i, j int) bool { return s[i].Amount < s[j].Amount } +func (s byAmount) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + // FundTx adds inputs to a transaction until amount. -func (w *Wallet) FundTx(tx *wire.MsgTx, amount, efee int64) error { - list, err := w.rpc.ListUnspent() - if err != nil { - return err - } - outs := []*wire.OutPoint{} - total := int64(0) - addfee := int64(0) - for _, utxo := range list { - txid, _ := chainhash.NewHashFromStr(utxo.TxID) - outs = append(outs, wire.NewOutPoint(txid, utxo.Vout)) - a, _ := btcutil.NewAmount(utxo.Amount) - total += int64(a) - addfee = int64(len(outs)) * dlc.DlcTxInSize * efee - if amount+addfee <= total { - if amount+addfee == total { - break - } - addfee += dlc.DlcTxOutSize * efee - if amount+addfee <= total { - break - } +func (w *Wallet) FundTx(eligible []wtxmgr.Credit) txauthor.InputSource { + + // outs := []*wire.OutPoint{} + // total := int64(0) + // addfee := int64(0) + // for _, utxo := range list { + // txid, _ := chainhash.NewHashFromStr(utxo.TxID) + // outs = append(outs, wire.NewOutPoint(txid, utxo.Vout)) + // a, _ := btcutil.NewAmount(utxo.Amount) + // total += int64(a) + // addfee = int64(len(outs)) * dlc.DlcTxInSize * efee + // if amount+addfee <= total { + // if amount+addfee == total { + // break + // } + // addfee += dlc.DlcTxOutSize * efee + // if amount+addfee <= total { + // break + // } + // } + // } + // if amount+addfee > total { + // return fmt.Errorf("short of bitcoin") + // } + // for _, out := range outs { + // tx.AddTxIn(wire.NewTxIn(out, nil, nil)) + // } + // if amount+addfee == total { + // return nil + // } + // change := total - (amount + addfee) + // pkScript := w.P2WPKHpkScript(w.GetFakePublicKey()) + // tx.AddTxOut(wire.NewTxOut(change, pkScript)) + // return nil + + // Pick largest outputs first. This is only done for compatibility with + // previous tx creation code, not because it's a good idea. + sort.Sort(sort.Reverse(byAmount(eligible))) + + // Current inputs and their total value. These are closed over by the + // returned input source and reused across multiple calls. + currentTotal := btcutil.Amount(0) + currentInputs := make([]*wire.TxIn, 0, len(eligible)) + currentScripts := make([][]byte, 0, len(eligible)) + currentInputValues := make([]btcutil.Amount, 0, len(eligible)) + + return func(target btcutil.Amount) (btcutil.Amount, []*wire.TxIn, + []btcutil.Amount, [][]byte, error) { + + for currentTotal < target && len(eligible) != 0 { + nextCredit := &eligible[0] + eligible = eligible[1:] + nextInput := wire.NewTxIn(&nextCredit.OutPoint, nil, nil) + currentTotal += nextCredit.Amount + currentInputs = append(currentInputs, nextInput) + currentScripts = append(currentScripts, nextCredit.PkScript) + currentInputValues = append(currentInputValues, nextCredit.Amount) } + return currentTotal, currentInputs, currentInputValues, currentScripts, nil } - if amount+addfee > total { - return fmt.Errorf("short of bitcoin") - } - for _, out := range outs { - tx.AddTxIn(wire.NewTxIn(out, nil, nil)) - } - if amount+addfee == total { - return nil - } - change := total - (amount + addfee) - pkScript := w.P2WPKHpkScript(w.GetFakePublicKey()) - tx.AddTxOut(wire.NewTxOut(change, pkScript)) - return nil + } From efcceb87210f2beca5625bdcef4f293f8ae7adec Mon Sep 17 00:00:00 2001 From: akekeke Date: Thu, 1 Nov 2018 12:55:20 +0900 Subject: [PATCH 11/28] added listunspent func --- internal/wallet/preparetx.go | 83 ------------------------------------ 1 file changed, 83 deletions(-) delete mode 100644 internal/wallet/preparetx.go diff --git a/internal/wallet/preparetx.go b/internal/wallet/preparetx.go deleted file mode 100644 index bca5d11..0000000 --- a/internal/wallet/preparetx.go +++ /dev/null @@ -1,83 +0,0 @@ -package wallet - -import ( - "sort" - - "github.com/btcsuite/btcd/wire" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcwallet/wallet/txauthor" - _ "github.com/btcsuite/btcwallet/walletdb/bdb" // blank import for bolt db driver - "github.com/btcsuite/btcwallet/wtxmgr" -) - -// byAmount defines the methods needed to satisify sort.Interface to -// sort credits by their output amount. -type byAmount []wtxmgr.Credit - -func (s byAmount) Len() int { return len(s) } -func (s byAmount) Less(i, j int) bool { return s[i].Amount < s[j].Amount } -func (s byAmount) Swap(i, j int) { s[i], s[j] = s[j], s[i] } - -// FundTx adds inputs to a transaction until amount. -func (w *Wallet) FundTx(eligible []wtxmgr.Credit) txauthor.InputSource { - - // outs := []*wire.OutPoint{} - // total := int64(0) - // addfee := int64(0) - // for _, utxo := range list { - // txid, _ := chainhash.NewHashFromStr(utxo.TxID) - // outs = append(outs, wire.NewOutPoint(txid, utxo.Vout)) - // a, _ := btcutil.NewAmount(utxo.Amount) - // total += int64(a) - // addfee = int64(len(outs)) * dlc.DlcTxInSize * efee - // if amount+addfee <= total { - // if amount+addfee == total { - // break - // } - // addfee += dlc.DlcTxOutSize * efee - // if amount+addfee <= total { - // break - // } - // } - // } - // if amount+addfee > total { - // return fmt.Errorf("short of bitcoin") - // } - // for _, out := range outs { - // tx.AddTxIn(wire.NewTxIn(out, nil, nil)) - // } - // if amount+addfee == total { - // return nil - // } - // change := total - (amount + addfee) - // pkScript := w.P2WPKHpkScript(w.GetFakePublicKey()) - // tx.AddTxOut(wire.NewTxOut(change, pkScript)) - // return nil - - // Pick largest outputs first. This is only done for compatibility with - // previous tx creation code, not because it's a good idea. - sort.Sort(sort.Reverse(byAmount(eligible))) - - // Current inputs and their total value. These are closed over by the - // returned input source and reused across multiple calls. - currentTotal := btcutil.Amount(0) - currentInputs := make([]*wire.TxIn, 0, len(eligible)) - currentScripts := make([][]byte, 0, len(eligible)) - currentInputValues := make([]btcutil.Amount, 0, len(eligible)) - - return func(target btcutil.Amount) (btcutil.Amount, []*wire.TxIn, - []btcutil.Amount, [][]byte, error) { - - for currentTotal < target && len(eligible) != 0 { - nextCredit := &eligible[0] - eligible = eligible[1:] - nextInput := wire.NewTxIn(&nextCredit.OutPoint, nil, nil) - currentTotal += nextCredit.Amount - currentInputs = append(currentInputs, nextInput) - currentScripts = append(currentScripts, nextCredit.PkScript) - currentInputValues = append(currentInputValues, nextCredit.Amount) - } - return currentTotal, currentInputs, currentInputValues, currentScripts, nil - } - -} From 8cc819d44fc74bdd37788050d0bfbc3fe813027f Mon Sep 17 00:00:00 2001 From: akekeke Date: Thu, 1 Nov 2018 13:09:43 +0900 Subject: [PATCH 12/28] fixed lint issues --- internal/wallet/utxo.go | 175 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 175 insertions(+) diff --git a/internal/wallet/utxo.go b/internal/wallet/utxo.go index 8513a56..d0adefc 100644 --- a/internal/wallet/utxo.go +++ b/internal/wallet/utxo.go @@ -7,7 +7,182 @@ import ( // Utxo is a unspend transaction output type Utxo btcjson.ListUnspentResult +<<<<<<< HEAD // ListUnspent returns unspent transactions func (w *wallet) ListUnspent() (utxos []Utxo, err error) { return +======= +// ListUnspent returns unspent transactions. +// TODO: add filter +// Only utxos with address contained the param addresses will be considered. +// If param addresses is empty, all addresses are considered and there is no +// filter +func (w *Wallet) ListUnspent() ([]*btcjson.ListUnspentResult, error) { + var results []*btcjson.ListUnspentResult + err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { + addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) + txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) + + syncBlock := w.Manager.SyncedTo() + // filter := len(addresses) != 0 + + unspent, e := w.TxStore.UnspentOutputs(txmgrNs) + if e != nil { + return e + } + + // make btcjson.ListUnspentResult from Credit types + results = make([]*btcjson.ListUnspentResult, 0, len(unspent)) + for i := range unspent { + output := unspent[i] + result := w.credit2ListUnspentResult(output, syncBlock, addrmgrNs) + // TODO: result might return nil... catch that nil? + results = append(results, result) + } + return nil + }) + return results, err +} + +func (w *Wallet) credit2ListUnspentResult(c wtxmgr.Credit, syncBlock waddrmgr.BlockStamp, + addrmgrNs walletdb.ReadBucket) *btcjson.ListUnspentResult { + + // TODO: add minconf, maxconf params + confs := confirms(c.Height, syncBlock.Height) + + // // Outputs with fewer confirmations than the minimum or more + // // confs than the maximum are excluded. + // confs := confirms(output.Height, syncBlock.Height) + // if confs < minconf || confs > maxconf { + // continue + // } + + // Only mature coinbase outputs are included. + if c.FromCoinBase { + target := int32(w.params.CoinbaseMaturity) // make param + if !confirmed(target, c.Height, syncBlock.Height) { + // continue + return nil // maybe? + + } + } + + // TODO: exclude locked outputs from result set. + // Exclude locked outputs from the result set. + + // Lookup the associated account for the output. Use the + // default account name in case there is no associated account + // for some reason, although this should never happen. + // + // This will be unnecessary once transactions and outputs are + // grouped under the associated account in the db. + defaultAccountName := "default" + acctName := defaultAccountName + sc, addrs, _, err := txscript.ExtractPkScriptAddrs( + c.PkScript, w.params) + if err != nil { + // continue + return nil // maybe? + } + if len(addrs) > 0 { + smgr, acct, err := w.Manager.AddrAccount(addrmgrNs, addrs[0]) + if err == nil { + s, err := smgr.AccountName(addrmgrNs, acct) + if err == nil { + acctName = s + } + } + } + + // not including this part... this func will assume there is no filter + // if filter { + // for _, addr := range addrs { + // _, ok := addresses[addr.EncodeAddress()] + // if ok { + // goto include + // } + // } + // // continue + // return nil // maybe? + // } + // include: + + result := &btcjson.ListUnspentResult{ + TxID: c.OutPoint.Hash.String(), + Vout: c.OutPoint.Index, + Account: acctName, + ScriptPubKey: hex.EncodeToString(c.PkScript), + Amount: c.Amount.ToBTC(), + Confirmations: int64(confs), + Spendable: w.isSpendable(sc, addrs, addrmgrNs), + } + + // BUG: this should be a JSON array so that all + // addresses can be included, or removed (and the + // caller extracts addresses from the pkScript). + if len(addrs) > 0 { + result.Address = addrs[0].EncodeAddress() + } + + return result +} + +// is spendable? +func (w *Wallet) isSpendable(sc txscript.ScriptClass, addrs []btcutil.Address, + addrmgrNs walletdb.ReadBucket) (spendable bool) { + // At the moment watch-only addresses are not supported, so all + // recorded outputs that are not multisig are "spendable". + // Multisig outputs are only "spendable" if all keys are + // controlled by this wallet. + // + // TODO: Each case will need updates when watch-only addrs + // is added. For P2PK, P2PKH, and P2SH, the address must be + // looked up and not be watching-only. For multisig, all + // pubkeys must belong to the manager with the associated + // private key (currently it only checks whether the pubkey + // exists, since the private key is required at the moment). +scSwitch: + switch sc { + case txscript.PubKeyHashTy: + spendable = true + case txscript.PubKeyTy: + spendable = true + case txscript.WitnessV0ScriptHashTy: + spendable = true + case txscript.WitnessV0PubKeyHashTy: + spendable = true + case txscript.MultiSigTy: + for _, a := range addrs { + _, err := w.Manager.Address(addrmgrNs, a) + if err == nil { + continue + } + if waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { + break scSwitch + } + // return err TODO: figure out what to replace the return error + } + spendable = true + } + + return spendable +} + +// confirms returns the number of confirmations for a transaction in a block at +// height txHeight (or -1 for an unconfirmed tx) given the chain height +// curHeight. +func confirms(txHeight, curHeight int32) int32 { + switch { + case txHeight == -1, txHeight > curHeight: + return 0 + default: + return curHeight - txHeight + 1 + } +} + +// confirmed checks whether a transaction at height txHeight has met minconf +// confirmations for a blockchain at height curHeight. +func confirmed(minconf, txHeight, curHeight int32) bool { + return confirms(txHeight, curHeight) >= minconf +>>>>>>> fixed lint issues } From aba2632c6da03424f076ee2d8e514be3393a4c5d Mon Sep 17 00:00:00 2001 From: akekeke Date: Thu, 1 Nov 2018 13:49:19 +0900 Subject: [PATCH 13/28] added comments and fixed lint --- internal/wallet/utxo.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/wallet/utxo.go b/internal/wallet/utxo.go index d0adefc..16f2e4b 100644 --- a/internal/wallet/utxo.go +++ b/internal/wallet/utxo.go @@ -94,7 +94,7 @@ func (w *Wallet) credit2ListUnspentResult(c wtxmgr.Credit, syncBlock waddrmgr.Bl } } - // not including this part... this func will assume there is no filter + // not including this part bc this func will assume there is no filter // if filter { // for _, addr := range addrs { // _, ok := addresses[addr.EncodeAddress()] From 9fc81ce98576e695d964932ecd79436e540c0c1c Mon Sep 17 00:00:00 2001 From: akekeke Date: Thu, 1 Nov 2018 13:54:02 +0900 Subject: [PATCH 14/28] added one more commmetn --- internal/wallet/utxo.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/internal/wallet/utxo.go b/internal/wallet/utxo.go index 16f2e4b..54d9f90 100644 --- a/internal/wallet/utxo.go +++ b/internal/wallet/utxo.go @@ -127,7 +127,9 @@ func (w *Wallet) credit2ListUnspentResult(c wtxmgr.Credit, syncBlock waddrmgr.Bl return result } -// is spendable? +// isSpendable determines if given ScriptClass is spendable or not. +// Does NOT support watch-only addresses. This func will need to be rewritten +// to support watch-only addresses func (w *Wallet) isSpendable(sc txscript.ScriptClass, addrs []btcutil.Address, addrmgrNs walletdb.ReadBucket) (spendable bool) { // At the moment watch-only addresses are not supported, so all From cbfea64ce531057779adc47f89e98efc2de007a9 Mon Sep 17 00:00:00 2001 From: akekeke Date: Fri, 2 Nov 2018 16:01:16 +0900 Subject: [PATCH 15/28] merge WIP --- internal/wallet/utxo.go | 32 +++---- internal/wallet/utxo_test.go | 156 ++++++++++++++++++++++++++++++++++- internal/wallet/wallet.go | 4 - 3 files changed, 171 insertions(+), 21 deletions(-) diff --git a/internal/wallet/utxo.go b/internal/wallet/utxo.go index 54d9f90..3c47b12 100644 --- a/internal/wallet/utxo.go +++ b/internal/wallet/utxo.go @@ -1,37 +1,38 @@ package wallet import ( + "encoding/hex" + "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcwallet/waddrmgr" + "github.com/btcsuite/btcwallet/walletdb" + "github.com/btcsuite/btcwallet/wtxmgr" ) // Utxo is a unspend transaction output -type Utxo btcjson.ListUnspentResult +type Utxo = *btcjson.ListUnspentResult -<<<<<<< HEAD -// ListUnspent returns unspent transactions -func (w *wallet) ListUnspent() (utxos []Utxo, err error) { - return -======= // ListUnspent returns unspent transactions. // TODO: add filter // Only utxos with address contained the param addresses will be considered. // If param addresses is empty, all addresses are considered and there is no // filter -func (w *Wallet) ListUnspent() ([]*btcjson.ListUnspentResult, error) { +func (w *wallet) ListUnspent() ([]Utxo, error) { var results []*btcjson.ListUnspentResult err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) - syncBlock := w.Manager.SyncedTo() + syncBlock := w.manager.SyncedTo() // filter := len(addresses) != 0 - unspent, e := w.TxStore.UnspentOutputs(txmgrNs) + unspent, e := w.txStore.UnspentOutputs(txmgrNs) if e != nil { return e } - // make btcjson.ListUnspentResult from Credit types results = make([]*btcjson.ListUnspentResult, 0, len(unspent)) for i := range unspent { output := unspent[i] @@ -44,7 +45,9 @@ func (w *Wallet) ListUnspent() ([]*btcjson.ListUnspentResult, error) { return results, err } -func (w *Wallet) credit2ListUnspentResult(c wtxmgr.Credit, syncBlock waddrmgr.BlockStamp, +func (w *wallet) credit2ListUnspentResult( + c wtxmgr.Credit, + syncBlock waddrmgr.BlockStamp, addrmgrNs walletdb.ReadBucket) *btcjson.ListUnspentResult { // TODO: add minconf, maxconf params @@ -85,7 +88,7 @@ func (w *Wallet) credit2ListUnspentResult(c wtxmgr.Credit, syncBlock waddrmgr.Bl return nil // maybe? } if len(addrs) > 0 { - smgr, acct, err := w.Manager.AddrAccount(addrmgrNs, addrs[0]) + smgr, acct, err := w.manager.AddrAccount(addrmgrNs, addrs[0]) if err == nil { s, err := smgr.AccountName(addrmgrNs, acct) if err == nil { @@ -130,7 +133,7 @@ func (w *Wallet) credit2ListUnspentResult(c wtxmgr.Credit, syncBlock waddrmgr.Bl // isSpendable determines if given ScriptClass is spendable or not. // Does NOT support watch-only addresses. This func will need to be rewritten // to support watch-only addresses -func (w *Wallet) isSpendable(sc txscript.ScriptClass, addrs []btcutil.Address, +func (w *wallet) isSpendable(sc txscript.ScriptClass, addrs []btcutil.Address, addrmgrNs walletdb.ReadBucket) (spendable bool) { // At the moment watch-only addresses are not supported, so all // recorded outputs that are not multisig are "spendable". @@ -155,7 +158,7 @@ scSwitch: spendable = true case txscript.MultiSigTy: for _, a := range addrs { - _, err := w.Manager.Address(addrmgrNs, a) + _, err := w.manager.Address(addrmgrNs, a) if err == nil { continue } @@ -186,5 +189,4 @@ func confirms(txHeight, curHeight int32) int32 { // confirmations for a blockchain at height curHeight. func confirmed(minconf, txHeight, curHeight int32) bool { return confirms(txHeight, curHeight) >= minconf ->>>>>>> fixed lint issues } diff --git a/internal/wallet/utxo_test.go b/internal/wallet/utxo_test.go index ac135ed..a049288 100644 --- a/internal/wallet/utxo_test.go +++ b/internal/wallet/utxo_test.go @@ -1,7 +1,17 @@ package wallet import ( + "encoding/binary" + "errors" + "fmt" "testing" + "time" + + "github.com/adiabat/btcd/chaincfg/chainhash" + "github.com/adiabat/btcd/wire" + "github.com/btcsuite/btcwallet/walletdb" + "github.com/btcsuite/btcwallet/wtxmgr" + "github.com/stretchr/testify/assert" ) // Test setup? @@ -11,6 +21,148 @@ import ( // TestListUnspent() will also need to check different types of scripts func TestListUnspent(t *testing.T) { - // I'm not sure how to test this yet... - // just pray that it works lol + tearDownFunc, wallet := setupWallet(t) + defer tearDownFunc() + + utxos := fakeUtxos(wallet) + fmt.Printf("%+v\n", utxos) + + syncBlock := wallet.manager.SyncedTo() + + err := walletdb.View(wallet.db, func(tx walletdb.ReadTx) error { + wtxmgrBucket := tx.ReadBucket(wtxmgrNamespaceKey) + if wtxmgrBucket == nil { + return errors.New("missing transaction manager namespace") + } + fmt.Printf("successfully got wtxmgr?\n") + asdf := w.credit2ListUnspentResult(utxos[0], syncBlock, wtxmgrBucket) + fmt.Printf("%+v\n", asdf) + + assert.NotNil(t, asdf) + + return nil + }) + if err != nil { + fmt.Println(err) + } +} + +// fakeUtxos creates fake transactions, and inserts them into the provided wallet's db +func fakeUtxos(w Wallet) []wtxmgr.Credit { + tx := spendOutput(&chainhash.Hash{}, 0, 10e8) + rec, err := wtxmgr.NewTxRecordFromMsgTx(tx, timeNow()) + if err != nil { + panic(err) + } + fakeTxRecordA = rec + + tx = spendOutput(&fakeTxRecordA.Hash, 0, 5e8, 5e8) + rec, err = wtxmgr.NewTxRecordFromMsgTx(tx, timeNow()) + if err != nil { + panic(err) + } + fakeTxRecordB = rec + + var utxos []wtxmgr.Credit + err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { + wtxmgrBucket := tx.ReadWriteBucket(wtxmgrNamespaceKey) + if wtxmgrBucket == nil { + return errors.New("missing transaction manager namespace") + } + fmt.Printf("successfully got wtxmgr?\n") + + e := w.TxStore.InsertTx(wtxmgrBucket, fakeTxRecordA, nil) + if e != nil { + fmt.Println(e) + return e + } + e = w.TxStore.AddCredit(wtxmgrBucket, fakeTxRecordA, nil, 0, false) + if e != nil { + fmt.Println(e) + return e + } + fmt.Printf("created fake credit A\n") + + // Insert a second transaction which spends the output, and creates two + // outputs. Mark the second one (5 BTC) as wallet change. + e = w.TxStore.InsertTx(wtxmgrBucket, fakeTxRecordB, nil) + if e != nil { + fmt.Println(e) + return e + } + e = w.TxStore.AddCredit(wtxmgrBucket, fakeTxRecordB, nil, 1, true) + if e != nil { + fmt.Println(e) + return e + } + fmt.Printf("created fake credit B\n") + + // // Mine each transaction in a block at height 100. + e = w.TxStore.InsertTx(wtxmgrBucket, fakeTxRecordA, &exampleBlock100) + if e != nil { + fmt.Println(e) + return e + } + e = w.TxStore.InsertTx(wtxmgrBucket, fakeTxRecordB, &exampleBlock100) + if e != nil { + fmt.Println(e) + return e + } + fmt.Printf("mined each transaction\n") + + // Print the one confirmation balance. + bal, e := w.TxStore.Balance(wtxmgrBucket, 1, 100) + if e != nil { + fmt.Println(e) + return nil + } + fmt.Println(bal) + + // Fetch unspent outputs. + utxos, e = w.TxStore.UnspentOutputs(wtxmgrBucket) + if e != nil { + fmt.Println(e) + } + return e + }) + if err != nil { + fmt.Println(err) + return nil + } + + return utxos +} + +func spendOutput(txHash *chainhash.Hash, index uint32, outputValues ...int64) *wire.MsgTx { + tx := wire.MsgTx{ + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: wire.OutPoint{Hash: *txHash, Index: index}, + }, + }, + } + for _, val := range outputValues { + tx.TxOut = append(tx.TxOut, &wire.TxOut{Value: val}) + } + return &tx +} + +func makeBlockMeta(height int32) wtxmgr.BlockMeta { + if height == -1 { + return wtxmgr.BlockMeta{Block: wtxmgr.Block{Height: -1}} + } + + b := wtxmgr.BlockMeta{ + Block: wtxmgr.Block{Height: height}, + Time: timeNow(), + } + // Give it a fake block hash created from the height and time. + binary.LittleEndian.PutUint32(b.Hash[0:4], uint32(height)) + binary.LittleEndian.PutUint64(b.Hash[4:12], uint64(b.Time.Unix())) + return b +} + +// Returns time.Now() with seconds resolution, this is what Store saves. +func timeNow() time.Time { + return time.Unix(time.Now().Unix(), 0) } diff --git a/internal/wallet/wallet.go b/internal/wallet/wallet.go index c58c699..9ec8bbe 100644 --- a/internal/wallet/wallet.go +++ b/internal/wallet/wallet.go @@ -49,11 +49,7 @@ type wallet struct { // TODO: separate db creation and Manager creation // TODO: create loader script for wallet init func CreateWallet(params *chaincfg.Params, seed, pubPass, privPass []byte, -<<<<<<< HEAD dbFilePath, walletName string) (Wallet, error) { -======= - dbFilePath, walletName string) (*Wallet, error) { ->>>>>>> refactored wallet init, seperated wallet and db creation // TODO: add prompts for dbDirPath, walletDBname // Create a new db at specified path dbDirPath := filepath.Join(dbFilePath, params.Name) From 25513330e88c15dfa6cc89777cc3ba18497c302e Mon Sep 17 00:00:00 2001 From: akekeke Date: Fri, 2 Nov 2018 16:28:36 +0900 Subject: [PATCH 16/28] merge WIP part 2 --- Gopkg.lock | 9 ---- internal/wallet/utxo_test.go | 83 ++++++++++++++++++++---------------- 2 files changed, 47 insertions(+), 45 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index 5149301..b38339c 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -42,20 +42,12 @@ [[projects]] branch = "master" -<<<<<<< HEAD digest = "1:83aa3de05caeb9faec1b32476a35418897a023543036eadb0bfc48db64664eb0" -======= - digest = "1:546eccde2b4056c2106e2a3dcf27736b2f1f7882db25fda9a3ddf4a33ace90d0" ->>>>>>> added Open func, added txstore to wallet name = "github.com/btcsuite/btcwallet" packages = [ - "internal/helpers", "internal/zero", "snacl", "waddrmgr", - "wallet/internal/txsizes", - "wallet/txauthor", - "wallet/txrules", "walletdb", "walletdb/bdb", "wtxmgr", @@ -155,7 +147,6 @@ "github.com/btcsuite/btcutil", "github.com/btcsuite/btcutil/hdkeychain", "github.com/btcsuite/btcwallet/waddrmgr", - "github.com/btcsuite/btcwallet/wallet/txauthor", "github.com/btcsuite/btcwallet/walletdb", "github.com/btcsuite/btcwallet/walletdb/bdb", "github.com/btcsuite/btcwallet/wtxmgr", diff --git a/internal/wallet/utxo_test.go b/internal/wallet/utxo_test.go index a049288..2eed68f 100644 --- a/internal/wallet/utxo_test.go +++ b/internal/wallet/utxo_test.go @@ -7,11 +7,22 @@ import ( "testing" "time" - "github.com/adiabat/btcd/chaincfg/chainhash" - "github.com/adiabat/btcd/wire" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/btcwallet/wtxmgr" - "github.com/stretchr/testify/assert" +) + +var ( + // Spends: bogus + // Outputs: 10 BTC + fakeTxRecordA *wtxmgr.TxRecord + + // Spends: A:0 + // Outputs: 5 BTC, 5 BTC + fakeTxRecordB *wtxmgr.TxRecord + + exampleBlock100 = makeBlockMeta(100) ) // Test setup? @@ -21,34 +32,34 @@ import ( // TestListUnspent() will also need to check different types of scripts func TestListUnspent(t *testing.T) { - tearDownFunc, wallet := setupWallet(t) - defer tearDownFunc() - - utxos := fakeUtxos(wallet) - fmt.Printf("%+v\n", utxos) - - syncBlock := wallet.manager.SyncedTo() - - err := walletdb.View(wallet.db, func(tx walletdb.ReadTx) error { - wtxmgrBucket := tx.ReadBucket(wtxmgrNamespaceKey) - if wtxmgrBucket == nil { - return errors.New("missing transaction manager namespace") - } - fmt.Printf("successfully got wtxmgr?\n") - asdf := w.credit2ListUnspentResult(utxos[0], syncBlock, wtxmgrBucket) - fmt.Printf("%+v\n", asdf) - - assert.NotNil(t, asdf) - - return nil - }) - if err != nil { - fmt.Println(err) - } + // tearDownFunc, wallet := setupWallet(t) + // defer tearDownFunc() + + // utxos := fakeUtxos(wallet) + // fmt.Printf("%+v\n", utxos) + + // syncBlock := wallet.manager.SyncedTo() + + // err := walletdb.View(wallet.db, func(tx walletdb.ReadTx) error { + // wtxmgrBucket := tx.ReadBucket(wtxmgrNamespaceKey) + // if wtxmgrBucket == nil { + // return errors.New("missing transaction manager namespace") + // } + // fmt.Printf("successfully got wtxmgr?\n") + // asdf := w.credit2ListUnspentResult(utxos[0], syncBlock, wtxmgrBucket) + // fmt.Printf("%+v\n", asdf) + + // assert.NotNil(t, asdf) + + // return nil + // }) + // if err != nil { + // fmt.Println(err) + // } } // fakeUtxos creates fake transactions, and inserts them into the provided wallet's db -func fakeUtxos(w Wallet) []wtxmgr.Credit { +func fakeUtxos(w wallet) []wtxmgr.Credit { tx := spendOutput(&chainhash.Hash{}, 0, 10e8) rec, err := wtxmgr.NewTxRecordFromMsgTx(tx, timeNow()) if err != nil { @@ -71,12 +82,12 @@ func fakeUtxos(w Wallet) []wtxmgr.Credit { } fmt.Printf("successfully got wtxmgr?\n") - e := w.TxStore.InsertTx(wtxmgrBucket, fakeTxRecordA, nil) + e := w.txStore.InsertTx(wtxmgrBucket, fakeTxRecordA, nil) if e != nil { fmt.Println(e) return e } - e = w.TxStore.AddCredit(wtxmgrBucket, fakeTxRecordA, nil, 0, false) + e = w.txStore.AddCredit(wtxmgrBucket, fakeTxRecordA, nil, 0, false) if e != nil { fmt.Println(e) return e @@ -85,12 +96,12 @@ func fakeUtxos(w Wallet) []wtxmgr.Credit { // Insert a second transaction which spends the output, and creates two // outputs. Mark the second one (5 BTC) as wallet change. - e = w.TxStore.InsertTx(wtxmgrBucket, fakeTxRecordB, nil) + e = w.txStore.InsertTx(wtxmgrBucket, fakeTxRecordB, nil) if e != nil { fmt.Println(e) return e } - e = w.TxStore.AddCredit(wtxmgrBucket, fakeTxRecordB, nil, 1, true) + e = w.txStore.AddCredit(wtxmgrBucket, fakeTxRecordB, nil, 1, true) if e != nil { fmt.Println(e) return e @@ -98,12 +109,12 @@ func fakeUtxos(w Wallet) []wtxmgr.Credit { fmt.Printf("created fake credit B\n") // // Mine each transaction in a block at height 100. - e = w.TxStore.InsertTx(wtxmgrBucket, fakeTxRecordA, &exampleBlock100) + e = w.txStore.InsertTx(wtxmgrBucket, fakeTxRecordA, &exampleBlock100) if e != nil { fmt.Println(e) return e } - e = w.TxStore.InsertTx(wtxmgrBucket, fakeTxRecordB, &exampleBlock100) + e = w.txStore.InsertTx(wtxmgrBucket, fakeTxRecordB, &exampleBlock100) if e != nil { fmt.Println(e) return e @@ -111,7 +122,7 @@ func fakeUtxos(w Wallet) []wtxmgr.Credit { fmt.Printf("mined each transaction\n") // Print the one confirmation balance. - bal, e := w.TxStore.Balance(wtxmgrBucket, 1, 100) + bal, e := w.txStore.Balance(wtxmgrBucket, 1, 100) if e != nil { fmt.Println(e) return nil @@ -119,7 +130,7 @@ func fakeUtxos(w Wallet) []wtxmgr.Credit { fmt.Println(bal) // Fetch unspent outputs. - utxos, e = w.TxStore.UnspentOutputs(wtxmgrBucket) + utxos, e = w.txStore.UnspentOutputs(wtxmgrBucket) if e != nil { fmt.Println(e) } From 88dcdc2b1002020a4f9ebdbe8bb5ed5e32e3d7bf Mon Sep 17 00:00:00 2001 From: akekeke Date: Fri, 2 Nov 2018 18:12:56 +0900 Subject: [PATCH 17/28] refactored methods to return structs instead of interfaces --- Gopkg.lock | 22 ++++++++++---- internal/wallet/address.go | 18 +++++++++--- internal/wallet/address_test.go | 15 ++++++++++ internal/wallet/test_util.go | 2 +- internal/wallet/utxo.go | 25 ---------------- internal/wallet/utxo_test.go | 51 +++++++++++++++++---------------- internal/wallet/wallet.go | 32 +++++++++++++++++++-- 7 files changed, 103 insertions(+), 62 deletions(-) diff --git a/Gopkg.lock b/Gopkg.lock index b38339c..76e466e 100644 --- a/Gopkg.lock +++ b/Gopkg.lock @@ -110,9 +110,20 @@ version = "v1.0.0" [[projects]] - digest = "1:18752d0b95816a1b777505a97f71c7467a8445b8ffb55631a7bf779f6ba4fa83" + digest = "1:ac83cf90d08b63ad5f7e020ef480d319ae890c208f8524622a2f3136e2686b02" + name = "github.com/stretchr/objx" + packages = ["."] + pruneopts = "UT" + revision = "477a77ecc69700c7cdeb1fa9e129548e1c1c393c" + version = "v0.1.1" + +[[projects]] + digest = "1:15a4a7e5afac3cea801fa24831fce3bf3b5bd3620cbf8355a07b7dbf06877883" name = "github.com/stretchr/testify" - packages = ["assert"] + packages = [ + "assert", + "mock", + ] pruneopts = "UT" revision = "f35b8ab0b5a2cef36673838d662e249dd9c94686" version = "v1.2.2" @@ -123,15 +134,15 @@ name = "golang.org/x/crypto" packages = ["ripemd160"] pruneopts = "UT" - revision = "45a5f77698d342a8c2ef8423abdf0ba6880b008a" + revision = "4d3f4d9ffa16a13f451c3b2999e9c49e9750bf06" [[projects]] branch = "master" - digest = "1:f22e437d9328275884d0ae018ed0de4c280871c944a5ab332aa37fd71fb5801b" + digest = "1:35560529f5721d65f36dc23aa15fbf7306c5091cdc3f29f65881af5031b80cc8" name = "golang.org/x/sys" packages = ["unix"] pruneopts = "UT" - revision = "95b1ffbd15a57cc5abb3f04402b9e8ec0016a52c" + revision = "9b800f95dbbc54abff0acf7ee32d88ba4e328c89" [solve-meta] analyzer-name = "dep" @@ -151,6 +162,7 @@ "github.com/btcsuite/btcwallet/walletdb/bdb", "github.com/btcsuite/btcwallet/wtxmgr", "github.com/stretchr/testify/assert", + "github.com/stretchr/testify/mock", ] solver-name = "gps-cdcl" solver-version = 1 diff --git a/internal/wallet/address.go b/internal/wallet/address.go index 0f783a4..85b7774 100644 --- a/internal/wallet/address.go +++ b/internal/wallet/address.go @@ -1,6 +1,8 @@ package wallet import ( + "fmt" + "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/walletdb" @@ -8,12 +10,20 @@ import ( ) func (w *wallet) NewPubkey() (pub *btcec.PublicKey, err error) { - // TODO: generate new pubkey and address using newAddress - _, err = w.newAddress(waddrmgr.KeyScopeBIP0084, []byte{}, uint32(1), uint32(1)) + + testPrivPass := []byte("81lUHXnOMZ@?XXd7O9xyDIWIbXX-lj") + + mAddrs, err := w.newAddress(waddrmgr.KeyScopeBIP0084, testPrivPass, uint32(1), uint32(1)) if err != nil { - return + return nil, err } - return + // pub = (waddrmgr.ManagedPubKeyAddress(mAddr[0])).PubKey() + fmt.Printf("MADDRS[0}\n%+v\n", mAddrs[0]) + + pub = (mAddrs[0].(waddrmgr.ManagedPubKeyAddress)).PubKey() + fmt.Printf("PUB\n%+v\n", pub) + + return pub, err } func (w *wallet) NewWitnessPubkeyScript() (pkScript []byte, err error) { diff --git a/internal/wallet/address_test.go b/internal/wallet/address_test.go index 4a7041b..3c6ec96 100644 --- a/internal/wallet/address_test.go +++ b/internal/wallet/address_test.go @@ -1,10 +1,25 @@ package wallet import ( + "fmt" "testing" + + "github.com/btcsuite/btcwallet/waddrmgr" + "github.com/stretchr/testify/assert" ) func TestNewPubkey(t *testing.T) { + tearDownFunc, wallet := setupWallet(t) + defer tearDownFunc() + + assert.NotNil(t, wallet) + fmt.Printf("WALLET\n%+v\n", wallet) + + wallet.CreateAccount(waddrmgr.KeyScopeBIP0084, testAccountName, testPrivPass) + + pub, _ := wallet.NewPubkey() + + assert.NotNil(t, pub) } func TestWitnessNewPubkeyScript(t *testing.T) { diff --git a/internal/wallet/test_util.go b/internal/wallet/test_util.go index b252ae3..62aff50 100644 --- a/internal/wallet/test_util.go +++ b/internal/wallet/test_util.go @@ -48,7 +48,7 @@ func setupDB(t *testing.T) (db walletdb.DB, tearDownFunc func()) { return } -func setupWallet(t *testing.T) (tearDownFunc func(), w Wallet) { +func setupWallet(t *testing.T) (tearDownFunc func(), w *wallet) { assert := assert.New(t) db, deleteDB := setupDB(t) diff --git a/internal/wallet/utxo.go b/internal/wallet/utxo.go index 3c47b12..ed916c1 100644 --- a/internal/wallet/utxo.go +++ b/internal/wallet/utxo.go @@ -19,31 +19,6 @@ type Utxo = *btcjson.ListUnspentResult // Only utxos with address contained the param addresses will be considered. // If param addresses is empty, all addresses are considered and there is no // filter -func (w *wallet) ListUnspent() ([]Utxo, error) { - var results []*btcjson.ListUnspentResult - err := walletdb.View(w.db, func(tx walletdb.ReadTx) error { - addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) - txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) - - syncBlock := w.manager.SyncedTo() - // filter := len(addresses) != 0 - - unspent, e := w.txStore.UnspentOutputs(txmgrNs) - if e != nil { - return e - } - - results = make([]*btcjson.ListUnspentResult, 0, len(unspent)) - for i := range unspent { - output := unspent[i] - result := w.credit2ListUnspentResult(output, syncBlock, addrmgrNs) - // TODO: result might return nil... catch that nil? - results = append(results, result) - } - return nil - }) - return results, err -} func (w *wallet) credit2ListUnspentResult( c wtxmgr.Credit, diff --git a/internal/wallet/utxo_test.go b/internal/wallet/utxo_test.go index 2eed68f..fea8da3 100644 --- a/internal/wallet/utxo_test.go +++ b/internal/wallet/utxo_test.go @@ -11,6 +11,7 @@ import ( "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/btcwallet/wtxmgr" + "github.com/stretchr/testify/assert" ) var ( @@ -32,34 +33,34 @@ var ( // TestListUnspent() will also need to check different types of scripts func TestListUnspent(t *testing.T) { - // tearDownFunc, wallet := setupWallet(t) - // defer tearDownFunc() - - // utxos := fakeUtxos(wallet) - // fmt.Printf("%+v\n", utxos) - - // syncBlock := wallet.manager.SyncedTo() - - // err := walletdb.View(wallet.db, func(tx walletdb.ReadTx) error { - // wtxmgrBucket := tx.ReadBucket(wtxmgrNamespaceKey) - // if wtxmgrBucket == nil { - // return errors.New("missing transaction manager namespace") - // } - // fmt.Printf("successfully got wtxmgr?\n") - // asdf := w.credit2ListUnspentResult(utxos[0], syncBlock, wtxmgrBucket) - // fmt.Printf("%+v\n", asdf) - - // assert.NotNil(t, asdf) - - // return nil - // }) - // if err != nil { - // fmt.Println(err) - // } + tearDownFunc, wallet := setupWallet(t) + defer tearDownFunc() + + utxos := fakeUtxos(wallet) + fmt.Printf("%+v\n", utxos) + + syncBlock := wallet.manager.SyncedTo() + + err := walletdb.View(wallet.db, func(tx walletdb.ReadTx) error { + wtxmgrBucket := tx.ReadBucket(wtxmgrNamespaceKey) + if wtxmgrBucket == nil { + return errors.New("missing transaction manager namespace") + } + fmt.Printf("successfully got wtxmgr?\n") + asdf := wallet.credit2ListUnspentResult(utxos[0], syncBlock, wtxmgrBucket) + fmt.Printf("%+v\n", asdf) + + assert.NotNil(t, asdf) + + return nil + }) + if err != nil { + fmt.Println(err) + } } // fakeUtxos creates fake transactions, and inserts them into the provided wallet's db -func fakeUtxos(w wallet) []wtxmgr.Credit { +func fakeUtxos(w *wallet) []wtxmgr.Credit { tx := spendOutput(&chainhash.Hash{}, 0, 10e8) rec, err := wtxmgr.NewTxRecordFromMsgTx(tx, timeNow()) if err != nil { diff --git a/internal/wallet/wallet.go b/internal/wallet/wallet.go index 9ec8bbe..ec8ac4e 100644 --- a/internal/wallet/wallet.go +++ b/internal/wallet/wallet.go @@ -7,6 +7,7 @@ import ( "time" "github.com/btcsuite/btcd/btcec" + "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/walletdb" @@ -49,7 +50,7 @@ type wallet struct { // TODO: separate db creation and Manager creation // TODO: create loader script for wallet init func CreateWallet(params *chaincfg.Params, seed, pubPass, privPass []byte, - dbFilePath, walletName string) (Wallet, error) { + dbFilePath, walletName string) (*wallet, error) { // TODO: add prompts for dbDirPath, walletDBname // Create a new db at specified path dbDirPath := filepath.Join(dbFilePath, params.Name) @@ -111,7 +112,7 @@ func Create(db walletdb.DB, params *chaincfg.Params, seed, pubPass, // Open loads a wallet from the passed db and public pass phrase. func Open(db walletdb.DB, pubPass []byte, - params *chaincfg.Params) (Wallet, error) { + params *chaincfg.Params) (*wallet, error) { err := walletdb.View(db, func(tx walletdb.ReadTx) error { waddrmgrBucket := tx.ReadBucket(waddrmgrNamespaceKey) if waddrmgrBucket == nil { @@ -198,6 +199,33 @@ func (w *wallet) CreateAccount(scope waddrmgr.KeyScope, name string, return account, nil } +func (w *wallet) ListUnspent() (utxos []Utxo, err error) { + var results []*btcjson.ListUnspentResult + err = walletdb.View(w.db, func(tx walletdb.ReadTx) error { + addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) + txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) + + syncBlock := w.manager.SyncedTo() + // filter := len(addresses) != 0 + + unspent, e := w.txStore.UnspentOutputs(txmgrNs) + if e != nil { + return e + } + + // utxos = make([]*btcjson.ListUnspentResult, 0, len(unspent)) + for i := range unspent { + output := unspent[i] + result := w.credit2ListUnspentResult(output, syncBlock, addrmgrNs) + // TODO: result might return nil... catch that nil? + results = append(results, result) + } + return nil + }) + utxos = results + return utxos, err +} + // Helper function, TODO: move somewhere else? func fileExists(filePath string) (bool, error) { _, err := os.Stat(filePath) From e0ebfbd856d07704e935017aabdf48af855aee40 Mon Sep 17 00:00:00 2001 From: akekeke Date: Fri, 2 Nov 2018 18:27:02 +0900 Subject: [PATCH 18/28] cleaned up --- internal/wallet/address.go | 9 +------ internal/wallet/address_test.go | 5 ---- internal/wallet/utxo_test.go | 47 +++++++-------------------------- 3 files changed, 11 insertions(+), 50 deletions(-) diff --git a/internal/wallet/address.go b/internal/wallet/address.go index 85b7774..222e60c 100644 --- a/internal/wallet/address.go +++ b/internal/wallet/address.go @@ -1,8 +1,6 @@ package wallet import ( - "fmt" - "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/walletdb" @@ -10,19 +8,14 @@ import ( ) func (w *wallet) NewPubkey() (pub *btcec.PublicKey, err error) { - + // TODO: remove later, only addded this line so tests would pass testPrivPass := []byte("81lUHXnOMZ@?XXd7O9xyDIWIbXX-lj") mAddrs, err := w.newAddress(waddrmgr.KeyScopeBIP0084, testPrivPass, uint32(1), uint32(1)) if err != nil { return nil, err } - // pub = (waddrmgr.ManagedPubKeyAddress(mAddr[0])).PubKey() - fmt.Printf("MADDRS[0}\n%+v\n", mAddrs[0]) - pub = (mAddrs[0].(waddrmgr.ManagedPubKeyAddress)).PubKey() - fmt.Printf("PUB\n%+v\n", pub) - return pub, err } diff --git a/internal/wallet/address_test.go b/internal/wallet/address_test.go index 3c6ec96..ac5a523 100644 --- a/internal/wallet/address_test.go +++ b/internal/wallet/address_test.go @@ -1,7 +1,6 @@ package wallet import ( - "fmt" "testing" "github.com/btcsuite/btcwallet/waddrmgr" @@ -12,11 +11,7 @@ func TestNewPubkey(t *testing.T) { tearDownFunc, wallet := setupWallet(t) defer tearDownFunc() - assert.NotNil(t, wallet) - fmt.Printf("WALLET\n%+v\n", wallet) - wallet.CreateAccount(waddrmgr.KeyScopeBIP0084, testAccountName, testPrivPass) - pub, _ := wallet.NewPubkey() assert.NotNil(t, pub) diff --git a/internal/wallet/utxo_test.go b/internal/wallet/utxo_test.go index fea8da3..d04108e 100644 --- a/internal/wallet/utxo_test.go +++ b/internal/wallet/utxo_test.go @@ -37,7 +37,6 @@ func TestListUnspent(t *testing.T) { defer tearDownFunc() utxos := fakeUtxos(wallet) - fmt.Printf("%+v\n", utxos) syncBlock := wallet.manager.SyncedTo() @@ -46,17 +45,16 @@ func TestListUnspent(t *testing.T) { if wtxmgrBucket == nil { return errors.New("missing transaction manager namespace") } - fmt.Printf("successfully got wtxmgr?\n") - asdf := wallet.credit2ListUnspentResult(utxos[0], syncBlock, wtxmgrBucket) - fmt.Printf("%+v\n", asdf) - - assert.NotNil(t, asdf) + result := wallet.credit2ListUnspentResult(utxos[0], syncBlock, wtxmgrBucket) + assert.NotNil(t, result) return nil }) if err != nil { fmt.Println(err) } + assert.Nil(t, err) + } // fakeUtxos creates fake transactions, and inserts them into the provided wallet's db @@ -81,45 +79,20 @@ func fakeUtxos(w *wallet) []wtxmgr.Credit { if wtxmgrBucket == nil { return errors.New("missing transaction manager namespace") } - fmt.Printf("successfully got wtxmgr?\n") - e := w.txStore.InsertTx(wtxmgrBucket, fakeTxRecordA, nil) - if e != nil { - fmt.Println(e) - return e - } - e = w.txStore.AddCredit(wtxmgrBucket, fakeTxRecordA, nil, 0, false) - if e != nil { - fmt.Println(e) - return e - } + _ = w.txStore.InsertTx(wtxmgrBucket, fakeTxRecordA, nil) + _ = w.txStore.AddCredit(wtxmgrBucket, fakeTxRecordA, nil, 0, false) fmt.Printf("created fake credit A\n") // Insert a second transaction which spends the output, and creates two // outputs. Mark the second one (5 BTC) as wallet change. - e = w.txStore.InsertTx(wtxmgrBucket, fakeTxRecordB, nil) - if e != nil { - fmt.Println(e) - return e - } - e = w.txStore.AddCredit(wtxmgrBucket, fakeTxRecordB, nil, 1, true) - if e != nil { - fmt.Println(e) - return e - } + _ = w.txStore.InsertTx(wtxmgrBucket, fakeTxRecordB, nil) + _ = w.txStore.AddCredit(wtxmgrBucket, fakeTxRecordB, nil, 1, true) fmt.Printf("created fake credit B\n") // // Mine each transaction in a block at height 100. - e = w.txStore.InsertTx(wtxmgrBucket, fakeTxRecordA, &exampleBlock100) - if e != nil { - fmt.Println(e) - return e - } - e = w.txStore.InsertTx(wtxmgrBucket, fakeTxRecordB, &exampleBlock100) - if e != nil { - fmt.Println(e) - return e - } + _ = w.txStore.InsertTx(wtxmgrBucket, fakeTxRecordA, &exampleBlock100) + _ = w.txStore.InsertTx(wtxmgrBucket, fakeTxRecordB, &exampleBlock100) fmt.Printf("mined each transaction\n") // Print the one confirmation balance. From 2cad0230d7fc37202f5cb5e65f7156577f3ec306 Mon Sep 17 00:00:00 2001 From: akekeke Date: Fri, 2 Nov 2018 18:47:29 +0900 Subject: [PATCH 19/28] more cleaning --- internal/wallet/test_util.go | 2 +- internal/wallet/utxo.go | 26 +++++++++++++++++++ internal/wallet/wallet.go | 48 +++++++++--------------------------- 3 files changed, 38 insertions(+), 38 deletions(-) diff --git a/internal/wallet/test_util.go b/internal/wallet/test_util.go index 62aff50..7361e4a 100644 --- a/internal/wallet/test_util.go +++ b/internal/wallet/test_util.go @@ -55,7 +55,7 @@ func setupWallet(t *testing.T) (tearDownFunc func(), w *wallet) { err := Create(db, testNetParams, testSeed, testPubPass, testPrivPass) assert.Nil(err) - w, err = Open(db, testPubPass, testNetParams) + w, err = Open(db, testPubPass, testPrivPass, testNetParams) assert.Nil(err) tearDownFunc = func() { diff --git a/internal/wallet/utxo.go b/internal/wallet/utxo.go index ed916c1..3101c4c 100644 --- a/internal/wallet/utxo.go +++ b/internal/wallet/utxo.go @@ -19,6 +19,32 @@ type Utxo = *btcjson.ListUnspentResult // Only utxos with address contained the param addresses will be considered. // If param addresses is empty, all addresses are considered and there is no // filter +func (w *wallet) ListUnspent() (utxos []Utxo, err error) { + var results []*btcjson.ListUnspentResult + err = walletdb.View(w.db, func(tx walletdb.ReadTx) error { + addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) + txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) + + syncBlock := w.manager.SyncedTo() + // filter := len(addresses) != 0 + + unspent, e := w.txStore.UnspentOutputs(txmgrNs) + if e != nil { + return e + } + + // utxos = make([]*btcjson.ListUnspentResult, 0, len(unspent)) + for i := range unspent { + output := unspent[i] + result := w.credit2ListUnspentResult(output, syncBlock, addrmgrNs) + // TODO: result might return nil... catch that nil? + results = append(results, result) + } + return nil + }) + utxos = results + return utxos, err +} func (w *wallet) credit2ListUnspentResult( c wtxmgr.Credit, diff --git a/internal/wallet/wallet.go b/internal/wallet/wallet.go index ec8ac4e..3040d7c 100644 --- a/internal/wallet/wallet.go +++ b/internal/wallet/wallet.go @@ -7,7 +7,6 @@ import ( "time" "github.com/btcsuite/btcd/btcec" - "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/walletdb" @@ -37,8 +36,9 @@ var ( // Wallet is hierarchical deterministic wallet type wallet struct { - params *chaincfg.Params - publicPassphrase []byte // I'm thinking this should removed... + params *chaincfg.Params + publicPassphrase []byte + privatePassphrase []byte // rpc *rpc.BtcRPC db walletdb.DB @@ -79,7 +79,7 @@ func CreateWallet(params *chaincfg.Params, seed, pubPass, privPass []byte, } // Open the wallet - return Open(db, pubPass, params) + return Open(db, pubPass, privPass, params) } // Create creates an new wallet, writing it to the passed in db. @@ -111,7 +111,7 @@ func Create(db walletdb.DB, params *chaincfg.Params, seed, pubPass, } // Open loads a wallet from the passed db and public pass phrase. -func Open(db walletdb.DB, pubPass []byte, +func Open(db walletdb.DB, pubPass, privPass []byte, params *chaincfg.Params) (*wallet, error) { err := walletdb.View(db, func(tx walletdb.ReadTx) error { waddrmgrBucket := tx.ReadBucket(waddrmgrNamespaceKey) @@ -151,11 +151,12 @@ func Open(db walletdb.DB, pubPass []byte, } w := &wallet{ - params: params, - publicPassphrase: pubPass, - db: db, - manager: addrMgr, - txStore: txMgr, + params: params, + publicPassphrase: pubPass, + privatePassphrase: privPass, + db: db, + manager: addrMgr, + txStore: txMgr, } return w, nil @@ -199,33 +200,6 @@ func (w *wallet) CreateAccount(scope waddrmgr.KeyScope, name string, return account, nil } -func (w *wallet) ListUnspent() (utxos []Utxo, err error) { - var results []*btcjson.ListUnspentResult - err = walletdb.View(w.db, func(tx walletdb.ReadTx) error { - addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) - txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) - - syncBlock := w.manager.SyncedTo() - // filter := len(addresses) != 0 - - unspent, e := w.txStore.UnspentOutputs(txmgrNs) - if e != nil { - return e - } - - // utxos = make([]*btcjson.ListUnspentResult, 0, len(unspent)) - for i := range unspent { - output := unspent[i] - result := w.credit2ListUnspentResult(output, syncBlock, addrmgrNs) - // TODO: result might return nil... catch that nil? - results = append(results, result) - } - return nil - }) - utxos = results - return utxos, err -} - // Helper function, TODO: move somewhere else? func fileExists(filePath string) (bool, error) { _, err := os.Stat(filePath) From c20b49c5244f4746d202be1f3408e64a7253ed3d Mon Sep 17 00:00:00 2001 From: akekeke Date: Fri, 2 Nov 2018 18:55:05 +0900 Subject: [PATCH 20/28] added privpass to wallet struct --- internal/wallet/address.go | 5 +---- internal/wallet/wallet.go | 2 +- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/internal/wallet/address.go b/internal/wallet/address.go index 222e60c..ccedee8 100644 --- a/internal/wallet/address.go +++ b/internal/wallet/address.go @@ -8,10 +8,7 @@ import ( ) func (w *wallet) NewPubkey() (pub *btcec.PublicKey, err error) { - // TODO: remove later, only addded this line so tests would pass - testPrivPass := []byte("81lUHXnOMZ@?XXd7O9xyDIWIbXX-lj") - - mAddrs, err := w.newAddress(waddrmgr.KeyScopeBIP0084, testPrivPass, uint32(1), uint32(1)) + mAddrs, err := w.newAddress(waddrmgr.KeyScopeBIP0084, w.privatePassphrase, uint32(1), uint32(1)) if err != nil { return nil, err } diff --git a/internal/wallet/wallet.go b/internal/wallet/wallet.go index 3040d7c..9706cc7 100644 --- a/internal/wallet/wallet.go +++ b/internal/wallet/wallet.go @@ -128,7 +128,7 @@ func Open(db walletdb.DB, pubPass, privPass []byte, return nil, err } - // TODO: Perform wallet upgrades if necessary? + // TODO: Perform wallet upgrades/updates if necessary? // Open database abstraction instances var ( From eeb6fbeab3da100ee95cf4fe249b763ab75f022c Mon Sep 17 00:00:00 2001 From: akekeke Date: Fri, 2 Nov 2018 19:30:29 +0900 Subject: [PATCH 21/28] moved some func to test_util --- internal/wallet/test_util.go | 152 ++++++++++++++++++++++++++++++++++ internal/wallet/utxo.go | 156 +---------------------------------- 2 files changed, 154 insertions(+), 154 deletions(-) diff --git a/internal/wallet/test_util.go b/internal/wallet/test_util.go index 7361e4a..09d8d0e 100644 --- a/internal/wallet/test_util.go +++ b/internal/wallet/test_util.go @@ -1,13 +1,19 @@ package wallet import ( + "encoding/hex" "io/ioutil" "os" "path/filepath" "testing" + "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/walletdb" + "github.com/btcsuite/btcwallet/wtxmgr" "github.com/stretchr/testify/assert" ) @@ -66,3 +72,149 @@ func setupWallet(t *testing.T) (tearDownFunc func(), w *wallet) { return } + +func (w *wallet) credit2ListUnspentResult( + c wtxmgr.Credit, + syncBlock waddrmgr.BlockStamp, + addrmgrNs walletdb.ReadBucket) *btcjson.ListUnspentResult { + + // TODO: add minconf, maxconf params + confs := confirms(c.Height, syncBlock.Height) + + // // Outputs with fewer confirmations than the minimum or more + // // confs than the maximum are excluded. + // confs := confirms(output.Height, syncBlock.Height) + // if confs < minconf || confs > maxconf { + // continue + // } + + // Only mature coinbase outputs are included. + if c.FromCoinBase { + target := int32(w.params.CoinbaseMaturity) // make param + if !confirmed(target, c.Height, syncBlock.Height) { + // continue + return nil // maybe? + + } + } + + // TODO: exclude locked outputs from result set. + // Exclude locked outputs from the result set. + + // Lookup the associated account for the output. Use the + // default account name in case there is no associated account + // for some reason, although this should never happen. + // + // This will be unnecessary once transactions and outputs are + // grouped under the associated account in the db. + defaultAccountName := "default" + acctName := defaultAccountName + sc, addrs, _, err := txscript.ExtractPkScriptAddrs( + c.PkScript, w.params) + if err != nil { + // continue + return nil // maybe? + } + if len(addrs) > 0 { + smgr, acct, err := w.manager.AddrAccount(addrmgrNs, addrs[0]) + if err == nil { + s, err := smgr.AccountName(addrmgrNs, acct) + if err == nil { + acctName = s + } + } + } + + // not including this part bc this func will assume there is no filter + // if filter { + // for _, addr := range addrs { + // _, ok := addresses[addr.EncodeAddress()] + // if ok { + // goto include + // } + // } + // // continue + // return nil // maybe? + // } + // include: + + result := &btcjson.ListUnspentResult{ + TxID: c.OutPoint.Hash.String(), + Vout: c.OutPoint.Index, + Account: acctName, + ScriptPubKey: hex.EncodeToString(c.PkScript), + Amount: c.Amount.ToBTC(), + Confirmations: int64(confs), + Spendable: w.isSpendable(sc, addrs, addrmgrNs), + } + + // BUG: this should be a JSON array so that all + // addresses can be included, or removed (and the + // caller extracts addresses from the pkScript). + if len(addrs) > 0 { + result.Address = addrs[0].EncodeAddress() + } + + return result +} + +// isSpendable determines if given ScriptClass is spendable or not. +// Does NOT support watch-only addresses. This func will need to be rewritten +// to support watch-only addresses +func (w *wallet) isSpendable(sc txscript.ScriptClass, addrs []btcutil.Address, + addrmgrNs walletdb.ReadBucket) (spendable bool) { + // At the moment watch-only addresses are not supported, so all + // recorded outputs that are not multisig are "spendable". + // Multisig outputs are only "spendable" if all keys are + // controlled by this wallet. + // + // TODO: Each case will need updates when watch-only addrs + // is added. For P2PK, P2PKH, and P2SH, the address must be + // looked up and not be watching-only. For multisig, all + // pubkeys must belong to the manager with the associated + // private key (currently it only checks whether the pubkey + // exists, since the private key is required at the moment). +scSwitch: + switch sc { + case txscript.PubKeyHashTy: + spendable = true + case txscript.PubKeyTy: + spendable = true + case txscript.WitnessV0ScriptHashTy: + spendable = true + case txscript.WitnessV0PubKeyHashTy: + spendable = true + case txscript.MultiSigTy: + for _, a := range addrs { + _, err := w.manager.Address(addrmgrNs, a) + if err == nil { + continue + } + if waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { + break scSwitch + } + // return err TODO: figure out what to replace the return error + } + spendable = true + } + + return spendable +} + +// confirms returns the number of confirmations for a transaction in a block at +// height txHeight (or -1 for an unconfirmed tx) given the chain height +// curHeight. +func confirms(txHeight, curHeight int32) int32 { + switch { + case txHeight == -1, txHeight > curHeight: + return 0 + default: + return curHeight - txHeight + 1 + } +} + +// confirmed checks whether a transaction at height txHeight has met minconf +// confirmations for a blockchain at height curHeight. +func confirmed(minconf, txHeight, curHeight int32) bool { + return confirms(txHeight, curHeight) >= minconf +} diff --git a/internal/wallet/utxo.go b/internal/wallet/utxo.go index 3101c4c..57addc2 100644 --- a/internal/wallet/utxo.go +++ b/internal/wallet/utxo.go @@ -1,25 +1,19 @@ package wallet import ( - "encoding/hex" - "github.com/btcsuite/btcd/btcjson" - "github.com/btcsuite/btcd/txscript" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/walletdb" - "github.com/btcsuite/btcwallet/wtxmgr" ) // Utxo is a unspend transaction output -type Utxo = *btcjson.ListUnspentResult +type Utxo = btcjson.ListUnspentResult // ListUnspent returns unspent transactions. // TODO: add filter // Only utxos with address contained the param addresses will be considered. // If param addresses is empty, all addresses are considered and there is no // filter -func (w *wallet) ListUnspent() (utxos []Utxo, err error) { +func (w *wallet) ListUnspent() (utxos []*Utxo, err error) { var results []*btcjson.ListUnspentResult err = walletdb.View(w.db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) @@ -45,149 +39,3 @@ func (w *wallet) ListUnspent() (utxos []Utxo, err error) { utxos = results return utxos, err } - -func (w *wallet) credit2ListUnspentResult( - c wtxmgr.Credit, - syncBlock waddrmgr.BlockStamp, - addrmgrNs walletdb.ReadBucket) *btcjson.ListUnspentResult { - - // TODO: add minconf, maxconf params - confs := confirms(c.Height, syncBlock.Height) - - // // Outputs with fewer confirmations than the minimum or more - // // confs than the maximum are excluded. - // confs := confirms(output.Height, syncBlock.Height) - // if confs < minconf || confs > maxconf { - // continue - // } - - // Only mature coinbase outputs are included. - if c.FromCoinBase { - target := int32(w.params.CoinbaseMaturity) // make param - if !confirmed(target, c.Height, syncBlock.Height) { - // continue - return nil // maybe? - - } - } - - // TODO: exclude locked outputs from result set. - // Exclude locked outputs from the result set. - - // Lookup the associated account for the output. Use the - // default account name in case there is no associated account - // for some reason, although this should never happen. - // - // This will be unnecessary once transactions and outputs are - // grouped under the associated account in the db. - defaultAccountName := "default" - acctName := defaultAccountName - sc, addrs, _, err := txscript.ExtractPkScriptAddrs( - c.PkScript, w.params) - if err != nil { - // continue - return nil // maybe? - } - if len(addrs) > 0 { - smgr, acct, err := w.manager.AddrAccount(addrmgrNs, addrs[0]) - if err == nil { - s, err := smgr.AccountName(addrmgrNs, acct) - if err == nil { - acctName = s - } - } - } - - // not including this part bc this func will assume there is no filter - // if filter { - // for _, addr := range addrs { - // _, ok := addresses[addr.EncodeAddress()] - // if ok { - // goto include - // } - // } - // // continue - // return nil // maybe? - // } - // include: - - result := &btcjson.ListUnspentResult{ - TxID: c.OutPoint.Hash.String(), - Vout: c.OutPoint.Index, - Account: acctName, - ScriptPubKey: hex.EncodeToString(c.PkScript), - Amount: c.Amount.ToBTC(), - Confirmations: int64(confs), - Spendable: w.isSpendable(sc, addrs, addrmgrNs), - } - - // BUG: this should be a JSON array so that all - // addresses can be included, or removed (and the - // caller extracts addresses from the pkScript). - if len(addrs) > 0 { - result.Address = addrs[0].EncodeAddress() - } - - return result -} - -// isSpendable determines if given ScriptClass is spendable or not. -// Does NOT support watch-only addresses. This func will need to be rewritten -// to support watch-only addresses -func (w *wallet) isSpendable(sc txscript.ScriptClass, addrs []btcutil.Address, - addrmgrNs walletdb.ReadBucket) (spendable bool) { - // At the moment watch-only addresses are not supported, so all - // recorded outputs that are not multisig are "spendable". - // Multisig outputs are only "spendable" if all keys are - // controlled by this wallet. - // - // TODO: Each case will need updates when watch-only addrs - // is added. For P2PK, P2PKH, and P2SH, the address must be - // looked up and not be watching-only. For multisig, all - // pubkeys must belong to the manager with the associated - // private key (currently it only checks whether the pubkey - // exists, since the private key is required at the moment). -scSwitch: - switch sc { - case txscript.PubKeyHashTy: - spendable = true - case txscript.PubKeyTy: - spendable = true - case txscript.WitnessV0ScriptHashTy: - spendable = true - case txscript.WitnessV0PubKeyHashTy: - spendable = true - case txscript.MultiSigTy: - for _, a := range addrs { - _, err := w.manager.Address(addrmgrNs, a) - if err == nil { - continue - } - if waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { - break scSwitch - } - // return err TODO: figure out what to replace the return error - } - spendable = true - } - - return spendable -} - -// confirms returns the number of confirmations for a transaction in a block at -// height txHeight (or -1 for an unconfirmed tx) given the chain height -// curHeight. -func confirms(txHeight, curHeight int32) int32 { - switch { - case txHeight == -1, txHeight > curHeight: - return 0 - default: - return curHeight - txHeight + 1 - } -} - -// confirmed checks whether a transaction at height txHeight has met minconf -// confirmations for a blockchain at height curHeight. -func confirmed(minconf, txHeight, curHeight int32) bool { - return confirms(txHeight, curHeight) >= minconf -} From 74077b0cf71d09b55fb26ec88837922b04445aa8 Mon Sep 17 00:00:00 2001 From: akekeke Date: Mon, 5 Nov 2018 10:23:38 +0900 Subject: [PATCH 22/28] refactored Wallet interface --- internal/wallet/address.go | 4 +- internal/wallet/address_test.go | 2 +- internal/wallet/test_util.go | 154 +------------------------------ internal/wallet/utxo.go | 155 +++++++++++++++++++++++++++++++- internal/wallet/utxo_test.go | 112 ++--------------------- internal/wallet/wallet.go | 16 +++- internal/wallet/wallet_test.go | 2 +- 7 files changed, 177 insertions(+), 268 deletions(-) diff --git a/internal/wallet/address.go b/internal/wallet/address.go index ccedee8..5377ccc 100644 --- a/internal/wallet/address.go +++ b/internal/wallet/address.go @@ -32,7 +32,7 @@ func (w *wallet) newAddress(scope waddrmgr.KeyScope, privPass []byte, // unlock Manager err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) - e := w.manager.Unlock(ns, privPass) + e := w.Manager().Unlock(ns, privPass) return e }) if err != nil { @@ -40,7 +40,7 @@ func (w *wallet) newAddress(scope waddrmgr.KeyScope, privPass []byte, } // get ScopedKeyManager - scopedMgr, err := w.manager.FetchScopedKeyManager(scope) + scopedMgr, err := w.Manager().FetchScopedKeyManager(scope) if err != nil { return nil, err } diff --git a/internal/wallet/address_test.go b/internal/wallet/address_test.go index ac5a523..0b5514f 100644 --- a/internal/wallet/address_test.go +++ b/internal/wallet/address_test.go @@ -8,7 +8,7 @@ import ( ) func TestNewPubkey(t *testing.T) { - tearDownFunc, wallet := setupWallet(t) + tearDownFunc, wallet, _ := setupWallet(t) defer tearDownFunc() wallet.CreateAccount(waddrmgr.KeyScopeBIP0084, testAccountName, testPrivPass) diff --git a/internal/wallet/test_util.go b/internal/wallet/test_util.go index 09d8d0e..237ea16 100644 --- a/internal/wallet/test_util.go +++ b/internal/wallet/test_util.go @@ -1,19 +1,13 @@ package wallet import ( - "encoding/hex" "io/ioutil" "os" "path/filepath" "testing" - "github.com/btcsuite/btcd/btcjson" "github.com/btcsuite/btcd/chaincfg" - "github.com/btcsuite/btcd/txscript" - "github.com/btcsuite/btcutil" - "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/walletdb" - "github.com/btcsuite/btcwallet/wtxmgr" "github.com/stretchr/testify/assert" ) @@ -54,7 +48,7 @@ func setupDB(t *testing.T) (db walletdb.DB, tearDownFunc func()) { return } -func setupWallet(t *testing.T) (tearDownFunc func(), w *wallet) { +func setupWallet(t *testing.T) (tearDownFunc func(), w Wallet, db walletd.DB) { assert := assert.New(t) db, deleteDB := setupDB(t) @@ -72,149 +66,3 @@ func setupWallet(t *testing.T) (tearDownFunc func(), w *wallet) { return } - -func (w *wallet) credit2ListUnspentResult( - c wtxmgr.Credit, - syncBlock waddrmgr.BlockStamp, - addrmgrNs walletdb.ReadBucket) *btcjson.ListUnspentResult { - - // TODO: add minconf, maxconf params - confs := confirms(c.Height, syncBlock.Height) - - // // Outputs with fewer confirmations than the minimum or more - // // confs than the maximum are excluded. - // confs := confirms(output.Height, syncBlock.Height) - // if confs < minconf || confs > maxconf { - // continue - // } - - // Only mature coinbase outputs are included. - if c.FromCoinBase { - target := int32(w.params.CoinbaseMaturity) // make param - if !confirmed(target, c.Height, syncBlock.Height) { - // continue - return nil // maybe? - - } - } - - // TODO: exclude locked outputs from result set. - // Exclude locked outputs from the result set. - - // Lookup the associated account for the output. Use the - // default account name in case there is no associated account - // for some reason, although this should never happen. - // - // This will be unnecessary once transactions and outputs are - // grouped under the associated account in the db. - defaultAccountName := "default" - acctName := defaultAccountName - sc, addrs, _, err := txscript.ExtractPkScriptAddrs( - c.PkScript, w.params) - if err != nil { - // continue - return nil // maybe? - } - if len(addrs) > 0 { - smgr, acct, err := w.manager.AddrAccount(addrmgrNs, addrs[0]) - if err == nil { - s, err := smgr.AccountName(addrmgrNs, acct) - if err == nil { - acctName = s - } - } - } - - // not including this part bc this func will assume there is no filter - // if filter { - // for _, addr := range addrs { - // _, ok := addresses[addr.EncodeAddress()] - // if ok { - // goto include - // } - // } - // // continue - // return nil // maybe? - // } - // include: - - result := &btcjson.ListUnspentResult{ - TxID: c.OutPoint.Hash.String(), - Vout: c.OutPoint.Index, - Account: acctName, - ScriptPubKey: hex.EncodeToString(c.PkScript), - Amount: c.Amount.ToBTC(), - Confirmations: int64(confs), - Spendable: w.isSpendable(sc, addrs, addrmgrNs), - } - - // BUG: this should be a JSON array so that all - // addresses can be included, or removed (and the - // caller extracts addresses from the pkScript). - if len(addrs) > 0 { - result.Address = addrs[0].EncodeAddress() - } - - return result -} - -// isSpendable determines if given ScriptClass is spendable or not. -// Does NOT support watch-only addresses. This func will need to be rewritten -// to support watch-only addresses -func (w *wallet) isSpendable(sc txscript.ScriptClass, addrs []btcutil.Address, - addrmgrNs walletdb.ReadBucket) (spendable bool) { - // At the moment watch-only addresses are not supported, so all - // recorded outputs that are not multisig are "spendable". - // Multisig outputs are only "spendable" if all keys are - // controlled by this wallet. - // - // TODO: Each case will need updates when watch-only addrs - // is added. For P2PK, P2PKH, and P2SH, the address must be - // looked up and not be watching-only. For multisig, all - // pubkeys must belong to the manager with the associated - // private key (currently it only checks whether the pubkey - // exists, since the private key is required at the moment). -scSwitch: - switch sc { - case txscript.PubKeyHashTy: - spendable = true - case txscript.PubKeyTy: - spendable = true - case txscript.WitnessV0ScriptHashTy: - spendable = true - case txscript.WitnessV0PubKeyHashTy: - spendable = true - case txscript.MultiSigTy: - for _, a := range addrs { - _, err := w.manager.Address(addrmgrNs, a) - if err == nil { - continue - } - if waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { - break scSwitch - } - // return err TODO: figure out what to replace the return error - } - spendable = true - } - - return spendable -} - -// confirms returns the number of confirmations for a transaction in a block at -// height txHeight (or -1 for an unconfirmed tx) given the chain height -// curHeight. -func confirms(txHeight, curHeight int32) int32 { - switch { - case txHeight == -1, txHeight > curHeight: - return 0 - default: - return curHeight - txHeight + 1 - } -} - -// confirmed checks whether a transaction at height txHeight has met minconf -// confirmations for a blockchain at height curHeight. -func confirmed(minconf, txHeight, curHeight int32) bool { - return confirms(txHeight, curHeight) >= minconf -} diff --git a/internal/wallet/utxo.go b/internal/wallet/utxo.go index 57addc2..d5303a9 100644 --- a/internal/wallet/utxo.go +++ b/internal/wallet/utxo.go @@ -1,8 +1,14 @@ package wallet import ( + "encoding/hex" + "github.com/btcsuite/btcd/btcjson" + "github.com/btcsuite/btcd/txscript" + "github.com/btcsuite/btcutil" + "github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/walletdb" + "github.com/btcsuite/btcwallet/wtxmgr" ) // Utxo is a unspend transaction output @@ -19,10 +25,10 @@ func (w *wallet) ListUnspent() (utxos []*Utxo, err error) { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) - syncBlock := w.manager.SyncedTo() + syncBlock := w.Manager().SyncedTo() // filter := len(addresses) != 0 - unspent, e := w.txStore.UnspentOutputs(txmgrNs) + unspent, e := w.TxStore().UnspentOutputs(txmgrNs) if e != nil { return e } @@ -39,3 +45,148 @@ func (w *wallet) ListUnspent() (utxos []*Utxo, err error) { utxos = results return utxos, err } + +func (w *wallet) credit2ListUnspentResult( + c wtxmgr.Credit, + syncBlock waddrmgr.BlockStamp, + addrmgrNs walletdb.ReadBucket) *btcjson.ListUnspentResult { + + // TODO: add minconf, maxconf params + confs := confirms(c.Height, syncBlock.Height) + // // Outputs with fewer confirmations than the minimum or more + // // confs than the maximum are excluded. + // confs := confirms(output.Height, syncBlock.Height) + // if confs < minconf || confs > maxconf { + // continue + // } + + // Only mature coinbase outputs are included. + if c.FromCoinBase { + target := int32(w.params.CoinbaseMaturity) // make param + if !confirmed(target, c.Height, syncBlock.Height) { + // continue + return nil // maybe? + + } + } + + // TODO: exclude locked outputs from result set. + // Exclude locked outputs from the result set. + + // Lookup the associated account for the output. Use the + // default account name in case there is no associated account + // for some reason, although this should never happen. + // + // This will be unnecessary once transactions and outputs are + // grouped under the associated account in the db. + defaultAccountName := "default" + acctName := defaultAccountName + sc, addrs, _, err := txscript.ExtractPkScriptAddrs( + c.PkScript, w.params) + if err != nil { + // continue + return nil // maybe? + } + if len(addrs) > 0 { + smgr, acct, err := w.Manager().AddrAccount(addrmgrNs, addrs[0]) + if err == nil { + s, err := smgr.AccountName(addrmgrNs, acct) + if err == nil { + acctName = s + } + } + } + + // not including this part bc this func will assume there is no filter + // if filter { + // for _, addr := range addrs { + // _, ok := addresses[addr.EncodeAddress()] + // if ok { + // goto include + // } + // } + // // continue + // return nil // maybe? + // } + // include: + + result := &btcjson.ListUnspentResult{ + TxID: c.OutPoint.Hash.String(), + Vout: c.OutPoint.Index, + Account: acctName, + ScriptPubKey: hex.EncodeToString(c.PkScript), + Amount: c.Amount.ToBTC(), + Confirmations: int64(confs), + Spendable: w.isSpendable(sc, addrs, addrmgrNs), + } + + // BUG: this should be a JSON array so that all + // addresses can be included, or removed (and the + // caller extracts addresses from the pkScript). + if len(addrs) > 0 { + result.Address = addrs[0].EncodeAddress() + } + + return result +} + +// isSpendable determines if given ScriptClass is spendable or not. +// Does NOT support watch-only addresses. This func will need to be rewritten +// to support watch-only addresses +func (w *wallet) isSpendable(sc txscript.ScriptClass, addrs []btcutil.Address, + addrmgrNs walletdb.ReadBucket) (spendable bool) { + // At the moment watch-only addresses are not supported, so all + // recorded outputs that are not multisig are "spendable". + // Multisig outputs are only "spendable" if all keys are + // controlled by this wallet. + // + // TODO: Each case will need updates when watch-only addrs + // is added. For P2PK, P2PKH, and P2SH, the address must be + // looked up and not be watching-only. For multisig, all + // pubkeys must belong to the manager with the associated + // private key (currently it only checks whether the pubkey + // exists, since the private key is required at the moment). +scSwitch: + switch sc { + case txscript.PubKeyHashTy: + spendable = true + case txscript.PubKeyTy: + spendable = true + case txscript.WitnessV0ScriptHashTy: + spendable = true + case txscript.WitnessV0PubKeyHashTy: + spendable = true + case txscript.MultiSigTy: + for _, a := range addrs { + _, err := w.Manager().Address(addrmgrNs, a) + if err == nil { + continue + } + if waddrmgr.IsError(err, waddrmgr.ErrAddressNotFound) { + break scSwitch + } + // return err TODO: figure out what to replace the return error + } + spendable = true + } + + return spendable +} + +// confirms returns the number of confirmations for a transaction in a block at +// height txHeight (or -1 for an unconfirmed tx) given the chain height +// curHeight. +func confirms(txHeight, curHeight int32) int32 { + switch { + case txHeight == -1, txHeight > curHeight: + return 0 + default: + return curHeight - txHeight + 1 + } +} + +// confirmed checks whether a transaction at height txHeight has met minconf +// confirmations for a blockchain at height curHeight. +func confirmed(minconf, txHeight, curHeight int32) bool { + return confirms(txHeight, curHeight) >= minconf +} diff --git a/internal/wallet/utxo_test.go b/internal/wallet/utxo_test.go index d04108e..632a738 100644 --- a/internal/wallet/utxo_test.go +++ b/internal/wallet/utxo_test.go @@ -1,14 +1,10 @@ package wallet import ( - "encoding/binary" "errors" "fmt" "testing" - "time" - "github.com/btcsuite/btcd/chaincfg/chainhash" - "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/btcwallet/wtxmgr" "github.com/stretchr/testify/assert" @@ -33,21 +29,20 @@ var ( // TestListUnspent() will also need to check different types of scripts func TestListUnspent(t *testing.T) { - tearDownFunc, wallet := setupWallet(t) + tearDownFunc, w, db := setupWallet(t) defer tearDownFunc() - utxos := fakeUtxos(wallet) + _ = fakeUtxos(w, db) - syncBlock := wallet.manager.SyncedTo() - - err := walletdb.View(wallet.db, func(tx walletdb.ReadTx) error { + err := walletdb.View(db, func(tx walletdb.ReadTx) error { wtxmgrBucket := tx.ReadBucket(wtxmgrNamespaceKey) if wtxmgrBucket == nil { return errors.New("missing transaction manager namespace") } - result := wallet.credit2ListUnspentResult(utxos[0], syncBlock, wtxmgrBucket) + utxos, e := w.ListUnspent() - assert.NotNil(t, result) + assert.Nil(t, e) + assert.NotNil(t, utxos) return nil }) if err != nil { @@ -56,98 +51,3 @@ func TestListUnspent(t *testing.T) { assert.Nil(t, err) } - -// fakeUtxos creates fake transactions, and inserts them into the provided wallet's db -func fakeUtxos(w *wallet) []wtxmgr.Credit { - tx := spendOutput(&chainhash.Hash{}, 0, 10e8) - rec, err := wtxmgr.NewTxRecordFromMsgTx(tx, timeNow()) - if err != nil { - panic(err) - } - fakeTxRecordA = rec - - tx = spendOutput(&fakeTxRecordA.Hash, 0, 5e8, 5e8) - rec, err = wtxmgr.NewTxRecordFromMsgTx(tx, timeNow()) - if err != nil { - panic(err) - } - fakeTxRecordB = rec - - var utxos []wtxmgr.Credit - err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { - wtxmgrBucket := tx.ReadWriteBucket(wtxmgrNamespaceKey) - if wtxmgrBucket == nil { - return errors.New("missing transaction manager namespace") - } - - _ = w.txStore.InsertTx(wtxmgrBucket, fakeTxRecordA, nil) - _ = w.txStore.AddCredit(wtxmgrBucket, fakeTxRecordA, nil, 0, false) - fmt.Printf("created fake credit A\n") - - // Insert a second transaction which spends the output, and creates two - // outputs. Mark the second one (5 BTC) as wallet change. - _ = w.txStore.InsertTx(wtxmgrBucket, fakeTxRecordB, nil) - _ = w.txStore.AddCredit(wtxmgrBucket, fakeTxRecordB, nil, 1, true) - fmt.Printf("created fake credit B\n") - - // // Mine each transaction in a block at height 100. - _ = w.txStore.InsertTx(wtxmgrBucket, fakeTxRecordA, &exampleBlock100) - _ = w.txStore.InsertTx(wtxmgrBucket, fakeTxRecordB, &exampleBlock100) - fmt.Printf("mined each transaction\n") - - // Print the one confirmation balance. - bal, e := w.txStore.Balance(wtxmgrBucket, 1, 100) - if e != nil { - fmt.Println(e) - return nil - } - fmt.Println(bal) - - // Fetch unspent outputs. - utxos, e = w.txStore.UnspentOutputs(wtxmgrBucket) - if e != nil { - fmt.Println(e) - } - return e - }) - if err != nil { - fmt.Println(err) - return nil - } - - return utxos -} - -func spendOutput(txHash *chainhash.Hash, index uint32, outputValues ...int64) *wire.MsgTx { - tx := wire.MsgTx{ - TxIn: []*wire.TxIn{ - { - PreviousOutPoint: wire.OutPoint{Hash: *txHash, Index: index}, - }, - }, - } - for _, val := range outputValues { - tx.TxOut = append(tx.TxOut, &wire.TxOut{Value: val}) - } - return &tx -} - -func makeBlockMeta(height int32) wtxmgr.BlockMeta { - if height == -1 { - return wtxmgr.BlockMeta{Block: wtxmgr.Block{Height: -1}} - } - - b := wtxmgr.BlockMeta{ - Block: wtxmgr.Block{Height: height}, - Time: timeNow(), - } - // Give it a fake block hash created from the height and time. - binary.LittleEndian.PutUint32(b.Hash[0:4], uint32(height)) - binary.LittleEndian.PutUint64(b.Hash[4:12], uint64(b.Time.Unix())) - return b -} - -// Returns time.Now() with seconds resolution, this is what Store saves. -func timeNow() time.Time { - return time.Unix(time.Now().Unix(), 0) -} diff --git a/internal/wallet/wallet.go b/internal/wallet/wallet.go index 9706cc7..c48f8cc 100644 --- a/internal/wallet/wallet.go +++ b/internal/wallet/wallet.go @@ -23,8 +23,10 @@ type Wallet interface { NewPubkey() (*btcec.PublicKey, error) NewWitnessPubkeyScript() (pkScript []byte, err error) - ListUnspent() (utxos []Utxo, err error) + ListUnspent() (utxos []*Utxo, err error) + Manager() *waddrmgr.Manager + TxStore() *wtxmgr.Store Close() error } @@ -50,7 +52,7 @@ type wallet struct { // TODO: separate db creation and Manager creation // TODO: create loader script for wallet init func CreateWallet(params *chaincfg.Params, seed, pubPass, privPass []byte, - dbFilePath, walletName string) (*wallet, error) { + dbFilePath, walletName string) (Wallet, error) { // TODO: add prompts for dbDirPath, walletDBname // Create a new db at specified path dbDirPath := filepath.Join(dbFilePath, params.Name) @@ -112,7 +114,7 @@ func Create(db walletdb.DB, params *chaincfg.Params, seed, pubPass, // Open loads a wallet from the passed db and public pass phrase. func Open(db walletdb.DB, pubPass, privPass []byte, - params *chaincfg.Params) (*wallet, error) { + params *chaincfg.Params) (Wallet, error) { err := walletdb.View(db, func(tx walletdb.ReadTx) error { waddrmgrBucket := tx.ReadBucket(waddrmgrNamespaceKey) if waddrmgrBucket == nil { @@ -200,6 +202,14 @@ func (w *wallet) CreateAccount(scope waddrmgr.KeyScope, name string, return account, nil } +func (w *wallet) Manager() *waddrmgr.Manager { + return w.manager +} + +func (w *wallet) TxStore() *wtxmgr.Store { + return w.txStore +} + // Helper function, TODO: move somewhere else? func fileExists(filePath string) (bool, error) { _, err := os.Stat(filePath) diff --git a/internal/wallet/wallet_test.go b/internal/wallet/wallet_test.go index 3131460..b1e7126 100644 --- a/internal/wallet/wallet_test.go +++ b/internal/wallet/wallet_test.go @@ -24,7 +24,7 @@ func TestCreateWallet(t *testing.T) { // TODO: add tests for Create(...) and Open(...) func TestCreateAccount(t *testing.T) { - tearDownFunc, wallet := setupWallet(t) + tearDownFunc, wallet, _ := setupWallet(t) defer tearDownFunc() expectedAccountNumber := uint32(1) From 42cfd0507e85c88d447a0e2b977beb00ca179413 Mon Sep 17 00:00:00 2001 From: akekeke Date: Mon, 5 Nov 2018 10:24:18 +0900 Subject: [PATCH 23/28] added back deleted stuff --- internal/wallet/utxo_test.go | 99 ++++++++++++++++++++++++++++++++++++ 1 file changed, 99 insertions(+) diff --git a/internal/wallet/utxo_test.go b/internal/wallet/utxo_test.go index 632a738..73885e6 100644 --- a/internal/wallet/utxo_test.go +++ b/internal/wallet/utxo_test.go @@ -1,10 +1,14 @@ package wallet import ( + "encoding/binary" "errors" "fmt" "testing" + "time" + "github.com/btcsuite/btcd/chaincfg/chainhash" + "github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/btcwallet/wtxmgr" "github.com/stretchr/testify/assert" @@ -51,3 +55,98 @@ func TestListUnspent(t *testing.T) { assert.Nil(t, err) } + +// fakeUtxos creates fake transactions, and inserts them into the provided wallet's db +func fakeUtxos(w Wallet, db walletdb.DB) []wtxmgr.Credit { + tx := spendOutput(&chainhash.Hash{}, 0, 10e8) + rec, err := wtxmgr.NewTxRecordFromMsgTx(tx, timeNow()) + if err != nil { + panic(err) + } + fakeTxRecordA = rec + + tx = spendOutput(&fakeTxRecordA.Hash, 0, 5e8, 5e8) + rec, err = wtxmgr.NewTxRecordFromMsgTx(tx, timeNow()) + if err != nil { + panic(err) + } + fakeTxRecordB = rec + + var utxos []wtxmgr.Credit + err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { + wtxmgrBucket := tx.ReadWriteBucket(wtxmgrNamespaceKey) + if wtxmgrBucket == nil { + return errors.New("missing transaction manager namespace") + } + + _ = w.TxStore().InsertTx(wtxmgrBucket, fakeTxRecordA, nil) + _ = w.TxStore().AddCredit(wtxmgrBucket, fakeTxRecordA, nil, 0, false) + fmt.Printf("created fake credit A\n") + + // Insert a second transaction which spends the output, and creates two + // outputs. Mark the second one (5 BTC) as wallet change. + _ = w.TxStore().InsertTx(wtxmgrBucket, fakeTxRecordB, nil) + _ = w.TxStore().AddCredit(wtxmgrBucket, fakeTxRecordB, nil, 1, true) + fmt.Printf("created fake credit B\n") + + // // Mine each transaction in a block at height 100. + _ = w.TxStore().InsertTx(wtxmgrBucket, fakeTxRecordA, &exampleBlock100) + _ = w.TxStore().InsertTx(wtxmgrBucket, fakeTxRecordB, &exampleBlock100) + fmt.Printf("mined each transaction\n") + + // Print the one confirmation balance. + bal, e := w.TxStore().Balance(wtxmgrBucket, 1, 100) + if e != nil { + fmt.Println(e) + return nil + } + fmt.Println(bal) + + // Fetch unspent outputs. + utxos, e = w.TxStore().UnspentOutputs(wtxmgrBucket) + if e != nil { + fmt.Println(e) + } + return e + }) + if err != nil { + fmt.Println(err) + return nil + } + + return utxos +} + +func spendOutput(txHash *chainhash.Hash, index uint32, outputValues ...int64) *wire.MsgTx { + tx := wire.MsgTx{ + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: wire.OutPoint{Hash: *txHash, Index: index}, + }, + }, + } + for _, val := range outputValues { + tx.TxOut = append(tx.TxOut, &wire.TxOut{Value: val}) + } + return &tx +} + +func makeBlockMeta(height int32) wtxmgr.BlockMeta { + if height == -1 { + return wtxmgr.BlockMeta{Block: wtxmgr.Block{Height: -1}} + } + + b := wtxmgr.BlockMeta{ + Block: wtxmgr.Block{Height: height}, + Time: timeNow(), + } + // Give it a fake block hash created from the height and time. + binary.LittleEndian.PutUint32(b.Hash[0:4], uint32(height)) + binary.LittleEndian.PutUint64(b.Hash[4:12], uint64(b.Time.Unix())) + return b +} + +// Returns time.Now() with seconds resolution, this is what Store saves. +func timeNow() time.Time { + return time.Unix(time.Now().Unix(), 0) +} From d2d5285058df34710f5f4a0ac0d9ecc579b801bc Mon Sep 17 00:00:00 2001 From: akekeke Date: Mon, 5 Nov 2018 10:26:02 +0900 Subject: [PATCH 24/28] fixed typo --- internal/wallet/test_util.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/wallet/test_util.go b/internal/wallet/test_util.go index 237ea16..890fff8 100644 --- a/internal/wallet/test_util.go +++ b/internal/wallet/test_util.go @@ -48,7 +48,7 @@ func setupDB(t *testing.T) (db walletdb.DB, tearDownFunc func()) { return } -func setupWallet(t *testing.T) (tearDownFunc func(), w Wallet, db walletd.DB) { +func setupWallet(t *testing.T) (tearDownFunc func(), w Wallet, db walletdb.DB) { assert := assert.New(t) db, deleteDB := setupDB(t) From 8b77ef5f738b3323d84b43d87681f1d5b88d4afd Mon Sep 17 00:00:00 2001 From: Junji Watanabe Date: Mon, 5 Nov 2018 13:24:30 +0900 Subject: [PATCH 25/28] fix wallet creation (#49) --- internal/wallet/address.go | 25 ++-- internal/wallet/address_test.go | 16 ++- internal/wallet/test_util.go | 25 ++-- internal/wallet/utxo.go | 8 +- internal/wallet/utxo_test.go | 21 ++-- internal/wallet/wallet.go | 194 ++++++++++++++++++-------------- internal/wallet/wallet_test.go | 36 ++++-- 7 files changed, 175 insertions(+), 150 deletions(-) diff --git a/internal/wallet/address.go b/internal/wallet/address.go index 5377ccc..b7e953a 100644 --- a/internal/wallet/address.go +++ b/internal/wallet/address.go @@ -8,7 +8,7 @@ import ( ) func (w *wallet) NewPubkey() (pub *btcec.PublicKey, err error) { - mAddrs, err := w.newAddress(waddrmgr.KeyScopeBIP0084, w.privatePassphrase, uint32(1), uint32(1)) + mAddrs, err := w.newAddress(uint32(1)) if err != nil { return nil, err } @@ -25,22 +25,11 @@ func (w *wallet) NewWitnessPubkeyScript() (pkScript []byte, err error) { return script.P2WPKHpkScript(pub) } -// NewAddress returns a new ManagedAddress for a given scope and account number. -// NOTE: this function callsNextExternalAddresses to generate a ManagadAdddress. -func (w *wallet) newAddress(scope waddrmgr.KeyScope, privPass []byte, - account uint32, numAddresses uint32) ([]waddrmgr.ManagedAddress, error) { - // unlock Manager - err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { - ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) - e := w.Manager().Unlock(ns, privPass) - return e - }) - if err != nil { - return nil, err - } - - // get ScopedKeyManager - scopedMgr, err := w.Manager().FetchScopedKeyManager(scope) +// NewAddress returns a new ManagedAddress +// NOTE: this function calls NextExternalAddresses to generate a ManagadAdddress. +func (w *wallet) newAddress( + numAddresses uint32) ([]waddrmgr.ManagedAddress, error) { + scopedMgr, err := w.manager.FetchScopedKeyManager(waddrmgrKeyScope) if err != nil { return nil, err } @@ -49,7 +38,7 @@ func (w *wallet) newAddress(scope waddrmgr.KeyScope, privPass []byte, err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) var e error - addrs, e = scopedMgr.NextExternalAddresses(ns, account, numAddresses) + addrs, e = scopedMgr.NextExternalAddresses(ns, w.account, numAddresses) return e }) if err != nil { diff --git a/internal/wallet/address_test.go b/internal/wallet/address_test.go index 0b5514f..b00c5f1 100644 --- a/internal/wallet/address_test.go +++ b/internal/wallet/address_test.go @@ -3,19 +3,25 @@ package wallet import ( "testing" - "github.com/btcsuite/btcwallet/waddrmgr" "github.com/stretchr/testify/assert" ) func TestNewPubkey(t *testing.T) { - tearDownFunc, wallet, _ := setupWallet(t) + wallet, tearDownFunc := setupWallet(t) defer tearDownFunc() - wallet.CreateAccount(waddrmgr.KeyScopeBIP0084, testAccountName, testPrivPass) - pub, _ := wallet.NewPubkey() + pub, err := wallet.NewPubkey() + assert.Nil(t, err) assert.NotNil(t, pub) } -func TestWitnessNewPubkeyScript(t *testing.T) { +func TestNewWitnessPubkeyScript(t *testing.T) { + wallet, tearDownFunc := setupWallet(t) + defer tearDownFunc() + + pkScript, err := wallet.NewWitnessPubkeyScript() + + assert.Nil(t, err) + assert.NotEmpty(t, pkScript) } diff --git a/internal/wallet/test_util.go b/internal/wallet/test_util.go index 890fff8..8f69d42 100644 --- a/internal/wallet/test_util.go +++ b/internal/wallet/test_util.go @@ -3,7 +3,6 @@ package wallet import ( "io/ioutil" "os" - "path/filepath" "testing" "github.com/btcsuite/btcd/chaincfg" @@ -19,10 +18,9 @@ var ( 0xb6, 0xb8, 0x39, 0xbe, 0xd9, 0xfd, 0x21, 0x6a, 0x6c, 0x03, 0xce, 0xe2, 0x2c, 0x84, } - testPubPass = []byte("_DJr{fL4H0O}*-0\n:V1izc)(6BomK") - testPrivPass = []byte("81lUHXnOMZ@?XXd7O9xyDIWIbXX-lj") - testWalletName = "testwallet" - testAccountName = "testy" + testPubPass = []byte("_DJr{fL4H0O}*-0\n:V1izc)(6BomK") + testPrivPass = []byte("81lUHXnOMZ@?XXd7O9xyDIWIbXX-lj") + testWalletName = "testwallet.db" ) func setupDB(t *testing.T) (db walletdb.DB, tearDownFunc func()) { @@ -31,11 +29,7 @@ func setupDB(t *testing.T) (db walletdb.DB, tearDownFunc func()) { dbDirPath, err := ioutil.TempDir("", "testdb") assert.Nil(err) - dbPath := filepath.Join(dbDirPath, testWalletName+".db") - err = os.MkdirAll(dbDirPath, 0700) - assert.Nil(err) - - db, err = walletdb.Create("bdb", dbPath) + db, err = createDB(dbDirPath, testWalletName) assert.Nil(err) tearDownFunc = func() { @@ -48,21 +42,18 @@ func setupDB(t *testing.T) (db walletdb.DB, tearDownFunc func()) { return } -func setupWallet(t *testing.T) (tearDownFunc func(), w Wallet, db walletdb.DB) { +func setupWallet(t *testing.T) (*wallet, func()) { assert := assert.New(t) db, deleteDB := setupDB(t) - err := Create(db, testNetParams, testSeed, testPubPass, testPrivPass) - assert.Nil(err) - - w, err = Open(db, testPubPass, testPrivPass, testNetParams) + w, err := create(db, testNetParams, testSeed, testPubPass, testPrivPass) assert.Nil(err) - tearDownFunc = func() { + tearDownFunc := func() { err = w.Close() assert.Nil(err) deleteDB() } - return + return w, tearDownFunc } diff --git a/internal/wallet/utxo.go b/internal/wallet/utxo.go index d5303a9..422f3ff 100644 --- a/internal/wallet/utxo.go +++ b/internal/wallet/utxo.go @@ -25,10 +25,10 @@ func (w *wallet) ListUnspent() (utxos []*Utxo, err error) { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) - syncBlock := w.Manager().SyncedTo() + syncBlock := w.manager.SyncedTo() // filter := len(addresses) != 0 - unspent, e := w.TxStore().UnspentOutputs(txmgrNs) + unspent, e := w.txStore.UnspentOutputs(txmgrNs) if e != nil { return e } @@ -88,7 +88,7 @@ func (w *wallet) credit2ListUnspentResult( return nil // maybe? } if len(addrs) > 0 { - smgr, acct, err := w.Manager().AddrAccount(addrmgrNs, addrs[0]) + smgr, acct, err := w.manager.AddrAccount(addrmgrNs, addrs[0]) if err == nil { s, err := smgr.AccountName(addrmgrNs, acct) if err == nil { @@ -158,7 +158,7 @@ scSwitch: spendable = true case txscript.MultiSigTy: for _, a := range addrs { - _, err := w.Manager().Address(addrmgrNs, a) + _, err := w.manager.Address(addrmgrNs, a) if err == nil { continue } diff --git a/internal/wallet/utxo_test.go b/internal/wallet/utxo_test.go index 73885e6..2bb23c6 100644 --- a/internal/wallet/utxo_test.go +++ b/internal/wallet/utxo_test.go @@ -33,9 +33,10 @@ var ( // TestListUnspent() will also need to check different types of scripts func TestListUnspent(t *testing.T) { - tearDownFunc, w, db := setupWallet(t) + w, tearDownFunc := setupWallet(t) defer tearDownFunc() + db := w.db _ = fakeUtxos(w, db) err := walletdb.View(db, func(tx walletdb.ReadTx) error { @@ -57,7 +58,7 @@ func TestListUnspent(t *testing.T) { } // fakeUtxos creates fake transactions, and inserts them into the provided wallet's db -func fakeUtxos(w Wallet, db walletdb.DB) []wtxmgr.Credit { +func fakeUtxos(w *wallet, db walletdb.DB) []wtxmgr.Credit { tx := spendOutput(&chainhash.Hash{}, 0, 10e8) rec, err := wtxmgr.NewTxRecordFromMsgTx(tx, timeNow()) if err != nil { @@ -79,23 +80,23 @@ func fakeUtxos(w Wallet, db walletdb.DB) []wtxmgr.Credit { return errors.New("missing transaction manager namespace") } - _ = w.TxStore().InsertTx(wtxmgrBucket, fakeTxRecordA, nil) - _ = w.TxStore().AddCredit(wtxmgrBucket, fakeTxRecordA, nil, 0, false) + _ = w.txStore.InsertTx(wtxmgrBucket, fakeTxRecordA, nil) + _ = w.txStore.AddCredit(wtxmgrBucket, fakeTxRecordA, nil, 0, false) fmt.Printf("created fake credit A\n") // Insert a second transaction which spends the output, and creates two // outputs. Mark the second one (5 BTC) as wallet change. - _ = w.TxStore().InsertTx(wtxmgrBucket, fakeTxRecordB, nil) - _ = w.TxStore().AddCredit(wtxmgrBucket, fakeTxRecordB, nil, 1, true) + _ = w.txStore.InsertTx(wtxmgrBucket, fakeTxRecordB, nil) + _ = w.txStore.AddCredit(wtxmgrBucket, fakeTxRecordB, nil, 1, true) fmt.Printf("created fake credit B\n") // // Mine each transaction in a block at height 100. - _ = w.TxStore().InsertTx(wtxmgrBucket, fakeTxRecordA, &exampleBlock100) - _ = w.TxStore().InsertTx(wtxmgrBucket, fakeTxRecordB, &exampleBlock100) + _ = w.txStore.InsertTx(wtxmgrBucket, fakeTxRecordA, &exampleBlock100) + _ = w.txStore.InsertTx(wtxmgrBucket, fakeTxRecordB, &exampleBlock100) fmt.Printf("mined each transaction\n") // Print the one confirmation balance. - bal, e := w.TxStore().Balance(wtxmgrBucket, 1, 100) + bal, e := w.txStore.Balance(wtxmgrBucket, 1, 100) if e != nil { fmt.Println(e) return nil @@ -103,7 +104,7 @@ func fakeUtxos(w Wallet, db walletdb.DB) []wtxmgr.Credit { fmt.Println(bal) // Fetch unspent outputs. - utxos, e = w.TxStore().UnspentOutputs(wtxmgrBucket) + utxos, e = w.txStore.UnspentOutputs(wtxmgrBucket) if e != nil { fmt.Println(e) } diff --git a/internal/wallet/wallet.go b/internal/wallet/wallet.go index c48f8cc..750cd0d 100644 --- a/internal/wallet/wallet.go +++ b/internal/wallet/wallet.go @@ -17,46 +17,54 @@ import ( // Wallet is an interface that provides access to manage pubkey addresses and // sign scripts of managed addressesc using private key. It also manags utxos. type Wallet interface { - CreateAccount( - scope waddrmgr.KeyScope, name string, privPass []byte, - ) (account uint32, err error) - NewPubkey() (*btcec.PublicKey, error) NewWitnessPubkeyScript() (pkScript []byte, err error) ListUnspent() (utxos []*Utxo, err error) - - Manager() *waddrmgr.Manager - TxStore() *wtxmgr.Store Close() error } // Namespace bucket keys. var ( waddrmgrNamespaceKey = []byte("waddrmgr") + waddrmgrKeyScope = waddrmgr.KeyScopeBIP0084 wtxmgrNamespaceKey = []byte("wtxmgr") ) +const accountName = "dlc" + // Wallet is hierarchical deterministic wallet type wallet struct { params *chaincfg.Params publicPassphrase []byte privatePassphrase []byte // rpc *rpc.BtcRPC - db walletdb.DB manager *waddrmgr.Manager txStore *wtxmgr.Store + account uint32 } // CreateWallet returns a new Wallet, also creates db where wallet resides // TODO: separate db creation and Manager creation // TODO: create loader script for wallet init -func CreateWallet(params *chaincfg.Params, seed, pubPass, privPass []byte, +// TODO: add prompts for dbDirPath, walletDBname +func CreateWallet( + params *chaincfg.Params, + seed, pubPass, privPass []byte, dbFilePath, walletName string) (Wallet, error) { - // TODO: add prompts for dbDirPath, walletDBname - // Create a new db at specified path + dbDirPath := filepath.Join(dbFilePath, params.Name) - dbPath := filepath.Join(dbDirPath, walletName+".db") + db, err := createDB(dbDirPath, walletName+".db") + if err != nil { + return nil, err + } + + return create(db, params, seed, pubPass, privPass) +} + +// createDB creates a new db at specified path +func createDB(dbDirPath, dbname string) (walletdb.DB, error) { + dbPath := filepath.Join(dbDirPath, dbname) exists, err := fileExists(dbPath) if err != nil { return nil, err @@ -74,20 +82,40 @@ func CreateWallet(params *chaincfg.Params, seed, pubPass, privPass []byte, return nil, err } - // Create Wallet struct - err = Create(db, params, seed, pubPass, privPass) + return db, err +} + +// Create creates an new wallet, writing it to the passed in db. +func create( + db walletdb.DB, + params *chaincfg.Params, + seed, pubPass, privPass []byte) (*wallet, error) { + + err := createManagers(db, seed, pubPass, privPass, params) + if err != nil { + return nil, err + } + + _, err = createAccount(db, privPass, pubPass, params) + if err != nil { + return nil, err + } + + w, err := open(db, pubPass, params) if err != nil { return nil, err } - // Open the wallet - return Open(db, pubPass, privPass, params) + return w, nil } -// Create creates an new wallet, writing it to the passed in db. -func Create(db walletdb.DB, params *chaincfg.Params, seed, pubPass, - privPass []byte) error { - err := walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { +// createManagers create address manager and tx manager +func createManagers( + db walletdb.DB, + seed, pubPass, privPass []byte, + params *chaincfg.Params, +) error { + return walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { addrmgrNs, e := tx.CreateTopLevelBucket(waddrmgrNamespaceKey) if e != nil { return e @@ -108,57 +136,95 @@ func Create(db walletdb.DB, params *chaincfg.Params, seed, pubPass, e = wtxmgr.Create(txmgrNs) return e }) - - return err } -// Open loads a wallet from the passed db and public pass phrase. -func Open(db walletdb.DB, pubPass, privPass []byte, - params *chaincfg.Params) (Wallet, error) { - err := walletdb.View(db, func(tx walletdb.ReadTx) error { - waddrmgrBucket := tx.ReadBucket(waddrmgrNamespaceKey) - if waddrmgrBucket == nil { - return errors.New("missing address manager namespace") +// createAccount creates a new account in ScopedKeyManagar of scope +func createAccount( + db walletdb.DB, privPass, pubPass []byte, params *chaincfg.Params, +) (account uint32, err error) { + err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { + ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) + addrMgr, e := waddrmgr.Open(ns, pubPass, params) + if e != nil { + return e } - wtxmgrBucket := tx.ReadBucket(wtxmgrNamespaceKey) - if wtxmgrBucket == nil { - return errors.New("missing transaction manager namespace") + + e = addrMgr.Unlock(ns, privPass) + if e != nil { + return e } - return nil + + scopedMgr, e := addrMgr.FetchScopedKeyManager(waddrmgrKeyScope) + if e != nil { + return e + } + + account, e = scopedMgr.NewAccount(ns, accountName) + return e }) - if err != nil { - return nil, err - } + return account, err +} + +// Open loads a wallet from the passed db and public pass phrase. +func Open( + db walletdb.DB, pubPass []byte, params *chaincfg.Params, +) (Wallet, error) { + return open(db, pubPass, params) +} +// open is an implementation of Open +func open( + db walletdb.DB, pubPass []byte, params *chaincfg.Params, +) (*wallet, error) { // TODO: Perform wallet upgrades/updates if necessary? // Open database abstraction instances var ( addrMgr *waddrmgr.Manager txMgr *wtxmgr.Store + account uint32 ) - err = walletdb.View(db, func(tx walletdb.ReadTx) error { + err := walletdb.View(db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) + if addrmgrNs == nil { + return errors.New("missing address manager namespace") + } txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) + if txmgrNs == nil { + return errors.New("missing transaction manager namespace") + } + var e error addrMgr, e = waddrmgr.Open(addrmgrNs, pubPass, params) if e != nil { return e } + scopedMgr, e := addrMgr.FetchScopedKeyManager(waddrmgrKeyScope) + if e != nil { + return e + } + account, e = scopedMgr.LookupAccount(addrmgrNs, accountName) + if e != nil { + return e + } txMgr, e = wtxmgr.Open(txmgrNs, params) - return e + if e != nil { + return e + } + + return nil }) if err != nil { return nil, err } w := &wallet{ - params: params, - publicPassphrase: pubPass, - privatePassphrase: privPass, - db: db, - manager: addrMgr, - txStore: txMgr, + params: params, + publicPassphrase: pubPass, + db: db, + manager: addrMgr, + txStore: txMgr, + account: account, } return w, nil @@ -170,46 +236,6 @@ func (w *wallet) Close() error { return nil } -// CreateAccount creates a new account in ScopedKeyManagar of scope -func (w *wallet) CreateAccount(scope waddrmgr.KeyScope, name string, - privPass []byte) (uint32, error) { - // unlock Manager - err := walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { - ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) - e := w.manager.Unlock(ns, privPass) - return e - }) - if err != nil { - return 0, err - } - - scopedMgr, err := w.manager.FetchScopedKeyManager(scope) - if err != nil { - return 0, err - } - - var account uint32 - err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { - ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) - var e error - account, e = scopedMgr.NewAccount(ns, name) - return e - }) - if err != nil { - return 0, err - } - - return account, nil -} - -func (w *wallet) Manager() *waddrmgr.Manager { - return w.manager -} - -func (w *wallet) TxStore() *wtxmgr.Store { - return w.txStore -} - // Helper function, TODO: move somewhere else? func fileExists(filePath string) (bool, error) { _, err := os.Stat(filePath) diff --git a/internal/wallet/wallet_test.go b/internal/wallet/wallet_test.go index b1e7126..231b130 100644 --- a/internal/wallet/wallet_test.go +++ b/internal/wallet/wallet_test.go @@ -5,7 +5,6 @@ import ( "os" "testing" - "github.com/btcsuite/btcwallet/waddrmgr" "github.com/stretchr/testify/assert" ) @@ -16,21 +15,34 @@ func TestCreateWallet(t *testing.T) { w, err := CreateWallet( testNetParams, testSeed, testPubPass, testPrivPass, dirName, testWalletName) - assert := assert.New(t) - assert.Nil(err) - assert.NotNil(w) + // assertions + assert.Nil(t, err) + _, ok := w.(Wallet) + assert.True(t, ok) } -// TODO: add tests for Create(...) and Open(...) - -func TestCreateAccount(t *testing.T) { - tearDownFunc, wallet, _ := setupWallet(t) +func TestOpen(t *testing.T) { + _w, tearDownFunc := setupWallet(t) defer tearDownFunc() - expectedAccountNumber := uint32(1) + // close wallet + _w.Close() + + // open wallet + db := _w.db + pubPass := _w.publicPassphrase + params := _w.params + w, err := open(db, pubPass, params) + + // assertions + assert := assert.New(t) + assert.Nil(err) - account, _ := wallet.CreateAccount( - waddrmgr.KeyScopeBIP0084, testAccountName, testPrivPass) + // test if the oepned account is the same with the created one + assert.Equal(_w.account, w.account) - assert.Equal(t, expectedAccountNumber, account) + // test if it satisfies Wallet interface + var W Wallet = w + _, ok := W.(Wallet) + assert.True(ok) } From 87c24db2f44cf0c087934d0960530fb1d42d287e Mon Sep 17 00:00:00 2001 From: akekeke Date: Mon, 5 Nov 2018 14:01:14 +0900 Subject: [PATCH 26/28] utxo is not pointer anymore --- internal/wallet/utxo.go | 6 +++--- internal/wallet/wallet.go | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/wallet/utxo.go b/internal/wallet/utxo.go index 422f3ff..87b2bf1 100644 --- a/internal/wallet/utxo.go +++ b/internal/wallet/utxo.go @@ -19,8 +19,8 @@ type Utxo = btcjson.ListUnspentResult // Only utxos with address contained the param addresses will be considered. // If param addresses is empty, all addresses are considered and there is no // filter -func (w *wallet) ListUnspent() (utxos []*Utxo, err error) { - var results []*btcjson.ListUnspentResult +func (w *wallet) ListUnspent() (utxos []Utxo, err error) { + var results []btcjson.ListUnspentResult err = walletdb.View(w.db, func(tx walletdb.ReadTx) error { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) @@ -38,7 +38,7 @@ func (w *wallet) ListUnspent() (utxos []*Utxo, err error) { output := unspent[i] result := w.credit2ListUnspentResult(output, syncBlock, addrmgrNs) // TODO: result might return nil... catch that nil? - results = append(results, result) + results = append(results, *result) } return nil }) diff --git a/internal/wallet/wallet.go b/internal/wallet/wallet.go index 750cd0d..0e9fc25 100644 --- a/internal/wallet/wallet.go +++ b/internal/wallet/wallet.go @@ -19,7 +19,7 @@ import ( type Wallet interface { NewPubkey() (*btcec.PublicKey, error) NewWitnessPubkeyScript() (pkScript []byte, err error) - ListUnspent() (utxos []*Utxo, err error) + ListUnspent() (utxos []Utxo, err error) Close() error } From 98a9c4172b8853b50fcecbe626597ef304a5c90b Mon Sep 17 00:00:00 2001 From: akekeke Date: Mon, 5 Nov 2018 14:38:29 +0900 Subject: [PATCH 27/28] cleaned up --- internal/wallet/utxo.go | 55 ++++------------------------------------- 1 file changed, 5 insertions(+), 50 deletions(-) diff --git a/internal/wallet/utxo.go b/internal/wallet/utxo.go index 87b2bf1..1cd9ea9 100644 --- a/internal/wallet/utxo.go +++ b/internal/wallet/utxo.go @@ -25,7 +25,6 @@ func (w *wallet) ListUnspent() (utxos []Utxo, err error) { addrmgrNs := tx.ReadBucket(waddrmgrNamespaceKey) txmgrNs := tx.ReadBucket(wtxmgrNamespaceKey) - syncBlock := w.manager.SyncedTo() // filter := len(addresses) != 0 unspent, e := w.txStore.UnspentOutputs(txmgrNs) @@ -36,7 +35,7 @@ func (w *wallet) ListUnspent() (utxos []Utxo, err error) { // utxos = make([]*btcjson.ListUnspentResult, 0, len(unspent)) for i := range unspent { output := unspent[i] - result := w.credit2ListUnspentResult(output, syncBlock, addrmgrNs) + result := w.credit2ListUnspentResult(output, addrmgrNs) // TODO: result might return nil... catch that nil? results = append(results, *result) } @@ -48,9 +47,10 @@ func (w *wallet) ListUnspent() (utxos []Utxo, err error) { func (w *wallet) credit2ListUnspentResult( c wtxmgr.Credit, - syncBlock waddrmgr.BlockStamp, addrmgrNs walletdb.ReadBucket) *btcjson.ListUnspentResult { + syncBlock := w.manager.SyncedTo() + // TODO: add minconf, maxconf params confs := confirms(c.Height, syncBlock.Height) // // Outputs with fewer confirmations than the minimum or more @@ -70,45 +70,7 @@ func (w *wallet) credit2ListUnspentResult( } } - // TODO: exclude locked outputs from result set. - // Exclude locked outputs from the result set. - - // Lookup the associated account for the output. Use the - // default account name in case there is no associated account - // for some reason, although this should never happen. - // - // This will be unnecessary once transactions and outputs are - // grouped under the associated account in the db. - defaultAccountName := "default" - acctName := defaultAccountName - sc, addrs, _, err := txscript.ExtractPkScriptAddrs( - c.PkScript, w.params) - if err != nil { - // continue - return nil // maybe? - } - if len(addrs) > 0 { - smgr, acct, err := w.manager.AddrAccount(addrmgrNs, addrs[0]) - if err == nil { - s, err := smgr.AccountName(addrmgrNs, acct) - if err == nil { - acctName = s - } - } - } - - // not including this part bc this func will assume there is no filter - // if filter { - // for _, addr := range addrs { - // _, ok := addresses[addr.EncodeAddress()] - // if ok { - // goto include - // } - // } - // // continue - // return nil // maybe? - // } - // include: + acctName := accountName result := &btcjson.ListUnspentResult{ TxID: c.OutPoint.Hash.String(), @@ -117,14 +79,7 @@ func (w *wallet) credit2ListUnspentResult( ScriptPubKey: hex.EncodeToString(c.PkScript), Amount: c.Amount.ToBTC(), Confirmations: int64(confs), - Spendable: w.isSpendable(sc, addrs, addrmgrNs), - } - - // BUG: this should be a JSON array so that all - // addresses can be included, or removed (and the - // caller extracts addresses from the pkScript). - if len(addrs) > 0 { - result.Address = addrs[0].EncodeAddress() + Spendable: true, } return result From c005e892e67404dba5fa21458f88f08be2152267 Mon Sep 17 00:00:00 2001 From: Jwata Date: Mon, 5 Nov 2018 14:50:49 +0900 Subject: [PATCH 28/28] fix lint errors --- internal/wallet/address.go | 10 +++++----- internal/wallet/wallet.go | 20 +++++++------------- 2 files changed, 12 insertions(+), 18 deletions(-) diff --git a/internal/wallet/address.go b/internal/wallet/address.go index b7e953a..7217716 100644 --- a/internal/wallet/address.go +++ b/internal/wallet/address.go @@ -8,11 +8,11 @@ import ( ) func (w *wallet) NewPubkey() (pub *btcec.PublicKey, err error) { - mAddrs, err := w.newAddress(uint32(1)) + mAddr, err := w.newAddress() if err != nil { return nil, err } - pub = (mAddrs[0].(waddrmgr.ManagedPubKeyAddress)).PubKey() + pub = (mAddr.(waddrmgr.ManagedPubKeyAddress)).PubKey() return pub, err } @@ -27,13 +27,13 @@ func (w *wallet) NewWitnessPubkeyScript() (pkScript []byte, err error) { // NewAddress returns a new ManagedAddress // NOTE: this function calls NextExternalAddresses to generate a ManagadAdddress. -func (w *wallet) newAddress( - numAddresses uint32) ([]waddrmgr.ManagedAddress, error) { +func (w *wallet) newAddress() (waddrmgr.ManagedAddress, error) { scopedMgr, err := w.manager.FetchScopedKeyManager(waddrmgrKeyScope) if err != nil { return nil, err } + var numAddresses uint32 = 1 var addrs []waddrmgr.ManagedAddress err = walletdb.Update(w.db, func(tx walletdb.ReadWriteTx) error { ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) @@ -45,5 +45,5 @@ func (w *wallet) newAddress( return nil, err } - return addrs, nil + return addrs[0], nil } diff --git a/internal/wallet/wallet.go b/internal/wallet/wallet.go index 0e9fc25..fd01cfd 100644 --- a/internal/wallet/wallet.go +++ b/internal/wallet/wallet.go @@ -34,9 +34,8 @@ const accountName = "dlc" // Wallet is hierarchical deterministic wallet type wallet struct { - params *chaincfg.Params - publicPassphrase []byte - privatePassphrase []byte + params *chaincfg.Params + publicPassphrase []byte // rpc *rpc.BtcRPC db walletdb.DB manager *waddrmgr.Manager @@ -96,7 +95,7 @@ func create( return nil, err } - _, err = createAccount(db, privPass, pubPass, params) + err = createAccount(db, privPass, pubPass, params) if err != nil { return nil, err } @@ -140,9 +139,8 @@ func createManagers( // createAccount creates a new account in ScopedKeyManagar of scope func createAccount( - db walletdb.DB, privPass, pubPass []byte, params *chaincfg.Params, -) (account uint32, err error) { - err = walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { + db walletdb.DB, privPass, pubPass []byte, params *chaincfg.Params) error { + return walletdb.Update(db, func(tx walletdb.ReadWriteTx) error { ns := tx.ReadWriteBucket(waddrmgrNamespaceKey) addrMgr, e := waddrmgr.Open(ns, pubPass, params) if e != nil { @@ -159,10 +157,9 @@ func createAccount( return e } - account, e = scopedMgr.NewAccount(ns, accountName) + _, e = scopedMgr.NewAccount(ns, accountName) return e }) - return account, err } // Open loads a wallet from the passed db and public pass phrase. @@ -208,11 +205,8 @@ func open( return e } txMgr, e = wtxmgr.Open(txmgrNs, params) - if e != nil { - return e - } - return nil + return e }) if err != nil { return nil, err