Skip to content

Commit

Permalink
Add block.is_malleable() and associated block error code.
Browse files Browse the repository at this point in the history
  • Loading branch information
evoskuil committed Mar 29, 2024
1 parent b0623c6 commit 7bfd5a7
Show file tree
Hide file tree
Showing 6 changed files with 45 additions and 3 deletions.
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

0 comments on commit 7bfd5a7

Please sign in to comment.