keep track of addresses, check incoming txs for full pkscript match

This commit is contained in:
Tadge Dryja 2016-01-21 01:04:45 -08:00
parent b7a2c46ea6
commit 9215c18113
3 changed files with 106 additions and 29 deletions

@ -73,7 +73,8 @@ func OpenSPV(remoteNode string, hfn, tsfn string,
if err != nil { if err != nil {
return s, err return s, err
} }
s.TS = inTs inTs.Param = p
s.TS = inTs // copy pointer of txstore into spvcon
myMsgVer, err := wire.NewMsgVersionFromConn(s.con, 0, 0) myMsgVer, err := wire.NewMsgVersionFromConn(s.con, 0, 0)
if err != nil { if err != nil {

@ -5,6 +5,8 @@ import (
"fmt" "fmt"
"log" "log"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
"github.com/boltdb/bolt" "github.com/boltdb/bolt"
@ -23,7 +25,7 @@ type TxStore struct {
LastIdx uint32 // should equal len(Adrs) LastIdx uint32 // should equal len(Adrs)
StateDB *bolt.DB // place to write all this down StateDB *bolt.DB // place to write all this down
// this is redundant with the SPVCon param... ugly and should be taken out // this is redundant with the SPVCon param... ugly and should be taken out
param *chaincfg.Params // network parameters (testnet3, testnetL) Param *chaincfg.Params // network parameters (testnet3, testnetL)
// From here, comes everything. It's a secret to everybody. // From here, comes everything. It's a secret to everybody.
rootPrivKey *hdkeychain.ExtendedKey rootPrivKey *hdkeychain.ExtendedKey
@ -39,20 +41,23 @@ type Utxo struct { // cash money.
} }
type MyAdr struct { // an address I have the private key for type MyAdr struct { // an address I have the private key for
btcutil.Address PkhAdr btcutil.Address
KeyIdx uint32 // index for private key needed to sign / spend KeyIdx uint32 // index for private key needed to sign / spend
// ^^ this is kindof redundant because it'll just be their position
// inside the Adrs slice, right? leave for now
} }
func NewTxStore() TxStore { func NewTxStore(rootkey *hdkeychain.ExtendedKey) TxStore {
var txs TxStore var txs TxStore
txs.rootPrivKey = rootkey
txs.OKTxids = make(map[wire.ShaHash]int32) txs.OKTxids = make(map[wire.ShaHash]int32)
return txs return txs
} }
// add addresses into the TxStore // add addresses into the TxStore in memory
func (t *TxStore) AddAdr(a btcutil.Address, kidx uint32) { func (t *TxStore) AddAdr(a btcutil.Address, kidx uint32) {
var ma MyAdr var ma MyAdr
ma.Address = a ma.PkhAdr = a
ma.KeyIdx = kidx ma.KeyIdx = kidx
t.Adrs = append(t.Adrs, ma) t.Adrs = append(t.Adrs, ma)
return return
@ -75,7 +80,7 @@ func (t *TxStore) GimmeFilter() (*bloom.Filter, error) {
} }
f := bloom.NewFilter(uint32(len(t.Adrs)), 0, 0.001, wire.BloomUpdateNone) f := bloom.NewFilter(uint32(len(t.Adrs)), 0, 0.001, wire.BloomUpdateNone)
for _, a := range t.Adrs { for _, a := range t.Adrs {
f.Add(a.ScriptAddress()) f.Add(a.PkhAdr.ScriptAddress())
} }
return f, nil return f, nil
} }
@ -111,9 +116,12 @@ func (t *TxStore) AbsorbTx(tx *wire.MsgTx, height int32) error {
// check if any of the tx's outputs match my adrs // check if any of the tx's outputs match my adrs
for i, out := range tx.TxOut { // in each output of tx for i, out := range tx.TxOut { // in each output of tx
for _, a := range t.Adrs { // compare to each adr we have for _, a := range t.Adrs { // compare to each adr we have
// more correct would be to check for full script // check for full script to eliminate false positives
// contains could have false positive? (p2sh/p2pkh same hash ..?) aPKscript, err := txscript.PayToAddrScript(a.PkhAdr)
if bytes.Contains(out.PkScript, a.ScriptAddress()) { // hit if err != nil {
return err
}
if bytes.Equal(out.PkScript, aPKscript) { // hit
hits++ hits++
acq += out.Value acq += out.Value
var newu Utxo var newu Utxo
@ -129,6 +137,7 @@ func (t *TxStore) AbsorbTx(tx *wire.MsgTx, height int32) error {
if err != nil { if err != nil {
return err return err
} }
t.Sum += newu.Value
t.Utxos = append(t.Utxos, newu) t.Utxos = append(t.Utxos, newu)
break break
} }

@ -4,17 +4,19 @@ import (
"bytes" "bytes"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
"log"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/boltdb/bolt" "github.com/boltdb/bolt"
) )
var ( var (
BKTUtxos = []byte("DuffelBag") // leave the rest to collect interest BKTUtxos = []byte("DuffelBag") // leave the rest to collect interest
BKTOld = []byte("SpentTxs") // for bookkeeping BKTOld = []byte("SpentTxs") // for bookkeeping
KEYState = []byte("LastUpdate") // last state of DB BKTState = []byte("MiscState") // last state of DB
KEYNumKeys = []byte("NumKeys") // number of keys used
) )
func (ts *TxStore) OpenDB(filename string) error { func (ts *TxStore) OpenDB(filename string) error {
@ -33,26 +35,70 @@ func (ts *TxStore) OpenDB(filename string) error {
if err != nil { if err != nil {
return err return err
} }
_, err = tx.CreateBucketIfNotExists(BKTState)
if err != nil {
return err
}
return nil return nil
}) })
} }
// NewAdr creates a new, never before seen address, and increments the
// DB counter as well as putting it in the ram Adrs store, and returns it
func (ts *TxStore) NewAdr() (*btcutil.AddressPubKeyHash, error) {
if ts.Param == nil {
return nil, fmt.Errorf("nil param")
}
n := uint32(len(ts.Adrs))
priv, err := ts.rootPrivKey.Child(n) // + hdkeychain.HardenedKeyStart)
if err != nil {
return nil, err
}
newAdr, err := priv.Address(ts.Param)
if err != nil {
return nil, err
}
// total number of keys (now +1) into 4 bytes
var buf bytes.Buffer
err = binary.Write(&buf, binary.BigEndian, n+1)
if err != nil {
return nil, err
}
// write to db file
err = ts.StateDB.Update(func(tx *bolt.Tx) error {
stt := tx.Bucket(BKTState)
return stt.Put(KEYNumKeys, buf.Bytes())
})
if err != nil {
return nil, err
}
// add in to ram.
ts.AddAdr(newAdr, n)
return newAdr, nil
}
// PopulateAdrs just puts a bunch of adrs in ram; it doesn't touch the DB
func (ts *TxStore) PopulateAdrs(lastKey uint32) error { func (ts *TxStore) PopulateAdrs(lastKey uint32) error {
for k := uint32(0); k < lastKey; k++ { for k := uint32(0); k < lastKey; k++ {
priv, err := ts.rootPrivKey.Child(k) priv, err := ts.rootPrivKey.Child(k) // + hdkeychain.HardenedKeyStart)
if err != nil { if err != nil {
log.Fatal(err) return err
} }
myadr, err := priv.Address(ts.param)
newAdr, err := priv.Address(ts.Param)
if err != nil { if err != nil {
log.Fatal(err) return err
} }
fmt.Printf("made adr %s\n", myadr.String())
ts.AddAdr(myadr, k) ts.AddAdr(newAdr, k)
} }
return nil return nil
} }
func (u *Utxo) SaveToDB(dbx *bolt.DB) error { func (u *Utxo) SaveToDB(dbx *bolt.DB) error {
return dbx.Update(func(tx *bolt.Tx) error { return dbx.Update(func(tx *bolt.Tx) error {
duf := tx.Bucket(BKTUtxos) duf := tx.Bucket(BKTUtxos)
@ -66,7 +112,6 @@ func (u *Utxo) SaveToDB(dbx *bolt.DB) error {
} }
func (ts *TxStore) MarkSpent(op *wire.OutPoint, h int32, stx *wire.MsgTx) error { func (ts *TxStore) MarkSpent(op *wire.OutPoint, h int32, stx *wire.MsgTx) error {
// we write in key = outpoint (32 hash, 4 index) // we write in key = outpoint (32 hash, 4 index)
// value = spending txid // value = spending txid
// if we care about the spending tx we can store that in another bucket. // if we care about the spending tx we can store that in another bucket.
@ -90,8 +135,14 @@ func (ts *TxStore) MarkSpent(op *wire.OutPoint, h int32, stx *wire.MsgTx) error
}) })
} }
func (ts *TxStore) LoadUtxos() error { // LoadFromDB loads everything in the db file into ram, rebuilding the TxStore
err := ts.StateDB.View(func(tx *bolt.Tx) error { // (except the rootPrivKey, that should be done before calling this --
// this will error if ts.rootPrivKey hasn't been loaded)
func (ts *TxStore) LoadFromDB() error {
if ts.rootPrivKey == nil {
return fmt.Errorf("LoadFromDB needs rootPrivKey loaded")
}
return ts.StateDB.View(func(tx *bolt.Tx) error {
duf := tx.Bucket(BKTUtxos) duf := tx.Bucket(BKTUtxos)
if duf == nil { if duf == nil {
return fmt.Errorf("no duffel bag") return fmt.Errorf("no duffel bag")
@ -100,9 +151,28 @@ func (ts *TxStore) LoadUtxos() error {
if spent == nil { if spent == nil {
return fmt.Errorf("no spenttx bucket") return fmt.Errorf("no spenttx bucket")
} }
state := tx.Bucket(BKTState)
if state == nil {
return fmt.Errorf("no state bucket")
}
// first populate addresses from state bucket
numKeysBytes := state.Get(KEYNumKeys)
if numKeysBytes != nil { // NumKeys exists, read into uint32
buf := bytes.NewBuffer(numKeysBytes)
var numKeys uint32
err := binary.Read(buf, binary.BigEndian, &numKeys)
if err != nil {
return err
}
fmt.Printf("db says %d keys\n", numKeys)
err = ts.PopulateAdrs(numKeys)
if err != nil {
return err
}
}
// next load all utxos from db into ram
duf.ForEach(func(k, v []byte) error { duf.ForEach(func(k, v []byte) error {
// have to copy these here, otherwise append will crash it. // have to copy k and v here, otherwise append will crash it.
// not quite sure why but append does weird stuff I guess. // not quite sure why but append does weird stuff I guess.
stx := spent.Get(k) stx := spent.Get(k)
if stx == nil { // if it's not in the spent bucket if stx == nil { // if it's not in the spent bucket
@ -115,6 +185,7 @@ func (ts *TxStore) LoadUtxos() error {
return err return err
} }
// and add it to ram // and add it to ram
ts.Sum += newU.Value
ts.Utxos = append(ts.Utxos, newU) ts.Utxos = append(ts.Utxos, newU)
} else { } else {
fmt.Printf("had utxo %x but spent by tx %x...\n", fmt.Printf("had utxo %x but spent by tx %x...\n",
@ -124,10 +195,6 @@ func (ts *TxStore) LoadUtxos() error {
}) })
return nil return nil
}) })
if err != nil {
return err
}
return nil
} }
// outPointToBytes turns an outpoint into 36 bytes. // outPointToBytes turns an outpoint into 36 bytes.