Fix listtransactions category for coinbase outputs.

The category for a received coinbase output should be "generate" for a
mature coinbase (one that has reached btcchain.CoinbaseMaturity
confirmations), or "immature" if the required number of confirmations
has not been reached yet.  New Confirmed and Confirmations methods
have been added to the transaction store's TxRecord type to check if
the required number of confirmations have been met for coinbase
outputs.

While here, update the main package to use the new TxRecord methods,
rather than duplicating the confirmation checking code in two places.
This commit is contained in:
Josh Rickmar 2014-05-06 22:48:12 -05:00
parent 17ebf9461f
commit e39fa32487
6 changed files with 42 additions and 36 deletions

View file

@ -142,7 +142,7 @@ func (a *Account) CalculateAddressBalance(addr btcutil.Address, confirms int) fl
return 0. return 0.
} }
for _, credit := range unspent { for _, credit := range unspent {
if confirmed(confirms, credit.BlockHeight, bs.Height) { if credit.Confirmed(confirms, bs.Height) {
// We only care about the case where len(addrs) == 1, and err // We only care about the case where len(addrs) == 1, and err
// will never be non-nil in that case // will never be non-nil in that case
_, addrs, _, _ := credit.Addresses(cfg.Net()) _, addrs, _, _ := credit.Addresses(cfg.Net())
@ -188,7 +188,7 @@ func (a *Account) ListSinceBlock(since, curBlockHeight int32,
// Transactions that have not met minconf confirmations are to // Transactions that have not met minconf confirmations are to
// be ignored. // be ignored.
if !confirmed(minconf, txRecord.BlockHeight, curBlockHeight) { if !txRecord.Confirmed(minconf, curBlockHeight) {
continue continue
} }
@ -681,7 +681,7 @@ func (a *Account) TotalReceived(confirms int) (float64, error) {
} }
// Tally if the appropiate number of block confirmations have passed. // Tally if the appropiate number of block confirmations have passed.
if confirmed(confirms, c.BlockHeight, bs.Height) { if c.Confirmed(confirms, bs.Height) {
amount += c.Amount() amount += c.Amount()
} }
} }
@ -689,21 +689,3 @@ func (a *Account) TotalReceived(confirms int) (float64, error) {
return amount.ToUnit(btcutil.AmountBTC), nil return amount.ToUnit(btcutil.AmountBTC), nil
} }
// confirmed checks whether a transaction at height txHeight has met
// minconf confirmations for a blockchain at height curHeight.
func confirmed(minconf int, txHeight, curHeight int32) bool {
return confirms(txHeight, curHeight) >= int32(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
}
}

View file

@ -810,9 +810,8 @@ func (am *AccountManager) ListUnspent(minconf, maxconf int,
return nil, err return nil, err
} }
for _, credit := range unspent { for _, credit := range unspent {
confs := confirms(credit.BlockHeight, bs.Height) confs := credit.Confirmations(bs.Height)
switch { if int(confs) < minconf || int(confs) > maxconf {
case int(confs) < minconf, int(confs) > maxconf:
continue continue
} }

View file

@ -89,7 +89,7 @@ func (u ByAmount) Swap(i, j int) {
// is the total number of satoshis which would be spent by the combination // is the total number of satoshis which would be spent by the combination
// of all selected previous outputs. err will equal ErrInsufficientFunds if there // of all selected previous outputs. err will equal ErrInsufficientFunds if there
// are not enough unspent outputs to spend amt. // are not enough unspent outputs to spend amt.
func selectInputs(utxos []*tx.Credit, amt btcutil.Amount, func selectInputs(credits []*tx.Credit, amt btcutil.Amount,
minconf int) (selected []*tx.Credit, out btcutil.Amount, err error) { minconf int) (selected []*tx.Credit, out btcutil.Amount, err error) {
bs, err := GetCurBlock() bs, err := GetCurBlock()
@ -100,18 +100,18 @@ func selectInputs(utxos []*tx.Credit, amt btcutil.Amount,
// Create list of eligible unspent previous outputs to use as tx // Create list of eligible unspent previous outputs to use as tx
// inputs, and sort by the amount in reverse order so a minimum number // inputs, and sort by the amount in reverse order so a minimum number
// of inputs is needed. // of inputs is needed.
eligible := make([]*tx.Credit, 0, len(utxos)) eligible := make([]*tx.Credit, 0, len(credits))
for _, utxo := range utxos { for _, c := range credits {
if confirmed(minconf, utxo.BlockHeight, bs.Height) { if c.Confirmed(minconf, bs.Height) {
// Coinbase transactions must have have reached maturity // Coinbase transactions must have have reached maturity
// before their outputs may be spent. // before their outputs may be spent.
if utxo.IsCoinbase() { if c.IsCoinbase() {
confs := confirms(utxo.BlockHeight, bs.Height) target := btcchain.CoinbaseMaturity
if confs < btcchain.CoinbaseMaturity { if !c.Confirmed(target, bs.Height) {
continue continue
} }
} }
eligible = append(eligible, utxo) eligible = append(eligible, c)
} }
} }
sort.Sort(sort.Reverse(ByAmount(eligible))) sort.Sort(sort.Reverse(ByAmount(eligible)))

View file

@ -1057,7 +1057,7 @@ func GetTransaction(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
ret.BlockIndex = int64(first.Tx.Tx().Index()) ret.BlockIndex = int64(first.Tx.Tx().Index())
ret.BlockHash = txBlock.Hash.String() ret.BlockHash = txBlock.Hash.String()
ret.BlockTime = txBlock.Time.Unix() ret.BlockTime = txBlock.Time.Unix()
ret.Confirmations = int64(confirms(txr.BlockHeight, bs.Height)) ret.Confirmations = int64(txr.Confirmations(bs.Height))
} }
// TODO(oga) if the tx is a coinbase we should set "generated" to true. // TODO(oga) if the tx is a coinbase we should set "generated" to true.
// Since we do not mine this currently is never the case. // Since we do not mine this currently is never the case.

View file

@ -17,6 +17,7 @@
package tx package tx
import ( import (
"github.com/conformal/btcchain"
"github.com/conformal/btcjson" "github.com/conformal/btcjson"
"github.com/conformal/btcscript" "github.com/conformal/btcscript"
"github.com/conformal/btcutil" "github.com/conformal/btcutil"
@ -81,7 +82,7 @@ func (d *Debits) ToJSON(account string, chainHeight int32,
result.BlockHash = b.Hash.String() result.BlockHash = b.Hash.String()
result.BlockIndex = int64(d.Tx().Index()) result.BlockIndex = int64(d.Tx().Index())
result.BlockTime = b.Time.Unix() result.BlockTime = b.Time.Unix()
result.Confirmations = int64(confirms(b.Height, chainHeight)) result.Confirmations = int64(d.Confirmations(chainHeight))
} }
reply = append(reply, result) reply = append(reply, result)
} }
@ -103,9 +104,21 @@ func (c *Credit) ToJSON(account string, chainHeight int32,
address = addrs[0].EncodeAddress() address = addrs[0].EncodeAddress()
} }
var category string
switch {
case c.IsCoinbase():
if c.Confirmed(btcchain.CoinbaseMaturity, chainHeight) {
category = "generate"
} else {
category = "immature"
}
default:
category = "receive"
}
result := btcjson.ListTransactionsResult{ result := btcjson.ListTransactionsResult{
Account: account, Account: account,
Category: "receive", Category: category,
Address: address, Address: address,
Amount: btcutil.Amount(txout.Value).ToUnit(btcutil.AmountBTC), Amount: btcutil.Amount(txout.Value).ToUnit(btcutil.AmountBTC),
TxID: c.Tx().Sha().String(), TxID: c.Tx().Sha().String(),
@ -122,7 +135,7 @@ func (c *Credit) ToJSON(account string, chainHeight int32,
result.BlockHash = b.Hash.String() result.BlockHash = b.Hash.String()
result.BlockIndex = int64(c.Tx().Index()) result.BlockIndex = int64(c.Tx().Index())
result.BlockTime = b.Time.Unix() result.BlockTime = b.Time.Unix()
result.Confirmations = int64(confirms(b.Height, chainHeight)) result.Confirmations = int64(c.Confirmations(chainHeight))
} }
return result, nil return result, nil

View file

@ -1297,6 +1297,18 @@ func (c *Credit) Change() bool {
return c.txRecord.credits[c.OutputIndex].change return c.txRecord.credits[c.OutputIndex].change
} }
// Confirmed returns whether a transaction has reached some target number of
// confirmations, given the current best chain height.
func (t *TxRecord) Confirmed(target int, chainHeight int32) bool {
return confirmed(target, t.BlockHeight, chainHeight)
}
// Confirmations returns the total number of confirmations a transaction has
// reached, given the current best chain height.
func (t *TxRecord) Confirmations(chainHeight int32) int32 {
return confirms(t.BlockHeight, chainHeight)
}
// IsCoinbase returns whether the transaction is a coinbase. // IsCoinbase returns whether the transaction is a coinbase.
func (t *TxRecord) IsCoinbase() bool { func (t *TxRecord) IsCoinbase() bool {
return t.BlockHeight != -1 && t.BlockIndex == 0 return t.BlockHeight != -1 && t.BlockIndex == 0