Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Unconfirmed tx in address list and input amounts on unconfirmed tx #489

Merged
merged 3 commits into from
Jul 25, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions db/dcrsqlite/apisource.go
Original file line number Diff line number Diff line change
Expand Up @@ -1148,25 +1148,30 @@ func (db *wiredDB) GetExplorerTx(txid string) *explorer.TxInfo {
inputs := make([]explorer.Vin, 0, len(txraw.Vin))
for i, vin := range txraw.Vin {
var addresses []string
// ValueIn is a temporary fix until amountIn is correct from dcrd call
var ValueIn dcrutil.Amount
if !(vin.IsCoinBase() || (vin.IsStakeBase() && i == 0)) {
addrs, err := txhelpers.OutPointAddresses(&msgTx.TxIn[i].PreviousOutPoint, db.client, db.params)
var addrs []string
addrs, ValueIn, err = txhelpers.OutPointAddresses(&msgTx.TxIn[i].PreviousOutPoint, db.client, db.params)
if err != nil {
log.Warnf("Failed to get outpoint address from txid: %v", err)
continue
}
addresses = addrs
} else {
ValueIn, _ = dcrutil.NewAmount(vin.AmountIn)
}
inputs = append(inputs, explorer.Vin{
Vin: &dcrjson.Vin{
Txid: vin.Txid,
Coinbase: vin.Coinbase,
Stakebase: vin.Stakebase,
Vout: vin.Vout,
AmountIn: vin.AmountIn,
AmountIn: ValueIn.ToCoin(),
BlockHeight: vin.BlockHeight,
},
Addresses: addresses,
FormattedAmount: humanize.Commaf(vin.AmountIn),
FormattedAmount: humanize.Commaf(ValueIn.ToCoin()),
})
}
tx.Vin = inputs
Expand Down Expand Up @@ -1360,7 +1365,6 @@ func (db *wiredDB) UnconfirmedTxnsForAddress(address string) (*txhelpers.Address
}

Tx, err1 := db.client.GetRawTransaction(txhash)

if err1 != nil {
log.Warnf("Unable to GetRawTransaction(%s): %v", tx, err1)
err = err1
Expand All @@ -1387,6 +1391,7 @@ func (db *wiredDB) UnconfirmedTxnsForAddress(address string) (*txhelpers.Address
// Merge the I/Os and the transactions into results
addressOutpoints.Update(prevTxns, outpoints, prevouts)
}

return addressOutpoints, numUnconfirmed, err
}

Expand Down
214 changes: 121 additions & 93 deletions explorer/explorerroutes.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"io"
"math"
"net/http"
"sort"
"strconv"
"strings"

Expand Down Expand Up @@ -412,90 +413,61 @@ func (exp *explorerUI) AddressPage(w http.ResponseWriter, r *http.Request) {
// Get addresses table rows for the address
addrHist, balance, errH := exp.explorerSource.AddressHistory(
address, limitN, offsetAddrOuts, txnType)
// Fallback to RPC if DB query fails
if errH != nil {
log.Errorf("Unable to get address %s history: %v", address, errH)
addrData = exp.blockData.GetExplorerAddress(address, limitN, offsetAddrOuts)

if errH == nil {
// Generate AddressInfo skeleton from the address table rows
addrData = ReduceAddressHistory(addrHist)
if addrData == nil {
log.Errorf("Unable to get address %s", address)
exp.StatusPage(w, defaultErrorCode, "could not find that address", NotFoundStatusType)
return
// Empty history is not expected for credit txnType with any txns.
if txnType != dbtypes.AddrTxnDebit && (balance.NumSpent+balance.NumUnspent) > 0 {
log.Debugf("empty address history (%s): n=%d&start=%d", address, limitN, offsetAddrOuts)
exp.StatusPage(w, defaultErrorCode, "that address has no history", NotFoundStatusType)
return
}
// No mined transactions
addrData = new(AddressInfo)
addrData.Address = address
}

// Set page parameters
addrData.Path = r.URL.Path
addrData.Limit, addrData.Offset = limitN, offsetAddrOuts
addrData.TxnType = txnType.String()

confirmHeights := make([]int64, len(addrData.Transactions))
for i, v := range addrData.Transactions {
confirmHeights[i] = exp.NewBlockData.Height - int64(v.Confirmations)
addrData.Fullmode = true

// Balances and txn counts (partial unless in full mode)
addrData.Balance = balance
addrData.KnownTransactions = (balance.NumSpent * 2) + balance.NumUnspent
addrData.KnownFundingTxns = balance.NumSpent + balance.NumUnspent
addrData.KnownSpendingTxns = balance.NumSpent

// Transactions to fetch with FillAddressTransactions. This should be a
// noop if ReduceAddressHistory is working right.
switch txnType {
case dbtypes.AddrTxnAll:
case dbtypes.AddrTxnCredit:
addrData.Transactions = addrData.TxnsFunding
case dbtypes.AddrTxnDebit:
addrData.Transactions = addrData.TxnsSpending
default:
log.Warnf("Unknown address transaction type: %v", txnType)
}

pageData := AddressPageData{
Data: addrData,
ConfirmHeight: confirmHeights,
Version: exp.Version,
// Transactions on current page
addrData.NumTransactions = int64(len(addrData.Transactions))
if addrData.NumTransactions > limitN {
addrData.NumTransactions = limitN
}
str, err := exp.templates.execTemplateToString("address", pageData)
if err != nil {
log.Errorf("Template execute failure: %v", err)
exp.StatusPage(w, defaultErrorCode, defaultErrorMessage, ErrorStatusType)
return
}

w.Header().Set("Content-Type", "text/html")
w.WriteHeader(http.StatusOK)
io.WriteString(w, str)
return
}

// Generate AddressInfo skeleton from the address table rows
addrData = ReduceAddressHistory(addrHist)
if addrData == nil {
// Empty history is not expected for credit txnType with any txns.
if txnType != dbtypes.AddrTxnDebit && (balance.NumSpent+balance.NumUnspent) > 0 {
log.Debugf("empty address history (%s): n=%d&start=%d", address, limitN, offsetAddrOuts)
exp.StatusPage(w, defaultErrorCode, "that address has no history", NotFoundStatusType)
// Query database for transaction details
err = exp.explorerSource.FillAddressTransactions(addrData)
if err != nil {
log.Errorf("Unable to fill address %s transactions: %v", address, err)
exp.StatusPage(w, defaultErrorCode, "could not find transactions for that address", NotFoundStatusType)
return
}
// No mined transactions
} else {
// We do not have any confirmed transactions. Prep to display ONLY
// unconfirmed transactions (or none at all)
addrData = new(AddressInfo)
addrData.Address = address
}
addrData.Fullmode = true

// Balances and txn counts (partial unless in full mode)
addrData.Balance = balance
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, dbtypes.AddrMergedTxnDebit:
case dbtypes.AddrTxnCredit:
addrData.Transactions = addrData.TxnsFunding
case dbtypes.AddrTxnDebit:
addrData.Transactions = addrData.TxnsSpending
default:
log.Warnf("Unknown address transaction type: %v", txnType)
}

// Transactions on current page
addrData.NumTransactions = int64(len(addrData.Transactions))
if addrData.NumTransactions > limitN {
addrData.NumTransactions = limitN
}

// Query database for transaction details
err = exp.explorerSource.FillAddressTransactions(addrData)
if err != nil {
log.Errorf("Unable to fill address %s transactions: %v", address, err)
exp.StatusPage(w, defaultErrorCode, "could not find transactions for that address", ErrorStatusType)
return
addrData.Fullmode = true
addrData.Balance = &AddressBalance{}
}

// Check for unconfirmed transactions
Expand All @@ -507,10 +479,20 @@ func (exp *explorerUI) AddressPage(w http.ResponseWriter, r *http.Request) {
if addrData.UnconfirmedTxns == nil {
addrData.UnconfirmedTxns = new(AddressTransactions)
}
uctxn := addrData.UnconfirmedTxns

// Funding transactions (unconfirmed)
var received, sent, numReceived, numSent int64
FUNDING_TX_DUPLICATE_CHECK:
for _, f := range addressOuts.Outpoints {
//Mempool transactions stick around for 2 blocks. The first block
//incorporates the transaction and mines it. The second block
//validates it by the stake. However, transactions move into our
//database as soon as they are mined and thus we need to be careful
//to not include those transactions in our list.
for _, b := range addrData.Transactions {
if f.Hash.String() == b.TxID && f.Index == b.InOutID {
continue FUNDING_TX_DUPLICATE_CHECK
}
}
fundingTx, ok := addressOuts.TxnsStore[f.Hash]
if !ok {
log.Errorf("An outpoint's transaction is not available in TxnStore.")
Expand All @@ -520,19 +502,34 @@ func (exp *explorerUI) AddressPage(w http.ResponseWriter, r *http.Request) {
log.Errorf("An outpoint's transaction is unexpectedly confirmed.")
continue
}
addrTx := &AddressTx{
TxID: fundingTx.Hash().String(),
InOutID: f.Index,
FormattedSize: humanize.Bytes(uint64(fundingTx.Tx.SerializeSize())),
Total: txhelpers.TotalOutFromMsgTx(fundingTx.Tx).ToCoin(),
ReceivedTotal: dcrutil.Amount(fundingTx.Tx.TxOut[f.Index].Value).ToCoin(),
if txnType == dbtypes.AddrTxnAll || txnType == dbtypes.AddrTxnCredit {
addrTx := &AddressTx{
TxID: fundingTx.Hash().String(),
InOutID: f.Index,
Time: fundingTx.MemPoolTime,
FormattedSize: humanize.Bytes(uint64(fundingTx.Tx.SerializeSize())),
Total: txhelpers.TotalOutFromMsgTx(fundingTx.Tx).ToCoin(),
ReceivedTotal: dcrutil.Amount(fundingTx.Tx.TxOut[f.Index].Value).ToCoin(),
}
addrData.Transactions = append(addrData.Transactions, addrTx)
}
uctxn.Transactions = append(uctxn.Transactions, addrTx)
uctxn.TxnsFunding = append(uctxn.TxnsFunding, addrTx)
}
received += fundingTx.Tx.TxOut[f.Index].Value
numReceived++

}
// Spending transactions (unconfirmed)
SPENDING_TX_DUPLICATE_CHECK:
for _, f := range addressOuts.PrevOuts {
//Mempool transactions stick around for 2 blocks. The first block
//incorporates the transaction and mines it. The second block
//validates it by the stake. However, transactions move into our
//database as soon as they are mined and thus we need to be careful
//to not include those transactions in our list.
for _, b := range addrData.Transactions {
if f.TxSpending.String() == b.TxID && f.InputIndex == int(b.InOutID) {
continue SPENDING_TX_DUPLICATE_CHECK
}
}
spendingTx, ok := addressOuts.TxnsStore[f.TxSpending]
if !ok {
log.Errorf("An outpoint's transaction is not available in TxnStore.")
Expand All @@ -542,16 +539,32 @@ func (exp *explorerUI) AddressPage(w http.ResponseWriter, r *http.Request) {
log.Errorf("An outpoint's transaction is unexpectedly confirmed.")
continue
}
addrTx := &AddressTx{
TxID: spendingTx.Hash().String(),
InOutID: uint32(f.InputIndex),
FormattedSize: humanize.Bytes(uint64(spendingTx.Tx.SerializeSize())),
Total: txhelpers.TotalOutFromMsgTx(spendingTx.Tx).ToCoin(),
SentTotal: dcrutil.Amount(spendingTx.Tx.TxIn[f.InputIndex].ValueIn).ToCoin(),

// sent total sats has to be a lookup of the vout:i prevout value
// because vin:i valuein is not reliable from dcrd at present
prevhash := spendingTx.Tx.TxIn[f.InputIndex].PreviousOutPoint.Hash
previndex := spendingTx.Tx.TxIn[f.InputIndex].PreviousOutPoint.Index
valuein := addressOuts.TxnsStore[prevhash].Tx.TxOut[previndex].Value

if txnType == dbtypes.AddrTxnAll || txnType == dbtypes.AddrTxnDebit {
addrTx := &AddressTx{
TxID: spendingTx.Hash().String(),
InOutID: uint32(f.InputIndex),
Time: spendingTx.MemPoolTime,
FormattedSize: humanize.Bytes(uint64(spendingTx.Tx.SerializeSize())),
Total: txhelpers.TotalOutFromMsgTx(spendingTx.Tx).ToCoin(),
SentTotal: dcrutil.Amount(valuein).ToCoin(),
}
addrData.Transactions = append(addrData.Transactions, addrTx)
}
uctxn.Transactions = append(uctxn.Transactions, addrTx)
uctxn.TxnsSpending = append(uctxn.TxnsSpending, addrTx)

sent += valuein
numSent++
}
addrData.Balance.NumSpent += numSent
addrData.Balance.NumUnspent += (numReceived - numSent)
addrData.Balance.TotalSpent += sent
addrData.Balance.TotalUnspent += (received - sent)
}

// Set page parameters
Expand All @@ -564,6 +577,21 @@ func (exp *explorerUI) AddressPage(w http.ResponseWriter, r *http.Request) {
confirmHeights[i] = exp.NewBlockData.Height - int64(v.Confirmations)
}

sort.Slice(addrData.Transactions, func(i, j int) bool {
if addrData.Transactions[i].Time == addrData.Transactions[j].Time {
return addrData.Transactions[i].InOutID > addrData.Transactions[j].InOutID
}
return addrData.Transactions[i].Time > addrData.Transactions[j].Time
})

// addresscount := len(addressRows)
// if addresscount > 0 {
// calcoffset := int(math.Min(float64(addresscount), float64(offset)))
// calcN := int(math.Min(float64(offset+N), float64(addresscount)))
// log.Infof("Slicing result set which is %d addresses long to offset: %d and N: %d", addresscount, calcoffset, calcN)
// addressRows = addressRows[calcoffset:calcN]
// }

pageData := AddressPageData{
Data: addrData,
ConfirmHeight: confirmHeights,
Expand Down
16 changes: 8 additions & 8 deletions txhelpers/txhelpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -399,17 +399,17 @@ func BlockReceivesToAddresses(block *dcrutil.Block, addrs map[string]TxAction,

// OutPointAddresses gets the addresses paid to by a transaction output.
func OutPointAddresses(outPoint *wire.OutPoint, c RawTransactionGetter,
params *chaincfg.Params) ([]string, error) {
params *chaincfg.Params) ([]string, dcrutil.Amount, error) {
// The addresses are encoded in the pkScript, so we need to get the
// raw transaction, and the TxOut that contains the pkScript.
prevTx, err := c.GetRawTransaction(&outPoint.Hash)
if err != nil {
return nil, fmt.Errorf("unable to get raw transaction for %s", outPoint.Hash.String())
return nil, 0, fmt.Errorf("unable to get raw transaction for %s", outPoint.Hash.String())
}

txOuts := prevTx.MsgTx().TxOut
if len(txOuts) <= int(outPoint.Index) {
return nil, fmt.Errorf("PrevOut index (%d) is beyond the TxOuts slice (length %d)",
return nil, 0, fmt.Errorf("PrevOut index (%d) is beyond the TxOuts slice (length %d)",
outPoint.Index, len(txOuts))
}

Expand All @@ -418,15 +418,15 @@ func OutPointAddresses(outPoint *wire.OutPoint, c RawTransactionGetter,
_, txAddrs, _, err := txscript.ExtractPkScriptAddrs(
txOut.Version, txOut.PkScript, params)
if err != nil {
return nil, fmt.Errorf("ExtractPkScriptAddrs: %v", err.Error())
return nil, 0, fmt.Errorf("ExtractPkScriptAddrs: %v", err.Error())
}

value := dcrutil.Amount(txOut.Value)
addresses := make([]string, 0, len(txAddrs))
for _, txAddr := range txAddrs {
addr := txAddr.EncodeAddress()
addresses = append(addresses, addr)
}
return addresses, nil
return addresses, value, nil
}

// OutPointAddressesFromString is the same as OutPointAddresses, but it takes
Expand All @@ -439,8 +439,8 @@ func OutPointAddressesFromString(txid string, index uint32, tree int8,
}

outPoint := wire.NewOutPoint(hash, index, tree)

return OutPointAddresses(outPoint, c, params)
outPointAddress, _, err := OutPointAddresses(outPoint, c, params)
return outPointAddress, err
}

// MedianAmount gets the median Amount from a slice of Amounts
Expand Down
2 changes: 1 addition & 1 deletion views/address.tmpl
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@
{{end}}
{{end}}
<td>
{{if eq .Time 0}}
{{if eq .Confirmations 0}}
Unconfirmed
{{else}}
{{.FormattedTime}}
Expand Down