Avoid slice out-of-bounds indexing panic.

The gettransaction handler was attempting to lookup the "sent-to"
address of an outgoing transaction from the transaction store (as a
wallet credit).  This is the incorrect address when sending to an
address controlled by another wallet, and panics when there are no
credits (for example, sending to another wallet without any change
address).  Instead, use the first non-change output address is used as
the address of the "send" result.

This fixes the panic reported when debugging issue #91.

While here, fix the category strings used for wallet credits to
support immature and generate (the categories for coinbase outputs).
This commit is contained in:
Josh Rickmar 2014-06-02 11:49:59 -05:00
parent 12c50f9611
commit 6597d789b7
2 changed files with 64 additions and 36 deletions

View file

@ -950,6 +950,8 @@ func GetReceivedByAccount(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
return amt, nil
}
// GetTransaction handles a gettransaction request by returning details about
// a single transaction saved by wallet.
func GetTransaction(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
// Type assert icmd to access parameters.
cmd, ok := icmd.(*btcjson.GetTransactionCmd)
@ -957,19 +959,28 @@ func GetTransaction(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
return nil, &btcjson.ErrInternal
}
txsha, err := btcwire.NewShaHashFromStr(cmd.Txid)
txSha, err := btcwire.NewShaHashFromStr(cmd.Txid)
if err != nil {
return nil, &btcjson.ErrDecodeHexString
}
accumulatedTxen := AcctMgr.GetTransaction(txsha)
accumulatedTxen := AcctMgr.GetTransaction(txSha)
if len(accumulatedTxen) == 0 {
return nil, &btcjson.ErrNoTxInfo
}
bs, err := GetCurBlock()
if err != nil {
return nil, &btcjson.Error{
Code: btcjson.ErrWallet.Code,
Message: err.Error(),
}
}
received := btcutil.Amount(0)
var debitTx *txstore.TxRecord
var debitAccount string
var targetAddr string
ret := btcjson.GetTransactionResult{
Details: []btcjson.GetTransactionDetailsResult{},
@ -991,16 +1002,16 @@ func GetTransaction(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
_, addrs, _, _ := cred.Addresses(activeNet.Params)
if len(addrs) == 1 {
addr = addrs[0].EncodeAddress()
// The first non-change output address is considered the
// target for sent transactions.
if targetAddr == "" {
targetAddr = addr
}
}
details = append(details, btcjson.GetTransactionDetailsResult{
Account: e.Account,
// TODO(oga) We don't mine for now so there
// won't be any special coinbase types. If the
// tx is a coinbase then we should handle it
// specially with the category depending on
// whether it is an orphan or in the blockchain.
Category: "receive",
Account: e.Account,
Category: cred.Category(bs.Height).String(),
Amount: cred.Amount().ToUnit(btcutil.AmountBTC),
Address: addr,
})
@ -1020,17 +1031,12 @@ func GetTransaction(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
totalAmount -= debits.InputAmount()
info := btcjson.GetTransactionDetailsResult{
Account: debitAccount,
Address: targetAddr,
Category: "send",
// negative since it is a send
Amount: (-debits.OutputAmount(true)).ToUnit(btcutil.AmountBTC),
Fee: debits.Fee().ToUnit(btcutil.AmountBTC),
}
// Errors don't matter here, as we only consider the
// case where len(addrs) == 1.
_, addrs, _, _ := debitTx.Credits()[0].Addresses(activeNet.Params)
if len(addrs) == 1 {
info.Address = addrs[0].EncodeAddress()
}
ret.Fee += info.Fee
// Add sent information to front.
ret.Details = append(ret.Details, info)
@ -1069,13 +1075,7 @@ func GetTransaction(icmd btcjson.Cmd) (interface{}, *btcjson.Error) {
Message: err.Error(),
}
}
bs, err := GetCurBlock()
if err != nil {
return nil, &btcjson.Error{
Code: btcjson.ErrWallet.Code,
Message: err.Error(),
}
}
ret.BlockIndex = int64(first.Tx.Tx().Index())
ret.BlockHash = txBlock.Hash.String()
ret.BlockTime = txBlock.Time.Unix()

View file

@ -24,7 +24,7 @@ import (
"github.com/conformal/btcutil"
)
// ToJSON returns a slice of btcjson listtransaction result types for all credits
// ToJSON returns a slice of btcjson listtransactions result types for all credits
// and debits of this transaction.
func (t *TxRecord) ToJSON(account string, chainHeight int32,
net *btcnet.Params) ([]btcjson.ListTransactionsResult, error) {
@ -90,6 +90,46 @@ func (d *Debits) ToJSON(account string, chainHeight int32,
return reply, nil
}
// CreditCategory describes the type of wallet transaction output. The category
// of "sent transactions" (debits) is always "send", and is not expressed by
// this type.
type CreditCategory int
// These constants define the possible credit categories.
const (
CreditReceive CreditCategory = iota
CreditGenerate
CreditImmature
)
// Category returns the category of the credit. The passed block chain height is
// used to distinguish immature from mature coinbase outputs.
func (c *Credit) Category(chainHeight int32) CreditCategory {
if c.IsCoinbase() {
if c.Confirmed(btcchain.CoinbaseMaturity, chainHeight) {
return CreditGenerate
}
return CreditImmature
}
return CreditReceive
}
// String returns the category as a string. This string may be used as the
// JSON string for categories as part of listtransactions and gettransaction
// RPC responses.
func (c CreditCategory) String() string {
switch c {
case CreditReceive:
return "receive"
case CreditGenerate:
return "generate"
case CreditImmature:
return "immature"
default:
return "unknown"
}
}
// ToJSON returns a slice of objects that may be marshaled as a JSON array
// of JSON objects for a listtransactions RPC reply.
func (c *Credit) ToJSON(account string, chainHeight int32,
@ -104,21 +144,9 @@ func (c *Credit) ToJSON(account string, chainHeight int32,
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{
Account: account,
Category: category,
Category: c.Category(chainHeight).String(),
Address: address,
Amount: btcutil.Amount(txout.Value).ToUnit(btcutil.AmountBTC),
TxID: c.Tx().Sha().String(),