-
Notifications
You must be signed in to change notification settings - Fork 0
/
generator.go
2114 lines (1913 loc) · 79.1 KB
/
generator.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) 2016-2017 The Decred developers
// Copyright (c) 2018-2020 The Hcd developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
package chaingen
import (
"bytes"
"encoding/binary"
"fmt"
"math"
"math/big"
"runtime"
"sort"
"time"
"github.com/coolsnady/hcd/blockchain"
"github.com/coolsnady/hcd/chaincfg"
"github.com/coolsnady/hcd/chaincfg/chainhash"
"github.com/coolsnady/hcd/txscript"
"github.com/coolsnady/hcd/wire"
"github.com/coolsnady/hcutil"
)
var (
// hash256prngSeedConst is a constant derived from the hex
// representation of pi and is used in conjuction with a caller-provided
// seed when initializing the deterministic lottery prng.
hash256prngSeedConst = []byte{0x24, 0x3f, 0x6a, 0x88, 0x85, 0xa3, 0x08,
0xd3}
// opTrueScript is a simple public key script that contains the OP_TRUE
// opcode. It is defined here to reduce garbage creation.
opTrueScript = []byte{txscript.OP_TRUE}
// opTrueRedeemScript is the signature script that can be used to redeem
// a p2sh output to the opTrueScript. It is defined here to reduce
// garbage creation.
opTrueRedeemScript = []byte{txscript.OP_DATA_1, txscript.OP_TRUE}
// coinbaseSigScript is the signature script used by the tests when
// creating standard coinbase transactions. It is defined here to
// reduce garbage creation.
coinbaseSigScript = []byte{txscript.OP_0, txscript.OP_0}
)
const (
// voteBitYes is the specific bit that is set in the vote bits to
// indicate that the previous block is valid.
voteBitYes = 0x01
)
// SpendableOut represents a transaction output that is spendable along with
// additional metadata such as the block its in and how much it pays.
type SpendableOut struct {
prevOut wire.OutPoint
blockHeight uint32
blockIndex uint32
amount hcutil.Amount
}
// PrevOut returns the outpoint associated with the spendable output.
func (s *SpendableOut) PrevOut() wire.OutPoint {
return s.prevOut
}
// BlockHeight returns the block height of the block the spendable output is in.
func (s *SpendableOut) BlockHeight() uint32 {
return s.blockHeight
}
// BlockIndex returns the offset into the block the spendable output is in.
func (s *SpendableOut) BlockIndex() uint32 {
return s.blockIndex
}
// Amount returns the amount associated with the spendable output.
func (s *SpendableOut) Amount() hcutil.Amount {
return s.amount
}
// MakeSpendableOutForTx returns a spendable output for the given transaction
// block height, transaction index within the block, and transaction output
// index within the transaction.
func MakeSpendableOutForTx(tx *wire.MsgTx, blockHeight, txIndex, txOutIndex uint32) SpendableOut {
return SpendableOut{
prevOut: wire.OutPoint{
Hash: *tx.CachedTxHash(),
Index: txOutIndex,
Tree: wire.TxTreeRegular,
},
blockHeight: blockHeight,
blockIndex: txIndex,
amount: hcutil.Amount(tx.TxOut[txOutIndex].Value),
}
}
// MakeSpendableOut returns a spendable output for the given block, transaction
// index within the block, and transaction output index within the transaction.
func MakeSpendableOut(block *wire.MsgBlock, txIndex, txOutIndex uint32) SpendableOut {
tx := block.Transactions[txIndex]
return MakeSpendableOutForTx(tx, block.Header.Height, txIndex, txOutIndex)
}
// stakeTicket represents a transaction that is an sstx along with the height of
// the block it was mined in and the its index within that block.
type stakeTicket struct {
tx *wire.MsgTx
blockHeight uint32
blockIndex uint32
}
// stakeTicketSorter implements sort.Interface to allow a slice of stake tickets
// to be sorted.
type stakeTicketSorter []*stakeTicket
// Len returns the number of stake tickets in the slice. It is part of the
// sort.Interface implementation.
func (t stakeTicketSorter) Len() int { return len(t) }
// Swap swaps the stake tickets at the passed indices. It is part of the
// sort.Interface implementation.
func (t stakeTicketSorter) Swap(i, j int) { t[i], t[j] = t[j], t[i] }
// Less returns whether the stake ticket with index i should sort before the
// stake ticket with index j. It is part of the sort.Interface implementation.
func (t stakeTicketSorter) Less(i, j int) bool {
iHash := t[i].tx.CachedTxHash()[:]
jHash := t[j].tx.CachedTxHash()[:]
return bytes.Compare(iHash, jHash) < 0
}
// Generator houses state used to ease the process of generating test blocks
// that build from one another along with housing other useful things such as
// available spendable outputs and generic payment scripts used throughout the
// tests.
type Generator struct {
params *chaincfg.Params
tip *wire.MsgBlock
tipName string
blocks map[chainhash.Hash]*wire.MsgBlock
blocksByName map[string]*wire.MsgBlock
p2shOpTrueAddr hcutil.Address
p2shOpTrueScript []byte
// Used for tracking spendable coinbase outputs.
spendableOuts [][]SpendableOut
prevCollectedHash chainhash.Hash
// Used for tracking the live ticket pool.
originalParents map[chainhash.Hash]chainhash.Hash
immatureTickets []*stakeTicket
liveTickets []*stakeTicket
wonTickets map[chainhash.Hash][]*stakeTicket
expiredTickets []*stakeTicket
}
// MakeGenerator returns a generator instance initialized with the genesis block
// as the tip as well as a cached generic pay-to-script-hash script for OP_TRUE.
func MakeGenerator(params *chaincfg.Params) (Generator, error) {
// Generate a generic pay-to-script-hash script that is a simple
// OP_TRUE. This allows the tests to avoid needing to generate and
// track actual public keys and signatures.
p2shOpTrueAddr, err := hcutil.NewAddressScriptHash(opTrueScript, params)
if err != nil {
return Generator{}, err
}
p2shOpTrueScript, err := txscript.PayToAddrScript(p2shOpTrueAddr)
if err != nil {
return Generator{}, err
}
genesis := params.GenesisBlock
genesisHash := genesis.BlockHash()
return Generator{
params: params,
tip: genesis,
tipName: "genesis",
blocks: map[chainhash.Hash]*wire.MsgBlock{genesisHash: genesis},
blocksByName: map[string]*wire.MsgBlock{"genesis": genesis},
p2shOpTrueAddr: p2shOpTrueAddr,
p2shOpTrueScript: p2shOpTrueScript,
originalParents: make(map[chainhash.Hash]chainhash.Hash),
wonTickets: make(map[chainhash.Hash][]*stakeTicket),
}, nil
}
// Params returns the chain params associated with the generator instance.
func (g *Generator) Params() *chaincfg.Params {
return g.params
}
// Tip returns the current tip block of the generator instance.
func (g *Generator) Tip() *wire.MsgBlock {
return g.tip
}
// TipName returns the name of the current tip block of the generator instance.
func (g *Generator) TipName() string {
return g.tipName
}
// BlockByName returns the block associated with the provided block name. It
// will panic if the specified block name does not exist.
func (g *Generator) BlockByName(blockName string) *wire.MsgBlock {
block, ok := g.blocksByName[blockName]
if !ok {
panic(fmt.Sprintf("block name %s does not exist", blockName))
}
return block
}
// BlockByHash returns the block associated with the provided block hash. It
// will panic if the specified block hash does not exist.
func (g *Generator) BlockByHash(hash *chainhash.Hash) *wire.MsgBlock {
block, ok := g.blocks[*hash]
if !ok {
panic(fmt.Sprintf("block with hash %s does not exist", hash))
}
return block
}
// opReturnScript returns a provably-pruneable OP_RETURN script with the
// provided data.
func opReturnScript(data []byte) []byte {
builder := txscript.NewScriptBuilder()
script, err := builder.AddOp(txscript.OP_RETURN).AddData(data).Script()
if err != nil {
panic(err)
}
return script
}
// UniqueOpReturnScript returns a standard provably-pruneable OP_RETURN script
// with a random uint64 encoded as the data.
func UniqueOpReturnScript() []byte {
rand, err := wire.RandomUint64()
if err != nil {
panic(err)
}
data := make([]byte, 8, 8)
binary.LittleEndian.PutUint64(data[0:8], rand)
return opReturnScript(data)
}
// calcFullSubsidy returns the full block subsidy for the given block height.
//
// NOTE: This and the other subsidy calculation funcs intentionally are not
// using the blockchain code since the intent is to be able to generate known
// good tests which exercise that code, so it wouldn't make sense to use the
// same code to generate them.
//A(n) = (a1+(n-1)d)q^(n-1)
//S(n) = a1(1-q^n)/(1-q) + d[q(1-q^(n-1))/((1-q)^2) - (n-1)q^n/(1-q)]
//A(n) = A(n-1) *q + d*q^(n-1)
func (g *Generator) calcFullSubsidy(blockHeight uint32) hcutil.Amount {
iterations := int64(blockHeight) / g.params.SubsidyReductionInterval
subsidy := g.params.BaseSubsidy
var q float64 = float64(g.params.MulSubsidy)/float64(g.params.DivSubsidy)
var temp float64 = 0.0
if iterations < 1682 {
temp = float64(g.params.BaseSubsidy) * (1.0 - float64(iterations) * 59363.0 / 100000000.0) * math.Pow(q,float64(iterations))
}else{//after 99 years
temp = 100000000.0/float64(g.params.SubsidyReductionInterval) * math.Pow(0.1, float64(float64(iterations)-1681.0))
}
subsidy = int64(temp)
return hcutil.Amount(subsidy)
}
// calcPoWSubsidy returns the proof-of-work subsidy portion from a given full
// subsidy, block height, and number of votes that will be included in the
// block.
//
// NOTE: This and the other subsidy calculation funcs intentionally are not
// using the blockchain code since the intent is to be able to generate known
// good tests which exercise that code, so it wouldn't make sense to use the
// same code to generate them.
func (g *Generator) calcPoWSubsidy(fullSubsidy hcutil.Amount, blockHeight uint32, numVotes uint16) hcutil.Amount {
powProportion := hcutil.Amount(g.params.WorkRewardProportion)
totalProportions := hcutil.Amount(g.params.TotalSubsidyProportions())
powSubsidy := (fullSubsidy * powProportion) / totalProportions
if int64(blockHeight) < g.params.StakeValidationHeight {
return powSubsidy
}
// Reduce the subsidy according to the number of votes.
ticketsPerBlock := hcutil.Amount(g.params.TicketsPerBlock)
return (powSubsidy * hcutil.Amount(numVotes)) / ticketsPerBlock
}
// calcPoSSubsidy returns the proof-of-stake subsidy portion for a given block
// height being voted on.
//
// NOTE: This and the other subsidy calculation funcs intentionally are not
// using the blockchain code since the intent is to be able to generate known
// good tests which exercise that code, so it wouldn't make sense to use the
// same code to generate them.
func (g *Generator) calcPoSSubsidy(heightVotedOn uint32) hcutil.Amount {
if int64(heightVotedOn+1) < g.params.StakeValidationHeight {
return 0
}
fullSubsidy := g.calcFullSubsidy(heightVotedOn)
posProportion := hcutil.Amount(g.params.StakeRewardProportion)
totalProportions := hcutil.Amount(g.params.TotalSubsidyProportions())
return (fullSubsidy * posProportion) / totalProportions
}
// calcDevSubsidy returns the dev org subsidy portion from a given full subsidy.
//
// NOTE: This and the other subsidy calculation funcs intentionally are not
// using the blockchain code since the intent is to be able to generate known
// good tests which exercise that code, so it wouldn't make sense to use the
// same code to generate them.
func (g *Generator) calcDevSubsidy(fullSubsidy hcutil.Amount, blockHeight uint32, numVotes uint16) hcutil.Amount {
devProportion := hcutil.Amount(g.params.BlockTaxProportion)
totalProportions := hcutil.Amount(g.params.TotalSubsidyProportions())
devSubsidy := (fullSubsidy * devProportion) / totalProportions
if int64(blockHeight) < g.params.StakeValidationHeight {
return devSubsidy
}
// Reduce the subsidy according to the number of votes.
ticketsPerBlock := hcutil.Amount(g.params.TicketsPerBlock)
return (devSubsidy * hcutil.Amount(numVotes)) / ticketsPerBlock
}
// standardCoinbaseOpReturnScript returns a standard script suitable for use as
// the second output of a standard coinbase transaction of a new block. In
// particular, the serialized data used with the OP_RETURN starts with the block
// height and is followed by 32 bytes which are treated as 4 uint64 extra
// nonces. This implementation puts a cryptographically random value into the
// final extra nonce position. The actual format of the data after the block
// height is not defined however this effectivley mirrors the actual mining code
// at the time it was written.
func standardCoinbaseOpReturnScript(blockHeight uint32) []byte {
rand, err := wire.RandomUint64()
if err != nil {
panic(err)
}
data := make([]byte, 36, 36)
binary.LittleEndian.PutUint32(data[0:4], blockHeight)
binary.LittleEndian.PutUint64(data[28:36], rand)
return opReturnScript(data)
}
// addCoinbaseTxOutputs adds the following outputs to the provided transaction
// which is assumed to be a coinbase transaction:
// - First output pays the development subsidy portion to the dev org
// - Second output is a standard provably prunable data-only coinbase output
// - Third and subsequent outputs pay the pow subsidy portion to the generic
// OP_TRUE p2sh script hash
func (g *Generator) addCoinbaseTxOutputs(tx *wire.MsgTx, blockHeight uint32, devSubsidy, powSubsidy hcutil.Amount) {
// First output is the developer subsidy.
tx.AddTxOut(&wire.TxOut{
Value: int64(devSubsidy),
Version: g.params.OrganizationPkScriptVersion,
PkScript: g.params.OrganizationPkScript,
})
// Second output is a provably prunable data-only output that is used
// to ensure the coinbase is unique.
tx.AddTxOut(wire.NewTxOut(0, standardCoinbaseOpReturnScript(blockHeight)))
// Final outputs are the proof-of-work subsidy split into more than one
// output. These are in turn used througout the tests as inputs to
// other transactions such as ticket purchases and additional spend
// transactions.
const numPoWOutputs = 6
amount := powSubsidy / numPoWOutputs
for i := 0; i < numPoWOutputs; i++ {
if i == numPoWOutputs-1 {
amount = powSubsidy - amount*(numPoWOutputs-1)
}
tx.AddTxOut(wire.NewTxOut(int64(amount), g.p2shOpTrueScript))
}
}
// CreateCoinbaseTx returns a coinbase transaction paying an appropriate
// subsidy based on the passed block height and number of votes to the dev org
// and proof-of-work miner.
//
// See the addCoinbaseTxOutputs documentation for a breakdown of the outputs
// the transaction contains.
func (g *Generator) CreateCoinbaseTx(blockHeight uint32, numVotes uint16) *wire.MsgTx {
// Calculate the subsidy proportions based on the block height and the
// number of votes the block will include.
fullSubsidy := g.calcFullSubsidy(blockHeight)
devSubsidy := g.calcDevSubsidy(fullSubsidy, blockHeight, numVotes)
powSubsidy := g.calcPoWSubsidy(fullSubsidy, blockHeight, numVotes)
tx := wire.NewMsgTx()
tx.AddTxIn(&wire.TxIn{
// Coinbase transactions have no inputs, so previous outpoint is
// zero hash and max index.
PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{},
wire.MaxPrevOutIndex, wire.TxTreeRegular),
Sequence: wire.MaxTxInSequenceNum,
ValueIn: int64(devSubsidy + powSubsidy),
BlockHeight: wire.NullBlockHeight,
BlockIndex: wire.NullBlockIndex,
SignatureScript: coinbaseSigScript,
})
g.addCoinbaseTxOutputs(tx, blockHeight, devSubsidy, powSubsidy)
return tx
}
// purchaseCommitmentScript returns a standard provably-pruneable OP_RETURN
// commitment script suitable for use in a ticket purchase tx (sstx) using the
// provided target address, amount, and fee limits.
func purchaseCommitmentScript(addr hcutil.Address, amount, voteFeeLimit, revocationFeeLimit hcutil.Amount) []byte {
// The limits are defined in terms of the closest base 2 exponent and
// a bit that must be set to specify the limit is to be applied. The
// vote fee exponent is in the bottom 8 bits, while the revocation fee
// exponent is in the upper 8 bits.
limits := uint16(0)
if voteFeeLimit != 0 {
exp := uint16(math.Ceil(math.Log2(float64(voteFeeLimit))))
limits |= (exp | 0x40)
}
if revocationFeeLimit != 0 {
exp := uint16(math.Ceil(math.Log2(float64(revocationFeeLimit))))
limits |= ((exp | 0x40) << 8)
}
// The data consists of the 20-byte raw script address for the given
// address, 8 bytes for the amount to commit to (with the upper bit flag
// set to indicate a pay-to-script-hash address), and 2 bytes for the
// fee limits.
var data [30]byte
copy(data[:], addr.ScriptAddress())
binary.LittleEndian.PutUint64(data[20:], uint64(amount))
data[27] |= 1 << 7
binary.LittleEndian.PutUint16(data[28:], limits)
script, err := txscript.NewScriptBuilder().AddOp(txscript.OP_RETURN).
AddData(data[:]).Script()
if err != nil {
panic(err)
}
return script
}
// createTicketPurchaseTx creates a new transaction that spends the provided
// output to purchase a stake submission ticket (sstx) at the given ticket
// price. Both the ticket and the change will go to a p2sh script that is
// composed with a single OP_TRUE.
//
// The transaction consists of the following outputs:
// - First output is an OP_SSTX followed by the OP_TRUE p2sh script hash
// - Second output is an OP_RETURN followed by the commitment script
// - Third output is an OP_SSTXCHANGE followed by the OP_TRUE p2sh script hash
func (g *Generator) createTicketPurchaseTx(spend *SpendableOut, ticketPrice, fee hcutil.Amount) *wire.MsgTx {
// The first output is the voting rights address. This impl uses the
// standard pay-to-script-hash to an OP_TRUE.
pkScript, err := txscript.PayToSStx(g.p2shOpTrueAddr)
if err != nil {
panic(err)
}
// Generate the commitment script.
commitScript := purchaseCommitmentScript(g.p2shOpTrueAddr,
ticketPrice+fee, 0, ticketPrice)
// Calculate change and generate script to deliver it.
change := spend.amount - ticketPrice - fee
changeScript, err := txscript.PayToSStxChange(g.p2shOpTrueAddr)
if err != nil {
panic(err)
}
// Generate and return the transaction spending from the provided
// spendable output with the previously described outputs.
tx := wire.NewMsgTx()
tx.AddTxIn(&wire.TxIn{
PreviousOutPoint: spend.prevOut,
Sequence: wire.MaxTxInSequenceNum,
ValueIn: int64(spend.amount),
BlockHeight: spend.blockHeight,
BlockIndex: spend.blockIndex,
SignatureScript: opTrueRedeemScript,
})
tx.AddTxOut(wire.NewTxOut(int64(ticketPrice), pkScript))
tx.AddTxOut(wire.NewTxOut(0, commitScript))
tx.AddTxOut(wire.NewTxOut(int64(change), changeScript))
return tx
}
// isTicketPurchaseTx returns whether or not the passed transaction is a stake
// ticket purchase.
//
// NOTE: Like many other functions in this test code, this function
// intentionally does not use the blockchain/stake package code since the intent
// is to be able to generate known good tests which exercise that code, so it
// wouldn't make sense to use the same code to generate them. It must also be
// noted that this function is NOT robust. It is the minimum necessary needed
// by the testing framework.
func isTicketPurchaseTx(tx *wire.MsgTx) bool {
if len(tx.TxOut) == 0 {
return false
}
txOut := tx.TxOut[0]
scriptClass := txscript.GetScriptClass(txOut.Version, txOut.PkScript)
return scriptClass == txscript.StakeSubmissionTy
}
// isVoteTx returns whether or not the passed tx is a stake vote (ssgen).
//
// NOTE: Like many other functions in this test code, this function
// intentionally does not use the blockchain/stake package code since the intent
// is to be able to generate known good tests which exercise that code, so it
// wouldn't make sense to use the same code to generate them. It must also be
// noted that this function is NOT robust. It is the minimum necessary needed
// by the testing framework.
func isVoteTx(tx *wire.MsgTx) bool {
if len(tx.TxOut) < 3 {
return false
}
txOut := tx.TxOut[2]
scriptClass := txscript.GetScriptClass(txOut.Version, txOut.PkScript)
return scriptClass == txscript.StakeGenTy
}
// isRevocationTx returns whether or not the passed tx is a stake ticket
// revocation (ssrtx).
//
// NOTE: Like many other functions in this test code, this function
// intentionally does not use the blockchain/stake package code since the intent
// is to be able to generate known good tests which exercise that code, so it
// wouldn't make sense to use the same code to generate them. It must also be
// noted that this function is NOT robust. It is the minimum necessary needed
// by the testing framework.
func isRevocationTx(tx *wire.MsgTx) bool {
if len(tx.TxOut) == 0 {
return false
}
txOut := tx.TxOut[0]
scriptClass := txscript.GetScriptClass(txOut.Version, txOut.PkScript)
return scriptClass == txscript.StakeRevocationTy
}
// voteBlockScript returns a standard provably-pruneable OP_RETURN script
// suitable for use in a vote tx (ssgen) given the block to vote on.
func voteBlockScript(parentBlock *wire.MsgBlock) []byte {
var data [36]byte
parentHash := parentBlock.BlockHash()
copy(data[:], parentHash[:])
binary.LittleEndian.PutUint32(data[32:], parentBlock.Header.Height)
script, err := txscript.NewScriptBuilder().AddOp(txscript.OP_RETURN).
AddData(data[:]).Script()
if err != nil {
panic(err)
}
return script
}
// voteBitsScript returns a standard provably-pruneable OP_RETURN script
// suitable for use in a vote tx (ssgen) with the appropriate vote bits set
// depending on the provided params.
func voteBitsScript(bits uint16, voteVersion uint32) []byte {
data := make([]byte, 6)
binary.LittleEndian.PutUint16(data[:], bits)
binary.LittleEndian.PutUint32(data[2:], voteVersion)
if voteVersion == 0 {
data = data[:2]
}
script, err := txscript.NewScriptBuilder().AddOp(txscript.OP_RETURN).
AddData(data[:]).Script()
if err != nil {
panic(err)
}
return script
}
// createVoteTx returns a new transaction (ssgen) paying an appropriate subsidy
// for the given block height (and the number of votes per block) as well as the
// original commitments.
//
// The transaction consists of the following outputs:
// - First output is an OP_RETURN followed by the block hash and height
// - Second output is an OP_RETURN followed by the vote bits
// - Third and subsequent outputs are the payouts according to the ticket
// commitments and the appropriate proportion of the vote subsidy.
func (g *Generator) createVoteTx(parentBlock *wire.MsgBlock, ticket *stakeTicket) *wire.MsgTx {
// Calculate the proof-of-stake subsidy proportion based on the block
// height.
posSubsidy := g.calcPoSSubsidy(parentBlock.Header.Height)
voteSubsidy := posSubsidy / hcutil.Amount(g.params.TicketsPerBlock)
ticketPrice := hcutil.Amount(ticket.tx.TxOut[0].Value)
// The first output is the block (hash and height) the vote is for.
blockScript := voteBlockScript(parentBlock)
// The second output is the vote bits.
voteScript := voteBitsScript(voteBitYes, 0)
// The third and subsequent outputs pay the original commitment amounts
// along with the appropriate portion of the vote subsidy. This impl
// uses the standard pay-to-script-hash to an OP_TRUE.
stakeGenScript, err := txscript.PayToSSGen(g.p2shOpTrueAddr)
if err != nil {
panic(err)
}
// Generate and return the transaction with the proof-of-stake subsidy
// coinbase and spending from the provided ticket along with the
// previously described outputs.
ticketHash := ticket.tx.CachedTxHash()
tx := wire.NewMsgTx()
tx.AddTxIn(&wire.TxIn{
PreviousOutPoint: *wire.NewOutPoint(&chainhash.Hash{},
wire.MaxPrevOutIndex, wire.TxTreeRegular),
Sequence: wire.MaxTxInSequenceNum,
ValueIn: int64(voteSubsidy),
BlockHeight: wire.NullBlockHeight,
BlockIndex: wire.NullBlockIndex,
SignatureScript: g.params.StakeBaseSigScript,
})
tx.AddTxIn(&wire.TxIn{
PreviousOutPoint: *wire.NewOutPoint(ticketHash, 0,
wire.TxTreeStake),
Sequence: wire.MaxTxInSequenceNum,
ValueIn: int64(ticketPrice),
BlockHeight: ticket.blockHeight,
BlockIndex: ticket.blockIndex,
SignatureScript: opTrueRedeemScript,
})
tx.AddTxOut(wire.NewTxOut(0, blockScript))
tx.AddTxOut(wire.NewTxOut(0, voteScript))
tx.AddTxOut(wire.NewTxOut(int64(voteSubsidy+ticketPrice), stakeGenScript))
return tx
}
// ancestorBlock returns the ancestor block at the provided height by following
// the chain backwards from the given block. The returned block will be nil
// when a height is requested that is after the height of the passed block.
// Also, a callback can optionally be provided that is invoked with each block
// as it traverses.
func (g *Generator) ancestorBlock(block *wire.MsgBlock, height uint32, f func(*wire.MsgBlock)) *wire.MsgBlock {
// Nothing to do if the requested height is outside of the valid
// range.
if block == nil || height > block.Header.Height {
return nil
}
// Iterate backwards until the requested height is reached.
for block != nil && block.Header.Height > height {
block = g.blocks[block.Header.PrevBlock]
if f != nil && block != nil {
f(block)
}
}
return block
}
// mergeDifficulty takes an original stake difficulty and two new, scaled
// stake difficulties, merges the new difficulties, and outputs a new
// merged stake difficulty.
func mergeDifficulty(oldDiff int64, newDiff1 int64, newDiff2 int64) int64 {
newDiff1Big := big.NewInt(newDiff1)
newDiff2Big := big.NewInt(newDiff2)
newDiff2Big.Lsh(newDiff2Big, 32)
oldDiffBig := big.NewInt(oldDiff)
oldDiffBigLSH := big.NewInt(oldDiff)
oldDiffBigLSH.Lsh(oldDiffBig, 32)
newDiff1Big.Div(oldDiffBigLSH, newDiff1Big)
newDiff2Big.Div(newDiff2Big, oldDiffBig)
// Combine the two changes in difficulty.
summedChange := big.NewInt(0)
summedChange.Set(newDiff2Big)
summedChange.Lsh(summedChange, 32)
summedChange.Div(summedChange, newDiff1Big)
summedChange.Mul(summedChange, oldDiffBig)
summedChange.Rsh(summedChange, 32)
return summedChange.Int64()
}
// limitRetarget clamps the passed new difficulty to the old one adjusted by the
// factor specified in the chain parameters. This ensures the difficulty can
// only move up or down by a limited amount.
func (g *Generator) limitRetarget(oldDiff, newDiff int64) int64 {
maxRetarget := g.params.RetargetAdjustmentFactor
switch {
case newDiff == 0:
fallthrough
case (oldDiff / newDiff) > (maxRetarget - 1):
return oldDiff / maxRetarget
case (newDiff / oldDiff) > (maxRetarget - 1):
return oldDiff * maxRetarget
}
return newDiff
}
// calcNextRequiredDifficulty returns the required proof-of-work difficulty for
// the block after the current tip block the generator is associated with.
//
// An overview of the algorithm is as follows:
// 1) Use the proof-of-work limit for all blocks before the first retarget
// window
// 2) Use the previous block's difficulty if the next block is not at a retarget
// interval
// 3) Calculate the ideal retarget difficulty for each window based on the
// actual timespan of the window versus the target timespan and exponentially
// weight each difficulty such that the most recent window has the highest
// weight
// 4) Calculate the final retarget difficulty based on the exponential weighted
// average and ensure it is limited to the max retarget adjustment factor
func (g *Generator) calcNextRequiredDifficulty() uint32 {
// Target difficulty before the first retarget interval is the pow
// limit.
nextHeight := g.tip.Header.Height + 1
windowSize := g.params.WorkDiffWindowSize
if int64(nextHeight) < windowSize {
return g.params.PowLimitBits
}
// Return the previous block's difficulty requirements if the next block
// is not at a difficulty retarget interval.
curDiff := int64(g.tip.Header.Bits)
if int64(nextHeight)%windowSize != 0 {
return uint32(curDiff)
}
// Calculate the ideal retarget difficulty for each window based on the
// actual time between blocks versus the target time and exponentially
// weight them.
adjustedTimespan := big.NewInt(0)
tempBig := big.NewInt(0)
weightedTimespanSum, weightSum := big.NewInt(0), big.NewInt(0)
targetTimespan := int64(g.params.TargetTimespan)
targetTimespanBig := big.NewInt(targetTimespan)
numWindows := g.params.WorkDiffWindows
weightAlpha := g.params.WorkDiffAlpha
block := g.tip
finalWindowTime := block.Header.Timestamp.UnixNano()
for i := int64(0); i < numWindows; i++ {
// Get the timestamp of the block at the start of the window and
// calculate the actual timespan accordingly. Use the target
// timespan if there are not yet enough blocks left to cover the
// window.
actualTimespan := targetTimespan
if int64(block.Header.Height) > windowSize {
for j := int64(0); j < windowSize; j++ {
block = g.blocks[block.Header.PrevBlock]
}
startWindowTime := block.Header.Timestamp.UnixNano()
actualTimespan = finalWindowTime - startWindowTime
// Set final window time for the next window.
finalWindowTime = startWindowTime
}
// Calculate the ideal retarget difficulty for the window based
// on the actual timespan and weight it exponentially by
// multiplying it by 2^(window_number) such that the most recent
// window receives the most weight.
//
// Also, since integer division is being used, shift up the
// number of new tickets 32 bits to avoid losing precision.
//
// windowWeightShift = ((numWindows - i) * weightAlpha)
// adjustedTimespan = (actualTimespan << 32) / targetTimespan
// weightedTimespanSum += adjustedTimespan << windowWeightShift
// weightSum += 1 << windowWeightShift
windowWeightShift := uint((numWindows - i) * weightAlpha)
adjustedTimespan.SetInt64(actualTimespan)
adjustedTimespan.Lsh(adjustedTimespan, 32)
adjustedTimespan.Div(adjustedTimespan, targetTimespanBig)
adjustedTimespan.Lsh(adjustedTimespan, windowWeightShift)
weightedTimespanSum.Add(weightedTimespanSum, adjustedTimespan)
weight := tempBig.SetInt64(1)
weight.Lsh(weight, windowWeightShift)
weightSum.Add(weightSum, weight)
}
// Calculate the retarget difficulty based on the exponential weigthed
// average and shift the result back down 32 bits to account for the
// previous shift up in order to avoid losing precision. Then, limit it
// to the maximum allowed retarget adjustment factor.
//
// nextDiff = (weightedTimespanSum/weightSum * curDiff) >> 32
curDiffBig := tempBig.SetInt64(curDiff)
weightedTimespanSum.Div(weightedTimespanSum, weightSum)
weightedTimespanSum.Mul(weightedTimespanSum, curDiffBig)
weightedTimespanSum.Rsh(weightedTimespanSum, 32)
nextDiff := weightedTimespanSum.Int64()
nextDiff = g.limitRetarget(curDiff, nextDiff)
if nextDiff > int64(g.params.PowLimitBits) {
return g.params.PowLimitBits
}
return uint32(nextDiff)
}
// calcNextRequiredStakeDifficulty returns the required stake difficulty (aka
// ticket price) for the block after the current tip block the generator is
// associated with.
//
// An overview of the algorithm is as follows:
// 1) Use the minimum value for any blocks before any tickets could have
// possibly been purchased due to coinbase maturity requirements
// 2) Return 0 if the current tip block stake difficulty is 0. This is a
// safety check against a condition that should never actually happen.
// 3) Use the previous block's difficulty if the next block is not at a retarget
// interval
// 4) Calculate the ideal retarget difficulty for each window based on the
// actual pool size in the window versus the target pool size skewed by a
// constant factor to weight the ticket pool size instead of the tickets per
// block and exponentially weight each difficulty such that the most recent
// window has the highest weight
// 5) Calculate the pool size retarget difficulty based on the exponential
// weighted average and ensure it is limited to the max retarget adjustment
// factor -- This is the first metric used to calculate the final difficulty
// 6) Calculate the ideal retarget difficulty for each window based on the
// actual new tickets in the window versus the target new tickets per window
// and exponentially weight each difficulty such that the most recent window
// has the highest weight
// 7) Calculate the tickets per window retarget difficulty based on the
// exponential weighted average and ensure it is limited to the max retarget
// adjustment factor
// 8) Calculate the final difficulty by averaging the pool size retarget
// difficulty from #5 and the tickets per window retarget difficulty from #7
// using scaled multiplication and ensure it is limited to the max retarget
// adjustment factor
//
// NOTE: In order to simplify the test code, this implementation does not use
// big integers so it will NOT match the actual consensus code for really big
// numbers. However, the parameters on simnet and the pool sizes used in these
// tests are low enough that this is not an issue for the tests. Anyone looking
// at this code should NOT use it for mainnet calculations as is since it will
// not always yield the correct results.
func (g *Generator) calcNextRequiredStakeDifficulty() int64 {
// Stake difficulty before any tickets could possibly be purchased is
// the minimum value.
nextHeight := g.tip.Header.Height + 1
stakeDiffStartHeight := uint32(g.params.CoinbaseMaturity) + 1
if nextHeight < stakeDiffStartHeight {
return g.params.MinimumStakeDiff
}
// Return 0 if the current difficulty is already zero since any scaling
// of 0 is still 0. This should never really happen since there is a
// minimum stake difficulty, but the consensus code checks the condition
// just in case, so follow suit here.
curDiff := g.tip.Header.SBits
if curDiff == 0 {
return 0
}
// Return the previous block's difficulty requirements if the next block
// is not at a difficulty retarget interval.
windowSize := g.params.StakeDiffWindowSize
if int64(nextHeight)%windowSize != 0 {
return curDiff
}
// --------------------------------
// Ideal pool size retarget metric.
// --------------------------------
// Calculate the ideal retarget difficulty for each window based on the
// actual pool size in the window versus the target pool size and
// exponentially weight them.
var weightedPoolSizeSum, weightSum uint64
ticketsPerBlock := int64(g.params.TicketsPerBlock)
targetPoolSize := ticketsPerBlock * int64(g.params.TicketPoolSize)
numWindows := g.params.StakeDiffWindows
weightAlpha := g.params.StakeDiffAlpha
block := g.tip
for i := int64(0); i < numWindows; i++ {
// Get the pool size for the block at the start of the window.
// Use zero if there are not yet enough blocks left to cover the
// window.
prevRetargetHeight := nextHeight - uint32(windowSize*(i+1))
windowPoolSize := int64(0)
block = g.ancestorBlock(block, prevRetargetHeight, nil)
if block != nil {
windowPoolSize = int64(block.Header.PoolSize)
}
// Skew the pool size by the constant weight factor specified in
// the chain parameters (which is typically the max adjustment
// factor) in order to help weight the ticket pool size versus
// tickets per block. Also, ensure the skewed pool size is a
// minimum of 1.
skewedPoolSize := targetPoolSize + (windowPoolSize-
targetPoolSize)*int64(g.params.TicketPoolSizeWeight)
if skewedPoolSize <= 0 {
skewedPoolSize = 1
}
// Calculate the ideal retarget difficulty for the window based
// on the skewed pool size and weight it exponentially by
// multiplying it by 2^(window_number) such that the most recent
// window receives the most weight.
//
// Also, since integer division is being used, shift up the
// number of new tickets 32 bits to avoid losing precision.
//
// NOTE: The real algorithm uses big ints, but these purpose
// built tests won't be using large enough values to overflow,
// so just use uint64s.
adjusted := (skewedPoolSize << 32) / targetPoolSize
adjusted = adjusted << uint64((numWindows-i)*weightAlpha)
weightedPoolSizeSum += uint64(adjusted)
weightSum += 1 << uint64((numWindows-i)*weightAlpha)
}
// Calculate the pool size retarget difficulty based on the exponential
// weigthed average and shift the result back down 32 bits to account
// for the previous shift up in order to avoid losing precision. Then,
// limit it to the maximum allowed retarget adjustment factor.
//
// This is the first metric used in the final calculated difficulty.
nextPoolSizeDiff := (int64(weightedPoolSizeSum/weightSum) * curDiff) >> 32
nextPoolSizeDiff = g.limitRetarget(curDiff, nextPoolSizeDiff)
// -----------------------------------------
// Ideal tickets per window retarget metric.
// -----------------------------------------
// Calculate the ideal retarget difficulty for each window based on the
// actual number of new tickets in the window versus the target tickets
// per window and exponentially weight them.
var weightedTicketsSum uint64
targetTicketsPerWindow := ticketsPerBlock * windowSize
block = g.tip
for i := int64(0); i < numWindows; i++ {
// Since the difficulty for the next block after the current tip
// is being calculated and there is no such block yet, the sum
// of all new tickets in the first window needs to start with
// the number of new tickets in the tip block.
var windowNewTickets int64
if i == 0 {
windowNewTickets = int64(block.Header.FreshStake)
}
// Tally all of the new tickets in all blocks in the window and
// ensure the number of new tickets is a minimum of 1.
prevRetargetHeight := nextHeight - uint32(windowSize*(i+1))
block = g.ancestorBlock(block, prevRetargetHeight, func(blk *wire.MsgBlock) {
windowNewTickets += int64(blk.Header.FreshStake)
})
if windowNewTickets <= 0 {
windowNewTickets = 1
}
// Calculate the ideal retarget difficulty for the window based
// on the number of new tickets and weight it exponentially by
// multiplying it by 2^(window_number) such that the most recent
// window receives the most weight.
//
// Also, since integer division is being used, shift up the
// number of new tickets 32 bits to avoid losing precision.
//
// NOTE: The real algorithm uses big ints, but these purpose
// built tests won't be using large enough values to overflow,
// so just use uint64s.
adjusted := (windowNewTickets << 32) / targetTicketsPerWindow
adjusted = adjusted << uint64((numWindows-i)*weightAlpha)
weightedTicketsSum += uint64(adjusted)
}
// Calculate the tickets per window retarget difficulty based on the
// exponential weighted average and shift the result back down 32 bits
// to account for the previous shift up in order to avoid losing
// precision. Then, limit it to the maximum allowed retarget adjustment
// factor.
//
// This is the second metric used in the final calculated difficulty.
nextNewTixDiff := (int64(weightedTicketsSum/weightSum) * curDiff) >> 32
nextNewTixDiff = g.limitRetarget(curDiff, nextNewTixDiff)
// Average the previous two metrics using scaled multiplication and
// ensure the result is limited to both the maximum allowed retarget
// adjustment factor and the minimum allowed stake difficulty.
nextDiff := mergeDifficulty(curDiff, nextPoolSizeDiff, nextNewTixDiff)
nextDiff = g.limitRetarget(curDiff, nextDiff)
if nextDiff < g.params.MinimumStakeDiff {
return g.params.MinimumStakeDiff
}
return nextDiff
}
// hash256prng is a determinstic pseudorandom number generator that uses a
// 256-bit secure hashing function to generate random uint32s starting from
// an initial seed.
type hash256prng struct {
seed chainhash.Hash // Initialization seed