forked from btcsuite/btcd
-
Notifications
You must be signed in to change notification settings - Fork 3
/
csv_fork_test.go
695 lines (612 loc) · 21.9 KB
/
csv_fork_test.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
// Copyright (c) 2016 The btcsuite developers
// Use of this source code is governed by an ISC
// license that can be found in the LICENSE file.
// This file is ignored during the regular tests due to the following build tag.
// +build rpctest
package integration
import (
"bytes"
"runtime"
"strings"
"testing"
"time"
"github.com/btcsuite/btcd/blockchain"
"github.com/btcsuite/btcd/btcec"
"github.com/btcsuite/btcd/chaincfg"
"github.com/btcsuite/btcd/chaincfg/chainhash"
"github.com/btcsuite/btcd/integration/rpctest"
"github.com/btcsuite/btcd/txscript"
"github.com/btcsuite/btcd/wire"
"github.com/btcsuite/btcutil"
)
const (
csvKey = "csv"
)
// makeTestOutput creates an on-chain output paying to a freshly generated
// p2pkh output with the specified amount.
func makeTestOutput(r *rpctest.Harness, t *testing.T,
amt btcutil.Amount) (*btcec.PrivateKey, *wire.OutPoint, []byte, error) {
// Create a fresh key, then send some coins to an address spendable by
// that key.
key, err := btcec.NewPrivateKey(btcec.S256())
if err != nil {
return nil, nil, nil, err
}
// Using the key created above, generate a pkScript which it's able to
// spend.
a, err := btcutil.NewAddressPubKey(key.PubKey().SerializeCompressed(), r.ActiveNet)
if err != nil {
return nil, nil, nil, err
}
selfAddrScript, err := txscript.PayToAddrScript(a.AddressPubKeyHash())
if err != nil {
return nil, nil, nil, err
}
output := &wire.TxOut{PkScript: selfAddrScript, Value: 1e8}
// Next, create and broadcast a transaction paying to the output.
fundTx, err := r.CreateTransaction([]*wire.TxOut{output}, 10, true)
if err != nil {
return nil, nil, nil, err
}
txHash, err := r.Node.SendRawTransaction(fundTx, true)
if err != nil {
return nil, nil, nil, err
}
// The transaction created above should be included within the next
// generated block.
blockHash, err := r.Node.Generate(1)
if err != nil {
return nil, nil, nil, err
}
assertTxInBlock(r, t, blockHash[0], txHash)
// Locate the output index of the coins spendable by the key we
// generated above, this is needed in order to create a proper utxo for
// this output.
var outputIndex uint32
if bytes.Equal(fundTx.TxOut[0].PkScript, selfAddrScript) {
outputIndex = 0
} else {
outputIndex = 1
}
utxo := &wire.OutPoint{
Hash: fundTx.TxHash(),
Index: outputIndex,
}
return key, utxo, selfAddrScript, nil
}
// TestBIP0113Activation tests for proper adherence of the BIP 113 rule
// constraint which requires all transaction finality tests to use the MTP of
// the last 11 blocks, rather than the timestamp of the block which includes
// them.
//
// Overview:
// - Pre soft-fork:
// - Transactions with non-final lock-times from the PoV of MTP should be
// rejected from the mempool.
// - Transactions within non-final MTP based lock-times should be accepted
// in valid blocks.
//
// - Post soft-fork:
// - Transactions with non-final lock-times from the PoV of MTP should be
// rejected from the mempool and when found within otherwise valid blocks.
// - Transactions with final lock-times from the PoV of MTP should be
// accepted to the mempool and mined in future block.
func TestBIP0113Activation(t *testing.T) {
t.Parallel()
btcdCfg := []string{"--rejectnonstd"}
r, err := rpctest.New(&chaincfg.SimNetParams, nil, btcdCfg)
if err != nil {
t.Fatal("unable to create primary harness: ", err)
}
if err := r.SetUp(true, 1); err != nil {
t.Fatalf("unable to setup test chain: %v", err)
}
defer r.TearDown()
// Create a fresh output for usage within the test below.
const outputValue = btcutil.SatoshiPerBitcoin
outputKey, testOutput, testPkScript, err := makeTestOutput(r, t,
outputValue)
if err != nil {
t.Fatalf("unable to create test output: %v", err)
}
// Fetch a fresh address from the harness, we'll use this address to
// send funds back into the Harness.
addr, err := r.NewAddress()
if err != nil {
t.Fatalf("unable to generate address: %v", err)
}
addrScript, err := txscript.PayToAddrScript(addr)
if err != nil {
t.Fatalf("unable to generate addr script: %v", err)
}
// Now create a transaction with a lock time which is "final" according
// to the latest block, but not according to the current median time
// past.
tx := wire.NewMsgTx(1)
tx.AddTxIn(&wire.TxIn{
PreviousOutPoint: *testOutput,
})
tx.AddTxOut(&wire.TxOut{
PkScript: addrScript,
Value: outputValue - 1000,
})
// We set the lock-time of the transaction to just one minute after the
// current MTP of the chain.
chainInfo, err := r.Node.GetBlockChainInfo()
if err != nil {
t.Fatalf("unable to query for chain info: %v", err)
}
tx.LockTime = uint32(chainInfo.MedianTime) + 1
sigScript, err := txscript.SignatureScript(tx, 0, testPkScript,
txscript.SigHashAll, outputKey, true)
if err != nil {
t.Fatalf("unable to generate sig: %v", err)
}
tx.TxIn[0].SignatureScript = sigScript
// This transaction should be rejected from the mempool as using MTP
// for transactions finality is now a policy rule. Additionally, the
// exact error should be the rejection of a non-final transaction.
_, err = r.Node.SendRawTransaction(tx, true)
if err == nil {
t.Fatalf("transaction accepted, but should be non-final")
} else if !strings.Contains(err.Error(), "not finalized") {
t.Fatalf("transaction should be rejected due to being "+
"non-final, instead: %v", err)
}
// However, since the block validation consensus rules haven't yet
// activated, a block including the transaction should be accepted.
txns := []*btcutil.Tx{btcutil.NewTx(tx)}
block, err := r.GenerateAndSubmitBlock(txns, -1, time.Time{})
if err != nil {
t.Fatalf("unable to submit block: %v", err)
}
txid := tx.TxHash()
assertTxInBlock(r, t, block.Hash(), &txid)
// At this point, the block height should be 103: we mined 101 blocks
// to create a single mature output, then an additional block to create
// a new output, and then mined a single block above to include our
// transaction.
assertChainHeight(r, t, 103)
// Next, mine enough blocks to ensure that the soft-fork becomes
// activated. Assert that the block version of the second-to-last block
// in the final range is active.
// Next, mine ensure blocks to ensure that the soft-fork becomes
// active. We're at height 103 and we need 200 blocks to be mined after
// the genesis target period, so we mine 196 blocks. This'll put us at
// height 299. The getblockchaininfo call checks the state for the
// block AFTER the current height.
numBlocks := (r.ActiveNet.MinerConfirmationWindow * 2) - 4
if _, err := r.Node.Generate(numBlocks); err != nil {
t.Fatalf("unable to generate blocks: %v", err)
}
assertChainHeight(r, t, 299)
assertSoftForkStatus(r, t, csvKey, blockchain.ThresholdActive)
// The timeLockDeltas slice represents a series of deviations from the
// current MTP which will be used to test border conditions w.r.t
// transaction finality. -1 indicates 1 second prior to the MTP, 0
// indicates the current MTP, and 1 indicates 1 second after the
// current MTP.
//
// This time, all transactions which are final according to the MTP
// *should* be accepted to both the mempool and within a valid block.
// While transactions with lock-times *after* the current MTP should be
// rejected.
timeLockDeltas := []int64{-1, 0, 1}
for _, timeLockDelta := range timeLockDeltas {
chainInfo, err = r.Node.GetBlockChainInfo()
if err != nil {
t.Fatalf("unable to query for chain info: %v", err)
}
medianTimePast := chainInfo.MedianTime
// Create another test output to be spent shortly below.
outputKey, testOutput, testPkScript, err = makeTestOutput(r, t,
outputValue)
if err != nil {
t.Fatalf("unable to create test output: %v", err)
}
// Create a new transaction with a lock-time past the current known
// MTP.
tx = wire.NewMsgTx(1)
tx.AddTxIn(&wire.TxIn{
PreviousOutPoint: *testOutput,
})
tx.AddTxOut(&wire.TxOut{
PkScript: addrScript,
Value: outputValue - 1000,
})
tx.LockTime = uint32(medianTimePast + timeLockDelta)
sigScript, err = txscript.SignatureScript(tx, 0, testPkScript,
txscript.SigHashAll, outputKey, true)
if err != nil {
t.Fatalf("unable to generate sig: %v", err)
}
tx.TxIn[0].SignatureScript = sigScript
// If the time-lock delta is greater than -1, then the
// transaction should be rejected from the mempool and when
// included within a block. A time-lock delta of -1 should be
// accepted as it has a lock-time of one
// second _before_ the current MTP.
_, err = r.Node.SendRawTransaction(tx, true)
if err == nil && timeLockDelta >= 0 {
t.Fatal("transaction was accepted into the mempool " +
"but should be rejected!")
} else if err != nil && !strings.Contains(err.Error(), "not finalized") {
t.Fatalf("transaction should be rejected from mempool "+
"due to being non-final, instead: %v", err)
}
txns = []*btcutil.Tx{btcutil.NewTx(tx)}
_, err := r.GenerateAndSubmitBlock(txns, -1, time.Time{})
if err == nil && timeLockDelta >= 0 {
t.Fatal("block should be rejected due to non-final " +
"txn, but was accepted")
} else if err != nil && !strings.Contains(err.Error(), "unfinalized") {
t.Fatalf("block should be rejected due to non-final "+
"tx, instead: %v", err)
}
}
}
// createCSVOutput creates an output paying to a trivially redeemable CSV
// pkScript with the specified time-lock.
func createCSVOutput(r *rpctest.Harness, t *testing.T,
numSatoshis btcutil.Amount, timeLock int32,
isSeconds bool) ([]byte, *wire.OutPoint, *wire.MsgTx, error) {
// Convert the time-lock to the proper sequence lock based according to
// if the lock is seconds or time based.
sequenceLock := blockchain.LockTimeToSequence(isSeconds,
uint32(timeLock))
// Our CSV script is simply: <sequenceLock> OP_CSV OP_DROP
b := txscript.NewScriptBuilder().
AddInt64(int64(sequenceLock)).
AddOp(txscript.OP_CHECKSEQUENCEVERIFY).
AddOp(txscript.OP_DROP)
csvScript, err := b.Script()
if err != nil {
return nil, nil, nil, err
}
// Using the script generated above, create a P2SH output which will be
// accepted into the mempool.
p2shAddr, err := btcutil.NewAddressScriptHash(csvScript, r.ActiveNet)
if err != nil {
return nil, nil, nil, err
}
p2shScript, err := txscript.PayToAddrScript(p2shAddr)
if err != nil {
return nil, nil, nil, err
}
output := &wire.TxOut{
PkScript: p2shScript,
Value: int64(numSatoshis),
}
// Finally create a valid transaction which creates the output crafted
// above.
tx, err := r.CreateTransaction([]*wire.TxOut{output}, 10, true)
if err != nil {
return nil, nil, nil, err
}
var outputIndex uint32
if !bytes.Equal(tx.TxOut[0].PkScript, p2shScript) {
outputIndex = 1
}
utxo := &wire.OutPoint{
Hash: tx.TxHash(),
Index: outputIndex,
}
return csvScript, utxo, tx, nil
}
// spendCSVOutput spends an output previously created by the createCSVOutput
// function. The sigScript is a trivial push of OP_TRUE followed by the
// redeemScript to pass P2SH evaluation.
func spendCSVOutput(redeemScript []byte, csvUTXO *wire.OutPoint,
sequence uint32, targetOutput *wire.TxOut,
txVersion int32) (*wire.MsgTx, error) {
tx := wire.NewMsgTx(txVersion)
tx.AddTxIn(&wire.TxIn{
PreviousOutPoint: *csvUTXO,
Sequence: sequence,
})
tx.AddTxOut(targetOutput)
b := txscript.NewScriptBuilder().
AddOp(txscript.OP_TRUE).
AddData(redeemScript)
sigScript, err := b.Script()
if err != nil {
return nil, err
}
tx.TxIn[0].SignatureScript = sigScript
return tx, nil
}
// assertTxInBlock asserts a transaction with the specified txid is found
// within the block with the passed block hash.
func assertTxInBlock(r *rpctest.Harness, t *testing.T, blockHash *chainhash.Hash,
txid *chainhash.Hash) {
block, err := r.Node.GetBlock(blockHash)
if err != nil {
t.Fatalf("unable to get block: %v", err)
}
if len(block.Transactions) < 2 {
t.Fatal("target transaction was not mined")
}
for _, txn := range block.Transactions {
txHash := txn.TxHash()
if txn.TxHash() == txHash {
return
}
}
_, _, line, _ := runtime.Caller(1)
t.Fatalf("assertion failed at line %v: txid %v was not found in "+
"block %v", line, txid, blockHash)
}
// TestBIP0068AndBIP0112Activation tests for the proper adherence to the BIP
// 112 and BIP 68 rule-set after the activation of the CSV-package soft-fork.
//
// Overview:
// - Pre soft-fork:
// - A transaction spending a CSV output validly should be rejected from the
// mempool, but accepted in a valid generated block including the
// transaction.
// - Post soft-fork:
// - See the cases exercised within the table driven tests towards the end
// of this test.
func TestBIP0068AndBIP0112Activation(t *testing.T) {
t.Parallel()
// We'd like the test proper evaluation and validation of the BIP 68
// (sequence locks) and BIP 112 rule-sets which add input-age based
// relative lock times.
btcdCfg := []string{"--rejectnonstd"}
r, err := rpctest.New(&chaincfg.SimNetParams, nil, btcdCfg)
if err != nil {
t.Fatal("unable to create primary harness: ", err)
}
if err := r.SetUp(true, 1); err != nil {
t.Fatalf("unable to setup test chain: %v", err)
}
defer r.TearDown()
assertSoftForkStatus(r, t, csvKey, blockchain.ThresholdStarted)
harnessAddr, err := r.NewAddress()
if err != nil {
t.Fatalf("unable to obtain harness address: %v", err)
}
harnessScript, err := txscript.PayToAddrScript(harnessAddr)
if err != nil {
t.Fatalf("unable to generate pkScript: %v", err)
}
const (
outputAmt = btcutil.SatoshiPerBitcoin
relativeBlockLock = 10
)
sweepOutput := &wire.TxOut{
Value: outputAmt - 5000,
PkScript: harnessScript,
}
// As the soft-fork hasn't yet activated _any_ transaction version
// which uses the CSV opcode should be accepted. Since at this point,
// CSV doesn't actually exist, it's just a NOP.
for txVersion := int32(0); txVersion < 3; txVersion++ {
// Create a trivially spendable output with a CSV lock-time of
// 10 relative blocks.
redeemScript, testUTXO, tx, err := createCSVOutput(r, t, outputAmt,
relativeBlockLock, false)
if err != nil {
t.Fatalf("unable to create CSV encumbered output: %v", err)
}
// As the transaction is p2sh it should be accepted into the
// mempool and found within the next generated block.
if _, err := r.Node.SendRawTransaction(tx, true); err != nil {
t.Fatalf("unable to broadcast tx: %v", err)
}
blocks, err := r.Node.Generate(1)
if err != nil {
t.Fatalf("unable to generate blocks: %v", err)
}
txid := tx.TxHash()
assertTxInBlock(r, t, blocks[0], &txid)
// Generate a custom transaction which spends the CSV output.
sequenceNum := blockchain.LockTimeToSequence(false, 10)
spendingTx, err := spendCSVOutput(redeemScript, testUTXO,
sequenceNum, sweepOutput, txVersion)
if err != nil {
t.Fatalf("unable to spend csv output: %v", err)
}
// This transaction should be rejected from the mempool since
// CSV validation is already mempool policy pre-fork.
_, err = r.Node.SendRawTransaction(spendingTx, true)
if err == nil {
t.Fatalf("transaction should have been rejected, but was " +
"instead accepted")
}
// However, this transaction should be accepted in a custom
// generated block as CSV validation for scripts within blocks
// shouldn't yet be active.
txns := []*btcutil.Tx{btcutil.NewTx(spendingTx)}
block, err := r.GenerateAndSubmitBlock(txns, -1, time.Time{})
if err != nil {
t.Fatalf("unable to submit block: %v", err)
}
txid = spendingTx.TxHash()
assertTxInBlock(r, t, block.Hash(), &txid)
}
// At this point, the block height should be 107: we started at height
// 101, then generated 2 blocks in each loop iteration above.
assertChainHeight(r, t, 107)
// With the height at 107 we need 200 blocks to be mined after the
// genesis target period, so we mine 192 blocks. This'll put us at
// height 299. The getblockchaininfo call checks the state for the
// block AFTER the current height.
numBlocks := (r.ActiveNet.MinerConfirmationWindow * 2) - 8
if _, err := r.Node.Generate(numBlocks); err != nil {
t.Fatalf("unable to generate blocks: %v", err)
}
assertChainHeight(r, t, 299)
assertSoftForkStatus(r, t, csvKey, blockchain.ThresholdActive)
// Knowing the number of outputs needed for the tests below, create a
// fresh output for use within each of the test-cases below.
const relativeTimeLock = 512
const numTests = 8
type csvOutput struct {
RedeemScript []byte
Utxo *wire.OutPoint
Timelock int32
}
var spendableInputs [numTests]csvOutput
// Create three outputs which have a block-based sequence locks, and
// three outputs which use the above time based sequence lock.
for i := 0; i < numTests; i++ {
timeLock := relativeTimeLock
isSeconds := true
if i < 7 {
timeLock = relativeBlockLock
isSeconds = false
}
redeemScript, utxo, tx, err := createCSVOutput(r, t, outputAmt,
int32(timeLock), isSeconds)
if err != nil {
t.Fatalf("unable to create CSV output: %v", err)
}
if _, err := r.Node.SendRawTransaction(tx, true); err != nil {
t.Fatalf("unable to broadcast transaction: %v", err)
}
spendableInputs[i] = csvOutput{
RedeemScript: redeemScript,
Utxo: utxo,
Timelock: int32(timeLock),
}
}
// Mine a single block including all the transactions generated above.
if _, err := r.Node.Generate(1); err != nil {
t.Fatalf("unable to generate block: %v", err)
}
// Now mine 10 additional blocks giving the inputs generated above a
// age of 11. Space out each block 10 minutes after the previous block.
prevBlockHash, err := r.Node.GetBestBlockHash()
if err != nil {
t.Fatalf("unable to get prior block hash: %v", err)
}
prevBlock, err := r.Node.GetBlock(prevBlockHash)
if err != nil {
t.Fatalf("unable to get block: %v", err)
}
for i := 0; i < relativeBlockLock; i++ {
timeStamp := prevBlock.Header.Timestamp.Add(time.Minute * 10)
b, err := r.GenerateAndSubmitBlock(nil, -1, timeStamp)
if err != nil {
t.Fatalf("unable to generate block: %v", err)
}
prevBlock = b.MsgBlock()
}
// A helper function to create fully signed transactions in-line during
// the array initialization below.
var inputIndex uint32
makeTxCase := func(sequenceNum uint32, txVersion int32) *wire.MsgTx {
csvInput := spendableInputs[inputIndex]
tx, err := spendCSVOutput(csvInput.RedeemScript, csvInput.Utxo,
sequenceNum, sweepOutput, txVersion)
if err != nil {
t.Fatalf("unable to spend CSV output: %v", err)
}
inputIndex++
return tx
}
tests := [numTests]struct {
tx *wire.MsgTx
accept bool
}{
// A valid transaction with a single input a sequence number
// creating a 100 block relative time-lock. This transaction
// should be rejected as its version number is 1, and only tx
// of version > 2 will trigger the CSV behavior.
{
tx: makeTxCase(blockchain.LockTimeToSequence(false, 100), 1),
accept: false,
},
// A transaction of version 2 spending a single input. The
// input has a relative time-lock of 1 block, but the disable
// bit it set. The transaction should be rejected as a result.
{
tx: makeTxCase(
blockchain.LockTimeToSequence(false, 1)|wire.SequenceLockTimeDisabled,
2,
),
accept: false,
},
// A v2 transaction with a single input having a 9 block
// relative time lock. The referenced input is 11 blocks old,
// but the CSV output requires a 10 block relative lock-time.
// Therefore, the transaction should be rejected.
{
tx: makeTxCase(blockchain.LockTimeToSequence(false, 9), 2),
accept: false,
},
// A v2 transaction with a single input having a 10 block
// relative time lock. The referenced input is 11 blocks old so
// the transaction should be accepted.
{
tx: makeTxCase(blockchain.LockTimeToSequence(false, 10), 2),
accept: true,
},
// A v2 transaction with a single input having a 11 block
// relative time lock. The input referenced has an input age of
// 11 and the CSV op-code requires 10 blocks to have passed, so
// this transaction should be accepted.
{
tx: makeTxCase(blockchain.LockTimeToSequence(false, 11), 2),
accept: true,
},
// A v2 transaction whose input has a 1000 blck relative time
// lock. This should be rejected as the input's age is only 11
// blocks.
{
tx: makeTxCase(blockchain.LockTimeToSequence(false, 1000), 2),
accept: false,
},
// A v2 transaction with a single input having a 512,000 second
// relative time-lock. This transaction should be rejected as 6
// days worth of blocks haven't yet been mined. The referenced
// input doesn't have sufficient age.
{
tx: makeTxCase(blockchain.LockTimeToSequence(true, 512000), 2),
accept: false,
},
// A v2 transaction whose single input has a 512 second
// relative time-lock. This transaction should be accepted as
// finalized.
{
tx: makeTxCase(blockchain.LockTimeToSequence(true, 512), 2),
accept: true,
},
}
for i, test := range tests {
txid, err := r.Node.SendRawTransaction(test.tx, true)
switch {
// Test case passes, nothing further to report.
case test.accept && err == nil:
// Transaction should have been accepted but we have a non-nil
// error.
case test.accept && err != nil:
t.Fatalf("test #%d, transaction should be accepted, "+
"but was rejected: %v", i, err)
// Transaction should have been rejected, but it was accepted.
case !test.accept && err == nil:
t.Fatalf("test #%d, transaction should be rejected, "+
"but was accepted", i)
// Transaction was rejected as wanted, nothing more to do.
case !test.accept && err != nil:
}
// If the transaction should be rejected, manually mine a block
// with the non-final transaction. It should be rejected.
if !test.accept {
txns := []*btcutil.Tx{btcutil.NewTx(test.tx)}
_, err := r.GenerateAndSubmitBlock(txns, -1, time.Time{})
if err == nil {
t.Fatalf("test #%d, invalid block accepted", i)
}
continue
}
// Generate a block, the transaction should be included within
// the newly mined block.
blockHashes, err := r.Node.Generate(1)
if err != nil {
t.Fatalf("unable to mine block: %v", err)
}
assertTxInBlock(r, t, blockHashes[0], txid)
}
}