compressor: use a prevector in compressed script serialization

Use a prevector for stack allocation instead of heap allocation during
script compression and decompression. These functions were doing
millions of unnecessary heap allocations during IBD.

We introduce a CompressedScript type alias for this prevector. It is
size 33 as that is the maximum size of a compressed script.

Fix the DecompressScript header to match the variable name from
compressor.cpp

Signed-off-by: William Casarin <jb55@jb55.com>
This commit is contained in:
William Casarin
2020-05-01 17:31:38 -07:00
parent 844d2070a2
commit 83a425d25a
4 changed files with 27 additions and 13 deletions

View File

@@ -52,7 +52,7 @@ static bool IsToPubKey(const CScript& script, CPubKey &pubkey)
return false; return false;
} }
bool CompressScript(const CScript& script, std::vector<unsigned char> &out) bool CompressScript(const CScript& script, CompressedScript& out)
{ {
CKeyID keyID; CKeyID keyID;
if (IsToKeyID(script, keyID)) { if (IsToKeyID(script, keyID)) {
@@ -92,7 +92,7 @@ unsigned int GetSpecialScriptSize(unsigned int nSize)
return 0; return 0;
} }
bool DecompressScript(CScript& script, unsigned int nSize, const std::vector<unsigned char> &in) bool DecompressScript(CScript& script, unsigned int nSize, const CompressedScript& in)
{ {
switch(nSize) { switch(nSize) {
case 0x00: case 0x00:

View File

@@ -6,14 +6,26 @@
#ifndef BITCOIN_COMPRESSOR_H #ifndef BITCOIN_COMPRESSOR_H
#define BITCOIN_COMPRESSOR_H #define BITCOIN_COMPRESSOR_H
#include <prevector.h>
#include <primitives/transaction.h> #include <primitives/transaction.h>
#include <script/script.h> #include <script/script.h>
#include <serialize.h> #include <serialize.h>
#include <span.h> #include <span.h>
bool CompressScript(const CScript& script, std::vector<unsigned char> &out); /**
* This saves us from making many heap allocations when serializing
* and deserializing compressed scripts.
*
* This prevector size is determined by the largest .resize() in the
* CompressScript function. The largest compressed script format is a
* compressed public key, which is 33 bytes.
*/
using CompressedScript = prevector<33, unsigned char>;
bool CompressScript(const CScript& script, CompressedScript& out);
unsigned int GetSpecialScriptSize(unsigned int nSize); unsigned int GetSpecialScriptSize(unsigned int nSize);
bool DecompressScript(CScript& script, unsigned int nSize, const std::vector<unsigned char> &out); bool DecompressScript(CScript& script, unsigned int nSize, const CompressedScript& in);
/** /**
* Compress amount. * Compress amount.
@@ -51,7 +63,7 @@ struct ScriptCompression
template<typename Stream> template<typename Stream>
void Ser(Stream &s, const CScript& script) { void Ser(Stream &s, const CScript& script) {
std::vector<unsigned char> compr; CompressedScript compr;
if (CompressScript(script, compr)) { if (CompressScript(script, compr)) {
s << MakeSpan(compr); s << MakeSpan(compr);
return; return;
@@ -66,7 +78,7 @@ struct ScriptCompression
unsigned int nSize = 0; unsigned int nSize = 0;
s >> VARINT(nSize); s >> VARINT(nSize);
if (nSize < nSpecialScripts) { if (nSize < nSpecialScripts) {
std::vector<unsigned char> vch(GetSpecialScriptSize(nSize), 0x00); CompressedScript vch(GetSpecialScriptSize(nSize), 0x00);
s >> MakeSpan(vch); s >> MakeSpan(vch);
DecompressScript(script, nSize, vch); DecompressScript(script, nSize, vch);
return; return;

View File

@@ -72,7 +72,7 @@ BOOST_AUTO_TEST_CASE(compress_script_to_ckey_id)
CScript script = CScript() << OP_DUP << OP_HASH160 << ToByteVector(pubkey.GetID()) << OP_EQUALVERIFY << OP_CHECKSIG; CScript script = CScript() << OP_DUP << OP_HASH160 << ToByteVector(pubkey.GetID()) << OP_EQUALVERIFY << OP_CHECKSIG;
BOOST_CHECK_EQUAL(script.size(), 25); BOOST_CHECK_EQUAL(script.size(), 25);
std::vector<unsigned char> out; CompressedScript out;
bool done = CompressScript(script, out); bool done = CompressScript(script, out);
BOOST_CHECK_EQUAL(done, true); BOOST_CHECK_EQUAL(done, true);
@@ -89,7 +89,7 @@ BOOST_AUTO_TEST_CASE(compress_script_to_cscript_id)
script << OP_HASH160 << ToByteVector(CScriptID(redeemScript)) << OP_EQUAL; script << OP_HASH160 << ToByteVector(CScriptID(redeemScript)) << OP_EQUAL;
BOOST_CHECK_EQUAL(script.size(), 23); BOOST_CHECK_EQUAL(script.size(), 23);
std::vector<unsigned char> out; CompressedScript out;
bool done = CompressScript(script, out); bool done = CompressScript(script, out);
BOOST_CHECK_EQUAL(done, true); BOOST_CHECK_EQUAL(done, true);
@@ -107,7 +107,7 @@ BOOST_AUTO_TEST_CASE(compress_script_to_compressed_pubkey_id)
CScript script = CScript() << ToByteVector(key.GetPubKey()) << OP_CHECKSIG; // COMPRESSED_PUBLIC_KEY_SIZE (33) CScript script = CScript() << ToByteVector(key.GetPubKey()) << OP_CHECKSIG; // COMPRESSED_PUBLIC_KEY_SIZE (33)
BOOST_CHECK_EQUAL(script.size(), 35); BOOST_CHECK_EQUAL(script.size(), 35);
std::vector<unsigned char> out; CompressedScript out;
bool done = CompressScript(script, out); bool done = CompressScript(script, out);
BOOST_CHECK_EQUAL(done, true); BOOST_CHECK_EQUAL(done, true);
@@ -124,7 +124,7 @@ BOOST_AUTO_TEST_CASE(compress_script_to_uncompressed_pubkey_id)
CScript script = CScript() << ToByteVector(key.GetPubKey()) << OP_CHECKSIG; // PUBLIC_KEY_SIZE (65) CScript script = CScript() << ToByteVector(key.GetPubKey()) << OP_CHECKSIG; // PUBLIC_KEY_SIZE (65)
BOOST_CHECK_EQUAL(script.size(), 67); // 1 char code + 65 char pubkey + OP_CHECKSIG BOOST_CHECK_EQUAL(script.size(), 67); // 1 char code + 65 char pubkey + OP_CHECKSIG
std::vector<unsigned char> out; CompressedScript out;
bool done = CompressScript(script, out); bool done = CompressScript(script, out);
BOOST_CHECK_EQUAL(done, true); BOOST_CHECK_EQUAL(done, true);

View File

@@ -36,7 +36,7 @@ void test_one_input(const std::vector<uint8_t>& buffer)
if (!script_opt) return; if (!script_opt) return;
const CScript script{*script_opt}; const CScript script{*script_opt};
std::vector<unsigned char> compressed; CompressedScript compressed;
if (CompressScript(script, compressed)) { if (CompressScript(script, compressed)) {
const unsigned int size = compressed[0]; const unsigned int size = compressed[0];
compressed.erase(compressed.begin()); compressed.erase(compressed.begin());
@@ -94,10 +94,12 @@ void test_one_input(const std::vector<uint8_t>& buffer)
{ {
const std::vector<uint8_t> bytes = ConsumeRandomLengthByteVector(fuzzed_data_provider); const std::vector<uint8_t> bytes = ConsumeRandomLengthByteVector(fuzzed_data_provider);
CompressedScript compressed_script;
compressed_script.assign(bytes.begin(), bytes.end());
// DecompressScript(..., ..., bytes) is not guaranteed to be defined if the bytes vector is too short // DecompressScript(..., ..., bytes) is not guaranteed to be defined if the bytes vector is too short
if (bytes.size() >= 32) { if (compressed_script.size() >= 32) {
CScript decompressed_script; CScript decompressed_script;
DecompressScript(decompressed_script, fuzzed_data_provider.ConsumeIntegral<unsigned int>(), bytes); DecompressScript(decompressed_script, fuzzed_data_provider.ConsumeIntegral<unsigned int>(), compressed_script);
} }
} }