diff --git a/uspv/eight333.go b/uspv/eight333.go index 7096bef3..c3082a77 100644 --- a/uspv/eight333.go +++ b/uspv/eight333.go @@ -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 { diff --git a/uspv/txstore.go b/uspv/txstore.go index 036ca4fe..09fd09f1 100644 --- a/uspv/txstore.go +++ b/uspv/txstore.go @@ -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 } diff --git a/uspv/utxodb.go b/uspv/utxodb.go index ffa7ee00..9c5704ff 100644 --- a/uspv/utxodb.go +++ b/uspv/utxodb.go @@ -4,17 +4,19 @@ import ( "bytes" "encoding/binary" "fmt" - "log" "github.com/btcsuite/btcd/wire" + "github.com/btcsuite/btcutil" "github.com/boltdb/bolt" ) var ( - BKTUtxos = []byte("DuffelBag") // leave the rest to collect interest - BKTOld = []byte("SpentTxs") // for bookkeeping - KEYState = []byte("LastUpdate") // last state of DB + BKTUtxos = []byte("DuffelBag") // leave the rest to collect interest + BKTOld = []byte("SpentTxs") // for bookkeeping + 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.