2016-01-15 06:56:25 +03:00
|
|
|
package uspv
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"log"
|
|
|
|
|
2016-01-21 12:04:45 +03:00
|
|
|
"github.com/btcsuite/btcd/txscript"
|
|
|
|
|
2016-01-21 08:08:05 +03:00
|
|
|
"github.com/btcsuite/btcd/chaincfg"
|
|
|
|
|
2016-01-20 07:02:18 +03:00
|
|
|
"github.com/boltdb/bolt"
|
2016-01-15 06:56:25 +03:00
|
|
|
"github.com/btcsuite/btcd/wire"
|
|
|
|
"github.com/btcsuite/btcutil"
|
|
|
|
"github.com/btcsuite/btcutil/bloom"
|
2016-01-21 08:08:05 +03:00
|
|
|
"github.com/btcsuite/btcutil/hdkeychain"
|
2016-01-15 06:56:25 +03:00
|
|
|
)
|
|
|
|
|
|
|
|
type TxStore struct {
|
2016-01-20 10:40:04 +03:00
|
|
|
OKTxids map[wire.ShaHash]int32 // known good txids and their heights
|
2016-01-19 12:33:58 +03:00
|
|
|
|
2016-01-23 04:15:56 +03:00
|
|
|
Utxos []*Utxo // stacks on stacks
|
2016-01-20 07:02:18 +03:00
|
|
|
Sum int64 // racks on racks
|
|
|
|
Adrs []MyAdr // endeavouring to acquire capital
|
2016-01-21 08:08:05 +03:00
|
|
|
LastIdx uint32 // should equal len(Adrs)
|
2016-01-20 07:02:18 +03:00
|
|
|
StateDB *bolt.DB // place to write all this down
|
2016-01-21 08:08:05 +03:00
|
|
|
// this is redundant with the SPVCon param... ugly and should be taken out
|
2016-01-21 12:04:45 +03:00
|
|
|
Param *chaincfg.Params // network parameters (testnet3, testnetL)
|
2016-01-21 08:08:05 +03:00
|
|
|
|
|
|
|
// From here, comes everything. It's a secret to everybody.
|
|
|
|
rootPrivKey *hdkeychain.ExtendedKey
|
2016-01-15 06:56:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
type Utxo struct { // cash money.
|
2016-01-27 12:24:16 +03:00
|
|
|
Op wire.OutPoint // where
|
|
|
|
|
2016-01-21 09:57:05 +03:00
|
|
|
// all the info needed to spend
|
|
|
|
AtHeight int32 // block height where this tx was confirmed, 0 for unconf
|
|
|
|
KeyIdx uint32 // index for private key needed to sign / spend
|
|
|
|
Value int64 // higher is better
|
|
|
|
|
2016-01-27 12:24:16 +03:00
|
|
|
// IsCoinbase bool // can't spend for a while
|
|
|
|
}
|
|
|
|
|
|
|
|
// Stxo is a utxo that has moved on.
|
|
|
|
type Stxo struct {
|
|
|
|
Utxo // when it used to be a utxo
|
|
|
|
SpendHeight int32 // height at which it met its demise
|
|
|
|
SpendTxid wire.ShaHash // the tx that consumed it
|
2016-01-15 06:56:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
type MyAdr struct { // an address I have the private key for
|
2016-01-21 12:04:45 +03:00
|
|
|
PkhAdr btcutil.Address
|
2016-01-15 06:56:25 +03:00
|
|
|
KeyIdx uint32 // index for private key needed to sign / spend
|
2016-01-21 12:04:45 +03:00
|
|
|
// ^^ this is kindof redundant because it'll just be their position
|
|
|
|
// inside the Adrs slice, right? leave for now
|
2016-01-15 06:56:25 +03:00
|
|
|
}
|
|
|
|
|
2016-01-21 12:04:45 +03:00
|
|
|
func NewTxStore(rootkey *hdkeychain.ExtendedKey) TxStore {
|
2016-01-19 12:33:58 +03:00
|
|
|
var txs TxStore
|
2016-01-21 12:04:45 +03:00
|
|
|
txs.rootPrivKey = rootkey
|
2016-01-20 10:40:04 +03:00
|
|
|
txs.OKTxids = make(map[wire.ShaHash]int32)
|
2016-01-19 12:33:58 +03:00
|
|
|
return txs
|
|
|
|
}
|
|
|
|
|
2016-01-21 12:04:45 +03:00
|
|
|
// add addresses into the TxStore in memory
|
2016-01-15 06:56:25 +03:00
|
|
|
func (t *TxStore) AddAdr(a btcutil.Address, kidx uint32) {
|
|
|
|
var ma MyAdr
|
2016-01-21 12:04:45 +03:00
|
|
|
ma.PkhAdr = a
|
2016-01-15 06:56:25 +03:00
|
|
|
ma.KeyIdx = kidx
|
|
|
|
t.Adrs = append(t.Adrs, ma)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-01-15 13:40:56 +03:00
|
|
|
// add txid of interest
|
2016-01-20 10:40:04 +03:00
|
|
|
func (t *TxStore) AddTxid(txid *wire.ShaHash, height int32) error {
|
2016-01-15 13:40:56 +03:00
|
|
|
if txid == nil {
|
|
|
|
return fmt.Errorf("tried to add nil txid")
|
|
|
|
}
|
2016-01-29 06:35:49 +03:00
|
|
|
log.Printf("added %s to OKTxids at height %d\n", txid.String(), height)
|
2016-01-19 12:33:58 +03:00
|
|
|
t.OKTxids[*txid] = height
|
2016-01-15 13:40:56 +03:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2016-01-15 10:08:37 +03:00
|
|
|
// ... or I'm gonna fade away
|
2016-01-15 06:56:25 +03:00
|
|
|
func (t *TxStore) GimmeFilter() (*bloom.Filter, error) {
|
|
|
|
if len(t.Adrs) == 0 {
|
|
|
|
return nil, fmt.Errorf("no addresses to filter for")
|
|
|
|
}
|
2016-01-22 08:50:42 +03:00
|
|
|
// add addresses to look for incoming
|
2016-01-27 12:24:16 +03:00
|
|
|
nutxo, err := t.NumUtxos()
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
elem := uint32(len(t.Adrs)) + nutxo
|
2016-01-29 06:35:49 +03:00
|
|
|
f := bloom.NewFilter(elem, 0, 0.000001, wire.BloomUpdateAll)
|
2016-01-15 06:56:25 +03:00
|
|
|
for _, a := range t.Adrs {
|
2016-01-21 12:04:45 +03:00
|
|
|
f.Add(a.PkhAdr.ScriptAddress())
|
2016-01-15 06:56:25 +03:00
|
|
|
}
|
2016-01-22 08:50:42 +03:00
|
|
|
// add txids of utxos to look for outgoing
|
|
|
|
for _, u := range t.Utxos {
|
|
|
|
f.AddOutPoint(&u.Op)
|
|
|
|
}
|
|
|
|
|
2016-01-15 06:56:25 +03:00
|
|
|
return f, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ingest a tx into wallet, dealing with both gains and losses
|
2016-01-29 06:35:49 +03:00
|
|
|
func (t *TxStore) AckTx(tx *wire.MsgTx) (uint32, error) {
|
|
|
|
var ioHits uint32 // number of utxos changed due to this tx
|
|
|
|
|
2016-01-15 13:40:56 +03:00
|
|
|
inTxid := tx.TxSha()
|
2016-01-19 12:33:58 +03:00
|
|
|
height, ok := t.OKTxids[inTxid]
|
|
|
|
if !ok {
|
2016-01-29 06:35:49 +03:00
|
|
|
log.Printf("%s", TxToString(tx))
|
|
|
|
return 0, fmt.Errorf("tx %s not in OKTxids.", inTxid.String())
|
2016-01-15 13:40:56 +03:00
|
|
|
}
|
2016-01-29 06:35:49 +03:00
|
|
|
delete(t.OKTxids, inTxid) // don't need anymore
|
|
|
|
hitsGained, err := t.AbsorbTx(tx, height)
|
2016-01-15 06:56:25 +03:00
|
|
|
if err != nil {
|
2016-01-29 06:35:49 +03:00
|
|
|
return 0, err
|
2016-01-15 06:56:25 +03:00
|
|
|
}
|
2016-01-29 06:35:49 +03:00
|
|
|
hitsLost, err := t.ExpellTx(tx, height)
|
2016-01-15 06:56:25 +03:00
|
|
|
if err != nil {
|
2016-01-29 06:35:49 +03:00
|
|
|
return 0, err
|
2016-01-15 06:56:25 +03:00
|
|
|
}
|
2016-01-29 06:35:49 +03:00
|
|
|
ioHits = hitsGained + hitsLost
|
|
|
|
|
2016-01-15 13:40:56 +03:00
|
|
|
// fmt.Printf("ingested tx %s total amt %d\n", inTxid.String(), t.Sum)
|
2016-01-29 06:35:49 +03:00
|
|
|
return ioHits, nil
|
2016-01-15 06:56:25 +03:00
|
|
|
}
|
|
|
|
|
2016-01-29 06:35:49 +03:00
|
|
|
// AbsorbTx Absorbs money into wallet from a tx. returns number of
|
|
|
|
// new utxos absorbed.
|
|
|
|
func (t *TxStore) AbsorbTx(tx *wire.MsgTx, height int32) (uint32, error) {
|
2016-01-15 06:56:25 +03:00
|
|
|
if tx == nil {
|
2016-01-29 06:35:49 +03:00
|
|
|
return 0, fmt.Errorf("Tried to add nil tx")
|
2016-01-15 06:56:25 +03:00
|
|
|
}
|
2016-01-23 04:15:56 +03:00
|
|
|
newTxid := tx.TxSha()
|
|
|
|
var hits uint32 // how many outputs of this tx are ours
|
|
|
|
var acq int64 // total acquirement from this tx
|
2016-01-27 12:24:16 +03:00
|
|
|
// check if any of the tx's outputs match my known outpoints
|
2016-01-15 06:56:25 +03:00
|
|
|
for i, out := range tx.TxOut { // in each output of tx
|
2016-01-23 04:15:56 +03:00
|
|
|
dup := false // start by assuming its new until found duplicate
|
|
|
|
newOp := wire.NewOutPoint(&newTxid, uint32(i))
|
|
|
|
// first look for dupes -- already known outpoints.
|
|
|
|
// if we find a dupe here overwrite it to the DB.
|
|
|
|
for _, u := range t.Utxos {
|
|
|
|
dup = OutPointsEqual(*newOp, u.Op) // is this outpoint known?
|
|
|
|
if dup { // found dupe
|
|
|
|
fmt.Printf(" %s is dupe\t", newOp.String())
|
2016-01-29 06:35:49 +03:00
|
|
|
hits++ // thought a dupe, still a hit
|
2016-01-23 04:15:56 +03:00
|
|
|
u.AtHeight = height // ONLY difference is height
|
|
|
|
// save modified utxo to db, overwriting old one
|
2016-01-27 12:24:16 +03:00
|
|
|
err := t.SaveUtxo(u)
|
2016-01-23 04:15:56 +03:00
|
|
|
if err != nil {
|
2016-01-29 06:35:49 +03:00
|
|
|
return 0, err
|
2016-01-23 04:15:56 +03:00
|
|
|
}
|
|
|
|
break // out of the t.Utxo range loop
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if dup {
|
|
|
|
// if we found the outpoint to be a dup above, don't add it again
|
|
|
|
// when it matches an address, just go to the next outpoint
|
|
|
|
continue
|
|
|
|
}
|
2016-01-27 12:24:16 +03:00
|
|
|
// check if this is a new txout matching one of my addresses
|
2016-01-15 06:56:25 +03:00
|
|
|
for _, a := range t.Adrs { // compare to each adr we have
|
2016-01-21 12:04:45 +03:00
|
|
|
// check for full script to eliminate false positives
|
|
|
|
aPKscript, err := txscript.PayToAddrScript(a.PkhAdr)
|
|
|
|
if err != nil {
|
2016-01-29 06:35:49 +03:00
|
|
|
return 0, err
|
2016-01-21 12:04:45 +03:00
|
|
|
}
|
|
|
|
if bytes.Equal(out.PkScript, aPKscript) { // hit
|
2016-01-27 12:24:16 +03:00
|
|
|
// already checked for dupes, so this must be a new outpoint
|
2016-01-15 06:56:25 +03:00
|
|
|
var newu Utxo
|
2016-01-19 12:33:58 +03:00
|
|
|
newu.AtHeight = height
|
2016-01-15 06:56:25 +03:00
|
|
|
newu.KeyIdx = a.KeyIdx
|
2016-01-21 09:57:05 +03:00
|
|
|
newu.Value = out.Value
|
2016-01-15 06:56:25 +03:00
|
|
|
|
|
|
|
var newop wire.OutPoint
|
|
|
|
newop.Hash = tx.TxSha()
|
|
|
|
newop.Index = uint32(i)
|
|
|
|
newu.Op = newop
|
2016-01-27 12:24:16 +03:00
|
|
|
err = t.SaveUtxo(&newu)
|
2016-01-20 12:23:47 +03:00
|
|
|
if err != nil {
|
2016-01-29 06:35:49 +03:00
|
|
|
return 0, err
|
2016-01-20 12:23:47 +03:00
|
|
|
}
|
2016-01-23 04:15:56 +03:00
|
|
|
|
|
|
|
acq += out.Value
|
|
|
|
hits++
|
|
|
|
t.Utxos = append(t.Utxos, &newu) // always add new utxo
|
2016-01-15 06:56:25 +03:00
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-01-29 06:35:49 +03:00
|
|
|
// log.Printf("%d hits, acquired %d", hits, acq)
|
2016-01-15 06:56:25 +03:00
|
|
|
t.Sum += acq
|
2016-01-29 06:35:49 +03:00
|
|
|
return hits, nil
|
2016-01-15 06:56:25 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
// Expell money from wallet due to a tx
|
2016-01-29 06:35:49 +03:00
|
|
|
func (t *TxStore) ExpellTx(tx *wire.MsgTx, height int32) (uint32, error) {
|
2016-01-15 06:56:25 +03:00
|
|
|
if tx == nil {
|
2016-01-29 06:35:49 +03:00
|
|
|
return 0, fmt.Errorf("Tried to add nil tx")
|
2016-01-15 06:56:25 +03:00
|
|
|
}
|
|
|
|
var hits uint32
|
|
|
|
var loss int64
|
|
|
|
|
|
|
|
for _, in := range tx.TxIn {
|
|
|
|
for i, myutxo := range t.Utxos {
|
2016-01-22 04:59:50 +03:00
|
|
|
if OutPointsEqual(myutxo.Op, in.PreviousOutPoint) {
|
2016-01-15 06:56:25 +03:00
|
|
|
hits++
|
2016-01-21 09:57:05 +03:00
|
|
|
loss += myutxo.Value
|
2016-01-27 12:24:16 +03:00
|
|
|
err := t.MarkSpent(*myutxo, height, tx)
|
2016-01-20 12:23:47 +03:00
|
|
|
if err != nil {
|
2016-01-29 06:35:49 +03:00
|
|
|
return 0, err
|
2016-01-20 12:23:47 +03:00
|
|
|
}
|
|
|
|
// delete from my in-ram utxo set
|
2016-01-15 06:56:25 +03:00
|
|
|
t.Utxos = append(t.Utxos[:i], t.Utxos[i+1:]...)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2016-01-29 06:35:49 +03:00
|
|
|
// log.Printf("%d hits, lost %d", hits, loss)
|
2016-01-15 06:56:25 +03:00
|
|
|
t.Sum -= loss
|
2016-01-29 06:35:49 +03:00
|
|
|
return hits, nil
|
2016-01-15 06:56:25 +03:00
|
|
|
}
|
2016-01-19 22:59:01 +03:00
|
|
|
|
2016-01-22 04:59:50 +03:00
|
|
|
// need this because before I was comparing pointers maybe?
|
|
|
|
// so they were the same outpoint but stored in 2 places so false negative?
|
|
|
|
func OutPointsEqual(a, b wire.OutPoint) bool {
|
|
|
|
if !a.Hash.IsEqual(&b.Hash) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
return a.Index == b.Index
|
|
|
|
}
|
|
|
|
|
2016-01-19 22:59:01 +03:00
|
|
|
// TxToString prints out some info about a transaction. for testing / debugging
|
|
|
|
func TxToString(tx *wire.MsgTx) string {
|
|
|
|
str := "\t\t\t - Tx - \n"
|
|
|
|
for i, in := range tx.TxIn {
|
|
|
|
str += fmt.Sprintf("Input %d: %s\n", i, in.PreviousOutPoint.String())
|
|
|
|
str += fmt.Sprintf("SigScript for input %d: %x\n", i, in.SignatureScript)
|
|
|
|
}
|
|
|
|
for i, out := range tx.TxOut {
|
|
|
|
if out != nil {
|
|
|
|
str += fmt.Sprintf("\toutput %d script: %x amt: %d\n",
|
|
|
|
i, out.PkScript, out.Value)
|
|
|
|
} else {
|
|
|
|
str += fmt.Sprintf("output %d nil (WARNING)\n", i)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return str
|
|
|
|
}
|