1// Copyright (c) 2015-2016 The btcsuite developers
2// Use of this source code is governed by an ISC
3// license that can be found in the LICENSE file.
4
5package blockchain
6
7import (
8	"bytes"
9	"errors"
10	"math/big"
11	"reflect"
12	"testing"
13
14	"github.com/btcsuite/btcd/database"
15	"github.com/btcsuite/btcd/wire"
16)
17
18// TestErrNotInMainChain ensures the functions related to errNotInMainChain work
19// as expected.
20func TestErrNotInMainChain(t *testing.T) {
21	errStr := "no block at height 1 exists"
22	err := error(errNotInMainChain(errStr))
23
24	// Ensure the stringized output for the error is as expected.
25	if err.Error() != errStr {
26		t.Fatalf("errNotInMainChain retuned unexpected error string - "+
27			"got %q, want %q", err.Error(), errStr)
28	}
29
30	// Ensure error is detected as the correct type.
31	if !isNotInMainChainErr(err) {
32		t.Fatalf("isNotInMainChainErr did not detect as expected type")
33	}
34	err = errors.New("something else")
35	if isNotInMainChainErr(err) {
36		t.Fatalf("isNotInMainChainErr detected incorrect type")
37	}
38}
39
40// TestStxoSerialization ensures serializing and deserializing spent transaction
41// output entries works as expected.
42func TestStxoSerialization(t *testing.T) {
43	t.Parallel()
44
45	tests := []struct {
46		name       string
47		stxo       SpentTxOut
48		serialized []byte
49	}{
50		// From block 170 in main blockchain.
51		{
52			name: "Spends last output of coinbase",
53			stxo: SpentTxOut{
54				Amount:     5000000000,
55				PkScript:   hexToBytes("410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac"),
56				IsCoinBase: true,
57				Height:     9,
58			},
59			serialized: hexToBytes("1300320511db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c"),
60		},
61		// Adapted from block 100025 in main blockchain.
62		{
63			name: "Spends last output of non coinbase",
64			stxo: SpentTxOut{
65				Amount:     13761000000,
66				PkScript:   hexToBytes("76a914b2fb57eadf61e106a100a7445a8c3f67898841ec88ac"),
67				IsCoinBase: false,
68				Height:     100024,
69			},
70			serialized: hexToBytes("8b99700086c64700b2fb57eadf61e106a100a7445a8c3f67898841ec"),
71		},
72		// Adapted from block 100025 in main blockchain.
73		{
74			name: "Does not spend last output, legacy format",
75			stxo: SpentTxOut{
76				Amount:   34405000000,
77				PkScript: hexToBytes("76a9146edbc6c4d31bae9f1ccc38538a114bf42de65e8688ac"),
78			},
79			serialized: hexToBytes("0091f20f006edbc6c4d31bae9f1ccc38538a114bf42de65e86"),
80		},
81	}
82
83	for _, test := range tests {
84		// Ensure the function to calculate the serialized size without
85		// actually serializing it is calculated properly.
86		gotSize := spentTxOutSerializeSize(&test.stxo)
87		if gotSize != len(test.serialized) {
88			t.Errorf("SpentTxOutSerializeSize (%s): did not get "+
89				"expected size - got %d, want %d", test.name,
90				gotSize, len(test.serialized))
91			continue
92		}
93
94		// Ensure the stxo serializes to the expected value.
95		gotSerialized := make([]byte, gotSize)
96		gotBytesWritten := putSpentTxOut(gotSerialized, &test.stxo)
97		if !bytes.Equal(gotSerialized, test.serialized) {
98			t.Errorf("putSpentTxOut (%s): did not get expected "+
99				"bytes - got %x, want %x", test.name,
100				gotSerialized, test.serialized)
101			continue
102		}
103		if gotBytesWritten != len(test.serialized) {
104			t.Errorf("putSpentTxOut (%s): did not get expected "+
105				"number of bytes written - got %d, want %d",
106				test.name, gotBytesWritten,
107				len(test.serialized))
108			continue
109		}
110
111		// Ensure the serialized bytes are decoded back to the expected
112		// stxo.
113		var gotStxo SpentTxOut
114		gotBytesRead, err := decodeSpentTxOut(test.serialized, &gotStxo)
115		if err != nil {
116			t.Errorf("decodeSpentTxOut (%s): unexpected error: %v",
117				test.name, err)
118			continue
119		}
120		if !reflect.DeepEqual(gotStxo, test.stxo) {
121			t.Errorf("decodeSpentTxOut (%s) mismatched entries - "+
122				"got %v, want %v", test.name, gotStxo, test.stxo)
123			continue
124		}
125		if gotBytesRead != len(test.serialized) {
126			t.Errorf("decodeSpentTxOut (%s): did not get expected "+
127				"number of bytes read - got %d, want %d",
128				test.name, gotBytesRead, len(test.serialized))
129			continue
130		}
131	}
132}
133
134// TestStxoDecodeErrors performs negative tests against decoding spent
135// transaction outputs to ensure error paths work as expected.
136func TestStxoDecodeErrors(t *testing.T) {
137	t.Parallel()
138
139	tests := []struct {
140		name       string
141		stxo       SpentTxOut
142		serialized []byte
143		bytesRead  int // Expected number of bytes read.
144		errType    error
145	}{
146		{
147			name:       "nothing serialized",
148			stxo:       SpentTxOut{},
149			serialized: hexToBytes(""),
150			errType:    errDeserialize(""),
151			bytesRead:  0,
152		},
153		{
154			name:       "no data after header code w/o reserved",
155			stxo:       SpentTxOut{},
156			serialized: hexToBytes("00"),
157			errType:    errDeserialize(""),
158			bytesRead:  1,
159		},
160		{
161			name:       "no data after header code with reserved",
162			stxo:       SpentTxOut{},
163			serialized: hexToBytes("13"),
164			errType:    errDeserialize(""),
165			bytesRead:  1,
166		},
167		{
168			name:       "no data after reserved",
169			stxo:       SpentTxOut{},
170			serialized: hexToBytes("1300"),
171			errType:    errDeserialize(""),
172			bytesRead:  2,
173		},
174		{
175			name:       "incomplete compressed txout",
176			stxo:       SpentTxOut{},
177			serialized: hexToBytes("1332"),
178			errType:    errDeserialize(""),
179			bytesRead:  2,
180		},
181	}
182
183	for _, test := range tests {
184		// Ensure the expected error type is returned.
185		gotBytesRead, err := decodeSpentTxOut(test.serialized,
186			&test.stxo)
187		if reflect.TypeOf(err) != reflect.TypeOf(test.errType) {
188			t.Errorf("decodeSpentTxOut (%s): expected error type "+
189				"does not match - got %T, want %T", test.name,
190				err, test.errType)
191			continue
192		}
193
194		// Ensure the expected number of bytes read is returned.
195		if gotBytesRead != test.bytesRead {
196			t.Errorf("decodeSpentTxOut (%s): unexpected number of "+
197				"bytes read - got %d, want %d", test.name,
198				gotBytesRead, test.bytesRead)
199			continue
200		}
201	}
202}
203
204// TestSpendJournalSerialization ensures serializing and deserializing spend
205// journal entries works as expected.
206func TestSpendJournalSerialization(t *testing.T) {
207	t.Parallel()
208
209	tests := []struct {
210		name       string
211		entry      []SpentTxOut
212		blockTxns  []*wire.MsgTx
213		serialized []byte
214	}{
215		// From block 2 in main blockchain.
216		{
217			name:       "No spends",
218			entry:      nil,
219			blockTxns:  nil,
220			serialized: nil,
221		},
222		// From block 170 in main blockchain.
223		{
224			name: "One tx with one input spends last output of coinbase",
225			entry: []SpentTxOut{{
226				Amount:     5000000000,
227				PkScript:   hexToBytes("410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac"),
228				IsCoinBase: true,
229				Height:     9,
230			}},
231			blockTxns: []*wire.MsgTx{{ // Coinbase omitted.
232				Version: 1,
233				TxIn: []*wire.TxIn{{
234					PreviousOutPoint: wire.OutPoint{
235						Hash:  *newHashFromStr("0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9"),
236						Index: 0,
237					},
238					SignatureScript: hexToBytes("47304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901"),
239					Sequence:        0xffffffff,
240				}},
241				TxOut: []*wire.TxOut{{
242					Value:    1000000000,
243					PkScript: hexToBytes("4104ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84cac"),
244				}, {
245					Value:    4000000000,
246					PkScript: hexToBytes("410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac"),
247				}},
248				LockTime: 0,
249			}},
250			serialized: hexToBytes("1300320511db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c"),
251		},
252		// Adapted from block 100025 in main blockchain.
253		{
254			name: "Two txns when one spends last output, one doesn't",
255			entry: []SpentTxOut{{
256				Amount:     34405000000,
257				PkScript:   hexToBytes("76a9146edbc6c4d31bae9f1ccc38538a114bf42de65e8688ac"),
258				IsCoinBase: false,
259				Height:     100024,
260			}, {
261				Amount:     13761000000,
262				PkScript:   hexToBytes("76a914b2fb57eadf61e106a100a7445a8c3f67898841ec88ac"),
263				IsCoinBase: false,
264				Height:     100024,
265			}},
266			blockTxns: []*wire.MsgTx{{ // Coinbase omitted.
267				Version: 1,
268				TxIn: []*wire.TxIn{{
269					PreviousOutPoint: wire.OutPoint{
270						Hash:  *newHashFromStr("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"),
271						Index: 1,
272					},
273					SignatureScript: hexToBytes("493046022100c167eead9840da4a033c9a56470d7794a9bb1605b377ebe5688499b39f94be59022100fb6345cab4324f9ea0b9ee9169337534834638d818129778370f7d378ee4a325014104d962cac5390f12ddb7539507065d0def320d68c040f2e73337c3a1aaaab7195cb5c4d02e0959624d534f3c10c3cf3d73ca5065ebd62ae986b04c6d090d32627c"),
274					Sequence:        0xffffffff,
275				}},
276				TxOut: []*wire.TxOut{{
277					Value:    5000000,
278					PkScript: hexToBytes("76a914f419b8db4ba65f3b6fcc233acb762ca6f51c23d488ac"),
279				}, {
280					Value:    34400000000,
281					PkScript: hexToBytes("76a914cadf4fc336ab3c6a4610b75f31ba0676b7f663d288ac"),
282				}},
283				LockTime: 0,
284			}, {
285				Version: 1,
286				TxIn: []*wire.TxIn{{
287					PreviousOutPoint: wire.OutPoint{
288						Hash:  *newHashFromStr("92fbe1d4be82f765dfabc9559d4620864b05cc897c4db0e29adac92d294e52b7"),
289						Index: 0,
290					},
291					SignatureScript: hexToBytes("483045022100e256743154c097465cf13e89955e1c9ff2e55c46051b627751dee0144183157e02201d8d4f02cde8496aae66768f94d35ce54465bd4ae8836004992d3216a93a13f00141049d23ce8686fe9b802a7a938e8952174d35dd2c2089d4112001ed8089023ab4f93a3c9fcd5bfeaa9727858bf640dc1b1c05ec3b434bb59837f8640e8810e87742"),
292					Sequence:        0xffffffff,
293				}},
294				TxOut: []*wire.TxOut{{
295					Value:    5000000,
296					PkScript: hexToBytes("76a914a983ad7c92c38fc0e2025212e9f972204c6e687088ac"),
297				}, {
298					Value:    13756000000,
299					PkScript: hexToBytes("76a914a6ebd69952ab486a7a300bfffdcb395dc7d47c2388ac"),
300				}},
301				LockTime: 0,
302			}},
303			serialized: hexToBytes("8b99700086c64700b2fb57eadf61e106a100a7445a8c3f67898841ec8b99700091f20f006edbc6c4d31bae9f1ccc38538a114bf42de65e86"),
304		},
305	}
306
307	for i, test := range tests {
308		// Ensure the journal entry serializes to the expected value.
309		gotBytes := serializeSpendJournalEntry(test.entry)
310		if !bytes.Equal(gotBytes, test.serialized) {
311			t.Errorf("serializeSpendJournalEntry #%d (%s): "+
312				"mismatched bytes - got %x, want %x", i,
313				test.name, gotBytes, test.serialized)
314			continue
315		}
316
317		// Deserialize to a spend journal entry.
318		gotEntry, err := deserializeSpendJournalEntry(test.serialized,
319			test.blockTxns)
320		if err != nil {
321			t.Errorf("deserializeSpendJournalEntry #%d (%s) "+
322				"unexpected error: %v", i, test.name, err)
323			continue
324		}
325
326		// Ensure that the deserialized spend journal entry has the
327		// correct properties.
328		if !reflect.DeepEqual(gotEntry, test.entry) {
329			t.Errorf("deserializeSpendJournalEntry #%d (%s) "+
330				"mismatched entries - got %v, want %v",
331				i, test.name, gotEntry, test.entry)
332			continue
333		}
334	}
335}
336
337// TestSpendJournalErrors performs negative tests against deserializing spend
338// journal entries to ensure error paths work as expected.
339func TestSpendJournalErrors(t *testing.T) {
340	t.Parallel()
341
342	tests := []struct {
343		name       string
344		blockTxns  []*wire.MsgTx
345		serialized []byte
346		errType    error
347	}{
348		// Adapted from block 170 in main blockchain.
349		{
350			name: "Force assertion due to missing stxos",
351			blockTxns: []*wire.MsgTx{{ // Coinbase omitted.
352				Version: 1,
353				TxIn: []*wire.TxIn{{
354					PreviousOutPoint: wire.OutPoint{
355						Hash:  *newHashFromStr("0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9"),
356						Index: 0,
357					},
358					SignatureScript: hexToBytes("47304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901"),
359					Sequence:        0xffffffff,
360				}},
361				LockTime: 0,
362			}},
363			serialized: hexToBytes(""),
364			errType:    AssertError(""),
365		},
366		{
367			name: "Force deserialization error in stxos",
368			blockTxns: []*wire.MsgTx{{ // Coinbase omitted.
369				Version: 1,
370				TxIn: []*wire.TxIn{{
371					PreviousOutPoint: wire.OutPoint{
372						Hash:  *newHashFromStr("0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9"),
373						Index: 0,
374					},
375					SignatureScript: hexToBytes("47304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901"),
376					Sequence:        0xffffffff,
377				}},
378				LockTime: 0,
379			}},
380			serialized: hexToBytes("1301320511db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a"),
381			errType:    errDeserialize(""),
382		},
383	}
384
385	for _, test := range tests {
386		// Ensure the expected error type is returned and the returned
387		// slice is nil.
388		stxos, err := deserializeSpendJournalEntry(test.serialized,
389			test.blockTxns)
390		if reflect.TypeOf(err) != reflect.TypeOf(test.errType) {
391			t.Errorf("deserializeSpendJournalEntry (%s): expected "+
392				"error type does not match - got %T, want %T",
393				test.name, err, test.errType)
394			continue
395		}
396		if stxos != nil {
397			t.Errorf("deserializeSpendJournalEntry (%s): returned "+
398				"slice of spent transaction outputs is not nil",
399				test.name)
400			continue
401		}
402	}
403}
404
405// TestUtxoSerialization ensures serializing and deserializing unspent
406// trasaction output entries works as expected.
407func TestUtxoSerialization(t *testing.T) {
408	t.Parallel()
409
410	tests := []struct {
411		name       string
412		entry      *UtxoEntry
413		serialized []byte
414	}{
415		// From tx in main blockchain:
416		// 0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098:0
417		{
418			name: "height 1, coinbase",
419			entry: &UtxoEntry{
420				amount:      5000000000,
421				pkScript:    hexToBytes("410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac"),
422				blockHeight: 1,
423				packedFlags: tfCoinBase,
424			},
425			serialized: hexToBytes("03320496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52"),
426		},
427		// From tx in main blockchain:
428		// 0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098:0
429		{
430			name: "height 1, coinbase, spent",
431			entry: &UtxoEntry{
432				amount:      5000000000,
433				pkScript:    hexToBytes("410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac"),
434				blockHeight: 1,
435				packedFlags: tfCoinBase | tfSpent,
436			},
437			serialized: nil,
438		},
439		// From tx in main blockchain:
440		// 8131ffb0a2c945ecaf9b9063e59558784f9c3a74741ce6ae2a18d0571dac15bb:1
441		{
442			name: "height 100001, not coinbase",
443			entry: &UtxoEntry{
444				amount:      1000000,
445				pkScript:    hexToBytes("76a914ee8bd501094a7d5ca318da2506de35e1cb025ddc88ac"),
446				blockHeight: 100001,
447				packedFlags: 0,
448			},
449			serialized: hexToBytes("8b99420700ee8bd501094a7d5ca318da2506de35e1cb025ddc"),
450		},
451		// From tx in main blockchain:
452		// 8131ffb0a2c945ecaf9b9063e59558784f9c3a74741ce6ae2a18d0571dac15bb:1
453		{
454			name: "height 100001, not coinbase, spent",
455			entry: &UtxoEntry{
456				amount:      1000000,
457				pkScript:    hexToBytes("76a914ee8bd501094a7d5ca318da2506de35e1cb025ddc88ac"),
458				blockHeight: 100001,
459				packedFlags: tfSpent,
460			},
461			serialized: nil,
462		},
463	}
464
465	for i, test := range tests {
466		// Ensure the utxo entry serializes to the expected value.
467		gotBytes, err := serializeUtxoEntry(test.entry)
468		if err != nil {
469			t.Errorf("serializeUtxoEntry #%d (%s) unexpected "+
470				"error: %v", i, test.name, err)
471			continue
472		}
473		if !bytes.Equal(gotBytes, test.serialized) {
474			t.Errorf("serializeUtxoEntry #%d (%s): mismatched "+
475				"bytes - got %x, want %x", i, test.name,
476				gotBytes, test.serialized)
477			continue
478		}
479
480		// Don't try to deserialize if the test entry was spent since it
481		// will have a nil serialization.
482		if test.entry.IsSpent() {
483			continue
484		}
485
486		// Deserialize to a utxo entry.
487		utxoEntry, err := deserializeUtxoEntry(test.serialized)
488		if err != nil {
489			t.Errorf("deserializeUtxoEntry #%d (%s) unexpected "+
490				"error: %v", i, test.name, err)
491			continue
492		}
493
494		// The deserialized entry must not be marked spent since unspent
495		// entries are not serialized.
496		if utxoEntry.IsSpent() {
497			t.Errorf("deserializeUtxoEntry #%d (%s) output should "+
498				"not be marked spent", i, test.name)
499			continue
500		}
501
502		// Ensure the deserialized entry has the same properties as the
503		// ones in the test entry.
504		if utxoEntry.Amount() != test.entry.Amount() {
505			t.Errorf("deserializeUtxoEntry #%d (%s) mismatched "+
506				"amounts: got %d, want %d", i, test.name,
507				utxoEntry.Amount(), test.entry.Amount())
508			continue
509		}
510
511		if !bytes.Equal(utxoEntry.PkScript(), test.entry.PkScript()) {
512			t.Errorf("deserializeUtxoEntry #%d (%s) mismatched "+
513				"scripts: got %x, want %x", i, test.name,
514				utxoEntry.PkScript(), test.entry.PkScript())
515			continue
516		}
517		if utxoEntry.BlockHeight() != test.entry.BlockHeight() {
518			t.Errorf("deserializeUtxoEntry #%d (%s) mismatched "+
519				"block height: got %d, want %d", i, test.name,
520				utxoEntry.BlockHeight(), test.entry.BlockHeight())
521			continue
522		}
523		if utxoEntry.IsCoinBase() != test.entry.IsCoinBase() {
524			t.Errorf("deserializeUtxoEntry #%d (%s) mismatched "+
525				"coinbase flag: got %v, want %v", i, test.name,
526				utxoEntry.IsCoinBase(), test.entry.IsCoinBase())
527			continue
528		}
529	}
530}
531
532// TestUtxoEntryHeaderCodeErrors performs negative tests against unspent
533// transaction output header codes to ensure error paths work as expected.
534func TestUtxoEntryHeaderCodeErrors(t *testing.T) {
535	t.Parallel()
536
537	tests := []struct {
538		name    string
539		entry   *UtxoEntry
540		code    uint64
541		errType error
542	}{
543		{
544			name:    "Force assertion due to spent output",
545			entry:   &UtxoEntry{packedFlags: tfSpent},
546			errType: AssertError(""),
547		},
548	}
549
550	for _, test := range tests {
551		// Ensure the expected error type is returned and the code is 0.
552		code, err := utxoEntryHeaderCode(test.entry)
553		if reflect.TypeOf(err) != reflect.TypeOf(test.errType) {
554			t.Errorf("utxoEntryHeaderCode (%s): expected error "+
555				"type does not match - got %T, want %T",
556				test.name, err, test.errType)
557			continue
558		}
559		if code != 0 {
560			t.Errorf("utxoEntryHeaderCode (%s): unexpected code "+
561				"on error - got %d, want 0", test.name, code)
562			continue
563		}
564	}
565}
566
567// TestUtxoEntryDeserializeErrors performs negative tests against deserializing
568// unspent transaction outputs to ensure error paths work as expected.
569func TestUtxoEntryDeserializeErrors(t *testing.T) {
570	t.Parallel()
571
572	tests := []struct {
573		name       string
574		serialized []byte
575		errType    error
576	}{
577		{
578			name:       "no data after header code",
579			serialized: hexToBytes("02"),
580			errType:    errDeserialize(""),
581		},
582		{
583			name:       "incomplete compressed txout",
584			serialized: hexToBytes("0232"),
585			errType:    errDeserialize(""),
586		},
587	}
588
589	for _, test := range tests {
590		// Ensure the expected error type is returned and the returned
591		// entry is nil.
592		entry, err := deserializeUtxoEntry(test.serialized)
593		if reflect.TypeOf(err) != reflect.TypeOf(test.errType) {
594			t.Errorf("deserializeUtxoEntry (%s): expected error "+
595				"type does not match - got %T, want %T",
596				test.name, err, test.errType)
597			continue
598		}
599		if entry != nil {
600			t.Errorf("deserializeUtxoEntry (%s): returned entry "+
601				"is not nil", test.name)
602			continue
603		}
604	}
605}
606
607// TestBestChainStateSerialization ensures serializing and deserializing the
608// best chain state works as expected.
609func TestBestChainStateSerialization(t *testing.T) {
610	t.Parallel()
611
612	workSum := new(big.Int)
613	tests := []struct {
614		name       string
615		state      bestChainState
616		serialized []byte
617	}{
618		{
619			name: "genesis",
620			state: bestChainState{
621				hash:      *newHashFromStr("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"),
622				height:    0,
623				totalTxns: 1,
624				workSum: func() *big.Int {
625					workSum.Add(workSum, CalcWork(486604799))
626					return new(big.Int).Set(workSum)
627				}(), // 0x0100010001
628			},
629			serialized: hexToBytes("6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000000000000100000000000000050000000100010001"),
630		},
631		{
632			name: "block 1",
633			state: bestChainState{
634				hash:      *newHashFromStr("00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"),
635				height:    1,
636				totalTxns: 2,
637				workSum: func() *big.Int {
638					workSum.Add(workSum, CalcWork(486604799))
639					return new(big.Int).Set(workSum)
640				}(), // 0x0200020002
641			},
642			serialized: hexToBytes("4860eb18bf1b1620e37e9490fc8a427514416fd75159ab86688e9a8300000000010000000200000000000000050000000200020002"),
643		},
644	}
645
646	for i, test := range tests {
647		// Ensure the state serializes to the expected value.
648		gotBytes := serializeBestChainState(test.state)
649		if !bytes.Equal(gotBytes, test.serialized) {
650			t.Errorf("serializeBestChainState #%d (%s): mismatched "+
651				"bytes - got %x, want %x", i, test.name,
652				gotBytes, test.serialized)
653			continue
654		}
655
656		// Ensure the serialized bytes are decoded back to the expected
657		// state.
658		state, err := deserializeBestChainState(test.serialized)
659		if err != nil {
660			t.Errorf("deserializeBestChainState #%d (%s) "+
661				"unexpected error: %v", i, test.name, err)
662			continue
663		}
664		if !reflect.DeepEqual(state, test.state) {
665			t.Errorf("deserializeBestChainState #%d (%s) "+
666				"mismatched state - got %v, want %v", i,
667				test.name, state, test.state)
668			continue
669
670		}
671	}
672}
673
674// TestBestChainStateDeserializeErrors performs negative tests against
675// deserializing the chain state to ensure error paths work as expected.
676func TestBestChainStateDeserializeErrors(t *testing.T) {
677	t.Parallel()
678
679	tests := []struct {
680		name       string
681		serialized []byte
682		errType    error
683	}{
684		{
685			name:       "nothing serialized",
686			serialized: hexToBytes(""),
687			errType:    database.Error{ErrorCode: database.ErrCorruption},
688		},
689		{
690			name:       "short data in hash",
691			serialized: hexToBytes("0000"),
692			errType:    database.Error{ErrorCode: database.ErrCorruption},
693		},
694		{
695			name:       "short data in work sum",
696			serialized: hexToBytes("6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d61900000000000000000001000000000000000500000001000100"),
697			errType:    database.Error{ErrorCode: database.ErrCorruption},
698		},
699	}
700
701	for _, test := range tests {
702		// Ensure the expected error type and code is returned.
703		_, err := deserializeBestChainState(test.serialized)
704		if reflect.TypeOf(err) != reflect.TypeOf(test.errType) {
705			t.Errorf("deserializeBestChainState (%s): expected "+
706				"error type does not match - got %T, want %T",
707				test.name, err, test.errType)
708			continue
709		}
710		if derr, ok := err.(database.Error); ok {
711			tderr := test.errType.(database.Error)
712			if derr.ErrorCode != tderr.ErrorCode {
713				t.Errorf("deserializeBestChainState (%s): "+
714					"wrong  error code got: %v, want: %v",
715					test.name, derr.ErrorCode,
716					tderr.ErrorCode)
717				continue
718			}
719		}
720	}
721}
722