1 package rpctest
2 3 import (
4 "fmt"
5 "os"
6 "testing"
7 "time"
8 9 "github.com/p9c/p9/pkg/amt"
10 "github.com/p9c/p9/pkg/btcaddr"
11 12 "github.com/p9c/p9/pkg/qu"
13 14 "github.com/p9c/p9/pkg/chaincfg"
15 "github.com/p9c/p9/pkg/chainhash"
16 "github.com/p9c/p9/pkg/txscript"
17 "github.com/p9c/p9/pkg/util"
18 "github.com/p9c/p9/pkg/wire"
19 )
20 21 func testSendOutputs(r *Harness, t *testing.T) {
22 genSpend := func(amt amt.Amount) *chainhash.Hash {
23 // Grab a fresh address from the wallet.
24 addr, e := r.NewAddress()
25 if e != nil {
26 t.Fatalf("unable to get new address: %v", e)
27 }
28 // Next, send amt DUO to this address,
29 // spending from one of our mature coinbase outputs.
30 addrScript, e := txscript.PayToAddrScript(addr)
31 if e != nil {
32 t.Fatalf("unable to generate pkscript to addr: %v", e)
33 }
34 output := wire.NewTxOut(int64(amt), addrScript)
35 txid, e := r.SendOutputs([]*wire.TxOut{output}, 10)
36 if e != nil {
37 t.Fatalf("coinbase spend failed: %v", e)
38 }
39 return txid
40 }
41 assertTxMined := func(txid *chainhash.Hash, blockHash *chainhash.Hash) {
42 block, e := r.Node.GetBlock(blockHash)
43 if e != nil {
44 t.Fatalf("unable to get block: %v", e)
45 }
46 numBlockTxns := len(block.Transactions)
47 if numBlockTxns < 2 {
48 t.Fatalf(
49 "crafted transaction wasn't mined, block should have "+
50 "at least %v transactions instead has %v", 2, numBlockTxns,
51 )
52 }
53 minedTx := block.Transactions[1]
54 txHash := minedTx.TxHash()
55 if txHash != *txid {
56 t.Fatalf("txid's don't match, %v vs %v", txHash, txid)
57 }
58 }
59 // First, generate a small spend which will require only a single input.
60 txid := genSpend(5 * amt.SatoshiPerBitcoin)
61 // Generate a single block, the transaction the wallet created should be found in this block.
62 blockHashes, e := r.Node.Generate(1)
63 if e != nil {
64 t.Fatalf("unable to generate single block: %v", e)
65 }
66 assertTxMined(txid, blockHashes[0])
67 // Next, generate a spend much greater than the block reward. This transaction should also have been mined properly.
68 txid = genSpend(500 * amt.SatoshiPerBitcoin)
69 blockHashes, e = r.Node.Generate(1)
70 if e != nil {
71 t.Fatalf("unable to generate single block: %v", e)
72 }
73 assertTxMined(txid, blockHashes[0])
74 }
75 76 func assertConnectedTo(t *testing.T, nodeA *Harness, nodeB *Harness) {
77 nodeAPeers, e := nodeA.Node.GetPeerInfo()
78 if e != nil {
79 t.Fatalf("unable to get nodeA's peer info")
80 }
81 nodeAddr := nodeB.node.config.listen
82 addrFound := false
83 for _, peerInfo := range nodeAPeers {
84 if peerInfo.Addr == nodeAddr {
85 addrFound = true
86 break
87 }
88 }
89 if !addrFound {
90 t.Fatal("nodeA not connected to nodeB")
91 }
92 }
93 94 func testConnectNode(r *Harness, t *testing.T) {
95 // Create a fresh test harness.
96 harness, e := New(&chaincfg.SimNetParams, nil, nil)
97 if e != nil {
98 t.Fatal(e)
99 }
100 if e := harness.SetUp(false, 0); E.Chk(e) {
101 t.Fatalf("unable to complete rpctest setup: %v", e)
102 }
103 defer func() {
104 if e := harness.TearDown(); E.Chk(e) {
105 }
106 }()
107 // Establish a p2p connection from our new local harness to the main harness.
108 if e := ConnectNode(harness, r); E.Chk(e) {
109 t.Fatalf("unable to connect local to main harness: %v", e)
110 }
111 // The main harness should show up in our local harness' peer's list, and vice verse.
112 assertConnectedTo(t, harness, r)
113 }
114 115 func testTearDownAll(t *testing.T) {
116 // Grab a local copy of the currently active harnesses before attempting to tear them all down.
117 initialActiveHarnesses := ActiveHarnesses()
118 // Tear down all currently active harnesses.
119 if e := TearDownAll(); E.Chk(e) {
120 t.Fatalf("unable to teardown all harnesses: %v", e)
121 }
122 // The global testInstances map should now be fully purged with no active test harnesses remaining.
123 if len(ActiveHarnesses()) != 0 {
124 t.Fatalf("test harnesses still active after TearDownAll")
125 }
126 for _, harness := range initialActiveHarnesses {
127 // Ensure all test directories have been deleted.
128 var e error
129 if _, e = os.Stat(harness.testNodeDir); e == nil {
130 t.Errorf("created test datadir was not deleted.")
131 }
132 }
133 }
134 func testActiveHarnesses(r *Harness, t *testing.T) {
135 numInitialHarnesses := len(ActiveHarnesses())
136 // Create a single test harness.
137 harness1, e := New(&chaincfg.SimNetParams, nil, nil)
138 if e != nil {
139 t.Fatal(e)
140 }
141 defer func() {
142 if e := harness1.TearDown(); E.Chk(e) {
143 }
144 }()
145 // With the harness created above, a single harness should be detected as active.
146 numActiveHarnesses := len(ActiveHarnesses())
147 if !(numActiveHarnesses > numInitialHarnesses) {
148 t.Fatalf(
149 "ActiveHarnesses not updated, should have an " +
150 "additional test harness listed.",
151 )
152 }
153 }
154 func testJoinMempools(r *Harness, t *testing.T) {
155 // Assert main test harness has no transactions in its mempool.
156 pooledHashes, e := r.Node.GetRawMempool()
157 if e != nil {
158 t.Fatalf("unable to get mempool for main test harness: %v", e)
159 }
160 if len(pooledHashes) != 0 {
161 t.Fatal("main test harness mempool not empty")
162 }
163 // Create a local test harness with only the genesis block. The nodes will be synced below so the same transaction
164 // can be sent to both nodes without it being an orphan.
165 var harness *Harness
166 harness, e = New(&chaincfg.SimNetParams, nil, nil)
167 if e != nil {
168 t.Fatal(e)
169 }
170 if e = harness.SetUp(false, 0); E.Chk(e) {
171 t.Fatalf("unable to complete rpctest setup: %v", e)
172 }
173 defer func() {
174 if e = harness.TearDown(); E.Chk(e) {
175 }
176 }()
177 nodeSlice := []*Harness{r, harness}
178 // Both mempools should be considered synced as they are empty. Therefore, this should return instantly.
179 if e = JoinNodes(nodeSlice, Mempools); E.Chk(e) {
180 t.Fatalf("unable to join node on mempools: %v", e)
181 }
182 // Generate a coinbase spend to a new address within the main harness' mempool.
183 var addr btcaddr.Address
184 if addr, e = r.NewAddress(); E.Chk(e) {
185 }
186 var addrScript []byte
187 addrScript, e = txscript.PayToAddrScript(addr)
188 if e != nil {
189 t.Fatalf("unable to generate pkscript to addr: %v", e)
190 }
191 output := wire.NewTxOut(5e8, addrScript)
192 var testTx *wire.MsgTx
193 testTx, e = r.CreateTransaction([]*wire.TxOut{output}, 10, true)
194 if e != nil {
195 t.Fatalf("coinbase spend failed: %v", e)
196 }
197 if _, e = r.Node.SendRawTransaction(testTx, true); E.Chk(e) {
198 t.Fatalf("send transaction failed: %v", e)
199 }
200 // Wait until the transaction shows up to ensure the two mempools are not the same.
201 harnessSynced := qu.T()
202 go func() {
203 for {
204 var poolHashes []*chainhash.Hash
205 poolHashes, e = r.Node.GetRawMempool()
206 if e != nil {
207 t.Fatalf("failed to retrieve harness mempool: %v", e)
208 }
209 if len(poolHashes) > 0 {
210 break
211 }
212 time.Sleep(time.Millisecond * 100)
213 }
214 harnessSynced <- struct{}{}
215 }()
216 select {
217 case <-harnessSynced.Wait():
218 case <-time.After(time.Minute):
219 t.Fatalf("harness node never received transaction")
220 }
221 // This select case should fall through to the default as the goroutine should be blocked on the JoinNodes call.
222 poolsSynced := qu.T()
223 go func() {
224 if e = JoinNodes(nodeSlice, Mempools); E.Chk(e) {
225 t.Fatalf("unable to join node on mempools: %v", e)
226 }
227 poolsSynced <- struct{}{}
228 }()
229 select {
230 case <-poolsSynced.Wait():
231 t.Fatalf("mempools detected as synced yet harness has a new tx")
232 default:
233 }
234 // Establish an outbound connection from the local harness to the main harness and wait for the chains to be synced.
235 if e = ConnectNode(harness, r); E.Chk(e) {
236 t.Fatalf("unable to connect harnesses: %v", e)
237 }
238 if e = JoinNodes(nodeSlice, Blocks); E.Chk(e) {
239 t.Fatalf("unable to join node on blocks: %v", e)
240 }
241 // Send the transaction to the local harness which will result in synced mempools.
242 if _, e = harness.Node.SendRawTransaction(testTx, true); E.Chk(e) {
243 t.Fatalf("send transaction failed: %v", e)
244 }
245 // Select once again with a special timeout case after 1 minute. The goroutine above should now be blocked on
246 // sending into the unbuffered channel. The send should immediately succeed. In order to avoid the test hanging
247 // indefinitely, a 1 minute timeout is in place.
248 select {
249 case <-poolsSynced.Wait():
250 case <-time.After(time.Minute):
251 t.Fatalf("mempools never detected as synced")
252 }
253 }
254 func testJoinBlocks(r *Harness, t *testing.T) {
255 // Create a second harness with only the genesis block so it is behind the main harness.
256 harness, e := New(&chaincfg.SimNetParams, nil, nil)
257 if e != nil {
258 t.Fatal(e)
259 }
260 if e := harness.SetUp(false, 0); E.Chk(e) {
261 t.Fatalf("unable to complete rpctest setup: %v", e)
262 }
263 defer func() {
264 if e := harness.TearDown(); E.Chk(e) {
265 }
266 }()
267 nodeSlice := []*Harness{r, harness}
268 blocksSynced := qu.T()
269 go func() {
270 if e := JoinNodes(nodeSlice, Blocks); E.Chk(e) {
271 t.Fatalf("unable to join node on blocks: %v", e)
272 }
273 blocksSynced <- struct{}{}
274 }()
275 // This select case should fall through to the default as the goroutine should be blocked on the JoinNodes calls.
276 select {
277 case <-blocksSynced.Wait():
278 t.Fatalf("blocks detected as synced yet local harness is behind")
279 default:
280 }
281 // Connect the local harness to the main harness which will sync the chains.
282 if e := ConnectNode(harness, r); E.Chk(e) {
283 t.Fatalf("unable to connect harnesses: %v", e)
284 }
285 // Select once again with a special timeout case after 1 minute. The goroutine above should now be blocked on
286 // sending into the unbuffered channel. The send should immediately succeed. In order to avoid the test hanging
287 // indefinitely, a 1 minute timeout is in place.
288 select {
289 case <-blocksSynced.Wait():
290 case <-time.After(time.Minute):
291 t.Fatalf("blocks never detected as synced")
292 }
293 }
294 func testGenerateAndSubmitBlock(r *Harness, t *testing.T) {
295 // Generate a few test spend transactions.
296 addr, e := r.NewAddress()
297 if e != nil {
298 t.Fatalf("unable to generate new address: %v", e)
299 }
300 pkScript, e := txscript.PayToAddrScript(addr)
301 if e != nil {
302 t.Fatalf("unable to create script: %v", e)
303 }
304 output := wire.NewTxOut(amt.SatoshiPerBitcoin.Int64(), pkScript)
305 const numTxns = 5
306 txns := make([]*util.Tx, 0, numTxns)
307 var tx *wire.MsgTx
308 for i := 0; i < numTxns; i++ {
309 tx, e = r.CreateTransaction([]*wire.TxOut{output}, 10, true)
310 if e != nil {
311 t.Fatalf("unable to create tx: %v", e)
312 }
313 txns = append(txns, util.NewTx(tx))
314 }
315 // Now generate a block with the default block version, and a zeroed out time.
316 block, e := r.GenerateAndSubmitBlock(txns, ^uint32(0), time.Time{})
317 if e != nil {
318 t.Fatalf("unable to generate block: %v", e)
319 }
320 // Ensure that all created transactions were included, and that the block version was properly set to the default.
321 numBlocksTxns := len(block.Transactions())
322 if numBlocksTxns != numTxns+1 {
323 t.Fatalf(
324 "block did not include all transactions: "+
325 "expected %v, got %v", numTxns+1, numBlocksTxns,
326 )
327 }
328 blockVersion := block.WireBlock().Header.Version
329 if blockVersion != BlockVersion {
330 t.Fatalf(
331 "block version is not default: expected %v, got %v",
332 BlockVersion, blockVersion,
333 )
334 }
335 // Next generate a block with a "non-standard" block version along with time stamp a minute after the previous
336 // block's timestamp.
337 timestamp := block.WireBlock().Header.Timestamp.Add(time.Minute)
338 targetBlockVersion := uint32(1337)
339 block, e = r.GenerateAndSubmitBlock(nil, targetBlockVersion, timestamp)
340 if e != nil {
341 t.Fatalf("unable to generate block: %v", e)
342 }
343 // Finally ensure that the desired block version and timestamp were set properly.
344 header := block.WireBlock().Header
345 blockVersion = header.Version
346 if blockVersion != int32(targetBlockVersion) {
347 t.Fatalf(
348 "block version mismatch: expected %v, got %v",
349 targetBlockVersion, blockVersion,
350 )
351 }
352 if !timestamp.Equal(header.Timestamp) {
353 t.Fatalf(
354 "header time stamp mismatch: expected %v, got %v",
355 timestamp, header.Timestamp,
356 )
357 }
358 }
359 func testGenerateAndSubmitBlockWithCustomCoinbaseOutputs(
360 r *Harness,
361 t *testing.T,
362 ) {
363 // Generate a few test spend transactions.
364 addr, e := r.NewAddress()
365 if e != nil {
366 t.Fatalf("unable to generate new address: %v", e)
367 }
368 pkScript, e := txscript.PayToAddrScript(addr)
369 if e != nil {
370 t.Fatalf("unable to create script: %v", e)
371 }
372 output := wire.NewTxOut(amt.SatoshiPerBitcoin.Int64(), pkScript)
373 const numTxns = 5
374 txns := make([]*util.Tx, 0, numTxns)
375 for i := 0; i < numTxns; i++ {
376 var tx *wire.MsgTx
377 tx, e = r.CreateTransaction([]*wire.TxOut{output}, 10, true)
378 if e != nil {
379 t.Fatalf("unable to create tx: %v", e)
380 }
381 txns = append(txns, util.NewTx(tx))
382 }
383 // Now generate a block with the default block version, a zero'd out time, and a burn output.
384 block, e := r.GenerateAndSubmitBlockWithCustomCoinbaseOutputs(
385 txns,
386 ^uint32(0), time.Time{}, []wire.TxOut{
387 {
388 Value: 0,
389 PkScript: []byte{},
390 },
391 },
392 )
393 if e != nil {
394 t.Fatalf("unable to generate block: %v", e)
395 }
396 // Ensure that all created transactions were included, and that the block version was properly set to the default.
397 numBlocksTxns := len(block.Transactions())
398 if numBlocksTxns != numTxns+1 {
399 t.Fatalf(
400 "block did not include all transactions: "+
401 "expected %v, got %v", numTxns+1, numBlocksTxns,
402 )
403 }
404 blockVersion := block.WireBlock().Header.Version
405 if blockVersion != BlockVersion {
406 t.Fatalf(
407 "block version is not default: expected %v, got %v",
408 BlockVersion, blockVersion,
409 )
410 }
411 // Next generate a block with a "non-standard" block version along with time stamp a minute after the previous
412 // block's timestamp.
413 timestamp := block.WireBlock().Header.Timestamp.Add(time.Minute)
414 targetBlockVersion := uint32(1337)
415 block, e = r.GenerateAndSubmitBlockWithCustomCoinbaseOutputs(
416 nil,
417 targetBlockVersion, timestamp, []wire.TxOut{
418 {
419 Value: 0,
420 PkScript: []byte{},
421 },
422 },
423 )
424 if e != nil {
425 t.Fatalf("unable to generate block: %v", e)
426 }
427 // Finally ensure that the desired block version and timestamp were set properly.
428 header := block.WireBlock().Header
429 blockVersion = header.Version
430 if blockVersion != int32(targetBlockVersion) {
431 t.Fatalf(
432 "block version mismatch: expected %v, got %v",
433 targetBlockVersion, blockVersion,
434 )
435 }
436 if !timestamp.Equal(header.Timestamp) {
437 t.Fatalf(
438 "header time stamp mismatch: expected %v, got %v",
439 timestamp, header.Timestamp,
440 )
441 }
442 }
443 func testMemWalletReorg(r *Harness, t *testing.T) {
444 // Create a fresh harness, we'll be using the main harness to force a re-org on this local harness.
445 harness, e := New(&chaincfg.SimNetParams, nil, nil)
446 if e != nil {
447 t.Fatal(e)
448 }
449 if e := harness.SetUp(true, 5); E.Chk(e) {
450 t.Fatalf("unable to complete rpctest setup: %v", e)
451 }
452 defer func() {
453 if e := harness.TearDown(); E.Chk(e) {
454 }
455 }()
456 // The internal wallet of this harness should now have 250 DUO.
457 expectedBalance := 250 * amt.SatoshiPerBitcoin
458 walletBalance := harness.ConfirmedBalance()
459 if expectedBalance != walletBalance {
460 t.Fatalf(
461 "wallet balance incorrect: expected %v, got %v",
462 expectedBalance, walletBalance,
463 )
464 }
465 // Now connect this local harness to the main harness then wait for their chains to synchronize.
466 if e := ConnectNode(harness, r); E.Chk(e) {
467 t.Fatalf("unable to connect harnesses: %v", e)
468 }
469 nodeSlice := []*Harness{r, harness}
470 if e := JoinNodes(nodeSlice, Blocks); E.Chk(e) {
471 t.Fatalf("unable to join node on blocks: %v", e)
472 }
473 // The original wallet should now have a balance of 0 DUO as its entire chain should have been decimated in favor of
474 // the main harness' chain.
475 expectedBalance = amt.Amount(0)
476 walletBalance = harness.ConfirmedBalance()
477 if expectedBalance != walletBalance {
478 t.Fatalf(
479 "wallet balance incorrect: expected %v, got %v",
480 expectedBalance, walletBalance,
481 )
482 }
483 }
484 func testMemWalletLockedOutputs(r *Harness, t *testing.T) {
485 // Obtain the initial balance of the wallet at this point.
486 startingBalance := r.ConfirmedBalance()
487 // First, create a signed transaction spending some outputs.
488 addr, e := r.NewAddress()
489 if e != nil {
490 t.Fatalf("unable to generate new address: %v", e)
491 }
492 pkScript, e := txscript.PayToAddrScript(addr)
493 if e != nil {
494 t.Fatalf("unable to create script: %v", e)
495 }
496 outputAmt := 50 * amt.SatoshiPerBitcoin
497 output := wire.NewTxOut(int64(outputAmt), pkScript)
498 tx, e := r.CreateTransaction([]*wire.TxOut{output}, 10, true)
499 if e != nil {
500 t.Fatalf("unable to create transaction: %v", e)
501 }
502 // The current wallet balance should now be at least 50 DUO less (accounting for fees) than the period balance
503 currentBalance := r.ConfirmedBalance()
504 if !(currentBalance <= startingBalance-outputAmt) {
505 t.Fatalf(
506 "spent outputs not locked: previous balance %v, "+
507 "current balance %v", startingBalance, currentBalance,
508 )
509 }
510 // Now unlocked all the spent inputs within the unbroadcast signed transaction. The current balance should now be
511 // exactly that of the starting balance.
512 r.UnlockOutputs(tx.TxIn)
513 currentBalance = r.ConfirmedBalance()
514 if currentBalance != startingBalance {
515 t.Fatalf(
516 "current and starting balance should now match: "+
517 "expected %v, got %v", startingBalance, currentBalance,
518 )
519 }
520 }
521 522 var harnessTestCases = []HarnessTestCase{
523 testSendOutputs,
524 testConnectNode,
525 testActiveHarnesses,
526 testJoinBlocks,
527 testJoinMempools, // Depends on results of testJoinBlocks
528 testGenerateAndSubmitBlock,
529 testGenerateAndSubmitBlockWithCustomCoinbaseOutputs,
530 testMemWalletReorg,
531 testMemWalletLockedOutputs,
532 }
533 534 var mainHarness *Harness
535 536 const (
537 numMatureOutputs = 25
538 )
539 540 func TestMain(m *testing.M) {
541 var e error
542 mainHarness, e = New(&chaincfg.SimNetParams, nil, nil)
543 if e != nil {
544 fmt.Println("unable to create main harness: ", e)
545 os.Exit(1)
546 }
547 // Initialize the main mining node with a chain of length 125, providing 25 mature coinbases to allow spending from
548 // for testing purposes.
549 if e = mainHarness.SetUp(true, numMatureOutputs); E.Chk(e) {
550 fmt.Println("unable to setup test chain: ", e)
551 // Even though the harness was not fully setup, it still needs to be torn down to ensure all resources such as
552 // temp directories are cleaned up. The error is intentionally ignored since this is already an error path and
553 // nothing else could be done about it anyways.
554 _ = mainHarness.TearDown()
555 os.Exit(1)
556 }
557 exitCode := m.Run()
558 // Clean up any active harnesses that are still currently running.
559 if len(ActiveHarnesses()) > 0 {
560 if e := TearDownAll(); E.Chk(e) {
561 fmt.Println("unable to tear down chain: ", e)
562 os.Exit(1)
563 }
564 }
565 os.Exit(exitCode)
566 }
567 func TestHarness(t *testing.T) {
568 // We should have (numMatureOutputs * 50 DUO) of mature unspendable
569 // outputs.
570 expectedBalance := numMatureOutputs * 50 * amt.SatoshiPerBitcoin
571 harnessBalance := mainHarness.ConfirmedBalance()
572 if harnessBalance != expectedBalance {
573 t.Fatalf(
574 "expected wallet balance of %v instead have %v",
575 expectedBalance, harnessBalance,
576 )
577 }
578 // Current tip should be at a height of numMatureOutputs plus the required number of blocks for coinbase maturity.
579 nodeInfo, e := mainHarness.Node.GetInfo()
580 if e != nil {
581 t.Fatalf("unable to execute getinfo on node: %v", e)
582 }
583 expectedChainHeight := numMatureOutputs + uint32(mainHarness.ActiveNet.CoinbaseMaturity)
584 if uint32(nodeInfo.Blocks) != expectedChainHeight {
585 t.Errorf(
586 "Chain height is %v, should be %v",
587 nodeInfo.Blocks, expectedChainHeight,
588 )
589 }
590 for _, testCase := range harnessTestCases {
591 testCase(mainHarness, t)
592 }
593 testTearDownAll(t)
594 }
595