Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

txscript: backport tokenizer from dcrd #1769

Merged

Conversation

Roasbeef
Copy link
Member

@Roasbeef Roasbeef commented Nov 10, 2021

This PR is a rebased version of #1684 the final review comments addressed.

Shouts out to @cfromknecht for doing the bulk of the work here with the backport changes.

This is a backport of decred/dcrd#1656 by @davecgh which converts
all script handling to use a combination of raw script analysis and a zero-allocation script tokenizer.
This vastly reduces the amount of memory and number of allocations performed during IBD, see
the original PR for the performance gains realized in dcrd. As a result, dcrd saw a 15% improvement
in IBD due to the reduction in CPU cycles spent performing GC.

Before and after benchmarks for various methods after converting to the
combination of raw script analysis + script tokenizer:

    benchmark                                       old ns/op     new ns/op     delta
    BenchmarkIsPayToScriptHash-8                    62393         0.51          -100.00%
    BenchmarkIsPubKeyHashScript-8                   62228         0.56          -100.00%
    BenchmarkGetSigOpCount-8                        61051         658           -98.92%
    BenchmarkExtractPkScriptAddrsLarge-8            60713         17.2          -99.97%
    BenchmarkExtractPkScriptAddrs-8                 289           17.9          -93.81%
    BenchmarkIsWitnessPubKeyHash-8                  61688         0.42          -100.00%
    BenchmarkIsUnspendable-8                        656           520           -20.73%
    BenchmarkExtractAtomicSwapDataPushesLarge-8     61332         44.0          -99.93%
    BenchmarkExtractAtomicSwapDataPushes-8          990           260           -73.74%
    BenchmarkDisasmString-8                         102902        39754         -61.37%
    BenchmarkGetPreciseSigOpCount-8                 130223        715           -99.45%
    BenchmarkScriptParsing-8                        63464         681           -98.93%
    BenchmarkIsMultisigScriptLarge-8                64166         5.83          -99.99%
    BenchmarkIsMultisigScript-8                     630           58.5          -90.71%
    BenchmarkPushedData-8                           64837         1779          -97.26%
    BenchmarkCalcSigHash-8                          3627895       3605459       -0.62%
    BenchmarkIsPubKeyScript-8                       62323         2.83          -100.00%
    BenchmarkIsPushOnlyScript-8                     62412         569           -99.09%
    BenchmarkIsWitnessScriptHash-8                  61243         0.56          -100.00%
    BenchmarkGetScriptClass-8                       61515         16.4          -99.97%
    BenchmarkIsNullDataScript-8                     62495         2.53          -100.00%
    BenchmarkIsMultisigSigScriptLarge-8             69328         2.52          -100.00%
    BenchmarkIsMultisigSigScript-8                  2375          141           -94.06%
    BenchmarkGetWitnessSigOpCountP2WKH-8            504           72.0          -85.71%
    BenchmarkGetWitnessSigOpCountNested-8           1158          136           -88.26%
    BenchmarkIsWitnessPubKeyHash-8                  68927         0.53          -100.00%
    BenchmarkIsWitnessScriptHash-8                  62774         0.63          -100.00%

    benchmark                                       old allocs     new allocs     delta
    BenchmarkIsPayToScriptHash-8                    1              0              -100.00%
    BenchmarkIsPubKeyHashScript-8                   1              0              -100.00%
    BenchmarkGetSigOpCount-8                        1              0              -100.00%
    BenchmarkExtractPkScriptAddrsLarge-8            1              0              -100.00%
    BenchmarkExtractPkScriptAddrs-8                 1              0              -100.00%
    BenchmarkIsWitnessPubKeyHash-8                  1              0              -100.00%
    BenchmarkIsUnspendable-8                        1              0              -100.00%
    BenchmarkExtractAtomicSwapDataPushesLarge-8     1              0              -100.00%
    BenchmarkExtractAtomicSwapDataPushes-8          2              1              -50.00%
    BenchmarkDisasmString-8                         46             51             +10.87%
    BenchmarkGetPreciseSigOpCount-8                 3              0              -100.00%
    BenchmarkScriptParsing-8                        1              0              -100.00%
    BenchmarkIsMultisigScriptLarge-8                1              0              -100.00%
    BenchmarkIsMultisigScript-8                     1              0              -100.00%
    BenchmarkPushedData-8                           7              6              -14.29%
    BenchmarkCalcSigHash-8                          1335           712            -46.67%
    BenchmarkIsPubKeyScript-8                       1              0              -100.00%
    BenchmarkIsPushOnlyScript-8                     1              0              -100.00%
    BenchmarkIsWitnessScriptHash-8                  1              0              -100.00%
    BenchmarkGetScriptClass-8                       1              0              -100.00%
    BenchmarkIsNullDataScript-8                     1              0              -100.00%
    BenchmarkIsMultisigSigScriptLarge-8             5              0              -100.00%
    BenchmarkIsMultisigSigScript-8                  3              0              -100.00%
    BenchmarkGetWitnessSigOpCountP2WKH-8            2              0              -100.00%
    BenchmarkGetWitnessSigOpCountNested-8           4              0              -100.00%
    BenchmarkIsWitnessPubKeyHash-8                  1              0              -100.00%
    BenchmarkIsWitnessScriptHash-8                  1              0              -100.00%

    benchmark                                       old bytes     new bytes     delta
    BenchmarkIsPayToScriptHash-8                    311299        0             -100.00%
    BenchmarkIsPubKeyHashScript-8                   311299        0             -100.00%
    BenchmarkGetSigOpCount-8                        311299        0             -100.00%
    BenchmarkExtractPkScriptAddrsLarge-8            311299        0             -100.00%
    BenchmarkExtractPkScriptAddrs-8                 768           0             -100.00%
    BenchmarkIsWitnessPubKeyHash-8                  311299        0             -100.00%
    BenchmarkIsUnspendable-8                        1             0             -100.00%
    BenchmarkExtractAtomicSwapDataPushesLarge-8     311299        0             -100.00%
    BenchmarkExtractAtomicSwapDataPushes-8          3168          96            -96.97%
    BenchmarkDisasmString-8                         389324        130552        -66.47%
    BenchmarkGetPreciseSigOpCount-8                 623367        0             -100.00%
    BenchmarkScriptParsing-8                        311299        0             -100.00%
    BenchmarkIsMultisigScriptLarge-8                311299        0             -100.00%
    BenchmarkIsMultisigScript-8                     2304          0             -100.00%
    BenchmarkPushedData-8                           312816        1520          -99.51%
    BenchmarkCalcSigHash-8                          1373812       1290507       -6.06%
    BenchmarkIsPubKeyScript-8                       311299        0             -100.00%
    BenchmarkIsPushOnlyScript-8                     311299        0             -100.00%
    BenchmarkIsWitnessScriptHash-8                  311299        0             -100.00%
    BenchmarkGetScriptClass-8                       311299        0             -100.00%
    BenchmarkIsNullDataScript-8                     311299        0             -100.00%
    BenchmarkIsMultisigSigScriptLarge-8             330035        0             -100.00%
    BenchmarkIsMultisigSigScript-8                  9472          0             -100.00%
    BenchmarkGetWitnessSigOpCountP2WKH-8            1408          0             -100.00%
    BenchmarkGetWitnessSigOpCountNested-8           3200          0             -100.00%
    BenchmarkIsWitnessPubKeyHash-8                  311299        0             -100.00%
    BenchmarkIsWitnessScriptHash-8                  311299        0             -100.00%

@coveralls
Copy link

coveralls commented Nov 10, 2021

Pull Request Test Coverage Report for Build 1469849071

  • 907 of 1100 (82.45%) changed or added relevant lines in 8 files are covered.
  • 34 unchanged lines in 8 files lost coverage.
  • Overall coverage decreased (-0.1%) to 52.877%

Changes Missing Coverage Covered Lines Changed/Added Lines %
txscript/sign.go 59 67 88.06%
txscript/tokenizer.go 82 90 91.11%
txscript/script.go 163 188 86.7%
txscript/engine.go 195 248 78.63%
txscript/standard.go 295 394 74.87%
Files with Coverage Reduction New Missed Lines %
txscript/sign.go 1 74.73%
connmgr/connmanager.go 2 85.36%
integration/rpctest/blockgen.go 2 83.67%
btcec/signature.go 3 92.82%
txscript/pkscript.go 3 80.0%
txscript/script.go 6 88.47%
txscript/standard.go 7 78.05%
peer/peer.go 10 75.57%
Totals Coverage Status
Change from base Build 1467201991: -0.1%
Covered Lines: 21144
Relevant Lines: 39987

💛 - Coveralls

@onyb
Copy link
Collaborator

onyb commented Nov 10, 2021

@Roasbeef There are some review comments from me in #1684 that are either unresolved or without a response. Can you please go through them and resolve if they aren't valid anymore?

@Roasbeef
Copy link
Member Author

@onyb thought I went through all fo them, can you point them out?

The typo doesn't exist anymore: #1684 (comment)

