Integrate wtxmgr package.

This commit is contained in:
Josh Rickmar 2015-04-06 15:03:24 -04:00
parent ee72c81a73
commit 56039deb94
11 changed files with 968 additions and 743 deletions

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com> * Copyright (c) 2013-2015 Conformal Systems LLC <info@conformal.com>
* *
* Permission to use, copy, modify, and distribute this software for any * Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -26,8 +26,8 @@ import (
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcrpcclient" "github.com/btcsuite/btcrpcclient"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/legacy/txstore"
"github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wtxmgr"
) )
// Client represents a persistent client connection to a bitcoin RPC server // Client represents a persistent client connection to a bitcoin RPC server
@ -159,18 +159,11 @@ type (
// BlockStamp was reorganized out of the best chain. // BlockStamp was reorganized out of the best chain.
BlockDisconnected waddrmgr.BlockStamp BlockDisconnected waddrmgr.BlockStamp
// RecvTx is a notification for a transaction which pays to a wallet // RelevantTx is a notification for a transaction which spends wallet
// address. // inputs or pays to a watched address.
RecvTx struct { RelevantTx struct {
Tx *btcutil.Tx // Index is guaranteed to be set. TxRecord *wtxmgr.TxRecord
Block *txstore.Block // nil if unmined Block *wtxmgr.BlockMeta // nil if unmined
}
// RedeemingTx is a notification for a transaction which spends an
// output controlled by the wallet.
RedeemingTx struct {
Tx *btcutil.Tx // Index is guaranteed to be set.
Block *txstore.Block // nil if unmined
} }
// RescanProgress is a notification describing the current status // RescanProgress is a notification describing the current status
@ -209,23 +202,25 @@ func (c *Client) BlockStamp() (*waddrmgr.BlockStamp, error) {
} }
} }
// parseBlock parses a btcjson definition of the block a tx is mined it to the // parseBlock parses a btcws definition of the block a tx is mined it to the
// Block structure of the txstore package, and the block index. This is done // Block structure of the wtxmgr package, and the block index. This is done
// here since btcrpcclient doesn't parse this nicely for us. // here since btcrpcclient doesn't parse this nicely for us.
func parseBlock(block *btcjson.BlockDetails) (blk *txstore.Block, idx int, err error) { func parseBlock(block *btcjson.BlockDetails) (*wtxmgr.BlockMeta, error) {
if block == nil { if block == nil {
return nil, btcutil.TxIndexUnknown, nil return nil, nil
} }
blksha, err := wire.NewShaHashFromStr(block.Hash) blksha, err := wire.NewShaHashFromStr(block.Hash)
if err != nil { if err != nil {
return nil, btcutil.TxIndexUnknown, err return nil, err
} }
blk = &txstore.Block{ blk := &wtxmgr.BlockMeta{
Block: wtxmgr.Block{
Height: block.Height, Height: block.Height,
Hash: *blksha, Hash: *blksha,
},
Time: time.Unix(block.Time, 0), Time: time.Unix(block.Time, 0),
} }
return blk, block.Index, nil return blk, nil
} }
func (c *Client) onClientConnect() { func (c *Client) onClientConnect() {
@ -242,35 +237,25 @@ func (c *Client) onBlockDisconnected(hash *wire.ShaHash, height int32) {
} }
func (c *Client) onRecvTx(tx *btcutil.Tx, block *btcjson.BlockDetails) { func (c *Client) onRecvTx(tx *btcutil.Tx, block *btcjson.BlockDetails) {
var blk *txstore.Block blk, err := parseBlock(block)
index := btcutil.TxIndexUnknown
if block != nil {
var err error
blk, index, err = parseBlock(block)
if err != nil { if err != nil {
// Log and drop improper notification. // Log and drop improper notification.
log.Errorf("recvtx notification bad block: %v", err) log.Errorf("recvtx notification bad block: %v", err)
return return
} }
rec, err := wtxmgr.NewTxRecordFromMsgTx(tx.MsgTx(), time.Now())
if err != nil {
log.Errorf("Cannot create transaction record for relevant "+
"tx: %v", err)
return
} }
tx.SetIndex(index) c.enqueueNotification <- RelevantTx{rec, blk}
c.enqueueNotification <- RecvTx{tx, blk}
} }
func (c *Client) onRedeemingTx(tx *btcutil.Tx, block *btcjson.BlockDetails) { func (c *Client) onRedeemingTx(tx *btcutil.Tx, block *btcjson.BlockDetails) {
var blk *txstore.Block // Handled exactly like recvtx notifications.
index := btcutil.TxIndexUnknown c.onRecvTx(tx, block)
if block != nil {
var err error
blk, index, err = parseBlock(block)
if err != nil {
// Log and drop improper notification.
log.Errorf("redeemingtx notification bad block: %v", err)
return
}
}
tx.SetIndex(index)
c.enqueueNotification <- RedeemingTx{tx, blk}
} }
func (c *Client) onRescanProgress(hash *wire.ShaHash, height int32, blkTime time.Time) { func (c *Client) onRescanProgress(hash *wire.ShaHash, height int32, blkTime time.Time) {

View file

@ -1,43 +0,0 @@
/*
* Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com>
*
* Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
package main
import (
"fmt"
"os"
)
// checkCreateDir checks that the path exists and is a directory.
// If path does not exist, it is created.
func checkCreateDir(path string) error {
if fi, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
// Attempt data directory creation
if err = os.MkdirAll(path, 0700); err != nil {
return fmt.Errorf("cannot create directory: %s", err)
}
} else {
return fmt.Errorf("error checking directory: %s", err)
}
} else {
if !fi.IsDir() {
return fmt.Errorf("path '%s' is not a directory", path)
}
}
return nil
}

10
log.go
View file

@ -22,8 +22,8 @@ import (
"github.com/btcsuite/btclog" "github.com/btcsuite/btclog"
"github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/legacy/txstore"
"github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/wtxmgr"
"github.com/btcsuite/seelog" "github.com/btcsuite/seelog"
) )
@ -44,7 +44,7 @@ var (
backendLog = seelog.Disabled backendLog = seelog.Disabled
log = btclog.Disabled log = btclog.Disabled
walletLog = btclog.Disabled walletLog = btclog.Disabled
txstLog = btclog.Disabled txmgrLog = btclog.Disabled
chainLog = btclog.Disabled chainLog = btclog.Disabled
) )
@ -52,7 +52,7 @@ var (
var subsystemLoggers = map[string]btclog.Logger{ var subsystemLoggers = map[string]btclog.Logger{
"BTCW": log, "BTCW": log,
"WLLT": walletLog, "WLLT": walletLog,
"TXST": txstLog, "TMGR": txmgrLog,
"CHNS": chainLog, "CHNS": chainLog,
} }
@ -87,8 +87,8 @@ func useLogger(subsystemID string, logger btclog.Logger) {
walletLog = logger walletLog = logger
wallet.UseLogger(logger) wallet.UseLogger(logger)
case "TXST": case "TXST":
txstLog = logger txmgrLog = logger
txstore.UseLogger(logger) wtxmgr.UseLogger(logger)
case "CHNS": case "CHNS":
chainLog = logger chainLog = logger
chain.UseLogger(logger) chain.UseLogger(logger)

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com> * Copyright (c) 2013-2015 Conformal Systems LLC <info@conformal.com>
* *
* Permission to use, copy, modify, and distribute this software for any * Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -45,9 +45,9 @@ import (
"github.com/btcsuite/btcrpcclient" "github.com/btcsuite/btcrpcclient"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/legacy/txstore"
"github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/wtxmgr"
"github.com/btcsuite/websocket" "github.com/btcsuite/websocket"
) )
@ -142,6 +142,24 @@ func checkDefaultAccount(account string) error {
return nil return nil
} }
// confirmed checks whether a transaction at height txHeight has met minconf
// confirmations for a blockchain at height curHeight.
func confirmed(minconf, txHeight, curHeight int32) bool {
return confirms(txHeight, curHeight) >= minconf
}
// confirms returns the number of confirmations for a transaction in a block at
// height txHeight (or -1 for an unconfirmed tx) given the chain height
// curHeight.
func confirms(txHeight, curHeight int32) int32 {
switch {
case txHeight == -1, txHeight > curHeight:
return 0
default:
return curHeight - txHeight + 1
}
}
type websocketClient struct { type websocketClient struct {
conn *websocket.Conn conn *websocket.Conn
authenticated bool authenticated bool
@ -309,10 +327,7 @@ type rpcServer struct {
// created. // created.
connectedBlocks <-chan waddrmgr.BlockStamp connectedBlocks <-chan waddrmgr.BlockStamp
disconnectedBlocks <-chan waddrmgr.BlockStamp disconnectedBlocks <-chan waddrmgr.BlockStamp
newCredits <-chan txstore.Credit relevantTxs <-chan chain.RelevantTx
newDebits <-chan txstore.Debits
minedCredits <-chan txstore.Credit
minedDebits <-chan txstore.Debits
managerLocked <-chan bool managerLocked <-chan bool
confirmedBalance <-chan btcutil.Amount confirmedBalance <-chan btcutil.Amount
unconfirmedBalance <-chan btcutil.Amount unconfirmedBalance <-chan btcutil.Amount
@ -1038,8 +1053,7 @@ type (
blockConnected waddrmgr.BlockStamp blockConnected waddrmgr.BlockStamp
blockDisconnected waddrmgr.BlockStamp blockDisconnected waddrmgr.BlockStamp
txCredit txstore.Credit relevantTx chain.RelevantTx
txDebit txstore.Debits
managerLocked bool managerLocked bool
@ -1059,36 +1073,30 @@ func (b blockDisconnected) notificationCmds(w *wallet.Wallet) []btcjson.Cmd {
return []btcjson.Cmd{n} return []btcjson.Cmd{n}
} }
func (c txCredit) notificationCmds(w *wallet.Wallet) []btcjson.Cmd { func (t relevantTx) notificationCmds(w *wallet.Wallet) []btcjson.Cmd {
blk := w.Manager.SyncedTo() syncBlock := w.Manager.SyncedTo()
acctName := waddrmgr.DefaultAccountName
if creditAccount, err := w.CreditAccount(txstore.Credit(c)); err == nil { var block *wtxmgr.Block
// acctName is defaulted to DefaultAccountName in case of an error if t.Block != nil {
acctName, _ = w.Manager.AccountName(creditAccount) block = &t.Block.Block
} }
ltr, err := txstore.Credit(c).ToJSON(acctName, blk.Height, activeNet.Params) details, err := w.TxStore.UniqueTxDetails(&t.TxRecord.Hash, block)
if err != nil { if err != nil {
log.Errorf("Cannot create notification for transaction "+ log.Errorf("Cannot fetch transaction details for "+
"credit: %v", err) "client notification: %v", err)
return nil return nil
} }
n := btcws.NewTxNtfn(acctName, &ltr) if details == nil {
return []btcjson.Cmd{n} log.Errorf("No details found for client transaction notification")
return nil
} }
func (d txDebit) notificationCmds(w *wallet.Wallet) []btcjson.Cmd { ltr := wallet.ListTransactions(details, syncBlock.Height, activeNet.Params)
blk := w.Manager.SyncedTo() ntfns := make([]btcjson.Cmd, len(ltr))
ltrs, err := txstore.Debits(d).ToJSON("", blk.Height, activeNet.Params) for i := range ntfns {
if err != nil { ntfns[i] = btcws.NewTxNtfn(ltr[i].Account, &ltr[i])
log.Errorf("Cannot create notification for transaction "+
"debits: %v", err)
return nil
} }
ns := make([]btcjson.Cmd, len(ltrs)) return ntfns
for i := range ns {
ns[i] = btcws.NewTxNtfn("", &ltrs[i])
}
return ns
} }
func (l managerLocked) notificationCmds(w *wallet.Wallet) []btcjson.Cmd { func (l managerLocked) notificationCmds(w *wallet.Wallet) []btcjson.Cmd {
@ -1121,14 +1129,8 @@ out:
s.enqueueNotification <- blockConnected(n) s.enqueueNotification <- blockConnected(n)
case n := <-s.disconnectedBlocks: case n := <-s.disconnectedBlocks:
s.enqueueNotification <- blockDisconnected(n) s.enqueueNotification <- blockDisconnected(n)
case n := <-s.newCredits: case n := <-s.relevantTxs:
s.enqueueNotification <- txCredit(n) s.enqueueNotification <- relevantTx(n)
case n := <-s.newDebits:
s.enqueueNotification <- txDebit(n)
case n := <-s.minedCredits:
s.enqueueNotification <- txCredit(n)
case n := <-s.minedDebits:
s.enqueueNotification <- txDebit(n)
case n := <-s.managerLocked: case n := <-s.managerLocked:
s.enqueueNotification <- managerLocked(n) s.enqueueNotification <- managerLocked(n)
case n := <-s.confirmedBalance: case n := <-s.confirmedBalance:
@ -1153,28 +1155,10 @@ out:
err) err)
continue continue
} }
newCredits, err := s.wallet.TxStore.ListenNewCredits() relevantTxs, err := s.wallet.ListenRelevantTxs()
if err != nil { if err != nil {
log.Errorf("Could not register for new "+ log.Errorf("Could not register for new relevant "+
"credit notifications: %v", err) "transaction notifications: %v", err)
continue
}
newDebits, err := s.wallet.TxStore.ListenNewDebits()
if err != nil {
log.Errorf("Could not register for new "+
"debit notifications: %v", err)
continue
}
minedCredits, err := s.wallet.TxStore.ListenMinedCredits()
if err != nil {
log.Errorf("Could not register for mined "+
"credit notifications: %v", err)
continue
}
minedDebits, err := s.wallet.TxStore.ListenMinedDebits()
if err != nil {
log.Errorf("Could not register for mined "+
"debit notifications: %v", err)
continue continue
} }
managerLocked, err := s.wallet.ListenLockStatus() managerLocked, err := s.wallet.ListenLockStatus()
@ -1197,10 +1181,7 @@ out:
} }
s.connectedBlocks = connectedBlocks s.connectedBlocks = connectedBlocks
s.disconnectedBlocks = disconnectedBlocks s.disconnectedBlocks = disconnectedBlocks
s.newCredits = newCredits s.relevantTxs = relevantTxs
s.newDebits = newDebits
s.minedCredits = minedCredits
s.minedDebits = minedDebits
s.managerLocked = managerLocked s.managerLocked = managerLocked
s.confirmedBalance = confirmedBalance s.confirmedBalance = confirmedBalance
s.unconfirmedBalance = unconfirmedBalance s.unconfirmedBalance = unconfirmedBalance
@ -1219,10 +1200,8 @@ func (s *rpcServer) drainNotifications() {
select { select {
case <-s.connectedBlocks: case <-s.connectedBlocks:
case <-s.disconnectedBlocks: case <-s.disconnectedBlocks:
case <-s.newCredits: case <-s.relevantTxs:
case <-s.newDebits: case <-s.managerLocked:
case <-s.minedCredits:
case <-s.minedDebits:
case <-s.confirmedBalance: case <-s.confirmedBalance:
case <-s.unconfirmedBalance: case <-s.unconfirmedBalance:
case <-s.registerWalletNtfns: case <-s.registerWalletNtfns:
@ -1694,13 +1673,13 @@ func GetBalance(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (int
var account uint32 var account uint32
var err error var err error
if cmd.Account == nil || *cmd.Account == "*" { if cmd.Account == nil || *cmd.Account == "*" {
balance, err = w.CalculateBalance(cmd.MinConf) balance, err = w.CalculateBalance(int32(cmd.MinConf))
} else { } else {
account, err = w.Manager.LookupAccount(*cmd.Account) account, err = w.Manager.LookupAccount(*cmd.Account)
if err != nil { if err != nil {
return nil, err return nil, err
} }
balance, err = w.CalculateAccountBalance(account, cmd.MinConf) balance, err = w.CalculateAccountBalance(account, int32(cmd.MinConf))
} }
if err != nil { if err != nil {
return nil, err return nil, err
@ -1964,7 +1943,7 @@ func GetReceivedByAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson
return nil, err return nil, err
} }
bal, _, err := w.TotalReceivedForAccount(account, cmd.MinConf) bal, _, err := w.TotalReceivedForAccount(account, int32(cmd.MinConf))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1981,7 +1960,7 @@ func GetReceivedByAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson
if err != nil { if err != nil {
return nil, InvalidAddressOrKeyError{err} return nil, InvalidAddressOrKeyError{err}
} }
total, err := w.TotalReceivedForAddr(addr, cmd.MinConf) total, err := w.TotalReceivedForAddr(addr, int32(cmd.MinConf))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1999,96 +1978,105 @@ func GetTransaction(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd)
return nil, btcjson.ErrDecodeHexString return nil, btcjson.ErrDecodeHexString
} }
record, ok := w.TxRecord(txSha) details, err := w.TxStore.TxDetails(txSha)
if !ok { if err != nil {
return nil, err
}
if details == nil {
return nil, btcjson.ErrNoTxInfo return nil, btcjson.ErrNoTxInfo
} }
blk := w.Manager.SyncedTo() syncBlock := w.Manager.SyncedTo()
// TODO: The serialized transaction is already in the DB, so
// reserializing can be avoided here.
var txBuf bytes.Buffer var txBuf bytes.Buffer
txBuf.Grow(record.Tx().MsgTx().SerializeSize()) txBuf.Grow(details.MsgTx.SerializeSize())
err = record.Tx().MsgTx().Serialize(&txBuf) err = details.MsgTx.Serialize(&txBuf)
if err != nil { if err != nil {
return nil, err return nil, err
} }
// TODO(jrick) set "generate" to true if this is the coinbase (if // TODO: Add a "generated" field to this result type. "generated":true
// record.Tx().Index() == 0). // is only added if the transaction is a coinbase.
ret := btcjson.GetTransactionResult{ ret := btcjson.GetTransactionResult{
TxID: txSha.String(), TxID: cmd.Txid,
Hex: hex.EncodeToString(txBuf.Bytes()), Hex: hex.EncodeToString(txBuf.Bytes()),
Time: record.Received().Unix(), Time: details.Received.Unix(),
TimeReceived: record.Received().Unix(), TimeReceived: details.Received.Unix(),
WalletConflicts: []string{}, WalletConflicts: []string{}, // Not saved
//Generated: blockchain.IsCoinBaseTx(&details.MsgTx),
} }
if record.BlockHeight != -1 { if details.Block.Height != -1 {
txBlock, err := record.Block() ret.BlockHash = details.Block.Hash.String()
if err != nil { ret.BlockTime = details.Block.Time.Unix()
return nil, err ret.Confirmations = int64(confirms(details.Block.Height, syncBlock.Height))
}
ret.BlockIndex = int64(record.Tx().Index())
ret.BlockHash = txBlock.Hash.String()
ret.BlockTime = txBlock.Time.Unix()
ret.Confirmations = int64(record.Confirmations(blk.Height))
} }
credits := record.Credits() var (
debits, err := record.Debits() debitTotal btcutil.Amount
var targetAddr *string creditTotal btcutil.Amount // Excludes change
var creditAmount btcutil.Amount outputTotal btcutil.Amount
if err != nil { fee btcutil.Amount
feeF64 float64
)
for _, deb := range details.Debits {
debitTotal += deb.Amount
}
for _, cred := range details.Credits {
if !cred.Change {
creditTotal += cred.Amount
}
}
for _, output := range details.MsgTx.TxOut {
outputTotal -= btcutil.Amount(output.Value)
}
// Fee can only be determined if every input is a debit.
if len(details.Debits) == len(details.MsgTx.TxIn) {
fee = debitTotal - outputTotal
feeF64 = fee.ToBTC()
}
if len(details.Debits) == 0 {
// Credits must be set later, but since we know the full length // Credits must be set later, but since we know the full length
// of the details slice, allocate it with the correct cap. // of the details slice, allocate it with the correct cap.
ret.Details = make([]btcjson.GetTransactionDetailsResult, 0, len(credits)) ret.Details = make([]btcjson.GetTransactionDetailsResult, 0, len(details.Credits))
} else { } else {
ret.Details = make([]btcjson.GetTransactionDetailsResult, 1, len(credits)+1) ret.Details = make([]btcjson.GetTransactionDetailsResult, 1, len(details.Credits)+1)
details := btcjson.GetTransactionDetailsResult{ ret.Details[0] = btcjson.GetTransactionDetailsResult{
Account: waddrmgr.DefaultAccountName, Account: waddrmgr.DefaultAccountName,
Category: "send", Category: "send",
// negative since it is a send Amount: (-debitTotal).ToBTC(), // negative since it is a send
Amount: (-debits.OutputAmount(true)).ToBTC(), Fee: feeF64,
Fee: debits.Fee().ToBTC(),
} }
targetAddr = &details.Address ret.Fee = feeF64
ret.Details[0] = details
ret.Fee = details.Fee
creditAmount = -debits.InputAmount()
} }
for _, cred := range record.Credits() { credCat := wallet.RecvCategory(details, syncBlock.Height).String()
for _, cred := range details.Credits {
// Change is ignored. // Change is ignored.
if cred.Change() { if cred.Change {
continue continue
} }
creditAmount += cred.Amount()
var addr string var addr string
// Errors don't matter here, as we only consider the _, addrs, _, err := txscript.ExtractPkScriptAddrs(
// case where len(addrs) == 1. details.MsgTx.TxOut[cred.Index].PkScript, activeNet.Params)
_, addrs, _, _ := cred.Addresses(activeNet.Params) if err == nil && len(addrs) == 1 {
if len(addrs) == 1 {
addr = addrs[0].EncodeAddress() addr = addrs[0].EncodeAddress()
// The first non-change output address is considered the
// target for sent transactions.
if targetAddr != nil && *targetAddr == "" {
*targetAddr = addr
}
} }
ret.Details = append(ret.Details, btcjson.GetTransactionDetailsResult{ ret.Details = append(ret.Details, btcjson.GetTransactionDetailsResult{
Account: waddrmgr.DefaultAccountName, Account: waddrmgr.DefaultAccountName,
Category: cred.Category(blk.Height).String(), Category: credCat,
Amount: cred.Amount().ToBTC(), Amount: cred.Amount.ToBTC(),
Address: addr, Address: addr,
}) })
} }
ret.Amount = creditAmount.ToBTC() ret.Amount = creditTotal.ToBTC()
return ret, nil return ret, nil
} }
@ -2107,7 +2095,7 @@ func ListAccounts(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (i
if err != nil { if err != nil {
return nil, ErrAccountNameNotFound return nil, ErrAccountNameNotFound
} }
bal, err := w.CalculateAccountBalance(account, cmd.MinConf) bal, err := w.CalculateAccountBalance(account, int32(cmd.MinConf))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2147,7 +2135,8 @@ func ListReceivedByAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjso
if err != nil { if err != nil {
return nil, ErrAccountNameNotFound return nil, ErrAccountNameNotFound
} }
bal, confirmations, err := w.TotalReceivedForAccount(account, cmd.MinConf) bal, confirmations, err := w.TotalReceivedForAccount(account,
int32(cmd.MinConf))
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2186,7 +2175,7 @@ func ListReceivedByAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjso
account string account string
} }
blk := w.Manager.SyncedTo() syncBlock := w.Manager.SyncedTo()
// Intermediate data for all addresses. // Intermediate data for all addresses.
allAddrData := make(map[string]AddrData) allAddrData := make(map[string]AddrData)
@ -2202,36 +2191,47 @@ func ListReceivedByAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjso
allAddrData[address] = AddrData{} allAddrData[address] = AddrData{}
} }
} }
for _, record := range w.TxStore.Records() {
for _, credit := range record.Credits() { var endHeight int32
confirmations := credit.Confirmations(blk.Height) if cmd.MinConf == -1 {
if !credit.Confirmed(cmd.MinConf, blk.Height) { endHeight = -1
// Not enough confirmations, skip the current block. } else {
continue endHeight = syncBlock.Height - int32(cmd.MinConf) + 1
} }
_, addresses, _, err := credit.Addresses(activeNet.Params) err := w.TxStore.RangeTransactions(0, endHeight, func(details []wtxmgr.TxDetails) (bool, error) {
confirmations := confirms(details[0].Block.Height, syncBlock.Height)
for _, tx := range details {
for _, cred := range tx.Credits {
pkScript := tx.MsgTx.TxOut[cred.Index].PkScript
_, addrs, _, err := txscript.ExtractPkScriptAddrs(
pkScript, activeNet.Params)
if err != nil { if err != nil {
// Unusable address, skip it. // Non standard script, skip.
continue continue
} }
for _, address := range addresses { for _, addr := range addrs {
addrStr := address.EncodeAddress() addrStr := addr.EncodeAddress()
addrData, ok := allAddrData[addrStr] addrData, ok := allAddrData[addrStr]
if ok { if ok {
addrData.amount += credit.Amount() addrData.amount += cred.Amount
// Always overwrite confirmations with newer ones. // Always overwrite confirmations with newer ones.
addrData.confirmations = confirmations addrData.confirmations = confirmations
} else { } else {
addrData = AddrData{ addrData = AddrData{
amount: credit.Amount(), amount: cred.Amount,
confirmations: confirmations, confirmations: confirmations,
} }
} }
addrData.tx = append(addrData.tx, credit.Tx().Sha().String()) addrData.tx = append(addrData.tx, tx.Hash.String())
allAddrData[addrStr] = addrData allAddrData[addrStr] = addrData
} }
} }
} }
return false, nil
})
if err != nil {
return nil, err
}
// Massage address data into output format. // Massage address data into output format.
numAddresses := len(allAddrData) numAddresses := len(allAddrData)
@ -2255,7 +2255,14 @@ func ListReceivedByAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjso
func ListSinceBlock(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) { func ListSinceBlock(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (interface{}, error) {
cmd := icmd.(*btcjson.ListSinceBlockCmd) cmd := icmd.(*btcjson.ListSinceBlockCmd)
height := int32(-1) syncBlock := w.Manager.SyncedTo()
// For the result we need the block hash for the last block counted
// in the blockchain due to confirmations. We send this off now so that
// it can arrive asynchronously while we figure out the rest.
gbh := chainSvr.GetBlockHashAsync(int64(syncBlock.Height) + 1 - int64(cmd.TargetConfirmations))
var start int32
if cmd.BlockHash != "" { if cmd.BlockHash != "" {
hash, err := wire.NewShaHashFromStr(cmd.BlockHash) hash, err := wire.NewShaHashFromStr(cmd.BlockHash)
if err != nil { if err != nil {
@ -2265,18 +2272,11 @@ func ListSinceBlock(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd)
if err != nil { if err != nil {
return nil, err return nil, err
} }
height = int32(block.Height()) start = int32(block.Height()) + 1
} }
end := syncBlock.Height - int32(cmd.TargetConfirmations) + 1
blk := w.Manager.SyncedTo() txInfoList, err := w.ListSinceBlock(start, end, syncBlock.Height)
// For the result we need the block hash for the last block counted
// in the blockchain due to confirmations. We send this off now so that
// it can arrive asynchronously while we figure out the rest.
gbh := chainSvr.GetBlockHashAsync(int64(blk.Height) + 1 - int64(cmd.TargetConfirmations))
txInfoList, err := w.ListSinceBlock(height, blk.Height,
cmd.TargetConfirmations)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -2377,7 +2377,7 @@ func ListUnspent(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (in
} }
} }
return w.ListUnspent(cmd.MinConf, cmd.MaxConf, addresses) return w.ListUnspent(int32(cmd.MinConf), int32(cmd.MaxConf), addresses)
} }
// LockUnspent handles the lockunspent command. // LockUnspent handles the lockunspent command.
@ -2405,9 +2405,10 @@ func LockUnspent(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (in
} }
// sendPairs is a helper routine to reduce duplicated code when creating and // sendPairs is a helper routine to reduce duplicated code when creating and
// sending payment transactions. // sending payment transactions. It returns the transaction hash in string
// format upon success.
func sendPairs(w *wallet.Wallet, chainSvr *chain.Client, cmd btcjson.Cmd, func sendPairs(w *wallet.Wallet, chainSvr *chain.Client, cmd btcjson.Cmd,
amounts map[string]btcutil.Amount, account uint32, minconf int) (interface{}, error) { amounts map[string]btcutil.Amount, account uint32, minconf int32) (string, error) {
// Create transaction, replying with an error if the creation // Create transaction, replying with an error if the creation
// was not successful. // was not successful.
@ -2415,41 +2416,44 @@ func sendPairs(w *wallet.Wallet, chainSvr *chain.Client, cmd btcjson.Cmd,
if err != nil { if err != nil {
switch { switch {
case err == wallet.ErrNonPositiveAmount: case err == wallet.ErrNonPositiveAmount:
return nil, ErrNeedPositiveAmount return "", ErrNeedPositiveAmount
case isManagerLockedError(err): case isManagerLockedError(err):
return nil, btcjson.ErrWalletUnlockNeeded return "", btcjson.ErrWalletUnlockNeeded
} }
return nil, err return "", err
} }
// Add to transaction store. // Create transaction record and insert into the db.
txr, err := w.TxStore.InsertTx(createdTx.Tx, nil) rec, err := wtxmgr.NewTxRecordFromMsgTx(createdTx.MsgTx, time.Now())
if err != nil {
log.Errorf("Cannot create record for created transaction: %v", err)
return "", btcjson.ErrInternal
}
err = w.TxStore.InsertTx(rec, nil)
if err != nil { if err != nil {
log.Errorf("Error adding sent tx history: %v", err) log.Errorf("Error adding sent tx history: %v", err)
return nil, btcjson.ErrInternal return "", btcjson.ErrInternal
}
_, err = txr.AddDebits()
if err != nil {
log.Errorf("Error adding sent tx history: %v", err)
return nil, btcjson.ErrInternal
} }
if createdTx.ChangeIndex >= 0 { if createdTx.ChangeIndex >= 0 {
_, err = txr.AddCredit(uint32(createdTx.ChangeIndex), true) err = w.TxStore.AddCredit(rec, nil, uint32(createdTx.ChangeIndex), true)
if err != nil { if err != nil {
log.Errorf("Error adding change address for sent "+ log.Errorf("Error adding change address for sent "+
"tx: %v", err) "tx: %v", err)
return nil, btcjson.ErrInternal return "", btcjson.ErrInternal
} }
} }
w.TxStore.MarkDirty()
txSha, err := chainSvr.SendRawTransaction(createdTx.Tx.MsgTx(), false) // TODO: The record already has the serialized tx, so no need to
// serialize it again.
txSha, err := chainSvr.SendRawTransaction(&rec.MsgTx, false)
if err != nil { if err != nil {
return nil, err return "", err
} }
log.Infof("Successfully sent transaction %v", txSha) txShaStr := txSha.String()
return txSha.String(), nil log.Infof("Successfully sent transaction %v", txShaStr)
return txShaStr, nil
} }
// SendFrom handles a sendfrom RPC request by creating a new transaction // SendFrom handles a sendfrom RPC request by creating a new transaction
@ -2477,7 +2481,7 @@ func SendFrom(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (inter
cmd.ToAddress: btcutil.Amount(cmd.Amount), cmd.ToAddress: btcutil.Amount(cmd.Amount),
} }
return sendPairs(w, chainSvr, cmd, pairs, account, cmd.MinConf) return sendPairs(w, chainSvr, cmd, pairs, account, int32(cmd.MinConf))
} }
// SendMany handles a sendmany RPC request by creating a new transaction // SendMany handles a sendmany RPC request by creating a new transaction
@ -2504,7 +2508,7 @@ func SendMany(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (inter
pairs[k] = btcutil.Amount(v) pairs[k] = btcutil.Amount(v)
} }
return sendPairs(w, chainSvr, cmd, pairs, account, cmd.MinConf) return sendPairs(w, chainSvr, cmd, pairs, account, int32(cmd.MinConf))
} }
// SendToAddress handles a sendtoaddress RPC request by creating a new // SendToAddress handles a sendtoaddress RPC request by creating a new

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com> * Copyright (c) 2013-2015 Conformal Systems LLC <info@conformal.com>
* *
* Permission to use, copy, modify, and distribute this software for any * Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -18,10 +18,9 @@ package wallet
import ( import (
"github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/legacy/txstore"
"github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wtxmgr"
) )
func (w *Wallet) handleChainNotifications() { func (w *Wallet) handleChainNotifications() {
@ -45,10 +44,8 @@ func (w *Wallet) handleChainNotifications() {
w.connectBlock(waddrmgr.BlockStamp(n)) w.connectBlock(waddrmgr.BlockStamp(n))
case chain.BlockDisconnected: case chain.BlockDisconnected:
err = w.disconnectBlock(waddrmgr.BlockStamp(n)) err = w.disconnectBlock(waddrmgr.BlockStamp(n))
case chain.RecvTx: case chain.RelevantTx:
err = w.addReceivedTx(n.Tx, n.Block) err = w.addRelevantTx(n.TxRecord, n.Block)
case chain.RedeemingTx:
err = w.addRedeemingTx(n.Tx, n.Block)
// The following are handled by the wallet's rescan // The following are handled by the wallet's rescan
// goroutines, so just pass them there. // goroutines, so just pass them there.
@ -116,69 +113,87 @@ func (w *Wallet) disconnectBlock(bs waddrmgr.BlockStamp) error {
return nil return nil
} }
func (w *Wallet) addReceivedTx(tx *btcutil.Tx, block *txstore.Block) error { func (w *Wallet) addRelevantTx(rec *wtxmgr.TxRecord, block *wtxmgr.BlockMeta) error {
// For every output, if it pays to a wallet address, insert the // TODO: The transaction store and address manager need to be updated
// transaction into the store (possibly moving it from unconfirmed to // together, but each operate under different namespaces and are changed
// confirmed), and add a credit record if one does not already exist. // under new transactions. This is not error safe as we lose
var txr *txstore.TxRecord // transaction semantics.
txInserted := false //
for txOutIdx, txOut := range tx.MsgTx().TxOut { // I'm unsure of the best way to solve this. Some possible solutions
// Errors don't matter here. If addrs is nil, the range below // and drawbacks:
// does nothing. //
_, addrs, _, _ := txscript.ExtractPkScriptAddrs(txOut.PkScript, // 1. Open write transactions here and pass the handle to every
w.chainParams) // waddrmr and wtxmgr method. This complicates the caller code
insert := false // everywhere, however.
for _, addr := range addrs { //
_, err := w.Manager.Address(addr) // 2. Move the wtxmgr namespace into the waddrmgr namespace, likely
if err == nil { // under its own bucket. This entire function can then be moved
insert = true // into the waddrmgr package, which updates the nested wtxmgr.
break // This removes some of separation between the components.
} //
} // 3. Use multiple wtxmgrs, one for each account, nested in the
if insert { // waddrmgr namespace. This still provides some sort of logical
if !txInserted { // separation (transaction handling remains in another package, and
var err error // is simply used by waddrmgr), but may result in duplicate
txr, err = w.TxStore.InsertTx(tx, block) // transactions being saved if they are relevant to multiple
// accounts.
//
// 4. Store wtxmgr-related details under the waddrmgr namespace, but
// solve the drawback of #3 by splitting wtxmgr to save entire
// transaction records globally for all accounts, with
// credit/debit/balance tracking per account. Each account would
// also save the relevant transaction hashes and block incidence so
// the full transaction can be loaded from the waddrmgr
// transactions bucket. This currently seems like the best
// solution.
// At the moment all notified transactions are assumed to actually be
// relevant. This assumption will not hold true when SPV support is
// added, but until then, simply insert the transaction because there
// should either be one or more relevant inputs or outputs.
err := w.TxStore.InsertTx(rec, block)
if err != nil { if err != nil {
return err return err
} }
// InsertTx may have moved a previous unmined
// tx, so mark the entire store as dirty. // Check every output to determine whether it is controlled by a wallet
w.TxStore.MarkDirty() // key. If so, mark the output as a credit.
txInserted = true for i, output := range rec.MsgTx.TxOut {
} _, addrs, _, err := txscript.ExtractPkScriptAddrs(output.PkScript,
if txr.HasCredit(txOutIdx) { w.chainParams)
if err != nil {
// Non-standard outputs are skipped.
continue continue
} }
_, err := txr.AddCredit(uint32(txOutIdx), false) for _, addr := range addrs {
if err != nil { ma, err := w.Manager.Address(addr)
return err
}
w.TxStore.MarkDirty()
}
}
bs, err := w.chainSvr.BlockStamp()
if err == nil { if err == nil {
w.notifyBalances(bs.Height) // TODO: Credits should be added with the
} // account they belong to, so wtxmgr is able to
// track per-account balances.
return nil err = w.TxStore.AddCredit(rec, block, uint32(i),
} ma.Internal())
// addRedeemingTx inserts the notified spending transaction as a debit and
// schedules the transaction store for a future file write.
func (w *Wallet) addRedeemingTx(tx *btcutil.Tx, block *txstore.Block) error {
txr, err := w.TxStore.InsertTx(tx, block)
if err != nil { if err != nil {
return err return err
} }
if _, err := txr.AddDebits(); err != nil { err = w.Manager.MarkUsed(addr)
if err != nil {
return err return err
} }
if err := w.markAddrsUsed(txr); err != nil { log.Debugf("Marked address %v used", addr)
continue
}
// Missing addresses are skipped. Other errors should
// be propagated.
code := err.(waddrmgr.ManagerError).ErrorCode
if code != waddrmgr.ErrAddressNotFound {
return err return err
} }
}
}
// TODO: Notify connected clients of the added transaction.
bs, err := w.chainSvr.BlockStamp() bs, err := w.chainSvr.BlockStamp()
if err == nil { if err == nil {

View file

@ -18,9 +18,9 @@ package wallet
import ( import (
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcwallet/legacy/txstore"
"github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/btcwallet/walletdb"
"github.com/btcsuite/btcwallet/wtxmgr"
) )
// Config is a structure used to initialize a Wallet // Config is a structure used to initialize a Wallet
@ -28,6 +28,6 @@ import (
type Config struct { type Config struct {
ChainParams *chaincfg.Params ChainParams *chaincfg.Params
Db *walletdb.DB Db *walletdb.DB
TxStore *txstore.Store TxStore *wtxmgr.Store
Waddrmgr *waddrmgr.Manager Waddrmgr *waddrmgr.Manager
} }

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (c) 2013, 2014 Conformal Systems LLC <info@conformal.com> * Copyright (c) 2013-2015 Conformal Systems LLC <info@conformal.com>
* *
* Permission to use, copy, modify, and distribute this software for any * Permission to use, copy, modify, and distribute this software for any
* purpose with or without fee is hereby granted, provided that the above * purpose with or without fee is hereby granted, provided that the above
@ -28,8 +28,8 @@ import (
"github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/legacy/txstore"
"github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wtxmgr"
) )
const ( const (
@ -109,17 +109,17 @@ const defaultFeeIncrement = 1e3
// CreatedTx holds the state of a newly-created transaction and the change // CreatedTx holds the state of a newly-created transaction and the change
// output (if one was added). // output (if one was added).
type CreatedTx struct { type CreatedTx struct {
Tx *btcutil.Tx MsgTx *wire.MsgTx
ChangeAddr btcutil.Address ChangeAddr btcutil.Address
ChangeIndex int // negative if no change ChangeIndex int // negative if no change
} }
// ByAmount defines the methods needed to satisify sort.Interface to // ByAmount defines the methods needed to satisify sort.Interface to
// sort a slice of Utxos by their amount. // sort a slice of Utxos by their amount.
type ByAmount []txstore.Credit type ByAmount []wtxmgr.Credit
func (u ByAmount) Len() int { return len(u) } func (u ByAmount) Len() int { return len(u) }
func (u ByAmount) Less(i, j int) bool { return u[i].Amount() < u[j].Amount() } func (u ByAmount) Less(i, j int) bool { return u[i].Amount < u[j].Amount }
func (u ByAmount) Swap(i, j int) { u[i], u[j] = u[j], u[i] } func (u ByAmount) Swap(i, j int) { u[i], u[j] = u[j], u[i] }
// txToPairs creates a raw transaction sending the amounts for each // txToPairs creates a raw transaction sending the amounts for each
@ -129,7 +129,7 @@ func (u ByAmount) Swap(i, j int) { u[i], u[j] = u[j], u[i] }
// to addr or as a fee for the miner are sent to a newly generated // to addr or as a fee for the miner are sent to a newly generated
// address. InsufficientFundsError is returned if there are not enough // address. InsufficientFundsError is returned if there are not enough
// eligible unspent outputs to create the transaction. // eligible unspent outputs to create the transaction.
func (w *Wallet) txToPairs(pairs map[string]btcutil.Amount, account uint32, minconf int) (*CreatedTx, error) { func (w *Wallet) txToPairs(pairs map[string]btcutil.Amount, account uint32, minconf int32) (*CreatedTx, error) {
// Address manager must be unlocked to compose transaction. Grab // Address manager must be unlocked to compose transaction. Grab
// the unlock if possible (to prevent future unlocks), or return the // the unlock if possible (to prevent future unlocks), or return the
@ -159,7 +159,7 @@ func (w *Wallet) txToPairs(pairs map[string]btcutil.Amount, account uint32, minc
// the mining fee. It then creates and returns a CreatedTx containing // the mining fee. It then creates and returns a CreatedTx containing
// the selected inputs and the given outputs, validating it (using // the selected inputs and the given outputs, validating it (using
// validateMsgTx) as well. // validateMsgTx) as well.
func createTx(eligible []txstore.Credit, func createTx(eligible []wtxmgr.Credit,
outputs map[string]btcutil.Amount, bs *waddrmgr.BlockStamp, outputs map[string]btcutil.Amount, bs *waddrmgr.BlockStamp,
feeIncrement btcutil.Amount, mgr *waddrmgr.Manager, account uint32, feeIncrement btcutil.Amount, mgr *waddrmgr.Manager, account uint32,
changeAddress func(account uint32) (btcutil.Address, error), changeAddress func(account uint32) (btcutil.Address, error),
@ -177,8 +177,8 @@ func createTx(eligible []txstore.Credit,
// Start by adding enough inputs to cover for the total amount of all // Start by adding enough inputs to cover for the total amount of all
// desired outputs. // desired outputs.
var input txstore.Credit var input wtxmgr.Credit
var inputs []txstore.Credit var inputs []wtxmgr.Credit
totalAdded := btcutil.Amount(0) totalAdded := btcutil.Amount(0)
for totalAdded < minAmount { for totalAdded < minAmount {
if len(eligible) == 0 { if len(eligible) == 0 {
@ -186,8 +186,8 @@ func createTx(eligible []txstore.Credit,
} }
input, eligible = eligible[0], eligible[1:] input, eligible = eligible[0], eligible[1:]
inputs = append(inputs, input) inputs = append(inputs, input)
msgtx.AddTxIn(wire.NewTxIn(input.OutPoint(), nil)) msgtx.AddTxIn(wire.NewTxIn(&input.OutPoint, nil))
totalAdded += input.Amount() totalAdded += input.Amount
} }
// Get an initial fee estimate based on the number of selected inputs // Get an initial fee estimate based on the number of selected inputs
@ -204,9 +204,9 @@ func createTx(eligible []txstore.Credit,
} }
input, eligible = eligible[0], eligible[1:] input, eligible = eligible[0], eligible[1:]
inputs = append(inputs, input) inputs = append(inputs, input)
msgtx.AddTxIn(wire.NewTxIn(input.OutPoint(), nil)) msgtx.AddTxIn(wire.NewTxIn(&input.OutPoint, nil))
szEst += txInEstimate szEst += txInEstimate
totalAdded += input.Amount() totalAdded += input.Amount
feeEst = minimumFee(feeIncrement, szEst, msgtx.TxOut, inputs, bs.Height, disallowFree) feeEst = minimumFee(feeIncrement, szEst, msgtx.TxOut, inputs, bs.Height, disallowFree)
} }
@ -255,9 +255,9 @@ func createTx(eligible []txstore.Credit,
} }
input, eligible = eligible[0], eligible[1:] input, eligible = eligible[0], eligible[1:]
inputs = append(inputs, input) inputs = append(inputs, input)
msgtx.AddTxIn(wire.NewTxIn(input.OutPoint(), nil)) msgtx.AddTxIn(wire.NewTxIn(&input.OutPoint, nil))
szEst += txInEstimate szEst += txInEstimate
totalAdded += input.Amount() totalAdded += input.Amount
feeEst = minimumFee(feeIncrement, szEst, msgtx.TxOut, inputs, bs.Height, disallowFree) feeEst = minimumFee(feeIncrement, szEst, msgtx.TxOut, inputs, bs.Height, disallowFree)
} }
} }
@ -267,7 +267,7 @@ func createTx(eligible []txstore.Credit,
} }
info := &CreatedTx{ info := &CreatedTx{
Tx: btcutil.NewTx(msgtx), MsgTx: msgtx,
ChangeAddr: changeAddr, ChangeAddr: changeAddr,
ChangeIndex: changeIdx, ChangeIndex: changeIdx,
} }
@ -316,44 +316,59 @@ func addOutputs(msgtx *wire.MsgTx, pairs map[string]btcutil.Amount, chainParams
return minAmount, nil return minAmount, nil
} }
func (w *Wallet) findEligibleOutputs(account uint32, minconf int, bs *waddrmgr.BlockStamp) ([]txstore.Credit, error) { func (w *Wallet) findEligibleOutputs(account uint32, minconf int32, bs *waddrmgr.BlockStamp) ([]wtxmgr.Credit, error) {
unspent, err := w.TxStore.UnspentOutputs() unspent, err := w.TxStore.UnspentOutputs()
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Filter out unspendable outputs, that is, remove those that (at this
// time) are not P2PKH outputs. Other inputs must be manually included // TODO: Eventually all of these filters (except perhaps output locking)
// in transactions and sent (for example, using createrawtransaction, // should be handled by the call to UnspentOutputs (or similar).
// signrawtransaction, and sendrawtransaction). // Because one of these filters requires matching the output script to
eligible := make([]txstore.Credit, 0, len(unspent)) // the desired account, this change depends on making wtxmgr a waddrmgr
// dependancy and requesting unspent outputs for a single account.
eligible := make([]wtxmgr.Credit, 0, len(unspent))
for i := range unspent { for i := range unspent {
switch txscript.GetScriptClass(unspent[i].TxOut().PkScript) { output := &unspent[i]
case txscript.PubKeyHashTy:
if !unspent[i].Confirmed(minconf, bs.Height) { // Only include this output if it meets the required number of
// confirmations. Coinbase transactions must have have reached
// maturity before their outputs may be spent.
if !confirmed(minconf, output.Height, bs.Height) {
continue continue
} }
// Coinbase transactions must have have reached maturity if output.FromCoinBase {
// before their outputs may be spent. const target = blockchain.CoinbaseMaturity
if unspent[i].IsCoinbase() { if !confirmed(target, output.Height, bs.Height) {
target := blockchain.CoinbaseMaturity
if !unspent[i].Confirmed(target, bs.Height) {
continue continue
} }
} }
// Locked unspent outputs are skipped. // Locked unspent outputs are skipped.
if w.LockedOutpoint(*unspent[i].OutPoint()) { if w.LockedOutpoint(output.OutPoint) {
continue continue
} }
creditAccount, err := w.CreditAccount(unspent[i]) // Filter out unspendable outputs, that is, remove those that
if err != nil { // (at this time) are not P2PKH outputs. Other inputs must be
// manually included in transactions and sent (for example,
// using createrawtransaction, signrawtransaction, and
// sendrawtransaction).
class, addrs, _, err := txscript.ExtractPkScriptAddrs(
output.PkScript, w.chainParams)
if err != nil || class != txscript.PubKeyHashTy {
continue continue
} }
if creditAccount == account {
eligible = append(eligible, unspent[i]) // Only include the output if it is associated with the passed
} // account. There should only be one address since this is a
// P2PKH script.
addrAcct, err := w.Manager.AddrAccount(addrs[0])
if err != nil || addrAcct != account {
continue
} }
eligible = append(eligible, *output)
} }
return eligible, nil return eligible, nil
} }
@ -361,7 +376,7 @@ func (w *Wallet) findEligibleOutputs(account uint32, minconf int, bs *waddrmgr.B
// signMsgTx sets the SignatureScript for every item in msgtx.TxIn. // signMsgTx sets the SignatureScript for every item in msgtx.TxIn.
// It must be called every time a msgtx is changed. // It must be called every time a msgtx is changed.
// Only P2PKH outputs are supported at this point. // Only P2PKH outputs are supported at this point.
func signMsgTx(msgtx *wire.MsgTx, prevOutputs []txstore.Credit, mgr *waddrmgr.Manager, chainParams *chaincfg.Params) error { func signMsgTx(msgtx *wire.MsgTx, prevOutputs []wtxmgr.Credit, mgr *waddrmgr.Manager, chainParams *chaincfg.Params) error {
if len(prevOutputs) != len(msgtx.TxIn) { if len(prevOutputs) != len(msgtx.TxIn) {
return fmt.Errorf( return fmt.Errorf(
"Number of prevOutputs (%d) does not match number of tx inputs (%d)", "Number of prevOutputs (%d) does not match number of tx inputs (%d)",
@ -370,7 +385,8 @@ func signMsgTx(msgtx *wire.MsgTx, prevOutputs []txstore.Credit, mgr *waddrmgr.Ma
for i, output := range prevOutputs { for i, output := range prevOutputs {
// Errors don't matter here, as we only consider the // Errors don't matter here, as we only consider the
// case where len(addrs) == 1. // case where len(addrs) == 1.
_, addrs, _, _ := output.Addresses(chainParams) _, addrs, _, _ := txscript.ExtractPkScriptAddrs(output.PkScript,
chainParams)
if len(addrs) != 1 { if len(addrs) != 1 {
continue continue
} }
@ -391,7 +407,7 @@ func signMsgTx(msgtx *wire.MsgTx, prevOutputs []txstore.Credit, mgr *waddrmgr.Ma
} }
sigscript, err := txscript.SignatureScript(msgtx, i, sigscript, err := txscript.SignatureScript(msgtx, i,
output.TxOut().PkScript, txscript.SigHashAll, privkey, output.PkScript, txscript.SigHashAll, privkey,
ai.Compressed()) ai.Compressed())
if err != nil { if err != nil {
return fmt.Errorf("cannot create sigscript: %s", err) return fmt.Errorf("cannot create sigscript: %s", err)
@ -402,9 +418,9 @@ func signMsgTx(msgtx *wire.MsgTx, prevOutputs []txstore.Credit, mgr *waddrmgr.Ma
return nil return nil
} }
func validateMsgTx(msgtx *wire.MsgTx, prevOutputs []txstore.Credit) error { func validateMsgTx(msgtx *wire.MsgTx, prevOutputs []wtxmgr.Credit) error {
for i := range msgtx.TxIn { for i := range msgtx.TxIn {
vm, err := txscript.NewEngine(prevOutputs[i].TxOut().PkScript, vm, err := txscript.NewEngine(prevOutputs[i].PkScript,
msgtx, i, txscript.StandardVerifyFlags) msgtx, i, txscript.StandardVerifyFlags)
if err != nil { if err != nil {
return fmt.Errorf("cannot create script engine: %s", err) return fmt.Errorf("cannot create script engine: %s", err)
@ -421,7 +437,7 @@ func validateMsgTx(msgtx *wire.MsgTx, prevOutputs []txstore.Credit) error {
// s less than 1 kilobyte and none of the outputs contain a value // s less than 1 kilobyte and none of the outputs contain a value
// less than 1 bitcent. Otherwise, the fee will be calculated using // less than 1 bitcent. Otherwise, the fee will be calculated using
// incr, incrementing the fee for each kilobyte of transaction. // incr, incrementing the fee for each kilobyte of transaction.
func minimumFee(incr btcutil.Amount, txLen int, outputs []*wire.TxOut, prevOutputs []txstore.Credit, height int32, disallowFree bool) btcutil.Amount { func minimumFee(incr btcutil.Amount, txLen int, outputs []*wire.TxOut, prevOutputs []wtxmgr.Credit, height int32, disallowFree bool) btcutil.Amount {
allowFree := false allowFree := false
if !disallowFree { if !disallowFree {
allowFree = allowNoFeeTx(height, prevOutputs, txLen) allowFree = allowNoFeeTx(height, prevOutputs, txLen)
@ -451,15 +467,15 @@ func minimumFee(incr btcutil.Amount, txLen int, outputs []*wire.TxOut, prevOutpu
// allowNoFeeTx calculates the transaction priority and checks that the // allowNoFeeTx calculates the transaction priority and checks that the
// priority reaches a certain threshold. If the threshhold is // priority reaches a certain threshold. If the threshhold is
// reached, a free transaction fee is allowed. // reached, a free transaction fee is allowed.
func allowNoFeeTx(curHeight int32, txouts []txstore.Credit, txSize int) bool { func allowNoFeeTx(curHeight int32, txouts []wtxmgr.Credit, txSize int) bool {
const blocksPerDayEstimate = 144.0 const blocksPerDayEstimate = 144.0
const txSizeEstimate = 250.0 const txSizeEstimate = 250.0
const threshold = btcutil.SatoshiPerBitcoin * blocksPerDayEstimate / txSizeEstimate const threshold = btcutil.SatoshiPerBitcoin * blocksPerDayEstimate / txSizeEstimate
var weightedSum int64 var weightedSum int64
for _, txout := range txouts { for _, txout := range txouts {
depth := chainDepth(txout.BlockHeight, curHeight) depth := chainDepth(txout.Height, curHeight)
weightedSum += int64(txout.Amount()) * int64(depth) weightedSum += int64(txout.Amount) * int64(depth)
} }
priority := float64(weightedSum) / float64(txSize) priority := float64(weightedSum) / float64(txSize)
return priority > threshold return priority > threshold

View file

@ -7,16 +7,18 @@ import (
"reflect" "reflect"
"sort" "sort"
"testing" "testing"
"time"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/chaincfg" "github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript" "github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain" "github.com/btcsuite/btcutil/hdkeychain"
"github.com/btcsuite/btcwallet/legacy/txstore"
"github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/btcwallet/walletdb"
_ "github.com/btcsuite/btcwallet/walletdb/bdb" _ "github.com/btcsuite/btcwallet/walletdb/bdb"
"github.com/btcsuite/btcwallet/wtxmgr"
) )
// This is a tx that transfers funds (0.371 BTC) to addresses of known privKeys. // This is a tx that transfers funds (0.371 BTC) to addresses of known privKeys.
@ -80,7 +82,7 @@ func TestCreateTx(t *testing.T) {
} }
// Pick all utxos from txInfo as eligible input. // Pick all utxos from txInfo as eligible input.
eligible := eligibleInputsFromTx(t, txInfo.hex, []uint32{1, 2, 3, 4, 5}) eligible := mockCredits(t, txInfo.hex, []uint32{1, 2, 3, 4, 5})
// Now create a new TX sending 25e6 satoshis to the following addresses: // Now create a new TX sending 25e6 satoshis to the following addresses:
outputs := map[string]btcutil.Amount{outAddr1: 15e6, outAddr2: 10e6} outputs := map[string]btcutil.Amount{outAddr1: 15e6, outAddr2: 10e6}
tx, err := createTx(eligible, outputs, bs, defaultFeeIncrement, mgr, account, tstChangeAddress, &chaincfg.TestNet3Params, false) tx, err := createTx(eligible, outputs, bs, defaultFeeIncrement, mgr, account, tstChangeAddress, &chaincfg.TestNet3Params, false)
@ -93,7 +95,7 @@ func TestCreateTx(t *testing.T) {
tx.ChangeAddr.String(), changeAddr.String()) tx.ChangeAddr.String(), changeAddr.String())
} }
msgTx := tx.Tx.MsgTx() msgTx := tx.MsgTx
if len(msgTx.TxOut) != 3 { if len(msgTx.TxOut) != 3 {
t.Fatalf("Unexpected number of outputs; got %d, want 3", len(msgTx.TxOut)) t.Fatalf("Unexpected number of outputs; got %d, want 3", len(msgTx.TxOut))
} }
@ -122,7 +124,7 @@ func TestCreateTx(t *testing.T) {
func TestCreateTxInsufficientFundsError(t *testing.T) { func TestCreateTxInsufficientFundsError(t *testing.T) {
outputs := map[string]btcutil.Amount{outAddr1: 10, outAddr2: 1e9} outputs := map[string]btcutil.Amount{outAddr1: 10, outAddr2: 1e9}
eligible := eligibleInputsFromTx(t, txInfo.hex, []uint32{1}) eligible := mockCredits(t, txInfo.hex, []uint32{1})
bs := &waddrmgr.BlockStamp{Height: 11111} bs := &waddrmgr.BlockStamp{Height: 11111}
account := uint32(0) account := uint32(0)
changeAddr, _ := btcutil.DecodeAddress("muqW4gcixv58tVbSKRC5q6CRKy8RmyLgZ5", &chaincfg.TestNet3Params) changeAddr, _ := btcutil.DecodeAddress("muqW4gcixv58tVbSKRC5q6CRKy8RmyLgZ5", &chaincfg.TestNet3Params)
@ -207,29 +209,36 @@ func newManager(t *testing.T, privKeys []string, bs *waddrmgr.BlockStamp) *waddr
return mgr return mgr
} }
// eligibleInputsFromTx decodes the given txHex and returns the outputs with // mockCredits decodes the given txHex and returns the outputs with
// the given indices as eligible inputs. // the given indices as eligible inputs.
func eligibleInputsFromTx(t *testing.T, txHex string, indices []uint32) []txstore.Credit { func mockCredits(t *testing.T, txHex string, indices []uint32) []wtxmgr.Credit {
serialized, err := hex.DecodeString(txHex) serialized, err := hex.DecodeString(txHex)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
tx, err := btcutil.NewTxFromBytes(serialized) utx, err := btcutil.NewTxFromBytes(serialized)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
s := txstore.New("/tmp/tx.bin") tx := utx.MsgTx()
r, err := s.InsertTx(tx, nil)
if err != nil { isCB := blockchain.IsCoinBaseTx(tx)
t.Fatal(err) now := time.Now()
eligible := make([]wtxmgr.Credit, len(indices))
c := wtxmgr.Credit{
OutPoint: wire.OutPoint{Hash: *utx.Sha()},
BlockMeta: wtxmgr.BlockMeta{
Block: wtxmgr.Block{Height: -1},
},
} }
eligible := make([]txstore.Credit, len(indices))
for i, idx := range indices { for i, idx := range indices {
credit, err := r.AddCredit(idx, false) c.OutPoint.Index = idx
if err != nil { c.Amount = btcutil.Amount(tx.TxOut[idx].Value)
t.Fatal(err) c.PkScript = tx.TxOut[idx].PkScript
} c.Received = now
eligible[i] = credit c.FromCoinBase = isCB
eligible[i] = c
} }
return eligible return eligible
} }

View file

@ -20,8 +20,8 @@ import (
"github.com/btcsuite/btcd/wire" "github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/chain" "github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/legacy/txstore"
"github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wtxmgr"
) )
// RescanProgressMsg reports the current progress made by a rescan for a // RescanProgressMsg reports the current progress made by a rescan for a
@ -252,10 +252,10 @@ func (w *Wallet) rescanRPCHandler() {
// a wallet. This is intended to be used to sync a wallet back up to the // a wallet. This is intended to be used to sync a wallet back up to the
// current best block in the main chain, and is considered an initial sync // current best block in the main chain, and is considered an initial sync
// rescan. // rescan.
func (w *Wallet) Rescan(addrs []btcutil.Address, unspent []txstore.Credit) error { func (w *Wallet) Rescan(addrs []btcutil.Address, unspent []wtxmgr.Credit) error {
outpoints := make([]*wire.OutPoint, len(unspent)) outpoints := make([]*wire.OutPoint, len(unspent))
for i, output := range unspent { for i, output := range unspent {
outpoints[i] = output.OutPoint() outpoints[i] = &output.OutPoint
} }
job := &RescanJob{ job := &RescanJob{

File diff suppressed because it is too large Load diff

View file

@ -31,17 +31,18 @@ import (
"github.com/btcsuite/btcutil" "github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain" "github.com/btcsuite/btcutil/hdkeychain"
"github.com/btcsuite/btcwallet/legacy/keystore" "github.com/btcsuite/btcwallet/legacy/keystore"
"github.com/btcsuite/btcwallet/legacy/txstore"
"github.com/btcsuite/btcwallet/waddrmgr" "github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet" "github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/walletdb" "github.com/btcsuite/btcwallet/walletdb"
_ "github.com/btcsuite/btcwallet/walletdb/bdb" _ "github.com/btcsuite/btcwallet/walletdb/bdb"
"github.com/btcsuite/btcwallet/wtxmgr"
"github.com/btcsuite/golangcrypto/ssh/terminal" "github.com/btcsuite/golangcrypto/ssh/terminal"
) )
// Namespace keys
var ( var (
// waddrmgrNamespaceKey is the namespace key for the waddrmgr package.
waddrmgrNamespaceKey = []byte("waddrmgr") waddrmgrNamespaceKey = []byte("waddrmgr")
wtxmgrNamespaceKey = []byte("wtxmgr")
) )
// networkDir returns the directory name of a network directory to hold wallet // networkDir returns the directory name of a network directory to hold wallet
@ -558,9 +559,30 @@ func createSimulationWallet(cfg *config) error {
return nil return nil
} }
// openDb opens and returns a *walletdb.DB (boltdb here) given the // checkCreateDir checks that the path exists and is a directory.
// directory and dbname // If path does not exist, it is created.
func openDb(directory string, dbname string) (*walletdb.DB, error) { func checkCreateDir(path string) error {
if fi, err := os.Stat(path); err != nil {
if os.IsNotExist(err) {
// Attempt data directory creation
if err = os.MkdirAll(path, 0700); err != nil {
return fmt.Errorf("cannot create directory: %s", err)
}
} else {
return fmt.Errorf("error checking directory: %s", err)
}
} else {
if !fi.IsDir() {
return fmt.Errorf("path '%s' is not a directory", path)
}
}
return nil
}
// openDb opens and returns a walletdb.DB (boltdb here) given the directory and
// dbname
func openDb(directory string, dbname string) (walletdb.DB, error) {
dbPath := filepath.Join(directory, dbname) dbPath := filepath.Join(directory, dbname)
// Ensure that the network directory exists. // Ensure that the network directory exists.
@ -569,33 +591,50 @@ func openDb(directory string, dbname string) (*walletdb.DB, error) {
} }
// Open the database using the boltdb backend. // Open the database using the boltdb backend.
db, err := walletdb.Open("bdb", dbPath) return walletdb.Open("bdb", dbPath)
if err != nil {
return nil, err
}
return &db, nil
} }
// openWaddrmgr returns an address manager given a database, namespace, // openManagers opens and returns the wallet address and transaction managers.
// public pass and the chain params // If the transaction store does not already exist, the manager is marked
// unsynced so the wallet will sync with a full rescan.
//
// It prompts for seed and private passphrase required in case of upgrades // It prompts for seed and private passphrase required in case of upgrades
func openWaddrmgr(db *walletdb.DB, namespaceKey []byte, pass string, func openManagers(db walletdb.DB, pass string) (*waddrmgr.Manager, *wtxmgr.Store, error) {
chainParams *chaincfg.Params) (*waddrmgr.Manager, error) {
// Get the namespace for the address manager. // Get the namespace for the address manager.
namespace, err := (*db).Namespace(namespaceKey) namespace, err := db.Namespace(waddrmgrNamespaceKey)
if err != nil { if err != nil {
return nil, err return nil, nil, err
} }
config := &waddrmgr.Options{ config := &waddrmgr.Options{
ObtainSeed: promptSeed, ObtainSeed: promptSeed,
ObtainPrivatePass: promptPrivPassPhrase, ObtainPrivatePass: promptPrivPassPhrase,
} }
// Open address manager and transaction store. addrMgr, err := waddrmgr.Open(namespace, []byte(pass), activeNet.Params, config)
// var txs *txstore.Store if err != nil {
return waddrmgr.Open(namespace, []byte(pass), return nil, nil, err
chainParams, config) }
namespace, err = db.Namespace(wtxmgrNamespaceKey)
if err != nil {
return nil, nil, err
}
txMgr, err := wtxmgr.Open(namespace)
if err != nil {
if !wtxmgr.IsNoExists(err) {
return nil, nil, err
}
log.Info("No recorded transaction history -- needs full rescan")
err = addrMgr.SetSyncedTo(nil)
if err != nil {
return nil, nil, err
}
txMgr, err = wtxmgr.Create(namespace)
if err != nil {
return nil, nil, err
}
}
return addrMgr, txMgr, nil
} }
// openWallet returns a wallet. The function handles opening an existing wallet // openWallet returns a wallet. The function handles opening an existing wallet
@ -610,35 +649,12 @@ func openWallet() (*wallet.Wallet, error) {
return nil, err return nil, err
} }
var txs *txstore.Store mgr, txs, err := openManagers(db, cfg.WalletPass)
mgr, err := openWaddrmgr(db, waddrmgrNamespaceKey, cfg.WalletPass,
activeNet.Params)
if err == nil {
txs, err = txstore.OpenDir(netdir)
}
if err != nil { if err != nil {
// Special case: if the address manager was successfully read
// (mgr != nil) but the transaction store was not, create a
// new txstore and write it out to disk. Write an unsynced
// manager back to disk so on future opens, the empty txstore
// is not considered fully synced.
if mgr == nil {
log.Errorf("%v", err)
return nil, err return nil, err
} }
txs = txstore.New(netdir)
txs.MarkDirty()
err = txs.WriteIfDirty()
if err != nil {
log.Errorf("%v", err)
return nil, err
}
mgr.SetSyncedTo(nil)
}
walletConfig := &wallet.Config{ walletConfig := &wallet.Config{
Db: db, Db: &db, // TODO: Remove the pointer
TxStore: txs, TxStore: txs,
Waddrmgr: mgr, Waddrmgr: mgr,
ChainParams: activeNet.Params, ChainParams: activeNet.Params,