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
* 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/btcrpcclient"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/legacy/txstore"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wtxmgr"
)
// Client represents a persistent client connection to a bitcoin RPC server
@ -159,18 +159,11 @@ type (
// BlockStamp was reorganized out of the best chain.
BlockDisconnected waddrmgr.BlockStamp
// RecvTx is a notification for a transaction which pays to a wallet
// address.
RecvTx struct {
Tx *btcutil.Tx // Index is guaranteed to be set.
Block *txstore.Block // 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
// RelevantTx is a notification for a transaction which spends wallet
// inputs or pays to a watched address.
RelevantTx struct {
TxRecord *wtxmgr.TxRecord
Block *wtxmgr.BlockMeta // nil if unmined
}
// 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
// Block structure of the txstore package, and the block index. This is done
// parseBlock parses a btcws definition of the block a tx is mined it to the
// Block structure of the wtxmgr package, and the block index. This is done
// 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 {
return nil, btcutil.TxIndexUnknown, nil
return nil, nil
}
blksha, err := wire.NewShaHashFromStr(block.Hash)
if err != nil {
return nil, btcutil.TxIndexUnknown, err
return nil, err
}
blk = &txstore.Block{
blk := &wtxmgr.BlockMeta{
Block: wtxmgr.Block{
Height: block.Height,
Hash: *blksha,
},
Time: time.Unix(block.Time, 0),
}
return blk, block.Index, nil
return blk, nil
}
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) {
var blk *txstore.Block
index := btcutil.TxIndexUnknown
if block != nil {
var err error
blk, index, err = parseBlock(block)
blk, err := parseBlock(block)
if err != nil {
// Log and drop improper notification.
log.Errorf("recvtx notification bad block: %v", err)
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 <- RecvTx{tx, blk}
c.enqueueNotification <- RelevantTx{rec, blk}
}
func (c *Client) onRedeemingTx(tx *btcutil.Tx, block *btcjson.BlockDetails) {
var blk *txstore.Block
index := btcutil.TxIndexUnknown
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}
// Handled exactly like recvtx notifications.
c.onRecvTx(tx, block)
}
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/btcwallet/chain"
"github.com/btcsuite/btcwallet/legacy/txstore"
"github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/wtxmgr"
"github.com/btcsuite/seelog"
)
@ -44,7 +44,7 @@ var (
backendLog = seelog.Disabled
log = btclog.Disabled
walletLog = btclog.Disabled
txstLog = btclog.Disabled
txmgrLog = btclog.Disabled
chainLog = btclog.Disabled
)
@ -52,7 +52,7 @@ var (
var subsystemLoggers = map[string]btclog.Logger{
"BTCW": log,
"WLLT": walletLog,
"TXST": txstLog,
"TMGR": txmgrLog,
"CHNS": chainLog,
}
@ -87,8 +87,8 @@ func useLogger(subsystemID string, logger btclog.Logger) {
walletLog = logger
wallet.UseLogger(logger)
case "TXST":
txstLog = logger
txstore.UseLogger(logger)
txmgrLog = logger
wtxmgr.UseLogger(logger)
case "CHNS":
chainLog = 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
* purpose with or without fee is hereby granted, provided that the above
@ -45,9 +45,9 @@ import (
"github.com/btcsuite/btcrpcclient"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/legacy/txstore"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/wtxmgr"
"github.com/btcsuite/websocket"
)
@ -142,6 +142,24 @@ func checkDefaultAccount(account string) error {
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 {
conn *websocket.Conn
authenticated bool
@ -309,10 +327,7 @@ type rpcServer struct {
// created.
connectedBlocks <-chan waddrmgr.BlockStamp
disconnectedBlocks <-chan waddrmgr.BlockStamp
newCredits <-chan txstore.Credit
newDebits <-chan txstore.Debits
minedCredits <-chan txstore.Credit
minedDebits <-chan txstore.Debits
relevantTxs <-chan chain.RelevantTx
managerLocked <-chan bool
confirmedBalance <-chan btcutil.Amount
unconfirmedBalance <-chan btcutil.Amount
@ -1038,8 +1053,7 @@ type (
blockConnected waddrmgr.BlockStamp
blockDisconnected waddrmgr.BlockStamp
txCredit txstore.Credit
txDebit txstore.Debits
relevantTx chain.RelevantTx
managerLocked bool
@ -1059,36 +1073,30 @@ func (b blockDisconnected) notificationCmds(w *wallet.Wallet) []btcjson.Cmd {
return []btcjson.Cmd{n}
}
func (c txCredit) notificationCmds(w *wallet.Wallet) []btcjson.Cmd {
blk := w.Manager.SyncedTo()
acctName := waddrmgr.DefaultAccountName
if creditAccount, err := w.CreditAccount(txstore.Credit(c)); err == nil {
// acctName is defaulted to DefaultAccountName in case of an error
acctName, _ = w.Manager.AccountName(creditAccount)
func (t relevantTx) notificationCmds(w *wallet.Wallet) []btcjson.Cmd {
syncBlock := w.Manager.SyncedTo()
var block *wtxmgr.Block
if t.Block != nil {
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 {
log.Errorf("Cannot create notification for transaction "+
"credit: %v", err)
log.Errorf("Cannot fetch transaction details for "+
"client notification: %v", err)
return nil
}
n := btcws.NewTxNtfn(acctName, &ltr)
return []btcjson.Cmd{n}
if details == nil {
log.Errorf("No details found for client transaction notification")
return nil
}
func (d txDebit) notificationCmds(w *wallet.Wallet) []btcjson.Cmd {
blk := w.Manager.SyncedTo()
ltrs, err := txstore.Debits(d).ToJSON("", blk.Height, activeNet.Params)
if err != nil {
log.Errorf("Cannot create notification for transaction "+
"debits: %v", err)
return nil
ltr := wallet.ListTransactions(details, syncBlock.Height, activeNet.Params)
ntfns := make([]btcjson.Cmd, len(ltr))
for i := range ntfns {
ntfns[i] = btcws.NewTxNtfn(ltr[i].Account, &ltr[i])
}
ns := make([]btcjson.Cmd, len(ltrs))
for i := range ns {
ns[i] = btcws.NewTxNtfn("", &ltrs[i])
}
return ns
return ntfns
}
func (l managerLocked) notificationCmds(w *wallet.Wallet) []btcjson.Cmd {
@ -1121,14 +1129,8 @@ out:
s.enqueueNotification <- blockConnected(n)
case n := <-s.disconnectedBlocks:
s.enqueueNotification <- blockDisconnected(n)
case n := <-s.newCredits:
s.enqueueNotification <- txCredit(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.relevantTxs:
s.enqueueNotification <- relevantTx(n)
case n := <-s.managerLocked:
s.enqueueNotification <- managerLocked(n)
case n := <-s.confirmedBalance:
@ -1153,28 +1155,10 @@ out:
err)
continue
}
newCredits, err := s.wallet.TxStore.ListenNewCredits()
relevantTxs, err := s.wallet.ListenRelevantTxs()
if err != nil {
log.Errorf("Could not register for new "+
"credit 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)
log.Errorf("Could not register for new relevant "+
"transaction notifications: %v", err)
continue
}
managerLocked, err := s.wallet.ListenLockStatus()
@ -1197,10 +1181,7 @@ out:
}
s.connectedBlocks = connectedBlocks
s.disconnectedBlocks = disconnectedBlocks
s.newCredits = newCredits
s.newDebits = newDebits
s.minedCredits = minedCredits
s.minedDebits = minedDebits
s.relevantTxs = relevantTxs
s.managerLocked = managerLocked
s.confirmedBalance = confirmedBalance
s.unconfirmedBalance = unconfirmedBalance
@ -1219,10 +1200,8 @@ func (s *rpcServer) drainNotifications() {
select {
case <-s.connectedBlocks:
case <-s.disconnectedBlocks:
case <-s.newCredits:
case <-s.newDebits:
case <-s.minedCredits:
case <-s.minedDebits:
case <-s.relevantTxs:
case <-s.managerLocked:
case <-s.confirmedBalance:
case <-s.unconfirmedBalance:
case <-s.registerWalletNtfns:
@ -1694,13 +1673,13 @@ func GetBalance(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (int
var account uint32
var err error
if cmd.Account == nil || *cmd.Account == "*" {
balance, err = w.CalculateBalance(cmd.MinConf)
balance, err = w.CalculateBalance(int32(cmd.MinConf))
} else {
account, err = w.Manager.LookupAccount(*cmd.Account)
if err != nil {
return nil, err
}
balance, err = w.CalculateAccountBalance(account, cmd.MinConf)
balance, err = w.CalculateAccountBalance(account, int32(cmd.MinConf))
}
if err != nil {
return nil, err
@ -1964,7 +1943,7 @@ func GetReceivedByAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson
return nil, err
}
bal, _, err := w.TotalReceivedForAccount(account, cmd.MinConf)
bal, _, err := w.TotalReceivedForAccount(account, int32(cmd.MinConf))
if err != nil {
return nil, err
}
@ -1981,7 +1960,7 @@ func GetReceivedByAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson
if err != nil {
return nil, InvalidAddressOrKeyError{err}
}
total, err := w.TotalReceivedForAddr(addr, cmd.MinConf)
total, err := w.TotalReceivedForAddr(addr, int32(cmd.MinConf))
if err != nil {
return nil, err
}
@ -1999,96 +1978,105 @@ func GetTransaction(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd)
return nil, btcjson.ErrDecodeHexString
}
record, ok := w.TxRecord(txSha)
if !ok {
details, err := w.TxStore.TxDetails(txSha)
if err != nil {
return nil, err
}
if details == nil {
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
txBuf.Grow(record.Tx().MsgTx().SerializeSize())
err = record.Tx().MsgTx().Serialize(&txBuf)
txBuf.Grow(details.MsgTx.SerializeSize())
err = details.MsgTx.Serialize(&txBuf)
if err != nil {
return nil, err
}
// TODO(jrick) set "generate" to true if this is the coinbase (if
// record.Tx().Index() == 0).
// TODO: Add a "generated" field to this result type. "generated":true
// is only added if the transaction is a coinbase.
ret := btcjson.GetTransactionResult{
TxID: txSha.String(),
TxID: cmd.Txid,
Hex: hex.EncodeToString(txBuf.Bytes()),
Time: record.Received().Unix(),
TimeReceived: record.Received().Unix(),
WalletConflicts: []string{},
Time: details.Received.Unix(),
TimeReceived: details.Received.Unix(),
WalletConflicts: []string{}, // Not saved
//Generated: blockchain.IsCoinBaseTx(&details.MsgTx),
}
if record.BlockHeight != -1 {
txBlock, err := record.Block()
if err != nil {
return nil, err
}
ret.BlockIndex = int64(record.Tx().Index())
ret.BlockHash = txBlock.Hash.String()
ret.BlockTime = txBlock.Time.Unix()
ret.Confirmations = int64(record.Confirmations(blk.Height))
if details.Block.Height != -1 {
ret.BlockHash = details.Block.Hash.String()
ret.BlockTime = details.Block.Time.Unix()
ret.Confirmations = int64(confirms(details.Block.Height, syncBlock.Height))
}
credits := record.Credits()
debits, err := record.Debits()
var targetAddr *string
var creditAmount btcutil.Amount
if err != nil {
var (
debitTotal btcutil.Amount
creditTotal btcutil.Amount // Excludes change
outputTotal btcutil.Amount
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
// 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 {
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,
Category: "send",
// negative since it is a send
Amount: (-debits.OutputAmount(true)).ToBTC(),
Fee: debits.Fee().ToBTC(),
Amount: (-debitTotal).ToBTC(), // negative since it is a send
Fee: feeF64,
}
targetAddr = &details.Address
ret.Details[0] = details
ret.Fee = details.Fee
creditAmount = -debits.InputAmount()
ret.Fee = feeF64
}
for _, cred := range record.Credits() {
credCat := wallet.RecvCategory(details, syncBlock.Height).String()
for _, cred := range details.Credits {
// Change is ignored.
if cred.Change() {
if cred.Change {
continue
}
creditAmount += cred.Amount()
var addr string
// Errors don't matter here, as we only consider the
// case where len(addrs) == 1.
_, addrs, _, _ := cred.Addresses(activeNet.Params)
if len(addrs) == 1 {
_, addrs, _, err := txscript.ExtractPkScriptAddrs(
details.MsgTx.TxOut[cred.Index].PkScript, activeNet.Params)
if err == nil && len(addrs) == 1 {
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{
Account: waddrmgr.DefaultAccountName,
Category: cred.Category(blk.Height).String(),
Amount: cred.Amount().ToBTC(),
Category: credCat,
Amount: cred.Amount.ToBTC(),
Address: addr,
})
}
ret.Amount = creditAmount.ToBTC()
ret.Amount = creditTotal.ToBTC()
return ret, nil
}
@ -2107,7 +2095,7 @@ func ListAccounts(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (i
if err != nil {
return nil, ErrAccountNameNotFound
}
bal, err := w.CalculateAccountBalance(account, cmd.MinConf)
bal, err := w.CalculateAccountBalance(account, int32(cmd.MinConf))
if err != nil {
return nil, err
}
@ -2147,7 +2135,8 @@ func ListReceivedByAccount(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjso
if err != nil {
return nil, ErrAccountNameNotFound
}
bal, confirmations, err := w.TotalReceivedForAccount(account, cmd.MinConf)
bal, confirmations, err := w.TotalReceivedForAccount(account,
int32(cmd.MinConf))
if err != nil {
return nil, err
}
@ -2186,7 +2175,7 @@ func ListReceivedByAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjso
account string
}
blk := w.Manager.SyncedTo()
syncBlock := w.Manager.SyncedTo()
// Intermediate data for all addresses.
allAddrData := make(map[string]AddrData)
@ -2202,36 +2191,47 @@ func ListReceivedByAddress(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjso
allAddrData[address] = AddrData{}
}
}
for _, record := range w.TxStore.Records() {
for _, credit := range record.Credits() {
confirmations := credit.Confirmations(blk.Height)
if !credit.Confirmed(cmd.MinConf, blk.Height) {
// Not enough confirmations, skip the current block.
continue
var endHeight int32
if cmd.MinConf == -1 {
endHeight = -1
} else {
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 {
// Unusable address, skip it.
// Non standard script, skip.
continue
}
for _, address := range addresses {
addrStr := address.EncodeAddress()
for _, addr := range addrs {
addrStr := addr.EncodeAddress()
addrData, ok := allAddrData[addrStr]
if ok {
addrData.amount += credit.Amount()
addrData.amount += cred.Amount
// Always overwrite confirmations with newer ones.
addrData.confirmations = confirmations
} else {
addrData = AddrData{
amount: credit.Amount(),
amount: cred.Amount,
confirmations: confirmations,
}
}
addrData.tx = append(addrData.tx, credit.Tx().Sha().String())
addrData.tx = append(addrData.tx, tx.Hash.String())
allAddrData[addrStr] = addrData
}
}
}
return false, nil
})
if err != nil {
return nil, err
}
// Massage address data into output format.
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) {
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 != "" {
hash, err := wire.NewShaHashFromStr(cmd.BlockHash)
if err != nil {
@ -2265,18 +2272,11 @@ func ListSinceBlock(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd)
if err != nil {
return nil, err
}
height = int32(block.Height())
start = int32(block.Height()) + 1
}
end := syncBlock.Height - int32(cmd.TargetConfirmations) + 1
blk := 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(blk.Height) + 1 - int64(cmd.TargetConfirmations))
txInfoList, err := w.ListSinceBlock(height, blk.Height,
cmd.TargetConfirmations)
txInfoList, err := w.ListSinceBlock(start, end, syncBlock.Height)
if err != nil {
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.
@ -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
// 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,
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
// was not successful.
@ -2415,41 +2416,44 @@ func sendPairs(w *wallet.Wallet, chainSvr *chain.Client, cmd btcjson.Cmd,
if err != nil {
switch {
case err == wallet.ErrNonPositiveAmount:
return nil, ErrNeedPositiveAmount
return "", ErrNeedPositiveAmount
case isManagerLockedError(err):
return nil, btcjson.ErrWalletUnlockNeeded
return "", btcjson.ErrWalletUnlockNeeded
}
return nil, err
return "", err
}
// Add to transaction store.
txr, err := w.TxStore.InsertTx(createdTx.Tx, nil)
// Create transaction record and insert into the db.
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 {
log.Errorf("Error adding sent tx history: %v", err)
return nil, btcjson.ErrInternal
}
_, err = txr.AddDebits()
if err != nil {
log.Errorf("Error adding sent tx history: %v", err)
return nil, btcjson.ErrInternal
return "", btcjson.ErrInternal
}
if createdTx.ChangeIndex >= 0 {
_, err = txr.AddCredit(uint32(createdTx.ChangeIndex), true)
err = w.TxStore.AddCredit(rec, nil, uint32(createdTx.ChangeIndex), true)
if err != nil {
log.Errorf("Error adding change address for sent "+
"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 {
return nil, err
return "", err
}
log.Infof("Successfully sent transaction %v", txSha)
return txSha.String(), nil
txShaStr := txSha.String()
log.Infof("Successfully sent transaction %v", txShaStr)
return txShaStr, nil
}
// 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),
}
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
@ -2504,7 +2508,7 @@ func SendMany(w *wallet.Wallet, chainSvr *chain.Client, icmd btcjson.Cmd) (inter
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

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
* purpose with or without fee is hereby granted, provided that the above
@ -18,10 +18,9 @@ package wallet
import (
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/legacy/txstore"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wtxmgr"
)
func (w *Wallet) handleChainNotifications() {
@ -45,10 +44,8 @@ func (w *Wallet) handleChainNotifications() {
w.connectBlock(waddrmgr.BlockStamp(n))
case chain.BlockDisconnected:
err = w.disconnectBlock(waddrmgr.BlockStamp(n))
case chain.RecvTx:
err = w.addReceivedTx(n.Tx, n.Block)
case chain.RedeemingTx:
err = w.addRedeemingTx(n.Tx, n.Block)
case chain.RelevantTx:
err = w.addRelevantTx(n.TxRecord, n.Block)
// The following are handled by the wallet's rescan
// goroutines, so just pass them there.
@ -116,69 +113,87 @@ func (w *Wallet) disconnectBlock(bs waddrmgr.BlockStamp) error {
return nil
}
func (w *Wallet) addReceivedTx(tx *btcutil.Tx, block *txstore.Block) error {
// For every output, if it pays to a wallet address, insert the
// transaction into the store (possibly moving it from unconfirmed to
// confirmed), and add a credit record if one does not already exist.
var txr *txstore.TxRecord
txInserted := false
for txOutIdx, txOut := range tx.MsgTx().TxOut {
// Errors don't matter here. If addrs is nil, the range below
// does nothing.
_, addrs, _, _ := txscript.ExtractPkScriptAddrs(txOut.PkScript,
w.chainParams)
insert := false
for _, addr := range addrs {
_, err := w.Manager.Address(addr)
if err == nil {
insert = true
break
}
}
if insert {
if !txInserted {
var err error
txr, err = w.TxStore.InsertTx(tx, block)
func (w *Wallet) addRelevantTx(rec *wtxmgr.TxRecord, block *wtxmgr.BlockMeta) error {
// TODO: The transaction store and address manager need to be updated
// together, but each operate under different namespaces and are changed
// under new transactions. This is not error safe as we lose
// transaction semantics.
//
// I'm unsure of the best way to solve this. Some possible solutions
// and drawbacks:
//
// 1. Open write transactions here and pass the handle to every
// waddrmr and wtxmgr method. This complicates the caller code
// everywhere, however.
//
// 2. Move the wtxmgr namespace into the waddrmgr namespace, likely
// under its own bucket. This entire function can then be moved
// into the waddrmgr package, which updates the nested wtxmgr.
// This removes some of separation between the components.
//
// 3. Use multiple wtxmgrs, one for each account, nested in the
// waddrmgr namespace. This still provides some sort of logical
// separation (transaction handling remains in another package, and
// is simply used by waddrmgr), but may result in duplicate
// 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 {
return err
}
// InsertTx may have moved a previous unmined
// tx, so mark the entire store as dirty.
w.TxStore.MarkDirty()
txInserted = true
}
if txr.HasCredit(txOutIdx) {
// Check every output to determine whether it is controlled by a wallet
// key. If so, mark the output as a credit.
for i, output := range rec.MsgTx.TxOut {
_, addrs, _, err := txscript.ExtractPkScriptAddrs(output.PkScript,
w.chainParams)
if err != nil {
// Non-standard outputs are skipped.
continue
}
_, err := txr.AddCredit(uint32(txOutIdx), false)
if err != nil {
return err
}
w.TxStore.MarkDirty()
}
}
bs, err := w.chainSvr.BlockStamp()
for _, addr := range addrs {
ma, err := w.Manager.Address(addr)
if err == nil {
w.notifyBalances(bs.Height)
}
return nil
}
// 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)
// TODO: Credits should be added with the
// account they belong to, so wtxmgr is able to
// track per-account balances.
err = w.TxStore.AddCredit(rec, block, uint32(i),
ma.Internal())
if err != nil {
return err
}
if _, err := txr.AddDebits(); err != nil {
err = w.Manager.MarkUsed(addr)
if err != nil {
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
}
}
}
// TODO: Notify connected clients of the added transaction.
bs, err := w.chainSvr.BlockStamp()
if err == nil {

View file

@ -18,9 +18,9 @@ package wallet
import (
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcwallet/legacy/txstore"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/walletdb"
"github.com/btcsuite/btcwallet/wtxmgr"
)
// Config is a structure used to initialize a Wallet
@ -28,6 +28,6 @@ import (
type Config struct {
ChainParams *chaincfg.Params
Db *walletdb.DB
TxStore *txstore.Store
TxStore *wtxmgr.Store
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
* 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/wire"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/legacy/txstore"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wtxmgr"
)
const (
@ -109,17 +109,17 @@ const defaultFeeIncrement = 1e3
// CreatedTx holds the state of a newly-created transaction and the change
// output (if one was added).
type CreatedTx struct {
Tx *btcutil.Tx
MsgTx *wire.MsgTx
ChangeAddr btcutil.Address
ChangeIndex int // negative if no change
}
// ByAmount defines the methods needed to satisify sort.Interface to
// 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) 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] }
// 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
// address. InsufficientFundsError is returned if there are not enough
// 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
// 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 selected inputs and the given outputs, validating it (using
// validateMsgTx) as well.
func createTx(eligible []txstore.Credit,
func createTx(eligible []wtxmgr.Credit,
outputs map[string]btcutil.Amount, bs *waddrmgr.BlockStamp,
feeIncrement btcutil.Amount, mgr *waddrmgr.Manager, account uint32,
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
// desired outputs.
var input txstore.Credit
var inputs []txstore.Credit
var input wtxmgr.Credit
var inputs []wtxmgr.Credit
totalAdded := btcutil.Amount(0)
for totalAdded < minAmount {
if len(eligible) == 0 {
@ -186,8 +186,8 @@ func createTx(eligible []txstore.Credit,
}
input, eligible = eligible[0], eligible[1:]
inputs = append(inputs, input)
msgtx.AddTxIn(wire.NewTxIn(input.OutPoint(), nil))
totalAdded += input.Amount()
msgtx.AddTxIn(wire.NewTxIn(&input.OutPoint, nil))
totalAdded += input.Amount
}
// 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:]
inputs = append(inputs, input)
msgtx.AddTxIn(wire.NewTxIn(input.OutPoint(), nil))
msgtx.AddTxIn(wire.NewTxIn(&input.OutPoint, nil))
szEst += txInEstimate
totalAdded += input.Amount()
totalAdded += input.Amount
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:]
inputs = append(inputs, input)
msgtx.AddTxIn(wire.NewTxIn(input.OutPoint(), nil))
msgtx.AddTxIn(wire.NewTxIn(&input.OutPoint, nil))
szEst += txInEstimate
totalAdded += input.Amount()
totalAdded += input.Amount
feeEst = minimumFee(feeIncrement, szEst, msgtx.TxOut, inputs, bs.Height, disallowFree)
}
}
@ -267,7 +267,7 @@ func createTx(eligible []txstore.Credit,
}
info := &CreatedTx{
Tx: btcutil.NewTx(msgtx),
MsgTx: msgtx,
ChangeAddr: changeAddr,
ChangeIndex: changeIdx,
}
@ -316,44 +316,59 @@ func addOutputs(msgtx *wire.MsgTx, pairs map[string]btcutil.Amount, chainParams
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()
if err != nil {
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
// in transactions and sent (for example, using createrawtransaction,
// signrawtransaction, and sendrawtransaction).
eligible := make([]txstore.Credit, 0, len(unspent))
// TODO: Eventually all of these filters (except perhaps output locking)
// should be handled by the call to UnspentOutputs (or similar).
// Because one of these filters requires matching the output script to
// 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 {
switch txscript.GetScriptClass(unspent[i].TxOut().PkScript) {
case txscript.PubKeyHashTy:
if !unspent[i].Confirmed(minconf, bs.Height) {
output := &unspent[i]
// 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
}
// Coinbase transactions must have have reached maturity
// before their outputs may be spent.
if unspent[i].IsCoinbase() {
target := blockchain.CoinbaseMaturity
if !unspent[i].Confirmed(target, bs.Height) {
if output.FromCoinBase {
const target = blockchain.CoinbaseMaturity
if !confirmed(target, output.Height, bs.Height) {
continue
}
}
// Locked unspent outputs are skipped.
if w.LockedOutpoint(*unspent[i].OutPoint()) {
if w.LockedOutpoint(output.OutPoint) {
continue
}
creditAccount, err := w.CreditAccount(unspent[i])
if err != nil {
// Filter out unspendable outputs, that is, remove those that
// (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
}
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
}
@ -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.
// It must be called every time a msgtx is changed.
// 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) {
return fmt.Errorf(
"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 {
// Errors don't matter here, as we only consider the
// case where len(addrs) == 1.
_, addrs, _, _ := output.Addresses(chainParams)
_, addrs, _, _ := txscript.ExtractPkScriptAddrs(output.PkScript,
chainParams)
if len(addrs) != 1 {
continue
}
@ -391,7 +407,7 @@ func signMsgTx(msgtx *wire.MsgTx, prevOutputs []txstore.Credit, mgr *waddrmgr.Ma
}
sigscript, err := txscript.SignatureScript(msgtx, i,
output.TxOut().PkScript, txscript.SigHashAll, privkey,
output.PkScript, txscript.SigHashAll, privkey,
ai.Compressed())
if err != nil {
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
}
func validateMsgTx(msgtx *wire.MsgTx, prevOutputs []txstore.Credit) error {
func validateMsgTx(msgtx *wire.MsgTx, prevOutputs []wtxmgr.Credit) error {
for i := range msgtx.TxIn {
vm, err := txscript.NewEngine(prevOutputs[i].TxOut().PkScript,
vm, err := txscript.NewEngine(prevOutputs[i].PkScript,
msgtx, i, txscript.StandardVerifyFlags)
if err != nil {
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
// less than 1 bitcent. Otherwise, the fee will be calculated using
// 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
if !disallowFree {
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
// priority reaches a certain threshold. If the threshhold is
// 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 txSizeEstimate = 250.0
const threshold = btcutil.SatoshiPerBitcoin * blocksPerDayEstimate / txSizeEstimate
var weightedSum int64
for _, txout := range txouts {
depth := chainDepth(txout.BlockHeight, curHeight)
weightedSum += int64(txout.Amount()) * int64(depth)
depth := chainDepth(txout.Height, curHeight)
weightedSum += int64(txout.Amount) * int64(depth)
}
priority := float64(weightedSum) / float64(txSize)
return priority > threshold

View file

@ -7,16 +7,18 @@ import (
"reflect"
"sort"
"testing"
"time"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcutil/hdkeychain"
"github.com/btcsuite/btcwallet/legacy/txstore"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/walletdb"
_ "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.
@ -80,7 +82,7 @@ func TestCreateTx(t *testing.T) {
}
// 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:
outputs := map[string]btcutil.Amount{outAddr1: 15e6, outAddr2: 10e6}
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())
}
msgTx := tx.Tx.MsgTx()
msgTx := tx.MsgTx
if len(msgTx.TxOut) != 3 {
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) {
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}
account := uint32(0)
changeAddr, _ := btcutil.DecodeAddress("muqW4gcixv58tVbSKRC5q6CRKy8RmyLgZ5", &chaincfg.TestNet3Params)
@ -207,29 +209,36 @@ func newManager(t *testing.T, privKeys []string, bs *waddrmgr.BlockStamp) *waddr
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.
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)
if err != nil {
t.Fatal(err)
}
tx, err := btcutil.NewTxFromBytes(serialized)
utx, err := btcutil.NewTxFromBytes(serialized)
if err != nil {
t.Fatal(err)
}
s := txstore.New("/tmp/tx.bin")
r, err := s.InsertTx(tx, nil)
if err != nil {
t.Fatal(err)
tx := utx.MsgTx()
isCB := blockchain.IsCoinBaseTx(tx)
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 {
credit, err := r.AddCredit(idx, false)
if err != nil {
t.Fatal(err)
}
eligible[i] = credit
c.OutPoint.Index = idx
c.Amount = btcutil.Amount(tx.TxOut[idx].Value)
c.PkScript = tx.TxOut[idx].PkScript
c.Received = now
c.FromCoinBase = isCB
eligible[i] = c
}
return eligible
}

View file

@ -20,8 +20,8 @@ import (
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
"github.com/btcsuite/btcwallet/chain"
"github.com/btcsuite/btcwallet/legacy/txstore"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wtxmgr"
)
// 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
// current best block in the main chain, and is considered an initial sync
// 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))
for i, output := range unspent {
outpoints[i] = output.OutPoint()
outpoints[i] = &output.OutPoint
}
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/hdkeychain"
"github.com/btcsuite/btcwallet/legacy/keystore"
"github.com/btcsuite/btcwallet/legacy/txstore"
"github.com/btcsuite/btcwallet/waddrmgr"
"github.com/btcsuite/btcwallet/wallet"
"github.com/btcsuite/btcwallet/walletdb"
_ "github.com/btcsuite/btcwallet/walletdb/bdb"
"github.com/btcsuite/btcwallet/wtxmgr"
"github.com/btcsuite/golangcrypto/ssh/terminal"
)
// Namespace keys
var (
// waddrmgrNamespaceKey is the namespace key for the waddrmgr package.
waddrmgrNamespaceKey = []byte("waddrmgr")
wtxmgrNamespaceKey = []byte("wtxmgr")
)
// networkDir returns the directory name of a network directory to hold wallet
@ -558,9 +559,30 @@ func createSimulationWallet(cfg *config) error {
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) {
// 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
}
// 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)
// 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.
db, err := walletdb.Open("bdb", dbPath)
if err != nil {
return nil, err
}
return &db, nil
return walletdb.Open("bdb", dbPath)
}
// openWaddrmgr returns an address manager given a database, namespace,
// public pass and the chain params
// openManagers opens and returns the wallet address and transaction managers.
// 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
func openWaddrmgr(db *walletdb.DB, namespaceKey []byte, pass string,
chainParams *chaincfg.Params) (*waddrmgr.Manager, error) {
func openManagers(db walletdb.DB, pass string) (*waddrmgr.Manager, *wtxmgr.Store, error) {
// Get the namespace for the address manager.
namespace, err := (*db).Namespace(namespaceKey)
namespace, err := db.Namespace(waddrmgrNamespaceKey)
if err != nil {
return nil, err
return nil, nil, err
}
config := &waddrmgr.Options{
ObtainSeed: promptSeed,
ObtainPrivatePass: promptPrivPassPhrase,
}
// Open address manager and transaction store.
// var txs *txstore.Store
return waddrmgr.Open(namespace, []byte(pass),
chainParams, config)
addrMgr, err := waddrmgr.Open(namespace, []byte(pass), activeNet.Params, config)
if err != nil {
return nil, nil, err
}
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
@ -610,35 +649,12 @@ func openWallet() (*wallet.Wallet, error) {
return nil, err
}
var txs *txstore.Store
mgr, err := openWaddrmgr(db, waddrmgrNamespaceKey, cfg.WalletPass,
activeNet.Params)
if err == nil {
txs, err = txstore.OpenDir(netdir)
}
mgr, txs, err := openManagers(db, cfg.WalletPass)
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
}
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{
Db: db,
Db: &db, // TODO: Remove the pointer
TxStore: txs,
Waddrmgr: mgr,
ChainParams: activeNet.Params,