I think the other two were smaller style nits (using a scoped if), zero constant vs OP_0 (also the current code is the same: https://github.com/btcsuite/btcd/blob/master/txscript/engine.go#L199

Copy link
Collaborator

@kcalvinalvin kcalvinalvin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I still need to take a look at these files:

txscript/bench_test.go
txscript/engine.go
txscript/engine_test.go
txscript/script.go
txscript/script_test.go
txscript/scriptnum.go
txscript/scriptnum_test.go
txscript/stack.go
txscript/stadard.go
txscript/standard_test.go

I'll do so in the next couple of days.

@@ -0,0 +1 @@
010000005901fa054079e69a46e5aacd8b57f94c3c99744d68c6097957eaa9021c344141a90000000000ffffffff020706fa6444e4c9d110274488a4641161165370cd11a208ad3a3a00178e1d9c0000000000ffffffff0210805cd4cceae0873daaf8b81d2eb7122cdcdf2dfae5a90569378f847e293c0000000000ffffffff021d93a6970f13f10f6b8b1b5763f772f1f37350f893cb305005319a76069f5a0000000000ffffffff021fa71c8d5f8c455820e6d83c6fca218ed2e70391c7dfba4119168c7ebc68c30000000000ffffffff0227b4b9041f1c8ac86ea16f48909c94adac7081b26a36ed5863e4c53175262b0000000000ffffffff0228a49d6e91275b9d0d8aa5f6e81d4e1e9cd957ecfdabd6df68fe6f4970a0b60000000000ffffffff022db9dd2128b6d1f1eba30a681f062f6eaad362e58b23e6cadcd2cb8d65d6880000000000ffffffff0243411baa5b4b3357849e505310dbd29a8a50e6324c4655e403043ac07ce9d10000000000ffffffff024b630ce631441c527d555c237bb68b694d50010b7723957ef4537185eef6c40000000000ffffffff025312b6a2f95a25a109b4f0b46fb35abd74b15a39241291dd3673f1dd58fd3b0000000000ffffffff02554f0d7fe6df9454c8152c6f46429596abc08b57d7cfbc7f5f22db8a0787e00000000000ffffffff025793409101621f48c0f9ebdef0867088c015c5e412ccbdd1d32a5de62ae1b80000000000ffffffff025b53aa8359a7a3fd92d69ef96be30dfed1ea99fa03db0050f3f6d3b9e2b7320000000000ffffffff025cfc35711a57b373b807bbfaadf0950b81a74d0215a0508f2e62989bdbb4aa0000000000ffffffff025ddd8921db66b54f1ad8ef2eae70913d22e5579e2781a1e485fc6a555f6e410000000000ffffffff026466da9b3ebf93b07e7de704789d5ae04f584fa39043716a9830edf5bf99140000000000ffffffff027904d29d4cb521d7c884325b1d42434bc99b6d712681e5e254597821a944550000000000ffffffff027c662f67150bca47a7b23f0fb7c50956b21a4f46b9f4c4a304d51ce9c5900f0000000000ffffffff027ef5afdeab2ec6515a739dc00c2ef8994303c19a74fd0512f8306fb601d10f0000000000ffffffff0284cdef7bdd0c45efdecb2a3a68079d7e01ccc8e229a3e72160ec93218b1ad90000000000ffffffff028885c672656acd03755167361be6ccd94ad72c5e3890773f11a5c627f046890000000000ffffffff028b372c55109471551db0aa772aa66321862fb3a11e4cd7a31cdc4ed989436d0000000000ffffffff0290198f3a73a8febae55d559676303ae495acbad11dde304c21483a951078270000000000ffffffff0298dc5698fb1823ee1ec7469936e0b9b71e69e843d993b2c0096ac3c1d86bb20000000000ffffffff029c5d6da641328e356368383951c5dad56398f29f58a922eb54afaf888e41430000000000ffffffff02d702320af97f4f498aedf0886cd6123692f6d7b6f0b1da3910703648bc80c40000000000ffffffff02d90670fc2d34b7fcfce03ecb3a3650a4cd384eca645c2b05263721e9a5c19f0000000000ffffffff02de1f9e03b532c0bd6a28a7066c81b7abf0f9dc7e40003f638b9a872fa9a74c0000000000ffffffff02ded955aaf74a935a479fb08d4e8ded2a99664cb2235bc617627616e0e313630000000000ffffffff02e3f8ded8a0d2716817259e4144faa83facd27a5c9550c7c65835cc2c93738a0000000000ffffffff03012cf75972966f2c7abb609b43c7e85dd65cada0ee20118c80d92d766a32400000000000ffffffff0307efdd04c2f3e033a0294aaa894b4245695876fc76c12b8523d448ef05e6620000000000ffffffff030db2a3d8afa67b68f0ae663cc88bee0af0a8900e88f099d2ee648603e7b0e30000000000ffffffff03133fb852424925f55c8a660fdec8a10dce9bb9b500bf07bc73e2641f1a55310000000000ffffffff0313954647c73d9c63e4e83d36e337b17797be10eb0d641deaff0c768aa6db060000000000ffffffff031bfd6a0bfc310e204fc0eb5e1c83b9d21dab82847b909db111a371ffdcb5a20000000000ffffffff032588268e35fbd648c621638633ef8188e8b946ebd3d5833a18d9bae556ee5c0000000000ffffffff03378677fc365f67102c2559a2a4300344d41a63920e7450951fc6d8d63fb0110000000000ffffffff033c9955351d351b4f5ac398adce94b2a7ed3ab14a1c790c8e4742b1558c6b200000000000ffffffff034061cfcca9fb77d28d2411f4502c3696e0aa28b040f6780a4bd00c3fa8a02b0000000000ffffffff034b063b587eb75409e56b5ff2f9cc24a994e3ecbf9de56bd46983443067e81b0000000000ffffffff034cb2f8a145b3a55bb07dcdb904859808b29a5f53a01ba3a3cd707211afb7190000000000ffffffff034f96fa5e134b5310731b23fa9235aad58008c7c61f1b8128542b51622a52250000000000ffffffff0357236d19aef1428f117c3067a89adbecfe234ef32d744acdd6b6a5107a95e90000000000ffffffff0362504fcc65272e50ba6cc4583b4b1486592b59d943ab375ea7d6f44ba8038c0000000000ffffffff0369b02dad4259759408f8a3b1fad3c24df0873b767a9d0f1d50bd68f18c789d0000000000ffffffff036e080ba3f983e5bd7d8700dfd4b510a502231d0112c38e804b59d8a84026230000000000ffffffff038298149c290f73c535c3a184d181316ef695146a58d3bb3d2e61ed993cc11c0000000000ffffffff038538c9d6b8200609e4ca2443910daa6d9bbe772b2793e11fcf1763202c998b0000000000ffffffff038aad027dade93191c546648f7125bda82c895f1d80bf174d9637e533e12a1a0000000000ffffffff03960708a37a5654abd75755d1b8f49a84cdbde4676e14fd4863b125a94e9fc40000000000ffffffff03b0e09c55ecabf221c30a086c812e35bc9b586547cde8a83961a7ffa9067cb20000000000ffffffff03b28e682c13e7ada60cb905f8070109ec5a7800a0424c5f7afb6bd3039d48cd0000000000ffffffff03b7e8adc6174ca369ec31b4f944a6b74231d09254fde6b2bfe3416f3c6096ad0000000000ffffffff03c4f1eef6176ce6a2114dce78dd292f6db20fdae7c534d22cbada58befad6cf0000000000ffffffff03c7b5bf7fef69ae11c467f68fb551d58424e8723d05ce3ccf88385187898a710000000000ffffffff03d5150f5295b1e817f44fa34cade950c13789c4129e98eb32634aaad2abe13e0000000000ffffffff03d8250c9557188c7672009c4b14f37f96528e3f9c6889607b2e25299efe08720000000000ffffffff03de1dc49d0146dcc0cdbeb84c68a14ecb7075aba67d6494a5c8d098147843640000000000ffffffff03df8c8392bc17eddab688530697c2c221420464eb643ca8edfd30d7371ed5460000000000ffffffff03ea1dbbe72e20229648695a9b01e09b6080fb1c4632931fb810c5bfbe4b4eed0000000000ffffffff03ebf599f34a7d2cfe7bda1722f76e3df5c7fe299fd5c6e4099cfec4f551f46b0000000000ffffffff03edf95bb2c93c07d734ee8b3b1de4d4085c6b65cdbc3ca23eceec0c4d2b29a00000000000ffffffff03fe259ab7c4c03447011200d7d5236f4b1eeffd075926b78650cf1a59d570590000000000ffffffff040f71baef9c5273ac67fcba44ab29456a820274654624255c27e53ff62fdf8e0000000000ffffffff042938cae95e712592060f872cca771dfcd0218562928311c81127c3170a57bc0000000000ffffffff043a09d788547db3d1bfd8512000f9ad8bb3caab6fc24fd186663542c9f06d750000000000ffffffff043fe8a5586d0309c8dad9277bfba04fb27e7a39d279c0c9a2232e2927874d470000000000ffffffff04426f1fb4981529c32ba5756f59a2211ee9d2b9cab2c5de06a07680e833c33c0000000000ffffffff04454fa62f2c03d785307982ae406224025343f19f183e0fdff83d8e1d4cc1500000000000ffffffff04496141484d641e3b0a661210b225df50221b13bcbcb3cb8c86786890c745db0000000000ffffffff045731a8be6bfab1f9d62d2b2158591029cae655fbde8c8bdee80f183e72ca4e0000000000ffffffff04610a21533b98d3360a6f2c65be5685a1bb3a43f67da08dc1674a0f517618990000000000ffffffff0471b7883558587879bd53e13694069d95acc0d809fd0a65125ba3acdcfd66900000000000ffffffff04729b78d9b706fcb20c9cbb49ec1baf5fa782ff44d45d9a23868bcbdcbca1330000000000ffffffff04747b056b7cdd34c9891845220c2b7227a7c7c4f90e1ced6f36fd2197b26af90000000000ffffffff047a37d63a2a17986896072c77fa2e068f6e593dc64bf2ad8e180fde729fe3250000000000ffffffff04875cf5c94486e6596beaffbd7d08cf5e3111df06a89f59d65989d1485949190000000000ffffffff048944d4aa4d42ae9e864bea755a4c934713c63ce1c61d177a1ef03fd679ee330000000000ffffffff0489d5c9638a3f689c232fc1b9b59e5f82bd2e720744c4405d6537d7931edb030000000000ffffffff04a206bfdb054b041ddca180111aad2abf7b31813e4551da3a29bfb16c03534c0000000000ffffffff04a5feab7629e0cd9569aa62ea70f7ab3fdd58d3aa9bf73e8f76ac32a949d31a0000000000ffffffff04a70237d798a0b48b5abf682b806f31ff425406d93e74cde1ab4b9939ce9bc20000000000ffffffff04acd550baa26049b3dc52fb9efef1cca0b8dfa228c64482440d7217a81e587a0000000000ffffffff04b1caf936f5746da6d683eb00ae36b885fd031a8928df91e3172b1ca17f4e520000000000ffffffff04c45bd25f17cc765d8a67ca80a801c1f1c2131eb51b7b8ceb71eb898582b0380000000000ffffffff04c99a8ab69a640f9891aff371876a9d0c9043cda0430cbd436145c5574c124b0000000000ffffffff04cc0c4723cf9952c0a51db275c9d11370a95f478de98dce603d09de8e3d8e680000000000ffffffff02c6afbe970400000000001976a914c055f905fe8d14a2766801307a418f1e03ba566e88ac5ee85e0d00000000000017a914f5916158e3e2c4551c1796708db8367207ed13bb8700000000000000005923aaa80d0000000018f6020000000000fd4301473044022044e60dc7ff9d720bdc8329f97c5b321a84e0846fed248b23d399d222a074bff402204aaab1ed5df1e4b94d627d8b06171df51140e65eb056536e91103b75d032cacf0147304402200fcc38b7bff890fbd390f4bd1057c72df74c2487a3cd20edee80a20d6b2ce02902200b7893fa29f24e09358b6777a946d40166bc71c25f8e5f850059a1914e25959401473044022006430580bc312023db40b6928ba0c39141a8a59bb0613c5f6feca1c1f9b2bce90220055ffd441357bc34c246024281a7b394c7ee6a2a3c013689d95a3b9dbbb98d9e014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000009503030000000000fd4501473044022059a8f1680e80b50cb8a9180c1fabe8a66e84b34fb6c10912715fa71a5bc0f9770220531c0836d4de61d8e291c517cda25919a8b822b80b5737068120411976e5577e01483045022100a48b720930c142235cc5585a97a11d20e73a81049791526cdcee34d36f92fcef02204d2523393dfc4ba21451fa563b08f53df7c7115b65a9f2f01fd578474d308eaa01483045022100a73d3ebefadefc6dd9c7041b32bf7a3a25e49afd41bb3251c502ae6fef89316102201652f9ee39ef09335f5c8e83b3fc7591b592ff0651c5e8c4786875710cb9d1bb014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000006113030000000000fd4501483045022100a5af3c72e2e15539caf81189fa020cd4b3ac5432c512dc48772d825dfca7c60802203ad5f3caaf54593b8068ce50db67111ac166c2efb73b52b30dfb7884682faadc0147304402207796a265903387a8a612cf47a8df0aae7c251a339590b24b6d813fb4cfb2dacc022076df8e67ec560d8fb74ec7ed0cd368a4da09c98846635c662e5484f5ac5b862101483045022100eb3318b340dd14f8fb797af81d8c1784668f082e54e58cd59c1224890d7f6c19022064ce22ff6bb93806930dd34dcbc846d38028db4d4d4b9c5527b6d493dba01cbd014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000099f2020000000000fd460148304502210081e68619898e8b0f9eadf2b5a324fdd7a684f935bf2cb72eef221e4310631ebc02206c7d5f1067d2980a95bfdf3b8530898ee94138dd99715c44e30b0894a5a6031b01483045022100f675ced0b828f2bae7a8046b41869ff865c081a79e32cbd400baa8ad99a975cb022075f347b57272df25c5d49c810af71fd1ba515b27166e61ad0c2bcbbd746a818101483045022100f271a0a0a8c5cfd149bf99411c4504d9905e6f9ce9f9787744c9593cab7f1c1b0220527057676ffcdd60caf0812b20b08b94ee1f935b44614b29bb9ab59e5bc0080c014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000001b02030000000000fd4401483045022100d068f2da5ba187b9d16b06dc87f1ff241063efaa786e07668abd06f8c2bea77e02206c541479ba49347104b386c36df7e14bc087bff7a17bd9c4096356a8c780fb2701473044022027336ad739d3ae08d1d136a9b7002a97873199d82abdd4bfd0531d62294116a9022001925305ccab8a328b873126baf7977b51154b4f0436685fc21f483ad347fb8b01473044022031a20fbdd66cf2a0d59ed37eda8ac50772c25adc62035b53ea675200fc85cc1c02204c1ab7d35c53ab9a5086fbcb669452367d68c8a3f07e47843c8fc2e2dbbbccc2014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d00000000c7f9020000000000fd4301473044022022bfa36e676571479c3dff1d1a5a5c0cee3677b5ac45c14314d242bee32664270220736875bcd0807c93c3a542dbf98c753c7164ad6830acec833cc43e157eb665180147304402203c2d6ce11bff536cd9bd4f58a930649f708a5735a26971dfd8d4efa7748c48b302201e4c69309d99c41d1c19ee200fe23547f1972173f0954e8833b8ba8ecedb6aca0147304402200e9ac49f352071840f13104f247d1a506a637d898495ffcc4aec7743ca56a2e702203e279aea8208e3128dc27173a6cfbab2bd8c2bc8f273794028ac77a129d5cab7014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000005210030000000000fd4401483045022100d8e2743e7051807e428574f9c881781b40daf0ec572dcc30357350c48683fd84022032a131a11df9571b53284a2cdceb7668e7b6442897fbfd54dfc7e26f8a3a19130147304402207798db2d9031f37eca9788b63869a91b100abfff8ba9966c91c42636e43c56bc0220499af71936bfae1579f6ae8d88dd6ff980ae1dc4fa8d1c5585d08a6461ce1e1301473044022062d193f3c5f74685fbccdd8795515f67b88c8a9aac089591fdc07a8238cbf5390220200f0d61cf48c33f7800150c8af578107c20875846b4a0f2af2a4aff9d2b172e014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d00000000dcfb020000000000fd4501483045022100e4711a4de4d6cb64e8cdaae2a4a5033c1ae87549c8d419b42b8170ee5217e04602202784ec57a6591083dbabaabd49dea7cddd3f8a32d9497e23d4b4219a56a9395f01483045022100fd776ca1d482c1d39e3a414bd0e6d353637ef626929ca5ee37db62e3dbe3501802202abe2e1279e5a354745fc5c8f8dcf367c616d82f91052925b1e2880f3906e7670147304402201976bceee0c3bb19da53e80a10a92dd6d135c86e581f349268f58b7d6b387bb5022042616ed8eee7f19ba7bee1fd5ae5415b521615aae99c45d56f9bde39d2577b9c014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000008f00030000000000fd4501483045022100fda77866c51a7ded28a6e12d3459e32b33401b1c0426f3c727ba4542ded40d1302205981be0eafc9b2c702038844769765df04bef8e17c80d1fef65c2bdba0a8e6ca01473044022018343c7eb59ef103fe102361f9ab8c789dadcfb3e60dc9b25b429165a9625af10220367f3f95f988ae6bc29c8a400687ffd70d009fe0b650994f48c1875620440fe501483045022100ac63791400e4ab5fee7f8c8ac602017409764dd7ef7ffc07d793298277661a2202200c3de5c22cad718ae83c101e987b66f31f62cc3364e8c8505407ebf11b392b66014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000006211030000000000fd4501473044022029a09ec85ea2c7741cdab03fb1185d94029a5511bbe830d11ac22dfa7f21f452022060d4039356c9cf06be106dc81390025735cde670de5c0a5a38e3946cca95211e01483045022100ca82755fa0c2a23e207c665099d102d13a393ace8cfabee79b1a6f97ffb001a2022014e2ef964e3fb5e90743c54e620d40d79ef111be1299d917f762c442d40c2f770148304502210094e9d5926748124f8c215fb006ec2bec55e4cf469a7f8579a339c08a267ed2540220323b0224d9964cd55b9ccc3f1dd919073d83edb293d022b00497b8681624bec3014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000b009030000000000fd4501483045022100a308d49610b603e9ff378fb40620f255216f3325aedc8d9a909f9c5e8983cd4e02205c93d14a43aa5d4cf7a3d4d483c6d47ccb8c2c4b78647e6cbe490ca6e03cfad601473044022004b8e60724ecba532e2677411e2617cbc49ba42cd4843423d724797aed8ba60f0220198d033cf732b5451d5ef00666ecd19bf4ea8a08f6c5fc16f4e153f1759021ba01483045022100e6217183d6869b431befccd5f4b12372028e7975640cebaeefd5f5172b871ad10220152db2015f3a8c16129f34e2488a4cf19a85f26956e3818f2cf76fdc9c43fd35014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000005805030000000000fd4401473044022011d6116b37d985ed52d59aa4fee1c67a44668f55d2db37fcecc3cf89245994a002202ecb47c384e42351995cfac7ee0fa8f55053dbaa11784a1890be84c164a5cdfd01483045022100922746c881c2becbadee3859902d93c9f623d1ccf3cdc0231d31e3ee806a1db90220715d1689faa159f4abe4959063e4dd8f108b4dae79167367843dc31e0fd46e97014730440220280556147f23f8cad1907869154a98b025baae29367cc80c6d23a3a0d3b6443e02207a62c31f1331459885ee236245d3e122013b77b304ed512914b05acd1886ce79014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d000000004bff020000000000fd45014730440220053f5cb6b633930fc7ccd1abd012292cee5b64d6d5c25220f9d9f22ab16d1df40220430e464f5823ee503616dfbfca55fdd01949cca336c8586f4e1de72ec3f3063201483045022100e16f4708245ca99bb01ab257f495d6a33b2db4ee9ee5089a6a2074ea9070d8f602201d3302d14c85f11b2f42af10e2ac31739f085036f26aa1723c3cfd2d4b35b2b501483045022100a0be245e53ce42f783481e3ff658c13ac850eda7ad21b8206bb48fa20fa2bc5d022072963db0ace1449652a083be585dc75574490248d1069c0a48f7f7a6902fddc2014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000001703030000000000fd4401473044022029960cfa720d3818d4d2d7a55fb1a68323e34678a2a8fa3e68505cd78184cf9d0220723861ca8cee7eaed1f503a9668d9866ccae44f15a3e9da77e4dd23679715e4201483045022100f3054837f1aa88101e983b28466b679fc43fac26c48d0359c4b881ed3cf8e73b02204f4579fffdb2a96fc82b0ede6bb5eab53a86b0bc04e1ea0fc1bae9953ff5a7f90147304402207e3132303406ca2924ad934d49e4027f52083638f08d73bed7138b6d267948ea022018720dc7f47cb13d3d37555c0abeca90cb00060ce1308a3de317d63da5d1f4f1014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000002f3020000000000fd4301473044022012cdbabc4d98705db72c8a6448d8b5e5483b404ce0ff26fad16b3751ccd667be022044bda8f743f51515281b41e7b7a0347e8d52e9aff07ad0c4c84486034b2257a201473044022021e2029bd87c699931a5eb4f997e56f1efe1dcc26a7fb8c8147a762bbea389ac02202ff5feed06720f5bba0c12107dca3d83c349a35c43256429b8f12eacc6b5df16014730440220269980cf3256b87c9ff83b29e3005b6af9c85c5a8fb7a501055b486bb51fe3e0022073196474be83c9cc24a843777835b97c4eca0ddf7956547f6cdc48a1cb42ddb3014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000030f9020000000000fd450147304402204442fa3cbc6d9ca9a217069f984e8fa1bda35a825d6bf39804c92faed97f2b8d02200976a8fa7653918fa23dc3adf15408d09a8069b033f4addb723a028f9928fbda01483045022100a36c038f7e49fea33c9f2a26a4d8e510095d42912ba1cabdd7da19ceccd2466802203b78e02c86e2fec18eea2ae2b94f1e6736dfa7a3453be9ee571c90832978509901483045022100af4a52bf94e8618d387c8f54feccdf0ef49b6b5503102cd405ab28c48c6597ea022031053ae17323c0fb24dc2cbd3107ca374279cac0b54c4a6633747effec83fc9c014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000006508030000000000fd440147304402206391486dc686a643c26e9b43038b2cb1b465ee6ba81ba9203d0d82f9550bd26602202ca9ebd53b20a6238c3d8abd4236b10d9ca304f5e5808db4d5004ab9ede10ad101483045022100e419233679a71a3380b662425657eca9ad26cb425a8aaaceb10de1847b3957ae02206780b4b428765f9bcee90443aaa497ade22fb813951205fd53c999f7ae27c168014730440220656dd7808be7db3a7e448608ccd3b8696cdefb687de8a4e37a58a2a0db8c714b022044683a0d0299b6eaed1ef4162d85f563aced9306d3e4a84961a301604234649d014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000cb13030000000000fd440147304402200ec0afc3a6b07063c61400fdd53e5419bb755bfcc99b1d6d2cf275b3f6848c75022036fa64742979454ce0c6b2e16963dca1d4509360cd35cb85fe533982a2ad9e690147304402206ca03acffd0198c7ea1d78e713eb45812dd0a296929bd679137dfe56f3f0601702201b9d6b25d85e86bc747b8c9d19a1a2329b02ca9de22a914419cec25bcd68e6fe01483045022100d22e473e19fda86e31314fedde03e1710f5de93efa284dab5f86817c99c9417602200bc43a59bee8a46bbc813ce52cdc76066903820af43d4caf173000543f672c6d014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d000000005efd020000000000fd440147304402205b7ef22035cfb0b11b62354d91f97910937de864369708e3e35376d4f620a1780220496aa95cdab061367e571568269afdc2d0a8fbb83b1bfd08c062d057041fada001483045022100db48444eda15deaab63bc6f3f5f31546d3b44a5a9412323445eb3e0853b440c80220201d7c95c47b5beed4a1139c27a79dbcd30f288ac106a208f43c61a0989f3e0801473044022040693ca2c95a33d5e8e7218b99a6df8507e9b8bf72014d19cae77d489b57b76102201883ef3c9df0b017e8fcf5b0af1bfbd61556d5bf347d77544e3bd263641fcd1c014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000046fb020000000000fd4301473044022005b50050f39f996b5f91e8e2c297e66521d5f72f2f4bec9993dbc44908f662be022001734f6df9a192f00e131fd8627b37b2afdbac8afafc38513b02c52ff305efad014730440220749ef2b72f235127bcc7c949f171ccfcc1ee87a9979543df9b568b323d9707260220440ae76846f8691bc471aa479e876a5fe01ce0e1f8311045cd8de1e7e58e4f890147304402201f5d4ea7f8fe9f08dac2020f4a2242645b27541b3cc591235fe927bfeed375a8022077627f400a799de27063ca0f2ea2d3ba5b8398bbac368a0501066097ad649e3a014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000009a09030000000000fd4501483045022100b83b84be0a0e613dd5e9dd7c6bec573a81bf95f83b546a4a717e3b708cc18bc802204bf97f877e03608103e30aeb9aab39c8f8d6e22cd9e89e66e7be74277bd7b7e201483045022100a9fe153e507bdebaf820f73e016ca8c7c61e3a07cfe7c79d74d674219c3aeb94022030abb047356650b8bb79d9eee45a9030c5cc55466568285e6d6f97cd6be577830147304402203da91297cb8e456b0089bbae43ad02d636f3f3777017c5b0224da4cf31ac5e5c022002a0ab0167395ee04f8e0f37fa261a80bff532c118a6f9fef229c5a9315115bb014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000cd0e030000000000fd4401473044022002012e7841bfd2dca62a5667f9bda8cc9b9e4ef86f8d0d6cca322b5bdb06a9b702206d5428e45ed0e18cb980b385cebc86eb6dcac59ee19f84afdc0c95ef0e782bfd01473044022073c34e5025fda812d8990f1f8cb16fbd4665554e8c467527b2c5c21132fbdf0b02206c22e854f2b704932e8134c46c40c738fc65939b2a516cceeafadd0c80f85efe01483045022100d808f0812a4a124c074166d87084bb3a38a6c632846a8ab4be7c69bda49b880702201a3625f5641c6a3a414196dc3b356f59b9bb8a44cd7623d601f28cb03691cd39014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000007fb020000000000fd4401483045022100e5357e2b20252b028d10d746c386959ba296630f08d94906a36251bf331073b102202c55e9e3a482e27326731821d25fa4c09103ec60c4cdced3f6774e6f1bc929970147304402202e58674ad455a4172f87439beaee82f59842d0c4565a88a358ebe8db35e2204702203cbffea9df59094f823f598cdb252927b1af451f522c8ca24d52274baa2c5f440147304402201a078f0959625258d4ef1bf7918b852bcf1629f8e79e2a95c1e6ffca017b7417022071dfe182e18f1debf0163e85ece438f1f3326e2eae39eab8d9c7a6cd8f179bd6014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000043fc020000000000fd450147304402201d697bdcd56006044872b398bac35b2c736f6bc22080ba7c81f5038de6a87a3702204c832224547a5bd89dbc5639e763dba74e3b35e77234078b425b7157b67f401e01483045022100bd54f0d2fdb584b15215e1eb8af08a2094f9ebabe0db8b01e107d0d6ca7c55f402204b1c9e9d368361903fe3cc99e48d045672818ae5ee15b3fc46e1c84049ed1c7a01483045022100e62ff90682934ec550ea89646de3a8d8bad4919bfd7d04a343aa490ca380d49d022046b3c4d8354661d6061798201cdec35744471b61bc55d83fa403d1aa6aa3b2b0014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d000000000cf6020000000000fd450147304402202d47018ec27c8d18a170cf22fa9f16df5da26c9ec85b5e7ab1220d7619d3bfb80220391bce1206a19b54496101317aa853f59fe8685f4ef1080525ec13149bfbe0e801483045022100ebab59d9714232e70788b14713364d8fa9c6284e0c2f173a3376521fffcdc80e0220624b5b801a7127e41c4378582026262e917a35c69ddb6fdf0de7ecaa26017904014830450221008905e07026703b5c1f6793fa9d749981369235d540fe0127be7b5d8cd18e401f022017b7e658adf3ad47487dc49c6aa8e88cc4d3b97f87ed0771299ef4026a8d8995014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453aea4a2d10a00000000c506030000000000fd4401483045022100c73924d67aaa9977d5b85fbad560b5315d36a657a402913be2aa90a1d12300e2022048b79577559462b4fd8c348a0b4d98caa20cff2c8a0363eda46037fd11782432014730440220345499c9d88b6989f158f7f980fa6f78f0a4b57d70f159739bf631b788fdbd3702207340e0fb636061cd9b7de0723d196b5f406d24120cb84f1236e3873f6e2fd6540147304402203332474e7a96938334a32052a802abb131d7a5000fb8fe1b903ca2474217d514022022533f3d58829d55689671efa27901878cfb75b20f5c696bb00ef4fcabde125f014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d000000002afd020000000000fd4601483045022100e7e10b3fa44e3bac1a8a5baa89ee3f34f437a37572a111d75ebc323007220956022007332c4fa0a81b99711a87342f7e13e720d09122f6f8d91ba093346d17cacbbf01483045022100d8f075435a1f1e6b3352beb482e899b95ee611551a499539c0004d703588a4cc02201b339915bf5cc5b1f4ca95d507576e58dfe20b2a2a3f9e97adc37c8f1f2921b301483045022100a25df71a3f33e602652db2a3ea88a65522edaf1e05c0900c87ef7149dafa9ed80220300d23d4abf09c979ad36916da78de09656f4c38a4fdac72454a888babc3f22b014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000b90f030000000000fd4501483045022100b6240b6c0f35b38bfc074939789eb53550f6c570487245666cdb825c13b95fe002200789f35de73db6342d1a3fd381ae55f5e801e1d170e7c9672c8179ddb7c9738101483045022100fb1a47917fef805908d2e02ff317a685a9550e5edc0aa8ef8568282575d4f6c802204a6b7a54338332ccff52ba21310cb0dca6a6b97370f1a621566c9214e03b49d20147304402203ef5647f608b1e921ed139dc54bfaf5e5ac3d959b88f3dc7a2ade0729f62818002205df3b44fab36a105bcecbe1f6b2794f692e0fe65eaa6d78126279c52db2a438e014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000004b10030000000000fd450147304402204cdd0d63816bd37ceaf959d323788b35a629d6b167cb2337ac81c0adae1366450220271435beb70cd8a18ddbcf98356de296ec16847a7a3e1a7c36d400f332374af601483045022100842dbb08884f9834eae867feedfa0ed158175348ee671aca1b470a9c5a723ee202201b9c8ffb52dc70fde054f18ab5a0419b603880bba4c9dec47d46bafb25c061dc01483045022100848c18295767444c5e0670930a0e0436fad251964bd4884ff5671db1a3f19d76022016edbdb3e65f99cdede5e957a941f3f35889342818f6805e0126e715a335aefe014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000001402030000000000fd430147304402202b7fe153e0a3f19123f677cfde86578aa546f8a46550806758b053d0b45b89710220792803175b29dab2c6e8c751436b30213a3b409c8d7cb0299f29a69522b120880147304402204e67807d8a30c8358cec44173c7a0727f542b4dc131f9701f2555a27cc17b628022044487b404d9edc2e2e8f553ac874c5dc549d9b261694752759d101ec00cc44c10147304402206d537d0aa88752db936ee48e986afd68338411ff68797ee2f80ce537bbf41d9002201d276c24365b68d18ce4020792c5d9558c7134495af96bcc55721d795c7662c2014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453aee854ed0a00000000f1fe020000000000fd4501483045022100a53176fb86d2190592abe80725917bfd5ab910b942d6f9648510e91730f3986b02205db19b272d2a2be3e315f2c9526a02c23f043c7b643128284faf734ce1d8930401483045022100be116b856b1f0ecac3c621c21a09d484a4bb86468a93e37f7c6d684c74c9031202205bcb65f89495e4575a1e5babb08cbd2aa21b200bbb9af1bd789b2a5b68c804f50147304402202a0dffdd955e6a5764e06113b5b712e789e956e7b6bcfaa225da8ef66e446c1b0220455f54d546976dd492b94f1693aa9d7e76dfffe3a7e861cec5f809fd5ac18caa014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000b305030000000000fd450148304502210098e4071de0540dd5d502e71a962803452695d3033ae1df83855e238197e2073e02202b825fb2190c784c050c375e1f520c0a2a85f9925fb43ffe58cdd25708e2877701483045022100892cb91af82982c870671e956805fa7aec721b82962311f4c51f959889ca8efc022051766e50a95d2ac480851b98f129889a4dd566c69b6ecda9654b3d4064aa40760147304402206ad1b9cd9af3c1e69ce32f42f1eb9ceab84e99b9f04cd14ab8842ad065a5589e022020b98e515e17d59e415d11b1be3e4fc34783d547200960ec96a4f67b6741aa58014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000018f8020000000000fd4501483045022100e500b89edaca4c2408069dd40b9645eec796f536ed10811016aceaaeba48b9b102201bc364c56b9647a7c6c53b235418a78c8d8faddf793bf3ffdbba41818b9a401601483045022100a9695d1776c81442dfc4931a960188be256f58c46ff546d1e872015c8ddf56a2022012443c230d3257b64bc8ee05d18269c211becf4b9ed3a5c7a3b63e2e216dabd8014730440220073a21f37e0d3cf9d706dde6e4fc2232395f4bf1982785ed04c61b1144f7c00f02202c7b18f4893413590bb4fb139896826bba6fc3e1205fa43fae30d8e7cce48edc014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000fe08030000000000fd4401483045022100ed3f9d7fedec8c9f8bbf1afb2b7b1edb3e4e8b9dfaf789f1effe19a32dea4728022046cba3fe18f0c1107d3fe2436d037aa8fee47e830cef788fa86541c84ac02a490147304402207326ffc63de54f4554f56a7da2072cb4bf89479bbdcf22d00169f4c3d7054a5c0220717c3c86a5470ad7f7a4900cc13e05a0a929ecc00e3ad58ac5850508ebcf7a6a01473044022052cfcc5fcd981d38c150af8897bbad4541a8aa2742d31369bf0c37c4670ec5eb02206031643bea000d5d33e180a75627cfa7bacd9eedc0926eaec773e529255c6262014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453aea4a2d10a00000000e001030000000000fd450147304402206c7e38bf498a7ff831513413e5bf283b93fa456993162fb698a88933e1451fa60220669d5f75d9817f3ec08607f4b3b0ffc3f4190b5f7cbfa3336f1b64dac28178f001483045022100d8a58946dc73974ead5766e023a11eb2f32d3b7d7e482ff92255137fa6aede21022040c02e902a208a25b10296ee0a5466c6a8ce211ae2a6e2b7a541d2ea69fa4fbb014830450221009bafaf344ef873797281daf907d4e1171d8220e47bac5888e5cc72f3e219ef85022029773c52b7785abb77d72c22fbda1f9cc2a8536ccca43ec756bc9655ce6a9a84014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000980a030000000000fd45014830450221009be858cd5d4a8bce15d433d040be529a38cf38d9358919a9c04a1ca26127e3e402203de44aed9159f37e4b04ca449621d82040eab2528e2cdb7539708574899175c80147304402207271dd94d087a7b2174f3c3ccea7b92e29c84c6471224269f30faea34f2b77bb022000c9e94e92ea9f311d7a6260a2283f879115eb84d319d0da3cdb724b2b66da890148304502210093397c395481625411641fec29b5f8c37b4163e6bf886843080082f14dc716c402202f1b776d07f69ac148ec36a257ed43a2a016cc3257b091f1180bdffe9fb8fd39014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453aee854ed0a0000000047f3020000000000fd4401483045022100e59a2bba52457cd3a884568aefb50a2500309e0262243553b31da735cd281d1402201c238950f19cff6cecb3e75a7308c6a0e9addb39001b36e12454575dd695ef07014730440220679cb79f57fdabea76ea42e3f145aea59f3cfd4401c4c5b1693b2ba03a2054b202205e3e2accd4d889e6fe4ce0a4ee8d6618dc79aa1f3b2d3bfb664d83d25d6562b501473044022043b80b0e55ea34ff22377a57c4633c1c5fdb57d53d04176c1673cc67e56d63a802207081bacf923edaa05c4848a11fbac160eaf3a33bb495c26361f85f3ff1fc482b014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000be08030000000000fd4601483045022100ebac21191cfac6123e0a66e7c6af7febc1a1ce2e63c4e6fa1e4f75d6d2f39dab02203d592a16f40f8c8c88817d0788457d66bdb35dde14b02b90c7c353d040d4af9101483045022100827b7e2ef669a5d1407b98562a4cd45f302f6e2489c1bef3b8bdc6a0592bd922022036fd1961511848f84c9e8c633044033501091c5e756e28a4787846156cb00f9201483045022100e330508c7b1072b3644bdaab4a6219a7cd7ddd364ec0729534385e2e6e7fdf5502200dfa2bc091014c8cc134728c60c40f21f737203e5c5c04740fbea54bba8eed04014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d00000000f5f2020000000000fd45014830450221009db0475dc62ee80b8b49bac4d0138de52f985642f5ac40b75f46b643e4115767022042a464c53ebf57bfcefa05634ceb14e7669b86e94bea517c68e2264d676c70f80147304402205e563709bdcb24975892a67eddf3958a31d3a58471c6c0e0cb6891be80b9eeb802203c69bdf9042af7429f1555cba2180108834b3d3b139514274fe5c30aeb9f054901483045022100f792db494bef64ca96ec49891434bd33f511db09d214a3ac323343d758e2d3c702203a86fd817a81cb84610fb948d6b3ec7f4d4fed24da15ae9ee245606c4f5c42ab014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d00000000bef8020000000000fd4501483045022100ca7a5b358614ba7615381667ed1d6ee7b2332d520caa6c5c6c1b4aab12fc4bd9022062990a8aef2fa44dc073cafaa2f3e95c3534ee075ba998beb716756f985b8f4601483045022100eb982763c3fdacf7bb59642df124f090cc5e55287b28c206a47c8a3ec8f08add0220668510362e0da663f117235f3aacdfa7fce13abfdd2c54425cf63d1b32c0b56d014730440220447bb198dc7466bfcf815e6b91e2431b970f6eba34a02260c224e503622e126902207c8fd1e1bdd32def46f7442df011f58a561b04477b372da26888c2a38d2bbb53014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000580a030000000000fd45014730440220702dd862dda92c7b3b214909a1f9dab7653a2f9103b7a62d6646d02c651b34e7022022e3c90cbf41c1fa2a5aa89ee139eee2e656a640947e8431c63c54e1e944763501483045022100f0781151a884d97b3de74df629265914f8d0253581e789a2eb511ef76568a4d602200f33782718c74ed75c25d66426f2037690d0ef3734fd06017bbd65d992b68bbe014830450221009d56003be4df82e009e4584088807d5f4a4322151865681dd04645c63b7a5a010220584ade2f5b18756f6c3cec7dd391cd174fa9103c747c8ea0d0fdf6877a4fc882014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000006407030000000000fd440147304402205d00ab5aa7656681bcde46fb484802c2a304249dddee6555e680067577805f2b02207541b235dbef5e123e7b392c63497ea074c26f08d004e766a7711dca4e2696ec01483045022100fed3c2fa5de22702bfb31075897e03b433fa593ae0fb80794103566c490a595102203f5b01c8e7296be4e2f1a836b9be4d073bd2378816ed09baf416188d9b25c3b00147304402202cc6e2988cf32e02b25b2a2d853a507857ff6eac50427c4def421f20aa36e9cc02201313060f0fedb86d1dc00b41b437705fae3e5e3df23b254a2343afde89e17ee9014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000074f3020000000000fd44014730440220681a9e9bcbffa35a8328fbbdb173d803042ab1a858c4ca9ed0e4f5a67d453c8e02205820f9fcbb4414d41c28e3509806443a869168cd1e17b428549ec2495161f5180147304402201339021529c4f6eb6dd22b56752ad5b2df7e1e8d978439c9578f0e907974711002202c63c6b1386bea4536364610c38ac0994ae863f9f35bef25850d6173901f794701483045022100bb021e7846352c38f3fd4fd21179b448a8d34295de73fa41c57a5f972e91c355022008f06f8bd66571e4027556cfa9124b4e1e518a9e82470261db3f0a3972efbcb6014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d00000000e1f8020000000000fd4601483045022100f30681d06bbe18241bfeeb7cdd484bfb44efcd579f2c2e2ae53cdb68fa5032e202200d96812f571f9d8e8fa0756e351c7a779c46cf4ac62a6825f14a5020b771988c01483045022100996344d3a16c60b94789cd5ae4d3cfd8f0b660520c8e7602e0f8a7aae705b127022008d6d5577a2b90e2462bbccb88c79cfa2936d001e1b14ea530261235460c0778014830450221009a64402bdfdaaa64d429576b0c77601e8da643dab7437e8cfd27db5a56c05d2002202d263058b96116381e1879ad483c664341174f341537665c1ba7b8a33e378e75014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000086f9020000000000fd46014830450221009b8535b0fbf8f854787cd30b5109010183dae5807fa34e66e15cd6066dcb6b2502200783da464daec7780b546c0b6a4c274afe5f4698987820d36d6f9d5798ba2b7b01483045022100ccf647bcc6a89c6cca668aa5d4cb8577b8b1ebde8caf476c4270d96c631f6b8b022072e9d434ae2130352841477494a39168fda21ba51af04f372416a12b2a3eeb3601483045022100b71cf0b31f05baf22586a01f0e1869e8a37c9fb50e1470c6d82f0e8c7e709bff022050bb9d7638620264be1f8d69a477be454dd753beaf0ad6dd92f3d996f62910ac014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000007e08030000000000fd44014730440220563b7ed04dfec4aa05efd0911b384ae4995b1ec21be2572013f924268db89c9902205b3c939ecf522cfcd9d4678bcbfe23a31a3fab94043725be1d1e0c2a1ac6faf001483045022100bcfa16f8de773f97dbf9dcab27ead17ac1b3bfaeb16733d1bc355380595730a7022000d0125f418f48606a41c8b6f3e7dbcc21fd45f8c64be25513d58f56a0cbb0bc01473044022038b52dabadc8e617c7efaea42019fb8732a1bec5fa7a696c4c050b36968b637f02205b0bb3befba6a9fad35b34b99280523ebc37c20ba2eb9996d91c3f25a8d2a933014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000001c0b030000000000fd4601483045022100df6c07633b6669c8a73e33a004924e1a1e488329a165bf73871a6153cd222b9b0220240131a2687dcb6ce091ddb241d9e533362b29e351a7e181180b09a6c395a3e301483045022100884e519da7405180a348e67a6f9065d6a28520168924cebdc291f00aa28901e402204ff72a4d9bdb0f4fe667eeb0ef658a0414fe978c71193649235251ec99e0ea1d01483045022100c54d7fc08cca52150fe97c943e8d174306f125a459f3e71b489e7d497cea63270220101d71cd023d9131e98a1630a1566ba445b8e006aed4171d09f859aedbfb010a014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d00000000befa020000000000fd4401483045022100e1c1a88390bc698025e6e40d1005a028e02f48f4708a2d8adb6b388e8ff83c33022013afc0b581e7b70d7f515d32bb1c7da68ff84dd741dbfb35855213467fc04cd30147304402205775454f05bf5fdd98df0d27b91eeb68955477768b9f50015f41a79b6f55b67002206861d59d11023c98d3a8df4edbc30a6008f615624fb18bc7d09c76d91f3abace01473044022055e4148ace2edcef6fd14e2aa7524ba7cdc397eaa025d2decfe8dff9ef93cfd402203cbdf55e08018990bcea1255149f31661fd568f1542c3c34ad9865161382a845014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000005507030000000000fd4501483045022100db1d65c4c10c62a9a727e8702730347528a810c18734666efbb8a9fb372d553c02205e3c8dee1c43ada5aab36e5e91291129dc06bca5dbcb2f72b143e98bbf8b506a01483045022100c824496528cf45264250c975ad4926e216c51a757aafe4d374b43d20fc3f108802205f3555d6c1c819be780e0fdb0b73e90727a9ffd86620c2eae6e71dba4bbb696001473044022045e218224cb0e51b36a8fedba5309d4bd0e0f3b3c89eaca58c1100a658f5370702202aefbb0c45f6044af9e8f873909e122f317c0d70872374308b6a4506ca4918fe014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000ba10030000000000fd4501483045022100a5ad7fa4026ee61e4c1e390a9d7cad33e7a23c8e84eac552ed5bd226350f2cec0220053a68add02401a90cbe70dd3ca95e02f7f3fa9c7554c9b85eb2ef9dbd7570d3014730440220458d2a918740dd329e7d3cfdb2035251503a472138b9ab19f18361ba1c05c40e0220523a8a265bbab021a019d54934bbccf0d7070342acf3ddbfb4c4d85671b34fcb01483045022100f8a7da4ff094abe375185b727847cbcf4dfb9edc7aa2c7d067153c6e4f59a479022001e0b4d4b71c818e3790accac1fcc7ab94c7949c9942224ab8146f31fe5b1fd8014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000003d0b030000000000fd44014830450221009ff8642e68b10e870a62eef426786fd90385f27ad7aeb2ed86313ef5572cf69202206ae5e31a87ee7537329c5a791ad06666797542d0d53e5e803565c46e850fdbe10147304402206477af187868ba99a2fd16ef32c5718e0710e497cf3b63c8d0451fc91aa2973e0220756a79e9d3c7ab5478caaa194c620ef6c5cb051105cd8d5906d2234226bf17420147304402200b6bec9b2956c32bdc2d39dd77790ee8469253cbed7ae4b1006eea6786bff7ab02207fde3b16eb595c5cbe2c022907f157a64a5d8dde2ce2a354228f6bf447a7b874014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453aea4a2d10a00000000f301030000000000fd4601483045022100b927e3f030945624fb57a1c78083dd5ecbd16bee095f1427fee2dcd8fe3571b4022059fae4cbe0bea4848e4d1e398f4d2aa4b2a17614fdbad4f1448a920cf73e189001483045022100f7fff756ed604f5239b0312d195e67880242c4090d92df66c2ee925c88acad9e02201aa1785336c8bdb7c604ba39809d889fcf5ee8eb63c90f4a04fe2726c58418fa01483045022100bda45c1641278939fa116163fbefbe1977628506882f873768a3c478739727d802200498f9e44d1e820ebc8e5d77e961aaf889bb72935b9274085fed3fa15f77e2f2014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000078f7020000000000fd440147304402207d4a786eaf1b2a169916850e5452c4e220b790fb3f2ad669a75aa54aed1a469c02205bce95b79ea705713d6c78fed676d2f0a0af31e4716b3d91f4f3722ac571357a01483045022100e5aa71c4430cc8acbec03d7a666c90e675df226bfd9e64f36e72da3fa3615a1c022038b8e99f6a16de52780723ee3f2b72ecd193002222df75db1035629ecce2a03b014730440220097f37a7c2996aa2e49aa2f36dc1669912a4cf2e5dd092751ed9df53188e078c0220676faaa01133f821bb044cf2d6f50a459c25c4b19eaf0145b4430967db73b01d014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453aee854ed0a00000000dcfd020000000000fd4501483045022100e60da48ef5ba5b29ab98cbe2ef99ff0a2bc92cd85024e49e5ca3597f12bcf300022048e6b0090e5c8057477dd7eb3789ae0b8fbdf5362421eda08c76d859ad7b544b01483045022100b4cdbbd212bbda1f70d6793d9956039d7edaa8fd70bfea7fb6a04bed7319e3780220425f48e5b29e7452107e19fddaec28e5846e04a2df14b80fc143bfd71e6266df014730440220279c65bad108d0007d821385aaad55ecad775a436de2b4e32988815020764847022067a86c7c33ece430d743102f84a9c48b29b9de9da821b6666929b13fb34e96e0014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000000f8020000000000fd4501483045022100e0d21364935dc638b62cc87e9a198a47afaa348143c28c35e3c1d4424bdf4121022008bd790132b5f6192ea6187f5cb5a9ffc6435eff720357382739d1a961fd9d890147304402203aab999b99e7753f39e2ef8d450241b911f6ffc4d9e858ca6743b2a5b7ff236802206344c728587b60b56672c4ada3fa58778cf4905697d46d026e1019eba70d1e3d014830450221009525cca77d25075ea465237b5259c3227c2924507e74851db33a71d6434a90fa02207fad277b64c89ade6a42a682f049b462e8257be13ee27cd16145b52bf60847ec014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d000000004ef5020000000000fd430147304402205879477cf645049300fbcb532853fd3672c4f00ebe8866df4479feec600e4217022021e95feaf3015d9d03fe78b3a0b5b61003e4182709aa7db198a050d9ca6d54be0147304402202df5b7711a3ca99433ca9a6bc6894e95ee4f1c5c94811f771e22fc7eba8514ed02206a2763d0ccd7a641d2bbca36110a4d0972bf5ab2e4660306bf8af866117759670147304402202a22e0d36d9f55d479414abfdba71a933083f44704997d0eeafca51a3dd8d37802201a4ec8031ee9325834f31805fa8bd50e5f9fa165b965f2027500ef0ee0b8138d014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453aea4a2d10a000000004412030000000000fd43014730440220221ca9f2149986b521ded423d8bf044b33502c128d5820c7ef6ea240a5bac8cb0220567cfd17e603a28a961f2a2ee88443ae709f5fab0d3cea9aa6232f5ea2570e000147304402202e38267764d91a348d25b328c11ad05323228bb3769334527418cc83df6815b7022072d856c6d2d45b1947f99e9c8e695da78c97e956eec9d75e2ac2f7cb450ae4240147304402202baa2f128838ad113ebdd95bd16db144ccd1f29be98fe435f145e7ca954c44b40220279934a60ff066f77c05031d6624cd1c7de4e120835162a2d43021526ad84bd1014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000006c0a030000000000fd4601483045022100c858877b7ebab9df2f7b0681239c92e6c03e95d963e42a075fad8c452e045b3a02202e04bbef330e7fbec49a7ba9859a6cadfd61830ab858362a2a695ad4db84ec8c014830450221009139aef771bb21f6ccd0b357d5e62f0991f98e67039b4ef146ca74c7a95883a00220268ea8e98573e4199ddaa75d2bb08f6b694af8bd383614f59e8699eacd7850be014830450221009694018de0f453d6732941e22309064ef42127d412ad135808acd9031e058fbd02202b4d893c63a553fe2401c983d4ab5a2f1ae9d411c195470be7d895ea4284b86f014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000ed10030000000000fd450147304402204a295e4d135f4f6421bea46bdd97d452b23b262408d77fe38c1f090f5f88651f02206f79e3a47f198091c92cf5733f9ab0acb3347e21f1d66ebc53e01db3dbd6845701483045022100a606efb31d0eb4cc4112721878cc3bf2fd0fa2b2cd3337a98cd3304e0cd2e38c02204b6117547daae39c06d7477c8f5e9e0e0d767007326df7b027cf80ec36282af1014830450221009d3a3360ead284b47ccc8a13eb1ad8de55563dbc5ff0a98744fde8947ff5484202203c350ab88bd4650aaa762bb1379022abedfd9316e9771e3c516190d07fce0349014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000be09030000000000fd4501483045022100bc76c0450c83c1a704cebf734d36f64e1ecdc76fdc1cd6e5f7986caab2d1dea902206bd1907f48bde52c8761bea4b7cbb848b2331ea58e6fbe683ecf2d60990001400147304402203287582617de76c70e8f73fba20100471cd40b4d6cff4b883a9ed88922c96dde02206e36f90628e7822206d93fb668e759ed62d6b73d3ecde935187d239c84d296a401483045022100957d7302576e215c6e97eeab33854a139f88839377eaf10edd1f80d7567ed5e102204452ec2dd7f273d586d7ca83f80c554c73531ec689bd961b524c2d0f2b16049e014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000fa10030000000000fd4501483045022100abeb8758b4ccf148e91c24c43b46c34330210bfa56eda2afa91c64f496215b21022046c5ac2cdb04ff964369cc6137b8c6b1133422640769d9f7bcd66cc45bea71b501483045022100901da0b92045f0c0195447fd2ef83b9cd6944e75e685e38a6ebafb16a03087a0022031f2261fa0f6486d000daa590875596ddd40fe99e943ee9d6bb8bd9129b0f90801473044022021e942e7c2e562631492f4c7bdab659af69efec054e5cf8432b9cca6e2b35579022051ce1c6da198abca7414783ef73e9119d6a345a0f0f989c53d74fb506c5c8fc8014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000850a030000000000fd44014730440220132a4fcc7f1a8f6f3fe4cd9ccb32fe3433a4e450f8594424e2389368f6a7203b0220075d01f8b9ed12b416d1d04cb9e95ceccfeeb420fb49720b4a3f0d1404be2f390147304402203707b8551104cac7c862aab1a2f730208f8ecee7180ac474b409be4c6af73fe0022023e293d0368a43397a93928d61586bc41c6c7bb1793b2bd2639ba8520014783a01483045022100d4504304bfa64c675045ffedf26e9b85a3ab839df273d93e2b1b9ff12338c7c50220080f395e7663fb9539181eee186a6960d69319333cbeb4fd5d596226c7cafa34014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000066f7020000000000fd44014730440220048efe6d5dd87a48a1deba890723e0e568ba8fe0112d29537237e0a1ea1c38070220197fda66031d9df627b79538eb4de824b9b49d94fab8aed185674ea4beac7bb901473044022006bc3ef529905db1a6729c0c8755267f63b3de05a0e1df68f9a2a983596e780e0220017582ffba0935ae9c8739bd6be3d21f9aec531ebe36e12073a5ee985df0110a01483045022100ef338afebdcddc9f46ac98ae9d9f936e44241cc46584096980690174b223da3b022062e0cbe00bc6ee9588ddf92e3c9c74f55632743d3d43dba4e43747fb380a4727014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000003901030000000000fd4601483045022100b21b940e774245ff2cbd5e797973a36ae61bd59c5a24081e73894f5823150d4202207c90190110b9607b151db47b81e7008b3fbf18ab952f83cae3189e4324a21709014830450221008949ea570a61d78294fbe8c2453a211e152b0b2190078b3cb085ef58fa2d47de02206dcede7043213254f01ed0e4b73d1f5d4550ac5fdcb815972b788edd1dc626b901483045022100f8b59f01836c28cbde52114bc8bebf8ece08f45ffe1660266e0dcdc187687bed02200b59ef8ca9b9a4d8d9c937014ac9bf66b7638101c89f10549cb28ee2bd7ce63f014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000004313030000000000fd440147304402205cfc98a5c55837d45207a22a7e20b646d0805a4d35e4bd10b2bbf3fff6ec0f1502207df92aa9d79c5eca866ddc95c446c774947a824e0901361dd42073f277212b66014830450221008845063e4b5c6387f1333d76010493b23d79d47f19b227b659daf8c12369e87a02203b7002cdd2ebf3974be41a5c0dd6ed267cad19b2fdd0526260e52a738bba2de4014730440220575a271b955e464d335bb32cece0813fc730be1c98f3b41d70739e9457c8b6d602206dba0e5fb0f0b3465736cfed2bab04e8f7defbb9f11c35cf1601b95fdaaa8829014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d00000000a8f6020000000000fd45014830450221009cb6793416713945a3b6b08e10139d5c58f3ad21ba52f45913bbeb33d8ce1f5d02202e550c9d430adfaa81b4c9f6f27c2de234dfdf3936c5ad8a3beead34a8a62fde01483045022100807dfe6e8df9e514b6850f94c04efae0e927466c88bae4985d1f5a8014a5504302205265e75bb3d7b2ecbed322b35e998d904a2e4f2a44d5906807ed06566dbf14320147304402204a629c423c8ddb65ebfd05dccc9510aa3e8119b02d73e5997d559b7f9ad6a87d022029190b7200278ad005bf2f1a7aed9c864f9733ac3ec5bd7102ab18de6f44cd17014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d00000000a6f5020000000000fd450148304502210094ef0e131bead508301ecf03a7ffa18ec9263bc3666450d2bddcfa752107310002207b8cac555f7a132aa98e41d2feff833fed68752601d0f84031eb870307630a480147304402203dffac6f1e5b22325cf2ff59a98665a827852a36225bb7db58c013022ce037cf0220502c495cfae8edbd53ca9e8b6b9bb4efbd1278966fd9156297959696d445c06f01483045022100f9d9f9b2058abc9aeb02bcebf6f5b22faaa6356ca640550b3c20744cbdee77fb022051c28736be4150081d5aef3e2253a5ce9d3ab9c4578beb398561029be530ebf3014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000003307030000000000fd44014730440220521bdce46b83ae123bcc4f0a008d2bd5cf76e76e0f98f21e029cdb2d0a4f868d02201d3abecb1216d4b41e6c6b23ad87feb8f7ba7735508a4f33d701289f736f366d0147304402203d15fd7c4c578a8acc93a1ec867199efec7f3cd532adf75e9a689af0a2b9399d0220598e77fbdb195843a2699c3464bbb5ccc55e608ec06fb2723c449469766d212c01483045022100e82cc5d9dde9a9086518ec2c18fa867ebb46112e37d65d9f34d88ac741996c95022053e900ac59bb0a391677ea209611b858a89540e59d66056852f8bf23fe5670d6014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d000000007eff020000000000fd4401483045022100d71d8a50ea69c55f7fd08defa76bf1b4da053e3575544b0471c461bf5aef137902207fd9f81b4a551bb7afa8dd1b69a0f6b1f2c4e24aa93c992fa82f82471aa8f72701473044022047ab50ee3683939bfef7410135d6b8de00f9405ca5f2faf22979df74bd4ce974022077f23743ab8ae449862ffd48caf40cbac62fdad49e005b7212852d085e82ee020147304402202e98296f1568f3b1fae805d2d6bc6fccd59bf4c5d6f1565b644d142297d7c40902207abe8a1988115e0ab88180cf90821005e645d97ce360d372f2f329fe2a284f76014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000008604030000000000fd4601483045022100d0befa3dba35057e2ef9b8b6d873dbe983166816ceb84eb875b346ce720a6b33022018d3acee74afd13496e6f1c06e7802d7813ad42a890be8a83280dc06ea23941c01483045022100822d27a60f0e4a8c37acfaadbb0be6d49c0981613610fa63c1f162d24e79e4500220316448804078b2df0d126fb9ec97dc1584ca84567332aa3dc2acab6f573856b0014830450221008fd2b5a6421611baaa3deb3739720dfec37f5cf95f93aff572dd92dca8925fa202205d2b99181fc69d7a46c7c58367a3927b1f6fdcf136142cf85f250837fe643454014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000180c030000000000fd44014830450221009c3ccf4b91b6e425a70216bafbcd277682da8bdbdff336b6f58cb97dfc3204cb022058f650ea3ad9d26b2c66f17cacdd10bee5446caa1ee34145061c227a56620225014730440220092afde83e1995e9964b85224dfc4a3782df00a0f982eb4ad3b38d083428149b02207669684a16f7af5563fe061874c80d9e35e745abbaf7396c7f71267f50fe52010147304402202858c626b1245cdfceefa6440e0c0ca838c10d6114098fca6df76559577de29302203fac70696457e9bfec335fa3a892ec3a2161a34c5bdec4a375d63f76a8d51fbe014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000066f4020000000000fd440147304402202397ebef3e1c8008050009f306560d15c5395fa07fae571fb55910749c3fb321022044acc8eab9dc7e1a9b4ab2cb167d0f944398a0b5e2c4d6e007f6f64779ab011301483045022100f4da2295f9558216c77cd9a0c56e0afc84e3ff1e6ec6b286ea5d7ab5a9f0b4b9022047d7353598ee12ea23234fa4fcdc05d7f9930f276cf4ecee76d128fe148596700147304402205216b5502fa0f62c6d5c9844f7b29ada8cda0fdce7d4dbacf8466adffb0eb6680220735d293adb02fb781f1cc02f9d1ef5a64c9436a95b4fc111dc0d69525ebadb03014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000004e09030000000000fd4501483045022100972f0d3af3ad98263fb8f500d6dc9bc75a589148b712ac0c7f95c78feb5a516102207316e12601bfb099b54075e67563a09df9e023094411ad19cbb5850a3778f067014830450221008ef3b3fc80b147b0ec47663d8756ee4d59e6ebbfd53a6d8629d1f30e9d17b70c02206583b2dd5b4f25d219e05da7fc34203ef667e0d49530e6211827f9f3041d3092014730440220088d12eb6a4424bb67b1aa85b8854cec0a959a62ee26e825a64bdc69c5e1219602206912f9adc20c5fe9f46ec852a6f433f276d1480f6a495a7984cf676a925a4f02014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000d80b030000000000fd4401473044022061236f509bf2232223af7219d64a2064dc4c12de828d46a1313b8f97b8a1b73b022043e93a6c2884668dc499ef67e370a6d736318ad39708183d2a702945a53e7cb2014830450221008e6deec7ff876824ee0f5a61f874eb5c303edd2717f5ca866afac208d591cb1502202fef21def83459e46dcb60387fa6ecac65adaf2ebec8d1c4bad92d1b0a928dda014730440220298c9412d02c1c39acfcb9dffa66cb5745904dae9d5f619b29d909cb9d6538ea02201dd6ca43c5fbe2038b6c347ce4e6a35e6d3e6f9703647661f0aa941cfd6136bc014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000b70c030000000000fd4501483045022100ed44d291e5e7c67d7817a0c1efead142048af4b2925df521f5ee011a6bbb797c0220268bc28e0b7f3cefd25b765801d25bc6de760361d5efc4ff4cc37721f191770c0147304402203425355cf89de0872aac696e9a54e1eb47d71b67a878b4291e505f5358b4c8f102205b7bf0aecb2c022fb93d72512fe820b1f9c0f396a80a75dbc72517d41006e12001483045022100b73867e6de73cf880d7eaa7a70f74685284ea52f3fae71eca4e8905d88367248022033f60a882ec544e5607201602229ddc7508c39ec25de2ee9e18e0bffc8f1130f014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d00000000dcfe020000000000fd4501483045022100e565de2989fb12d59dceb8717fa7efe1d430031701a178e93a860b6e7c593b5302207a2c8ee53cc8deceb79e959fdbe72f7fc30e97e51652ee6043687a9f68e24eee01473044022035da650da8f643511b343b3314aab45635f5c7a70d09a34eccf208ee8c0285a602206d3125eeb0ba309c14dbac2a67fc807f8e805d210e8b4d36966ca67e80e8c5a001483045022100d2336814da44438d4e6792dec2ed9017ffe295dff7877a326eda47e76bfa587302200e5ddfdf57537fb32b0ff333744c4a426488b8199923de93691e138e7a00928a014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000ca0e030000000000fd440148304502210085f57e21fb1cd388db4e5c62353dc7f520eab422f3d7ef8624552d1385199ce502206565aa1221fc512ae6236bafc08cb8157d774e1d499ebbbe712c10032f5aaf1601473044022047a029d05cfc8da77b76625abb93ee65fa4ffd23672a7d0348e45c145cba13050220740e4bb703957e56da185bfcaece5e4d3cc602271201dadb2d5afb5475c96283014730440220573c5fd3b203915a057765b361f08082e2d891e57bcd010ecaf6e8d307358b54022018848d5a770f33d667e792853f454359d1bf1c1c846dd00ee3cb3296602dd5b8014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d000000000bf8020000000000fd450147304402203b89df3d311981d4918681f0ee17a629ea632592342085cf1c1794f75626b7d00220141f2bb0d18b5f07d5549d279437c82319ba23b5e23d8b37ee217150331fe05f01483045022100a5b0d516a0457646836b850c195c8389b2747abbf747a557e96059190ad1e38302202ab7e8d5001290350544299dc3556e9abb9da2e8f91dd2ce3b00fa0c718cdec601483045022100f7f5b03dd0f3d6a7edf18847427ac60fb49cb626b074cae1ea95f233960a0729022000aff8e788a856b194effca3a08594ce28d97138143f0b205556886b4a098fec014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000008705030000000000fd4501483045022100c9728ad82c64ab5d548752adf590654a58eb4bfaba85f1f504b57708abc3eb5902204c36886103e1aa4da4c96bb52182710806b25b9b18310d4b9eb574027eddb3100147304402205017577d2e107371b7c8bd17bc15e128e4e1186cef93ed260d6f54947e94382602200ef247802da72844a33163e6d12136027538c6855ff5761aa5ffc65fda7c810a01483045022100b56e938da3ea2ae0248afa7f6be5e8f7ddb81cd3c82e16a41919864482c28ca102202eeb3e20e95448e46c340736d980fc1b75d860264edb814f01e0e430521f51d4014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000004203030000000000fd4501473044022032a1358398cad98c16a8829ffc75fc7b44f77b93d5627dc717b66a44b59dff7102207b0472f44654abdfe1a24eda879f28838bd5184dfba238fb1b3d4ed0adba15a7014830450221008cbaf2f1616afd756d773443a52e55acafdf5202c7cecc5d0775d681de0e903902201218f989b0300a0d75672676a20c3136ef671f5a4c52ea34213eed22b3ee382401483045022100bb7ccc2f7822a511aac2104ef57e34a04c37f3e09bcff1bc779df446a0840d020220789dece4f3172564a56b6001dfddfc1b79e41ab6d156a40f020bb5dde18877a6014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d0000000054fc020000000000fd4401473044022021c6612e0d9d3a57628d54fdfb7ee9756d41ad3247c8c460c499164b6ced3fb302206cb072bf1b753b8ff407b767160acf074f6f34286f4caf3ab5061fb4c980fd270147304402203b4f06ae4d5314e5dd95341c9668b16bc4a71efa1ea4bda2403d2be25e9baf8e02200f09352867d755d614bdb6af4cf22211c93c420fec4fa6fcdacae32b11d1036a01483045022100efd849b2c87782427ce6241c9cb87c60eb0202c12d1ce5f2306b25e857aa8f8d02202eac2b493b0be06093bd4c35d111fb5bdcf24d5795449ebdcbbf234237ecb837014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae23aaa80d00000000c2f3020000000000fd450147304402202649f1955b9a482cab0eef5bde264adf912f5c9351c5d9c10ebd74a7b41dda4602205675c9cfb36ad8ed29e7d431bb7b362e001e999b52d10939636147d19d8de00a01483045022100cbe00b8a033296b9b9e269388a63fb96c4a8f75085c941fa32b037a5991932cb0220020788969972594ccac489afad39bcad1534628f6a649ad5b34f14e5e9421d8401483045022100b7160b1edb5919751ecf6d18c98cceeac9f5941c1854af8560432b34b77dd74b02207105457d94cc4839df8af02f9abc34302eca1b061666e6516d859ec73d89a748014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000009209030000000000fd4401483045022100a54e526956343cfc84ce3e428c138d40381003386375d4ebf9d246101ff678e402201c80e61034511ae79e486380e3a652af58ad5b489aa15f29ec56fb84864e768f0147304402203bdeb20123223d56f13e68817d4a29c87e690540019813b2cdcb4fecbab7044b0220396a1d6971b69b4ef3e63e8727f093b3d7bface1d677a798b4a2d5ece218e8fb0147304402203ff11e576e45e7254fa5959c93956a97c09bb123e95da708001f2ede5f9ba8c602200f9f36ba15933a54a4758d97b7842776bd9706c6a8fab6a58649b9040824634f014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000670a030000000000fd4301473044022029bb920d11e2bf9201d18e7730f3bc5f6553e62bc8314336ae9c8a5859914c1502203bdacff928d4d666507dd2583b9442959c88726a817c67348817cd65e6c3606601473044022077a8ad215d7005f651f3b73a4f5a2e33e98dd2168268e04eb41af2ed6435ce2d02200af0d91cd2880f4158d175e28bce4ddc3fe50ad78b87f969c3662ae242930d980147304402205479fac1941e63c1de3cb25c4760b13f6b62ca59cf7942fb3170fd2d195678f9022040e827f224ea5551a4dad03daaf0ad63ea470b4fceffa8e4bd495e7560f32289014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000fc11030000000000fd440147304402205c09825c601853704b1b0231fbee7591c79b6cbaf30e2e955a0b6a72793b370502203fa13d433613f7e6e63419d5db0ade84cb18d699da64a2138fc086afb6066e8301483045022100eafd46546faf8c4e1152145d0a5c30ef070d5e455b6b3b685e1cb8f47cc2a0030220452568d3b52d7a6f7b1f30f321cb4481aaf8af3d7223d5e3de2d6612fa2c41bc01473044022012fc1168c457070445b883dfbbad6996a9dca5eb006b3755daeee5908918a6bc022063b589fae666c446199742cd78eb1843385660f2a962492476c6cacf3997c9eb014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000a60f030000000000fd4501483045022100872b40bd06e8bcf66cbc8ce478d9ccb62972670682146d19b06e63b919a86da0022071cf9163b60e61078f4a5be7a46cee4978585f7eb27387c0b27ae696c3d79f4701483045022100848eb32ab0284db06df29338a34ec5d63660d8ff8838501acc00694b203a346b02203f67e1f2a8d05894f14e965748ef9391675c263afc356519f3516df5adbc87ff0147304402201a20d9f8b3e236b0807d84a7d7e041d77ee26f002d2b77c695fecca316c01b5e022035edbd8bbd6917b892166fcf4b8202b93609e04440ef7b6d850c12409cb3c1e5014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d000000008409030000000000fd4401473044022009153c7b1bfdfe8ac504213c3ba3321ede43e2dc89261d803601ed6e87cace55022051969259b11cacb80d06ca68563ebdbf8a6e85fa88e9c14afc5797dc82f0fdca01483045022100951209e2d941dd5ba148ef2a0d4a0eaf09c9e0c22051719cb5f3a762517964ab02204acdcfddb8b8aa6a1f61b5126dfb607a472b22d5f731e1318f2405732fc54e750147304402200cd5be575d4b681975316726c211db5fbbc3037d29acdeb06bb7c72119fce5db0220514ea54faba32014d3422ca09c2ebcd332888370249af76809a9f5e4c380dfe6014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000e600030000000000fd45014830450221008d99ed1beda44a35e04ed4b6a20a9335c29d6fa974011c06f7a4467420b7ea39022041faf1da827bf32bc43e56a3de62512491209b1175c8779afe3e07731db7620001483045022100f38d3d3adec74d05204c0c9f0b79521abdc57f23d9599a0423d0227294a0ae3e02204ffe4c18ccf208ce95ade49bb78cfdd86aded1071e1fcede37e19252ee0b7882014730440220477a3fa0d716f8ca73d0f5cd55508231dc05d90d14ee670527f30c7f68406e4302201ee6504b744a53375afb2e84fab902ff8d299d6d897bed9db8dcbd85fd890bad014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae4e0b860d00000000ed13030000000000fd4301473044022075d9d2d1d73ff65f1c3cb5fc705aa3f117b866901a527607c81807408fc924180220062031fca37eed36da2f119ad7f5018f8fdd6ca01f42c4bfebbe8550c17e8d690147304402202897a5b6ecb4426de79bc5be348c4b359c7a9adfec0c152345538bfd5279a3e8022053992fd5f382089e1f41e0c47a1ecc11cdd604dfc46210e6053b7f690e7ec0bb01473044022031a31eeaac96cce93cd3a5fe3d75e7faf99f17a5bba04a6a8387057815663ced0220266867c47ad462ab2e5a794b5db1325e0dd75588aa45e10dfb9c219918eb7af3014c69532102bc2a9206d10e5d5173583cbafebc78998745abeca13ed33151c93afbd850ca8e2103c995ce342de266d561d6ab4b06c7a14adb0f0b30002997f076f71c8f8ac93c98210330d6c9371b561d2b961a5987d336c0186a9c5dacd6ed4551420b5977e46a29b453ae
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I know this is only for benchmarking but would have liked for this to be something like multiWitnessTxEncoded in msgtx_test.go where there are comments explain each tx.

@@ -37,6 +37,10 @@ $ go get -u github.com/btcsuite/btcd/txscript
* [Manually Signing a Transaction Output](https://pkg.go.dev/github.com/btcsuite/btcd/txscript#example-SignTxOutput)
Demonstrates manually creating and signing a redeem transaction.

* [Counting Opcodes in Scripts](http://godoc.org/github.com/decred/dcrd/txscript#example-ScriptTokenizer)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is trivial to add to the PR right? Might be better to just include the code snippet here and link it.

Or maybe just link toExampleScriptTokenizer() in txscript/example_test.go since that looks like the exact same thing

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm lean towards adding link to the example code here. @kcalvinalvin maybe we can do that as a sep. PR.

txscript/tokenizer.go Show resolved Hide resolved
txscript/tokenizer_test.go Outdated Show resolved Hide resolved
case -4:
dataLen = int32(binary.LittleEndian.Uint32(script[:4]))
default:
str := fmt.Sprintf("invalid opcode length %d", op.length)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

May want to leave a comment here that this case is impossible to hit as the opcodes grabbed are pre-defined in opcodeArray.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea, added!

@@ -843,11 +843,7 @@ func TestCalcMultiSigStats(t *testing.T) {
},
{
name: "multisig script",
script: "0 DATA_72 0x30450220106a3e4ef0b51b764a2887226" +
Copy link
Collaborator

@kcalvinalvin kcalvinalvin Nov 14, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

decred got rid of CODESEPARATOR so this wouldn't apply to them but I think the existing script should still parse as a multisig script. Right now it doesn't and throw a bug like so:

standard_test.go:868: CalcMultiSigStats #2 (multisig script): wrong error - got txscript.Error (script 004830450220106a3e4ef0b51b764a28872262ffef55846514dacbdcbbdd652c849d395b4384022100e03ae554c3cbb40600d31dd46fc33f25e47bf8525b1fe07282e3b6ecb5f3bb2801ab51210232abdc893e7f0631364d7fd01cb33d24da45329a00357b3a7886211ab414d55a51ae is not a multisig script), want <nil>FAIL

EDIT:Ok I see that extractMultisigScriptDetails mentions that it only parses standard scripts. Non-issue here.

Copy link
Member

@jcvernaleo jcvernaleo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm good with it going in as is @Roasbeef

OK

@kcalvinalvin
Copy link
Collaborator

kcalvinalvin commented Nov 16, 2021

I'm good with it going in as is @Roasbeef

OK

https://github.com/btcsuite/btcd/pull/1769/files/70dabecd6e6b00f536a209d75fc51289fc058177#r748811980

Could you take a look at this comment? I believe that the deleted script shouldn't throw an error but it does with the new code.

EDIT: Nvm it was a non-issue.

Copy link
Collaborator

@onyb onyb left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Leaving my approval here, as I've already reviewed and tested #1684.

davecgh and others added 22 commits November 16, 2021 18:45
This implements an efficient and zero-allocation script tokenizer that
is exported to both provide a new capability to tokenize scripts to
external consumers of the API as well as to serve as a base for
refactoring the existing highly inefficient internal code.

It is important to note that this tokenizer is intended to be used in
consensus critical code in the future, so it must exactly follow the
existing semantics.

The current script parsing mechanism used throughout the txscript module
is to fully tokenize the scripts into an array of internal parsed
opcodes which are then examined and passed around in order to implement
virtually everything related to scripts.

While that approach does simplify the analysis of certain scripts and
thus provide some nice properties in that regard, it is both extremely
inefficient in many cases, and makes it impossible for external
consumers of the API to implement any form of custom script analysis
without manually implementing a bunch of error prone tokenizing code or,
alternatively, the script engine exposing internal structures.

For example, as shown by profiling the total memory allocations of an
initial sync, the existing script parsing code allocates a total of
around 295.12GB, which equates to around 50% of all allocations
performed.  The zero-alloc tokenizer this introduces will allow that to
be reduced to virtually zero.

The following is a before and after comparison of tokenizing a large
script with a high opcode count using the existing code versus the
tokenizer this introduces for both speed and memory allocations:

benchmark                    old ns/op     new ns/op     delta
BenchmarkScriptParsing-8     63464         677           -98.93%

benchmark                    old allocs     new allocs     delta
BenchmarkScriptParsing-8     1              0              -100.00%

benchmark                    old bytes     new bytes     delta
BenchmarkScriptParsing-8     311299        0             -100.00%

The following is an overview of the changes:

- Introduce new error code ErrUnsupportedScriptVersion
- Implement zero-allocation script tokenizer
- Add a full suite of tests to ensure the tokenizer works as intended
  and follows the required consensus semantics
- Add an example of using the new tokenizer to count the number of
  opcodes in a script
- Update README.md to include the new example
- Update script parsing benchmark to use the new tokenizer
This converts the DisasmString function to make use of the new
zero-allocation script tokenizer instead of the far less efficient
parseScript thereby significantly optimizing the function.

In order to facilitate this, the opcode disassembly functionality is
split into a separate function called disasmOpcode that accepts the
opcode struct and data independently as opposed to requiring a parsed
opcode.  The new function also accepts a pointer to a string builder so
the disassembly can be more efficiently be built.

While here, the comment is modified to explicitly call out the script
version semantics.

The following is a before and after comparison of a large script:

benchmark                   old ns/op     new ns/op     delta
BenchmarkDisasmString-8     102902        40124         -61.01%

benchmark                   old allocs     new allocs     delta
BenchmarkDisasmString-8     46             51             +10.87%

benchmark                   old bytes     new bytes     delta
BenchmarkDisasmString-8     389324        130552        -66.47%
This introduces a new function named calcSignatureHashRaw which accepts
the raw script bytes to calculate the script hash versus requiring the
parsed opcode only to unparse them later in order to make it more
flexible for working with raw scripts.

Since there are several places in the rest of the code that currently
only have access to the parsed opcodes, this modifies the existing
calcSignatureHash to first unparse the script before calling the new
function.

Backport of decred/dcrd:f306a72a16eaabfb7054a26f9d9f850b87b00279
This modifies the CalcSignatureHash function to make use of the new
signature hash calculation function that accepts raw scripts without
needing to first parse them.  Consequently, it also doubles as a slight
optimization to the execution time and a significant reduction in the
number of allocations.

In order to convert the CalcScriptHash function and keep the same
semantics, a new function named checkScriptParses is introduced which
will quickly determine if a script can be fully parsed without failure
and return the parse failure in the case it can't.

The following is a before and after comparison of analyzing a large
multiple input transaction:

benchmark                  old ns/op     new ns/op     delta
BenchmarkCalcSigHash-8     3627895       3619477       -0.23%

benchmark                  old allocs     new allocs     delta
BenchmarkCalcSigHash-8     1335           801            -40.00%

benchmark                  old bytes     new bytes     delta
BenchmarkCalcSigHash-8     1373812       1293354       -5.86%
This converts the tests for calculating signature hashes to use the
exported function which handles the raw script versus the now deprecated
variant requiring parsed opcodes.

Backport of 06f769e
This converts the isSmallInt function to accept an opcode as a byte
instead of the internal opcode data struct in order to make it more
flexible for raw script analysis.

The comment is modified to explicitly call out the script version
semantics.

Finally, it updates all callers accordingly.
This converts the asSmallInt function to accept an opcode as a byte
instead of the internal opcode data struct in order to make it more
flexible for raw script analysis.

It also updates all callers accordingly.
This converts the IsPayToScriptHash function to analyze the raw script
instead of using the far less efficient parseScript, thereby
significantly optimizing the function.

In order to accomplish this, it introduces four new functions:
extractCompressedPubKey, extractUncompressedPubKey, extractPubKey, and
isPubKeyScript.  The extractPubKey function makes use of
extractCompressedPubKey and extractUncompressedPubKey to combine their
functionality as a convenience and isPubKeyScript is defined in terms of
extractPubKey.

The extractCompressedPubKey works with the raw script bytes to
simultaneously determine if the script is a pay-to-compressed-pubkey
script, and in the case it is, extract and return the raw compressed
pubkey bytes.

Similarly, the extractUncompressedPubKey works in the same way except it
determines if the script is a pay-to-uncompressed-pubkey script and
returns the raw uncompressed pubkey bytes in the case it is.

The extract function approach was chosen because it is common for
callers to want to only extract relevant details from a script if the
script is of the specific type.  Extracting those details requires
performing the exact same checks to ensure the script is of the correct
type, so it is more efficient to combine the two into one and define the
type determination in terms of the result so long as the extraction does
not require allocations.

The following is a before and after comparison of analyzing a large
script:

benchmark                     old ns/op     new ns/op     delta
BenchmarkIsPubKeyScript-8     62323         2.97          -100.00%

benchmark                     old allocs     new allocs     delta
BenchmarkIsPubKeyScript-8     1              0              -100.00%

benchmark                     old bytes     new bytes     delta
BenchmarkIsPubKeyScript-8     311299        0             -100.00%
This converts the IsPayToPubKeyHash function to analyze the raw script
instead of using the far less efficient parseScript, thereby
significantly optimization the function.

In order to accomplish this, it introduces two new functions.  The first
one is named extractPubKeyHash and works with the raw script bytes
to simultaneously determine if the script is a pay-to-pubkey-hash script,
and in the case it is, extract and return the hash.  The second new
function is named isPubKeyHashScript and is defined in terms of the
former.

The extract function approach was chosen because it is common for
callers to want to only extract relevant details from a script if the
script is of the specific type.  Extracting those details requires
performing the exact same checks to ensure the script is of the correct
type, so it is more efficient to combine the two into one and define the
type determination in terms of the result so long as the extraction does
not require allocations.

The following is a before and after comparison of analyzing a large
script:

benchmark                         old ns/op     new ns/op     delta
BenchmarkIsPubKeyHashScript-8     62228         0.45          -100.00%

benchmark                         old allocs     new allocs     delta
BenchmarkIsPubKeyHashScript-8     1              0              -100.00%

benchmark                         old bytes     new bytes     delta
BenchmarkIsPubKeyHashScript-8     311299        0             -100.00%
This converts the IsPayToScriptHash function to analyze the raw script
instead of using the far less efficient parseScript thereby
significantly optimizing the function.

In order to accomplish this, it introduces two new functions.  The first
one is named extractScriptHash and works with the raw script bytes to
simultaneously determine if the script is a p2sh script, and in the case
it is, extract and return the hash.  The second new function is named
isScriptHashScript and is defined in terms of the former.

The extract function approach was chosen because it is common for
callers to want to only extract relevant details from a script if the
script is of the specific type.  Extracting those details requires
performing the exact same checks to ensure the script is of the correct
type, so it is more efficient to combine the two into one and define the
type determination in terms of the result so long as the extraction does
not require allocations.

Finally, this also deprecates the isScriptHash function that requires
opcodes in favor of the new functions and modifies the comment on
IsPayToScriptHash to explicitly call out the script version semantics.

The following is a before and after comparison of analyzing a large
script that is not a p2sh script:

benchmark                        old ns/op     new ns/op     delta
BenchmarkIsPayToScriptHash-8     62393         0.60          -100.00%

benchmark                        old allocs     new allocs     delta
BenchmarkIsPayToScriptHash-8     1              0              -100.00%

benchmark                        old bytes     new bytes     delta
BenchmarkIsPayToScriptHash-8     311299        0             -100.00%
This converts the IsMultisigScript function to make use of the new
tokenizer instead of the far less efficient parseScript thereby
significantly optimizing the function.

In order to accomplish this, it introduces two new functions.  The first
one is named extractMultisigScriptDetails and works with the raw script
bytes to simultaneously determine if the script is a multisignature
script, and in the case it is, extract and return the relevant details.
The second new function is named isMultisigScript and is defined in
terms of the former.

The extract function accepts the script version, raw script bytes, and a
flag to determine whether or not the public keys should also be
extracted.  The flag is provided because extracting pubkeys results in
an allocation that the caller might wish to avoid.

The extract function approach was chosen because it is common for
callers to want to only extract relevant details from a script if the
script is of the specific type.  Extracting those details requires
performing the exact same checks to ensure the script is of the correct
type, so it is more efficient to combine the two into one and define the
type determination in terms of the result so long as the extraction does
not require allocations.

It is important to note that this new implementation intentionally has a
semantic difference from the existing implementation in that it will now
correctly identify a multisig script with zero pubkeys whereas
previously it incorrectly required at least one pubkey.  This change is
acceptable because the function only deals with standardness rather than
consensus rules.

Finally, this also deprecates the isMultiSig function that requires
opcodes in favor of the new functions and deprecates the error return on
the export IsMultisigScript function since it really does not make sense
given the purpose of the function.

The following is a before and after comparison of analyzing both a large
script that is not a multisig script and a 1-of-2 multisig public key
script:

benchmark                            old ns/op     new ns/op     delta
BenchmarkIsMultisigScriptLarge-8     64166         5.52          -99.99%
BenchmarkIsMultisigScript-8          630           59.4          -90.57%

benchmark                            old allocs     new allocs     delta
BenchmarkIsMultisigScriptLarge-8     1              0              -100.00%
BenchmarkIsMultisigScript-8          1              0              -100.00%

benchmark                            old bytes     new bytes     delta
BenchmarkIsMultisigScriptLarge-8     311299        0             -100.00%
BenchmarkIsMultisigScript-8          2304          0             -100.00%
This converts the IsMultisigSigScript function to analyze the raw script
and make use of the new tokenizer instead of the far less efficient
parseScript thereby significantly optimizing the function.

In order to accomplish this, it first rejects scripts that can't
possibly fit the bill due to the final byte of what would be the redeem
script not being the appropriate opcode or the overall script not having
enough bytes.  Then, it uses a new function that is introduced named
finalOpcodeData that uses the tokenizer to return any data associated
with the final opcode in the signature script (which will be nil for
non-push opcodes or if the script fails to parse) and analyzes it as if
it were a redeem script when it is non nil.

It is also worth noting that this new implementation intentionally has
the same semantic difference from the existing implementation as the
updated IsMultisigScript function in regards to allowing zero pubkeys
whereas previously it incorrectly required at least one pubkey.

Finally, the comment is modified to explicitly call out the script
version semantics.

The following is a before and after comparison of analyzing a large
script that is not a multisig script and both a 1-of-2 multisig public
key script (which should be false) and a signature script comprised of a
pay-to-script-hash 1-of-2 multisig redeem script (which should be true):

benchmark                               old ns/op     new ns/op     delta
BenchmarkIsMultisigSigScriptLarge-8     69328         2.93          -100.00%
BenchmarkIsMultisigSigScript-8          2375          146           -93.85%

benchmark                               old allocs     new allocs     delta
BenchmarkIsMultisigSigScriptLarge-8     5              0              -100.00%
BenchmarkIsMultisigSigScript-8          3              0              -100.00%

benchmark                               old bytes     new bytes     delta
BenchmarkIsMultisigSigScriptLarge-8     330035        0             -100.00%
BenchmarkIsMultisigSigScript-8          9472          0             -100.00%
davecgh and others added 24 commits November 16, 2021 18:48
This introduces a new function named removeOpcodeByDataRaw which accepts
the raw scripts and data to remove versus requiring the parsed opcodes
to both significantly optimize it as well as make it more flexible for
working with raw scripts.

There are several places in the rest of the code that currently only
have access to the parsed opcodes, so this only introduces the function
for use in the future and deprecates the existing one.

Note that, in practice, the script will never actually contain the data
that is intended to be removed since the function is only used during
signature verification to remove the signature itself which would
require some incredibly non-standard code to create.

Thus, as an optimization, it avoids allocating a new script unless there
is actually a match that needs to be removed.

Finally, it updates the tests to use the new function.
This converts the isDisabled function defined on a parsed opcode to a
standalone function which accepts an opcode as a byte instead in order
to make it more flexible for raw script analysis.

It also updates all callers accordingly.
This converts the alwaysIllegal function defined on a parsed opcode to a
standalone function named isOpcodeAlwaysIllegal which accepts an opcode
as a byte instead in order to make it more flexible for raw script
analysis.

It also updates all callers accordingly.
This converts the isConditional function defined on a parsed opcode to a
standalone function named isOpcodeConditional which accepts an opcode as
a byte instead in order to make it more flexible for raw script
analysis.

It also updates all callers accordingly.
This converts the checkMinimalDataPush function defined on a parsed
opcode to a standalone function which accepts an opcode and data slice
instead in order to make it more flexible for raw script analysis.

It also updates all callers accordingly.
This converts the engine's current program counter disasembly to make
use of the standalone disassembly function to remove the dependency on
the parsed opcode struct.

It also updates the tests accordingly.
This refactors the script engine to store and step through raw scripts
by making using of the new zero-allocation script tokenizer as opposed
to the less efficient method of storing and stepping through parsed
opcodes.  It also improves several aspects while refactoring such as
optimizing the disassembly trace, showing all scripts in the trace in
the case of execution failure, and providing additional comments
describing the purpose of each field in the engine.

It should be noted that this is a step towards removing the parsed
opcode struct and associated supporting code altogether, however, in
order to ease the review process, this retains the struct and all
function signatures for opcode execution which make use of an individual
parsed opcode.  Those will be updated in future commits.

The following is an overview of the changes:

- Modify internal engine scripts slice to use raw scripts instead of
  parsed opcodes
- Introduce a tokenizer to the engine to track the current script
- Remove no longer needed script offset parameter from the engine since
  that is tracked by the tokenizer
- Add an opcode index counter for disassembly purposes to the engine
- Update check for valid program counter to only consider the script
  index
  - Update tests for bad program counter accordingly
- Rework the NewEngine function
  - Store the raw scripts
  - Setup the initial tokenizer
  - Explicitly check against version 0 instead of DefaultScriptVersion
    which would break consensus if changed
  - Check the scripts parse according to version 0 semantics to retain
    current consensus rules
  - Improve comments throughout
- Rework the Step function
  - Use the tokenizer and raw scripts
  - Create a parsed opcode on the fly for now to retain existing
    opcode execution function signatures
  - Improve comments throughout
- Update the Execute function
  - Explicitly check against version 0 instead of DefaultScriptVersion
    which would break consensus if changed
  - Improve the disassembly tracing in the case of error
- Update the CheckErrorCondition function
  - Modify clean stack error message to make sense in all cases
  - Improve the comments
- Update the DisasmPC and DisasmScript functions on the engine
  - Use the tokenizer
  - Optimize construction via the use of strings.Builder
- Modify the subScript function to return the raw script bytes since the
  parsed opcodes are no longer stored
- Update the various signature checking opcodes to use the raw opcode
  data removal and signature hash calculation functions since the
  subscript is now a raw script
  - opcodeCheckSig
  - opcodeCheckMultiSig
  - opcodeCheckSigAlt
This renames the removeOpcodeByDataRaw to removeOpcodeByData now that
the old version has been removed.
Also remove tests associated with unparsing opcodes accordingly.
Also remove tests associated with the func accordingly.
This converts the executeOpcode function defined on the engine to accept
an opcode and data slice instead of a parsed opcode as a step towards
removing the parsed opcode struct and associated supporting code altogether.

It also updates all callers accordingly.
This converts the callback function defined on the internal opcode
struct to accept the opcode and data slice instead of a parsed opcode as
the final step towards removing the parsed opcode struct and associated
supporting code altogether.

It also updates all of the callbacks and tests accordingly and finally
removes the now unused parsedOpcode struct.

The final results for the raw script analysis and tokenizer
optimizations are as follows:

benchmark                                       old ns/op     new ns/op     delta
BenchmarkIsPayToScriptHash-8                    62393         0.51          -100.00%
BenchmarkIsPubKeyHashScript-8                   62228         0.56          -100.00%
BenchmarkGetSigOpCount-8                        61051         658           -98.92%
BenchmarkExtractPkScriptAddrsLarge-8            60713         17.2          -99.97%
BenchmarkExtractPkScriptAddrs-8                 289           17.9          -93.81%
BenchmarkIsWitnessPubKeyHash-8                  61688         0.42          -100.00%
BenchmarkIsUnspendable-8                        656           520           -20.73%
BenchmarkExtractAtomicSwapDataPushesLarge-8     61332         44.0          -99.93%
BenchmarkExtractAtomicSwapDataPushes-8          990           260           -73.74%
BenchmarkDisasmString-8                         102902        39754         -61.37%
BenchmarkGetPreciseSigOpCount-8                 130223        715           -99.45%
BenchmarkScriptParsing-8                        63464         681           -98.93%
BenchmarkIsMultisigScriptLarge-8                64166         5.83          -99.99%
BenchmarkIsMultisigScript-8                     630           58.5          -90.71%
BenchmarkPushedData-8                           64837         1779          -97.26%
BenchmarkCalcSigHash-8                          3627895       3605459       -0.62%
BenchmarkIsPubKeyScript-8                       62323         2.83          -100.00%
BenchmarkIsPushOnlyScript-8                     62412         569           -99.09%
BenchmarkIsWitnessScriptHash-8                  61243         0.56          -100.00%
BenchmarkGetScriptClass-8                       61515         16.4          -99.97%
BenchmarkIsNullDataScript-8                     62495         2.53          -100.00%
BenchmarkIsMultisigSigScriptLarge-8             69328         2.52          -100.00%
BenchmarkIsMultisigSigScript-8                  2375          141           -94.06%
BenchmarkGetWitnessSigOpCountP2WKH-8            504           72.0          -85.71%
BenchmarkGetWitnessSigOpCountNested-8           1158          136           -88.26%
BenchmarkIsWitnessPubKeyHash-8                  68927         0.53          -100.00%
BenchmarkIsWitnessScriptHash-8                  62774         0.63          -100.00%

benchmark                                       old allocs     new allocs     delta
BenchmarkIsPayToScriptHash-8                    1              0              -100.00%
BenchmarkIsPubKeyHashScript-8                   1              0              -100.00%
BenchmarkGetSigOpCount-8                        1              0              -100.00%
BenchmarkExtractPkScriptAddrsLarge-8            1              0              -100.00%
BenchmarkExtractPkScriptAddrs-8                 1              0              -100.00%
BenchmarkIsWitnessPubKeyHash-8                  1              0              -100.00%
BenchmarkIsUnspendable-8                        1              0              -100.00%
BenchmarkExtractAtomicSwapDataPushesLarge-8     1              0              -100.00%
BenchmarkExtractAtomicSwapDataPushes-8          2              1              -50.00%
BenchmarkDisasmString-8                         46             51             +10.87%
BenchmarkGetPreciseSigOpCount-8                 3              0              -100.00%
BenchmarkScriptParsing-8                        1              0              -100.00%
BenchmarkIsMultisigScriptLarge-8                1              0              -100.00%
BenchmarkIsMultisigScript-8                     1              0              -100.00%
BenchmarkPushedData-8                           7              6              -14.29%
BenchmarkCalcSigHash-8                          1335           712            -46.67%
BenchmarkIsPubKeyScript-8                       1              0              -100.00%
BenchmarkIsPushOnlyScript-8                     1              0              -100.00%
BenchmarkIsWitnessScriptHash-8                  1              0              -100.00%
BenchmarkGetScriptClass-8                       1              0              -100.00%
BenchmarkIsNullDataScript-8                     1              0              -100.00%
BenchmarkIsMultisigSigScriptLarge-8             5              0              -100.00%
BenchmarkIsMultisigSigScript-8                  3              0              -100.00%
BenchmarkGetWitnessSigOpCountP2WKH-8            2              0              -100.00%
BenchmarkGetWitnessSigOpCountNested-8           4              0              -100.00%
BenchmarkIsWitnessPubKeyHash-8                  1              0              -100.00%
BenchmarkIsWitnessScriptHash-8                  1              0              -100.00%

benchmark                                       old bytes     new bytes     delta
BenchmarkIsPayToScriptHash-8                    311299        0             -100.00%
BenchmarkIsPubKeyHashScript-8                   311299        0             -100.00%
BenchmarkGetSigOpCount-8                        311299        0             -100.00%
BenchmarkExtractPkScriptAddrsLarge-8            311299        0             -100.00%
BenchmarkExtractPkScriptAddrs-8                 768           0             -100.00%
BenchmarkIsWitnessPubKeyHash-8                  311299        0             -100.00%
BenchmarkIsUnspendable-8                        1             0             -100.00%
BenchmarkExtractAtomicSwapDataPushesLarge-8     311299        0             -100.00%
BenchmarkExtractAtomicSwapDataPushes-8          3168          96            -96.97%
BenchmarkDisasmString-8                         389324        130552        -66.47%
BenchmarkGetPreciseSigOpCount-8                 623367        0             -100.00%
BenchmarkScriptParsing-8                        311299        0             -100.00%
BenchmarkIsMultisigScriptLarge-8                311299        0             -100.00%
BenchmarkIsMultisigScript-8                     2304          0             -100.00%
BenchmarkPushedData-8                           312816        1520          -99.51%
BenchmarkCalcSigHash-8                          1373812       1290507       -6.06%
BenchmarkIsPubKeyScript-8                       311299        0             -100.00%
BenchmarkIsPushOnlyScript-8                     311299        0             -100.00%
BenchmarkIsWitnessScriptHash-8                  311299        0             -100.00%
BenchmarkGetScriptClass-8                       311299        0             -100.00%
BenchmarkIsNullDataScript-8                     311299        0             -100.00%
BenchmarkIsMultisigSigScriptLarge-8             330035        0             -100.00%
BenchmarkIsMultisigSigScript-8                  9472          0             -100.00%
BenchmarkGetWitnessSigOpCountP2WKH-8            1408          0             -100.00%
BenchmarkGetWitnessSigOpCountNested-8           3200          0             -100.00%
BenchmarkIsWitnessPubKeyHash-8                  311299        0             -100.00%
BenchmarkIsWitnessScriptHash-8                  311299        0             -100.00%
@Roasbeef Roasbeef force-pushed the txscript_zero_alloc_optimization_refactor branch from 70dabec to b95ba0a Compare November 17, 2021 03:08
@Roasbeef
Copy link
Member Author

Thanks for the additional round of review y'all! Onwards to taproot!

(will resolve that example comment in another PR)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

7 participants