github.com/BlockABC/godash@v0.0.0-20191112120524-f4aa3a32c566/blockchain/chainio_test.go (about) 1 // Copyright (c) 2015-2016 The btcsuite developers 2 // Copyright (c) 2016 The Dash developers 3 // Use of this source code is governed by an ISC 4 // license that can be found in the LICENSE file. 5 6 package blockchain 7 8 import ( 9 "bytes" 10 "errors" 11 "math/big" 12 "reflect" 13 "testing" 14 15 "github.com/BlockABC/godash/database" 16 "github.com/BlockABC/godash/wire" 17 ) 18 19 // TestErrNotInMainChain ensures the functions related to errNotInMainChain work 20 // as expected. 21 func TestErrNotInMainChain(t *testing.T) { 22 errStr := "no block at height 1 exists" 23 err := error(errNotInMainChain(errStr)) 24 25 // Ensure the stringized output for the error is as expected. 26 if err.Error() != errStr { 27 t.Fatalf("errNotInMainChain retuned unexpected error string - "+ 28 "got %q, want %q", err.Error(), errStr) 29 } 30 31 // Ensure error is detected as the correct type. 32 if !isNotInMainChainErr(err) { 33 t.Fatalf("isNotInMainChainErr did not detect as expected type") 34 } 35 err = errors.New("something else") 36 if isNotInMainChainErr(err) { 37 t.Fatalf("isNotInMainChainErr detected incorrect type") 38 } 39 } 40 41 // maybeDecompress decompresses the amount and public key script fields of the 42 // stxo and marks it decompressed if needed. 43 func (o *spentTxOut) maybeDecompress(version int32) { 44 // Nothing to do if it's not compressed. 45 if !o.compressed { 46 return 47 } 48 49 o.amount = int64(decompressTxOutAmount(uint64(o.amount))) 50 o.pkScript = decompressScript(o.pkScript, version) 51 o.compressed = false 52 } 53 54 // TestStxoSerialization ensures serializing and deserializing spent transaction 55 // output entries works as expected. 56 func TestStxoSerialization(t *testing.T) { 57 t.Parallel() 58 59 tests := []struct { 60 name string 61 stxo spentTxOut 62 txVersion int32 // When the txout is not fully spent. 63 serialized []byte 64 }{ 65 // From block 170 in main blockchain. 66 { 67 name: "Spends last output of coinbase", 68 stxo: spentTxOut{ 69 amount: 5000000000, 70 pkScript: hexToBytes("410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac"), 71 isCoinBase: true, 72 height: 9, 73 version: 1, 74 }, 75 serialized: hexToBytes("1301320511db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c"), 76 }, 77 // Adapted from block 100025 in main blockchain. 78 { 79 name: "Spends last output of non coinbase", 80 stxo: spentTxOut{ 81 amount: 13761000000, 82 pkScript: hexToBytes("76a914b2fb57eadf61e106a100a7445a8c3f67898841ec88ac"), 83 isCoinBase: false, 84 height: 100024, 85 version: 1, 86 }, 87 serialized: hexToBytes("8b99700186c64700b2fb57eadf61e106a100a7445a8c3f67898841ec"), 88 }, 89 // Adapted from block 100025 in main blockchain. 90 { 91 name: "Does not spend last output", 92 stxo: spentTxOut{ 93 amount: 34405000000, 94 pkScript: hexToBytes("76a9146edbc6c4d31bae9f1ccc38538a114bf42de65e8688ac"), 95 version: 1, 96 }, 97 txVersion: 1, 98 serialized: hexToBytes("0091f20f006edbc6c4d31bae9f1ccc38538a114bf42de65e86"), 99 }, 100 } 101 102 for _, test := range tests { 103 // Ensure the function to calculate the serialized size without 104 // actually serializing it is calculated properly. 105 gotSize := spentTxOutSerializeSize(&test.stxo) 106 if gotSize != len(test.serialized) { 107 t.Errorf("spentTxOutSerializeSize (%s): did not get "+ 108 "expected size - got %d, want %d", test.name, 109 gotSize, len(test.serialized)) 110 continue 111 } 112 113 // Ensure the stxo serializes to the expected value. 114 gotSerialized := make([]byte, gotSize) 115 gotBytesWritten := putSpentTxOut(gotSerialized, &test.stxo) 116 if !bytes.Equal(gotSerialized, test.serialized) { 117 t.Errorf("putSpentTxOut (%s): did not get expected "+ 118 "bytes - got %x, want %x", test.name, 119 gotSerialized, test.serialized) 120 continue 121 } 122 if gotBytesWritten != len(test.serialized) { 123 t.Errorf("putSpentTxOut (%s): did not get expected "+ 124 "number of bytes written - got %d, want %d", 125 test.name, gotBytesWritten, 126 len(test.serialized)) 127 continue 128 } 129 130 // Ensure the serialized bytes are decoded back to the expected 131 // stxo. 132 var gotStxo spentTxOut 133 gotBytesRead, err := decodeSpentTxOut(test.serialized, &gotStxo, 134 test.txVersion) 135 if err != nil { 136 t.Errorf("decodeSpentTxOut (%s): unexpected error: %v", 137 test.name, err) 138 continue 139 } 140 gotStxo.maybeDecompress(test.stxo.version) 141 if !reflect.DeepEqual(gotStxo, test.stxo) { 142 t.Errorf("decodeSpentTxOut (%s) mismatched entries - "+ 143 "got %v, want %v", test.name, gotStxo, test.stxo) 144 continue 145 } 146 if gotBytesRead != len(test.serialized) { 147 t.Errorf("decodeSpentTxOut (%s): did not get expected "+ 148 "number of bytes read - got %d, want %d", 149 test.name, gotBytesRead, len(test.serialized)) 150 continue 151 } 152 } 153 } 154 155 // TestStxoDecodeErrors performs negative tests against decoding spent 156 // transaction outputs to ensure error paths work as expected. 157 func TestStxoDecodeErrors(t *testing.T) { 158 t.Parallel() 159 160 tests := []struct { 161 name string 162 stxo spentTxOut 163 txVersion int32 // When the txout is not fully spent. 164 serialized []byte 165 bytesRead int // Expected number of bytes read. 166 errType error 167 }{ 168 { 169 name: "nothing serialized", 170 stxo: spentTxOut{}, 171 serialized: hexToBytes(""), 172 errType: errDeserialize(""), 173 bytesRead: 0, 174 }, 175 { 176 name: "no data after header code w/o version", 177 stxo: spentTxOut{}, 178 serialized: hexToBytes("00"), 179 errType: errDeserialize(""), 180 bytesRead: 1, 181 }, 182 { 183 name: "no data after header code with version", 184 stxo: spentTxOut{}, 185 serialized: hexToBytes("13"), 186 errType: errDeserialize(""), 187 bytesRead: 1, 188 }, 189 { 190 name: "no data after version", 191 stxo: spentTxOut{}, 192 serialized: hexToBytes("1301"), 193 errType: errDeserialize(""), 194 bytesRead: 2, 195 }, 196 { 197 name: "no serialized tx version and passed 0", 198 stxo: spentTxOut{}, 199 serialized: hexToBytes("003205"), 200 errType: AssertError(""), 201 bytesRead: 1, 202 }, 203 { 204 name: "incomplete compressed txout", 205 stxo: spentTxOut{}, 206 txVersion: 1, 207 serialized: hexToBytes("0032"), 208 errType: errDeserialize(""), 209 bytesRead: 2, 210 }, 211 } 212 213 for _, test := range tests { 214 // Ensure the expected error type is returned. 215 gotBytesRead, err := decodeSpentTxOut(test.serialized, 216 &test.stxo, test.txVersion) 217 if reflect.TypeOf(err) != reflect.TypeOf(test.errType) { 218 t.Errorf("decodeSpentTxOut (%s): expected error type "+ 219 "does not match - got %T, want %T", test.name, 220 err, test.errType) 221 continue 222 } 223 224 // Ensure the expected number of bytes read is returned. 225 if gotBytesRead != test.bytesRead { 226 t.Errorf("decodeSpentTxOut (%s): unexpected number of "+ 227 "bytes read - got %d, want %d", test.name, 228 gotBytesRead, test.bytesRead) 229 continue 230 } 231 } 232 } 233 234 // TestSpendJournalSerialization ensures serializing and deserializing spend 235 // journal entries works as expected. 236 func TestSpendJournalSerialization(t *testing.T) { 237 t.Parallel() 238 239 tests := []struct { 240 name string 241 entry []spentTxOut 242 blockTxns []*wire.MsgTx 243 utxoView *UtxoViewpoint 244 serialized []byte 245 }{ 246 // From block 2 in main blockchain. 247 { 248 name: "No spends", 249 entry: nil, 250 blockTxns: nil, 251 utxoView: NewUtxoViewpoint(), 252 serialized: nil, 253 }, 254 // From block 170 in main blockchain. 255 { 256 name: "One tx with one input spends last output of coinbase", 257 entry: []spentTxOut{{ 258 amount: 5000000000, 259 pkScript: hexToBytes("410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac"), 260 isCoinBase: true, 261 height: 9, 262 version: 1, 263 }}, 264 blockTxns: []*wire.MsgTx{{ // Coinbase omitted. 265 Version: 1, 266 TxIn: []*wire.TxIn{{ 267 PreviousOutPoint: wire.OutPoint{ 268 Hash: *newShaHashFromStr("0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9"), 269 Index: 0, 270 }, 271 SignatureScript: hexToBytes("47304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901"), 272 Sequence: 0xffffffff, 273 }}, 274 TxOut: []*wire.TxOut{{ 275 Value: 1000000000, 276 PkScript: hexToBytes("4104ae1a62fe09c5f51b13905f07f06b99a2f7159b2225f374cd378d71302fa28414e7aab37397f554a7df5f142c21c1b7303b8a0626f1baded5c72a704f7e6cd84cac"), 277 }, { 278 Value: 4000000000, 279 PkScript: hexToBytes("410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac"), 280 }}, 281 LockTime: 0, 282 }}, 283 utxoView: NewUtxoViewpoint(), 284 serialized: hexToBytes("1301320511db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c"), 285 }, 286 // Adapted from block 100025 in main blockchain. 287 { 288 name: "Two txns when one spends last output, one doesn't", 289 entry: []spentTxOut{{ 290 amount: 34405000000, 291 pkScript: hexToBytes("76a9146edbc6c4d31bae9f1ccc38538a114bf42de65e8688ac"), 292 version: 1, 293 }, { 294 amount: 13761000000, 295 pkScript: hexToBytes("76a914b2fb57eadf61e106a100a7445a8c3f67898841ec88ac"), 296 isCoinBase: false, 297 height: 100024, 298 version: 1, 299 }}, 300 blockTxns: []*wire.MsgTx{{ // Coinbase omitted. 301 Version: 1, 302 TxIn: []*wire.TxIn{{ 303 PreviousOutPoint: wire.OutPoint{ 304 Hash: *newShaHashFromStr("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"), 305 Index: 1, 306 }, 307 SignatureScript: hexToBytes("493046022100c167eead9840da4a033c9a56470d7794a9bb1605b377ebe5688499b39f94be59022100fb6345cab4324f9ea0b9ee9169337534834638d818129778370f7d378ee4a325014104d962cac5390f12ddb7539507065d0def320d68c040f2e73337c3a1aaaab7195cb5c4d02e0959624d534f3c10c3cf3d73ca5065ebd62ae986b04c6d090d32627c"), 308 Sequence: 0xffffffff, 309 }}, 310 TxOut: []*wire.TxOut{{ 311 Value: 5000000, 312 PkScript: hexToBytes("76a914f419b8db4ba65f3b6fcc233acb762ca6f51c23d488ac"), 313 }, { 314 Value: 34400000000, 315 PkScript: hexToBytes("76a914cadf4fc336ab3c6a4610b75f31ba0676b7f663d288ac"), 316 }}, 317 LockTime: 0, 318 }, { 319 Version: 1, 320 TxIn: []*wire.TxIn{{ 321 PreviousOutPoint: wire.OutPoint{ 322 Hash: *newShaHashFromStr("92fbe1d4be82f765dfabc9559d4620864b05cc897c4db0e29adac92d294e52b7"), 323 Index: 0, 324 }, 325 SignatureScript: hexToBytes("483045022100e256743154c097465cf13e89955e1c9ff2e55c46051b627751dee0144183157e02201d8d4f02cde8496aae66768f94d35ce54465bd4ae8836004992d3216a93a13f00141049d23ce8686fe9b802a7a938e8952174d35dd2c2089d4112001ed8089023ab4f93a3c9fcd5bfeaa9727858bf640dc1b1c05ec3b434bb59837f8640e8810e87742"), 326 Sequence: 0xffffffff, 327 }}, 328 TxOut: []*wire.TxOut{{ 329 Value: 5000000, 330 PkScript: hexToBytes("76a914a983ad7c92c38fc0e2025212e9f972204c6e687088ac"), 331 }, { 332 Value: 13756000000, 333 PkScript: hexToBytes("76a914a6ebd69952ab486a7a300bfffdcb395dc7d47c2388ac"), 334 }}, 335 LockTime: 0, 336 }}, 337 utxoView: &UtxoViewpoint{entries: map[wire.ShaHash]*UtxoEntry{ 338 *newShaHashFromStr("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"): { 339 version: 1, 340 isCoinBase: false, 341 blockHeight: 100024, 342 sparseOutputs: map[uint32]*utxoOutput{ 343 1: { 344 amount: 34405000000, 345 pkScript: hexToBytes("76a9142084541c3931677527a7eafe56fd90207c344eb088ac"), 346 }, 347 }, 348 }, 349 }}, 350 serialized: hexToBytes("8b99700186c64700b2fb57eadf61e106a100a7445a8c3f67898841ec0091f20f006edbc6c4d31bae9f1ccc38538a114bf42de65e86"), 351 }, 352 // Hand crafted. 353 { 354 name: "One tx, two inputs from same tx, neither spend last output", 355 entry: []spentTxOut{{ 356 amount: 165125632, 357 pkScript: hexToBytes("51"), 358 version: 1, 359 }, { 360 amount: 154370000, 361 pkScript: hexToBytes("51"), 362 version: 1, 363 }}, 364 blockTxns: []*wire.MsgTx{{ // Coinbase omitted. 365 Version: 1, 366 TxIn: []*wire.TxIn{{ 367 PreviousOutPoint: wire.OutPoint{ 368 Hash: *newShaHashFromStr("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"), 369 Index: 1, 370 }, 371 SignatureScript: hexToBytes(""), 372 Sequence: 0xffffffff, 373 }, { 374 PreviousOutPoint: wire.OutPoint{ 375 Hash: *newShaHashFromStr("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"), 376 Index: 2, 377 }, 378 SignatureScript: hexToBytes(""), 379 Sequence: 0xffffffff, 380 }}, 381 TxOut: []*wire.TxOut{{ 382 Value: 165125632, 383 PkScript: hexToBytes("51"), 384 }, { 385 Value: 154370000, 386 PkScript: hexToBytes("51"), 387 }}, 388 LockTime: 0, 389 }}, 390 utxoView: &UtxoViewpoint{entries: map[wire.ShaHash]*UtxoEntry{ 391 *newShaHashFromStr("c0ed017828e59ad5ed3cf70ee7c6fb0f426433047462477dc7a5d470f987a537"): { 392 version: 1, 393 isCoinBase: false, 394 blockHeight: 100000, 395 sparseOutputs: map[uint32]*utxoOutput{ 396 0: { 397 amount: 165712179, 398 pkScript: hexToBytes("51"), 399 }, 400 }, 401 }, 402 }}, 403 serialized: hexToBytes("0087bc3707510084c3d19a790751"), 404 }, 405 } 406 407 for i, test := range tests { 408 // Ensure the journal entry serializes to the expected value. 409 gotBytes := serializeSpendJournalEntry(test.entry) 410 if !bytes.Equal(gotBytes, test.serialized) { 411 t.Errorf("serializeSpendJournalEntry #%d (%s): "+ 412 "mismatched bytes - got %x, want %x", i, 413 test.name, gotBytes, test.serialized) 414 continue 415 } 416 417 // Deserialize to a spend journal entry. 418 gotEntry, err := deserializeSpendJournalEntry(test.serialized, 419 test.blockTxns, test.utxoView) 420 if err != nil { 421 t.Errorf("deserializeSpendJournalEntry #%d (%s) "+ 422 "unexpected error: %v", i, test.name, err) 423 continue 424 } 425 for stxoIdx := range gotEntry { 426 stxo := &gotEntry[stxoIdx] 427 stxo.maybeDecompress(test.entry[stxoIdx].version) 428 } 429 430 // Ensure that the deserialized spend journal entry has the 431 // correct properties. 432 if !reflect.DeepEqual(gotEntry, test.entry) { 433 t.Errorf("deserializeSpendJournalEntry #%d (%s) "+ 434 "mismatched entries - got %v, want %v", 435 i, test.name, gotEntry, test.entry) 436 continue 437 } 438 } 439 } 440 441 // TestSpendJournalErrors performs negative tests against deserializing spend 442 // journal entries to ensure error paths work as expected. 443 func TestSpendJournalErrors(t *testing.T) { 444 t.Parallel() 445 446 tests := []struct { 447 name string 448 blockTxns []*wire.MsgTx 449 utxoView *UtxoViewpoint 450 serialized []byte 451 errType error 452 }{ 453 // Adapted from block 170 in main blockchain. 454 { 455 name: "Force assertion due to missing stxos", 456 blockTxns: []*wire.MsgTx{{ // Coinbase omitted. 457 Version: 1, 458 TxIn: []*wire.TxIn{{ 459 PreviousOutPoint: wire.OutPoint{ 460 Hash: *newShaHashFromStr("0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9"), 461 Index: 0, 462 }, 463 SignatureScript: hexToBytes("47304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901"), 464 Sequence: 0xffffffff, 465 }}, 466 LockTime: 0, 467 }}, 468 utxoView: NewUtxoViewpoint(), 469 serialized: hexToBytes(""), 470 errType: AssertError(""), 471 }, 472 { 473 name: "Force deserialization error in stxos", 474 blockTxns: []*wire.MsgTx{{ // Coinbase omitted. 475 Version: 1, 476 TxIn: []*wire.TxIn{{ 477 PreviousOutPoint: wire.OutPoint{ 478 Hash: *newShaHashFromStr("0437cd7f8525ceed2324359c2d0ba26006d92d856a9c20fa0241106ee5a597c9"), 479 Index: 0, 480 }, 481 SignatureScript: hexToBytes("47304402204e45e16932b8af514961a1d3a1a25fdf3f4f7732e9d624c6c61548ab5fb8cd410220181522ec8eca07de4860a4acdd12909d831cc56cbbac4622082221a8768d1d0901"), 482 Sequence: 0xffffffff, 483 }}, 484 LockTime: 0, 485 }}, 486 utxoView: NewUtxoViewpoint(), 487 serialized: hexToBytes("1301320511db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a"), 488 errType: errDeserialize(""), 489 }, 490 } 491 492 for _, test := range tests { 493 // Ensure the expected error type is returned and the returned 494 // slice is nil. 495 stxos, err := deserializeSpendJournalEntry(test.serialized, 496 test.blockTxns, test.utxoView) 497 if reflect.TypeOf(err) != reflect.TypeOf(test.errType) { 498 t.Errorf("deserializeSpendJournalEntry (%s): expected "+ 499 "error type does not match - got %T, want %T", 500 test.name, err, test.errType) 501 continue 502 } 503 if stxos != nil { 504 t.Errorf("deserializeSpendJournalEntry (%s): returned "+ 505 "slice of spent transaction outputs is not nil", 506 test.name) 507 continue 508 } 509 } 510 } 511 512 // TestUtxoSerialization ensures serializing and deserializing unspent 513 // trasaction output entries works as expected. 514 func TestUtxoSerialization(t *testing.T) { 515 t.Parallel() 516 517 tests := []struct { 518 name string 519 entry *UtxoEntry 520 serialized []byte 521 }{ 522 // From tx in main blockchain: 523 // 0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098 524 { 525 name: "Only output 0, coinbase", 526 entry: &UtxoEntry{ 527 version: 1, 528 isCoinBase: true, 529 blockHeight: 1, 530 sparseOutputs: map[uint32]*utxoOutput{ 531 0: { 532 amount: 5000000000, 533 pkScript: hexToBytes("410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac"), 534 }, 535 }, 536 }, 537 serialized: hexToBytes("010103320496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52"), 538 }, 539 // From tx in main blockchain: 540 // 8131ffb0a2c945ecaf9b9063e59558784f9c3a74741ce6ae2a18d0571dac15bb 541 { 542 name: "Only output 1, not coinbase", 543 entry: &UtxoEntry{ 544 version: 1, 545 isCoinBase: false, 546 blockHeight: 100001, 547 sparseOutputs: map[uint32]*utxoOutput{ 548 1: { 549 amount: 1000000, 550 pkScript: hexToBytes("76a914ee8bd501094a7d5ca318da2506de35e1cb025ddc88ac"), 551 }, 552 }, 553 }, 554 serialized: hexToBytes("01858c21040700ee8bd501094a7d5ca318da2506de35e1cb025ddc"), 555 }, 556 // Adapted from tx in main blockchain: 557 // df3f3f442d9699857f7f49de4ff0b5d0f3448bec31cdc7b5bf6d25f2abd637d5 558 { 559 name: "Only output 2, coinbase", 560 entry: &UtxoEntry{ 561 version: 1, 562 isCoinBase: true, 563 blockHeight: 99004, 564 sparseOutputs: map[uint32]*utxoOutput{ 565 2: { 566 amount: 100937281, 567 pkScript: hexToBytes("76a914da33f77cee27c2a975ed5124d7e4f7f97513510188ac"), 568 }, 569 }, 570 }, 571 serialized: hexToBytes("0185843c010182b095bf4100da33f77cee27c2a975ed5124d7e4f7f975135101"), 572 }, 573 // Adapted from tx in main blockchain: 574 // 4a16969aa4764dd7507fc1de7f0baa4850a246de90c45e59a3207f9a26b5036f 575 { 576 name: "outputs 0 and 2 not coinbase", 577 entry: &UtxoEntry{ 578 version: 1, 579 isCoinBase: false, 580 blockHeight: 113931, 581 sparseOutputs: map[uint32]*utxoOutput{ 582 0: { 583 amount: 20000000, 584 pkScript: hexToBytes("76a914e2ccd6ec7c6e2e581349c77e067385fa8236bf8a88ac"), 585 }, 586 2: { 587 amount: 15000000, 588 pkScript: hexToBytes("76a914b8025be1b3efc63b0ad48e7f9f10e87544528d5888ac"), 589 }, 590 }, 591 }, 592 serialized: hexToBytes("0185f90b0a011200e2ccd6ec7c6e2e581349c77e067385fa8236bf8a800900b8025be1b3efc63b0ad48e7f9f10e87544528d58"), 593 }, 594 // Adapted from tx in main blockchain: 595 // 4a16969aa4764dd7507fc1de7f0baa4850a246de90c45e59a3207f9a26b5036f 596 { 597 name: "outputs 0 and 2, not coinbase, 1 marked spent", 598 entry: &UtxoEntry{ 599 version: 1, 600 isCoinBase: false, 601 blockHeight: 113931, 602 sparseOutputs: map[uint32]*utxoOutput{ 603 0: { 604 amount: 20000000, 605 pkScript: hexToBytes("76a914e2ccd6ec7c6e2e581349c77e067385fa8236bf8a88ac"), 606 }, 607 1: { // This won't be serialized. 608 spent: true, 609 amount: 1000000, 610 pkScript: hexToBytes("76a914e43031c3e46f20bf1ccee9553ce815de5a48467588ac"), 611 }, 612 2: { 613 amount: 15000000, 614 pkScript: hexToBytes("76a914b8025be1b3efc63b0ad48e7f9f10e87544528d5888ac"), 615 }, 616 }, 617 }, 618 serialized: hexToBytes("0185f90b0a011200e2ccd6ec7c6e2e581349c77e067385fa8236bf8a800900b8025be1b3efc63b0ad48e7f9f10e87544528d58"), 619 }, 620 // Adapted from tx in main blockchain: 621 // 4a16969aa4764dd7507fc1de7f0baa4850a246de90c45e59a3207f9a26b5036f 622 { 623 name: "outputs 0 and 2, not coinbase, output 2 compressed", 624 entry: &UtxoEntry{ 625 version: 1, 626 isCoinBase: false, 627 blockHeight: 113931, 628 sparseOutputs: map[uint32]*utxoOutput{ 629 0: { 630 amount: 20000000, 631 pkScript: hexToBytes("76a914e2ccd6ec7c6e2e581349c77e067385fa8236bf8a88ac"), 632 }, 633 2: { 634 // Uncompressed Amount: 15000000 635 // Uncompressed PkScript: 76a914b8025be1b3efc63b0ad48e7f9f10e87544528d5888ac 636 compressed: true, 637 amount: 137, 638 pkScript: hexToBytes("00b8025be1b3efc63b0ad48e7f9f10e87544528d58"), 639 }, 640 }, 641 }, 642 serialized: hexToBytes("0185f90b0a011200e2ccd6ec7c6e2e581349c77e067385fa8236bf8a800900b8025be1b3efc63b0ad48e7f9f10e87544528d58"), 643 }, 644 // Adapted from tx in main blockchain: 645 // 4a16969aa4764dd7507fc1de7f0baa4850a246de90c45e59a3207f9a26b5036f 646 { 647 name: "outputs 0 and 2, not coinbase, output 2 compressed, packed indexes reversed", 648 entry: &UtxoEntry{ 649 version: 1, 650 isCoinBase: false, 651 blockHeight: 113931, 652 sparseOutputs: map[uint32]*utxoOutput{ 653 0: { 654 amount: 20000000, 655 pkScript: hexToBytes("76a914e2ccd6ec7c6e2e581349c77e067385fa8236bf8a88ac"), 656 }, 657 2: { 658 // Uncompressed Amount: 15000000 659 // Uncompressed PkScript: 76a914b8025be1b3efc63b0ad48e7f9f10e87544528d5888ac 660 compressed: true, 661 amount: 137, 662 pkScript: hexToBytes("00b8025be1b3efc63b0ad48e7f9f10e87544528d58"), 663 }, 664 }, 665 }, 666 serialized: hexToBytes("0185f90b0a011200e2ccd6ec7c6e2e581349c77e067385fa8236bf8a800900b8025be1b3efc63b0ad48e7f9f10e87544528d58"), 667 }, 668 // From tx in main blockchain: 669 // 0e3e2357e806b6cdb1f70b54c3a3a17b6714ee1f0e68bebb44a74b1efd512098 670 { 671 name: "Only output 0, coinbase, fully spent", 672 entry: &UtxoEntry{ 673 version: 1, 674 isCoinBase: true, 675 blockHeight: 1, 676 sparseOutputs: map[uint32]*utxoOutput{ 677 0: { 678 spent: true, 679 amount: 5000000000, 680 pkScript: hexToBytes("410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac"), 681 }, 682 }, 683 }, 684 serialized: nil, 685 }, 686 // Adapted from tx in main blockchain: 687 // 1b02d1c8cfef60a189017b9a420c682cf4a0028175f2f563209e4ff61c8c3620 688 { 689 name: "Only output 22, not coinbase", 690 entry: &UtxoEntry{ 691 version: 1, 692 isCoinBase: false, 693 blockHeight: 338156, 694 sparseOutputs: map[uint32]*utxoOutput{ 695 22: { 696 spent: false, 697 amount: 366875659, 698 pkScript: hexToBytes("a9141dd46a006572d820e448e12d2bbb38640bc718e687"), 699 }, 700 }, 701 }, 702 serialized: hexToBytes("0193d06c100000108ba5b9e763011dd46a006572d820e448e12d2bbb38640bc718e6"), 703 }, 704 } 705 706 for i, test := range tests { 707 // Ensure the utxo entry serializes to the expected value. 708 gotBytes, err := serializeUtxoEntry(test.entry) 709 if err != nil { 710 t.Errorf("serializeUtxoEntry #%d (%s) unexpected "+ 711 "error: %v", i, test.name, err) 712 continue 713 } 714 if !bytes.Equal(gotBytes, test.serialized) { 715 t.Errorf("serializeUtxoEntry #%d (%s): mismatched "+ 716 "bytes - got %x, want %x", i, test.name, 717 gotBytes, test.serialized) 718 continue 719 } 720 721 // Don't try to deserialize if the test entry was fully spent 722 // since it will have a nil serialization. 723 if test.entry.IsFullySpent() { 724 continue 725 } 726 727 // Deserialize to a utxo entry. 728 utxoEntry, err := deserializeUtxoEntry(test.serialized) 729 if err != nil { 730 t.Errorf("deserializeUtxoEntry #%d (%s) unexpected "+ 731 "error: %v", i, test.name, err) 732 continue 733 } 734 735 // Ensure that the deserialized utxo entry has the same 736 // properties for the containing transaction and block height. 737 if utxoEntry.Version() != test.entry.Version() { 738 t.Errorf("deserializeUtxoEntry #%d (%s) mismatched "+ 739 "version: got %d, want %d", i, test.name, 740 utxoEntry.Version(), test.entry.Version()) 741 continue 742 } 743 if utxoEntry.IsCoinBase() != test.entry.IsCoinBase() { 744 t.Errorf("deserializeUtxoEntry #%d (%s) mismatched "+ 745 "coinbase flag: got %v, want %v", i, test.name, 746 utxoEntry.IsCoinBase(), test.entry.IsCoinBase()) 747 continue 748 } 749 if utxoEntry.BlockHeight() != test.entry.BlockHeight() { 750 t.Errorf("deserializeUtxoEntry #%d (%s) mismatched "+ 751 "block height: got %d, want %d", i, test.name, 752 utxoEntry.BlockHeight(), 753 test.entry.BlockHeight()) 754 continue 755 } 756 if utxoEntry.IsFullySpent() != test.entry.IsFullySpent() { 757 t.Errorf("deserializeUtxoEntry #%d (%s) mismatched "+ 758 "fully spent: got %v, want %v", i, test.name, 759 utxoEntry.IsFullySpent(), 760 test.entry.IsFullySpent()) 761 continue 762 } 763 764 // Ensure all of the outputs in the test entry match the 765 // spentness of the output in the deserialized entry and the 766 // deserialized entry does not contain any additional utxos. 767 var numUnspent int 768 for outputIndex := range test.entry.sparseOutputs { 769 gotSpent := utxoEntry.IsOutputSpent(outputIndex) 770 wantSpent := test.entry.IsOutputSpent(outputIndex) 771 if !wantSpent { 772 numUnspent++ 773 } 774 if gotSpent != wantSpent { 775 t.Errorf("deserializeUtxoEntry #%d (%s) output "+ 776 "#%d: mismatched spent: got %v, want "+ 777 "%v", i, test.name, outputIndex, 778 gotSpent, wantSpent) 779 continue 780 781 } 782 } 783 if len(utxoEntry.sparseOutputs) != numUnspent { 784 t.Errorf("deserializeUtxoEntry #%d (%s): mismatched "+ 785 "number of unspent outputs: got %d, want %d", i, 786 test.name, len(utxoEntry.sparseOutputs), 787 numUnspent) 788 continue 789 } 790 791 // Ensure all of the amounts and scripts of the utxos in the 792 // deserialized entry match the ones in the test entry. 793 for outputIndex := range utxoEntry.sparseOutputs { 794 gotAmount := utxoEntry.AmountByIndex(outputIndex) 795 wantAmount := test.entry.AmountByIndex(outputIndex) 796 if gotAmount != wantAmount { 797 t.Errorf("deserializeUtxoEntry #%d (%s) "+ 798 "output #%d: mismatched amounts: got "+ 799 "%d, want %d", i, test.name, 800 outputIndex, gotAmount, wantAmount) 801 continue 802 } 803 804 gotPkScript := utxoEntry.PkScriptByIndex(outputIndex) 805 wantPkScript := test.entry.PkScriptByIndex(outputIndex) 806 if !bytes.Equal(gotPkScript, wantPkScript) { 807 t.Errorf("deserializeUtxoEntry #%d (%s) "+ 808 "output #%d mismatched scripts: got "+ 809 "%x, want %x", i, test.name, 810 outputIndex, gotPkScript, wantPkScript) 811 continue 812 } 813 } 814 } 815 } 816 817 // TestUtxoEntryHeaderCodeErrors performs negative tests against unspent 818 // transaction output header codes to ensure error paths work as expected. 819 func TestUtxoEntryHeaderCodeErrors(t *testing.T) { 820 t.Parallel() 821 822 tests := []struct { 823 name string 824 entry *UtxoEntry 825 code uint64 826 bytesRead int // Expected number of bytes read. 827 errType error 828 }{ 829 { 830 name: "Force assertion due to fully spent tx", 831 entry: &UtxoEntry{}, 832 errType: AssertError(""), 833 bytesRead: 0, 834 }, 835 } 836 837 for _, test := range tests { 838 // Ensure the expected error type is returned and the code is 0. 839 code, gotBytesRead, err := utxoEntryHeaderCode(test.entry, 0) 840 if reflect.TypeOf(err) != reflect.TypeOf(test.errType) { 841 t.Errorf("utxoEntryHeaderCode (%s): expected error "+ 842 "type does not match - got %T, want %T", 843 test.name, err, test.errType) 844 continue 845 } 846 if code != 0 { 847 t.Errorf("utxoEntryHeaderCode (%s): unexpected code "+ 848 "on error - got %d, want 0", test.name, code) 849 continue 850 } 851 852 // Ensure the expected number of bytes read is returned. 853 if gotBytesRead != test.bytesRead { 854 t.Errorf("utxoEntryHeaderCode (%s): unexpected number "+ 855 "of bytes read - got %d, want %d", test.name, 856 gotBytesRead, test.bytesRead) 857 continue 858 } 859 } 860 } 861 862 // TestUtxoEntryDeserializeErrors performs negative tests against deserializing 863 // unspent transaction outputs to ensure error paths work as expected. 864 func TestUtxoEntryDeserializeErrors(t *testing.T) { 865 t.Parallel() 866 867 tests := []struct { 868 name string 869 serialized []byte 870 errType error 871 }{ 872 { 873 name: "no data after version", 874 serialized: hexToBytes("01"), 875 errType: errDeserialize(""), 876 }, 877 { 878 name: "no data after block height", 879 serialized: hexToBytes("0101"), 880 errType: errDeserialize(""), 881 }, 882 { 883 name: "no data after header code", 884 serialized: hexToBytes("010102"), 885 errType: errDeserialize(""), 886 }, 887 { 888 name: "not enough bytes for unspentness bitmap", 889 serialized: hexToBytes("01017800"), 890 errType: errDeserialize(""), 891 }, 892 { 893 name: "incomplete compressed txout", 894 serialized: hexToBytes("01010232"), 895 errType: errDeserialize(""), 896 }, 897 } 898 899 for _, test := range tests { 900 // Ensure the expected error type is returned and the returned 901 // entry is nil. 902 entry, err := deserializeUtxoEntry(test.serialized) 903 if reflect.TypeOf(err) != reflect.TypeOf(test.errType) { 904 t.Errorf("deserializeUtxoEntry (%s): expected error "+ 905 "type does not match - got %T, want %T", 906 test.name, err, test.errType) 907 continue 908 } 909 if entry != nil { 910 t.Errorf("deserializeUtxoEntry (%s): returned entry "+ 911 "is not nil", test.name) 912 continue 913 } 914 } 915 } 916 917 // TestBestChainStateSerialization ensures serializing and deserializing the 918 // best chain state works as expected. 919 func TestBestChainStateSerialization(t *testing.T) { 920 t.Parallel() 921 922 workSum := new(big.Int) 923 tests := []struct { 924 name string 925 state bestChainState 926 serialized []byte 927 }{ 928 { 929 name: "genesis", 930 state: bestChainState{ 931 hash: *newShaHashFromStr("000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"), 932 height: 0, 933 totalTxns: 1, 934 workSum: func() *big.Int { 935 workSum.Add(workSum, CalcWork(486604799)) 936 return new(big.Int).Set(workSum) 937 }(), // 0x0100010001 938 }, 939 serialized: hexToBytes("6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000000000000100000000000000050000000100010001"), 940 }, 941 { 942 name: "block 1", 943 state: bestChainState{ 944 hash: *newShaHashFromStr("00000000839a8e6886ab5951d76f411475428afc90947ee320161bbf18eb6048"), 945 height: 1, 946 totalTxns: 2, 947 workSum: func() *big.Int { 948 workSum.Add(workSum, CalcWork(486604799)) 949 return new(big.Int).Set(workSum) 950 }(), // 0x0200020002 951 }, 952 serialized: hexToBytes("4860eb18bf1b1620e37e9490fc8a427514416fd75159ab86688e9a8300000000010000000200000000000000050000000200020002"), 953 }, 954 } 955 956 for i, test := range tests { 957 // Ensure the state serializes to the expected value. 958 gotBytes := serializeBestChainState(test.state) 959 if !bytes.Equal(gotBytes, test.serialized) { 960 t.Errorf("serializeBestChainState #%d (%s): mismatched "+ 961 "bytes - got %x, want %x", i, test.name, 962 gotBytes, test.serialized) 963 continue 964 } 965 966 // Ensure the serialized bytes are decoded back to the expected 967 // state. 968 state, err := deserializeBestChainState(test.serialized) 969 if err != nil { 970 t.Errorf("deserializeBestChainState #%d (%s) "+ 971 "unexpected error: %v", i, test.name, err) 972 continue 973 } 974 if !reflect.DeepEqual(state, test.state) { 975 t.Errorf("deserializeBestChainState #%d (%s) "+ 976 "mismatched state - got %v, want %v", i, 977 test.name, state, test.state) 978 continue 979 980 } 981 } 982 } 983 984 // TestBestChainStateDeserializeErrors performs negative tests against 985 // deserializing the chain state to ensure error paths work as expected. 986 func TestBestChainStateDeserializeErrors(t *testing.T) { 987 t.Parallel() 988 989 tests := []struct { 990 name string 991 serialized []byte 992 errType error 993 }{ 994 { 995 name: "nothing serialized", 996 serialized: hexToBytes(""), 997 errType: database.Error{ErrorCode: database.ErrCorruption}, 998 }, 999 { 1000 name: "short data in hash", 1001 serialized: hexToBytes("0000"), 1002 errType: database.Error{ErrorCode: database.ErrCorruption}, 1003 }, 1004 { 1005 name: "short data in work sum", 1006 serialized: hexToBytes("6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d61900000000000000000001000000000000000500000001000100"), 1007 errType: database.Error{ErrorCode: database.ErrCorruption}, 1008 }, 1009 } 1010 1011 for _, test := range tests { 1012 // Ensure the expected error type and code is returned. 1013 _, err := deserializeBestChainState(test.serialized) 1014 if reflect.TypeOf(err) != reflect.TypeOf(test.errType) { 1015 t.Errorf("deserializeBestChainState (%s): expected "+ 1016 "error type does not match - got %T, want %T", 1017 test.name, err, test.errType) 1018 continue 1019 } 1020 if derr, ok := err.(database.Error); ok { 1021 tderr := test.errType.(database.Error) 1022 if derr.ErrorCode != tderr.ErrorCode { 1023 t.Errorf("deserializeBestChainState (%s): "+ 1024 "wrong error code got: %v, want: %v", 1025 test.name, derr.ErrorCode, 1026 tderr.ErrorCode) 1027 continue 1028 } 1029 } 1030 } 1031 }