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  }