github.com/btcsuite/btcd@v0.24.0/txscript/bench_test.go (about)

     1  // Copyright (c) 2018-2019 The Decred 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 txscript
     6  
     7  import (
     8  	"bytes"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"testing"
    12  
    13  	"github.com/btcsuite/btcd/chaincfg"
    14  	"github.com/btcsuite/btcd/wire"
    15  )
    16  
    17  var (
    18  	// manyInputsBenchTx is a transaction that contains a lot of inputs which is
    19  	// useful for benchmarking signature hash calculation.
    20  	manyInputsBenchTx wire.MsgTx
    21  
    22  	// A mock previous output script to use in the signing benchmark.
    23  	prevOutScript = hexToBytes("a914f5916158e3e2c4551c1796708db8367207ed13bb87")
    24  )
    25  
    26  func init() {
    27  	// tx 620f57c92cf05a7f7e7f7d28255d5f7089437bc48e34dcfebf7751d08b7fb8f5
    28  	txHex, err := ioutil.ReadFile("data/many_inputs_tx.hex")
    29  	if err != nil {
    30  		panic(fmt.Sprintf("unable to read benchmark tx file: %v", err))
    31  	}
    32  
    33  	txBytes := hexToBytes(string(txHex))
    34  	err = manyInputsBenchTx.Deserialize(bytes.NewReader(txBytes))
    35  	if err != nil {
    36  		panic(err)
    37  	}
    38  }
    39  
    40  // BenchmarkCalcSigHash benchmarks how long it takes to calculate the signature
    41  // hashes for all inputs of a transaction with many inputs.
    42  func BenchmarkCalcSigHash(b *testing.B) {
    43  	b.ReportAllocs()
    44  	for i := 0; i < b.N; i++ {
    45  		for j := 0; j < len(manyInputsBenchTx.TxIn); j++ {
    46  			_, err := CalcSignatureHash(prevOutScript, SigHashAll,
    47  				&manyInputsBenchTx, j)
    48  			if err != nil {
    49  				b.Fatalf("failed to calc signature hash: %v", err)
    50  			}
    51  		}
    52  	}
    53  }
    54  
    55  // BenchmarkCalcWitnessSigHash benchmarks how long it takes to calculate the
    56  // witness signature hashes for all inputs of a transaction with many inputs.
    57  func BenchmarkCalcWitnessSigHash(b *testing.B) {
    58  	prevOutFetcher := NewCannedPrevOutputFetcher(prevOutScript, 5)
    59  	sigHashes := NewTxSigHashes(&manyInputsBenchTx, prevOutFetcher)
    60  
    61  	b.ResetTimer()
    62  	b.ReportAllocs()
    63  	for i := 0; i < b.N; i++ {
    64  		for j := 0; j < len(manyInputsBenchTx.TxIn); j++ {
    65  			_, err := CalcWitnessSigHash(
    66  				prevOutScript, sigHashes, SigHashAll,
    67  				&manyInputsBenchTx, j, 5,
    68  			)
    69  			if err != nil {
    70  				b.Fatalf("failed to calc signature hash: %v", err)
    71  			}
    72  		}
    73  	}
    74  }
    75  
    76  // genComplexScript returns a script comprised of half as many opcodes as the
    77  // maximum allowed followed by as many max size data pushes fit without
    78  // exceeding the max allowed script size.
    79  func genComplexScript() ([]byte, error) {
    80  	var scriptLen int
    81  	builder := NewScriptBuilder()
    82  	for i := 0; i < MaxOpsPerScript/2; i++ {
    83  		builder.AddOp(OP_TRUE)
    84  		scriptLen++
    85  	}
    86  	maxData := bytes.Repeat([]byte{0x02}, MaxScriptElementSize)
    87  	for i := 0; i < (MaxScriptSize-scriptLen)/(MaxScriptElementSize+3); i++ {
    88  		builder.AddData(maxData)
    89  	}
    90  	return builder.Script()
    91  }
    92  
    93  // BenchmarkScriptParsing benchmarks how long it takes to parse a very large
    94  // script.
    95  func BenchmarkScriptParsing(b *testing.B) {
    96  	script, err := genComplexScript()
    97  	if err != nil {
    98  		b.Fatalf("failed to create benchmark script: %v", err)
    99  	}
   100  
   101  	const scriptVersion = 0
   102  	b.ResetTimer()
   103  	b.ReportAllocs()
   104  	for i := 0; i < b.N; i++ {
   105  		tokenizer := MakeScriptTokenizer(scriptVersion, script)
   106  		for tokenizer.Next() {
   107  			_ = tokenizer.Opcode()
   108  			_ = tokenizer.Data()
   109  			_ = tokenizer.ByteIndex()
   110  		}
   111  		if err := tokenizer.Err(); err != nil {
   112  			b.Fatalf("failed to parse script: %v", err)
   113  		}
   114  	}
   115  }
   116  
   117  // BenchmarkDisasmString benchmarks how long it takes to disassemble a very
   118  // large script.
   119  func BenchmarkDisasmString(b *testing.B) {
   120  	script, err := genComplexScript()
   121  	if err != nil {
   122  		b.Fatalf("failed to create benchmark script: %v", err)
   123  	}
   124  
   125  	b.ResetTimer()
   126  	b.ReportAllocs()
   127  	for i := 0; i < b.N; i++ {
   128  		_, err := DisasmString(script)
   129  		if err != nil {
   130  			b.Fatalf("failed to disasm script: %v", err)
   131  		}
   132  	}
   133  }
   134  
   135  // BenchmarkIsPubKeyScript benchmarks how long it takes to analyze a very large
   136  // script to determine if it is a standard pay-to-pubkey script.
   137  func BenchmarkIsPubKeyScript(b *testing.B) {
   138  	script, err := genComplexScript()
   139  	if err != nil {
   140  		b.Fatalf("failed to create benchmark script: %v", err)
   141  	}
   142  
   143  	b.ResetTimer()
   144  	b.ReportAllocs()
   145  	for i := 0; i < b.N; i++ {
   146  		_ = IsPayToPubKey(script)
   147  	}
   148  }
   149  
   150  // BenchmarkIsPubKeyHashScript benchmarks how long it takes to analyze a very
   151  // large script to determine if it is a standard pay-to-pubkey-hash script.
   152  func BenchmarkIsPubKeyHashScript(b *testing.B) {
   153  	script, err := genComplexScript()
   154  	if err != nil {
   155  		b.Fatalf("failed to create benchmark script: %v", err)
   156  	}
   157  
   158  	b.ResetTimer()
   159  	b.ReportAllocs()
   160  	for i := 0; i < b.N; i++ {
   161  		_ = IsPayToPubKeyHash(script)
   162  	}
   163  }
   164  
   165  // BenchmarkIsPayToScriptHash benchmarks how long it takes IsPayToScriptHash to
   166  // analyze a very large script.
   167  func BenchmarkIsPayToScriptHash(b *testing.B) {
   168  	script, err := genComplexScript()
   169  	if err != nil {
   170  		b.Fatalf("failed to create benchmark script: %v", err)
   171  	}
   172  
   173  	b.ResetTimer()
   174  	b.ReportAllocs()
   175  	for i := 0; i < b.N; i++ {
   176  		_ = IsPayToScriptHash(script)
   177  	}
   178  }
   179  
   180  // BenchmarkIsMultisigScriptLarge benchmarks how long it takes IsMultisigScript
   181  // to analyze a very large script.
   182  func BenchmarkIsMultisigScriptLarge(b *testing.B) {
   183  	script, err := genComplexScript()
   184  	if err != nil {
   185  		b.Fatalf("failed to create benchmark script: %v", err)
   186  	}
   187  
   188  	b.ResetTimer()
   189  	b.ReportAllocs()
   190  	for i := 0; i < b.N; i++ {
   191  		isMultisig, err := IsMultisigScript(script)
   192  		if err != nil {
   193  			b.Fatalf("unexpected err: %v", err)
   194  		}
   195  		if isMultisig {
   196  			b.Fatalf("script should NOT be reported as mutisig script")
   197  		}
   198  	}
   199  }
   200  
   201  // BenchmarkIsMultisigScript benchmarks how long it takes IsMultisigScript to
   202  // analyze a 1-of-2 multisig public key script.
   203  func BenchmarkIsMultisigScript(b *testing.B) {
   204  	multisigShortForm := "1 " +
   205  		"DATA_33 " +
   206  		"0x030478aaaa2be30772f1e69e581610f1840b3cf2fe7228ee0281cd599e5746f81e " +
   207  		"DATA_33 " +
   208  		"0x0284f4d078b236a9ff91661f8ffbe012737cd3507566f30fd97d25f2b23539f3cd " +
   209  		"2 CHECKMULTISIG"
   210  	pkScript := mustParseShortForm(multisigShortForm)
   211  
   212  	b.ResetTimer()
   213  	b.ReportAllocs()
   214  	for i := 0; i < b.N; i++ {
   215  		isMultisig, err := IsMultisigScript(pkScript)
   216  		if err != nil {
   217  			b.Fatalf("unexpected err: %v", err)
   218  		}
   219  		if !isMultisig {
   220  			b.Fatalf("script should be reported as a mutisig script")
   221  		}
   222  	}
   223  }
   224  
   225  // BenchmarkIsMultisigSigScript benchmarks how long it takes IsMultisigSigScript
   226  // to analyze a very large script.
   227  func BenchmarkIsMultisigSigScriptLarge(b *testing.B) {
   228  	script, err := genComplexScript()
   229  	if err != nil {
   230  		b.Fatalf("failed to create benchmark script: %v", err)
   231  	}
   232  
   233  	b.ResetTimer()
   234  	b.ReportAllocs()
   235  	for i := 0; i < b.N; i++ {
   236  		if IsMultisigSigScript(script) {
   237  			b.Fatalf("script should NOT be reported as mutisig sig script")
   238  		}
   239  	}
   240  }
   241  
   242  // BenchmarkIsMultisigSigScript benchmarks how long it takes IsMultisigSigScript
   243  // to analyze both a 1-of-2 multisig public key script (which should be false)
   244  // and a signature script comprised of a pay-to-script-hash 1-of-2 multisig
   245  // redeem script (which should be true).
   246  func BenchmarkIsMultisigSigScript(b *testing.B) {
   247  	multisigShortForm := "1 " +
   248  		"DATA_33 " +
   249  		"0x030478aaaa2be30772f1e69e581610f1840b3cf2fe7228ee0281cd599e5746f81e " +
   250  		"DATA_33 " +
   251  		"0x0284f4d078b236a9ff91661f8ffbe012737cd3507566f30fd97d25f2b23539f3cd " +
   252  		"2 CHECKMULTISIG"
   253  	pkScript := mustParseShortForm(multisigShortForm)
   254  
   255  	sigHex := "0x304402205795c3ab6ba11331eeac757bf1fc9c34bef0c7e1a9c8bd5eebb8" +
   256  		"82f3b79c5838022001e0ab7b4c7662e4522dc5fa479e4b4133fa88c6a53d895dc1d5" +
   257  		"2eddc7bbcf2801 "
   258  	sigScript := mustParseShortForm("DATA_71 " + sigHex + "DATA_71 " +
   259  		multisigShortForm)
   260  
   261  	b.ResetTimer()
   262  	b.ReportAllocs()
   263  	for i := 0; i < b.N; i++ {
   264  		if IsMultisigSigScript(pkScript) {
   265  			b.Fatalf("script should NOT be reported as mutisig sig script")
   266  		}
   267  		if !IsMultisigSigScript(sigScript) {
   268  			b.Fatalf("script should be reported as a mutisig sig script")
   269  		}
   270  	}
   271  }
   272  
   273  // BenchmarkIsPushOnlyScript benchmarks how long it takes IsPushOnlyScript to
   274  // analyze a very large script.
   275  func BenchmarkIsPushOnlyScript(b *testing.B) {
   276  	script, err := genComplexScript()
   277  	if err != nil {
   278  		b.Fatalf("failed to create benchmark script: %v", err)
   279  	}
   280  
   281  	b.ResetTimer()
   282  	b.ReportAllocs()
   283  	for i := 0; i < b.N; i++ {
   284  		_ = IsPushOnlyScript(script)
   285  	}
   286  }
   287  
   288  // BenchmarkIsWitnessPubKeyHash benchmarks how long it takes to analyze a very
   289  // large script to determine if it is a standard witness pubkey hash script.
   290  func BenchmarkIsWitnessPubKeyHash(b *testing.B) {
   291  	script, err := genComplexScript()
   292  	if err != nil {
   293  		b.Fatalf("failed to create benchmark script: %v", err)
   294  	}
   295  
   296  	b.ResetTimer()
   297  	b.ReportAllocs()
   298  	for i := 0; i < b.N; i++ {
   299  		_ = IsPayToWitnessPubKeyHash(script)
   300  	}
   301  }
   302  
   303  // BenchmarkIsWitnessScriptHash benchmarks how long it takes to analyze a very
   304  // large script to determine if it is a standard witness script hash script.
   305  func BenchmarkIsWitnessScriptHash(b *testing.B) {
   306  	script, err := genComplexScript()
   307  	if err != nil {
   308  		b.Fatalf("failed to create benchmark script: %v", err)
   309  	}
   310  
   311  	b.ResetTimer()
   312  	b.ReportAllocs()
   313  	for i := 0; i < b.N; i++ {
   314  		_ = IsPayToWitnessScriptHash(script)
   315  	}
   316  }
   317  
   318  // BenchmarkIsNullDataScript benchmarks how long it takes to analyze a very
   319  // large script to determine if it is a standard nulldata script.
   320  func BenchmarkIsNullDataScript(b *testing.B) {
   321  	script, err := genComplexScript()
   322  	if err != nil {
   323  		b.Fatalf("failed to create benchmark script: %v", err)
   324  	}
   325  
   326  	b.ResetTimer()
   327  	b.ReportAllocs()
   328  	for i := 0; i < b.N; i++ {
   329  		_ = IsNullData(script)
   330  	}
   331  }
   332  
   333  // BenchmarkIsUnspendable benchmarks how long it takes IsUnspendable to analyze
   334  // a very large script.
   335  func BenchmarkIsUnspendable(b *testing.B) {
   336  	script, err := genComplexScript()
   337  	if err != nil {
   338  		b.Fatalf("failed to create benchmark script: %v", err)
   339  	}
   340  	b.ResetTimer()
   341  	b.ReportAllocs()
   342  	for i := 0; i < b.N; i++ {
   343  		_ = IsUnspendable(script)
   344  	}
   345  }
   346  
   347  // BenchmarkGetSigOpCount benchmarks how long it takes to count the signature
   348  // operations of a very large script.
   349  func BenchmarkGetSigOpCount(b *testing.B) {
   350  	script, err := genComplexScript()
   351  	if err != nil {
   352  		b.Fatalf("failed to create benchmark script: %v", err)
   353  	}
   354  
   355  	b.ResetTimer()
   356  	b.ReportAllocs()
   357  	for i := 0; i < b.N; i++ {
   358  		_ = GetSigOpCount(script)
   359  	}
   360  }
   361  
   362  // BenchmarkGetPreciseSigOpCount benchmarks how long it takes to count the
   363  // signature operations of a very large script using the more precise counting
   364  // method.
   365  func BenchmarkGetPreciseSigOpCount(b *testing.B) {
   366  	redeemScript, err := genComplexScript()
   367  	if err != nil {
   368  		b.Fatalf("failed to create benchmark script: %v", err)
   369  	}
   370  
   371  	// Create a fake pay-to-script-hash to pass the necessary checks and create
   372  	// the signature script accordingly by pushing the generated "redeem" script
   373  	// as the final data push so the benchmark will cover the p2sh path.
   374  	scriptHash := "0x0000000000000000000000000000000000000001"
   375  	pkScript := mustParseShortForm("HASH160 DATA_20 " + scriptHash + " EQUAL")
   376  	sigScript, err := NewScriptBuilder().AddFullData(redeemScript).Script()
   377  	if err != nil {
   378  		b.Fatalf("failed to create signature script: %v", err)
   379  	}
   380  
   381  	b.ResetTimer()
   382  	b.ReportAllocs()
   383  	for i := 0; i < b.N; i++ {
   384  		_ = GetPreciseSigOpCount(sigScript, pkScript, true)
   385  	}
   386  }
   387  
   388  // BenchmarkGetWitnessSigOpCount benchmarks how long it takes to count the
   389  // witness signature operations of a very large script.
   390  func BenchmarkGetWitnessSigOpCountP2WKH(b *testing.B) {
   391  	pkScript := mustParseShortForm("OP_0 DATA_20 0x0000000000000000000000000000000000000000")
   392  	redeemScript, err := genComplexScript()
   393  	if err != nil {
   394  		b.Fatalf("failed to create benchmark script: %v", err)
   395  	}
   396  
   397  	witness := wire.TxWitness{
   398  		redeemScript,
   399  	}
   400  
   401  	b.ResetTimer()
   402  	b.ReportAllocs()
   403  	for i := 0; i < b.N; i++ {
   404  		_ = GetWitnessSigOpCount(nil, pkScript, witness)
   405  	}
   406  }
   407  
   408  // BenchmarkGetWitnessSigOpCount benchmarks how long it takes to count the
   409  // witness signature operations of a very large script.
   410  func BenchmarkGetWitnessSigOpCountNested(b *testing.B) {
   411  	pkScript := mustParseShortForm("HASH160 DATA_20 0x0000000000000000000000000000000000000000 OP_EQUAL")
   412  	sigScript := mustParseShortForm("DATA_22 0x001600000000000000000000000000000000000000000000")
   413  	redeemScript, err := genComplexScript()
   414  	if err != nil {
   415  		b.Fatalf("failed to create benchmark script: %v", err)
   416  	}
   417  
   418  	witness := wire.TxWitness{
   419  		redeemScript,
   420  	}
   421  
   422  	b.ResetTimer()
   423  	b.ReportAllocs()
   424  	for i := 0; i < b.N; i++ {
   425  		_ = GetWitnessSigOpCount(sigScript, pkScript, witness)
   426  	}
   427  }
   428  
   429  // BenchmarkGetScriptClass benchmarks how long it takes GetScriptClass to
   430  // analyze a very large script.
   431  func BenchmarkGetScriptClass(b *testing.B) {
   432  	script, err := genComplexScript()
   433  	if err != nil {
   434  		b.Fatalf("failed to create benchmark script: %v", err)
   435  	}
   436  
   437  	b.ResetTimer()
   438  	b.ReportAllocs()
   439  	for i := 0; i < b.N; i++ {
   440  		_ = GetScriptClass(script)
   441  	}
   442  }
   443  
   444  // BenchmarkPushedData benchmarks how long it takes to extract the pushed data
   445  // from a very large script.
   446  func BenchmarkPushedData(b *testing.B) {
   447  	script, err := genComplexScript()
   448  	if err != nil {
   449  		b.Fatalf("failed to create benchmark script: %v", err)
   450  	}
   451  
   452  	b.ResetTimer()
   453  	b.ReportAllocs()
   454  	for i := 0; i < b.N; i++ {
   455  		_, err := PushedData(script)
   456  		if err != nil {
   457  			b.Fatalf("unexpected err: %v", err)
   458  		}
   459  	}
   460  }
   461  
   462  // BenchmarkExtractAtomicSwapDataPushesLarge benchmarks how long it takes
   463  // ExtractAtomicSwapDataPushes to analyze a very large script.
   464  func BenchmarkExtractAtomicSwapDataPushesLarge(b *testing.B) {
   465  	script, err := genComplexScript()
   466  	if err != nil {
   467  		b.Fatalf("failed to create benchmark script: %v", err)
   468  	}
   469  
   470  	const scriptVersion = 0
   471  	b.ResetTimer()
   472  	b.ReportAllocs()
   473  	for i := 0; i < b.N; i++ {
   474  		_, err := ExtractAtomicSwapDataPushes(scriptVersion, script)
   475  		if err != nil {
   476  			b.Fatalf("unexpected err: %v", err)
   477  		}
   478  	}
   479  }
   480  
   481  // BenchmarkExtractAtomicSwapDataPushesLarge benchmarks how long it takes
   482  // ExtractAtomicSwapDataPushes to analyze a standard atomic swap script.
   483  func BenchmarkExtractAtomicSwapDataPushes(b *testing.B) {
   484  	secret := "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
   485  	recipient := "0000000000000000000000000000000000000001"
   486  	refund := "0000000000000000000000000000000000000002"
   487  	script := mustParseShortForm(fmt.Sprintf("IF SIZE 32 EQUALVERIFY SHA256 "+
   488  		"DATA_32 0x%s EQUALVERIFY DUP HASH160 DATA_20 0x%s ELSE 300000 "+
   489  		"CHECKLOCKTIMEVERIFY DROP DUP HASH160 DATA_20 0x%s ENDIF "+
   490  		"EQUALVERIFY CHECKSIG", secret, recipient, refund))
   491  
   492  	const scriptVersion = 0
   493  	b.ResetTimer()
   494  	b.ReportAllocs()
   495  	for i := 0; i < b.N; i++ {
   496  		_, err := ExtractAtomicSwapDataPushes(scriptVersion, script)
   497  		if err != nil {
   498  			b.Fatalf("unexpected err: %v", err)
   499  		}
   500  	}
   501  }
   502  
   503  // BenchmarkExtractPkScriptAddrsLarge benchmarks how long it takes to analyze
   504  // and potentially extract addresses from a very large non-standard script.
   505  func BenchmarkExtractPkScriptAddrsLarge(b *testing.B) {
   506  	script, err := genComplexScript()
   507  	if err != nil {
   508  		b.Fatalf("failed to create benchmark script: %v", err)
   509  	}
   510  
   511  	params := &chaincfg.MainNetParams
   512  	b.ResetTimer()
   513  	b.ReportAllocs()
   514  	for i := 0; i < b.N; i++ {
   515  		_, _, _, err := ExtractPkScriptAddrs(script, params)
   516  		if err != nil {
   517  			b.Fatalf("unexpected err: %v", err)
   518  		}
   519  	}
   520  }
   521  
   522  // BenchmarkExtractPkScriptAddrs benchmarks how long it takes to analyze and
   523  // potentially extract addresses from a typical script.
   524  func BenchmarkExtractPkScriptAddrs(b *testing.B) {
   525  	script := mustParseShortForm("OP_DUP HASH160 " +
   526  		"DATA_20 0x0102030405060708090a0b0c0d0e0f1011121314 " +
   527  		"EQUAL")
   528  
   529  	params := &chaincfg.MainNetParams
   530  	b.ResetTimer()
   531  	b.ReportAllocs()
   532  	for i := 0; i < b.N; i++ {
   533  		_, _, _, err := ExtractPkScriptAddrs(script, params)
   534  		if err != nil {
   535  			b.Fatalf("unexpected err: %v", err)
   536  		}
   537  	}
   538  }