vspd: Use filter.MatchAny instead of filter.Match.

This brings a notable performance improvement to spent ticket scanning
without changing functionality. Can cut scanning time in half.
This commit is contained in:
Jamie Holdstock 2023-09-04 16:46:24 +01:00 committed by GitHub
parent 0c5016ae62
commit 50c343e177
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

View File

@ -89,13 +89,16 @@ func (v *vspd) findSpentTickets(toCheck database.TicketList, startHeight int64)
numBlocks, pluralize(len(toCheck), "spent ticket")) numBlocks, pluralize(len(toCheck), "spent ticket"))
} }
// Get commitment address payment script for each ticket. // Get commitment address payment scripts and parse hashes for each ticket
// prior to the main loop. Two slices are needed because payment scripts
// must be in their own slice to be passed into the MatchAny func.
type ticketTuple struct { type ticketTuple struct {
dbTicket database.Ticket dbTicket database.Ticket
pkScript []byte hash chainhash.Hash
} }
tickets := make(map[chainhash.Hash]ticketTuple) tickets := make([]ticketTuple, 0, len(toCheck))
scripts := make([][]byte, 0, len(toCheck))
for _, ticket := range toCheck { for _, ticket := range toCheck {
parsedAddr, err := stdaddr.DecodeAddress(ticket.CommitmentAddress, params) parsedAddr, err := stdaddr.DecodeAddress(ticket.CommitmentAddress, params)
if err != nil { if err != nil {
@ -108,10 +111,8 @@ func (v *vspd) findSpentTickets(toCheck database.TicketList, startHeight int64)
return nil, 0, err return nil, 0, err
} }
tickets[*hash] = ticketTuple{ tickets = append(tickets, ticketTuple{ticket, *hash})
dbTicket: ticket, scripts = append(scripts, script)
pkScript: script,
}
} }
spent := make([]spentTicket, 0) spent := make([]spentTicket, 0)
@ -133,42 +134,44 @@ func (v *vspd) findSpentTickets(toCheck database.TicketList, startHeight int64)
return nil, 0, err return nil, 0, err
} }
var iBlock *wire.MsgBlock if !filter.MatchAny(key, scripts) {
outer: // No tickets are spent in this block, continue to the next one.
for ticketHash, ticket := range tickets { continue
if filter.Match(key, ticket.pkScript) { }
// Filter match means the ticket is likely spent in block. Get
// the full block to confirm. // Filter match means a ticket is likely spent in this block. Get the
if iBlock == nil { // full block to confirm.
iBlock, err = dcrdClient.GetBlock(iHash) iBlock, err := dcrdClient.GetBlock(iHash)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
for i := range tickets {
// The regular transaction tree does not need to be checked because
// tickets can only be spent by vote or revoke transactions which
// are always in the stake tree.
for _, blkTx := range iBlock.STransactions {
if !txSpendsTicket(blkTx, tickets[i].hash) {
continue
} }
// The regular transaction tree does not need to be checked // Confirmed - ticket is spent in block.
// because tickets can only be spent by vote or revoke
// transactions which are always in the stake tree.
for _, blkTx := range iBlock.STransactions {
if !txSpendsTicket(blkTx, ticketHash) {
continue
}
// Confirmed - ticket is spent in block. spent = append(spent, spentTicket{
dbTicket: tickets[i].dbTicket,
expiryHeight: tickets[i].dbTicket.PurchaseHeight + int64(params.TicketMaturity) + int64(params.TicketExpiry),
heightSpent: iHeight,
spendingTx: blkTx,
})
spent = append(spent, spentTicket{ // Remove this ticket and its script before continuing with the
dbTicket: ticket.dbTicket, // next one.
expiryHeight: ticket.dbTicket.PurchaseHeight + int64(params.TicketMaturity) + int64(params.TicketExpiry), tickets = nonOrderPreservingRemove(tickets, i)
heightSpent: iHeight, scripts = nonOrderPreservingRemove(scripts, i)
spendingTx: blkTx,
})
// Remove this ticket and continue with the next one. // Current index has been removed which means everything else
delete(tickets, ticketHash) // moved up one and thus the same index needs to be repeated.
continue outer i--
}
// Ticket is not spent in block.
} }
} }
@ -193,6 +196,16 @@ func txSpendsTicket(tx *wire.MsgTx, outputHash chainhash.Hash) bool {
return false return false
} }
// nonOrderPreservingRemove removes index i from slice s. The order of the slice
// is not preserved, however the important property of the function is that
// removing the same index from two slices of identical length will modify the
// order of each slice in the same way. This allows a 1-to-1 mapping between two
// (or more) slices to be preserved.
func nonOrderPreservingRemove[T any](s []T, i int) []T {
s[i] = s[len(s)-1]
return s[:len(s)-1]
}
// pluralize suffixes the provided noun with "s" if n is not 1, then // pluralize suffixes the provided noun with "s" if n is not 1, then
// concatenates n and noun with a space between them. For example: // concatenates n and noun with a space between them. For example:
// //