2015-12-13 14:51:43 +01:00
|
|
|
// Copyright (c) 2012-2015 The Bitcoin Core developers
|
2014-12-13 05:09:33 +01:00
|
|
|
// Distributed under the MIT software license, see the accompanying
|
2014-03-18 10:11:00 +01:00
|
|
|
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|
|
|
|
2020-03-19 23:46:56 +01:00
|
|
|
#include <serialize.h>
|
|
|
|
#include <streams.h>
|
|
|
|
#include <hash.h>
|
|
|
|
#include <test/test_dash.h>
|
2012-06-15 14:19:11 +02:00
|
|
|
|
2013-04-13 07:13:08 +02:00
|
|
|
#include <stdint.h>
|
2012-06-15 14:19:11 +02:00
|
|
|
|
2013-04-13 07:13:08 +02:00
|
|
|
#include <boost/test/unit_test.hpp>
|
2012-06-15 14:19:11 +02:00
|
|
|
|
2015-03-12 09:34:42 +01:00
|
|
|
BOOST_FIXTURE_TEST_SUITE(serialize_tests, BasicTestingSetup)
|
2012-06-15 14:19:11 +02:00
|
|
|
|
2017-07-27 16:28:05 +02:00
|
|
|
class CSerializeMethodsTestSingle
|
|
|
|
{
|
|
|
|
protected:
|
|
|
|
int intval;
|
|
|
|
bool boolval;
|
|
|
|
std::string stringval;
|
2018-03-30 13:10:00 +02:00
|
|
|
char charstrval[16];
|
2016-12-05 08:01:20 +01:00
|
|
|
CTransactionRef txval;
|
2017-07-27 16:28:05 +02:00
|
|
|
public:
|
|
|
|
CSerializeMethodsTestSingle() = default;
|
2018-04-17 19:25:34 +02:00
|
|
|
CSerializeMethodsTestSingle(int intvalin, bool boolvalin, std::string stringvalin, const char* charstrvalin, const CTransactionRef& txvalin) : intval(intvalin), boolval(boolvalin), stringval(std::move(stringvalin)), txval(txvalin)
|
2018-03-30 13:10:00 +02:00
|
|
|
{
|
|
|
|
memcpy(charstrval, charstrvalin, sizeof(charstrval));
|
|
|
|
}
|
|
|
|
|
2020-05-20 13:30:21 +02:00
|
|
|
SERIALIZE_METHODS(CSerializeMethodsTestSingle, obj)
|
|
|
|
{
|
|
|
|
READWRITE(obj.intval);
|
|
|
|
READWRITE(obj.boolval);
|
|
|
|
READWRITE(obj.stringval);
|
|
|
|
READWRITE(obj.charstrval);
|
|
|
|
READWRITE(obj.txval);
|
2017-07-27 16:28:05 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
bool operator==(const CSerializeMethodsTestSingle& rhs)
|
|
|
|
{
|
|
|
|
return intval == rhs.intval && \
|
|
|
|
boolval == rhs.boolval && \
|
|
|
|
stringval == rhs.stringval && \
|
|
|
|
strcmp(charstrval, rhs.charstrval) == 0 && \
|
2016-12-05 08:01:20 +01:00
|
|
|
*txval == *rhs.txval;
|
2017-07-27 16:28:05 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
class CSerializeMethodsTestMany : public CSerializeMethodsTestSingle
|
|
|
|
{
|
|
|
|
public:
|
|
|
|
using CSerializeMethodsTestSingle::CSerializeMethodsTestSingle;
|
|
|
|
|
2020-05-20 13:30:21 +02:00
|
|
|
SERIALIZE_METHODS(CSerializeMethodsTestMany, obj)
|
|
|
|
{
|
|
|
|
READWRITE(obj.intval, obj.boolval, obj.stringval, obj.charstrval, obj.txval);
|
2017-07-27 16:28:05 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2014-12-19 11:41:50 +01:00
|
|
|
BOOST_AUTO_TEST_CASE(sizes)
|
|
|
|
{
|
|
|
|
BOOST_CHECK_EQUAL(sizeof(char), GetSerializeSize(char(0), 0));
|
|
|
|
BOOST_CHECK_EQUAL(sizeof(int8_t), GetSerializeSize(int8_t(0), 0));
|
|
|
|
BOOST_CHECK_EQUAL(sizeof(uint8_t), GetSerializeSize(uint8_t(0), 0));
|
|
|
|
BOOST_CHECK_EQUAL(sizeof(int16_t), GetSerializeSize(int16_t(0), 0));
|
|
|
|
BOOST_CHECK_EQUAL(sizeof(uint16_t), GetSerializeSize(uint16_t(0), 0));
|
|
|
|
BOOST_CHECK_EQUAL(sizeof(int32_t), GetSerializeSize(int32_t(0), 0));
|
|
|
|
BOOST_CHECK_EQUAL(sizeof(uint32_t), GetSerializeSize(uint32_t(0), 0));
|
|
|
|
BOOST_CHECK_EQUAL(sizeof(int64_t), GetSerializeSize(int64_t(0), 0));
|
|
|
|
BOOST_CHECK_EQUAL(sizeof(uint64_t), GetSerializeSize(uint64_t(0), 0));
|
|
|
|
BOOST_CHECK_EQUAL(sizeof(float), GetSerializeSize(float(0), 0));
|
|
|
|
BOOST_CHECK_EQUAL(sizeof(double), GetSerializeSize(double(0), 0));
|
|
|
|
// Bool is serialized as char
|
|
|
|
BOOST_CHECK_EQUAL(sizeof(char), GetSerializeSize(bool(0), 0));
|
2015-01-14 11:07:13 +01:00
|
|
|
|
|
|
|
// Sanity-check GetSerializeSize and c++ type matching
|
2018-04-11 14:58:51 +02:00
|
|
|
BOOST_CHECK_EQUAL(GetSerializeSize(char(0), 0), 1U);
|
|
|
|
BOOST_CHECK_EQUAL(GetSerializeSize(int8_t(0), 0), 1U);
|
|
|
|
BOOST_CHECK_EQUAL(GetSerializeSize(uint8_t(0), 0), 1U);
|
|
|
|
BOOST_CHECK_EQUAL(GetSerializeSize(int16_t(0), 0), 2U);
|
|
|
|
BOOST_CHECK_EQUAL(GetSerializeSize(uint16_t(0), 0), 2U);
|
|
|
|
BOOST_CHECK_EQUAL(GetSerializeSize(int32_t(0), 0), 4U);
|
|
|
|
BOOST_CHECK_EQUAL(GetSerializeSize(uint32_t(0), 0), 4U);
|
|
|
|
BOOST_CHECK_EQUAL(GetSerializeSize(int64_t(0), 0), 8U);
|
|
|
|
BOOST_CHECK_EQUAL(GetSerializeSize(uint64_t(0), 0), 8U);
|
|
|
|
BOOST_CHECK_EQUAL(GetSerializeSize(float(0), 0), 4U);
|
|
|
|
BOOST_CHECK_EQUAL(GetSerializeSize(double(0), 0), 8U);
|
|
|
|
BOOST_CHECK_EQUAL(GetSerializeSize(bool(0), 0), 1U);
|
2014-12-19 11:41:50 +01:00
|
|
|
}
|
|
|
|
|
2015-01-14 11:07:13 +01:00
|
|
|
BOOST_AUTO_TEST_CASE(floats_conversion)
|
|
|
|
{
|
2017-01-29 16:05:06 +01:00
|
|
|
// Choose values that map unambiguously to binary floating point to avoid
|
2015-01-14 11:07:13 +01:00
|
|
|
// rounding issues at the compiler side.
|
|
|
|
BOOST_CHECK_EQUAL(ser_uint32_to_float(0x00000000), 0.0F);
|
|
|
|
BOOST_CHECK_EQUAL(ser_uint32_to_float(0x3f000000), 0.5F);
|
|
|
|
BOOST_CHECK_EQUAL(ser_uint32_to_float(0x3f800000), 1.0F);
|
|
|
|
BOOST_CHECK_EQUAL(ser_uint32_to_float(0x40000000), 2.0F);
|
|
|
|
BOOST_CHECK_EQUAL(ser_uint32_to_float(0x40800000), 4.0F);
|
|
|
|
BOOST_CHECK_EQUAL(ser_uint32_to_float(0x44444444), 785.066650390625F);
|
|
|
|
|
2018-04-11 14:58:51 +02:00
|
|
|
BOOST_CHECK_EQUAL(ser_float_to_uint32(0.0F), 0x00000000U);
|
|
|
|
BOOST_CHECK_EQUAL(ser_float_to_uint32(0.5F), 0x3f000000U);
|
|
|
|
BOOST_CHECK_EQUAL(ser_float_to_uint32(1.0F), 0x3f800000U);
|
|
|
|
BOOST_CHECK_EQUAL(ser_float_to_uint32(2.0F), 0x40000000U);
|
|
|
|
BOOST_CHECK_EQUAL(ser_float_to_uint32(4.0F), 0x40800000U);
|
|
|
|
BOOST_CHECK_EQUAL(ser_float_to_uint32(785.066650390625F), 0x44444444U);
|
2015-01-14 11:07:13 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
BOOST_AUTO_TEST_CASE(doubles_conversion)
|
|
|
|
{
|
2017-01-29 16:05:06 +01:00
|
|
|
// Choose values that map unambiguously to binary floating point to avoid
|
2015-01-14 11:07:13 +01:00
|
|
|
// rounding issues at the compiler side.
|
|
|
|
BOOST_CHECK_EQUAL(ser_uint64_to_double(0x0000000000000000ULL), 0.0);
|
|
|
|
BOOST_CHECK_EQUAL(ser_uint64_to_double(0x3fe0000000000000ULL), 0.5);
|
|
|
|
BOOST_CHECK_EQUAL(ser_uint64_to_double(0x3ff0000000000000ULL), 1.0);
|
|
|
|
BOOST_CHECK_EQUAL(ser_uint64_to_double(0x4000000000000000ULL), 2.0);
|
|
|
|
BOOST_CHECK_EQUAL(ser_uint64_to_double(0x4010000000000000ULL), 4.0);
|
|
|
|
BOOST_CHECK_EQUAL(ser_uint64_to_double(0x4088888880000000ULL), 785.066650390625);
|
|
|
|
|
|
|
|
BOOST_CHECK_EQUAL(ser_double_to_uint64(0.0), 0x0000000000000000ULL);
|
|
|
|
BOOST_CHECK_EQUAL(ser_double_to_uint64(0.5), 0x3fe0000000000000ULL);
|
|
|
|
BOOST_CHECK_EQUAL(ser_double_to_uint64(1.0), 0x3ff0000000000000ULL);
|
|
|
|
BOOST_CHECK_EQUAL(ser_double_to_uint64(2.0), 0x4000000000000000ULL);
|
|
|
|
BOOST_CHECK_EQUAL(ser_double_to_uint64(4.0), 0x4010000000000000ULL);
|
|
|
|
BOOST_CHECK_EQUAL(ser_double_to_uint64(785.066650390625), 0x4088888880000000ULL);
|
|
|
|
}
|
|
|
|
/*
|
|
|
|
Python code to generate the below hashes:
|
|
|
|
|
|
|
|
def reversed_hex(x):
|
|
|
|
return binascii.hexlify(''.join(reversed(x)))
|
|
|
|
def dsha256(x):
|
|
|
|
return hashlib.sha256(hashlib.sha256(x).digest()).digest()
|
|
|
|
|
|
|
|
reversed_hex(dsha256(''.join(struct.pack('<f', x) for x in range(0,1000)))) == '8e8b4cf3e4df8b332057e3e23af42ebc663b61e0495d5e7e32d85099d7f3fe0c'
|
|
|
|
reversed_hex(dsha256(''.join(struct.pack('<d', x) for x in range(0,1000)))) == '43d0c82591953c4eafe114590d392676a01585d25b25d433557f0d7878b23f96'
|
|
|
|
*/
|
2014-12-19 11:41:50 +01:00
|
|
|
BOOST_AUTO_TEST_CASE(floats)
|
|
|
|
{
|
2015-01-14 11:07:13 +01:00
|
|
|
CDataStream ss(SER_DISK, 0);
|
|
|
|
// encode
|
|
|
|
for (int i = 0; i < 1000; i++) {
|
|
|
|
ss << float(i);
|
|
|
|
}
|
|
|
|
BOOST_CHECK(Hash(ss.begin(), ss.end()) == uint256S("8e8b4cf3e4df8b332057e3e23af42ebc663b61e0495d5e7e32d85099d7f3fe0c"));
|
|
|
|
|
|
|
|
// decode
|
|
|
|
for (int i = 0; i < 1000; i++) {
|
|
|
|
float j;
|
|
|
|
ss >> j;
|
|
|
|
BOOST_CHECK_MESSAGE(i == j, "decoded:" << j << " expected:" << i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
BOOST_AUTO_TEST_CASE(doubles)
|
|
|
|
{
|
|
|
|
CDataStream ss(SER_DISK, 0);
|
|
|
|
// encode
|
|
|
|
for (int i = 0; i < 1000; i++) {
|
|
|
|
ss << double(i);
|
|
|
|
}
|
|
|
|
BOOST_CHECK(Hash(ss.begin(), ss.end()) == uint256S("43d0c82591953c4eafe114590d392676a01585d25b25d433557f0d7878b23f96"));
|
|
|
|
|
|
|
|
// decode
|
|
|
|
for (int i = 0; i < 1000; i++) {
|
|
|
|
double j;
|
|
|
|
ss >> j;
|
|
|
|
BOOST_CHECK_MESSAGE(i == j, "decoded:" << j << " expected:" << i);
|
|
|
|
}
|
2014-12-19 11:41:50 +01:00
|
|
|
}
|
|
|
|
|
2012-06-15 14:19:11 +02:00
|
|
|
BOOST_AUTO_TEST_CASE(varints)
|
|
|
|
{
|
|
|
|
// encode
|
|
|
|
|
|
|
|
CDataStream ss(SER_DISK, 0);
|
|
|
|
CDataStream::size_type size = 0;
|
|
|
|
for (int i = 0; i < 100000; i++) {
|
2020-12-17 13:20:31 +01:00
|
|
|
ss << VARINT(i, VarIntMode::NONNEGATIVE_SIGNED);
|
|
|
|
size += ::GetSerializeSize(VARINT(i, VarIntMode::NONNEGATIVE_SIGNED), 0, 0);
|
2012-06-15 14:19:11 +02:00
|
|
|
BOOST_CHECK(size == ss.size());
|
|
|
|
}
|
|
|
|
|
2013-04-13 07:13:08 +02:00
|
|
|
for (uint64_t i = 0; i < 100000000000ULL; i += 999999937) {
|
2012-06-15 14:19:11 +02:00
|
|
|
ss << VARINT(i);
|
|
|
|
size += ::GetSerializeSize(VARINT(i), 0, 0);
|
|
|
|
BOOST_CHECK(size == ss.size());
|
|
|
|
}
|
|
|
|
|
|
|
|
// decode
|
|
|
|
for (int i = 0; i < 100000; i++) {
|
2013-03-07 18:38:25 +01:00
|
|
|
int j = -1;
|
2020-12-17 13:20:31 +01:00
|
|
|
ss >> VARINT(j, VarIntMode::NONNEGATIVE_SIGNED);
|
2012-06-15 14:19:11 +02:00
|
|
|
BOOST_CHECK_MESSAGE(i == j, "decoded:" << j << " expected:" << i);
|
|
|
|
}
|
|
|
|
|
2013-04-13 07:13:08 +02:00
|
|
|
for (uint64_t i = 0; i < 100000000000ULL; i += 999999937) {
|
|
|
|
uint64_t j = -1;
|
2012-06-15 14:19:11 +02:00
|
|
|
ss >> VARINT(j);
|
|
|
|
BOOST_CHECK_MESSAGE(i == j, "decoded:" << j << " expected:" << i);
|
|
|
|
}
|
Reject non-canonically-encoded sizes
The length of vectors, maps, sets, etc are serialized using
Write/ReadCompactSize -- which, unfortunately, do not use a
unique encoding.
So deserializing and then re-serializing a transaction (for example)
can give you different bits than you started with. That doesn't
cause any problems that we are aware of, but it is exactly the type
of subtle mismatch that can lead to exploits.
With this pull, reading a non-canonical CompactSize throws an
exception, which means nodes will ignore 'tx' or 'block' or
other messages that are not properly encoded.
Please check my logic... but this change is safe with respect to
causing a network split. Old clients that receive
non-canonically-encoded transactions or blocks deserialize
them into CTransaction/CBlock structures in memory, and then
re-serialize them before relaying them to peers.
And please check my logic with respect to causing a blockchain
split: there are no CompactSize fields in the block header, so
the block hash is always canonical. The merkle root in the block
header is computed on a vector<CTransaction>, so
any non-canonical encoding of the transactions in 'tx' or 'block'
messages is erased as they are read into memory by old clients,
and does not affect the block hash. And, as noted above, old
clients re-serialize (with canonical encoding) 'tx' and 'block'
messages before relaying to peers.
2013-08-07 04:21:34 +02:00
|
|
|
}
|
|
|
|
|
2016-04-14 14:45:32 +02:00
|
|
|
BOOST_AUTO_TEST_CASE(varints_bitpatterns)
|
|
|
|
{
|
|
|
|
CDataStream ss(SER_DISK, 0);
|
2020-12-17 13:20:31 +01:00
|
|
|
ss << VARINT(0, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "00"); ss.clear();
|
|
|
|
ss << VARINT(0x7f, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "7f"); ss.clear();
|
|
|
|
ss << VARINT((int8_t)0x7f, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "7f"); ss.clear();
|
|
|
|
ss << VARINT(0x80, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "8000"); ss.clear();
|
2016-04-14 14:45:32 +02:00
|
|
|
ss << VARINT((uint8_t)0x80); BOOST_CHECK_EQUAL(HexStr(ss), "8000"); ss.clear();
|
2020-12-17 13:20:31 +01:00
|
|
|
ss << VARINT(0x1234, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "a334"); ss.clear();
|
|
|
|
ss << VARINT((int16_t)0x1234, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "a334"); ss.clear();
|
|
|
|
ss << VARINT(0xffff, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "82fe7f"); ss.clear();
|
2016-04-14 14:45:32 +02:00
|
|
|
ss << VARINT((uint16_t)0xffff); BOOST_CHECK_EQUAL(HexStr(ss), "82fe7f"); ss.clear();
|
2020-12-17 13:20:31 +01:00
|
|
|
ss << VARINT(0x123456, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "c7e756"); ss.clear();
|
|
|
|
ss << VARINT((int32_t)0x123456, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "c7e756"); ss.clear();
|
2016-04-14 14:45:32 +02:00
|
|
|
ss << VARINT(0x80123456U); BOOST_CHECK_EQUAL(HexStr(ss), "86ffc7e756"); ss.clear();
|
|
|
|
ss << VARINT((uint32_t)0x80123456U); BOOST_CHECK_EQUAL(HexStr(ss), "86ffc7e756"); ss.clear();
|
|
|
|
ss << VARINT(0xffffffff); BOOST_CHECK_EQUAL(HexStr(ss), "8efefefe7f"); ss.clear();
|
2020-12-17 13:20:31 +01:00
|
|
|
ss << VARINT(0x7fffffffffffffffLL, VarIntMode::NONNEGATIVE_SIGNED); BOOST_CHECK_EQUAL(HexStr(ss), "fefefefefefefefe7f"); ss.clear();
|
2016-04-14 14:45:32 +02:00
|
|
|
ss << VARINT(0xffffffffffffffffULL); BOOST_CHECK_EQUAL(HexStr(ss), "80fefefefefefefefe7f"); ss.clear();
|
|
|
|
}
|
|
|
|
|
Reject non-canonically-encoded sizes
The length of vectors, maps, sets, etc are serialized using
Write/ReadCompactSize -- which, unfortunately, do not use a
unique encoding.
So deserializing and then re-serializing a transaction (for example)
can give you different bits than you started with. That doesn't
cause any problems that we are aware of, but it is exactly the type
of subtle mismatch that can lead to exploits.
With this pull, reading a non-canonical CompactSize throws an
exception, which means nodes will ignore 'tx' or 'block' or
other messages that are not properly encoded.
Please check my logic... but this change is safe with respect to
causing a network split. Old clients that receive
non-canonically-encoded transactions or blocks deserialize
them into CTransaction/CBlock structures in memory, and then
re-serialize them before relaying them to peers.
And please check my logic with respect to causing a blockchain
split: there are no CompactSize fields in the block header, so
the block hash is always canonical. The merkle root in the block
header is computed on a vector<CTransaction>, so
any non-canonical encoding of the transactions in 'tx' or 'block'
messages is erased as they are read into memory by old clients,
and does not affect the block hash. And, as noted above, old
clients re-serialize (with canonical encoding) 'tx' and 'block'
messages before relaying to peers.
2013-08-07 04:21:34 +02:00
|
|
|
BOOST_AUTO_TEST_CASE(compactsize)
|
|
|
|
{
|
|
|
|
CDataStream ss(SER_DISK, 0);
|
2017-01-05 11:31:56 +01:00
|
|
|
std::vector<char>::size_type i, j;
|
Reject non-canonically-encoded sizes
The length of vectors, maps, sets, etc are serialized using
Write/ReadCompactSize -- which, unfortunately, do not use a
unique encoding.
So deserializing and then re-serializing a transaction (for example)
can give you different bits than you started with. That doesn't
cause any problems that we are aware of, but it is exactly the type
of subtle mismatch that can lead to exploits.
With this pull, reading a non-canonical CompactSize throws an
exception, which means nodes will ignore 'tx' or 'block' or
other messages that are not properly encoded.
Please check my logic... but this change is safe with respect to
causing a network split. Old clients that receive
non-canonically-encoded transactions or blocks deserialize
them into CTransaction/CBlock structures in memory, and then
re-serialize them before relaying them to peers.
And please check my logic with respect to causing a blockchain
split: there are no CompactSize fields in the block header, so
the block hash is always canonical. The merkle root in the block
header is computed on a vector<CTransaction>, so
any non-canonical encoding of the transactions in 'tx' or 'block'
messages is erased as they are read into memory by old clients,
and does not affect the block hash. And, as noted above, old
clients re-serialize (with canonical encoding) 'tx' and 'block'
messages before relaying to peers.
2013-08-07 04:21:34 +02:00
|
|
|
|
|
|
|
for (i = 1; i <= MAX_SIZE; i *= 2)
|
|
|
|
{
|
|
|
|
WriteCompactSize(ss, i-1);
|
|
|
|
WriteCompactSize(ss, i);
|
|
|
|
}
|
|
|
|
for (i = 1; i <= MAX_SIZE; i *= 2)
|
|
|
|
{
|
|
|
|
j = ReadCompactSize(ss);
|
|
|
|
BOOST_CHECK_MESSAGE((i-1) == j, "decoded:" << j << " expected:" << (i-1));
|
|
|
|
j = ReadCompactSize(ss);
|
|
|
|
BOOST_CHECK_MESSAGE(i == j, "decoded:" << j << " expected:" << i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
static bool isCanonicalException(const std::ios_base::failure& ex)
|
|
|
|
{
|
2013-12-16 01:26:04 +01:00
|
|
|
std::ios_base::failure expectedException("non-canonical ReadCompactSize()");
|
2013-12-15 15:25:41 +01:00
|
|
|
|
2013-12-16 01:26:04 +01:00
|
|
|
// The string returned by what() can be different for different platforms.
|
|
|
|
// Instead of directly comparing the ex.what() with an expected string,
|
2020-07-29 03:23:12 +02:00
|
|
|
// create an instance of exception to see if ex.what() matches
|
|
|
|
// the expected explanatory string returned by the exception instance.
|
2013-12-16 01:26:04 +01:00
|
|
|
return strcmp(expectedException.what(), ex.what()) == 0;
|
Reject non-canonically-encoded sizes
The length of vectors, maps, sets, etc are serialized using
Write/ReadCompactSize -- which, unfortunately, do not use a
unique encoding.
So deserializing and then re-serializing a transaction (for example)
can give you different bits than you started with. That doesn't
cause any problems that we are aware of, but it is exactly the type
of subtle mismatch that can lead to exploits.
With this pull, reading a non-canonical CompactSize throws an
exception, which means nodes will ignore 'tx' or 'block' or
other messages that are not properly encoded.
Please check my logic... but this change is safe with respect to
causing a network split. Old clients that receive
non-canonically-encoded transactions or blocks deserialize
them into CTransaction/CBlock structures in memory, and then
re-serialize them before relaying them to peers.
And please check my logic with respect to causing a blockchain
split: there are no CompactSize fields in the block header, so
the block hash is always canonical. The merkle root in the block
header is computed on a vector<CTransaction>, so
any non-canonical encoding of the transactions in 'tx' or 'block'
messages is erased as they are read into memory by old clients,
and does not affect the block hash. And, as noted above, old
clients re-serialize (with canonical encoding) 'tx' and 'block'
messages before relaying to peers.
2013-08-07 04:21:34 +02:00
|
|
|
}
|
|
|
|
|
2019-08-26 20:32:47 +02:00
|
|
|
BOOST_AUTO_TEST_CASE(vector_bool)
|
|
|
|
{
|
|
|
|
std::vector<uint8_t> vec1{1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1};
|
|
|
|
std::vector<bool> vec2{1, 0, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 1, 0, 0, 1};
|
|
|
|
|
|
|
|
BOOST_CHECK(vec1 == std::vector<uint8_t>(vec2.begin(), vec2.end()));
|
|
|
|
BOOST_CHECK(SerializeHash(vec1) == SerializeHash(vec2));
|
|
|
|
}
|
2013-12-15 15:25:41 +01:00
|
|
|
|
Reject non-canonically-encoded sizes
The length of vectors, maps, sets, etc are serialized using
Write/ReadCompactSize -- which, unfortunately, do not use a
unique encoding.
So deserializing and then re-serializing a transaction (for example)
can give you different bits than you started with. That doesn't
cause any problems that we are aware of, but it is exactly the type
of subtle mismatch that can lead to exploits.
With this pull, reading a non-canonical CompactSize throws an
exception, which means nodes will ignore 'tx' or 'block' or
other messages that are not properly encoded.
Please check my logic... but this change is safe with respect to
causing a network split. Old clients that receive
non-canonically-encoded transactions or blocks deserialize
them into CTransaction/CBlock structures in memory, and then
re-serialize them before relaying them to peers.
And please check my logic with respect to causing a blockchain
split: there are no CompactSize fields in the block header, so
the block hash is always canonical. The merkle root in the block
header is computed on a vector<CTransaction>, so
any non-canonical encoding of the transactions in 'tx' or 'block'
messages is erased as they are read into memory by old clients,
and does not affect the block hash. And, as noted above, old
clients re-serialize (with canonical encoding) 'tx' and 'block'
messages before relaying to peers.
2013-08-07 04:21:34 +02:00
|
|
|
BOOST_AUTO_TEST_CASE(noncanonical)
|
|
|
|
{
|
|
|
|
// Write some non-canonical CompactSize encodings, and
|
|
|
|
// make sure an exception is thrown when read back.
|
|
|
|
CDataStream ss(SER_DISK, 0);
|
2017-01-05 11:31:56 +01:00
|
|
|
std::vector<char>::size_type n;
|
Reject non-canonically-encoded sizes
The length of vectors, maps, sets, etc are serialized using
Write/ReadCompactSize -- which, unfortunately, do not use a
unique encoding.
So deserializing and then re-serializing a transaction (for example)
can give you different bits than you started with. That doesn't
cause any problems that we are aware of, but it is exactly the type
of subtle mismatch that can lead to exploits.
With this pull, reading a non-canonical CompactSize throws an
exception, which means nodes will ignore 'tx' or 'block' or
other messages that are not properly encoded.
Please check my logic... but this change is safe with respect to
causing a network split. Old clients that receive
non-canonically-encoded transactions or blocks deserialize
them into CTransaction/CBlock structures in memory, and then
re-serialize them before relaying them to peers.
And please check my logic with respect to causing a blockchain
split: there are no CompactSize fields in the block header, so
the block hash is always canonical. The merkle root in the block
header is computed on a vector<CTransaction>, so
any non-canonical encoding of the transactions in 'tx' or 'block'
messages is erased as they are read into memory by old clients,
and does not affect the block hash. And, as noted above, old
clients re-serialize (with canonical encoding) 'tx' and 'block'
messages before relaying to peers.
2013-08-07 04:21:34 +02:00
|
|
|
|
|
|
|
// zero encoded with three bytes:
|
|
|
|
ss.write("\xfd\x00\x00", 3);
|
|
|
|
BOOST_CHECK_EXCEPTION(ReadCompactSize(ss), std::ios_base::failure, isCanonicalException);
|
|
|
|
|
|
|
|
// 0xfc encoded with three bytes:
|
|
|
|
ss.write("\xfd\xfc\x00", 3);
|
|
|
|
BOOST_CHECK_EXCEPTION(ReadCompactSize(ss), std::ios_base::failure, isCanonicalException);
|
|
|
|
|
|
|
|
// 0xfd encoded with three bytes is OK:
|
|
|
|
ss.write("\xfd\xfd\x00", 3);
|
|
|
|
n = ReadCompactSize(ss);
|
|
|
|
BOOST_CHECK(n == 0xfd);
|
|
|
|
|
|
|
|
// zero encoded with five bytes:
|
|
|
|
ss.write("\xfe\x00\x00\x00\x00", 5);
|
|
|
|
BOOST_CHECK_EXCEPTION(ReadCompactSize(ss), std::ios_base::failure, isCanonicalException);
|
|
|
|
|
|
|
|
// 0xffff encoded with five bytes:
|
|
|
|
ss.write("\xfe\xff\xff\x00\x00", 5);
|
|
|
|
BOOST_CHECK_EXCEPTION(ReadCompactSize(ss), std::ios_base::failure, isCanonicalException);
|
|
|
|
|
|
|
|
// zero encoded with nine bytes:
|
|
|
|
ss.write("\xff\x00\x00\x00\x00\x00\x00\x00\x00", 9);
|
|
|
|
BOOST_CHECK_EXCEPTION(ReadCompactSize(ss), std::ios_base::failure, isCanonicalException);
|
2012-06-15 14:19:11 +02:00
|
|
|
|
Reject non-canonically-encoded sizes
The length of vectors, maps, sets, etc are serialized using
Write/ReadCompactSize -- which, unfortunately, do not use a
unique encoding.
So deserializing and then re-serializing a transaction (for example)
can give you different bits than you started with. That doesn't
cause any problems that we are aware of, but it is exactly the type
of subtle mismatch that can lead to exploits.
With this pull, reading a non-canonical CompactSize throws an
exception, which means nodes will ignore 'tx' or 'block' or
other messages that are not properly encoded.
Please check my logic... but this change is safe with respect to
causing a network split. Old clients that receive
non-canonically-encoded transactions or blocks deserialize
them into CTransaction/CBlock structures in memory, and then
re-serialize them before relaying them to peers.
And please check my logic with respect to causing a blockchain
split: there are no CompactSize fields in the block header, so
the block hash is always canonical. The merkle root in the block
header is computed on a vector<CTransaction>, so
any non-canonical encoding of the transactions in 'tx' or 'block'
messages is erased as they are read into memory by old clients,
and does not affect the block hash. And, as noted above, old
clients re-serialize (with canonical encoding) 'tx' and 'block'
messages before relaying to peers.
2013-08-07 04:21:34 +02:00
|
|
|
// 0x01ffffff encoded with nine bytes:
|
|
|
|
ss.write("\xff\xff\xff\xff\x01\x00\x00\x00\x00", 9);
|
|
|
|
BOOST_CHECK_EXCEPTION(ReadCompactSize(ss), std::ios_base::failure, isCanonicalException);
|
2012-06-15 14:19:11 +02:00
|
|
|
}
|
|
|
|
|
2013-10-29 02:16:27 +01:00
|
|
|
BOOST_AUTO_TEST_CASE(insert_delete)
|
|
|
|
{
|
|
|
|
// Test inserting/deleting bytes.
|
|
|
|
CDataStream ss(SER_DISK, 0);
|
2018-04-11 14:58:51 +02:00
|
|
|
BOOST_CHECK_EQUAL(ss.size(), 0U);
|
2013-10-29 02:16:27 +01:00
|
|
|
|
|
|
|
ss.write("\x00\x01\x02\xff", 4);
|
2018-04-11 14:58:51 +02:00
|
|
|
BOOST_CHECK_EQUAL(ss.size(), 4U);
|
2013-10-29 02:16:27 +01:00
|
|
|
|
|
|
|
char c = (char)11;
|
|
|
|
|
|
|
|
// Inserting at beginning/end/middle:
|
|
|
|
ss.insert(ss.begin(), c);
|
2018-04-11 14:58:51 +02:00
|
|
|
BOOST_CHECK_EQUAL(ss.size(), 5U);
|
2013-10-29 02:16:27 +01:00
|
|
|
BOOST_CHECK_EQUAL(ss[0], c);
|
|
|
|
BOOST_CHECK_EQUAL(ss[1], 0);
|
|
|
|
|
|
|
|
ss.insert(ss.end(), c);
|
2018-04-11 14:58:51 +02:00
|
|
|
BOOST_CHECK_EQUAL(ss.size(), 6U);
|
2013-10-29 02:16:27 +01:00
|
|
|
BOOST_CHECK_EQUAL(ss[4], (char)0xff);
|
|
|
|
BOOST_CHECK_EQUAL(ss[5], c);
|
|
|
|
|
|
|
|
ss.insert(ss.begin()+2, c);
|
2018-04-11 14:58:51 +02:00
|
|
|
BOOST_CHECK_EQUAL(ss.size(), 7U);
|
2013-10-29 02:16:27 +01:00
|
|
|
BOOST_CHECK_EQUAL(ss[2], c);
|
|
|
|
|
|
|
|
// Delete at beginning/end/middle
|
|
|
|
ss.erase(ss.begin());
|
2018-04-11 14:58:51 +02:00
|
|
|
BOOST_CHECK_EQUAL(ss.size(), 6U);
|
2013-10-29 02:16:27 +01:00
|
|
|
BOOST_CHECK_EQUAL(ss[0], 0);
|
|
|
|
|
|
|
|
ss.erase(ss.begin()+ss.size()-1);
|
2018-04-11 14:58:51 +02:00
|
|
|
BOOST_CHECK_EQUAL(ss.size(), 5U);
|
2013-10-29 02:16:27 +01:00
|
|
|
BOOST_CHECK_EQUAL(ss[4], (char)0xff);
|
|
|
|
|
|
|
|
ss.erase(ss.begin()+1);
|
2018-04-11 14:58:51 +02:00
|
|
|
BOOST_CHECK_EQUAL(ss.size(), 4U);
|
2013-10-29 02:16:27 +01:00
|
|
|
BOOST_CHECK_EQUAL(ss[0], 0);
|
|
|
|
BOOST_CHECK_EQUAL(ss[1], 1);
|
|
|
|
BOOST_CHECK_EQUAL(ss[2], 2);
|
|
|
|
BOOST_CHECK_EQUAL(ss[3], (char)0xff);
|
|
|
|
|
|
|
|
// Make sure GetAndClear does the right thing:
|
|
|
|
CSerializeData d;
|
|
|
|
ss.GetAndClear(d);
|
2018-04-11 14:58:51 +02:00
|
|
|
BOOST_CHECK_EQUAL(ss.size(), 0U);
|
2013-10-29 02:16:27 +01:00
|
|
|
}
|
|
|
|
|
2017-07-04 19:31:57 +02:00
|
|
|
// Change struct size and check if it can be deserialized
|
|
|
|
// from old version archive and vice versa
|
|
|
|
struct old_version
|
|
|
|
{
|
|
|
|
int field1;
|
|
|
|
|
2021-05-27 17:17:29 +02:00
|
|
|
SERIALIZE_METHODS(old_version, obj)
|
|
|
|
{
|
|
|
|
READWRITE(obj.field1);
|
2017-07-04 19:31:57 +02:00
|
|
|
}
|
2021-05-27 17:17:29 +02:00
|
|
|
};
|
|
|
|
|
2017-07-04 19:31:57 +02:00
|
|
|
struct new_version
|
|
|
|
{
|
|
|
|
int field1;
|
|
|
|
int field2;
|
|
|
|
|
2021-05-27 17:17:29 +02:00
|
|
|
template<typename Stream>
|
|
|
|
void Serialize(Stream &s) const
|
|
|
|
{
|
|
|
|
s << field1 << field2;
|
|
|
|
}
|
2017-07-04 19:31:57 +02:00
|
|
|
|
2021-05-27 17:17:29 +02:00
|
|
|
template<typename Stream>
|
|
|
|
void Unserialize(Stream &s)
|
|
|
|
{
|
|
|
|
s >> field1;
|
|
|
|
if (s.size() == 0) {
|
2017-07-04 19:31:57 +02:00
|
|
|
field2 = 0;
|
|
|
|
return;
|
|
|
|
}
|
2021-05-27 17:17:29 +02:00
|
|
|
s >> field2;
|
2017-07-04 19:31:57 +02:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
BOOST_AUTO_TEST_CASE(check_backward_compatibility)
|
|
|
|
{
|
|
|
|
CDataStream ss(SER_DISK, 0);
|
|
|
|
old_version old_src({5});
|
|
|
|
ss << old_src;
|
|
|
|
new_version new_dest({6, 7});
|
|
|
|
BOOST_REQUIRE_NO_THROW(ss >> new_dest);
|
|
|
|
BOOST_REQUIRE(old_src.field1 == new_dest.field1);
|
|
|
|
BOOST_REQUIRE(ss.size() == 0);
|
|
|
|
|
|
|
|
new_version new_src({6, 7});
|
|
|
|
ss << new_src;
|
|
|
|
old_version old_dest({5});
|
|
|
|
BOOST_REQUIRE_NO_THROW(ss >> old_dest);
|
|
|
|
BOOST_REQUIRE(new_src.field1 == old_dest.field1);
|
|
|
|
}
|
|
|
|
|
2017-07-27 16:28:05 +02:00
|
|
|
BOOST_AUTO_TEST_CASE(class_methods)
|
|
|
|
{
|
|
|
|
int intval(100);
|
|
|
|
bool boolval(true);
|
|
|
|
std::string stringval("testing");
|
2018-03-30 13:10:00 +02:00
|
|
|
const char charstrval[16] = "testing charstr";
|
2017-07-27 16:28:05 +02:00
|
|
|
CMutableTransaction txval;
|
2018-04-17 19:25:34 +02:00
|
|
|
CTransactionRef tx_ref{MakeTransactionRef(txval)};
|
|
|
|
CSerializeMethodsTestSingle methodtest1(intval, boolval, stringval, charstrval, tx_ref);
|
|
|
|
CSerializeMethodsTestMany methodtest2(intval, boolval, stringval, charstrval, tx_ref);
|
2017-07-27 16:28:05 +02:00
|
|
|
CSerializeMethodsTestSingle methodtest3;
|
|
|
|
CSerializeMethodsTestMany methodtest4;
|
|
|
|
CDataStream ss(SER_DISK, PROTOCOL_VERSION);
|
|
|
|
BOOST_CHECK(methodtest1 == methodtest2);
|
|
|
|
ss << methodtest1;
|
|
|
|
ss >> methodtest4;
|
|
|
|
ss << methodtest2;
|
|
|
|
ss >> methodtest3;
|
|
|
|
BOOST_CHECK(methodtest1 == methodtest2);
|
|
|
|
BOOST_CHECK(methodtest2 == methodtest3);
|
|
|
|
BOOST_CHECK(methodtest3 == methodtest4);
|
|
|
|
|
2018-03-30 13:10:00 +02:00
|
|
|
CDataStream ss2(SER_DISK, PROTOCOL_VERSION, intval, boolval, stringval, charstrval, txval);
|
2017-07-27 16:28:05 +02:00
|
|
|
ss2 >> methodtest3;
|
|
|
|
BOOST_CHECK(methodtest3 == methodtest4);
|
|
|
|
}
|
|
|
|
|
2012-06-15 14:19:11 +02:00
|
|
|
BOOST_AUTO_TEST_SUITE_END()
|