dcrd/blockchain/utxoentry.go
Ryan Staudt 9b5e8c7c14 blockchain: Add size method to UtxoEntry.
This adds a size method to UtxoEntry, which returns the number of bytes
that the entry uses on a 64-bit platform.  This will be used as part of
tracking the total size of the utxo cache.
2021-02-22 12:16:31 -06:00

246 lines
7.6 KiB
Go

// Copyright (c) 2021 The Decred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package blockchain
import (
"github.com/decred/dcrd/blockchain/stake/v4"
)
const (
// baseEntrySize is the base size of a utxo entry on a 64-bit platform,
// excluding the contents of the script and ticket minimal outputs. It is
// equivalent to what unsafe.Sizeof(UtxoEntry{}) returns on a 64-bit platform.
baseEntrySize = 56
)
// utxoState defines the in-memory state of a utxo entry.
//
// The bit representation is:
// bit 0 - transaction output has been spent
// bit 1 - transaction output has been modified since it was loaded
// bit 2 - transaction output is fresh
// bits 3-7 - unused
type utxoState uint8
const (
// utxoStateSpent indicates that a txout is spent.
utxoStateSpent utxoState = 1 << iota
// utxoStateModified indicates that a txout has been modified since it was
// loaded.
utxoStateModified
// utxoStateFresh indicates that a txout is fresh, which means that it exists
// in the utxo cache but does not exist in the underlying database.
utxoStateFresh
)
// utxoFlags defines additional information for the containing transaction of a
// utxo entry.
//
// The bit representation is:
// bit 0 - containing transaction is a coinbase
// bit 1 - containing transaction has an expiry
// bits 2-5 - transaction type
type utxoFlags uint8
const (
// utxoFlagCoinBase indicates that a txout was contained in a coinbase tx.
utxoFlagCoinBase utxoFlags = 1 << iota
// utxoFlagHasExpiry indicates that a txout was contained in a tx that
// included an expiry.
utxoFlagHasExpiry
)
const (
// utxoFlagTxTypeBitmask describes the bitmask that yields bits 2-5 from
// utxoFlags.
utxoFlagTxTypeBitmask = 0x3c
// utxoFlagTxTypeShift is the number of bits to shift utxoFlags to the right
// to yield the correct integer value after applying the bitmask with AND.
utxoFlagTxTypeShift = 2
)
// encodeUtxoFlags returns utxoFlags representing the passed parameters.
func encodeUtxoFlags(coinbase bool, hasExpiry bool, txType stake.TxType) utxoFlags {
packedFlags := utxoFlags(txType) << utxoFlagTxTypeShift
if coinbase {
packedFlags |= utxoFlagCoinBase
}
if hasExpiry {
packedFlags |= utxoFlagHasExpiry
}
return packedFlags
}
// isTicketSubmissionOutput returns true if the output is a ticket submission.
func isTicketSubmissionOutput(txType stake.TxType, txOutIdx uint32) bool {
return txType == stake.TxTypeSStx && txOutIdx == 0
}
// ticketMinimalOutputs stores the minimal outputs for ticket transactions and
// is used in ticket submission utxo entries.
//
// The minimal outputs for ticket transactions are stored since all outputs of a
// ticket need to be retrieved when validating vote transaction inputs or
// revoking a ticket.
type ticketMinimalOutputs struct {
data []byte
}
// UtxoEntry houses details about an individual transaction output in a utxo
// view such as whether or not it was contained in a coinbase tx, the height of
// the block that contains the tx, whether or not it is spent, its public key
// script, and how much it pays.
//
// The struct is aligned for memory efficiency.
type UtxoEntry struct {
amount int64
pkScript []byte
// ticketMinOuts is the minimal outputs for the ticket transaction that the
// output is contained in. This is only stored in ticket submission outputs
// and is nil for all other output types.
//
// Note that this is using a pointer rather than a slice in order to occupy
// less space when it is nil. It is nil in the vast majority of entries, so
// this provides a significant overall reduction in memory usage.
ticketMinOuts *ticketMinimalOutputs
blockHeight uint32
blockIndex uint32
scriptVersion uint16
// state contains info for the in-memory state of the output as defined by
// utxoState.
state utxoState
// packedFlags contains additional info for the containing transaction of the
// output as defined by utxoFlags. This approach is used in order to reduce
// memory usage since there will be a lot of these in memory.
packedFlags utxoFlags
}
// size returns the number of bytes that the entry uses on a 64-bit platform.
func (entry *UtxoEntry) size() uint64 {
size := baseEntrySize + len(entry.pkScript)
if entry.ticketMinOuts != nil {
size += len(entry.ticketMinOuts.data)
}
return uint64(size)
}
// isModified returns whether or not the output has been modified since it was
// loaded.
func (entry *UtxoEntry) isModified() bool {
return entry.state&utxoStateModified == utxoStateModified
}
// isFresh returns whether or not the output is fresh.
func (entry *UtxoEntry) isFresh() bool {
return entry.state&utxoStateFresh == utxoStateFresh
}
// IsCoinBase returns whether or not the output was contained in a coinbase
// transaction.
func (entry *UtxoEntry) IsCoinBase() bool {
return entry.packedFlags&utxoFlagCoinBase == utxoFlagCoinBase
}
// IsSpent returns whether or not the output has been spent based upon the
// current state of the unspent transaction output view it was obtained from.
func (entry *UtxoEntry) IsSpent() bool {
return entry.state&utxoStateSpent == utxoStateSpent
}
// HasExpiry returns whether or not the output was contained in a transaction
// that included an expiry.
func (entry *UtxoEntry) HasExpiry() bool {
return entry.packedFlags&utxoFlagHasExpiry == utxoFlagHasExpiry
}
// BlockHeight returns the height of the block containing the output.
func (entry *UtxoEntry) BlockHeight() int64 {
return int64(entry.blockHeight)
}
// BlockIndex returns the index of the transaction that the output is contained
// in.
func (entry *UtxoEntry) BlockIndex() uint32 {
return entry.blockIndex
}
// TransactionType returns the type of the transaction that the output is
// contained in.
func (entry *UtxoEntry) TransactionType() stake.TxType {
txType := (entry.packedFlags & utxoFlagTxTypeBitmask) >> utxoFlagTxTypeShift
return stake.TxType(txType)
}
// Spend marks the output as spent. Spending an output that is already spent
// has no effect.
func (entry *UtxoEntry) Spend() {
// Nothing to do if the output is already spent.
if entry.IsSpent() {
return
}
// Mark the output as spent and modified.
entry.state |= utxoStateSpent | utxoStateModified
}
// Amount returns the amount of the output.
func (entry *UtxoEntry) Amount() int64 {
return entry.amount
}
// PkScript returns the public key script for the output.
func (entry *UtxoEntry) PkScript() []byte {
return entry.pkScript
}
// ScriptVersion returns the public key script version for the output.
func (entry *UtxoEntry) ScriptVersion() uint16 {
return entry.scriptVersion
}
// TicketMinimalOutputs returns the minimal outputs for the ticket transaction
// that the output is contained in. Note that the ticket minimal outputs are
// only stored in ticket submission outputs and nil will be returned for all
// other output types.
func (entry *UtxoEntry) TicketMinimalOutputs() []*stake.MinimalOutput {
if entry.ticketMinOuts == nil {
return nil
}
minOuts, _ := deserializeToMinimalOutputs(entry.ticketMinOuts.data)
return minOuts
}
// Clone returns a copy of the utxo entry. It performs a deep copy for any
// fields that are mutable. Specifically, the script and ticket minimal outputs
// are NOT deep copied since they are immutable.
func (entry *UtxoEntry) Clone() *UtxoEntry {
if entry == nil {
return nil
}
newEntry := &UtxoEntry{
amount: entry.amount,
pkScript: entry.pkScript,
ticketMinOuts: entry.ticketMinOuts,
blockHeight: entry.blockHeight,
blockIndex: entry.blockIndex,
scriptVersion: entry.scriptVersion,
state: entry.state,
packedFlags: entry.packedFlags,
}
return newEntry
}