dd1f2c00fe
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.
351 lines
9.4 KiB
Go
351 lines
9.4 KiB
Go
package uspv
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"log"
|
|
"sort"
|
|
|
|
"github.com/btcsuite/btcd/txscript"
|
|
"github.com/btcsuite/btcd/wire"
|
|
"github.com/btcsuite/btcutil"
|
|
"github.com/btcsuite/btcutil/bloom"
|
|
"github.com/btcsuite/btcutil/hdkeychain"
|
|
"github.com/btcsuite/btcutil/txsort"
|
|
)
|
|
|
|
func (s *SPVCon) PongBack(nonce uint64) {
|
|
mpong := wire.NewMsgPong(nonce)
|
|
|
|
s.outMsgQueue <- mpong
|
|
return
|
|
}
|
|
|
|
func (s *SPVCon) SendFilter(f *bloom.Filter) {
|
|
s.outMsgQueue <- f.MsgFilterLoad()
|
|
|
|
return
|
|
}
|
|
|
|
// Rebroadcast sends an inv message of all the unconfirmed txs the db is
|
|
// aware of. This is called after every sync. Only txids so hopefully not
|
|
// too annoying for nodes.
|
|
func (s *SPVCon) Rebroadcast() {
|
|
// get all unconfirmed txs
|
|
invMsg, err := s.TS.GetPendingInv()
|
|
if err != nil {
|
|
log.Printf("Rebroadcast error: %s", err.Error())
|
|
}
|
|
if len(invMsg.InvList) == 0 { // nothing to broadcast, so don't
|
|
return
|
|
}
|
|
s.outMsgQueue <- invMsg
|
|
return
|
|
}
|
|
|
|
// make utxo slices sortable
|
|
type utxoSlice []Utxo
|
|
|
|
// Sort utxos just like txins -- Len, Less, Swap
|
|
func (s utxoSlice) Len() int { return len(s) }
|
|
func (s utxoSlice) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
|
|
|
// outpoint sort; First input hash (reversed / rpc-style), then index.
|
|
func (s utxoSlice) Less(i, j int) bool {
|
|
// Input hashes are the same, so compare the index.
|
|
ihash := s[i].Op.Hash
|
|
jhash := s[j].Op.Hash
|
|
if ihash == jhash {
|
|
return s[i].Op.Index < s[j].Op.Index
|
|
}
|
|
// At this point, the hashes are not equal, so reverse them to
|
|
// big-endian and return the result of the comparison.
|
|
const hashSize = wire.HashSize
|
|
for b := 0; b < hashSize/2; b++ {
|
|
ihash[b], ihash[hashSize-1-b] = ihash[hashSize-1-b], ihash[b]
|
|
jhash[b], jhash[hashSize-1-b] = jhash[hashSize-1-b], jhash[b]
|
|
}
|
|
return bytes.Compare(ihash[:], jhash[:]) == -1
|
|
}
|
|
|
|
func (s *SPVCon) NewOutgoingTx(tx *wire.MsgTx) error {
|
|
txid := tx.TxSha()
|
|
// assign height of zero for txs we create
|
|
err := s.TS.AddTxid(&txid, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = s.TS.Ingest(tx, 0) // our own tx; don't keep track of false positives
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// make an inv message instead of a tx message to be polite
|
|
iv1 := wire.NewInvVect(wire.InvTypeWitnessTx, &txid)
|
|
invMsg := wire.NewMsgInv()
|
|
err = invMsg.AddInvVect(iv1)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
s.outMsgQueue <- invMsg
|
|
return nil
|
|
}
|
|
|
|
// 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 {
|
|
if len(adrs) != len(sendAmts) {
|
|
return fmt.Errorf("%d addresses and %d amounts", len(adrs), len(sendAmts))
|
|
}
|
|
var err error
|
|
var score, totalSend, fee int64
|
|
dustCutoff := int64(20000) // below this amount, just give to miners
|
|
satPerByte := int64(30) // satoshis per byte fee; have as arg later
|
|
allUtxos, err := s.TS.GetAllUtxos()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, utxo := range allUtxos {
|
|
score += utxo.Value
|
|
}
|
|
for _, amt := range sendAmts {
|
|
totalSend += amt
|
|
}
|
|
// important rule in bitcoin, output total > input total is invalid.
|
|
if totalSend > score {
|
|
return fmt.Errorf("trying to send %d but %d available.",
|
|
totalSend, score)
|
|
}
|
|
|
|
tx := wire.NewMsgTx() // make new tx
|
|
// add non-change (arg) outputs
|
|
for i, adr := range adrs {
|
|
// make address script 76a914...88ac or 0014...
|
|
outAdrScript, err := txscript.PayToAddrScript(adr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
// make user specified txout and add to tx
|
|
txout := wire.NewTxOut(sendAmts[i], outAdrScript)
|
|
tx.AddTxOut(txout)
|
|
}
|
|
|
|
// generate a utxo slice for your inputs
|
|
var ins utxoSlice
|
|
|
|
// add utxos until we've had enough
|
|
nokori := totalSend // nokori is how much is needed on input side
|
|
for _, utxo := range allUtxos {
|
|
// yeah, lets add this utxo!
|
|
ins = append(ins, *utxo)
|
|
// as we add utxos, fill in sigscripts
|
|
// generate previous pkscripts (subscritpt?) for all utxos
|
|
// then make txins with the utxo and prevpk, and insert them into the tx
|
|
// these are all zeroed out during signing but it's an easy way to keep track
|
|
var prevPKs []byte
|
|
if utxo.IsWit {
|
|
tx.Flags = 0x01
|
|
wa, err := btcutil.NewAddressWitnessPubKeyHash(
|
|
s.TS.Adrs[utxo.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[utxo.KeyIdx].PkhAdr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
tx.AddTxIn(wire.NewTxIn(&utxo.Op, prevPKs, nil))
|
|
nokori -= utxo.Value
|
|
// 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
|
|
|
|
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
|
|
}
|
|
|
|
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
|
|
// because we want to remember which keys are associated with which inputs
|
|
sort.Sort(ins)
|
|
|
|
// sort tx -- this only will change txouts since inputs are already sorted
|
|
txsort.InPlaceSort(tx)
|
|
|
|
// tx is ready for signing,
|
|
sigStash := make([][]byte, len(ins))
|
|
witStash := make([][][]byte, len(ins))
|
|
for i, txin := range tx.TxIn {
|
|
// pick key
|
|
child, err := s.TS.rootPrivKey.Child(
|
|
ins[i].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 ins[i].IsWit {
|
|
witStash[i], err = txscript.WitnessScript(
|
|
tx, i, ins[i].Value, txin.SignatureScript,
|
|
txscript.SigHashAll, priv, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
} else {
|
|
sigStash[i], err = txscript.SignatureScript(
|
|
tx, i, txin.SignatureScript,
|
|
txscript.SigHashAll, priv, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
// swap sigs into sigScripts in txins
|
|
for i, txin := range tx.TxIn {
|
|
if sigStash[i] != nil {
|
|
txin.SignatureScript = sigStash[i]
|
|
}
|
|
if witStash[i] != nil {
|
|
txin.Witness = witStash[i]
|
|
txin.SignatureScript = nil
|
|
}
|
|
|
|
}
|
|
|
|
fmt.Printf("tx: %s", TxToString(tx))
|
|
buf := bytes.NewBuffer(make([]byte, 0, tx.SerializeSize()))
|
|
tx.SerializeWitness(buf)
|
|
fmt.Printf("tx: %x\n", buf.Bytes())
|
|
|
|
// send it out on the wire. hope it gets there.
|
|
// we should deal with rejects. Don't yet.
|
|
err = s.NewOutgoingTx(tx)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// EstFee gives a fee estimate based on a tx and a sat/Byte target.
|
|
// The TX should have all outputs, including the change address already
|
|
// populated (with potentially 0 amount. Also it should have all inputs
|
|
// populated, but inputs don't need to have sigscripts or witnesses
|
|
// (it'll guess the sizes of sigs/wits that arent' filled in).
|
|
func EstFee(otx *wire.MsgTx, spB int64) int64 {
|
|
mtsig := make([]byte, 72)
|
|
mtpub := make([]byte, 33)
|
|
|
|
tx := otx.Copy()
|
|
|
|
// iterate through txins, replacing subscript sigscripts with noise
|
|
// sigs or witnesses
|
|
for _, txin := range tx.TxIn {
|
|
// check wpkh
|
|
if len(txin.SignatureScript) == 22 &&
|
|
txin.SignatureScript[0] == 0x00 && txin.SignatureScript[1] == 0x14 {
|
|
txin.SignatureScript = nil
|
|
txin.Witness = make([][]byte, 2)
|
|
txin.Witness[0] = mtsig
|
|
txin.Witness[1] = mtpub
|
|
} else if len(txin.SignatureScript) == 34 &&
|
|
txin.SignatureScript[0] == 0x00 && txin.SignatureScript[1] == 0x20 {
|
|
// p2wsh -- sig lenght is a total guess!
|
|
txin.SignatureScript = nil
|
|
txin.Witness = make([][]byte, 3)
|
|
// 3 sigs? totally guessing here
|
|
txin.Witness[0] = mtsig
|
|
txin.Witness[1] = mtsig
|
|
txin.Witness[2] = mtsig
|
|
} else {
|
|
// assume everything else is p2pkh. Even though it's not
|
|
txin.Witness = nil
|
|
txin.SignatureScript = make([]byte, 105) // len of p2pkh sigscript
|
|
}
|
|
}
|
|
fmt.Printf(TxToString(tx))
|
|
size := int64(tx.VirtualSize())
|
|
fmt.Printf("%d spB, est vsize %d, fee %d\n", spB, size, size*spB)
|
|
return size * spB
|
|
}
|
|
|
|
// SignThis isn't used anymore...
|
|
func (t *TxStore) SignThis(tx *wire.MsgTx) error {
|
|
fmt.Printf("-= SignThis =-\n")
|
|
|
|
// sort tx before signing.
|
|
txsort.InPlaceSort(tx)
|
|
|
|
sigs := make([][]byte, len(tx.TxIn))
|
|
// first iterate over each input
|
|
for j, in := range tx.TxIn {
|
|
for k := uint32(0); k < uint32(len(t.Adrs)); k++ {
|
|
child, err := t.rootPrivKey.Child(k + hdkeychain.HardenedKeyStart)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
myadr, err := child.Address(t.Param)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
adrScript, err := txscript.PayToAddrScript(myadr)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if bytes.Equal(adrScript, in.SignatureScript) {
|
|
fmt.Printf("Hit; key %d matches input %d. Signing.\n", k, j)
|
|
priv, err := child.ECPrivKey()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
sigs[j], err = txscript.SignatureScript(
|
|
tx, j, in.SignatureScript, txscript.SigHashAll, priv, true)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
for i, s := range sigs {
|
|
if s != nil {
|
|
tx.TxIn[i].SignatureScript = s
|
|
}
|
|
}
|
|
return nil
|
|
}
|