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 {
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)
if err != nil {

@ -5,6 +5,8 @@ import (
"fmt"
"log"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/chaincfg"
"github.com/boltdb/bolt"
@ -23,7 +25,7 @@ type TxStore struct {
LastIdx uint32 // should equal len(Adrs)
StateDB *bolt.DB // place to write all this down
// 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.
rootPrivKey *hdkeychain.ExtendedKey
@ -39,20 +41,23 @@ type Utxo struct { // cash money.
}
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
// ^^ 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
txs.rootPrivKey = rootkey
txs.OKTxids = make(map[wire.ShaHash]int32)
return txs
}
// add addresses into the TxStore
// add addresses into the TxStore in memory
func (t *TxStore) AddAdr(a btcutil.Address, kidx uint32) {
var ma MyAdr
ma.Address = a
ma.PkhAdr = a
ma.KeyIdx = kidx
t.Adrs = append(t.Adrs, ma)
return
@ -75,7 +80,7 @@ func (t *TxStore) GimmeFilter() (*bloom.Filter, error) {
}
f := bloom.NewFilter(uint32(len(t.Adrs)), 0, 0.001, wire.BloomUpdateNone)
for _, a := range t.Adrs {
f.Add(a.ScriptAddress())
f.Add(a.PkhAdr.ScriptAddress())
}
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
for i, out := range tx.TxOut { // in each output of tx
for _, a := range t.Adrs { // compare to each adr we have
// more correct would be to check for full script
// contains could have false positive? (p2sh/p2pkh same hash ..?)
if bytes.Contains(out.PkScript, a.ScriptAddress()) { // hit
// check for full script to eliminate false positives
aPKscript, err := txscript.PayToAddrScript(a.PkhAdr)
if err != nil {
return err
}
if bytes.Equal(out.PkScript, aPKscript) { // hit
hits++
acq += out.Value
var newu Utxo
@ -129,6 +137,7 @@ func (t *TxStore) AbsorbTx(tx *wire.MsgTx, height int32) error {
if err != nil {
return err
}
t.Sum += newu.Value
t.Utxos = append(t.Utxos, newu)
break
}

@ -4,9 +4,9 @@ import (
"bytes"
"encoding/binary"
"fmt"
"log"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/boltdb/bolt"
)
@ -14,7 +14,9 @@ import (
var (
BKTUtxos = []byte("DuffelBag") // leave the rest to collect interest
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 {
@ -33,26 +35,70 @@ func (ts *TxStore) OpenDB(filename string) error {
if err != nil {
return err
}
_, err = tx.CreateBucketIfNotExists(BKTState)
if err != nil {
return err
}
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 {
for k := uint32(0); k < lastKey; k++ {
priv, err := ts.rootPrivKey.Child(k)
priv, err := ts.rootPrivKey.Child(k) // + hdkeychain.HardenedKeyStart)
if err != nil {
log.Fatal(err)
return err
}
myadr, err := priv.Address(ts.param)
newAdr, err := priv.Address(ts.Param)
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
}
func (u *Utxo) SaveToDB(dbx *bolt.DB) error {
return dbx.Update(func(tx *bolt.Tx) error {
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 {
// we write in key = outpoint (32 hash, 4 index)
// value = spending txid
// 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 {
err := ts.StateDB.View(func(tx *bolt.Tx) error {
// LoadFromDB loads everything in the db file into ram, rebuilding the TxStore
// (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)
if duf == nil {
return fmt.Errorf("no duffel bag")
@ -100,9 +151,28 @@ func (ts *TxStore) LoadUtxos() error {
if spent == nil {
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 {
// 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.
stx := spent.Get(k)
if stx == nil { // if it's not in the spent bucket
@ -115,6 +185,7 @@ func (ts *TxStore) LoadUtxos() error {
return err
}
// and add it to ram
ts.Sum += newU.Value
ts.Utxos = append(ts.Utxos, newU)
} else {
fmt.Printf("had utxo %x but spent by tx %x...\n",
@ -124,10 +195,6 @@ func (ts *TxStore) LoadUtxos() error {
})
return nil
})
if err != nil {
return err
}
return nil
}
// outPointToBytes turns an outpoint into 36 bytes.