Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions include/bitcoin/system/chain/views/transaction_view.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,11 @@ class BC_API transaction_view final
transaction_view(reader& source, const data_chunk& block_buffer,
bool coinbase, bool witness) NOEXCEPT;

/// Serialization.
data_chunk to_data(bool witness) const NOEXCEPT;
void to_data(std::ostream& stream, bool witness) const NOEXCEPT;
void to_data(writer& sink, bool witness) const NOEXCEPT;

/// Properties.
bool is_valid() const NOEXCEPT;
bool is_coinbase() const NOEXCEPT;
Expand Down
28 changes: 13 additions & 15 deletions src/chain/views/block_view.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -76,22 +76,20 @@ void block_view::to_data(std::ostream& stream, bool witness) const NOEXCEPT
to_data(out, witness);
}

void block_view::to_data(writer& , bool ) const NOEXCEPT
void block_view::to_data(writer& sink, bool witness) const NOEXCEPT
{
////if (witness)
////{
//// sink.write_bytes(*buffer_);
//// return;
////}
////
// TODO: write header from first bytes in buffer.
////header_->to_data(sink);
////sink.write_variable(txs_->size());
////
// TODO: add writers to tx, skip witness as applicable.
////for (const auto& tx: *txs_)
//// tx->to_data(sink, witness);
BC_ASSERT_MSG(false, "not implemented");
// The witnessed form is the original buffer.
if (witness)
{
sink.write_bytes(*buffer_);
return;
}

// Strip witness: the header and transaction count are unaffected.
sink.write_bytes(buffer_->data(), header::serialized_size());
sink.write_variable(txs_.size());
for (const auto& tx: txs_)
tx.to_data(sink, false);
}

// public
Expand Down
38 changes: 38 additions & 0 deletions src/chain/views/transaction_view.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
#include <bitcoin/system/chain/views/transaction_view.hpp>

#include <iterator>
#include <bitcoin/system/chain/enums/magic_numbers.hpp>
#include <bitcoin/system/chain/point.hpp>
#include <bitcoin/system/chain/transaction.hpp>
Expand Down Expand Up @@ -189,6 +190,43 @@ size_t transaction_view::serialized_size(bool witness) const NOEXCEPT
return witness && is_segregated() ? size_ : stripped_size();
}

data_chunk transaction_view::to_data(bool witness) const NOEXCEPT
{
data_chunk data(serialized_size(witness));
stream::out::fast ostream(data);
write::bytes::fast out(ostream);
to_data(out, witness);
return data;
}

void transaction_view::to_data(std::ostream& stream,
bool witness) const NOEXCEPT
{
write::bytes::ostream out(stream);
to_data(out, witness);
}

void transaction_view::to_data(writer& sink, bool witness) const NOEXCEPT
{
// Witness can be stripped but never added (mirrors chain::transaction).
if (witness && is_segregated())
{
sink.write_bytes(tx_ptr_, size_);
return;
}

// Stripped form drops the witness marker/flag sentinels (after version)
// and the witness data (before locktime); inputs/outputs are unchanged.
const auto sentinels = is_zero(witnesses_size_) ? zero : sentinels_size;
const auto body = std::next(tx_ptr_, version_size + sentinels);
const auto body_size = size_ - witnesses_size_ - locktime_size -
version_size - sentinels;

sink.write_bytes(tx_ptr_, version_size);
sink.write_bytes(body, body_size);
sink.write_bytes(std::next(tx_ptr_, size_ - locktime_size), locktime_size);
}

const hash_digest& transaction_view::hash(bool witness) const NOEXCEPT
{
return witness && is_segregated() ? wtxid_ : txid_;
Expand Down
50 changes: 50 additions & 0 deletions test/chain/views/block_view.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,56 @@ BOOST_AUTO_TEST_CASE(block_view__construct__block1a_non_witness__valid)
BOOST_CHECK_EQUAL(view.serialized_size(false), block.serialized_size(false));
}

