Skip to content

Commit

Permalink
Merge pull request #430 from evoskuil/master
Browse files Browse the repository at this point in the history
Implement block disassociation/test, test txs.malleable.
  • Loading branch information
evoskuil authored Apr 12, 2024
2 parents bde7ee9 + 7b86135 commit 0f1fc04
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 45 deletions.
68 changes: 30 additions & 38 deletions include/bitcoin/database/impl/query/archive.ipp
Original file line number Diff line number Diff line change
Expand Up @@ -79,10 +79,35 @@ inline bool CLASS::is_coinbase(const tx_link& link) const NOEXCEPT
return store_.tx.get(link, tx) && tx.coinbase;
}

TEMPLATE
inline bool CLASS::is_malleated(const block& block) const NOEXCEPT
{
// is_malleable_duplicate is always invalid, so never stored.
if (!block.is_malleable())
return false;

// This is invoked for all new blocks, most of which will not exist (cheap).
// TODO: determine if block is a bitwise match for a stored instance, where
// TODO: the stored and unassociated instances share the same block hash.
// TODO: if there is an associated instance this check need not be called.
// TODO: if the associated instance is matched then the assumption is that
// TODO: that instance is not malleated, but that may be later determined.
// TODO: so only disassociated instances are compared and otherwise false.
return true;
}

TEMPLATE
inline bool CLASS::is_malleable(const header_link& link) const NOEXCEPT
{
table::txs::get_malleable txs{};
return store_.txs.get(to_txs_link(link), txs) && txs.malleable;
}

TEMPLATE
inline bool CLASS::is_associated(const header_link& link) const NOEXCEPT
{
return !link.is_terminal() && store_.txs.exists(link);
table::txs::get_associated txs{};
return store_.txs.get(to_txs_link(link), txs) && txs.associated;
}

TEMPLATE
Expand Down Expand Up @@ -762,13 +787,12 @@ txs_link CLASS::set_link(const transactions& txs,
if (!push_link_value(links, set_link(*tx)))
return {};

// Malleable if all txs serialize to 64 bytes without witness.
const auto mutable_ = block::is_malleable64(txs);
const auto malleable = block::is_malleable64(txs);

// ========================================================================
const auto scope = store_.get_transactor();

return store_.txs.put_link(link, table::txs::slab{ {}, mutable_, links });
return store_.txs.put_link(link, table::txs::slab{ {}, malleable, links });
// ========================================================================
}

Expand All @@ -778,47 +802,15 @@ bool CLASS::dissasociate(const header_link& link) NOEXCEPT
if (link.is_terminal())
return false;

// TODO: disassociate the header from its transactions.
const auto malleable = is_malleable(link);

// ========================================================================
const auto scope = store_.get_transactor();

return true;
return store_.txs.put_link(link, table::txs::slab{ {}, malleable, {} });
// ========================================================================
}

TEMPLATE
inline bool CLASS::is_malleated(const block& block) const NOEXCEPT
{
// is_malleable_duplicate is always invalid, so never stored.
if (!block.is_malleable())
return false;

// This is invoked for all new blocks, most of which will not exist (cheap).
// TODO: determine if block is a bitwise match for a stored instance, where
// TODO: the stored and unassociated instances share the same block hash.
// TODO: if there is an associated instance this check need not be called.
// TODO: if the associated instance is matched then the assumption is that
// TODO: that instance is not malleated, but that may be later determined.
// TODO: so only disassociated instances are compared and otherwise false.
return true;
}

TEMPLATE
inline bool CLASS::is_malleable(const header_link& link) const NOEXCEPT
{
// Unknown treated as false, should not have been called unless associated.
if (!is_associated(link))
return false;

// Invoked after confirm failure (free).
// Invoked to not bypass checkpoints in validate/confirm (expensive).
// TODO: implement block.is_malleable() from the store.
// TODO: find any tx without a 64 byte non-witness size (false).
// TODO: or archive malleable bit with txs record.
return true;
}

} // namespace database
} // namespace libbitcoin

Expand Down
25 changes: 25 additions & 0 deletions include/bitcoin/database/tables/archives/txs.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,31 @@ struct txs

tx::integer coinbase_fk{};
};

struct get_malleable
: public schema::txs
{
inline bool from_data(reader& source) NOEXCEPT
{
source.skip_bytes(schema::count_);
malleable = to_bool(source.read_byte());
return source;
}

bool malleable{};
};

struct get_associated
: public schema::txs
{
inline bool from_data(reader& source) NOEXCEPT
{
associated = to_bool(source.read_little_endian<tx::integer, schema::count_>());
return source;
}

bool associated{};
};
};

} // namespace table
Expand Down
57 changes: 56 additions & 1 deletion test/query/archive.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -716,8 +716,10 @@ BOOST_AUTO_TEST_CASE(query_archive__set_block_txs__get_block__expected)
// Set header and then txs.
BOOST_REQUIRE(!query.is_block(test::genesis.hash()));
BOOST_REQUIRE(query.set(test::genesis.header(), test::context));
BOOST_REQUIRE(!query.is_associated(0));
BOOST_REQUIRE(query.set(test::genesis));
BOOST_REQUIRE(query.is_block(test::genesis.hash()));
BOOST_REQUIRE(query.is_associated(0));

// Verify idempotentcy (these do not change store state).
////BOOST_REQUIRE(query.set(test::genesis.header(), test::context));
Expand Down Expand Up @@ -753,6 +755,11 @@ BOOST_AUTO_TEST_CASE(query_archive__set_block_txs__get_block__expected)
const auto hashes = query.get_tx_keys(query.to_header(test::genesis.hash()));
BOOST_REQUIRE_EQUAL(hashes.size(), 1u);
BOOST_REQUIRE_EQUAL(hashes, test::genesis.transaction_hashes(false));

