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

The Zero Allocations project #18849

Closed
wants to merge 9 commits into from
Prev Previous commit
Next Next commit
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>
  • Loading branch information
jb55 committed May 15, 2020
commit 83a425d25af033086744c1c8c892015014ed46bd
4 changes: 2 additions & 2 deletions src/compressor.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ static bool IsToPubKey(const CScript& script, CPubKey &pubkey)
return false;
}

bool CompressScript(const CScript& script, std::vector<unsigned char> &out)
bool CompressScript(const CScript& script, CompressedScript& out)
{
CKeyID keyID;
if (IsToKeyID(script, keyID)) {
Expand Down Expand Up @@ -92,7 +92,7 @@ unsigned int GetSpecialScriptSize(unsigned int nSize)
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) {
case 0x00:
Expand Down
20 changes: 16 additions & 4 deletions src/compressor.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,26 @@
#ifndef BITCOIN_COMPRESSOR_H
#define BITCOIN_COMPRESSOR_H

#include <prevector.h>
#include <primitives/transaction.h>
#include <script/script.h>
#include <serialize.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);
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.
Expand Down Expand Up @@ -51,7 +63,7 @@ struct ScriptCompression

template<typename Stream>
void Ser(Stream &s, const CScript& script) {
std::vector<unsigned char> compr;
CompressedScript compr;
if (CompressScript(script, compr)) {
s << MakeSpan(compr);
return;
Expand All @@ -66,7 +78,7 @@ struct ScriptCompression
unsigned int nSize = 0;
s >> VARINT(nSize);
if (nSize < nSpecialScripts) {
std::vector<unsigned char> vch(GetSpecialScriptSize(nSize), 0x00);
CompressedScript vch(GetSpecialScriptSize(nSize), 0x00);
s >> MakeSpan(vch);
DecompressScript(script, nSize, vch);
return;
Expand Down
8 changes: 4 additions & 4 deletions src/test/compress_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
BOOST_CHECK_EQUAL(script.size(), 25);

std::vector<unsigned char> out;
CompressedScript out;
bool done = CompressScript(script, out);
BOOST_CHECK_EQUAL(done, true);

Expand All @@ -89,7 +89,7 @@ BOOST_AUTO_TEST_CASE(compress_script_to_cscript_id)
script << OP_HASH160 << ToByteVector(CScriptID(redeemScript)) << OP_EQUAL;
BOOST_CHECK_EQUAL(script.size(), 23);

std::vector<unsigned char> out;
CompressedScript out;
bool done = CompressScript(script, out);
BOOST_CHECK_EQUAL(done, true);

Expand All @@ -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)
BOOST_CHECK_EQUAL(script.size(), 35);

std::vector<unsigned char> out;
CompressedScript out;
bool done = CompressScript(script, out);
BOOST_CHECK_EQUAL(done, true);

Expand All @@ -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)
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);
BOOST_CHECK_EQUAL(done, true);

Expand Down
8 changes: 5 additions & 3 deletions src/test/fuzz/script.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ void test_one_input(const std::vector<uint8_t>& buffer)
if (!script_opt) return;
const CScript script{*script_opt};

std::vector<unsigned char> compressed;
CompressedScript compressed;
if (CompressScript(script, compressed)) {
const unsigned int size = compressed[0];
compressed.erase(compressed.begin());
Expand Down Expand Up @@ -94,10 +94,12 @@ void test_one_input(const std::vector<uint8_t>& buffer)

{
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
if (bytes.size() >= 32) {
if (compressed_script.size() >= 32) {
CScript decompressed_script;
DecompressScript(decompressed_script, fuzzed_data_provider.ConsumeIntegral<unsigned int>(), bytes);
DecompressScript(decompressed_script, fuzzed_data_provider.ConsumeIntegral<unsigned int>(), compressed_script);
}
}

Expand Down