github.com/BlockABC/godash@v0.0.0-20191112120524-f4aa3a32c566/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  }