github.com/dashpay/godash@v0.0.0-20160726055534-e038a21e0e3d/blockchain/compress_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 "encoding/hex" 11 "testing" 12 ) 13 14 // hexToBytes converts the passed hex string into bytes and will panic if there 15 // is an error. This is only provided for the hard-coded constants so errors in 16 // the source code can be detected. It will only (and must only) be called with 17 // hard-coded values. 18 func hexToBytes(s string) []byte { 19 b, err := hex.DecodeString(s) 20 if err != nil { 21 panic("invalid hex in source file: " + s) 22 } 23 return b 24 } 25 26 // TestVLQ ensures the variable length quantity serialization, deserialization, 27 // and size calculation works as expected. 28 func TestVLQ(t *testing.T) { 29 t.Parallel() 30 31 tests := []struct { 32 val uint64 33 serialized []byte 34 }{ 35 {0, hexToBytes("00")}, 36 {1, hexToBytes("01")}, 37 {127, hexToBytes("7f")}, 38 {128, hexToBytes("8000")}, 39 {129, hexToBytes("8001")}, 40 {255, hexToBytes("807f")}, 41 {256, hexToBytes("8100")}, 42 {16383, hexToBytes("fe7f")}, 43 {16384, hexToBytes("ff00")}, 44 {16511, hexToBytes("ff7f")}, // Max 2-byte value 45 {16512, hexToBytes("808000")}, 46 {16513, hexToBytes("808001")}, 47 {16639, hexToBytes("80807f")}, 48 {32895, hexToBytes("80ff7f")}, 49 {2113663, hexToBytes("ffff7f")}, // Max 3-byte value 50 {2113664, hexToBytes("80808000")}, 51 {270549119, hexToBytes("ffffff7f")}, // Max 4-byte value 52 {270549120, hexToBytes("8080808000")}, 53 {2147483647, hexToBytes("86fefefe7f")}, 54 {2147483648, hexToBytes("86fefeff00")}, 55 {4294967295, hexToBytes("8efefefe7f")}, // Max uint32, 5 bytes 56 // Max uint64, 10 bytes 57 {18446744073709551615, hexToBytes("80fefefefefefefefe7f")}, 58 } 59 60 for _, test := range tests { 61 // Ensure the function to calculate the serialized size without 62 // actually serializing the value is calculated properly. 63 gotSize := serializeSizeVLQ(test.val) 64 if gotSize != len(test.serialized) { 65 t.Errorf("serializeSizeVLQ: did not get expected size "+ 66 "for %d - got %d, want %d", test.val, gotSize, 67 len(test.serialized)) 68 continue 69 } 70 71 // Ensure the value serializes to the expected bytes. 72 gotBytes := make([]byte, gotSize) 73 gotBytesWritten := putVLQ(gotBytes, test.val) 74 if !bytes.Equal(gotBytes, test.serialized) { 75 t.Errorf("putVLQUnchecked: did not get expected bytes "+ 76 "for %d - got %x, want %x", test.val, gotBytes, 77 test.serialized) 78 continue 79 } 80 if gotBytesWritten != len(test.serialized) { 81 t.Errorf("putVLQUnchecked: did not get expected number "+ 82 "of bytes written for %d - got %d, want %d", 83 test.val, gotBytesWritten, len(test.serialized)) 84 continue 85 } 86 87 // Ensure the serialized bytes deserialize to the expected 88 // value. 89 gotVal, gotBytesRead := deserializeVLQ(test.serialized) 90 if gotVal != test.val { 91 t.Errorf("deserializeVLQ: did not get expected value "+ 92 "for %x - got %d, want %d", test.serialized, 93 gotVal, test.val) 94 continue 95 } 96 if gotBytesRead != len(test.serialized) { 97 t.Errorf("deserializeVLQ: did not get expected number "+ 98 "of bytes read for %d - got %d, want %d", 99 test.serialized, gotBytesRead, 100 len(test.serialized)) 101 continue 102 } 103 } 104 } 105 106 // TestScriptCompression ensures the domain-specific script compression and 107 // decompression works as expected. 108 func TestScriptCompression(t *testing.T) { 109 t.Parallel() 110 111 tests := []struct { 112 name string 113 version int32 114 uncompressed []byte 115 compressed []byte 116 }{ 117 { 118 name: "nil", 119 version: 1, 120 uncompressed: nil, 121 compressed: hexToBytes("06"), 122 }, 123 { 124 name: "pay-to-pubkey-hash 1", 125 version: 1, 126 uncompressed: hexToBytes("76a9141018853670f9f3b0582c5b9ee8ce93764ac32b9388ac"), 127 compressed: hexToBytes("001018853670f9f3b0582c5b9ee8ce93764ac32b93"), 128 }, 129 { 130 name: "pay-to-pubkey-hash 2", 131 version: 1, 132 uncompressed: hexToBytes("76a914e34cce70c86373273efcc54ce7d2a491bb4a0e8488ac"), 133 compressed: hexToBytes("00e34cce70c86373273efcc54ce7d2a491bb4a0e84"), 134 }, 135 { 136 name: "pay-to-script-hash 1", 137 version: 1, 138 uncompressed: hexToBytes("a914da1745e9b549bd0bfa1a569971c77eba30cd5a4b87"), 139 compressed: hexToBytes("01da1745e9b549bd0bfa1a569971c77eba30cd5a4b"), 140 }, 141 { 142 name: "pay-to-script-hash 2", 143 version: 1, 144 uncompressed: hexToBytes("a914f815b036d9bbbce5e9f2a00abd1bf3dc91e9551087"), 145 compressed: hexToBytes("01f815b036d9bbbce5e9f2a00abd1bf3dc91e95510"), 146 }, 147 { 148 name: "pay-to-pubkey compressed 0x02", 149 version: 1, 150 uncompressed: hexToBytes("2102192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b4ac"), 151 compressed: hexToBytes("02192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b4"), 152 }, 153 { 154 name: "pay-to-pubkey compressed 0x03", 155 version: 1, 156 uncompressed: hexToBytes("2103b0bd634234abbb1ba1e986e884185c61cf43e001f9137f23c2c409273eb16e65ac"), 157 compressed: hexToBytes("03b0bd634234abbb1ba1e986e884185c61cf43e001f9137f23c2c409273eb16e65"), 158 }, 159 { 160 name: "pay-to-pubkey uncompressed 0x04 even", 161 version: 1, 162 uncompressed: hexToBytes("4104192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b40d45264838c0bd96852662ce6a847b197376830160c6d2eb5e6a4c44d33f453eac"), 163 compressed: hexToBytes("04192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b4"), 164 }, 165 { 166 name: "pay-to-pubkey uncompressed 0x04 odd", 167 version: 1, 168 uncompressed: hexToBytes("410411db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5cb2e0eaddfb84ccf9744464f82e160bfa9b8b64f9d4c03f999b8643f656b412a3ac"), 169 compressed: hexToBytes("0511db93e1dcdb8a016b49840f8c53bc1eb68a382e97b1482ecad7b148a6909a5c"), 170 }, 171 { 172 name: "pay-to-pubkey invalid pubkey", 173 version: 1, 174 uncompressed: hexToBytes("3302aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac"), 175 compressed: hexToBytes("293302aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac"), 176 }, 177 { 178 name: "null data", 179 version: 1, 180 uncompressed: hexToBytes("6a200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"), 181 compressed: hexToBytes("286a200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"), 182 }, 183 { 184 name: "requires 2 size bytes - data push 200 bytes", 185 version: 1, 186 uncompressed: append(hexToBytes("4cc8"), bytes.Repeat([]byte{0x00}, 200)...), 187 // [0x80, 0x50] = 208 as a variable length quantity 188 // [0x4c, 0xc8] = OP_PUSHDATA1 200 189 compressed: append(hexToBytes("80504cc8"), bytes.Repeat([]byte{0x00}, 200)...), 190 }, 191 } 192 193 for _, test := range tests { 194 // Ensure the function to calculate the serialized size without 195 // actually serializing the value is calculated properly. 196 gotSize := compressedScriptSize(test.uncompressed, test.version) 197 if gotSize != len(test.compressed) { 198 t.Errorf("compressedScriptSize (%s): did not get "+ 199 "expected size - got %d, want %d", test.name, 200 gotSize, len(test.compressed)) 201 continue 202 } 203 204 // Ensure the script compresses to the expected bytes. 205 gotCompressed := make([]byte, gotSize) 206 gotBytesWritten := putCompressedScript(gotCompressed, 207 test.uncompressed, test.version) 208 if !bytes.Equal(gotCompressed, test.compressed) { 209 t.Errorf("putCompressedScript (%s): did not get "+ 210 "expected bytes - got %x, want %x", test.name, 211 gotCompressed, test.compressed) 212 continue 213 } 214 if gotBytesWritten != len(test.compressed) { 215 t.Errorf("putCompressedScript (%s): did not get "+ 216 "expected number of bytes written - got %d, "+ 217 "want %d", test.name, gotBytesWritten, 218 len(test.compressed)) 219 continue 220 } 221 222 // Ensure the compressed script size is properly decoded from 223 // the compressed script. 224 gotDecodedSize := decodeCompressedScriptSize(test.compressed, 225 test.version) 226 if gotDecodedSize != len(test.compressed) { 227 t.Errorf("decodeCompressedScriptSize (%s): did not get "+ 228 "expected size - got %d, want %d", test.name, 229 gotDecodedSize, len(test.compressed)) 230 continue 231 } 232 233 // Ensure the script decompresses to the expected bytes. 234 gotDecompressed := decompressScript(test.compressed, test.version) 235 if !bytes.Equal(gotDecompressed, test.uncompressed) { 236 t.Errorf("decompressScript (%s): did not get expected "+ 237 "bytes - got %x, want %x", test.name, 238 gotDecompressed, test.uncompressed) 239 continue 240 } 241 } 242 } 243 244 // TestScriptCompressionErrors ensures calling various functions related to 245 // script compression with incorrect data returns the expected results. 246 func TestScriptCompressionErrors(t *testing.T) { 247 t.Parallel() 248 249 // A nil script must result in a decoded size of 0. 250 if gotSize := decodeCompressedScriptSize(nil, 1); gotSize != 0 { 251 t.Fatalf("decodeCompressedScriptSize with nil script did not "+ 252 "return 0 - got %d", gotSize) 253 } 254 255 // A nil script must result in a nil decompressed script. 256 if gotScript := decompressScript(nil, 1); gotScript != nil { 257 t.Fatalf("decompressScript with nil script did not return nil "+ 258 "decompressed script - got %x", gotScript) 259 } 260 261 // A compressed script for a pay-to-pubkey (uncompressed) that results 262 // in an invalid pubkey must result in a nil decompressed script. 263 compressedScript := hexToBytes("04012d74d0cb94344c9569c2e77901573d8d" + 264 "7903c3ebec3a957724895dca52c6b4") 265 if gotScript := decompressScript(compressedScript, 1); gotScript != nil { 266 t.Fatalf("decompressScript with compressed pay-to-"+ 267 "uncompressed-pubkey that is invalid did not return "+ 268 "nil decompressed script - got %x", gotScript) 269 } 270 } 271 272 // TestAmountCompression ensures the domain-specific transaction output amount 273 // compression and decompression works as expected. 274 func TestAmountCompression(t *testing.T) { 275 t.Parallel() 276 277 tests := []struct { 278 name string 279 uncompressed uint64 280 compressed uint64 281 }{ 282 { 283 name: "0 BTC (sometimes used in nulldata)", 284 uncompressed: 0, 285 compressed: 0, 286 }, 287 { 288 name: "546 Satoshi (current network dust value)", 289 uncompressed: 546, 290 compressed: 4911, 291 }, 292 { 293 name: "0.00001 BTC (typical transaction fee)", 294 uncompressed: 1000, 295 compressed: 4, 296 }, 297 { 298 name: "0.0001 BTC (typical transaction fee)", 299 uncompressed: 10000, 300 compressed: 5, 301 }, 302 { 303 name: "0.12345678 BTC", 304 uncompressed: 12345678, 305 compressed: 111111101, 306 }, 307 { 308 name: "0.5 BTC", 309 uncompressed: 50000000, 310 compressed: 48, 311 }, 312 { 313 name: "1 BTC", 314 uncompressed: 100000000, 315 compressed: 9, 316 }, 317 { 318 name: "5 BTC", 319 uncompressed: 500000000, 320 compressed: 49, 321 }, 322 { 323 name: "21000000 BTC (max minted coins)", 324 uncompressed: 2100000000000000, 325 compressed: 21000000, 326 }, 327 } 328 329 for _, test := range tests { 330 // Ensure the amount compresses to the expected value. 331 gotCompressed := compressTxOutAmount(test.uncompressed) 332 if gotCompressed != test.compressed { 333 t.Errorf("compressTxOutAmount (%s): did not get "+ 334 "expected value - got %d, want %d", test.name, 335 gotCompressed, test.compressed) 336 continue 337 } 338 339 // Ensure the value decompresses to the expected value. 340 gotDecompressed := decompressTxOutAmount(test.compressed) 341 if gotDecompressed != test.uncompressed { 342 t.Errorf("decompressTxOutAmount (%s): did not get "+ 343 "expected value - got %d, want %d", test.name, 344 gotDecompressed, test.uncompressed) 345 continue 346 } 347 } 348 } 349 350 // TestCompressedTxOut ensures the transaction output serialization and 351 // deserialization works as expected. 352 func TestCompressedTxOut(t *testing.T) { 353 t.Parallel() 354 355 tests := []struct { 356 name string 357 amount uint64 358 compAmount uint64 359 pkScript []byte 360 compPkScript []byte 361 version int32 362 compressed []byte 363 }{ 364 { 365 name: "nulldata with 0 BTC", 366 amount: 0, 367 compAmount: 0, 368 pkScript: hexToBytes("6a200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"), 369 compPkScript: hexToBytes("286a200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"), 370 version: 1, 371 compressed: hexToBytes("00286a200102030405060708090a0b0c0d0e0f101112131415161718191a1b1c1d1e1f20"), 372 }, 373 { 374 name: "pay-to-pubkey-hash dust", 375 amount: 546, 376 compAmount: 4911, 377 pkScript: hexToBytes("76a9141018853670f9f3b0582c5b9ee8ce93764ac32b9388ac"), 378 compPkScript: hexToBytes("001018853670f9f3b0582c5b9ee8ce93764ac32b93"), 379 version: 1, 380 compressed: hexToBytes("a52f001018853670f9f3b0582c5b9ee8ce93764ac32b93"), 381 }, 382 { 383 name: "pay-to-pubkey uncompressed 1 BTC", 384 amount: 100000000, 385 compAmount: 9, 386 pkScript: hexToBytes("4104192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b40d45264838c0bd96852662ce6a847b197376830160c6d2eb5e6a4c44d33f453eac"), 387 compPkScript: hexToBytes("04192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b4"), 388 version: 1, 389 compressed: hexToBytes("0904192d74d0cb94344c9569c2e77901573d8d7903c3ebec3a957724895dca52c6b4"), 390 }, 391 } 392 393 for _, test := range tests { 394 // Ensure the function to calculate the serialized size without 395 // actually serializing the txout is calculated properly. 396 gotSize := compressedTxOutSize(test.amount, test.pkScript, 397 test.version, false) 398 if gotSize != len(test.compressed) { 399 t.Errorf("compressedTxOutSize (%s): did not get "+ 400 "expected size - got %d, want %d", test.name, 401 gotSize, len(test.compressed)) 402 continue 403 } 404 405 // Ensure the txout compresses to the expected value. 406 gotCompressed := make([]byte, gotSize) 407 gotBytesWritten := putCompressedTxOut(gotCompressed, 408 test.amount, test.pkScript, test.version, false) 409 if !bytes.Equal(gotCompressed, test.compressed) { 410 t.Errorf("compressTxOut (%s): did not get expected "+ 411 "bytes - got %x, want %x", test.name, 412 gotCompressed, test.compressed) 413 continue 414 } 415 if gotBytesWritten != len(test.compressed) { 416 t.Errorf("compressTxOut (%s): did not get expected "+ 417 "number of bytes written - got %d, want %d", 418 test.name, gotBytesWritten, 419 len(test.compressed)) 420 continue 421 } 422 423 // Ensure the serialized bytes are decoded back to the expected 424 // compressed values. 425 gotAmount, gotScript, gotBytesRead, err := decodeCompressedTxOut( 426 test.compressed, test.version) 427 if err != nil { 428 t.Errorf("decodeCompressedTxOut (%s): unexpected "+ 429 "error: %v", test.name, err) 430 continue 431 } 432 if gotAmount != test.compAmount { 433 t.Errorf("decodeCompressedTxOut (%s): did not get "+ 434 "expected amount - got %d, want %d", 435 test.name, gotAmount, test.compAmount) 436 continue 437 } 438 if !bytes.Equal(gotScript, test.compPkScript) { 439 t.Errorf("decodeCompressedTxOut (%s): did not get "+ 440 "expected script - got %x, want %x", 441 test.name, gotScript, test.compPkScript) 442 continue 443 } 444 if gotBytesRead != len(test.compressed) { 445 t.Errorf("decodeCompressedTxOut (%s): did not get "+ 446 "expected number of bytes read - got %d, want %d", 447 test.name, gotBytesRead, len(test.compressed)) 448 continue 449 } 450 451 // Ensure the compressed values decompress to the expected 452 // txout. 453 gotAmount = decompressTxOutAmount(gotAmount) 454 if gotAmount != test.amount { 455 t.Errorf("decompressTxOut (%s): did not get expected "+ 456 "value - got %d, want %d", test.name, gotAmount, 457 test.amount) 458 continue 459 } 460 gotScript = decompressScript(gotScript, test.version) 461 if !bytes.Equal(gotScript, test.pkScript) { 462 t.Errorf("decompressTxOut (%s): did not get expected "+ 463 "script - got %x, want %x", test.name, 464 gotScript, test.pkScript) 465 continue 466 } 467 } 468 } 469 470 // TestTxOutCompressionErrors ensures calling various functions related to 471 // txout compression with incorrect data returns the expected results. 472 func TestTxOutCompressionErrors(t *testing.T) { 473 t.Parallel() 474 475 // A compressed txout with missing compressed script must error. 476 compressedTxOut := hexToBytes("00") 477 _, _, _, err := decodeCompressedTxOut(compressedTxOut, 1) 478 if !isDeserializeErr(err) { 479 t.Fatalf("decodeCompressedTxOut with missing compressed script "+ 480 "did not return expected error type - got %T, want "+ 481 "errDeserialize", err) 482 } 483 484 // A compressed txout with short compressed script must error. 485 compressedTxOut = hexToBytes("0010") 486 _, _, _, err = decodeCompressedTxOut(compressedTxOut, 1) 487 if !isDeserializeErr(err) { 488 t.Fatalf("decodeCompressedTxOut with short compressed script "+ 489 "did not return expected error type - got %T, want "+ 490 "errDeserialize", err) 491 } 492 }