BOOST_REQUIRE(!query.is_malleable(0));
BOOST_REQUIRE(query.dissasociate(0));
BOOST_REQUIRE(!query.is_associated(0));
BOOST_REQUIRE(!query.is_malleable(0));
}

// Moved to protected, set_link(block) covers.
Expand Down Expand Up @@ -885,7 +892,7 @@ BOOST_AUTO_TEST_CASE(query_archive__is_coinbase__coinbase__true)
BOOST_REQUIRE(query.is_coinbase(3));
}

BOOST_AUTO_TEST_CASE(query_archive__is_coinbase__non_coinbase__true)
BOOST_AUTO_TEST_CASE(query_archive__is_coinbase__non_coinbase__false)
{
settings settings{};
settings.path = TEST_DIRECTORY;
Expand All @@ -903,6 +910,54 @@ BOOST_AUTO_TEST_CASE(query_archive__is_coinbase__non_coinbase__true)
BOOST_REQUIRE(!query.is_coinbase(42));
}

BOOST_AUTO_TEST_CASE(query_archive__is_malleable__malleable__true)
{
using namespace system::chain;
transaction tx64
{
42,
inputs{ { point{}, script{ { opcode::dup, opcode::dup } }, 42 } },
outputs{ { 42, script{ { opcode::dup, opcode::dup } } } },
42
};

const block block1{ header{ 42, {}, {}, {}, {}, {} }, { tx64 } };
const block block2{ header{ 43, {}, {}, {}, {}, {} }, { tx64, tx64 } };
const block block3{ header{ 44, {}, {}, {}, {}, {} }, { tx64, tx64, tx64 } };

settings settings{};
settings.path = TEST_DIRECTORY;
test::chunk_store store{ settings };
test::query_accessor query{ store };
BOOST_REQUIRE_EQUAL(store.create(events), error::success);
BOOST_REQUIRE(query.initialize(test::genesis));
BOOST_REQUIRE_EQUAL(query.set_link(block1, database::context{}), 1u);
BOOST_REQUIRE_EQUAL(query.set_link(block2, database::context{}), 2u);
BOOST_REQUIRE_EQUAL(query.set_link(block3, database::context{}), 3u);
BOOST_REQUIRE(!query.is_malleable(0));
BOOST_REQUIRE(query.is_malleable(1));
BOOST_REQUIRE(query.is_malleable(2));
BOOST_REQUIRE(query.is_malleable(3));
}

BOOST_AUTO_TEST_CASE(query_archive__is_malleable__non_malleable__false)
{
settings settings{};
settings.path = TEST_DIRECTORY;
test::chunk_store store{ settings };
test::query_accessor query{ store };
BOOST_REQUIRE_EQUAL(store.create(events), error::success);
BOOST_REQUIRE(query.initialize(test::genesis));
BOOST_REQUIRE(query.set(test::block1a, context{}));
BOOST_REQUIRE(query.set(test::block2a, context{}));
BOOST_REQUIRE(!query.is_malleable(1));
BOOST_REQUIRE(!query.is_malleable(2));
BOOST_REQUIRE(!query.is_malleable(3));
BOOST_REQUIRE(!query.is_malleable(4));
BOOST_REQUIRE(!query.is_malleable(5));
BOOST_REQUIRE(!query.is_malleable(42));
}

BOOST_AUTO_TEST_CASE(query_archive__get_header__invalid_parent__expected)
{
constexpr auto root = system::base16_array("119192939495969798999a9b9c9d9e9f229192939495969798999a9b9c9d9e9f");
Expand Down
24 changes: 18 additions & 6 deletions test/tables/archives/txs.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const table::txs::slab expected0{};
const table::txs::slab expected1
{
{}, // schema::txs [all const static members]
false,
true,
std_vector<uint32_t>
{
0x56341211_u32
Expand All @@ -46,7 +46,7 @@ const table::txs::slab expected2
const table::txs::slab expected3
{
{}, // schema::txs [all const static members]
false,
true,
std_vector<uint32_t>
{
0x56341231_u32,
Expand All @@ -63,7 +63,10 @@ const data_chunk expected_file
0x11, 0x22, 0x33,

// slab0 (count) [0]
0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00,

// slab0 (malleable) [false]
0x00,

// slab0 (txs)

Expand All @@ -76,7 +79,10 @@ const data_chunk expected_file
0x11, 0x22, 0x33,

// slab1 (count) [1]
0x01, 0x00, 0x00, 0x00,
0x01, 0x00, 0x00,

// slab0 (malleable) [true]
0x01,

// slab1 (txs)
0x11, 0x12, 0x34, 0x56,
Expand All @@ -90,7 +96,10 @@ const data_chunk expected_file
0x11, 0x22, 0x33,

// slab2 (count) [2]
0x02, 0x00, 0x00, 0x00,
0x02, 0x00, 0x00,

// slab0 (malleable) [false]
0x00,

// slab2
0x21, 0x12, 0x34, 0x56,
Expand All @@ -105,7 +114,10 @@ const data_chunk expected_file
0x11, 0x22, 0x33,

// slab3 (count) [3]
0x03, 0x00, 0x00, 0x00,
0x03, 0x00, 0x00,

// slab0 (malleable) [true]
0x01,

// slab3
0x31, 0x12, 0x34, 0x56,
Expand Down

0 comments on commit 0f1fc04

Please sign in to comment.