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

Add block.is_malleable() and associated block error code. #1430

Merged
merged 1 commit into from
Mar 29, 2024
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
1 change: 1 addition & 0 deletions include/bitcoin/system/chain/block.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ class BC_API block
uint64_t fees() const NOEXCEPT;
uint64_t claim() const NOEXCEPT;
hash_digest hash() const NOEXCEPT;
bool is_malleable() const NOEXCEPT;
bool is_segregated() const NOEXCEPT;
size_t serialized_size(bool witness) const NOEXCEPT;
size_t signature_operations(bool bip16, bool bip141) const NOEXCEPT;
Expand Down
1 change: 1 addition & 0 deletions include/bitcoin/system/error/block_error_t.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ enum block_error_t : uint8_t
forward_reference,
merkle_mismatch,
block_legacy_sigop_limit,
malleable_identifier,

// accept block
block_non_final,
Expand Down
35 changes: 32 additions & 3 deletions src/chain/block.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,12 @@ size_t block::serialized_size(bool witness) const NOEXCEPT
// Connect.
// ----------------------------------------------------------------------------

////// Subset of is_internal_double_spend if sha256 collisions cannot happen.
// Subset of is_internal_double_spend if sha256 collisions cannot happen. This
// is because each tx must have an input and for there to be no double spend in
// the block the inputs must be unique (and only one coinbase). As the
// is_internal_double_spend check invalidates any block with duplicated txs,
// there can be no tx hash duplication within the merkle tree. And a block that
// fails block.check is not archived, and its header remains potentially valid.
////bool block::is_distinct_transaction_set() const
////{
//// // A set is used to collapse duplicates.
Expand Down Expand Up @@ -450,6 +455,30 @@ bool block::is_hash_limit_exceeded() const NOEXCEPT
return hashes.size() > hash_limit;
}

// This is not part of validation. Should be called after *invalidation* to
// determine if the invalidity is universal (otherwise do not cache invalid).
// lists.linuxfoundation.org/pipermail/bitcoin-dev/2019-February/016697.html
bool block::is_malleable() const NOEXCEPT
{
// A two tx block cannot be malleable as coinbase is singular, otherwise
// if the last two non-witness tx hashes match then the id is malleable.
const auto count = txs_->size();
if (count > two && is_even(count) &&
txs_->at(sub1(count))->hash(false) == txs_->at(count)->hash(false))
{
return true;
}

// Hash of two same concatenated leaves is same as doubling one odd leaf.
const auto two_leaf_size = [](const transaction::cptr& tx) NOEXCEPT
{
return tx->serialized_size(false) == two * hash_size;
};

// If all non-witness tx serializations are 64 bytes the id is malleable.
return std::all_of(txs_->begin(), txs_->end(), two_leaf_size);
}

bool block::is_segregated() const NOEXCEPT
{
const auto segregated = [](const transaction::cptr& tx) NOEXCEPT
Expand Down Expand Up @@ -666,8 +695,8 @@ code block::check() const NOEXCEPT
{
// context free.
// empty_block is redundant with first_not_coinbase.
////if (is_empty())
//// return error::empty_block;
//if (is_empty())
// return error::empty_block;
if (is_oversized())
return error::block_size_limit;
if (is_first_non_coinbase())
Expand Down
1 change: 1 addition & 0 deletions src/error/block_error_t.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ DEFINE_ERROR_T_MESSAGE_MAP(block_error)
{ forward_reference, "transactions out of order" },
{ merkle_mismatch, "merkle root mismatch" },
{ block_legacy_sigop_limit, "too many block legacy signature operations" },
{ malleable_identifier, "block identifier is malleable" },

// accept block
{ block_non_final, "block contains a non-final transaction" },
Expand Down
1 change: 1 addition & 0 deletions test/chain/block.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,7 @@ BOOST_AUTO_TEST_CASE(block__hash__default__matches_header_hash)
BOOST_REQUIRE_EQUAL(instance.hash(), instance.header().hash());
}

// is_malleable
// is_segregated
// serialized_size

Expand Down
9 changes: 9 additions & 0 deletions test/error/block_error_t.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -172,6 +172,15 @@ BOOST_AUTO_TEST_CASE(block_error_t__code__block_legacy_sigop_limit__true_exected
BOOST_REQUIRE_EQUAL(ec.message(), "too many block legacy signature operations");
}

BOOST_AUTO_TEST_CASE(block_error_t__code__malleable_identifier__true_exected_message)
{
constexpr auto value = error::malleable_identifier;
const auto ec = code(value);
BOOST_REQUIRE(ec);
BOOST_REQUIRE(ec == value);
BOOST_REQUIRE_EQUAL(ec.message(), "block identifier is malleable");
}

// accept block

BOOST_AUTO_TEST_CASE(block_error_t__code__block_non_final__true_exected_message)
Expand Down
Loading