// to_data

BOOST_AUTO_TEST_CASE(block_view__to_data__genesis__matches_block)
{
const auto& block = test::genesis;
const chain::block_view view{ block.to_data(true), true };
BOOST_CHECK_EQUAL(view.to_data(true), block.to_data(true));
BOOST_CHECK_EQUAL(view.to_data(false), block.to_data(false));
}

BOOST_AUTO_TEST_CASE(block_view__to_data__block1a_witness__matches_block)
{
const auto& block = test::block1a;
const chain::block_view view{ block.to_data(true), true };
BOOST_CHECK_EQUAL(view.to_data(true), block.to_data(true));
BOOST_CHECK_EQUAL(view.to_data(false), block.to_data(false));
}

BOOST_AUTO_TEST_CASE(block_view__to_data__block1a_stripped_source__stripped)
{
const auto& block = test::block1a;
const chain::block_view view{ block.to_data(false), false };
BOOST_CHECK_EQUAL(view.to_data(true), block.to_data(false));
BOOST_CHECK_EQUAL(view.to_data(false), block.to_data(false));
}

BOOST_AUTO_TEST_CASE(block_view__to_data__block2a_witness__matches_block)
{
// block2a is a multi-transaction block bearing witness data.
const auto& block = test::block2a;
const chain::block_view view{ block.to_data(true), true };
BOOST_REQUIRE(view.is_segregated());
BOOST_CHECK_EQUAL(view.transactions(), block.transactions_ptr()->size());
BOOST_CHECK_EQUAL(view.to_data(true), block.to_data(true));
BOOST_CHECK_EQUAL(view.to_data(false), block.to_data(false));
}

BOOST_AUTO_TEST_CASE(block_view__to_data__mixed_witness_and_legacy__matches_block)
{
// block2c has one segregated (witness) and one legacy (non-witness)
// transaction, so the per-transaction witness strip is exercised both
// ways within one to_data(false) call.
const auto& block = test::block2c;
const chain::block_view view{ block.to_data(true), true };
BOOST_REQUIRE(view.is_segregated());
BOOST_REQUIRE_EQUAL(view.transactions(), 2u);
BOOST_CHECK_EQUAL(view.to_data(true), block.to_data(true));
BOOST_CHECK_EQUAL(view.to_data(false), block.to_data(false));
}

// identify1 and identify2

BOOST_AUTO_TEST_CASE(block_view__identify__genesis__expected)
Expand Down
82 changes: 82 additions & 0 deletions test/chain/views/transaction_view.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -273,4 +273,86 @@ BOOST_AUTO_TEST_CASE(transaction_view__write_witness__tx4_witness__expected)
BOOST_CHECK_EQUAL(witness, expected);
}

// to_data

BOOST_AUTO_TEST_CASE(transaction_view__to_data__tx4_witness__matches_transaction)
{
// Serialized to buffer WITH witness data, parsed for a witness node.
const auto transaction = test::tx4.to_data(true);
const auto& tx = test::tx4;
stream::in::fast istream{ transaction };
read::bytes::fast reader{ istream };

const chain::transaction_view view{ reader, transaction, false, true };
BOOST_REQUIRE(view.is_valid());
BOOST_REQUIRE(view.is_segregated());

// Witnessed form is a copy; stripped drops marker, flag and witness.
data_chunk witnessed(view.serialized_size(true));
stream::out::fast witnessed_stream(witnessed);
write::bytes::fast witnessed_sink(witnessed_stream);
view.to_data(witnessed_sink, true);
BOOST_CHECK_EQUAL(witnessed, tx.to_data(true));

data_chunk stripped(view.serialized_size(false));
stream::out::fast stripped_stream(stripped);
write::bytes::fast stripped_sink(stripped_stream);
view.to_data(stripped_sink, false);
BOOST_CHECK_EQUAL(stripped, tx.to_data(false));
}

