diff --git a/shell.go b/shell.go index 58bdc6a4..0e197e5a 100644 --- a/shell.go +++ b/shell.go @@ -140,6 +140,13 @@ func Shellparse(cmdslice []string) error { } return nil } + if cmd == "fan" { + err = Fan(args) + if err != nil { + fmt.Printf("fan error: %s\n", err) + } + return nil + } if cmd == "txs" { err = Txs(args) if err != nil { @@ -238,6 +245,14 @@ func Adr(args []string) error { } } } + if len(args) > 1 { + for i := 0; i < 1000; i++ { + _, err := SCon.TS.NewAdr() + if err != nil { + return err + } + } + } // always make one a, err := SCon.TS.NewAdr() @@ -250,6 +265,50 @@ func Adr(args []string) error { return nil } +// Fan generates a bunch of fanout. Only for testing, can be expensive. +// syntax: fan numOutputs valOutputs witty +func Fan(args []string) error { + if len(args) < 3 { + return fmt.Errorf("fan syntax: fan numOutputs valOutputs witty") + } + numOutputs, err := strconv.ParseInt(args[0], 10, 64) + if err != nil { + return err + } + valOutputs, err := strconv.ParseInt(args[1], 10, 64) + if err != nil { + return err + } + wittynum, err := strconv.ParseInt(args[2], 10, 64) + if err != nil { + return err + } + + adrs := make([]btcutil.Address, numOutputs) + amts := make([]int64, numOutputs) + + oAdr, err := SCon.TS.NewAdr() + if err != nil { + return err + } + for i := int64(0); i < numOutputs; i++ { + if wittynum != 0 { + wAdr, err := btcutil.NewAddressWitnessPubKeyHash( + oAdr.ScriptAddress(), SCon.TS.Param) + if err != nil { + return err + } + adrs[i] = wAdr + } else { + adrs[i] = oAdr + } + amts[i] = valOutputs + i + } + + return SCon.SendCoins(adrs, amts) + +} + // Send sends coins. func Send(args []string) error { if SCon.RBytes == 0 { diff --git a/uspv/sortsignsend.go b/uspv/sortsignsend.go index 69e81933..1d294483 100644 --- a/uspv/sortsignsend.go +++ b/uspv/sortsignsend.go @@ -160,33 +160,39 @@ func (s *SPVCon) SendCoins(adrs []btcutil.Address, sendAmts []int64) error { } tx.AddTxIn(wire.NewTxIn(&utxo.Op, prevPKs, nil)) nokori -= utxo.Value - fee = EstFee(tx, satPerByte) - if nokori < -fee { // done adding utxos: nokori below negative est. fee - break + // if nokori is positive, don't bother checking fee yet. + if nokori < 0 { + fee = EstFee(tx, satPerByte) + if nokori < -fee { // done adding utxos: nokori below negative est. fee + break + } } } // see if there's enough left to also add a change output - if nokori < -dustCutoff { - changeOld, err := s.TS.NewAdr() // change is witnessy - if err != nil { - return err - } - changeAdr, err := btcutil.NewAddressWitnessPubKeyHash( - changeOld.ScriptAddress(), s.TS.Param) - if err != nil { - return err - } - changeScript, err := txscript.PayToAddrScript(changeAdr) - if err != nil { - return err - } + changeOld, err := s.TS.NewAdr() // change is witnessy + if err != nil { + return err + } + changeAdr, err := btcutil.NewAddressWitnessPubKeyHash( + changeOld.ScriptAddress(), s.TS.Param) + if err != nil { + return err + } - changeOut := wire.NewTxOut(0, changeScript) - tx.AddTxOut(changeOut) - fee = EstFee(tx, satPerByte) - changeOut.Value = -(nokori + fee) + changeScript, err := txscript.PayToAddrScript(changeAdr) + if err != nil { + return err + } + + changeOut := wire.NewTxOut(0, changeScript) + tx.AddTxOut(changeOut) + fee = EstFee(tx, satPerByte) + changeOut.Value = -(nokori + fee) + if changeOut.Value < dustCutoff { + // remove last output (change) : not worth it + tx.TxOut = tx.TxOut[:len(tx.TxOut)-1] } // sort utxos on the input side. use this instead of txsort diff --git a/uspv/utxodb.go b/uspv/utxodb.go index 7c18626d..f5cb37e5 100644 --- a/uspv/utxodb.go +++ b/uspv/utxodb.go @@ -353,7 +353,6 @@ func (ts *TxStore) PopulateAdrs(lastKey uint32) error { func (ts *TxStore) Ingest(tx *wire.MsgTx, height int32) (uint32, error) { var hits uint32 var err error - var spentOPs [][]byte var nUtxoBytes [][]byte // tx has been OK'd by SPV; check tx sanity @@ -366,13 +365,14 @@ func (ts *TxStore) Ingest(tx *wire.MsgTx, height int32) (uint32, error) { // note that you can't check signatures; this is SPV. // 0 conf SPV means pretty much nothing. Anyone can say anything. + spentOPs := make([][]byte, len(tx.TxIn)) // before entering into db, serialize all inputs of the ingested tx - for _, txin := range tx.TxIn { + for i, txin := range tx.TxIn { nOP, err := outPointToBytes(&txin.PreviousOutPoint) if err != nil { return hits, err } - spentOPs = append(spentOPs, nOP) + spentOPs[i] = nOP } // also generate PKscripts for all addresses (maybe keep storing these?) for _, adr := range ts.Adrs { @@ -419,7 +419,7 @@ func (ts *TxStore) Ingest(tx *wire.MsgTx, height int32) (uint32, error) { } nUtxoBytes = append(nUtxoBytes, b) hits++ - break // only one match + // break // keep looking! address re-use in same tx } } } @@ -437,24 +437,25 @@ func (ts *TxStore) Ingest(tx *wire.MsgTx, height int32) (uint32, error) { // first see if we lose utxos // iterate through duffel bag and look for matches // this makes us lose money, which is regrettable, but we need to know. + + var delOPs [][]byte + fmt.Printf("%d nOP to iterate over\n", len(spentOPs)) for _, nOP := range spentOPs { duf.ForEach(func(k, v []byte) error { if bytes.Equal(k, nOP) { // matched, we lost utxo // do all this just to figure out value we lost x := make([]byte, len(k)+len(v)) copy(x, k) + // mark utxos for deletion + delOPs = append(delOPs, x) copy(x[len(k):], v) lostTxo, err := UtxoFromBytes(x) if err != nil { return err } hits++ - // then delete the utxo from duf, save to old - err = duf.Delete(k) - if err != nil { - return err - } - // after deletion, save stxo to old bucket + + // after marking for deletion, save stxo to old bucket var st Stxo // generate spent txo st.Utxo = lostTxo // assign outpoint st.SpendHeight = height // spent at height @@ -480,6 +481,13 @@ func (ts *TxStore) Ingest(tx *wire.MsgTx, height int32) (uint32, error) { } return nil // no match }) + // delete after done with foreach + for _, k := range delOPs { + err = duf.Delete(k) + if err != nil { + return err + } + } } // done losing utxos, next gain utxos // next add all new utxos to db, this is quick as the work is above for _, ub := range nUtxoBytes {