-
Notifications
You must be signed in to change notification settings - Fork 287
/
utxoentry.go
260 lines (218 loc) · 8.19 KB
/
utxoentry.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
// Copyright (c) 2022 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/v5"
)
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 modified since it was loaded
// bit 1 - transaction output has been spent
// bit 2 - transaction output has been spent by tx later in the same block
// bit 3 - transaction output is fresh
// bits 4-7 - unused
type utxoState uint8
const (
// utxoStateModified indicates that a txout has been modified since it was
// loaded.
utxoStateModified utxoState = 1 << iota
// utxoStateSpent indicates that a txout is spent.
utxoStateSpent
// utxoStateSpentByZeroConf indicates that a txout was spent by another
// transaction later in the same block.
utxoStateSpentByZeroConf
// 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
}
// isSpentByZeroConf returns whether or not the output is marked as spent by a
// zero confirmation transaction. In other words, it is an output that was
// created in a block and later spent by another transaction in the same block.
func (entry *UtxoEntry) isSpentByZeroConf() bool {
return entry.state&utxoStateSpentByZeroConf == utxoStateSpentByZeroConf
}
// 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
}