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