From f9a740d3923e8b898cbeadfce0322eeff1f0ca78 Mon Sep 17 00:00:00 2001 From: Tadge Dryja Date: Wed, 24 Feb 2016 17:27:29 -0800 Subject: [PATCH] more stress testing stuff --- shell.go | 79 +++++++++++++++++++++++++++--- uspv/msghandler.go | 1 - uspv/sortsignsend.go | 111 ++++++++++++++++++++++++++++++++++++++----- uspv/utxodb.go | 28 +++++++---- 4 files changed, 189 insertions(+), 30 deletions(-) diff --git a/shell.go b/shell.go index a1b85d9a..2ec9a50b 100644 --- a/shell.go +++ b/shell.go @@ -5,6 +5,7 @@ import ( "fmt" "log" "os" + "sort" "strconv" "strings" @@ -147,6 +148,13 @@ func Shellparse(cmdslice []string) error { } return nil } + if cmd == "sweep" { + err = Sweep(args) + if err != nil { + fmt.Printf("sweep error: %s\n", err) + } + return nil + } if cmd == "txs" { err = Txs(args) if err != nil { @@ -202,10 +210,17 @@ func Bal(args []string) error { return fmt.Errorf("Can't get balance, spv connection broken") } fmt.Printf(" ----- Account Balance ----- \n") - allUtxos, err := SCon.TS.GetAllUtxos() + rawUtxos, err := SCon.TS.GetAllUtxos() if err != nil { return err } + var allUtxos uspv.SortableUtxoSlice + for _, utxo := range rawUtxos { + allUtxos = append(allUtxos, *utxo) + } + // smallest and unconfirmed last (because it's reversed) + sort.Sort(sort.Reverse(allUtxos)) + var score, confScore int64 for i, u := range allUtxos { fmt.Printf("\tutxo %d height %d %s key:%d amt %d", @@ -221,6 +236,10 @@ func Bal(args []string) error { } height, _ := SCon.TS.GetDBSyncHeight() + atx, err := SCon.TS.GetAllTxs() + + stxos, err := SCon.TS.GetAllStxos() + for i, a := range SCon.TS.Adrs { wa, err := btcutil.NewAddressWitnessPubKeyHash( a.PkhAdr.ScriptAddress(), Params) @@ -229,8 +248,9 @@ func Bal(args []string) error { } fmt.Printf("address %d %s OR %s\n", i, a.PkhAdr.String(), wa.String()) } - - fmt.Printf("Total known utxos: %d\n", len(allUtxos)) + fmt.Printf("Total known txs: %d\n", len(atx)) + fmt.Printf("Known utxos: %d\tPreviously spent txos: %d\n", + len(allUtxos), len(stxos)) fmt.Printf("Total coin: %d confirmed: %d\n", score, confScore) fmt.Printf("DB sync height: %d\n", height) return nil @@ -268,13 +288,62 @@ func Adr(args []string) error { return nil } +// Sweep sends every confirmed uxto in your wallet to an address. +// it does them all individually to there are a lot of txs generated. +// syntax: sweep adr +func Sweep(args []string) error { + if len(args) < 2 { + return fmt.Errorf("sweep syntax: sweep adr") + } + + adr, err := btcutil.DecodeAddress(args[0], SCon.TS.Param) + if err != nil { + fmt.Printf("error parsing %s as address\t", args[0]) + return err + } + numTxs, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return err + } + if numTxs < 1 { + return fmt.Errorf("can't send %d txs", numTxs) + } + + rawUtxos, err := SCon.TS.GetAllUtxos() + if err != nil { + return err + } + var allUtxos uspv.SortableUtxoSlice + for _, utxo := range rawUtxos { + allUtxos = append(allUtxos, *utxo) + } + // smallest and unconfirmed last (because it's reversed) + sort.Sort(sort.Reverse(allUtxos)) + + for i, u := range allUtxos { + if u.AtHeight != 0 { + err = SCon.SendOne(allUtxos[i], adr) + if err != nil { + return err + } + numTxs-- + if numTxs == 0 { + return nil + } + } + } + + fmt.Printf("spent all confirmed utxos; not enough by %d\n", numTxs) + + return nil +} + // Fan generates a bunch of fanout. Only for testing, can be expensive. // syntax: fan adr numOutputs valOutputs witty func Fan(args []string) error { if len(args) < 3 { return fmt.Errorf("fan syntax: fan adr numOutputs valOutputs") } - adr, err := btcutil.DecodeAddress(args[0], SCon.TS.Param) if err != nil { fmt.Printf("error parsing %s as address\t", args[0]) @@ -296,9 +365,7 @@ func Fan(args []string) error { adrs[i] = adr amts[i] = valOutputs + i } - return SCon.SendCoins(adrs, amts) - } // Send sends coins. diff --git a/uspv/msghandler.go b/uspv/msghandler.go index f7fcf582..64f7fce0 100644 --- a/uspv/msghandler.go +++ b/uspv/msghandler.go @@ -189,7 +189,6 @@ func (s *SPVCon) GetDataHandler(m *wire.MsgGetData) { log.Printf("error getting tx %s: %s", thing.Hash.String(), err.Error()) } - fmt.Printf(TxToString(tx)) s.outMsgQueue <- tx sent++ continue diff --git a/uspv/sortsignsend.go b/uspv/sortsignsend.go index c41d806d..de8e340e 100644 --- a/uspv/sortsignsend.go +++ b/uspv/sortsignsend.go @@ -68,12 +68,22 @@ func (s utxoSlice) Less(i, j int) bool { return bytes.Compare(ihash[:], jhash[:]) == -1 } -type utxoByAmt []Utxo +type SortableUtxoSlice []Utxo -// utxoByAmts get sorted by utxo value -func (s utxoByAmt) Len() int { return len(s) } -func (s utxoByAmt) Swap(i, j int) { s[i], s[j] = s[j], s[i] } -func (s utxoByAmt) Less(i, j int) bool { return s[i].Value < s[j].Value } +// utxoByAmts get sorted by utxo value. also put unconfirmed last +func (s SortableUtxoSlice) Len() int { return len(s) } +func (s SortableUtxoSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] } + +// height 0 means your lesser +func (s SortableUtxoSlice) Less(i, j int) bool { + if s[i].AtHeight == 0 { + return true + } + if s[j].AtHeight == 0 { + return false + } + return s[i].Value < s[j].Value +} func (s *SPVCon) NewOutgoingTx(tx *wire.MsgTx) error { txid := tx.TxSha() @@ -97,6 +107,84 @@ func (s *SPVCon) NewOutgoingTx(tx *wire.MsgTx) error { return nil } +func (s *SPVCon) SendOne(u Utxo, adr btcutil.Address) error { + // fixed fee + fee := int64(5000) + + sendAmt := u.Value - fee + tx := wire.NewMsgTx() // make new tx + // add single output + outAdrScript, err := txscript.PayToAddrScript(adr) + if err != nil { + return err + } + // make user specified txout and add to tx + txout := wire.NewTxOut(sendAmt, outAdrScript) + tx.AddTxOut(txout) + + var prevPKs []byte + if u.IsWit { + tx.Flags = 0x01 + wa, err := btcutil.NewAddressWitnessPubKeyHash( + s.TS.Adrs[u.KeyIdx].PkhAdr.ScriptAddress(), s.TS.Param) + prevPKs, err = txscript.PayToAddrScript(wa) + if err != nil { + return err + } + } else { // otherwise generate directly + prevPKs, err = txscript.PayToAddrScript( + s.TS.Adrs[u.KeyIdx].PkhAdr) + if err != nil { + return err + } + } + + tx.AddTxIn(wire.NewTxIn(&u.Op, prevPKs, nil)) + + var sig []byte + var wit [][]byte + hCache := txscript.CalcHashCache(tx, 0, txscript.SigHashAll) + + child, err := s.TS.rootPrivKey.Child(u.KeyIdx + hdkeychain.HardenedKeyStart) + + if err != nil { + return err + } + priv, err := child.ECPrivKey() + if err != nil { + return err + } + + // This is where witness based sighash types need to happen + // sign into stash + if u.IsWit { + wit, err = txscript.WitnessScript( + tx, hCache, 0, u.Value, tx.TxIn[0].SignatureScript, + txscript.SigHashAll, priv, true) + if err != nil { + return err + } + } else { + sig, err = txscript.SignatureScript( + tx, 0, tx.TxIn[0].SignatureScript, + txscript.SigHashAll, priv, true) + if err != nil { + return err + } + } + + // swap sigs into sigScripts in txins + + if sig != nil { + tx.TxIn[0].SignatureScript = sig + } + if wit != nil { + tx.TxIn[0].Witness = wit + tx.TxIn[0].SignatureScript = nil + } + return s.NewOutgoingTx(tx) +} + // SendCoins does send coins, but it's very rudimentary // wit makes it into p2wpkh. Which is not yet spendable. func (s *SPVCon) SendCoins(adrs []btcutil.Address, sendAmts []int64) error { @@ -111,18 +199,17 @@ func (s *SPVCon) SendCoins(adrs []btcutil.Address, sendAmts []int64) error { if err != nil { return err } - var allUtxos utxoByAmt + var allUtxos SortableUtxoSlice // start with utxos sorted by value. for _, utxo := range rawUtxos { score += utxo.Value allUtxos = append(allUtxos, *utxo) } + // smallest and unconfirmed last (because it's reversed) sort.Sort(sort.Reverse(allUtxos)) + // sort.Reverse(allUtxos) - for _, u := range allUtxos { - fmt.Printf("%d ", u.Value) - } for _, amt := range sendAmts { totalSend += amt } @@ -272,10 +359,8 @@ func (s *SPVCon) SendCoins(adrs []btcutil.Address, sendAmts []int64) error { } - fmt.Printf("tx: %s", TxToString(tx)) - buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize())) - tx.SerializeWitness(buf) - fmt.Printf("tx: %x\n", buf.Bytes()) + // fmt.Printf("tx: %s", TxToString(tx)) + // buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize())) // send it out on the wire. hope it gets there. // we should deal with rejects. Don't yet. diff --git a/uspv/utxodb.go b/uspv/utxodb.go index 4f1c1746..265ec315 100644 --- a/uspv/utxodb.go +++ b/uspv/utxodb.go @@ -373,8 +373,14 @@ func (ts *TxStore) Ingest(tx *wire.MsgTx, height int32) (uint32, error) { return hits, err } } - // also generate PKscripts for all addresses (maybe keep storing these?) - for _, adr := range ts.Adrs { + + // go through txouts, and then go through addresses to match + + // generate PKscripts for all addresses + wPKscripts := make([][]byte, len(ts.Adrs)) + aPKscripts := make([][]byte, len(ts.Adrs)) + + for i, adr := range ts.Adrs { // iterate through all our addresses // convert regular address to witness address. (split adrs later) wa, err := btcutil.NewAddressWitnessPubKeyHash( @@ -383,28 +389,30 @@ func (ts *TxStore) Ingest(tx *wire.MsgTx, height int32) (uint32, error) { return hits, err } - wPKscript, err := txscript.PayToAddrScript(wa) + wPKscripts[i], err = txscript.PayToAddrScript(wa) if err != nil { return hits, err } - aPKscript, err := txscript.PayToAddrScript(adr.PkhAdr) + aPKscripts[i], err = txscript.PayToAddrScript(adr.PkhAdr) if err != nil { return hits, err } + } - // iterate through all outputs of this tx, see if we gain - for i, out := range tx.TxOut { + // iterate through all outputs of this tx, see if we gain + for i, out := range tx.TxOut { + for j, ascr := range aPKscripts { // detect p2wpkh witBool := false - if bytes.Equal(out.PkScript, wPKscript) { + if bytes.Equal(out.PkScript, wPKscripts[j]) { witBool = true } - if bytes.Equal(out.PkScript, aPKscript) || witBool { // new utxo found + if bytes.Equal(out.PkScript, ascr) || witBool { // new utxo found var newu Utxo // create new utxo and copy into it newu.AtHeight = height - newu.KeyIdx = adr.KeyIdx + newu.KeyIdx = ts.Adrs[j].KeyIdx newu.Value = out.Value newu.IsWit = witBool // copy witness version from pkscript var newop wire.OutPoint @@ -417,7 +425,7 @@ func (ts *TxStore) Ingest(tx *wire.MsgTx, height int32) (uint32, error) { } nUtxoBytes = append(nUtxoBytes, b) hits++ - // break // keep looking! address re-use in same tx + break // txos can match only 1 script } } }