BOOST_AUTO_TEST_CASE(transaction_view__to_data__tx4_stripped_source__stripped)
{
// Serialized to buffer WITHOUT witness data (nothing to strip).
const auto transaction = test::tx4.to_data(false);
const auto& tx = test::tx4;
stream::in::fast istream{ transaction };
read::bytes::fast reader{ istream };

const chain::transaction_view view{ reader, transaction, false, false };
BOOST_REQUIRE(view.is_valid());
BOOST_REQUIRE(!view.is_segregated());

// Both forms are the stripped bytes; witness is never added.
data_chunk witnessed(view.serialized_size(true));
stream::out::fast witnessed_stream(witnessed);
write::bytes::fast witnessed_sink(witnessed_stream);
view.to_data(witnessed_sink, true);
BOOST_CHECK_EQUAL(witnessed, tx.to_data(false));

data_chunk stripped(view.serialized_size(false));
stream::out::fast stripped_stream(stripped);
write::bytes::fast stripped_sink(stripped_stream);
view.to_data(stripped_sink, false);
BOOST_CHECK_EQUAL(stripped, tx.to_data(false));
}

BOOST_AUTO_TEST_CASE(transaction_view__to_data__tx4_overloads__match_transaction)
{
// Serialized to buffer WITH witness data, parsed for a witness node.
const auto transaction = test::tx4.to_data(true);
const auto& tx = test::tx4;
stream::in::fast istream{ transaction };
read::bytes::fast reader{ istream };

const chain::transaction_view view{ reader, transaction, false, true };
BOOST_REQUIRE(view.is_valid());
BOOST_REQUIRE(view.is_segregated());

// Chunk overload.
BOOST_CHECK_EQUAL(view.to_data(true), tx.to_data(true));
BOOST_CHECK_EQUAL(view.to_data(false), tx.to_data(false));

// Stream overload.
std::stringstream witnessed_stream{};
view.to_data(witnessed_stream, true);
BOOST_REQUIRE(witnessed_stream);
BOOST_CHECK_EQUAL(to_chunk(witnessed_stream.str()), tx.to_data(true));

std::stringstream stripped_stream{};
view.to_data(stripped_stream, false);
BOOST_REQUIRE(stripped_stream);
BOOST_CHECK_EQUAL(to_chunk(stripped_stream.str()), tx.to_data(false));
}

BOOST_AUTO_TEST_SUITE_END()
47 changes: 47 additions & 0 deletions test/mocks/blocks.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -730,6 +730,53 @@ const block block2b
}
}
};
const block block2c
{
header
{
0x31323334, // version
block1a.hash(), // previous_block_hash
hash_digest{ 0x2c },// merkle_root
0x41424344, // timestamp
0x51525354, // bits
0x61626364 // nonce
},
transactions
{
transaction // segregated (has witness)
{
0xb2, // version
inputs
{
input
{
point{ block1a.transactions_ptr()->front()->hash(false), 0x00 },
script{ { { opcode::checkmultisig }, { opcode::pick } } },
witness{ "[242424]" },
0xb2 // sequence
}
},
outputs{ output{ 0x81, script{ { { opcode::pick } } } } },
0x81 // locktime
},
transaction // legacy (no witness)
{
0xb3, // version
inputs
{
input
{
point{ block1a.transactions_ptr()->front()->hash(false), 0x01 },
script{ { { opcode::checkmultisig }, { opcode::roll } } },
witness{},
0x83 // sequence
}
},
outputs{ output{ 0x81, script{ { { opcode::pick } } } } },
0x81 // locktime
}
}
};
const transaction tx2b
{
transaction
Expand Down
1 change: 1 addition & 0 deletions test/mocks/blocks.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ extern const system::chain::transaction tx4;
extern const system::chain::transaction tx5;
extern const system::chain::block block1b;
extern const system::chain::block block2b;
extern const system::chain::block block2c;
extern const system::chain::transaction tx2b;
extern const system::chain::block mock_block_b;
extern const system::chain::block mock_block_c;
Expand Down
Loading