github.com/lbryio/lbcd@v0.22.119/blockchain/chainio_test.go (about) 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 5 package blockchain 6 7 import ( 8 "bytes" 9 "errors" 10 "math/big" 11 "reflect" 12 "testing" 13 14 "github.com/lbryio/lbcd/database" 15 "github.com/lbryio/lbcd/wire" 16 ) 17 18 // TestErrNotInMainChain ensures the functions related to errNotInMainChain work 19 // as expected. 20 func 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. 42 func 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. 136 func 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. 206 func 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. 339 func 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. 407 func 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. 534 func 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. 569 func 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. 609 func 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. 676 func 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 }