blockchain: Separate utxo state from tx flags.
This splits the utxo packed flags into two separate types, utxoState and utxoFlags. The reasoning is that: - This cleanly separates the purpose of the flags. utxoState defines the in-memory state of a utxo entry, whereas utxoFlags defines additional information for the containing transaction of a utxo entry. - This makes room for an additional state that is required for the utxo cache, namely whether or not a utxo entry is fresh (does not exist as an unspent transaction output in the database).
This commit is contained in:
parent
20d48dc775
commit
21b0dadcb3
@ -1,5 +1,5 @@
|
||||
// Copyright (c) 2015-2016 The btcsuite developers
|
||||
// Copyright (c) 2016-2020 The Decred developers
|
||||
// Copyright (c) 2016-2021 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -993,16 +993,13 @@ func deserializeUtxoEntry(serialized []byte, txOutIndex uint32) (*UtxoEntry, err
|
||||
offset += bytesRead
|
||||
|
||||
// Create a new utxo entry with the details deserialized above.
|
||||
const spent = false
|
||||
const modified = false
|
||||
entry := &UtxoEntry{
|
||||
amount: amount,
|
||||
pkScript: script,
|
||||
blockHeight: uint32(blockHeight),
|
||||
blockIndex: uint32(blockIndex),
|
||||
scriptVersion: scriptVersion,
|
||||
packedFlags: encodeUtxoFlags(isCoinBase, spent, modified, hasExpiry,
|
||||
txType),
|
||||
packedFlags: encodeUtxoFlags(isCoinBase, hasExpiry, txType),
|
||||
}
|
||||
|
||||
// Copy the minimal outputs if this was a ticket submission output.
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
// Copyright (c) 2015-2016 The btcsuite developers
|
||||
// Copyright (c) 2015-2020 The Decred developers
|
||||
// Copyright (c) 2015-2021 The Decred developers
|
||||
// Use of this source code is governed by an ISC
|
||||
// license that can be found in the LICENSE file.
|
||||
|
||||
@ -828,9 +828,6 @@ func TestUtxoSerialization(t *testing.T) {
|
||||
withCoinbase = true
|
||||
noExpiry = false
|
||||
withExpiry = true
|
||||
unspent = false
|
||||
spent = true
|
||||
unmodified = false
|
||||
)
|
||||
|
||||
tests := []struct {
|
||||
@ -851,8 +848,6 @@ func TestUtxoSerialization(t *testing.T) {
|
||||
scriptVersion: 0,
|
||||
packedFlags: encodeUtxoFlags(
|
||||
withCoinbase,
|
||||
unspent,
|
||||
unmodified,
|
||||
noExpiry,
|
||||
stake.TxTypeRegular,
|
||||
),
|
||||
@ -872,8 +867,6 @@ func TestUtxoSerialization(t *testing.T) {
|
||||
scriptVersion: 0,
|
||||
packedFlags: encodeUtxoFlags(
|
||||
withCoinbase,
|
||||
unspent,
|
||||
unmodified,
|
||||
noExpiry,
|
||||
stake.TxTypeRegular,
|
||||
),
|
||||
@ -892,8 +885,6 @@ func TestUtxoSerialization(t *testing.T) {
|
||||
scriptVersion: 0,
|
||||
packedFlags: encodeUtxoFlags(
|
||||
noCoinbase,
|
||||
unspent,
|
||||
unmodified,
|
||||
noExpiry,
|
||||
stake.TxTypeRegular,
|
||||
),
|
||||
@ -912,8 +903,6 @@ func TestUtxoSerialization(t *testing.T) {
|
||||
scriptVersion: 0,
|
||||
packedFlags: encodeUtxoFlags(
|
||||
noCoinbase,
|
||||
unspent,
|
||||
unmodified,
|
||||
withExpiry,
|
||||
stake.TxTypeSStx,
|
||||
),
|
||||
@ -940,8 +929,6 @@ func TestUtxoSerialization(t *testing.T) {
|
||||
scriptVersion: 0xffff,
|
||||
packedFlags: encodeUtxoFlags(
|
||||
withCoinbase,
|
||||
unspent,
|
||||
unmodified,
|
||||
noExpiry,
|
||||
stake.TxTypeRegular,
|
||||
),
|
||||
@ -960,8 +947,6 @@ func TestUtxoSerialization(t *testing.T) {
|
||||
scriptVersion: 0,
|
||||
packedFlags: encodeUtxoFlags(
|
||||
noCoinbase,
|
||||
unspent,
|
||||
unmodified,
|
||||
withExpiry,
|
||||
stake.TxTypeRegular,
|
||||
),
|
||||
@ -979,10 +964,9 @@ func TestUtxoSerialization(t *testing.T) {
|
||||
blockHeight: 33333,
|
||||
blockIndex: 3,
|
||||
scriptVersion: 0,
|
||||
state: utxoStateModified | utxoStateSpent,
|
||||
packedFlags: encodeUtxoFlags(
|
||||
withCoinbase,
|
||||
spent,
|
||||
unmodified,
|
||||
withExpiry,
|
||||
stake.TxTypeRegular,
|
||||
),
|
||||
|
||||
@ -6,55 +6,57 @@ package blockchain
|
||||
|
||||
import "github.com/decred/dcrd/blockchain/stake/v4"
|
||||
|
||||
// utxoFlags defines additional information and state for a transaction output
|
||||
// in a utxo view. The bit representation is:
|
||||
// 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
|
||||
// bits 2-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
|
||||
)
|
||||
|
||||
// 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 - transaction output has been spent
|
||||
// bit 2 - transaction output has been modified since it was loaded
|
||||
// bit 3 - containing transaction has an expiry
|
||||
// bits 4-7 - transaction type
|
||||
// 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
|
||||
|
||||
// utxoFlagSpent indicates that a txout is spent.
|
||||
utxoFlagSpent
|
||||
|
||||
// utxoFlagModified indicates that a txout has been modified since it was
|
||||
// loaded.
|
||||
utxoFlagModified
|
||||
|
||||
// utxoFlagHasExpiry indicates that a txout was contained in a tx that
|
||||
// included an expiry.
|
||||
utxoFlagHasExpiry
|
||||
)
|
||||
|
||||
const (
|
||||
// utxoFlagTxTypeBitmask describes the bitmask that yields bits 4-7 from
|
||||
// utxoFlagTxTypeBitmask describes the bitmask that yields bits 2-5 from
|
||||
// utxoFlags.
|
||||
utxoFlagTxTypeBitmask = 0xf0
|
||||
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 = 4
|
||||
utxoFlagTxTypeShift = 2
|
||||
)
|
||||
|
||||
// encodeUtxoFlags returns utxoFlags representing the passed parameters.
|
||||
func encodeUtxoFlags(coinbase bool, spent bool, modified bool, hasExpiry bool,
|
||||
txType stake.TxType) utxoFlags {
|
||||
|
||||
func encodeUtxoFlags(coinbase bool, hasExpiry bool, txType stake.TxType) utxoFlags {
|
||||
packedFlags := utxoFlags(txType) << utxoFlagTxTypeShift
|
||||
if coinbase {
|
||||
packedFlags |= utxoFlagCoinBase
|
||||
}
|
||||
if spent {
|
||||
packedFlags |= utxoFlagSpent
|
||||
}
|
||||
if modified {
|
||||
packedFlags |= utxoFlagModified
|
||||
}
|
||||
if hasExpiry {
|
||||
packedFlags |= utxoFlagHasExpiry
|
||||
}
|
||||
@ -100,16 +102,20 @@ type UtxoEntry struct {
|
||||
blockIndex uint32
|
||||
scriptVersion uint16
|
||||
|
||||
// packedFlags contains additional info about 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.
|
||||
// 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
|
||||
}
|
||||
|
||||
// isModified returns whether or not the output has been modified since it was
|
||||
// loaded.
|
||||
func (entry *UtxoEntry) isModified() bool {
|
||||
return entry.packedFlags&utxoFlagModified == utxoFlagModified
|
||||
return entry.state&utxoStateModified == utxoStateModified
|
||||
}
|
||||
|
||||
// IsCoinBase returns whether or not the output was contained in a coinbase
|
||||
@ -121,7 +127,7 @@ func (entry *UtxoEntry) IsCoinBase() bool {
|
||||
// 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.packedFlags&utxoFlagSpent == utxoFlagSpent
|
||||
return entry.state&utxoStateSpent == utxoStateSpent
|
||||
}
|
||||
|
||||
// HasExpiry returns whether or not the output was contained in a transaction
|
||||
@ -157,7 +163,7 @@ func (entry *UtxoEntry) Spend() {
|
||||
}
|
||||
|
||||
// Mark the output as spent and modified.
|
||||
entry.packedFlags |= utxoFlagSpent | utxoFlagModified
|
||||
entry.state |= utxoStateSpent | utxoStateModified
|
||||
}
|
||||
|
||||
// Amount returns the amount of the output.
|
||||
@ -203,6 +209,7 @@ func (entry *UtxoEntry) Clone() *UtxoEntry {
|
||||
blockHeight: entry.blockHeight,
|
||||
blockIndex: entry.blockIndex,
|
||||
scriptVersion: entry.scriptVersion,
|
||||
state: entry.state,
|
||||
packedFlags: entry.packedFlags,
|
||||
}
|
||||
|
||||
|
||||
@ -20,40 +20,31 @@ func TestEncodeUtxoFlags(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
coinbase bool
|
||||
spent bool
|
||||
modified bool
|
||||
hasExpiry bool
|
||||
txType stake.TxType
|
||||
want utxoFlags
|
||||
}{{
|
||||
name: "no flags set, regular tx",
|
||||
coinbase: false,
|
||||
spent: false,
|
||||
modified: false,
|
||||
hasExpiry: false,
|
||||
txType: stake.TxTypeRegular,
|
||||
want: 0x00,
|
||||
}, {
|
||||
name: "coinbase, has expiry, vote tx",
|
||||
coinbase: true,
|
||||
spent: false,
|
||||
modified: false,
|
||||
hasExpiry: true,
|
||||
txType: stake.TxTypeSSGen,
|
||||
want: 0x29,
|
||||
want: 0x0b,
|
||||
}, {
|
||||
name: "spent, modified, has expiry, ticket tx",
|
||||
name: "has expiry, ticket tx",
|
||||
coinbase: false,
|
||||
spent: true,
|
||||
modified: true,
|
||||
hasExpiry: true,
|
||||
txType: stake.TxTypeSStx,
|
||||
want: 0x1e,
|
||||
want: 0x06,
|
||||
}}
|
||||
|
||||
for _, test := range tests {
|
||||
got := encodeUtxoFlags(test.coinbase, test.spent, test.modified,
|
||||
test.hasExpiry, test.txType)
|
||||
got := encodeUtxoFlags(test.coinbase, test.hasExpiry, test.txType)
|
||||
if got != test.want {
|
||||
t.Errorf("%q: unexpected result -- got %x, want %x", test.name, got,
|
||||
test.want)
|
||||
@ -104,9 +95,9 @@ func TestUtxoEntry(t *testing.T) {
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
coinbase bool
|
||||
spent bool
|
||||
modified bool
|
||||
coinbase bool
|
||||
expiry bool
|
||||
txType stake.TxType
|
||||
amount int64
|
||||
@ -171,13 +162,26 @@ func TestUtxoEntry(t *testing.T) {
|
||||
scriptVersion: test.scriptVersion,
|
||||
packedFlags: encodeUtxoFlags(
|
||||
test.coinbase,
|
||||
test.spent,
|
||||
test.modified,
|
||||
test.expiry,
|
||||
test.txType,
|
||||
),
|
||||
}
|
||||
|
||||
// Set state flags given the parameters for the current test.
|
||||
if test.spent {
|
||||
entry.state |= utxoStateSpent
|
||||
}
|
||||
if test.modified {
|
||||
entry.state |= utxoStateModified
|
||||
}
|
||||
|
||||
// Validate the spent flag.
|
||||
isSpent := entry.IsSpent()
|
||||
if isSpent != test.spent {
|
||||
t.Fatalf("%q: unexpected spent flag -- got %v, want %v", test.name,
|
||||
isSpent, test.spent)
|
||||
}
|
||||
|
||||
// Validate the modified flag.
|
||||
isModified := entry.isModified()
|
||||
if isModified != test.modified {
|
||||
@ -192,13 +196,6 @@ func TestUtxoEntry(t *testing.T) {
|
||||
isCoinBase, test.coinbase)
|
||||
}
|
||||
|
||||
// Validate the spent flag.
|
||||
isSpent := entry.IsSpent()
|
||||
if isSpent != test.spent {
|
||||
t.Fatalf("%q: unexpected spent flag -- got %v, want %v", test.name,
|
||||
isSpent, test.spent)
|
||||
}
|
||||
|
||||
// Validate the expiry flag.
|
||||
hasExpiry := entry.HasExpiry()
|
||||
if hasExpiry != test.expiry {
|
||||
@ -206,6 +203,13 @@ func TestUtxoEntry(t *testing.T) {
|
||||
hasExpiry, test.expiry)
|
||||
}
|
||||
|
||||
// Validate the type of the transaction that the output is contained in.
|
||||
gotTxType := entry.TransactionType()
|
||||
if gotTxType != test.txType {
|
||||
t.Fatalf("%q: unexpected transaction type -- got %v, want %v", test.name,
|
||||
gotTxType, test.txType)
|
||||
}
|
||||
|
||||
// Validate the height of the block containing the output.
|
||||
gotBlockHeight := entry.BlockHeight()
|
||||
if gotBlockHeight != int64(test.blockHeight) {
|
||||
@ -220,13 +224,6 @@ func TestUtxoEntry(t *testing.T) {
|
||||
gotBlockIndex, test.blockIndex)
|
||||
}
|
||||
|
||||
// Validate the type of the transaction that the output is contained in.
|
||||
gotTxType := entry.TransactionType()
|
||||
if gotTxType != test.txType {
|
||||
t.Fatalf("%q: unexpected transaction type -- got %v, want %v", test.name,
|
||||
gotTxType, test.txType)
|
||||
}
|
||||
|
||||
// Validate the amount of the output.
|
||||
gotAmount := entry.Amount()
|
||||
if gotAmount != test.amount {
|
||||
|
||||
@ -79,6 +79,11 @@ func (view *UtxoViewpoint) addTxOut(outpoint wire.OutPoint, txOut *wire.TxOut,
|
||||
entry.scriptVersion = txOut.Version
|
||||
entry.packedFlags = packedFlags
|
||||
entry.ticketMinOuts = ticketMinOuts
|
||||
|
||||
// The referenced transaction output should always be marked as unspent and
|
||||
// modified when being added to the view.
|
||||
entry.state &^= utxoStateSpent
|
||||
entry.state |= utxoStateModified
|
||||
}
|
||||
|
||||
// AddTxOut adds the specified output of the passed transaction to the view if
|
||||
@ -96,15 +101,13 @@ func (view *UtxoViewpoint) AddTxOut(tx *dcrutil.Tx, txOutIdx uint32,
|
||||
|
||||
// Set encoded flags for the transaction.
|
||||
isCoinBase := standalone.IsCoinBaseTx(msgTx, isTreasuryEnabled)
|
||||
const spent = false
|
||||
hasExpiry := msgTx.Expiry != wire.NoExpiryValue
|
||||
const modified = true
|
||||
txType := stake.DetermineTxType(msgTx, isTreasuryEnabled)
|
||||
tree := wire.TxTreeRegular
|
||||
if txType != stake.TxTypeRegular {
|
||||
tree = wire.TxTreeStake
|
||||
}
|
||||
flags := encodeUtxoFlags(isCoinBase, spent, modified, hasExpiry, txType)
|
||||
flags := encodeUtxoFlags(isCoinBase, hasExpiry, txType)
|
||||
|
||||
// Update existing entries. All fields are updated because it's possible
|
||||
// (although extremely unlikely) that the existing entry is being replaced by
|
||||
@ -131,15 +134,13 @@ func (view *UtxoViewpoint) AddTxOuts(tx *dcrutil.Tx, blockHeight int64, blockInd
|
||||
|
||||
// Set encoded flags for the transaction.
|
||||
isCoinBase := standalone.IsCoinBaseTx(msgTx, isTreasuryEnabled)
|
||||
const spent = false
|
||||
hasExpiry := msgTx.Expiry != wire.NoExpiryValue
|
||||
const modified = true
|
||||
txType := stake.DetermineTxType(msgTx, isTreasuryEnabled)
|
||||
tree := wire.TxTreeRegular
|
||||
if txType != stake.TxTypeRegular {
|
||||
tree = wire.TxTreeStake
|
||||
}
|
||||
flags := encodeUtxoFlags(isCoinBase, spent, modified, hasExpiry, txType)
|
||||
flags := encodeUtxoFlags(isCoinBase, hasExpiry, txType)
|
||||
|
||||
// Loop through all of the transaction outputs and add those which are not
|
||||
// provably unspendable.
|
||||
@ -325,16 +326,14 @@ func (view *UtxoViewpoint) disconnectTransactions(block *dcrutil.Block, stxos []
|
||||
outpoint.Index = uint32(txOutIdx)
|
||||
entry := view.entries[outpoint]
|
||||
if entry == nil {
|
||||
const spent = false
|
||||
const modified = true
|
||||
entry = &UtxoEntry{
|
||||
amount: txOut.Value,
|
||||
pkScript: txOut.PkScript,
|
||||
blockHeight: uint32(block.Height()),
|
||||
blockIndex: uint32(txIdx),
|
||||
scriptVersion: txOut.Version,
|
||||
packedFlags: encodeUtxoFlags(isCoinBase, spent, modified, hasExpiry,
|
||||
txType),
|
||||
state: utxoStateModified,
|
||||
packedFlags: encodeUtxoFlags(isCoinBase, hasExpiry, txType),
|
||||
}
|
||||
|
||||
if isTicketSubmissionOutput(txType, uint32(txOutIdx)) {
|
||||
@ -374,8 +373,6 @@ func (view *UtxoViewpoint) disconnectTransactions(block *dcrutil.Block, stxos []
|
||||
txIn := msgTx.TxIn[txInIdx]
|
||||
entry := view.entries[txIn.PreviousOutPoint]
|
||||
if entry == nil {
|
||||
const spent = false
|
||||
const modified = true
|
||||
entry = &UtxoEntry{
|
||||
amount: txIn.ValueIn,
|
||||
pkScript: stxo.pkScript,
|
||||
@ -383,8 +380,9 @@ func (view *UtxoViewpoint) disconnectTransactions(block *dcrutil.Block, stxos []
|
||||
blockHeight: stxo.blockHeight,
|
||||
blockIndex: stxo.blockIndex,
|
||||
scriptVersion: stxo.scriptVersion,
|
||||
packedFlags: encodeUtxoFlags(stxo.IsCoinBase(), spent, modified,
|
||||
stxo.HasExpiry(), stxo.TransactionType()),
|
||||
state: utxoStateModified,
|
||||
packedFlags: encodeUtxoFlags(stxo.IsCoinBase(), stxo.HasExpiry(),
|
||||
stxo.TransactionType()),
|
||||
}
|
||||
|
||||
view.entries[txIn.PreviousOutPoint] = entry
|
||||
@ -392,8 +390,8 @@ func (view *UtxoViewpoint) disconnectTransactions(block *dcrutil.Block, stxos []
|
||||
|
||||
// Mark the existing referenced transaction output as unspent and
|
||||
// modified.
|
||||
entry.packedFlags &^= utxoFlagSpent
|
||||
entry.packedFlags |= utxoFlagModified
|
||||
entry.state &^= utxoStateSpent
|
||||
entry.state |= utxoStateModified
|
||||
}
|
||||
}
|
||||
|
||||
@ -601,7 +599,7 @@ func (view *UtxoViewpoint) commit() {
|
||||
continue
|
||||
}
|
||||
|
||||
entry.packedFlags &^= utxoFlagModified
|
||||
entry.state &^= utxoStateModified
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user