diff --git a/Godeps/Godeps.json b/Godeps/Godeps.json index 8e95e2f9b3..12d3f49f34 100644 --- a/Godeps/Godeps.json +++ b/Godeps/Godeps.json @@ -13,7 +13,7 @@ }, { "ImportPath": "github.com/OpenBazaar/spvwallet", - "Rev": "bbb329ee515ac36f9f8b2424799e305f5e1667f3" + "Rev": "94f54b27d12db95445fc18ed9843d74a570a5de5" }, { "ImportPath": "github.com/boltdb/bolt", diff --git a/api/jsonapi.go b/api/jsonapi.go index a2bbb697f6..d14d1aa5a4 100644 --- a/api/jsonapi.go +++ b/api/jsonapi.go @@ -1272,6 +1272,7 @@ func (i *jsonAPIHandler) GETOrder(w http.ResponseWriter, r *http.Request) { tx := new(pb.TransactionRecord) tx.Txid = r.Txid tx.Value = r.Value + // TODO: add confirmations txs = append(txs, tx) } diff --git a/bitcoin/bitcoind/wallet.go b/bitcoin/bitcoind/wallet.go index 0b4dc9c9e5..e135263352 100644 --- a/bitcoin/bitcoind/wallet.go +++ b/bitcoin/bitcoind/wallet.go @@ -170,6 +170,27 @@ func (w *BitcoindWallet) Balance() (confirmed, unconfirmed int64) { return int64(c.ToUnit(btc.AmountSatoshi)), int64(u.ToUnit(btc.AmountSatoshi)) } +func (w *BitcoindWallet) Transactions() ([]spvwallet.Txn, error) { + var ret []spvwallet.Txn + resp, err := w.rpcClient.ListTransactions(Account) + if err != nil { + return ret, err + } + for _, r := range resp { + amt, err := btc.NewAmount(r.Amount) + if err != nil { + return ret, err + } + t := spvwallet.Txn{ + Txid: r.TxID, + Value: int64(amt.ToUnit(btc.AmountSatoshi)), + Height: uint32(*r.BlockIndex), + } + ret = append(ret, t) + } + return ret, nil +} + func (w *BitcoindWallet) ChainTip() uint32 { info, err := w.rpcClient.GetInfo() if err != nil { @@ -564,6 +585,8 @@ func (w *BitcoindWallet) ReSyncBlockchain(fromHeight int32) { } func (w *BitcoindWallet) Close() { - w.rpcClient.RawRequest("stop", []json.RawMessage{}) - w.rpcClient.Shutdown() + if w.rpcClient != nil { + w.rpcClient.RawRequest("stop", []json.RawMessage{}) + w.rpcClient.Shutdown() + } } diff --git a/bitcoin/wallet.go b/bitcoin/wallet.go index d1cb197f5f..2f29ab5121 100644 --- a/bitcoin/wallet.go +++ b/bitcoin/wallet.go @@ -37,6 +37,9 @@ type BitcoinWallet interface { // Get the confirmed and unconfirmed balances Balance() (confirmed, unconfirmed int64) + // Returns a list of transactions for this wallet + Transactions() ([]spvwallet.Txn, error) + // Get the height of the blockchain ChainTip() uint32 diff --git a/repo/db/db.go b/repo/db/db.go index 39798a9a3e..e90cd4717e 100644 --- a/repo/db/db.go +++ b/repo/db/db.go @@ -253,7 +253,7 @@ func initDatabaseTables(db *sql.DB, password string) error { create table keys (scriptPubKey text primary key not null, purpose integer, keyIndex integer, used integer, key text); create table utxos (outpoint text primary key not null, value integer, height integer, scriptPubKey text, freeze int); create table stxos (outpoint text primary key not null, value integer, height integer, scriptPubKey text, spendHeight integer, spendTxid text); - create table txns (txid text primary key not null, tx blob); + create table txns (txid text primary key not null, value integer, height integer, tx blob); create table inventory (slug text primary key not null, count integer); create table purchases (orderID text primary key not null, contract blob, state integer, read integer, date integer, total integer, thumbnail text, vendorID text, vendorBlockchainID text, title text, shippingName text, shippingAddress text, paymentAddr text, funded integer, transactions blob); create index index_purchases on purchases (paymentAddr); diff --git a/repo/db/txns.go b/repo/db/txns.go index e3d8d4dd77..f8cea1e004 100644 --- a/repo/db/txns.go +++ b/repo/db/txns.go @@ -3,6 +3,7 @@ package db import ( "bytes" "database/sql" + "github.com/OpenBazaar/spvwallet" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/wire" "sync" @@ -13,14 +14,14 @@ type TxnsDB struct { lock sync.RWMutex } -func (t *TxnsDB) Put(txn *wire.MsgTx) error { +func (t *TxnsDB) Put(txn *wire.MsgTx, value, height int) error { t.lock.Lock() defer t.lock.Unlock() tx, err := t.db.Begin() if err != nil { return err } - stmt, err := tx.Prepare("insert into txns(txid, tx) values(?,?)") + stmt, err := tx.Prepare("insert or replace into txns(txid, value, height, tx) values(?,?,?,?)") defer stmt.Close() if err != nil { tx.Rollback() @@ -28,7 +29,7 @@ func (t *TxnsDB) Put(txn *wire.MsgTx) error { } var buf bytes.Buffer txn.Serialize(&buf) - _, err = stmt.Exec(txn.TxHash().String(), buf.Bytes()) + _, err = stmt.Exec(txn.TxHash().String(), value, height, buf.Bytes()) if err != nil { tx.Rollback() return err @@ -37,41 +38,46 @@ func (t *TxnsDB) Put(txn *wire.MsgTx) error { return nil } -func (t *TxnsDB) Get(txid chainhash.Hash) (*wire.MsgTx, error) { - t.lock.RLock() - defer t.lock.RUnlock() - stmt, err := t.db.Prepare("select tx from txns where txid=?") +func (t *TxnsDB) Get(txid chainhash.Hash) (*wire.MsgTx, uint32, error) { + t.lock.Lock() + defer t.lock.Unlock() + stmt, err := t.db.Prepare("select tx, height from txns where txid=?") defer stmt.Close() var ret []byte - err = stmt.QueryRow(txid.String()).Scan(&ret) + var height int + err = stmt.QueryRow(txid.String()).Scan(&ret, &height) if err != nil { - return nil, err + return nil, uint32(0), err } r := bytes.NewReader(ret) - msgTx := wire.NewMsgTx(wire.TxVersion) + msgTx := wire.NewMsgTx(1) msgTx.BtcDecode(r, 1) - return msgTx, nil + return msgTx, uint32(height), nil } -func (t *TxnsDB) GetAll() ([]*wire.MsgTx, error) { - t.lock.RLock() - defer t.lock.RUnlock() - var ret []*wire.MsgTx - stm := "select tx from txns" +func (t *TxnsDB) GetAll() ([]spvwallet.Txn, error) { + t.lock.Lock() + defer t.lock.Unlock() + var ret []spvwallet.Txn + stm := "select tx, value, height from txns" rows, err := t.db.Query(stm) + defer rows.Close() if err != nil { return ret, err } - defer rows.Close() for rows.Next() { var tx []byte - if err := rows.Scan(&tx); err != nil { + var value int + var height int + if err := rows.Scan(&tx, &value, &height); err != nil { continue } r := bytes.NewReader(tx) - msgTx := wire.NewMsgTx(wire.TxVersion) + msgTx := wire.NewMsgTx(1) msgTx.BtcDecode(r, 1) - ret = append(ret, msgTx) + + txn := spvwallet.Txn{msgTx.TxHash().String(), int64(value), uint32(height)} + ret = append(ret, txn) } return ret, nil } diff --git a/repo/db/txns_test.go b/repo/db/txns_test.go index 9c0a0ee46a..18f5e903e2 100644 --- a/repo/db/txns_test.go +++ b/repo/db/txns_test.go @@ -25,20 +25,28 @@ func TestTxnsPut(t *testing.T) { r := bytes.NewReader(raw) tx.Deserialize(r) - err := txdb.Put(tx) + err := txdb.Put(tx, 5, 1) if err != nil { t.Error(err) } - stmt, err := txdb.db.Prepare("select tx from txns where txid=?") + stmt, err := txdb.db.Prepare("select tx, value, height from txns where txid=?") defer stmt.Close() var ret []byte - err = stmt.QueryRow(tx.TxHash().String()).Scan(&ret) + var val int + var height int + err = stmt.QueryRow(tx.TxHash().String()).Scan(&ret, &val, &height) if err != nil { t.Error(err) } if hex.EncodeToString(ret) != txHex { t.Error("Txn db put failed") } + if val != 5 { + t.Error("Txn db failed to put value") + } + if height != 1 { + t.Error("Tns db failed to put height") + } } func TestTxnsGet(t *testing.T) { @@ -48,17 +56,20 @@ func TestTxnsGet(t *testing.T) { r := bytes.NewReader(raw) tx.Deserialize(r) - err := txdb.Put(tx) + err := txdb.Put(tx, 0, 1) if err != nil { t.Error(err) } - tx2, err := txdb.Get(tx.TxHash()) + tx2, h, err := txdb.Get(tx.TxHash()) if err != nil { t.Error(err) } if tx.TxHash().String() != tx2.TxHash().String() { t.Error("Txn db get failed") } + if h != 1 { + t.Error("Txn db failed to get height") + } } func TestTxnsGetAll(t *testing.T) { @@ -68,7 +79,7 @@ func TestTxnsGetAll(t *testing.T) { r := bytes.NewReader(raw) tx.Deserialize(r) - err := txdb.Put(tx) + err := txdb.Put(tx, 1, 5) if err != nil { t.Error(err) } @@ -88,7 +99,7 @@ func TestDeleteTxns(t *testing.T) { r := bytes.NewReader(raw) tx.Deserialize(r) - err := txdb.Put(tx) + err := txdb.Put(tx, 0, 1) if err != nil { t.Error(err) } @@ -102,7 +113,7 @@ func TestDeleteTxns(t *testing.T) { t.Error(err) } for _, txn := range txns { - if txn.TxHash().String() == txid.String() { + if txn.Txid == txid.String() { t.Error("Txns db delete failed") } } diff --git a/vendor/github.com/OpenBazaar/spvwallet/datastore.go b/vendor/github.com/OpenBazaar/spvwallet/datastore.go index 78ae5fdc7b..ba8638ca0e 100644 --- a/vendor/github.com/OpenBazaar/spvwallet/datastore.go +++ b/vendor/github.com/OpenBazaar/spvwallet/datastore.go @@ -41,13 +41,13 @@ type Stxos interface { type Txns interface { // Put a new transaction to the database - Put(txn *wire.MsgTx) error + Put(txn *wire.MsgTx, value, height int) error - // Fetch a tx given it's hash - Get(txid chainhash.Hash) (*wire.MsgTx, error) + // Fetch a tx and height given it's hash + Get(txid chainhash.Hash) (*wire.MsgTx, uint32, error) // Fetch all transactions from the db - GetAll() ([]*wire.MsgTx, error) + GetAll() ([]Txn, error) // Delete a transactions from the db Delete(txid *chainhash.Hash) error @@ -126,3 +126,14 @@ type Stxo struct { // The tx that consumed it SpendTxid chainhash.Hash } + +type Txn struct { + // Transaction ID + Txid string + + // The value relevant to the wallet + Value int64 + + // The height at which it was mined + Height uint32 +} diff --git a/vendor/github.com/OpenBazaar/spvwallet/eight333.go b/vendor/github.com/OpenBazaar/spvwallet/eight333.go index 8df45e62d2..7ce2a3335f 100644 --- a/vendor/github.com/OpenBazaar/spvwallet/eight333.go +++ b/vendor/github.com/OpenBazaar/spvwallet/eight333.go @@ -126,7 +126,7 @@ func (w *SPVWallet) onGetData(p *peer.Peer, m *wire.MsgGetData) { var sent int32 for _, thing := range m.InvList { if thing.Type == wire.InvTypeTx { - tx, err := w.txstore.Txns().Get(thing.Hash) + tx, _, err := w.txstore.Txns().Get(thing.Hash) if err != nil { log.Errorf("Error getting tx %s: %s", thing.Hash.String(), err.Error()) } diff --git a/vendor/github.com/OpenBazaar/spvwallet/txstore.go b/vendor/github.com/OpenBazaar/spvwallet/txstore.go index 28182fb3a4..386a4fd664 100644 --- a/vendor/github.com/OpenBazaar/spvwallet/txstore.go +++ b/vendor/github.com/OpenBazaar/spvwallet/txstore.go @@ -2,8 +2,10 @@ package spvwallet import ( "bytes" + "errors" "fmt" "github.com/btcsuite/btcd/blockchain" + "github.com/btcsuite/btcd/btcec" "github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg/chainhash" "github.com/btcsuite/btcd/txscript" @@ -12,8 +14,6 @@ import ( "github.com/btcsuite/btcutil/bloom" hd "github.com/btcsuite/btcutil/hdkeychain" "sync" - "github.com/btcsuite/btcd/btcec" - "errors" ) const FlagPrefix = 0x00 @@ -225,6 +225,7 @@ func (ts *TxStore) Ingest(tx *wire.MsgTx, height int32) (uint32, error) { cachedSha := tx.TxHash() // Iterate through all outputs of this tx, see if we gain cb := TransactionCallback{Txid: cachedSha.CloneBytes()} + value := int64(0) for i, txout := range tx.TxOut { out := TransactionOutput{ScriptPubKey: txout.PkScript, Value: txout.Value, Index: uint32(i)} for _, script := range PKscripts { @@ -241,6 +242,7 @@ func (ts *TxStore) Ingest(tx *wire.MsgTx, height int32) (uint32, error) { Op: newop, Freeze: false, } + value += newu.Value ts.Utxos().Put(newu) hits++ break @@ -265,7 +267,7 @@ func (ts *TxStore) Ingest(tx *wire.MsgTx, height int32) (uint32, error) { } } // Check stealth - if len(txout.PkScript) == 38 && txout.PkScript[0] == 0x6a && bytes.Equal(txout.PkScript[2:4], ts.stealthFlag){ + if len(txout.PkScript) == 38 && txout.PkScript[0] == 0x6a && bytes.Equal(txout.PkScript[2:4], ts.stealthFlag) { outIndex, key, err := ts.checkStealth(tx, txout.PkScript) if err == nil { newop := wire.OutPoint{ @@ -279,6 +281,7 @@ func (ts *TxStore) Ingest(tx *wire.MsgTx, height int32) (uint32, error) { Op: newop, Freeze: false, } + value += newu.Value ts.Utxos().Put(newu) hits++ ts.Keys().ImportKey(tx.TxOut[outIndex].PkScript, key) @@ -303,6 +306,7 @@ func (ts *TxStore) Ingest(tx *wire.MsgTx, height int32) (uint32, error) { ts.Stxos().Put(st) ts.Utxos().Delete(u) utxos = append(utxos[:i], utxos[i+1:]...) + value -= u.Value in := TransactionInput{ OutpointHash: u.Op.Hash.CloneBytes(), @@ -318,15 +322,19 @@ func (ts *TxStore) Ingest(tx *wire.MsgTx, height int32) (uint32, error) { // If hits is nonzero it's a relevant tx and we should store it if hits > 0 { - _, err := ts.Txns().Get(tx.TxHash()) + _, h, err := ts.Txns().Get(tx.TxHash()) if err != nil { // Callback on listeners for _, listener := range ts.listeners { listener(cb) } - ts.Txns().Put(tx) ts.PopulateAdrs() } + // Let's check the height before committing so we don't allow rogue peers to send us a lose + // tx that resets our height to zero. + if h <= 0 { + ts.Txns().Put(tx, int(value), int(height)) + } } return hits, err } diff --git a/vendor/github.com/OpenBazaar/spvwallet/wallet.go b/vendor/github.com/OpenBazaar/spvwallet/wallet.go index 3ff85b341b..52d6f08ab9 100644 --- a/vendor/github.com/OpenBazaar/spvwallet/wallet.go +++ b/vendor/github.com/OpenBazaar/spvwallet/wallet.go @@ -200,6 +200,10 @@ func (w *SPVWallet) Balance() (confirmed, unconfirmed int64) { return confirmed, unconfirmed } +func (w *SPVWallet) Transactions() ([]Txn, error) { + return w.txstore.Txns().GetAll() +} + func (w *SPVWallet) checkIfStxoIsConfirmed(utxo Utxo, stxos []Stxo) bool { for _, stxo := range stxos { if stxo.SpendTxid.IsEqual(&utxo.Op.Hash) { @@ -218,7 +222,7 @@ func (w *SPVWallet) Params() *chaincfg.Params { } func (w *SPVWallet) AcceptStealth() bool { - return true + return false } func (w *SPVWallet) AddTransactionListener(callback func(TransactionCallback)) {