/
utxoviewpoint.go
1001 lines (889 loc) · 35.4 KB
/
utxoviewpoint.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
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
// Copyright (c) 2015-2016 The btcsuite developers
// Copyright (c) 2015-2019 The Eacred developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package blockchain
import (
"fmt"
"github.com/Eacred/eacrd/blockchain/stake"
"github.com/Eacred/eacrd/blockchain/standalone"
"github.com/Eacred/eacrd/chaincfg/chainhash"
"github.com/Eacred/eacrd/database"
"github.com/Eacred/eacrd/dcrutil"
"github.com/Eacred/eacrd/txscript"
"github.com/Eacred/eacrd/wire"
)
// utxoOutput houses details about an individual unspent transaction output such
// as whether or not it is spent, its public key script, and how much it pays.
//
// Standard public key scripts are stored in the database using a compressed
// format. Since the vast majority of scripts are of the standard form, a fairly
// significant savings is achieved by discarding the portions of the standard
// scripts that can be reconstructed.
//
// Also, since it is common for only a specific output in a given utxo entry to
// be referenced from a redeeming transaction, the script and amount for a given
// output is not uncompressed until the first time it is accessed. This
// provides a mechanism to avoid the overhead of needlessly uncompressing all
// outputs for a given utxo entry at the time of load.
//
// The struct is aligned for memory efficiency.
type utxoOutput struct {
pkScript []byte // The public key script for the output.
amount int64 // The amount of the output.
scriptVersion uint16 // The script version
compressed bool // The public key script is compressed.
spent bool // Output is spent.
}
// maybeDecompress decompresses the amount and public key script fields of the
// utxo and marks it decompressed if needed.
func (o *utxoOutput) maybeDecompress(compressionVersion uint32) {
// Nothing to do if it's not compressed.
if !o.compressed {
return
}
o.pkScript = decompressScript(o.pkScript, compressionVersion)
o.compressed = false
}
// UtxoEntry contains contextual information about an unspent transaction such
// as whether or not it is a coinbase transaction, which block it was found in,
// and the spent status of its outputs.
//
// The struct is aligned for memory efficiency.
type UtxoEntry struct {
sparseOutputs map[uint32]*utxoOutput // Sparse map of unspent outputs.
stakeExtra []byte // Extra data for the staking system.
txType stake.TxType // The stake type of the transaction.
height uint32 // Height of block containing tx.
index uint32 // Index of containing tx in block.
txVersion uint16 // The tx version of this tx.
isCoinBase bool // Whether entry is a coinbase tx.
hasExpiry bool // Whether entry has an expiry.
modified bool // Entry changed since load.
}
// TxVersion returns the transaction version of the transaction the
// utxo represents.
func (entry *UtxoEntry) TxVersion() uint16 {
return entry.txVersion
}
// HasExpiry returns the transaction expiry for the transaction that the utxo
// entry represents.
func (entry *UtxoEntry) HasExpiry() bool {
return entry.hasExpiry
}
// IsCoinBase returns whether or not the transaction the utxo entry represents
// is a coinbase.
func (entry *UtxoEntry) IsCoinBase() bool {
return entry.isCoinBase
}
// BlockHeight returns the height of the block containing the transaction the
// utxo entry represents.
func (entry *UtxoEntry) BlockHeight() int64 {
return int64(entry.height)
}
// BlockIndex returns the height of the block containing the transaction the
// utxo entry represents.
func (entry *UtxoEntry) BlockIndex() uint32 {
return entry.index
}
// TransactionType returns the transaction type of the transaction the utxo entry
// represents.
func (entry *UtxoEntry) TransactionType() stake.TxType {
return entry.txType
}
// 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 uint32) bool {
output, ok := entry.sparseOutputs[outputIndex]
if !ok {
return true
}
return output.spent
}
// 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 uint32) {
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
}
// 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 uint32) int64 {
output, ok := entry.sparseOutputs[outputIndex]
if !ok {
return 0
}
return output.amount
}
// ScriptVersionByIndex returns the public key script for 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) ScriptVersionByIndex(outputIndex uint32) uint16 {
output, ok := entry.sparseOutputs[outputIndex]
if !ok {
return 0
}
return output.scriptVersion
}
// PkScriptByIndex 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) PkScriptByIndex(outputIndex uint32) []byte {
output, ok := entry.sparseOutputs[outputIndex]
if !ok {
return nil
}
// Ensure the output is decompressed before returning the script.
output.maybeDecompress(currentCompressionVersion)
return output.pkScript
}
// Clone returns a deep copy of the utxo entry.
func (entry *UtxoEntry) Clone() *UtxoEntry {
if entry == nil {
return nil
}
newEntry := &UtxoEntry{
stakeExtra: make([]byte, len(entry.stakeExtra)),
txVersion: entry.txVersion,
height: entry.height,
index: entry.index,
txType: entry.txType,
isCoinBase: entry.isCoinBase,
hasExpiry: entry.hasExpiry,
sparseOutputs: make(map[uint32]*utxoOutput),
}
copy(newEntry.stakeExtra, entry.stakeExtra)
for outputIndex, output := range entry.sparseOutputs {
newEntry.sparseOutputs[outputIndex] = &utxoOutput{
pkScript: output.pkScript,
amount: output.amount,
scriptVersion: output.scriptVersion,
compressed: output.compressed,
spent: output.spent,
}
}
return newEntry
}
// newUtxoEntry returns a new unspent transaction output entry with the provided
// coinbase flag and block height ready to have unspent outputs added.
func newUtxoEntry(txVersion uint16, height uint32, index uint32, isCoinBase bool, hasExpiry bool, txType stake.TxType) *UtxoEntry {
return &UtxoEntry{
sparseOutputs: make(map[uint32]*utxoOutput),
txVersion: txVersion,
height: height,
index: index,
isCoinBase: isCoinBase,
hasExpiry: hasExpiry,
txType: txType,
}
}
// 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[chainhash.Hash]*UtxoEntry
bestHash chainhash.Hash
}
// BestHash returns the hash of the best block in the chain the view currently
// respresents.
func (view *UtxoViewpoint) BestHash() *chainhash.Hash {
return &view.bestHash
}
// SetBestHash sets the hash of the best block in the chain the view currently
// respresents.
func (view *UtxoViewpoint) SetBestHash(hash *chainhash.Hash) {
view.bestHash = *hash
}
// 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 *chainhash.Hash) *UtxoEntry {
entry, ok := view.entries[*txHash]
if !ok {
return nil
}
return entry
}
// PrevScript returns the script and script version associated with the provided
// previous outpoint along with a bool that indicates whether or not the
// requested entry exists. This ensures the caller is able to distinguish
// between missing entry and empty v0 scripts.
func (view *UtxoViewpoint) PrevScript(prevOut *wire.OutPoint) (uint16, []byte, bool) {
entry := view.LookupEntry(&prevOut.Hash)
if entry == nil {
return 0, nil, false
}
version := entry.ScriptVersionByIndex(prevOut.Index)
pkScript := entry.PkScriptByIndex(prevOut.Index)
return version, pkScript, true
}
// 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 *dcrutil.Tx, blockHeight int64, blockIndex uint32) {
// 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 {
msgTx := tx.MsgTx()
txType := stake.DetermineTxType(msgTx)
entry = newUtxoEntry(msgTx.Version, uint32(blockHeight),
blockIndex, standalone.IsCoinBaseTx(msgTx), msgTx.Expiry != 0,
txType)
if txType == stake.TxTypeSStx {
stakeExtra := make([]byte, serializeSizeForMinimalOutputs(tx))
putTxToMinimalOutputs(stakeExtra, tx)
entry.stakeExtra = stakeExtra
}
view.entries[*tx.Hash()] = entry
} else {
entry.height = uint32(blockHeight)
entry.index = blockIndex
}
entry.modified = true
// Loop all of the transaction outputs and add those which are not
// provably unspendable.
for txOutIdx, txOut := range tx.MsgTx().TxOut {
// TODO allow pruning of stake utxs after all other outputs are spent
if txscript.IsUnspendable(txOut.Value, txOut.PkScript) {
continue
}
// 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[uint32(txOutIdx)]; ok {
output.spent = false
output.amount = txOut.Value
output.scriptVersion = txOut.Version
output.pkScript = txOut.PkScript
output.compressed = false
continue
}
// Add the unspent transaction output.
entry.sparseOutputs[uint32(txOutIdx)] = &utxoOutput{
spent: false,
amount: txOut.Value,
scriptVersion: txOut.Version,
pkScript: txOut.PkScript,
compressed: false,
}
}
}
// 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 *dcrutil.Tx, blockHeight int64, blockIndex uint32, stxos *[]spentTxOut) error {
// Coinbase transactions don't have any inputs to spend.
msgTx := tx.MsgTx()
if standalone.IsCoinBaseTx(msgTx) {
// Add the transaction's outputs as available utxos.
view.AddTxOuts(tx, blockHeight, blockIndex)
return nil
}
// 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.
isVote := stake.IsSSGen(msgTx)
for txInIdx, txIn := range msgTx.TxIn {
// Ignore stakebase since it has no input.
if isVote && txInIdx == 0 {
continue
}
// Ensure the referenced utxo exists in the view. This should
// never happen unless there is a bug is introduced in the code.
originIndex := txIn.PreviousOutPoint.Index
entry := view.entries[txIn.PreviousOutPoint.Hash]
if entry == nil {
return AssertError(fmt.Sprintf("view missing input %v",
txIn.PreviousOutPoint))
}
entry.SpendOutput(originIndex)
// Don't create the stxo details if not requested.
if stxos == nil {
continue
}
// Populate the stxo details using the utxo entry. When the
// transaction is fully spent, set the additional stxo fields
// accordingly since those details will no longer be available
// in the utxo set.
var stxo = spentTxOut{
compressed: false,
amount: txIn.ValueIn,
scriptVersion: entry.ScriptVersionByIndex(originIndex),
pkScript: entry.PkScriptByIndex(originIndex),
}
if entry.IsFullySpent() {
stxo.txVersion = entry.TxVersion()
stxo.height = uint32(entry.BlockHeight())
stxo.index = entry.BlockIndex()
stxo.isCoinBase = entry.IsCoinBase()
stxo.hasExpiry = entry.HasExpiry()
stxo.txType = entry.TransactionType()
stxo.txFullySpent = true
if entry.txType == stake.TxTypeSStx {
stxo.stakeExtra = entry.stakeExtra
}
}
// Append the entry to the provided spent txouts slice.
*stxos = append(*stxos, stxo)
}
// Add the transaction's outputs as available utxos.
view.AddTxOuts(tx, blockHeight, blockIndex)
return nil
}
// disconnectTransactions updates the view by removing all utxos created by
// the transactions in either the regular or stake tree of the block, depending
// on the flag, and unspending all of the txos spent by those same transactions
// by using the provided spent txo information.
func (view *UtxoViewpoint) disconnectTransactions(block *dcrutil.Block, stxos []spentTxOut, stakeTree bool) error {
// Choose which transaction tree to use and the appropriate offset into the
// spent transaction outputs that corresponds to them depending on the flag.
// Transactions in the stake tree are spent before transactions in the
// regular tree, thus skip all of the outputs spent by the regular tree when
// disconnecting stake transactions.
stxoIdx := len(stxos) - 1
transactions := block.Transactions()
if stakeTree {
stxoIdx = len(stxos) - countSpentRegularOutputs(block) - 1
transactions = block.STransactions()
}
for txIdx := len(transactions) - 1; txIdx > -1; txIdx-- {
tx := transactions[txIdx]
msgTx := tx.MsgTx()
txType := stake.TxTypeRegular
if stakeTree {
txType = stake.DetermineTxType(msgTx)
}
isVote := txType == stake.TxTypeSSGen
// Clear this transaction from the view if it already exists or create a
// new empty entry for when it does not. This is done because the code
// relies on its existence in the view in order to signal modifications
// have happened.
isCoinbase := !stakeTree && txIdx == 0
entry := view.entries[*tx.Hash()]
if entry == nil {
entry = newUtxoEntry(msgTx.Version, uint32(block.Height()),
uint32(txIdx), isCoinbase, msgTx.Expiry != 0, txType)
view.entries[*tx.Hash()] = entry
}
entry.modified = true
entry.sparseOutputs = make(map[uint32]*utxoOutput)
// Loop backwards through all of the transaction inputs (except for the
// coinbase which has no inputs) and unspend the referenced txos. This
// is necessary to match the order of the spent txout entries.
if isCoinbase {
continue
}
for txInIdx := len(msgTx.TxIn) - 1; txInIdx > -1; txInIdx-- {
// Ignore stakebase since it has no input.
if isVote && txInIdx == 0 {
continue
}
// Ensure the spent txout index is decremented to stay in sync with
// the transaction input.
stxo := &stxos[stxoIdx]
stxoIdx--
// When there is not already an entry for the referenced transaction
// in the view, it means it was fully spent, so create a new utxo
// entry in order to resurrect it.
txIn := msgTx.TxIn[txInIdx]
originHash := &txIn.PreviousOutPoint.Hash
originIndex := txIn.PreviousOutPoint.Index
entry := view.entries[*originHash]
if entry == nil {
if !stxo.txFullySpent {
return AssertError(fmt.Sprintf("tried to revive unspent "+
"tx %v from non-fully spent stx entry", originHash))
}
entry = newUtxoEntry(msgTx.Version, stxo.height, stxo.index,
stxo.isCoinBase, stxo.hasExpiry, stxo.txType)
if stxo.txType == stake.TxTypeSStx {
entry.stakeExtra = stxo.stakeExtra
}
view.entries[*originHash] = entry
}
// Mark the entry as modified since it is either new or will be
// changed below.
entry.modified = true
// Restore the specific utxo using the stxo data from the spend
// journal if it doesn't already exist in the view.
output, ok := entry.sparseOutputs[originIndex]
if !ok {
// Add the unspent transaction output.
entry.sparseOutputs[originIndex] = &utxoOutput{
compressed: stxo.compressed,
spent: false,
amount: txIn.ValueIn,
scriptVersion: stxo.scriptVersion,
pkScript: stxo.pkScript,
}
continue
}
// Mark the existing referenced transaction output as unspent.
output.spent = false
}
}
return nil
}
// disconnectRegularTransactions updates the view by removing all utxos created
// by the transactions in regular tree of the provided block and unspending all
// of the txos spent by those same transactions by using the provided spent txo
// information.
func (view *UtxoViewpoint) disconnectRegularTransactions(block *dcrutil.Block, stxos []spentTxOut) error {
return view.disconnectTransactions(block, stxos, false)
}
// disconnectStakeTransactions updates the view by removing all utxos created
// by the transactions in stake tree of the provided block and unspending all
// of the txos spent by those same transactions by using the provided spent txo
// information.
func (view *UtxoViewpoint) disconnectStakeTransactions(block *dcrutil.Block, stxos []spentTxOut) error {
return view.disconnectTransactions(block, stxos, true)
}
// disconnectDisapprovedBlock updates the view by disconnecting all of the
// transactions in the regular tree of the passed block.
//
// Disconnecting a transaction entails removing the utxos created by it and
// restoring the outputs spent by it with the help of the provided spent txo
// information.
//func (view *UtxoViewpoint) disconnectDisapprovedBlock(db database.DB, block *dcrutil.Block, stxos []spentTxOut) error {
func (view *UtxoViewpoint) disconnectDisapprovedBlock(db database.DB, block *dcrutil.Block) error {
// Load all of the spent txos for the block from the database spend journal.
var stxos []spentTxOut
err := db.View(func(dbTx database.Tx) error {
var err error
stxos, err = dbFetchSpendJournalEntry(dbTx, block)
return err
})
if err != nil {
return err
}
// Load all of the utxos referenced by the inputs for all transactions in
// the block that don't already exist in the utxo view from the database.
err = view.fetchRegularInputUtxos(db, block)
if err != nil {
return err
}
// Sanity check the correct number of stxos are provided.
if len(stxos) != countSpentOutputs(block) {
panicf("provided %v stxos for block %v (height %v) which spends %v "+
"outputs", len(stxos), block.Hash(), block.MsgBlock().Header.Height,
countSpentOutputs(block))
}
return view.disconnectRegularTransactions(block, stxos)
}
// connectBlock updates the view by potentially disconnecting all of the
// transactions in the regular tree of the parent block of the passed block in
// the case the passed block disapproves the parent block, connecting all of
// transactions in both the regular and stake trees of the passed block, and
// setting the best hash for the view to the passed block.
//
// Connecting a transaction entails marking all utxos it spends as spent, and
// adding all of the new utxos created by it.
//
// Disconnecting a transaction entails removing the utxos created by it and
// restoring the outputs spent by it with the help of the provided spent txo
// information.
//
// In addition, when the 'stxos' argument is not nil, it will be updated to
// append an entry for each spent txout.
func (view *UtxoViewpoint) connectBlock(db database.DB, block, parent *dcrutil.Block, stxos *[]spentTxOut) error {
// Disconnect the transactions in the regular tree of the parent block if
// the passed block disapproves it.
if !headerApprovesParent(&block.MsgBlock().Header) {
err := view.disconnectDisapprovedBlock(db, parent)
if err != nil {
return err
}
}
// Load all of the utxos referenced by the inputs for all transactions in
// the block that don't already exist in the utxo view from the database.
err := view.fetchInputUtxos(db, block)
if err != nil {
return err
}
// Connect all of the transactions in both the regular and stake trees of
// the block. Notice that the stake tree is connected before the regular
// tree. This means that stake transactions are not able to redeem outputs
// of transactions created in the regular tree of the same block, which is
// important since the regular tree may be disapproved by the subsequent
// block while the stake tree must remain valid.
for i, stx := range block.STransactions() {
err := view.connectTransaction(stx, block.Height(), uint32(i), stxos)
if err != nil {
return err
}
}
for i, tx := range block.Transactions() {
err := view.connectTransaction(tx, block.Height(), uint32(i), stxos)
if err != nil {
return err
}
}
// Update the best hash for view to include this block since all of its
// transactions have been connected.
view.SetBestHash(block.Hash())
return nil
}
// disconnectBlock updates the view by disconnecting all transactions in both
// the regular and stake trees of the passed block, in the case the block
// disapproves the parent block, reconnecting all of the transactions in the
// regular tree of the previous block, and setting the best hash for the view to
// the parent block.
//
// Connecting a transaction entails marking all utxos it spends as spent, and
// adding all of the new utxos created by it.
//
// Disconnecting a transaction entails removing the utxos created by it and
// restoring the outputs spent by it with the help of the provided spent txo
// information.
//
// Note that, unlike block connection, the spent transaction output (stxo)
// information is required and failure to provide it will result in an assertion
// panic.
func (view *UtxoViewpoint) disconnectBlock(db database.DB, block, parent *dcrutil.Block, stxos []spentTxOut) error {
// Sanity check the correct number of stxos are provided.
if len(stxos) != countSpentOutputs(block) {
panicf("provided %v stxos for block %v (height %v) which spends %v "+
"outputs", len(stxos), block.Hash(), block.MsgBlock().Header.Height,
countSpentOutputs(block))
}
// Load all of the utxos referenced by the inputs for all transactions in
// the block don't already exist in the utxo view from the database.
err := view.fetchInputUtxos(db, block)
if err != nil {
return err
}
// Disconnect all of the transactions in both the regular and stake trees of
// the block. Notice that the regular tree is disconnected before the stake
// tree since that is the reverse of how they are connected.
err = view.disconnectRegularTransactions(block, stxos)
if err != nil {
return err
}
err = view.disconnectStakeTransactions(block, stxos)
if err != nil {
return err
}
// Reconnect the transactions in the regular tree of the parent block if the
// block that is being disconnected disapproves it.
if !headerApprovesParent(&block.MsgBlock().Header) {
// Load all of the utxos referenced by the inputs for all transactions
// in the regular tree of the parent block that don't already exist in
// the utxo view from the database.
err := view.fetchRegularInputUtxos(db, parent)
if err != nil {
return err
}
for i, tx := range parent.Transactions() {
err := view.connectTransaction(tx, parent.Height(), uint32(i), nil)
if err != nil {
return err
}
}
}
// Update the best hash for view to include this block since all of its
// transactions have been connected.
view.SetBestHash(&block.MsgBlock().Header.PrevBlock)
return nil
}
// Entries returns the underlying map that stores of all the utxo entries.
func (view *UtxoViewpoint) Entries() map[chainhash.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
}
}
// viewFilteredSet represents a set of utxos to fetch from the database that are
// not already in a view.
type viewFilteredSet map[chainhash.Hash]struct{}
// add conditionally adds the provided utxo hash to the set if it does not
// already exist in the provided view.
func (set viewFilteredSet) add(view *UtxoViewpoint, hash *chainhash.Hash) {
if _, ok := view.entries[*hash]; !ok {
set[*hash] = struct{}{}
}
}
// fetchUtxosMain fetches unspent transaction output data about the provided
// set of transactions from the point of view of the end of the main chain at
// the time of the call.
//
// Upon completion of this function, the view will contain an entry for each
// requested transaction. Fully spent transactions, or those which otherwise
// don't exist, will result in a nil entry in the view.
func (view *UtxoViewpoint) fetchUtxosMain(db database.DB, filteredSet viewFilteredSet) error {
// Nothing to do if there are no requested hashes.
if len(filteredSet) == 0 {
return nil
}
// Load the unspent transaction output information for the requested set
// of transactions from the point of view of the end of the main chain.
//
// NOTE: Missing entries are not considered an error here and instead
// will result in nil entries in the view. This is intentionally done
// since other code uses the presence of an entry in the store as a way
// to optimize spend and unspend updates to apply only to the specific
// utxos that the caller needs access to.
return db.View(func(dbTx database.Tx) error {
for hash := range filteredSet {
hashCopy := hash
entry, err := dbFetchUtxoEntry(dbTx, &hashCopy)
if err != nil {
return err
}
view.entries[hash] = entry
}
return nil
})
}
// addRegularInputUtxos adds any outputs of transactions in the regular tree of
// the provided block that are referenced by inputs of transactions that are
// located later in the regular tree of the block and returns a set of the
// referenced outputs that are not already in the view and thus need to be
// fetched from the database.
func (view *UtxoViewpoint) addRegularInputUtxos(block *dcrutil.Block) viewFilteredSet {
// Build a map of in-flight transactions because some of the inputs in the
// regular transaction tree of this block could be referencing other
// transactions earlier in the block which are not yet in the chain.
txInFlight := map[chainhash.Hash]int{}
regularTxns := block.Transactions()
for i, tx := range regularTxns {
txInFlight[*tx.Hash()] = i
}
// Loop through all of the inputs of the transactions in the regular
// transaction tree (except for the coinbase which has no inputs) collecting
// them into sets of what is needed and what is already known (in-flight).
filteredSet := make(viewFilteredSet)
for i, tx := range regularTxns[1:] {
for _, txIn := range tx.MsgTx().TxIn {
// It is acceptable for a transaction input in the regular tree to
// reference the output of another transaction in the regular tree
// of this block only if the referenced transaction comes before the
// current one in this block. Add the outputs of the referenced
// transaction as available utxos when this is the case. Otherwise,
// the utxo details are still needed.
//
// NOTE: The >= is correct here because i is one less than the
// actual position of the transaction within the block due to
// skipping the coinbase.
originHash := &txIn.PreviousOutPoint.Hash
if inFlightIndex, ok := txInFlight[*originHash]; ok &&
i >= inFlightIndex {
originTx := regularTxns[inFlightIndex]
view.AddTxOuts(originTx, block.Height(), uint32(inFlightIndex))
continue
}
// Only request entries that are not already in the view from the
// database.
filteredSet.add(view, originHash)
}
}
return filteredSet
}
// fetchRegularInputUtxos loads utxo details about the input transactions
// referenced by the transactions in the regular tree of the given block into
// the view from the database as needed. In particular, referenced entries that
// are earlier in the block are added to the view and entries that are already
// in the view are not modified.
func (view *UtxoViewpoint) fetchRegularInputUtxos(db database.DB, block *dcrutil.Block) error {
// Add any outputs of transactions in the regular tree of the block that are
// referenced by inputs of transactions that are located later in the tree
// and fetch any inputs that are not already in the view from the database.
filteredSet := view.addRegularInputUtxos(block)
return view.fetchUtxosMain(db, filteredSet)
}
// fetchInputUtxos loads utxo details about the input transactions referenced
// by the transactions in both the regular and stake trees of the given block
// into the view from the database as needed. In the case of regular tree,
// referenced entries that are earlier in the regular tree of the block are
// added to the view. In all cases, entries that are already in the view are
// not modified.
func (view *UtxoViewpoint) fetchInputUtxos(db database.DB, block *dcrutil.Block) error {
// Add any outputs of transactions in the regular tree of the block that are
// referenced by inputs of transactions that are located later in the tree
// and, while doing so, determine which inputs are not already in the view
// and thus need to be fetched from the database.
filteredSet := view.addRegularInputUtxos(block)
// Loop through all of the inputs of the transaction in the stake tree and
// add those that aren't already known to the set of what is needed.
//
// Note that, unlike in the regular transaction tree, transactions in the
// stake tree are not allowed to access outputs of transactions earlier in
// the block. This applies to both transactions earlier in the stake tree
// as well as those in the regular tree.
for _, stx := range block.STransactions() {
isVote := stake.IsSSGen(stx.MsgTx())
for txInIdx, txIn := range stx.MsgTx().TxIn {
// Ignore stakebase since it has no input.
if isVote && txInIdx == 0 {
continue
}
// Only request entries that are not already in the view
// from the database.
originHash := &txIn.PreviousOutPoint.Hash
filteredSet.add(view, originHash)
}
}
// Request the input utxos from the database.
return view.fetchUtxosMain(db, filteredSet)
}
// clone returns a deep copy of the view.
func (view *UtxoViewpoint) clone() *UtxoViewpoint {
clonedView := &UtxoViewpoint{
entries: make(map[chainhash.Hash]*UtxoEntry),
bestHash: view.bestHash,
}
for txHash, entry := range view.entries {
clonedView.entries[txHash] = entry.Clone()
}
return clonedView
}
// NewUtxoViewpoint returns a new empty unspent transaction output view.
func NewUtxoViewpoint() *UtxoViewpoint {
return &UtxoViewpoint{
entries: make(map[chainhash.Hash]*UtxoEntry),
}
}
// FetchUtxoView loads utxo details about the input transactions referenced by
// the passed transaction from the point of view of the end of the main chain
// while taking into account whether or not the transactions in the regular tree
// of the block just prior should be included or not depending on the provided
// flag. It also attempts to fetch the utxo details for the transaction itself
// so the returned view can be examined for duplicate unspent transaction
// outputs.
//
// This function is safe for concurrent access however the returned view is NOT.
func (b *BlockChain) FetchUtxoView(tx *dcrutil.Tx, includePrevRegularTxns bool) (*UtxoViewpoint, error) {
b.chainLock.RLock()
defer b.chainLock.RUnlock()
// The genesis block does not have any spendable transactions, so there
// can't possibly be any details about it. This is also necessary
// because the code below requires the parent block and the genesis
// block doesn't have one.
tip := b.bestChain.Tip()
view := NewUtxoViewpoint()
view.SetBestHash(&tip.hash)
if tip.height == 0 {
return view, nil
}
// Disconnect the transactions in the regular tree of the parent block if
// the caller requests it. In order to avoid the overhead of repeated
// lookups, only create a view with the changes once and cache it.
if !includePrevRegularTxns {
b.disapprovedViewLock.Lock()
if b.disapprovedView == nil || *b.disapprovedView.BestHash() !=
tip.hash {
// Grab the parent of the current block.
parent, err := b.fetchMainChainBlockByNode(tip.parent)
if err != nil {
b.disapprovedViewLock.Unlock()
return nil, err
}
// Disconnect the transactions in the regular tree of the parent
// block.
err = view.disconnectDisapprovedBlock(b.db, parent)
if err != nil {
b.disapprovedViewLock.Unlock()
return nil, err
}
// Clone the view so the caller can safely mutate it.
b.disapprovedView = view.clone()
} else {
// Clone the view so the caller can safely mutate it.
view = b.disapprovedView.clone()
}
b.disapprovedViewLock.Unlock()
}
// Create a set of needed transactions based on those referenced by the
// inputs of the passed transaction. Also, add the passed transaction
// itself as a way for the caller to detect duplicates that are not fully
// spent.
filteredSet := make(viewFilteredSet)
filteredSet.add(view, tx.Hash())
msgTx := tx.MsgTx()
if !standalone.IsCoinBaseTx(msgTx) {
isVote := stake.IsSSGen(msgTx)
for txInIdx, txIn := range msgTx.TxIn {
// Ignore stakebase since it has no input.
if isVote && txInIdx == 0 {
continue
}
filteredSet.add(view, &txIn.PreviousOutPoint.Hash)
}
}
err := view.fetchUtxosMain(b.db, filteredSet)
return view, err
}
// FetchUtxoEntry loads and returns the unspent transaction output entry for the
// passed hash from the point of view of the end of the main chain.
//
// NOTE: Requesting a hash for which there is no data will NOT return an error.
// Instead both the entry and the error will be nil. This is done to allow
// pruning of fully spent transactions. In practice this means the caller must
// check if the returned entry is nil before invoking methods on it.
//
// This function is safe for concurrent access however the returned entry (if
// any) is NOT.
func (b *BlockChain) FetchUtxoEntry(txHash *chainhash.Hash) (*UtxoEntry, error) {
b.chainLock.RLock()
defer b.chainLock.RUnlock()
var entry *UtxoEntry
err := b.db.View(func(dbTx database.Tx) error {
var err error
entry, err = dbFetchUtxoEntry(dbTx, txHash)
return err
})
if err != nil {
return nil, err
}
return entry, nil