-
Notifications
You must be signed in to change notification settings - Fork 61
/
utxo.go
283 lines (247 loc) · 9.28 KB
/
utxo.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
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
// Copyright (C) 2017, Zipper Team. All rights reserved.
//
// This file is part of zipper
//
// The zipper is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The zipper is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
//
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package utxo
import (
"github.com/zipper-project/zipper/proto"
)
type Hash [32]byte
// utxoOutput houses details about an individual unspent transaction output such
// as whether or not it is spent, its address, and how much it pays.
type utxoOutput struct {
asset int32 // The asset of the output
amount int64 // The amount of the output.
address []byte // The address of the output
spent bool // Output is spent.
}
// UtxoEntry contains contextual information about an unspent transaction such
// as which block it was found in, and the spent status of its outputs.
type UtxoEntry struct {
modified bool // Entry changed since load.
version int32 // The version of this tx.
blockHeight int32 // Height of block containing tx.
sparseOutputs map[int32]*utxoOutput // Sparse map of unspent outputs.
}
// Version returns the version of the transaction the utxo represents.
func (entry *UtxoEntry) Version() int32 {
return entry.version
}
// BlockHeight returns the height of the block containing the transaction the
// utxo entry represents.
func (entry *UtxoEntry) BlockHeight() int32 {
return entry.blockHeight
}
// IsOutputSpent returns whether or not the provided output index has been
// spent based upon the current state of the unspent transaction output view
// the entry was obtained from.
//
// Returns true if the output index references an output that does not exist
// either due to it being invalid or because the output is not part of the view
// due to previously being spent/pruned.
func (entry *UtxoEntry) IsOutputSpent(outputIndex int32) bool {
output, ok := entry.sparseOutputs[outputIndex]
if !ok {
return true
}
return output.spent
}
// UnspendOutput marks the output at the provided index as unspent. Specifying an
// output index that does not exist will not have any effect.
func (entry *UtxoEntry) UnspendOutput(outputIndex int32) {
output, ok := entry.sparseOutputs[outputIndex]
if !ok {
return
}
// Nothing to do if the output is already unspent.
if !output.spent {
return
}
entry.modified = true
output.spent = false
}
// SpendOutput marks the output at the provided index as spent. Specifying an
// output index that does not exist will not have any effect.
func (entry *UtxoEntry) SpendOutput(outputIndex int32) {
output, ok := entry.sparseOutputs[outputIndex]
if !ok {
return
}
// Nothing to do if the output is already spent.
if output.spent {
return
}
entry.modified = true
output.spent = true
}
// IsFullySpent returns whether or not the transaction the utxo entry represents
// is fully spent.
func (entry *UtxoEntry) IsFullySpent() bool {
// The entry is not fully spent if any of the outputs are unspent.
for _, output := range entry.sparseOutputs {
if !output.spent {
return false
}
}
return true
}
// AssetByIndex returns the amount of the provided output index.
//
// Returns 0 if the output index references an output that does not exist
// either due to it being invalid or because the output is not part of the view
// due to previously being spent/pruned.
func (entry *UtxoEntry) AssetByIndex(outputIndex int32) int32 {
return 0
}
// AmountByIndex returns the amount of the provided output index.
//
// Returns 0 if the output index references an output that does not exist
// either due to it being invalid or because the output is not part of the view
// due to previously being spent/pruned.
func (entry *UtxoEntry) AmountByIndex(outputIndex int32) int64 {
output, ok := entry.sparseOutputs[outputIndex]
if !ok {
return 0
}
return output.amount
}
// AddressByIndex returns the public key script for the provided output index.
//
// Returns nil if the output index references an output that does not exist
// either due to it being invalid or because the output is not part of the view
// due to previously being spent/pruned.
func (entry *UtxoEntry) AddressByIndex(outputIndex int32) []byte {
output, ok := entry.sparseOutputs[outputIndex]
if !ok {
return nil
}
return output.address
}
// newUtxoEntry returns a new unspent transaction output entry with the provided
// coinbase flag and block height ready to have unspent outputs added.
func newUtxoEntry(version int32, blockHeight int32) *UtxoEntry {
return &UtxoEntry{
version: version,
blockHeight: blockHeight,
sparseOutputs: make(map[int32]*utxoOutput),
}
}
// UtxoViewpoint represents a view into the set of unspent transaction outputs
// from a specific point of view in the chain. For example, it could be for
// the end of the main chain, some point in the history of the main chain, or
// down a side chain.
//
// The unspent outputs are needed by other transactions for things such as
// script validation and double spend prevention.
type UtxoViewpoint struct {
entries map[Hash]*UtxoEntry
}
// LookupEntry returns information about a given transaction according to the
// current state of the view. It will return nil if the passed transaction
// hash does not exist in the view or is otherwise not available such as when
// it has been disconnected during a reorg.
func (view *UtxoViewpoint) LookupEntry(txHash *Hash) *UtxoEntry {
entry, ok := view.entries[*txHash]
if !ok {
return nil
}
return entry
}
// Entries returns the underlying map that stores of all the utxo entries.
func (view *UtxoViewpoint) Entries() map[Hash]*UtxoEntry {
return view.entries
}
// commit prunes all entries marked modified that are now fully spent and marks
// all entries as unmodified.
func (view *UtxoViewpoint) commit() {
for txHash, entry := range view.entries {
if entry == nil || (entry.modified && entry.IsFullySpent()) {
delete(view.entries, txHash)
continue
}
entry.modified = false
}
}
// NewUtxoViewpoint returns a new empty unspent transaction output view.
func NewUtxoViewpoint() *UtxoViewpoint {
return &UtxoViewpoint{
entries: make(map[Hash]*UtxoEntry),
}
}
// AddTxOuts adds all outputs in the passed transaction which are not provably
// unspendable to the view. When the view already has entries for any of the
// outputs, they are simply marked unspent. All fields will be updated for
// existing entries since it's possible it has changed during a reorg.
func (view *UtxoViewpoint) AddTxOuts(tx *proto.Transaction, blockHeight int32) {
// When there are not already any utxos associated with the transaction,
// add a new entry for it to the view.
//entry := view.LookupEntry(tx.Hash())
// if entry == nil {
// entry = newUtxoEntry(tx.Header.Version, blockHeight)
// view.entries[*tx.Hash()] = entry
// } else {
// entry.blockHeight = blockHeight
// }
// entry.modified = true
// // Loop all of the transaction outputs and add those which are not
// // provably unspendable.
// for txOutIdx, txOut := range tx.Outputs {
// // Update existing entries. All fields are updated because it's
// // possible (although extremely unlikely) that the existing
// // entry is being replaced by a different transaction with the
// // same hash. This is allowed so long as the previous
// // transaction is fully spent.
// if output, ok := entry.sparseOutputs[int32(txOutIdx)]; ok {
// output.spent = false
// output.asset = txOut.Asset
// output.amount = txOut.Value
// output.address = txOut.Address
// continue
// }
// // Add the unspent transaction output.
// entry.sparseOutputs[int32(txOutIdx)] = &utxoOutput{
// spent: false,
// asset: txOut.Asset,
// amount: txOut.Value,
// address: txOut.Address,
// }
// }
return
}
// connectTransaction updates the view by adding all new utxos created by the
// passed transaction and marking all utxos that the transactions spend as
// spent. In addition, when the 'stxos' argument is not nil, it will be updated
// to append an entry for each spent txout. An error will be returned if the
// view does not contain the required utxos.
func (view *UtxoViewpoint) connectTransaction(tx *proto.Transaction, blockHeight int32) error {
// Spend the referenced utxos by marking them spent in the view and,
// if a slice was provided for the spent txout details, append an entry
// to it.
// for _, txIn := range tx.Inputs {
// originIndex := txIn.PreviousOutPoint.Index
// entry := view.entries[txIn.PreviousOutPoint.Hash]
// // Ensure the referenced utxo exists in the view. This should
// // never happen unless there is a bug is introduced in the code.
// if entry == nil {
// //TODO
// return fmt.Errorf("view missing input %v", txIn.PreviousOutPoint)
// }
// entry.SpendOutput(originIndex)
// }
// // Add the transaction's outputs as available utxos.
// view.AddTxOuts(tx, blockHeight)
return nil
}