Skip to content

Commit

Permalink
Consolidated debit view (decred#524)
Browse files Browse the repository at this point in the history
This introduces a new "merged debit" view on the addresses history page that lists only unique transactions.  The Debit DCR amount is the sum of all outpoints corresponding to the given address that are spent in a transaction.

It also fixes a bug with the next button on the same page.
  • Loading branch information
dmigwi authored and JFixby committed Aug 1, 2018
1 parent e432eb0 commit 4aa6f65
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 77 deletions.
2 changes: 1 addition & 1 deletion api/insight/apiroutes.go
Original file line number Diff line number Diff line change
Expand Up @@ -874,7 +874,7 @@ func (c *insightApiContext) getAddressInfo(w http.ResponseWriter, r *http.Reques

// Get Confirmed Balances
var unconfirmedBalanceSat int64
_, _, totalSpent, totalUnspent, err := c.BlockData.ChainDB.RetrieveAddressSpentUnspent(address)
_, _, totalSpent, totalUnspent, _, err := c.BlockData.ChainDB.RetrieveAddressSpentUnspent(address)
if err != nil {
return
}
Expand Down
31 changes: 18 additions & 13 deletions db/dbtypes/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,15 +44,17 @@ const (
AddrTxnAll AddrTxnType = iota
AddrTxnCredit
AddrTxnDebit
AddrMergedTxnDebit
AddrTxnUnknown
)

// AddrTxnTypes is the canonical mapping from AddrTxnType to string.
var AddrTxnTypes = map[AddrTxnType]string{
AddrTxnAll: "all",
AddrTxnCredit: "credit",
AddrTxnDebit: "debit",
AddrTxnUnknown: "unknown",
AddrTxnAll: "all",
AddrTxnCredit: "credit",
AddrTxnDebit: "debit",
AddrMergedTxnDebit: "merged debit",
AddrTxnUnknown: "unknown",
}

func (a AddrTxnType) String() string {
Expand All @@ -73,6 +75,8 @@ func AddrTxnTypeFromStr(txnType string) AddrTxnType {
fallthrough
case "debits":
return AddrTxnDebit
case "merged debit":
return AddrMergedTxnDebit
default:
return AddrTxnUnknown
}
Expand Down Expand Up @@ -297,15 +301,16 @@ type Vout struct {
type AddressRow struct {
// id int64
Address string
// MatchingTxHash provides the relationship between spending tx inputs and
// funding tx outputs.
MatchingTxHash string
IsFunding bool
TxBlockTime uint64
TxHash string
TxVinVoutIndex uint32
Value uint64
VinVoutDbID uint64
// MatchingTxHash that provides the relationship
// between spending tx inputs and funding tx outputs
MatchingTxHash string
IsFunding bool
TxBlockTime uint64
TxHash string
TxVinVoutIndex uint32
Value uint64
VinVoutDbID uint64
MergedDebitCount uint64
}

// ChartsData defines the fields that store the values needed to plot the charts
Expand Down
2 changes: 1 addition & 1 deletion db/dcrpg/insightapi.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ func (pgb *ChainDB) InsightPgGetAddressTransactions(addr []string,

// RetrieveAddressSpentUnspent retrieves balance information for a specific
// address.
func (pgb *ChainDB) RetrieveAddressSpentUnspent(address string) (int64, int64, int64, int64, error) {
func (pgb *ChainDB) RetrieveAddressSpentUnspent(address string) (int64, int64, int64, int64, int64, error) {
return RetrieveAddressSpentUnspent(pgb.db, address)
}

Expand Down
11 changes: 9 additions & 2 deletions db/dcrpg/internal/addrstmts.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,10 @@ const (
WHERE address = $1 and is_funding = TRUE and matching_tx_hash = '';`

SelectAddressSpentCountAndValue = `SELECT COUNT(*), SUM(value) FROM addresses
WHERE address = $1 and is_funding = FALSE and matching_tx_hash != '';`
WHERE address = $1 and is_funding = FALSE and matching_tx_hash != '';`

SelectAddressesMergedSpentCount = `SELECT COUNT( distinct tx_hash ) FROM addresses
WHERE address = $1 and is_funding = false`

SelectAddressUnspentWithTxn = `SELECT addresses.address, addresses.tx_hash, addresses.value,
transactions.block_height, addresses.block_time, tx_vin_vout_index, pkscript
Expand All @@ -72,7 +75,11 @@ const (

SelectAddressLimitNByAddressSubQry = `WITH these as (SELECT ` + addrsColumnNames +
` FROM addresses WHERE address=$1)
SELECT * FROM these ORDER BY block_time DESC LIMIT $2 OFFSET $3;`
SELECT * FROM these order by block_time desc limit $2 offset $3;`

SelectAddressMergedDebitView = `SELECT tx_hash, block_time, sum(value),
COUNT(*) FROM addresses WHERE address=$1 AND is_funding = FALSE
GROUP BY (tx_hash, block_time) ORDER BY block_time DESC LIMIT $2 OFFSET $3;`

SelectAddressDebitsLimitNByAddress = `SELECT ` + addrsColumnNames + `
FROM addresses WHERE address=$1 and is_funding = FALSE
Expand Down
35 changes: 20 additions & 15 deletions db/dcrpg/pgblockchain.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,6 +412,9 @@ func (pgb *ChainDB) AddressTransactions(address string, N, offset int64,
}
case dbtypes.AddrTxnDebit:
addrFunc = RetrieveAddressDebitTxns

case dbtypes.AddrMergedTxnDebit:
addrFunc = RetrieveAddressMergedDebitTxns
default:
return nil, fmt.Errorf("unknown AddrTxnType %v", txnType)
}
Expand Down Expand Up @@ -540,18 +543,19 @@ func (pgb *ChainDB) addressBalance(address string) (*explorer.AddressBalance, er
}

if !fresh {
var numSpent, numUnspent, totalSpent, totalUnspent int64
numSpent, numUnspent, totalSpent, totalUnspent, err =
var numSpent, numUnspent, totalSpent, totalUnspent, totalMergedSpent int64
numSpent, numUnspent, totalSpent, totalUnspent, totalMergedSpent, err =
RetrieveAddressSpentUnspent(pgb.db, address)
if err != nil {
return nil, err
}
balanceInfo = explorer.AddressBalance{
Address: address,
NumSpent: numSpent,
NumUnspent: numUnspent,
TotalSpent: totalSpent,
TotalUnspent: totalUnspent,
Address: address,
NumSpent: numSpent,
NumUnspent: numUnspent,
NumMergedSpent: totalMergedSpent,
TotalSpent: totalSpent,
TotalUnspent: totalUnspent,
}

totals.balance[address] = balanceInfo
Expand Down Expand Up @@ -616,18 +620,19 @@ func (pgb *ChainDB) AddressHistory(address string, N, offset int64,
TotalUnspent: int64(addrInfo.AmountUnspent),
}
} else {
var numSpent, numUnspent, totalSpent, totalUnspent int64
numSpent, numUnspent, totalSpent, totalUnspent, err =
var numSpent, numUnspent, totalSpent, totalUnspent, totalMergedSpent int64
numSpent, numUnspent, totalSpent, totalUnspent, totalMergedSpent, err =
RetrieveAddressSpentUnspent(pgb.db, address)
if err != nil {
return nil, nil, err
}
balanceInfo = explorer.AddressBalance{
Address: address,
NumSpent: numSpent,
NumUnspent: numUnspent,
TotalSpent: totalSpent,
TotalUnspent: totalUnspent,
Address: address,
NumSpent: numSpent,
NumUnspent: numUnspent,
NumMergedSpent: totalMergedSpent,
TotalSpent: totalSpent,
TotalUnspent: totalUnspent,
}
}

Expand Down Expand Up @@ -737,7 +742,7 @@ func (pgb *ChainDB) addressInfo(addr string, count, skip int64,
// Transactions to fetch with FillAddressTransactions. This should be a
// noop if AddressHistory/ReduceAddressHistory are working right.
switch txnType {
case dbtypes.AddrTxnAll:
case dbtypes.AddrTxnAll, dbtypes.AddrMergedTxnDebit:
case dbtypes.AddrTxnCredit:
addrData.Transactions = addrData.TxnsFunding
case dbtypes.AddrTxnDebit:
Expand Down
52 changes: 46 additions & 6 deletions db/dcrpg/queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -746,7 +746,7 @@ func RetrieveAddressSpent(db *sql.DB, address string) (count, totalAmount int64,
}

func RetrieveAddressSpentUnspent(db *sql.DB, address string) (numSpent, numUnspent,
totalSpent, totalUnspent int64, err error) {
totalSpent, totalUnspent, totalMergedSpent int64, err error) {
dbtx, err := db.Begin()
if err != nil {
err = fmt.Errorf("unable to begin database transaction: %v", err)
Expand Down Expand Up @@ -777,6 +777,23 @@ func RetrieveAddressSpentUnspent(db *sql.DB, address string) (numSpent, numUnspe
}
numSpent, totalSpent = ns.Int64, ts.Int64

var nms sql.NullInt64
err = dbtx.QueryRow(internal.SelectAddressesMergedSpentCount, address).
Scan(&nms)
if err != nil && err != sql.ErrNoRows {
if errRoll := dbtx.Rollback(); errRoll != nil {
log.Errorf("Rollback failed: %v", errRoll)
}
err = fmt.Errorf("unable to QueryRow for merged spent count: %v", err)
return
}

totalMergedSpent = nms.Int64

if !nms.Valid {
log.Debug("Merged debit spent count is not valid")
}

err = dbtx.Rollback()
return
}
Expand All @@ -797,26 +814,31 @@ func RetrieveAllAddressTxns(db *sql.DB, address string) ([]uint64, []*dbtypes.Ad

func RetrieveAddressTxns(db *sql.DB, address string, N, offset int64) ([]uint64, []*dbtypes.AddressRow, error) {
return retrieveAddressTxns(db, address, N, offset,
internal.SelectAddressLimitNByAddressSubQry)
internal.SelectAddressLimitNByAddressSubQry, false)
}

func RetrieveAddressTxnsAlt(db *sql.DB, address string, N, offset int64) ([]uint64, []*dbtypes.AddressRow, error) {
return retrieveAddressTxns(db, address, N, offset,
internal.SelectAddressLimitNByAddress)
internal.SelectAddressLimitNByAddress, false)
}

func RetrieveAddressDebitTxns(db *sql.DB, address string, N, offset int64) ([]uint64, []*dbtypes.AddressRow, error) {
return retrieveAddressTxns(db, address, N, offset,
internal.SelectAddressDebitsLimitNByAddress)
internal.SelectAddressDebitsLimitNByAddress, false)
}

func RetrieveAddressCreditTxns(db *sql.DB, address string, N, offset int64) ([]uint64, []*dbtypes.AddressRow, error) {
return retrieveAddressTxns(db, address, N, offset,
internal.SelectAddressCreditsLimitNByAddress)
internal.SelectAddressCreditsLimitNByAddress, false)
}

func RetrieveAddressMergedDebitTxns(db *sql.DB, address string, N, offset int64) ([]uint64, []*dbtypes.AddressRow, error) {
return retrieveAddressTxns(db, address, N, offset,
internal.SelectAddressMergedDebitView, true)
}

func retrieveAddressTxns(db *sql.DB, address string, N, offset int64,
statement string) ([]uint64, []*dbtypes.AddressRow, error) {
statement string, isMergedDebitView bool) ([]uint64, []*dbtypes.AddressRow, error) {
rows, err := db.Query(statement, address, N, offset)
if err != nil {
return nil, nil, err
Expand All @@ -827,9 +849,27 @@ func retrieveAddressTxns(db *sql.DB, address string, N, offset int64,
}
}()

if isMergedDebitView {
addr, err := scanPartialAddressQueryRows(rows, address)
return nil, addr, err
}
return scanAddressQueryRows(rows)
}

func scanPartialAddressQueryRows(rows *sql.Rows, addr string) (addressRows []*dbtypes.AddressRow, err error) {
for rows.Next() {
var addr = dbtypes.AddressRow{Address: addr}

err = rows.Scan(&addr.TxHash, &addr.TxBlockTime,
&addr.Value, &addr.MergedDebitCount)
if err != nil {
return
}
addressRows = append(addressRows, &addr)
}
return
}

func scanAddressQueryRows(rows *sql.Rows) (ids []uint64, addressRows []*dbtypes.AddressRow, err error) {
for rows.Next() {
var id uint64
Expand Down
3 changes: 2 additions & 1 deletion explorer/explorerroutes.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,11 +470,12 @@ func (exp *explorerUI) AddressPage(w http.ResponseWriter, r *http.Request) {
addrData.KnownTransactions = (balance.NumSpent * 2) + balance.NumUnspent
addrData.KnownFundingTxns = balance.NumSpent + balance.NumUnspent
addrData.KnownSpendingTxns = balance.NumSpent
addrData.KnownMergedSpendingTxns = balance.NumMergedSpent

// Transactions to fetch with FillAddressTransactions. This should be a
// noop if ReduceAddressHistory is working right.
switch txnType {
case dbtypes.AddrTxnAll:
case dbtypes.AddrTxnAll, dbtypes.AddrMergedTxnDebit:
case dbtypes.AddrTxnCredit:
addrData.Transactions = addrData.TxnsFunding
case dbtypes.AddrTxnDebit:
Expand Down
52 changes: 33 additions & 19 deletions explorer/explorertypes.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,24 +61,29 @@ type ChartDataCounter struct {

// AddressTx models data for transactions on the address page
type AddressTx struct {
TxID string
InOutID uint32
Size uint32
FormattedSize string
Total float64
Confirmations uint64
Time int64
FormattedTime string
ReceivedTotal float64
SentTotal float64
IsFunding bool
MatchedTx string
BlockTime uint64
TxID string
InOutID uint32
Size uint32
FormattedSize string
Total float64
Confirmations uint64
Time int64
FormattedTime string
ReceivedTotal float64
SentTotal float64
IsFunding bool
MatchedTx string
BlockTime uint64
MergedTxnCount uint64 `json:",omitempty"`
}

// IOID formats an identification string for the transaction input (or output)
// represented by the AddressTx.
func (a *AddressTx) IOID() string {
func (a *AddressTx) IOID(txType ...string) string {
// if transaction is of type merged debit, return unformatted transaction ID
if len(txType) > 0 && dbtypes.AddrTxnTypeFromStr(txType[0]) == dbtypes.AddrMergedTxnDebit {
return a.TxID
}
// When AddressTx is used properly, at least one of ReceivedTotal or
// SentTotal should be zero.
if a.IsFunding {
Expand Down Expand Up @@ -248,6 +253,10 @@ type AddressInfo struct {
KnownTransactions int64
KnownFundingTxns int64
KnownSpendingTxns int64

// KnownMergedSpendingTxns refers to the total count of unique debit transactions
// that appear in the merged debit view.
KnownMergedSpendingTxns int64
}

// TxnCount returns the number of transaction "rows" available.
Expand All @@ -262,6 +271,8 @@ func (a *AddressInfo) TxnCount() int64 {
return a.KnownFundingTxns
case dbtypes.AddrTxnDebit:
return a.KnownSpendingTxns
case dbtypes.AddrMergedTxnDebit:
return a.KnownMergedSpendingTxns
default:
log.Warnf("Unknown address transaction type: %v", a.TxnType)
return 0
Expand All @@ -271,11 +282,12 @@ func (a *AddressInfo) TxnCount() int64 {
// AddressBalance represents the number and value of spent and unspent outputs
// for an address.
type AddressBalance struct {
Address string `json:"address"`
NumSpent int64 `json:"num_stxos"`
NumUnspent int64 `json:"num_utxos"`
TotalSpent int64 `json:"amount_spent"`
TotalUnspent int64 `json:"amount_unspent"`
Address string `json:"address"`
NumSpent int64 `json:"num_stxos"`
NumUnspent int64 `json:"num_utxos"`
TotalSpent int64 `json:"amount_spent"`
TotalUnspent int64 `json:"amount_unspent"`
NumMergedSpent int64 `json:"num_merged_spent,omitempty"`
}

// HomeInfo represents data used for the home page
Expand Down Expand Up @@ -370,6 +382,8 @@ func ReduceAddressHistory(addrHist []*dbtypes.AddressRow) *AddressInfo {
// Spending transaction
sent += int64(addrOut.Value)
tx.SentTotal = coin
tx.MergedTxnCount = addrOut.MergedDebitCount

debitTxns = append(debitTxns, &tx)
}

Expand Down
Loading

0 comments on commit 4aa6f65

Please sign in to comment.