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

Add transaction and staking overview #201

Merged
merged 4 commits into from
Jun 22, 2021
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
34 changes: 34 additions & 0 deletions ticket.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,40 @@ func (wallet *Wallet) StakeInfo() (*w.StakeInfoData, error) {
return wallet.internal.StakeInfo(ctx)
}

func (wallet *Wallet) StakingOverview() (stOverview *StakingOverview, err error) {
stOverview = &StakingOverview{}

stOverview.Voted, err = wallet.CountTransactions(TxFilterVoted)
if err != nil {
return nil, err
}

stOverview.Revoked, err = wallet.CountTransactions(TxFilterRevoked)
if err != nil {
return nil, err
}

stOverview.Expired, err = wallet.CountTransactions(TxFilterExpired)
if err != nil {
return nil, err
}

stOverview.Live, err = wallet.CountTransactions(TxFilterLive)
if err != nil {
return nil, err
}

stOverview.Immature, err = wallet.CountTransactions(TxFilterImmature)
if err != nil {
return nil, err
}

stOverview.All = stOverview.Immature + stOverview.Live + stOverview.Voted +
stOverview.Expired + stOverview.Revoked

return stOverview, nil
}

func (wallet *Wallet) GetTickets(startingBlockHash, endingBlockHash []byte, targetCount int32) ([]*TicketInfo, error) {
return wallet.getTickets(&GetTicketsRequest{
StartingBlockHash: startingBlockHash,
Expand Down
116 changes: 112 additions & 4 deletions transactions.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ const (
TxFilterCoinBase = walletdata.TxFilterCoinBase
TxFilterRegular = walletdata.TxFilterRegular
TxFilterMixed = walletdata.TxFilterMixed
TxFilterVoted = walletdata.TxFilterVoted
TxFilterRevoked = walletdata.TxFilterRevoked
TxFilterImmature = walletdata.TxFilterImmature
TxFilterLive = walletdata.TxFilterLive
TxFilterExpired = walletdata.TxFilterExpired

TxDirectionInvalid = txhelper.TxDirectionInvalid
TxDirectionSent = txhelper.TxDirectionSent
Expand Down Expand Up @@ -91,7 +96,7 @@ func (wallet *Wallet) GetTransactions(offset, limit, txFilter int32, newestFirst
}

func (wallet *Wallet) GetTransactionsRaw(offset, limit, txFilter int32, newestFirst bool) (transactions []Transaction, err error) {
err = wallet.walletDataDB.Read(offset, limit, txFilter, newestFirst, &transactions)
err = wallet.walletDataDB.Read(offset, limit, txFilter, newestFirst, wallet.GetBestBlock(), &transactions)
return
}

Expand Down Expand Up @@ -137,7 +142,7 @@ func (mw *MultiWallet) GetTransactionsRaw(offset, limit, txFilter int32, newestF
}

func (wallet *Wallet) CountTransactions(txFilter int32) (int, error) {
return wallet.walletDataDB.Count(txFilter, &Transaction{})
return wallet.walletDataDB.Count(txFilter, wallet.GetBestBlock(), &Transaction{})
}

func (wallet *Wallet) TicketHasVotedOrRevoked(ticketHash string) (bool, error) {
Expand All @@ -152,6 +157,109 @@ func (wallet *Wallet) TicketHasVotedOrRevoked(ticketHash string) (bool, error) {
return true, nil
}

func TxMatchesFilter(txType string, txDirection, txFilter int32) bool {
return walletdata.TxMatchesFilter(txType, txDirection, txFilter)
func (wallet *Wallet) TicketSpender(ticketHash string) (*Transaction, error) {
var spender Transaction
err := wallet.walletDataDB.FindOne("TicketSpentHash", ticketHash, &spender)
if err != nil {
if err == storm.ErrNotFound {
return nil, nil
}
return nil, err
}

return &spender, nil
}

func (wallet *Wallet) TransactionOverview() (txOverview *TransactionOverview, err error) {

txOverview = &TransactionOverview{}

txOverview.Sent, err = wallet.CountTransactions(TxFilterSent)
if err != nil {
return
}

txOverview.Received, err = wallet.CountTransactions(TxFilterReceived)
if err != nil {
return
}

txOverview.Transferred, err = wallet.CountTransactions(TxFilterTransferred)
if err != nil {
return
}

txOverview.Mixed, err = wallet.CountTransactions(TxFilterMixed)
if err != nil {
return
}

txOverview.Staking, err = wallet.CountTransactions(TxFilterStaking)
if err != nil {
return
}

txOverview.Coinbase, err = wallet.CountTransactions(TxFilterCoinBase)
if err != nil {
return
}

txOverview.All = txOverview.Sent + txOverview.Received + txOverview.Transferred + txOverview.Mixed +
txOverview.Staking + txOverview.Coinbase

return txOverview, nil
}

func (wallet *Wallet) TxMatchesFilter(tx *Transaction, txFilter int32) bool {
switch txFilter {
case TxFilterSent:
return tx.Type == TxTypeRegular && tx.Direction == TxDirectionSent
case TxFilterReceived:
return tx.Type == TxTypeRegular && tx.Direction == TxDirectionReceived
case TxFilterTransferred:
return tx.Type == TxTypeRegular && tx.Direction == TxDirectionTransferred
case TxFilterStaking:
switch tx.Type {
case TxTypeTicketPurchase:
fallthrough
case TxTypeVote:
fallthrough
case TxTypeRevocation:
return true
}

return false
case TxFilterCoinBase:
return tx.Type == TxTypeCoinBase
case TxFilterRegular:
return tx.Type == TxTypeRegular
case TxFilterMixed:
return tx.Type == TxTypeMixed
case TxFilterVoted:
return tx.Type == TxTypeVote
case TxFilterRevoked:
return tx.Type == TxTypeRevocation
case walletdata.TxFilterImmature:
bestBlock := wallet.GetBestBlock()
return tx.Type == TxTypeTicketPurchase &&
(tx.BlockHeight > (bestBlock-int32(wallet.chainParams.TicketMaturity)) ||
tx.BlockHeight == -1)
case TxFilterLive:
bestBlock := wallet.GetBestBlock()
// ticket is live if we don't have the spender hash and it hasn't expired.
// we cannot detect missed tickets over spv.
return tx.Type == TxTypeTicketPurchase &&
tx.TicketSpender == "" &&
tx.BlockHeight > 0 &&
tx.BlockHeight <= (bestBlock-int32(wallet.chainParams.TicketMaturity)) &&
(tx.Expiry >= bestBlock || tx.Expiry == 0)
case TxFilterExpired:
bestBlock := wallet.GetBestBlock()
return tx.Type == TxTypeTicketPurchase && tx.TicketSpender == "" &&
(tx.Expiry <= bestBlock && tx.Expiry != 0)
case TxFilterAll:
return true
}

return false
}
2 changes: 1 addition & 1 deletion txindex.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func (wallet *Wallet) IndexTransactions() error {
endBlock := w.NewBlockIdentifierFromHeight(endHeight)

defer func() {
count, err := wallet.walletDataDB.Count(walletdata.TxFilterAll, &Transaction{})
count, err := wallet.walletDataDB.Count(walletdata.TxFilterAll, endHeight, &Transaction{})
if err != nil {
log.Errorf("[%d] Post-indexing tx count error :%v", wallet.ID, err)
} else if count > 0 {
Expand Down
4 changes: 4 additions & 0 deletions txparser.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ func (wallet *Wallet) decodeTransactionWithTxSummary(txSummary *w.TransactionSum

reward := ticketOutput - ticketInvestment
decodedTx.VoteReward = reward

// update ticket with spender hash
ticketPurchaseTx.TicketSpender = decodedTx.Hash
wallet.walletDataDB.SaveOrUpdate(&Transaction{}, ticketPurchaseTx)
}

return decodedTx, nil
Expand Down
32 changes: 26 additions & 6 deletions types.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,12 +175,13 @@ type BlocksRescanProgressListener interface {
// Transaction is used with storm for tx indexing operations.
// For faster queries, the `Hash`, `Type` and `Direction` fields are indexed.
type Transaction struct {
WalletID int `json:"walletID"`
Hash string `storm:"id,unique" json:"hash"`
Type string `storm:"index" json:"type"`
Hex string `json:"hex"`
Timestamp int64 `json:"timestamp"`
BlockHeight int32 `json:"block_height"`
WalletID int `json:"walletID"`
Hash string `storm:"id,unique" json:"hash"`
Type string `storm:"index" json:"type"`
Hex string `json:"hex"`
Timestamp int64 `storm:"index" json:"timestamp"`
BlockHeight int32 `storm:"index" json:"block_height"`
TicketSpender string `storm:"index" json:"ticket_spender"`

MixDenomination int64 `json:"mix_denom"`
MixCount int32 `json:"mix_count"`
Expand Down Expand Up @@ -260,6 +261,16 @@ type TransactionDestination struct {
SendMax bool
}

type TransactionOverview struct {
All int
Sent int
Received int
Transferred int
Mixed int
Staking int
Coinbase int
}

/** end tx-related types */

/** begin ticket-related types */
Expand Down Expand Up @@ -311,6 +322,15 @@ type VSPTicketPurchaseInfo struct {
TicketAddress string
}

type StakingOverview struct {
All int
Immature int
Live int
Voted int
Expired int
Revoked int
}

/** end ticket-related types */

/** begin politeia types */
Expand Down
2 changes: 1 addition & 1 deletion wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func (wallet *Wallet) prepare(rootDir string, chainParams *chaincfg.Params,
if exists, _ := fileExists(oldTxDBPath); exists {
moveFile(oldTxDBPath, walletDataDBPath)
}
wallet.walletDataDB, err = walletdata.Initialize(walletDataDBPath, &Transaction{}, &VspdTicketInfo{})
wallet.walletDataDB, err = walletdata.Initialize(walletDataDBPath, chainParams, &Transaction{}, &VspdTicketInfo{})
if err != nil {
log.Error(err.Error())
return err
Expand Down
7 changes: 5 additions & 2 deletions walletdata/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"os"

"github.com/asdine/storm"
"github.com/decred/dcrd/chaincfg/v3"
bolt "go.etcd.io/bbolt"
)

Expand All @@ -17,19 +18,20 @@ const (

// TxDbVersion is necessary to force re-indexing if changes are made to the structure of data being stored.
// Increment this version number if db structure changes such that client apps need to re-index.
TxDbVersion uint32 = 2
TxDbVersion uint32 = 3
)

type DB struct {
walletDataDB *storm.DB
chainParams *chaincfg.Params
Close func() error
}

// Initialize opens the existing storm db at `dbPath`
// and checks the database version for compatibility.
// If there is a version mismatch or the db does not exist at `dbPath`,
// a new db is created and the current db version number saved to the db.
func Initialize(dbPath string, txData, vspdData interface{}) (*DB, error) {
func Initialize(dbPath string, chainParams *chaincfg.Params, txData, vspdData interface{}) (*DB, error) {
walletDataDB, err := openOrCreateDB(dbPath)
if err != nil {
return nil, err
Expand All @@ -54,6 +56,7 @@ func Initialize(dbPath string, txData, vspdData interface{}) (*DB, error) {

return &DB{
walletDataDB,
chainParams,
walletDataDB.Close,
}, nil
}
Expand Down
75 changes: 42 additions & 33 deletions walletdata/filter.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,41 +15,14 @@ const (
TxFilterCoinBase int32 = 5
TxFilterRegular int32 = 6
TxFilterMixed int32 = 7
TxFilterVoted int32 = 8
TxFilterRevoked int32 = 9
TxFilterImmature int32 = 10
TxFilterLive int32 = 11
TxFilterExpired int32 = 12
)

func TxMatchesFilter(txType string, txDirection, txFilter int32) bool {
switch txFilter {
case TxFilterSent:
return txType == txhelper.TxTypeRegular && txDirection == txhelper.TxDirectionSent
case TxFilterReceived:
return txType == txhelper.TxTypeRegular && txDirection == txhelper.TxDirectionReceived
case TxFilterTransferred:
return txType == txhelper.TxTypeRegular && txDirection == txhelper.TxDirectionTransferred
case TxFilterStaking:
switch txType {
case txhelper.TxTypeTicketPurchase:
fallthrough
case txhelper.TxTypeVote:
fallthrough
case txhelper.TxTypeRevocation:
return true
}

return false
case TxFilterCoinBase:
return txType == txhelper.TxTypeCoinBase
case TxFilterRegular:
return txType == txhelper.TxTypeRegular
case TxFilterMixed:
return txType == txhelper.TxTypeMixed
case TxFilterAll:
return true
}

return false
}

func (db *DB) prepareTxQuery(txFilter int32) (query storm.Query) {
func (db *DB) prepareTxQuery(txFilter, bestBlock int32) (query storm.Query) {
switch txFilter {
case TxFilterSent:
query = db.walletDataDB.Select(
Expand Down Expand Up @@ -86,6 +59,42 @@ func (db *DB) prepareTxQuery(txFilter int32) (query storm.Query) {
query = db.walletDataDB.Select(
q.Eq("Type", txhelper.TxTypeMixed),
)
case TxFilterVoted:
query = db.walletDataDB.Select(
q.Eq("Type", txhelper.TxTypeVote),
)
case TxFilterRevoked:
query = db.walletDataDB.Select(
q.Eq("Type", txhelper.TxTypeRevocation),
)
case TxFilterImmature:
query = db.walletDataDB.Select(
q.Eq("Type", txhelper.TxTypeTicketPurchase),
q.Or(
q.Eq("BlockHeight", -1), // include unconfirmed
q.Gt("BlockHeight", bestBlock-int32(db.chainParams.TicketMaturity)),
),
)
case TxFilterLive:
query = db.walletDataDB.Select(
q.Eq("Type", txhelper.TxTypeTicketPurchase),
q.Eq("TicketSpender", ""), // not spent by a vote or revoke
q.Gt("BlockHeight", 0), // mined
q.Lte("BlockHeight", bestBlock-int32(db.chainParams.TicketMaturity)), // must be matured
q.Or( // must not be expired (tx with expiry=0 are excluded)
q.Gte("Expiry", bestBlock),
q.Eq("Expiry", 0),
),
)
case TxFilterExpired:
query = db.walletDataDB.Select(
q.Eq("Type", txhelper.TxTypeTicketPurchase),
q.Eq("TicketSpender", ""),
q.And(
q.Lte("Expiry", bestBlock),
q.Not(q.Eq("Expiry", 0)),
),
)
default:
query = db.walletDataDB.Select(
q.True(),
Expand Down
Loading