chainio_test.go raw

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