From dd1f2c00fe07e80c9429c9814dd8f34e8658ef9a Mon Sep 17 00:00:00 2001 From: Tadge Dryja Date: Mon, 22 Feb 2016 16:32:58 -0800 Subject: [PATCH] add fanout. fix boltdb weirdness. again with keys / values within the bolttx acting weird. it wasn't deleting utxos that had been spent by the ingested tx. it'd do the first 30 then stop. Deferred deletion and copied the serialized utxo. Not sure which of those fixed it. Maybe both. --- shell.go | 59 ++++++++++++++++++++++++++++++++++++++++++++ uspv/sortsignsend.go | 48 +++++++++++++++++++---------------- uspv/utxodb.go | 28 +++++++++++++-------- 3 files changed, 104 insertions(+), 31 deletions(-) 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 {