From c954ddb2e914dc19ee08d795c7216c6b93db7222 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Wed, 29 May 2024 14:13:03 -0400 Subject: [PATCH 01/13] Allow ref wrapped chain::point to be using in equality containers. --- include/bitcoin/system/chain/point.hpp | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/include/bitcoin/system/chain/point.hpp b/include/bitcoin/system/chain/point.hpp index 76caac3015..a369b559da 100644 --- a/include/bitcoin/system/chain/point.hpp +++ b/include/bitcoin/system/chain/point.hpp @@ -19,6 +19,7 @@ #ifndef LIBBITCOIN_SYSTEM_CHAIN_POINT_HPP #define LIBBITCOIN_SYSTEM_CHAIN_POINT_HPP +#include #include #include #include @@ -128,6 +129,24 @@ struct hash bc::system::unique_hash_t<>{}(value.hash())); } }; + +template<> +struct hash> +{ + using wrapped = std::reference_wrapper; + std::size_t operator()(const wrapped& point) const NOEXCEPT + { + return std::hash{}(point.get()); + } +}; + +inline bool operator==( + const std::reference_wrapper& left, + const std::reference_wrapper& right) NOEXCEPT +{ + return left.get() == right.get(); +} + } // namespace std #endif From d92b57728995a1c5f3c9cddfc1b726fa0e6472c2 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Wed, 29 May 2024 14:13:55 -0400 Subject: [PATCH 02/13] Optimize is_internal_double_spend. --- include/bitcoin/system/chain/block.hpp | 6 +++++ src/chain/block.cpp | 32 ++++++++++++++++++-------- 2 files changed, 28 insertions(+), 10 deletions(-) diff --git a/include/bitcoin/system/chain/block.hpp b/include/bitcoin/system/chain/block.hpp index 2fc5c4d87d..82e070dcc2 100644 --- a/include/bitcoin/system/chain/block.hpp +++ b/include/bitcoin/system/chain/block.hpp @@ -19,7 +19,9 @@ #ifndef LIBBITCOIN_SYSTEM_CHAIN_BLOCK_HPP #define LIBBITCOIN_SYSTEM_CHAIN_BLOCK_HPP +#include #include +#include #include #include #include @@ -165,6 +167,10 @@ class BC_API block bool is_unspent_coinbase_collision() const NOEXCEPT; private: + using unordered_set_of_constant_referenced_points = + std::unordered_set, + std::hash>>; + static block from_data(reader& source, bool witness) NOEXCEPT; // context free diff --git a/src/chain/block.cpp b/src/chain/block.cpp index 61252f159c..1a7b285f66 100644 --- a/src/chain/block.cpp +++ b/src/chain/block.cpp @@ -19,6 +19,7 @@ #include #include +#include #include #include #include @@ -331,6 +332,7 @@ bool block::is_forward_reference() const NOEXCEPT BC_POP_WARNING() }; + // TODO: change to std::ref(tx.hash). for (const auto& tx: views_reverse(*txs_)) { BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) @@ -364,19 +366,29 @@ bool block::is_internal_double_spend() const NOEXCEPT if (txs_->empty()) return false; - const auto inputs = non_coinbase_inputs(); - std::vector outs{}; - outs.reserve(inputs); + const auto input_count = non_coinbase_inputs(); + BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) + unordered_set_of_constant_referenced_points points(input_count); + BC_POP_WARNING() - // Copy all block.txs.points into the vector. - for (auto tx = std::next(txs_->begin()); tx != txs_->end(); ++tx) + const auto double_in = [&points](const input::cptr& in) NOEXCEPT { - auto out = (*tx)->points(); - std::move(out.begin(), out.end(), std::inserter(outs, outs.end())); - } + const auto& point = in->point(); + BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) + const auto found = (points.find(std::cref(point)) != points.end()); + points.emplace(in->point()); + BC_POP_WARNING() + + return found; + }; + + const auto double_tx = [&double_in](const transaction::cptr& tx) NOEXCEPT + { + const auto& ins = *tx->inputs_ptr(); + return std::any_of(ins.begin(), ins.end(), double_in); + }; - distinct(outs); - return outs.size() != inputs; + return std::any_of(std::next(txs_->begin()), txs_->end(), double_tx); } // private From bc13e580ae5638866904be4a9a42c535bb9d3e1e Mon Sep 17 00:00:00 2001 From: evoskuil Date: Wed, 29 May 2024 16:41:04 -0400 Subject: [PATCH 03/13] Optimize is_internal_double_spend and is_forward_reference. --- src/chain/block.cpp | 39 ++++++++++++++++++++------------------- test/chain/block.cpp | 1 + 2 files changed, 21 insertions(+), 19 deletions(-) diff --git a/src/chain/block.cpp b/src/chain/block.cpp index 1a7b285f66..c911201924 100644 --- a/src/chain/block.cpp +++ b/src/chain/block.cpp @@ -318,33 +318,37 @@ bool block::is_extra_coinbases() const NOEXCEPT //***************************************************************************** bool block::is_forward_reference() const NOEXCEPT { - // unordered_set manages the maximum load factor (number of elements per - // bucket). The container automatically increases the number of buckets - // if the load factor exceeds this threshold. This defaults to 1.0. + if (txs_->empty()) + return false; + + const auto non_coinbase_txs = sub1(txs_->size()); + + // TODO: change to reference_wrapper(tx.hash) - first make referencable. BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) - std::unordered_set> hashes(txs_->size()); + std::unordered_set> hashes(non_coinbase_txs); BC_POP_WARNING() - const auto is_forward = [&hashes](const input::cptr& input) NOEXCEPT + const auto forward_in = [&hashes](const input::cptr& input) NOEXCEPT { BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) return hashes.find(input->point().hash()) != hashes.end(); BC_POP_WARNING() }; - // TODO: change to std::ref(tx.hash). - for (const auto& tx: views_reverse(*txs_)) + const auto forward_tx = [&forward_in, &hashes]( + const transaction::cptr& tx) NOEXCEPT { + const auto& ins = *tx->inputs_ptr(); + const auto found = std::any_of(ins.begin(), ins.end(), forward_in); + BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) hashes.emplace(tx->hash(false)); BC_POP_WARNING() - const auto& inputs = *tx->inputs_ptr(); - if (std::any_of(inputs.begin(), inputs.end(), is_forward)) - return true; - } + return found; + }; - return false; + return std::any_of(txs_->rbegin(), std::prev(txs_->rend()), forward_tx); } // private @@ -366,20 +370,17 @@ bool block::is_internal_double_spend() const NOEXCEPT if (txs_->empty()) return false; - const auto input_count = non_coinbase_inputs(); + const auto non_coinbase_ins = non_coinbase_inputs(); + BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) - unordered_set_of_constant_referenced_points points(input_count); + unordered_set_of_constant_referenced_points points(non_coinbase_ins); BC_POP_WARNING() const auto double_in = [&points](const input::cptr& in) NOEXCEPT { - const auto& point = in->point(); BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) - const auto found = (points.find(std::cref(point)) != points.end()); - points.emplace(in->point()); + return !points.emplace(in->point()).second; BC_POP_WARNING() - - return found; }; const auto double_tx = [&double_in](const transaction::cptr& tx) NOEXCEPT diff --git a/test/chain/block.cpp b/test/chain/block.cpp index de3d0e9de2..3b1f3d7d6e 100644 --- a/test/chain/block.cpp +++ b/test/chain/block.cpp @@ -405,6 +405,7 @@ BOOST_AUTO_TEST_CASE(block__is_forward_reference__forward_reference__true) { {}, { + {}, from, to } From 2e1f08449fa6c3e892548b4ebed9551b67b504a1 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Wed, 29 May 2024 17:07:35 -0400 Subject: [PATCH 04/13] move cache_ conditions into the wrapping methods. --- include/bitcoin/system/chain/transaction.hpp | 8 +++---- src/chain/transaction.cpp | 22 +++++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/include/bitcoin/system/chain/transaction.hpp b/include/bitcoin/system/chain/transaction.hpp index 97e335c615..44e7e9fbb6 100644 --- a/include/bitcoin/system/chain/transaction.hpp +++ b/include/bitcoin/system/chain/transaction.hpp @@ -121,10 +121,6 @@ class BC_API transaction /// Assumes coinbase if prevout not populated (returns only legacy sigops). size_t signature_operations(bool bip16, bool bip141) const NOEXCEPT; - chain::points points() const NOEXCEPT; - hash_digest outputs_hash() const NOEXCEPT; - hash_digest points_hash() const NOEXCEPT; - hash_digest sequences_hash() const NOEXCEPT; // signature_hash exposed for op_check_multisig caching. hash_digest signature_hash(const input_iterator& input, const script& sub, @@ -219,6 +215,10 @@ class BC_API transaction bool is_confirmed_double_spend(size_t height) const NOEXCEPT; private: + chain::points points() const NOEXCEPT; + hash_digest outputs_hash() const NOEXCEPT; + hash_digest points_hash() const NOEXCEPT; + hash_digest sequences_hash() const NOEXCEPT; static transaction from_data(reader& source, bool witness) NOEXCEPT; static bool segregated(const chain::inputs& inputs) NOEXCEPT; static bool segregated(const chain::input_cptrs& inputs) NOEXCEPT; diff --git a/src/chain/transaction.cpp b/src/chain/transaction.cpp index 8de7906188..9694ad8cef 100644 --- a/src/chain/transaction.cpp +++ b/src/chain/transaction.cpp @@ -500,6 +500,9 @@ chain::points transaction::points() const NOEXCEPT hash_digest transaction::outputs_hash() const NOEXCEPT { + if (cache_) + return cache_->outputs; + BC_PUSH_WARNING(LOCAL_VARIABLE_NOT_INITIALIZED) hash_digest digest; BC_POP_WARNING() @@ -517,6 +520,9 @@ hash_digest transaction::outputs_hash() const NOEXCEPT hash_digest transaction::points_hash() const NOEXCEPT { + if (cache_) + return cache_->points; + BC_PUSH_WARNING(LOCAL_VARIABLE_NOT_INITIALIZED) hash_digest digest; BC_POP_WARNING() @@ -534,6 +540,9 @@ hash_digest transaction::points_hash() const NOEXCEPT hash_digest transaction::sequences_hash() const NOEXCEPT { + if (cache_) + return cache_->sequences; + BC_PUSH_WARNING(LOCAL_VARIABLE_NOT_INITIALIZED) hash_digest digest; BC_POP_WARNING() @@ -851,16 +860,10 @@ hash_digest transaction::version_0_signature_hash(const input_iterator& input, // conditionally passing them from methods avoids copying the cached hash. // points - if (cache_) - sink.write_bytes(!anyone ? cache_->points : null_hash); - else - sink.write_bytes(!anyone ? points_hash() : null_hash); + sink.write_bytes(!anyone ? points_hash() : null_hash); // sequences - if (cache_) - sink.write_bytes(!anyone && all ? cache_->sequences : null_hash); - else - sink.write_bytes(!anyone && all ? sequences_hash() : null_hash); + sink.write_bytes(!anyone && all ? sequences_hash() : null_hash); self.point().to_data(sink); sub.to_data(sink, prefixed); @@ -870,8 +873,6 @@ hash_digest transaction::version_0_signature_hash(const input_iterator& input, // outputs if (single) sink.write_bytes(output_hash(input)); - else if (cache_) - sink.write_bytes(all ? cache_->outputs : null_hash); else sink.write_bytes(all ? outputs_hash() : null_hash); @@ -959,6 +960,7 @@ bool transaction::is_coinbase() const NOEXCEPT bool transaction::is_internal_double_spend() const NOEXCEPT { + // TODO: optimize (see block.is_internal_double_spend). return !is_distinct(points()); } From d242308139c1eb7962572f8a4885442a6593b74e Mon Sep 17 00:00:00 2001 From: evoskuil Date: Wed, 29 May 2024 22:32:28 -0400 Subject: [PATCH 05/13] Use sets/maps of object refs vs. copies in block validations. --- include/bitcoin/system/chain/block.hpp | 12 +++- include/bitcoin/system/chain/transaction.hpp | 14 ++++- src/chain/block.cpp | 25 ++++---- src/chain/transaction.cpp | 43 ++++++++++++-- test/chain/transaction.cpp | 60 +++++++++++++------- test/test.hpp | 8 +++ 6 files changed, 122 insertions(+), 40 deletions(-) diff --git a/include/bitcoin/system/chain/block.hpp b/include/bitcoin/system/chain/block.hpp index 82e070dcc2..c07092857c 100644 --- a/include/bitcoin/system/chain/block.hpp +++ b/include/bitcoin/system/chain/block.hpp @@ -167,9 +167,17 @@ class BC_API block bool is_unspent_coinbase_collision() const NOEXCEPT; private: + using hash_cref = std::reference_wrapper; + using hash_hash = unique_hash_t<>; + using point_cref = std::reference_wrapper; + using point_hash = std::hash>; + + using unordered_set_of_constant_referenced_hashes = + std::unordered_set; using unordered_set_of_constant_referenced_points = - std::unordered_set, - std::hash>>; + std::unordered_set; + using unordered_map_of_constant_referenced_points = + std::unordered_map; static block from_data(reader& source, bool witness) NOEXCEPT; diff --git a/include/bitcoin/system/chain/transaction.hpp b/include/bitcoin/system/chain/transaction.hpp index 44e7e9fbb6..78d66df3c7 100644 --- a/include/bitcoin/system/chain/transaction.hpp +++ b/include/bitcoin/system/chain/transaction.hpp @@ -109,9 +109,12 @@ class BC_API transaction bool is_segregated() const NOEXCEPT; size_t serialized_size(bool witness) const NOEXCEPT; - /// Cache (these override hash(bool) computation). - void set_hash(hash_digest&& hash) const NOEXCEPT; + /// Cache setters/getters, not thread safe. + /// ----------------------------------------------------------------------- + + void set_nominal_hash(hash_digest&& hash) const NOEXCEPT; void set_witness_hash(hash_digest&& hash) const NOEXCEPT; + const hash_digest& get_hash(bool witness) const NOEXCEPT; /// Methods. /// ----------------------------------------------------------------------- @@ -291,6 +294,13 @@ struct hash return bc::system::unique_hash_t<>{}(value.hash(true)); } }; + +inline bool operator==( + const std::reference_wrapper& left, + const std::reference_wrapper& right) NOEXCEPT +{ + return left.get() == right.get(); +} } // namespace std #endif diff --git a/src/chain/block.cpp b/src/chain/block.cpp index c911201924..ecf27ed27d 100644 --- a/src/chain/block.cpp +++ b/src/chain/block.cpp @@ -26,7 +26,6 @@ #include #include #include -#include #include #include #include @@ -325,13 +324,13 @@ bool block::is_forward_reference() const NOEXCEPT // TODO: change to reference_wrapper(tx.hash) - first make referencable. BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) - std::unordered_set> hashes(non_coinbase_txs); + unordered_set_of_constant_referenced_hashes hashes(non_coinbase_txs); BC_POP_WARNING() const auto forward_in = [&hashes](const input::cptr& input) NOEXCEPT { BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) - return hashes.find(input->point().hash()) != hashes.end(); + return hashes.find(std::ref(input->point().hash())) != hashes.end(); BC_POP_WARNING() }; @@ -342,7 +341,7 @@ bool block::is_forward_reference() const NOEXCEPT const auto found = std::any_of(ins.begin(), ins.end(), forward_in); BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) - hashes.emplace(tx->hash(false)); + hashes.emplace(tx->get_hash(false)); BC_POP_WARNING() return found; @@ -363,7 +362,8 @@ size_t block::non_coinbase_inputs() const NOEXCEPT return std::accumulate(std::next(txs_->begin()), txs_->end(), zero, inputs); } -// This also precludes the block merkle calculation DoS exploit. +// This also precludes the block merkle calculation DoS exploit by preventing +// duplicate txs, as a duplicate non-empty tx implies a duplicate point. // bitcointalk.org/?topic=102395 bool block::is_internal_double_spend() const NOEXCEPT { @@ -437,18 +437,18 @@ bool block::is_hash_limit_exceeded() const NOEXCEPT return false; // A set is used to collapse duplicates. - std::unordered_set> hashes; + unordered_set_of_constant_referenced_hashes hashes; // Just the coinbase tx hash, skip its null input hashes. BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) - hashes.insert(txs_->front()->hash(false)); + hashes.emplace(txs_->front()->get_hash(false)); BC_POP_WARNING() for (auto tx = std::next(txs_->begin()); tx != txs_->end(); ++tx) { // Insert the transaction hash. BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) - hashes.insert((*tx)->hash(false)); + hashes.emplace((*tx)->get_hash(false)); BC_POP_WARNING() const auto& inputs = *(*tx)->inputs_ptr(); @@ -457,7 +457,7 @@ bool block::is_hash_limit_exceeded() const NOEXCEPT for (const auto& input: inputs) { BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) - hashes.insert(input->point().hash()); + hashes.emplace(input->point().hash()); BC_POP_WARNING() } } @@ -653,20 +653,21 @@ bool block::is_unspent_coinbase_collision() const NOEXCEPT void block::populate() const NOEXCEPT { BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) - std::unordered_map points{}; + unordered_map_of_constant_referenced_points points{}; uint32_t index{}; // Populate outputs hash table. for (auto tx = txs_->begin(); tx != txs_->end(); ++tx, index = 0) for (const auto& out: *(*tx)->outputs_ptr()) - points.emplace(point{ (*tx)->hash(false), index++ }, out); + points.emplace(std::pair{ point{ (*tx)->get_hash(false), + index++ }, out }); // Populate input prevouts from hash table. for (auto tx = txs_->begin(); tx != txs_->end(); ++tx) { for (const auto& in: *(*tx)->inputs_ptr()) { - const auto point = points.find(in->point()); + const auto point = points.find(std::cref(in->point())); if (point != points.end()) in->prevout = point->second; } diff --git a/src/chain/transaction.cpp b/src/chain/transaction.cpp index 9694ad8cef..0e5cdc1c57 100644 --- a/src/chain/transaction.cpp +++ b/src/chain/transaction.cpp @@ -86,7 +86,6 @@ transaction::transaction(transaction&& other) NOEXCEPT { } -// Cache not copied or moved. transaction::transaction(const transaction& other) NOEXCEPT : transaction( other.version_, @@ -96,6 +95,14 @@ transaction::transaction(const transaction& other) NOEXCEPT other.segregated_, other.valid_) { + if (other.cache_) + cache_ = std::make_unique(*other.cache_); + + if (other.nominal_hash_) + nominal_hash_ = std::make_unique(*other.nominal_hash_); + + if (other.witness_hash_) + witness_hash_ = std::make_unique(*other.witness_hash_); } transaction::transaction(uint32_t version, chain::inputs&& inputs, @@ -181,13 +188,22 @@ transaction& transaction::operator=(transaction&& other) NOEXCEPT transaction& transaction::operator=(const transaction& other) NOEXCEPT { - // Cache not assigned. version_ = other.version_; inputs_ = other.inputs_; outputs_ = other.outputs_; locktime_ = other.locktime_; segregated_ = other.segregated_; valid_ = other.valid_; + + if (other.cache_) + cache_ = std::make_unique(*other.cache_); + + if (other.nominal_hash_) + nominal_hash_ = std::make_unique(*other.nominal_hash_); + + if (other.witness_hash_) + witness_hash_ = std::make_unique(*other.witness_hash_); + return *this; } @@ -405,7 +421,7 @@ uint64_t transaction::fee() const NOEXCEPT return floored_subtract(value(), claim()); } -void transaction::set_hash(hash_digest&& hash) const NOEXCEPT +void transaction::set_nominal_hash(hash_digest&& hash) const NOEXCEPT { BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) nominal_hash_ = std::make_unique(std::move(hash)); @@ -419,13 +435,31 @@ void transaction::set_witness_hash(hash_digest&& hash) const NOEXCEPT BC_POP_WARNING() } +const hash_digest& transaction::get_hash(bool witness) const NOEXCEPT +{ + if (witness) + { + if (!witness_hash_) + set_witness_hash(hash(witness)); + + return *witness_hash_; + } + else + { + if (!nominal_hash_) + set_nominal_hash(hash(witness)); + + return *nominal_hash_; + } +} + hash_digest transaction::hash(bool witness) const NOEXCEPT { if (segregated_) { if (witness) { - // Avoid is_coinbase call if cache present (and avoid caching it). + // Avoid is_coinbase call if cache present. if (witness_hash_) return *witness_hash_; // Witness coinbase tx hash is assumed to be null_hash (bip141). @@ -439,7 +473,6 @@ hash_digest transaction::hash(bool witness) const NOEXCEPT else { if (nominal_hash_) return *nominal_hash_; - if (witness_hash_) return *witness_hash_; } BC_PUSH_WARNING(LOCAL_VARIABLE_NOT_INITIALIZED) diff --git a/test/chain/transaction.cpp b/test/chain/transaction.cpp index fa8767216f..f81fc91ba4 100644 --- a/test/chain/transaction.cpp +++ b/test/chain/transaction.cpp @@ -43,7 +43,7 @@ static const auto tx1_data = base16_chunk( "0000001976a9141ee32412020a324b93b1a1acfdfff6ab9ca8fac288ac000000" "00"); -static const auto tx1_hash = base16_hash( +constexpr auto tx1_hash = base16_hash( "bf7c3f5a69a78edd81f3eff7e93a37fb2d7da394d48db4d85e7e5353b9b8e270"); static const auto tx2_data = base16_chunk( @@ -65,7 +65,7 @@ static const auto tx2_data = base16_chunk( "10c3d488ac20300500000000001976a914905f933de850988603aafeeb2fd7fc" "e61e66fe5d88ac00000000"); -static const auto tx2_hash = base16_hash( +constexpr auto tx2_hash = base16_hash( "8a6d9302fbe24f0ec756a94ecfc837eaffe16c43d1e68c62dfe980d99eea556f"); static const auto tx3_data = base16_chunk( @@ -198,8 +198,8 @@ BOOST_AUTO_TEST_CASE(transaction__constructor__copy__expected) BOOST_AUTO_TEST_CASE(transaction__constructor__move_parameters__expected) { - const uint32_t version = 2345; - const uint32_t locktime = 4568656; + constexpr uint32_t version = 2345; + constexpr uint32_t locktime = 4568656; const input input(tx0_inputs); BOOST_REQUIRE(input.is_valid()); @@ -219,8 +219,8 @@ BOOST_AUTO_TEST_CASE(transaction__constructor__move_parameters__expected) BOOST_AUTO_TEST_CASE(transaction__constructor__copy_parameters__expected) { - const uint32_t version = 2345; - const uint32_t locktime = 4568656; + constexpr uint32_t version = 2345; + constexpr uint32_t locktime = 4568656; const input input(tx0_inputs); BOOST_REQUIRE(input.is_valid()); @@ -456,11 +456,11 @@ BOOST_AUTO_TEST_CASE(transaction__fee__default_output__zero) BOOST_AUTO_TEST_CASE(transaction__fee__nonempty__outputs_minus_inputs) { - const uint64_t value0 = 123; - const uint64_t value1 = 321; - const uint64_t claim0 = 11; - const uint64_t claim1 = 11; - const uint64_t claim2 = 22; + constexpr uint64_t value0 = 123; + constexpr uint64_t value1 = 321; + constexpr uint64_t claim0 = 11; + constexpr uint64_t claim1 = 11; + constexpr uint64_t claim2 = 22; input input0; input input1; @@ -531,8 +531,8 @@ BOOST_AUTO_TEST_CASE(transaction__value__default_input2__max_uint64) BOOST_AUTO_TEST_CASE(transaction__value__two_prevouts__sum) { - const uint64_t value0 = 123; - const uint64_t value1 = 321; + constexpr uint64_t value0 = 123; + constexpr uint64_t value1 = 321; const input input0; const input input1; @@ -559,6 +559,28 @@ BOOST_AUTO_TEST_CASE(transaction__hash__block320670__success) BOOST_REQUIRE_EQUAL(instance.to_data(true), tx4_data); } +BOOST_AUTO_TEST_CASE(transaction__set_hash__get_hash__expected) +{ + const transaction instance(tx1_data, true); + + BOOST_REQUIRE_EQUAL(instance.hash(false), tx1_hash); + BOOST_REQUIRE_EQUAL(instance.hash(true), tx1_hash); + BOOST_REQUIRE_EQUAL(instance.get_hash(false), tx1_hash); + BOOST_REQUIRE_EQUAL(instance.get_hash(true), tx1_hash); + + instance.set_nominal_hash(test::move_copy(tx2_hash)); + BOOST_REQUIRE_EQUAL(instance.hash(false), tx2_hash); + BOOST_REQUIRE_EQUAL(instance.hash(true), tx2_hash); + BOOST_REQUIRE_EQUAL(instance.get_hash(false), tx2_hash); + BOOST_REQUIRE_EQUAL(instance.get_hash(true), tx1_hash); + + instance.set_witness_hash(test::move_copy(tx4_hash)); + BOOST_REQUIRE_EQUAL(instance.hash(false), tx2_hash); + BOOST_REQUIRE_EQUAL(instance.hash(true), tx2_hash); + BOOST_REQUIRE_EQUAL(instance.get_hash(false), tx2_hash); + BOOST_REQUIRE_EQUAL(instance.get_hash(true), tx4_hash); +} + BOOST_AUTO_TEST_CASE(transaction__is_coinbase__empty__false) { transaction instance; @@ -1065,12 +1087,12 @@ BOOST_AUTO_TEST_CASE(transaction__is_non_final__locktime_less_block_height_less_ BOOST_AUTO_TEST_CASE(transaction__is_non_final__locktime_input_not_final__true) { - const bool bip113 = false; - const size_t height = 100; - const uint32_t time = 100; - const uint32_t past = 0; - const uint32_t locktime = 101; - const uint32_t sequence = 1; + constexpr bool bip113 = false; + constexpr size_t height = 100; + constexpr uint32_t time = 100; + constexpr uint32_t past = 0; + constexpr uint32_t locktime = 101; + constexpr uint32_t sequence = 1; const accessor instance { diff --git a/test/test.hpp b/test/test.hpp index c7c10f297d..3b1e1c6dd2 100644 --- a/test/test.hpp +++ b/test/test.hpp @@ -95,6 +95,14 @@ bool create(const std::filesystem::path& file_path) NOEXCEPT; bool exists(const std::filesystem::path& file_path) NOEXCEPT; bool remove(const std::filesystem::path& file_path) NOEXCEPT; +// Utility to convert a const reference instance to moveable. +template +Type move_copy(const Type& instance) NOEXCEPT +{ + auto copy = instance; + return copy; +} + } // namespace test #endif From a6e472d0f50fee5c2deedc20edf61cd0bc54dc1a Mon Sep 17 00:00:00 2001 From: evoskuil Date: Wed, 29 May 2024 22:35:29 -0400 Subject: [PATCH 06/13] Revert "Disable use of count_op/op_count to preallocate script ops." This reverts commit 8176c187b67b83d396408f3b088de2dcf329326b. --- include/bitcoin/system/chain/operation.hpp | 4 +-- src/chain/operation.cpp | 18 +++++------ src/chain/script.cpp | 36 +++++++++++----------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/include/bitcoin/system/chain/operation.hpp b/include/bitcoin/system/chain/operation.hpp index f0a03deea7..89250f0a69 100644 --- a/include/bitcoin/system/chain/operation.hpp +++ b/include/bitcoin/system/chain/operation.hpp @@ -434,8 +434,8 @@ class BC_API operation bool underflow) NOEXCEPT; private: - ////// So script may call count_op. - ////friend class script; + // So script may call count_op. + friend class script; static operation from_data(reader& source) NOEXCEPT; static operation from_push_data(const chunk_cptr& data, diff --git a/src/chain/operation.cpp b/src/chain/operation.cpp index c90267752b..a951b465b9 100644 --- a/src/chain/operation.cpp +++ b/src/chain/operation.cpp @@ -486,15 +486,15 @@ size_t operation::serialized_size() const NOEXCEPT // static/private // Advances stream, returns true unless exhausted. // Does not advance to end position in the case of underflow operation. -////bool operation::count_op(reader& source) NOEXCEPT -////{ -//// if (source.is_exhausted()) -//// return false; -//// -//// const auto code = static_cast(source.read_byte()); -//// source.skip_bytes(read_data_size(code, source)); -//// return true; -////} +bool operation::count_op(reader& source) NOEXCEPT +{ + if (source.is_exhausted()) + return false; + + const auto code = static_cast(source.read_byte()); + source.skip_bytes(read_data_size(code, source)); + return true; +} // static/private uint32_t operation::read_data_size(opcode code, reader& source) NOEXCEPT diff --git a/src/chain/script.cpp b/src/chain/script.cpp index 662e9f5387..6668937e15 100644 --- a/src/chain/script.cpp +++ b/src/chain/script.cpp @@ -214,21 +214,22 @@ bool script::operator!=(const script& other) const NOEXCEPT // ---------------------------------------------------------------------------- // static/private -////size_t script::op_count(reader& source) NOEXCEPT -////{ -//// // Stream errors reset by set_position so trap here. -//// if (!source) -//// return zero; -//// -//// const auto start = source.get_read_position(); -//// size_t count{}; -//// -//// while (operation::count_op(source)) -//// ++count; -//// -//// source.set_position(start); -//// return count; -////} +size_t script::op_count(reader& source) NOEXCEPT +{ + // Stream errors reset by set_position so trap here. + if (!source) + return zero; + + const auto start = source.get_read_position(); + auto count = zero; + + // TODO: this is expensive (0.83%). + while (operation::count_op(source)) + ++count; + + source.set_position(start); + return count; +} // static/private script script::from_data(reader& source, bool prefix) NOEXCEPT @@ -242,9 +243,8 @@ script script::from_data(reader& source, bool prefix) NOEXCEPT source.set_limit(expected); } - // op_count is more expensive than the reallocations. - operations ops{}; - ////ops.reserve(op_count(source)); + operations ops; + ops.reserve(op_count(source)); const auto start = source.get_read_position(); while (!source.is_exhausted()) From 2d3a8db8d2bae7f9ace31b9209a3144678e84259 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Thu, 30 May 2024 02:03:58 -0400 Subject: [PATCH 07/13] Style, comments. --- include/bitcoin/system/chain/block.hpp | 13 ++- include/bitcoin/system/chain/transaction.hpp | 6 ++ src/chain/block.cpp | 94 ++++++-------------- src/chain/script.cpp | 2 +- src/chain/transaction.cpp | 38 +++----- 5 files changed, 52 insertions(+), 101 deletions(-) diff --git a/include/bitcoin/system/chain/block.hpp b/include/bitcoin/system/chain/block.hpp index c07092857c..c2aa238489 100644 --- a/include/bitcoin/system/chain/block.hpp +++ b/include/bitcoin/system/chain/block.hpp @@ -167,17 +167,17 @@ class BC_API block bool is_unspent_coinbase_collision() const NOEXCEPT; private: - using hash_cref = std::reference_wrapper; - using hash_hash = unique_hash_t<>; using point_cref = std::reference_wrapper; using point_hash = std::hash>; + using hash_cref = std::reference_wrapper; + using hash_hash = unique_hash_t<>; - using unordered_set_of_constant_referenced_hashes = - std::unordered_set; - using unordered_set_of_constant_referenced_points = - std::unordered_set; using unordered_map_of_constant_referenced_points = std::unordered_map; + using unordered_set_of_constant_referenced_points = + std::unordered_set; + using unordered_set_of_constant_referenced_hashes = + std::unordered_set; static block from_data(reader& source, bool witness) NOEXCEPT; @@ -185,7 +185,6 @@ class BC_API block hash_digest generate_merkle_root(bool witness) const NOEXCEPT; // contextual - size_t non_coinbase_inputs() const NOEXCEPT; uint64_t reward(size_t height, uint64_t subsidy_interval, uint64_t initial_block_subsidy_satoshi, bool bip42) const NOEXCEPT; diff --git a/include/bitcoin/system/chain/transaction.hpp b/include/bitcoin/system/chain/transaction.hpp index 78d66df3c7..471a65583c 100644 --- a/include/bitcoin/system/chain/transaction.hpp +++ b/include/bitcoin/system/chain/transaction.hpp @@ -112,8 +112,14 @@ class BC_API transaction /// Cache setters/getters, not thread safe. /// ----------------------------------------------------------------------- + /// Initialize with externally-produced nominal hash value, as from store. void set_nominal_hash(hash_digest&& hash) const NOEXCEPT; + + /// Initialize with externally-produced witness hash value, as from store. + /// This need not be set if the transaction is not segmented. void set_witness_hash(hash_digest&& hash) const NOEXCEPT; + + /// Reference used to avoid copy, sets cache if not set. const hash_digest& get_hash(bool witness) const NOEXCEPT; /// Methods. diff --git a/src/chain/block.cpp b/src/chain/block.cpp index ecf27ed27d..7913075557 100644 --- a/src/chain/block.cpp +++ b/src/chain/block.cpp @@ -44,6 +44,8 @@ namespace libbitcoin { namespace system { namespace chain { +BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) + // Constructors. // ---------------------------------------------------------------------------- @@ -72,9 +74,7 @@ block::block(const chain::header::cptr& header, } block::block(const data_slice& data, bool witness) NOEXCEPT - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) : block(stream::in::copy(data), witness) - BC_POP_WARNING() { } @@ -144,10 +144,8 @@ block block::from_data(reader& source, bool witness) NOEXCEPT for (size_t tx = 0; tx < capacity; ++tx) { BC_PUSH_WARNING(NO_NEW_OR_DELETE) - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) txs->emplace_back(new transaction{ source, witness }); BC_POP_WARNING() - BC_POP_WARNING() } // This is a pointer copy (non-const to const). @@ -168,11 +166,7 @@ block block::from_data(reader& source, bool witness) NOEXCEPT data_chunk block::to_data(bool witness) const NOEXCEPT { data_chunk data(serialized_size(witness)); - - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) stream::out::copy ostream(data); - BC_POP_WARNING() - to_data(ostream, witness); return data; } @@ -218,17 +212,14 @@ const chain::header::cptr block::header_ptr() const NOEXCEPT // Roll up inputs for concurrent prevout processing. const inputs_cptr block::inputs_ptr() const NOEXCEPT { - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) const auto inputs = std::make_shared(); - BC_POP_WARNING() - - const auto append_inputs = [&inputs](const transaction::cptr& tx) + const auto append_ins = [&inputs](const transaction::cptr& tx) NOEXCEPT { const auto& tx_ins = *tx->inputs_ptr(); inputs->insert(inputs->end(), tx_ins.begin(), tx_ins.end()); }; - std::for_each(txs_->begin(), txs_->end(), append_inputs); + std::for_each(txs_->begin(), txs_->end(), append_ins); return inputs; } @@ -268,7 +259,7 @@ hash_digest block::hash() const NOEXCEPT size_t block::serialized_size(bool witness) const NOEXCEPT { // Overflow returns max_size_t. - const auto sum = [witness](size_t total, const transaction::cptr& tx) NOEXCEPT + const auto sum = [=](size_t total, const transaction::cptr& tx) NOEXCEPT { return ceilinged_add(total, tx->serialized_size(witness)); }; @@ -320,46 +311,22 @@ bool block::is_forward_reference() const NOEXCEPT if (txs_->empty()) return false; - const auto non_coinbase_txs = sub1(txs_->size()); - - // TODO: change to reference_wrapper(tx.hash) - first make referencable. - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) - unordered_set_of_constant_referenced_hashes hashes(non_coinbase_txs); - BC_POP_WARNING() - - const auto forward_in = [&hashes](const input::cptr& input) NOEXCEPT + const auto sum_txs = sub1(txs_->size()); + unordered_set_of_constant_referenced_hashes hashes{ sum_txs }; + const auto spent = [&hashes](const input::cptr& input) NOEXCEPT { - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) return hashes.find(std::ref(input->point().hash())) != hashes.end(); - BC_POP_WARNING() }; - const auto forward_tx = [&forward_in, &hashes]( - const transaction::cptr& tx) NOEXCEPT + const auto spend = [&spent, &hashes](const transaction::cptr& tx) NOEXCEPT { const auto& ins = *tx->inputs_ptr(); - const auto found = std::any_of(ins.begin(), ins.end(), forward_in); - - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) + const auto forward = std::any_of(ins.begin(), ins.end(), spent); hashes.emplace(tx->get_hash(false)); - BC_POP_WARNING() - - return found; + return forward; }; - return std::any_of(txs_->rbegin(), std::prev(txs_->rend()), forward_tx); -} - -// private -size_t block::non_coinbase_inputs() const NOEXCEPT -{ - // Overflow returns max_size_t. - const auto inputs = [](size_t total, const transaction::cptr& tx) NOEXCEPT - { - return ceilinged_add(total, tx->inputs()); - }; - - return std::accumulate(std::next(txs_->begin()), txs_->end(), zero, inputs); + return std::any_of(txs_->rbegin(), std::prev(txs_->rend()), spend); } // This also precludes the block merkle calculation DoS exploit by preventing @@ -370,26 +337,27 @@ bool block::is_internal_double_spend() const NOEXCEPT if (txs_->empty()) return false; - const auto non_coinbase_ins = non_coinbase_inputs(); - - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) - unordered_set_of_constant_referenced_points points(non_coinbase_ins); - BC_POP_WARNING() + // Overflow returns max_size_t. + const auto sum_ins = [](size_t total, const transaction::cptr& tx) NOEXCEPT + { + return ceilinged_add(total, tx->inputs()); + }; - const auto double_in = [&points](const input::cptr& in) NOEXCEPT + const auto tx1 = std::next(txs_->begin()); + const auto spends_count = std::accumulate(tx1, txs_->end(), zero, sum_ins); + unordered_set_of_constant_referenced_points points{ spends_count }; + const auto spent = [&points](const input::cptr& in) NOEXCEPT { - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) return !points.emplace(in->point()).second; - BC_POP_WARNING() }; - const auto double_tx = [&double_in](const transaction::cptr& tx) NOEXCEPT + const auto double_spent = [&spent](const transaction::cptr& tx) NOEXCEPT { const auto& ins = *tx->inputs_ptr(); - return std::any_of(ins.begin(), ins.end(), double_in); + return std::any_of(ins.begin(), ins.end(), spent); }; - return std::any_of(std::next(txs_->begin()), txs_->end(), double_tx); + return std::any_of(tx1, txs_->end(), double_spent); } // private @@ -440,26 +408,17 @@ bool block::is_hash_limit_exceeded() const NOEXCEPT unordered_set_of_constant_referenced_hashes hashes; // Just the coinbase tx hash, skip its null input hashes. - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) hashes.emplace(txs_->front()->get_hash(false)); - BC_POP_WARNING() for (auto tx = std::next(txs_->begin()); tx != txs_->end(); ++tx) { // Insert the transaction hash. - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) hashes.emplace((*tx)->get_hash(false)); - BC_POP_WARNING() - const auto& inputs = *(*tx)->inputs_ptr(); // Insert all input point hashes. for (const auto& input: inputs) - { - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) hashes.emplace(input->point().hash()); - BC_POP_WARNING() - } } return hashes.size() > hash_limit; @@ -652,7 +611,6 @@ bool block::is_unspent_coinbase_collision() const NOEXCEPT // Search is not ordered, forward references are caught by block.check. void block::populate() const NOEXCEPT { - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) unordered_map_of_constant_referenced_points points{}; uint32_t index{}; @@ -672,8 +630,6 @@ void block::populate() const NOEXCEPT in->prevout = point->second; } } - - BC_POP_WARNING() } // Delegated. @@ -833,6 +789,8 @@ code block::connect(const context& ctx) const NOEXCEPT return connect_transactions(ctx); } +BC_POP_WARNING() + // JSON value convertors. // ---------------------------------------------------------------------------- diff --git a/src/chain/script.cpp b/src/chain/script.cpp index 6668937e15..8598ca5be8 100644 --- a/src/chain/script.cpp +++ b/src/chain/script.cpp @@ -223,7 +223,7 @@ size_t script::op_count(reader& source) NOEXCEPT const auto start = source.get_read_position(); auto count = zero; - // TODO: this is expensive (0.83%). + // This is expensive (1.1%) but far less than vector reallocs (11.6%). while (operation::count_op(source)) ++count; diff --git a/src/chain/transaction.cpp b/src/chain/transaction.cpp index 0e5cdc1c57..ee4794eef8 100644 --- a/src/chain/transaction.cpp +++ b/src/chain/transaction.cpp @@ -43,6 +43,8 @@ namespace libbitcoin { namespace system { namespace chain { +BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) + // Precompute fixed elements of signature hashing. // ---------------------------------------------------------------------------- @@ -97,10 +99,8 @@ transaction::transaction(const transaction& other) NOEXCEPT { if (other.cache_) cache_ = std::make_unique(*other.cache_); - if (other.nominal_hash_) nominal_hash_ = std::make_unique(*other.nominal_hash_); - if (other.witness_hash_) witness_hash_ = std::make_unique(*other.witness_hash_); } @@ -128,9 +128,7 @@ transaction::transaction(uint32_t version, const chain::inputs_cptr& inputs, } transaction::transaction(const data_slice& data, bool witness) NOEXCEPT - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) : transaction(stream::in::copy(data), witness) - BC_POP_WARNING() { } @@ -237,10 +235,8 @@ read_puts(Source& source) NOEXCEPT for (auto put = zero; put < capacity; ++put) { BC_PUSH_WARNING(NO_NEW_OR_DELETE) - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) puts->emplace_back(new Put{ source }); BC_POP_WARNING() - BC_POP_WARNING() } // This is a pointer copy (non-const to const). @@ -257,8 +253,7 @@ transaction transaction::from_data(reader& source, bool witness) NOEXCEPT chain::outputs_cptr outputs; // Expensive repeated recomputation, so cache segregated state. - const auto segregated = - inputs->size() == witness_marker && + const auto segregated = inputs->size() == witness_marker && source.peek_byte() == witness_enabled; // Detect witness as no inputs (marker) and expected flag (bip144). @@ -423,32 +418,24 @@ uint64_t transaction::fee() const NOEXCEPT void transaction::set_nominal_hash(hash_digest&& hash) const NOEXCEPT { - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) nominal_hash_ = std::make_unique(std::move(hash)); - BC_POP_WARNING() } void transaction::set_witness_hash(hash_digest&& hash) const NOEXCEPT { - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) witness_hash_ = std::make_unique(std::move(hash)); - BC_POP_WARNING() } const hash_digest& transaction::get_hash(bool witness) const NOEXCEPT { if (witness) { - if (!witness_hash_) - set_witness_hash(hash(witness)); - + if (!witness_hash_) set_witness_hash(hash(witness)); return *witness_hash_; } else { - if (!nominal_hash_) - set_nominal_hash(hash(witness)); - + if (!nominal_hash_) set_nominal_hash(hash(witness)); return *nominal_hash_; } } @@ -459,10 +446,8 @@ hash_digest transaction::hash(bool witness) const NOEXCEPT { if (witness) { - // Avoid is_coinbase call if cache present. - if (witness_hash_) return *witness_hash_; - // Witness coinbase tx hash is assumed to be null_hash (bip141). + if (witness_hash_) return *witness_hash_; if (is_coinbase()) return null_hash; } else @@ -614,7 +599,6 @@ uint32_t transaction::input_index(const input_iterator& input) const NOEXCEPT std::distance(inputs_->begin(), input)); } -// C++14: switch in constexpr. //***************************************************************************** // CONSENSUS: Due to masking of bits 6/7 (8 is the anyone_can_pay flag), // there are 4 possible 7 bit values that can set "single" and 4 others that @@ -633,7 +617,6 @@ inline coverage mask_sighash(uint8_t sighash_flags) NOEXCEPT } } -/// REQUIRES INDEX. void transaction::signature_hash_single(writer& sink, const input_iterator& input, const script& sub, uint8_t sighash_flags) const NOEXCEPT @@ -666,8 +649,7 @@ void transaction::signature_hash_single(writer& sink, } }; - const auto write_outputs = [this, &input]( - writer& sink) NOEXCEPT + const auto write_outputs = [this, &input](writer& sink) NOEXCEPT { // Guarded by unversioned_signature_hash. const auto index = input_index(input); @@ -804,11 +786,15 @@ hash_digest transaction::unversioned_signature_hash( break; } case coverage::hash_none: + { signature_hash_none(sink, input, sub, sighash_flags); break; + } default: case coverage::hash_all: + { signature_hash_all(sink, input, sub, sighash_flags); + } } sink.flush(); @@ -1399,6 +1385,8 @@ code transaction::connect(const context& ctx) const NOEXCEPT return error::transaction_success; } +BC_POP_WARNING() + // JSON value convertors. // ---------------------------------------------------------------------------- From db1f2638649a25d89cd4280635d39973f0461f03 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Thu, 30 May 2024 13:24:08 -0400 Subject: [PATCH 08/13] Move is_roller() into script, style, comments. --- include/bitcoin/system/chain/block.hpp | 2 + include/bitcoin/system/chain/input.hpp | 3 ++ include/bitcoin/system/chain/output.hpp | 2 + include/bitcoin/system/chain/point.hpp | 2 + include/bitcoin/system/chain/script.hpp | 2 + include/bitcoin/system/chain/transaction.hpp | 33 +++++++------- include/bitcoin/system/chain/witness.hpp | 2 + src/chain/input.cpp | 5 ++ src/chain/script.cpp | 9 ++++ src/chain/transaction.cpp | 48 ++++++-------------- 10 files changed, 58 insertions(+), 50 deletions(-) diff --git a/include/bitcoin/system/chain/block.hpp b/include/bitcoin/system/chain/block.hpp index c2aa238489..7eac765b57 100644 --- a/include/bitcoin/system/chain/block.hpp +++ b/include/bitcoin/system/chain/block.hpp @@ -199,6 +199,8 @@ class BC_API block // copy: 4 * 64 + 1 = 33 bytes (vs. 16 when shared). chain::header::cptr header_; chain::transactions_cptr txs_; + + // Cache. bool valid_; }; diff --git a/include/bitcoin/system/chain/input.hpp b/include/bitcoin/system/chain/input.hpp index 7e2f19b6d5..50f71d63f2 100644 --- a/include/bitcoin/system/chain/input.hpp +++ b/include/bitcoin/system/chain/input.hpp @@ -109,6 +109,7 @@ class BC_API input /// ----------------------------------------------------------------------- bool is_final() const NOEXCEPT; + bool is_roller() const NOEXCEPT; bool reserved_hash(hash_digest& out) const NOEXCEPT; /// Assumes coinbase if prevout not populated (returns only legacy sigops). @@ -137,6 +138,8 @@ class BC_API input chain::script::cptr script_; chain::witness::cptr witness_; uint32_t sequence_; + + // Cache. bool valid_; public: diff --git a/include/bitcoin/system/chain/output.hpp b/include/bitcoin/system/chain/output.hpp index 42273880ee..712c83342b 100644 --- a/include/bitcoin/system/chain/output.hpp +++ b/include/bitcoin/system/chain/output.hpp @@ -101,6 +101,8 @@ class BC_API output // copy: 3 * 64 + 1 = 25 bytes (vs. 16 when shared). uint64_t value_; chain::script::cptr script_; + + // cache bool valid_; }; diff --git a/include/bitcoin/system/chain/point.hpp b/include/bitcoin/system/chain/point.hpp index a369b559da..3d9c8c75d4 100644 --- a/include/bitcoin/system/chain/point.hpp +++ b/include/bitcoin/system/chain/point.hpp @@ -103,6 +103,8 @@ class BC_API point // copy: 256 + 32 + 1 = 37 bytes (vs. 16 when shared). hash_digest hash_; uint32_t index_; + + // Cache. bool valid_; }; diff --git a/include/bitcoin/system/chain/script.hpp b/include/bitcoin/system/chain/script.hpp index 13336e9c75..1a1e51ceb1 100644 --- a/include/bitcoin/system/chain/script.hpp +++ b/include/bitcoin/system/chain/script.hpp @@ -483,6 +483,7 @@ class BC_API script const operations& ops() const NOEXCEPT; /// Computed properties. + bool is_roller() const NOEXCEPT; hash_digest hash() const NOEXCEPT; size_t serialized_size(bool prefix) const NOEXCEPT; @@ -526,6 +527,7 @@ class BC_API script // Script should be stored as shared. operations ops_; + // Cache. bool valid_; bool prefail_; size_t size_; diff --git a/include/bitcoin/system/chain/transaction.hpp b/include/bitcoin/system/chain/transaction.hpp index 471a65583c..3bee26af6b 100644 --- a/include/bitcoin/system/chain/transaction.hpp +++ b/include/bitcoin/system/chain/transaction.hpp @@ -224,14 +224,16 @@ class BC_API transaction bool is_confirmed_double_spend(size_t height) const NOEXCEPT; private: - chain::points points() const NOEXCEPT; - hash_digest outputs_hash() const NOEXCEPT; - hash_digest points_hash() const NOEXCEPT; - hash_digest sequences_hash() const NOEXCEPT; + typedef struct + { + hash_digest outputs; + hash_digest points; + hash_digest sequences; + } sighash_cache; + static transaction from_data(reader& source, bool witness) NOEXCEPT; static bool segregated(const chain::inputs& inputs) NOEXCEPT; static bool segregated(const chain::input_cptrs& inputs) NOEXCEPT; - ////static size_t maximum_size(bool coinbase) NOEXCEPT; // signature hash hash_digest output_hash(const input_iterator& input) const NOEXCEPT; @@ -249,6 +251,13 @@ class BC_API transaction const script& sub, uint64_t value, uint8_t sighash_flags, bool bip143) const NOEXCEPT; + // Caching. + chain::points points() const NOEXCEPT; + hash_digest outputs_hash() const NOEXCEPT; + hash_digest points_hash() const NOEXCEPT; + hash_digest sequences_hash() const NOEXCEPT; + void initialize_sighash_cache() const NOEXCEPT; + // Transaction should be stored as shared (adds 16 bytes). // copy: 5 * 64 + 2 = 41 bytes (vs. 16 when shared). uint32_t version_; @@ -256,25 +265,15 @@ class BC_API transaction chain::outputs_cptr outputs_; uint32_t locktime_; - // TODO: pack these flags. + // Cache. bool segregated_; bool valid_; -private: - typedef struct - { - hash_digest outputs; - hash_digest points; - hash_digest sequences; - } hash_cache; - - void initialize_hash_cache() const NOEXCEPT; - // TODO: use std::optional to avoid these pointer allocations (0.16%). // Signature and identity hash caching (witness hash if witnessed). - mutable std::unique_ptr cache_{}; mutable std::unique_ptr nominal_hash_{}; mutable std::unique_ptr witness_hash_{}; + mutable std::unique_ptr sighash_cache_{}; }; typedef std::vector transactions; diff --git a/include/bitcoin/system/chain/witness.hpp b/include/bitcoin/system/chain/witness.hpp index 2bf6dd3915..0a99490d5e 100644 --- a/include/bitcoin/system/chain/witness.hpp +++ b/include/bitcoin/system/chain/witness.hpp @@ -143,6 +143,8 @@ class BC_API witness // Witness should be stored as shared. chunk_cptrs stack_; + + // Cache. bool valid_; size_t size_; }; diff --git a/src/chain/input.cpp b/src/chain/input.cpp index bbfe19e9d9..5bb8e7bf4a 100644 --- a/src/chain/input.cpp +++ b/src/chain/input.cpp @@ -284,6 +284,11 @@ bool input::is_final() const NOEXCEPT return sequence_ == max_input_sequence; } +bool input::is_roller() const NOEXCEPT +{ + return script_->is_roller() || (prevout && prevout->script().is_roller()); +} + // static bool input::is_locked(uint32_t sequence, size_t height, uint32_t median_time_past, size_t prevout_height, diff --git a/src/chain/script.cpp b/src/chain/script.cpp index 8598ca5be8..8c7b8da3ff 100644 --- a/src/chain/script.cpp +++ b/src/chain/script.cpp @@ -368,6 +368,15 @@ const operations& script::ops() const NOEXCEPT return ops_; } +bool script::is_roller() const NOEXCEPT +{ + static const auto roll = operation{ opcode::roll }; + + // Naive implementation, any op_roll in script, late-counted. + // TODO: precompute on script parse, tune using performance profiling. + return contains(ops_, roll); +}; + // Consensus (witness::extract_script) and Electrum server payments key. hash_digest script::hash() const NOEXCEPT { diff --git a/src/chain/transaction.cpp b/src/chain/transaction.cpp index ee4794eef8..eaafa0ab77 100644 --- a/src/chain/transaction.cpp +++ b/src/chain/transaction.cpp @@ -97,12 +97,12 @@ transaction::transaction(const transaction& other) NOEXCEPT other.segregated_, other.valid_) { - if (other.cache_) - cache_ = std::make_unique(*other.cache_); if (other.nominal_hash_) nominal_hash_ = std::make_unique(*other.nominal_hash_); if (other.witness_hash_) witness_hash_ = std::make_unique(*other.witness_hash_); + if (other.sighash_cache_) + sighash_cache_ = std::make_unique(*other.sighash_cache_); } transaction::transaction(uint32_t version, chain::inputs&& inputs, @@ -193,14 +193,12 @@ transaction& transaction::operator=(const transaction& other) NOEXCEPT segregated_ = other.segregated_; valid_ = other.valid_; - if (other.cache_) - cache_ = std::make_unique(*other.cache_); - if (other.nominal_hash_) nominal_hash_ = std::make_unique(*other.nominal_hash_); - if (other.witness_hash_) witness_hash_ = std::make_unique(*other.witness_hash_); + if (other.sighash_cache_) + sighash_cache_ = std::make_unique(*other.sighash_cache_); return *this; } @@ -302,11 +300,7 @@ data_chunk transaction::to_data(bool witness) const NOEXCEPT witness &= segregated_; data_chunk data(serialized_size(witness)); - - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) stream::out::copy ostream(data); - BC_POP_WARNING() - to_data(ostream, witness); return data; } @@ -518,8 +512,8 @@ chain::points transaction::points() const NOEXCEPT hash_digest transaction::outputs_hash() const NOEXCEPT { - if (cache_) - return cache_->outputs; + if (sighash_cache_) + return sighash_cache_->outputs; BC_PUSH_WARNING(LOCAL_VARIABLE_NOT_INITIALIZED) hash_digest digest; @@ -538,8 +532,8 @@ hash_digest transaction::outputs_hash() const NOEXCEPT hash_digest transaction::points_hash() const NOEXCEPT { - if (cache_) - return cache_->points; + if (sighash_cache_) + return sighash_cache_->points; BC_PUSH_WARNING(LOCAL_VARIABLE_NOT_INITIALIZED) hash_digest digest; @@ -558,8 +552,8 @@ hash_digest transaction::points_hash() const NOEXCEPT hash_digest transaction::sequences_hash() const NOEXCEPT { - if (cache_) - return cache_->sequences; + if (sighash_cache_) + return sighash_cache_->sequences; BC_PUSH_WARNING(LOCAL_VARIABLE_NOT_INITIALIZED) hash_digest digest; @@ -806,22 +800,20 @@ hash_digest transaction::unversioned_signature_hash( // private // TODO: taproot requires both single and double hash of each. -void transaction::initialize_hash_cache() const NOEXCEPT +void transaction::initialize_sighash_cache() const NOEXCEPT { // This overconstructs the cache (anyone or !all), however it is simple and // the same criteria applied by satoshi. if (segregated_) { BC_PUSH_WARNING(NO_NEW_OR_DELETE) - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) - cache_.reset(new hash_cache + sighash_cache_.reset(new sighash_cache { outputs_hash(), points_hash(), sequences_hash() }); BC_POP_WARNING() - BC_POP_WARNING() } } @@ -1355,26 +1347,16 @@ code transaction::connect(const context& ctx) const NOEXCEPT if (is_coinbase()) return error::transaction_success; - code ec; + code ec{}; using namespace machine; - initialize_hash_cache(); - - const auto is_roller = [](const auto& input) NOEXCEPT - { - static const auto roll = operation{ opcode::roll }; - - // Naive implementation, any op_roll in either script, late-counted. - // TODO: precompute on script parse, tune using performance profiling. - return contains(input.script().ops(), roll) - || (input.prevout && contains(input.prevout->script().ops(), roll)); - }; + initialize_sighash_cache(); // Validate scripts. for (auto input = inputs_->begin(); input != inputs_->end(); ++input) { // Evaluate rolling scripts with linear search but constant erase. // Evaluate non-rolling scripts with constant search but linear erase. - if ((ec = is_roller(**input) ? + if ((ec = (*input)->is_roller() ? interpreter::connect(ctx, *this, input) : interpreter::connect(ctx, *this, input))) return ec; From c35db51a1c65aacab1988015e9f76160e9983468 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Thu, 30 May 2024 14:02:22 -0400 Subject: [PATCH 09/13] Fix ambiguous block test construction. --- test/chain/block.cpp | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/test/chain/block.cpp b/test/chain/block.cpp index 3b1f3d7d6e..67ec571a99 100644 --- a/test/chain/block.cpp +++ b/test/chain/block.cpp @@ -399,13 +399,14 @@ BOOST_AUTO_TEST_CASE(block__is_forward_reference__backward_reference__false) BOOST_AUTO_TEST_CASE(block__is_forward_reference__forward_reference__true) { - const transaction to{ 0, inputs{}, {}, 0 }; + const transaction cb{ 0, inputs{}, {}, 0 }; + const transaction to{ 0, inputs{}, {}, 42 }; const transaction from{ 0, { { { to.hash(false), 0 }, {}, 0 } }, {}, 0 }; const accessor instance { {}, { - {}, + cb, from, to } From 064eb73de7f9f78bca91df4ab3b86710fc0e14bc Mon Sep 17 00:00:00 2001 From: evoskuil Date: Thu, 30 May 2024 14:16:00 -0400 Subject: [PATCH 10/13] Add and apply to_unique<>. --- include/bitcoin/system/data/memory.hpp | 20 ++++++++++++++++++++ src/chain/transaction.cpp | 16 ++++++++-------- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/include/bitcoin/system/data/memory.hpp b/include/bitcoin/system/data/memory.hpp index f81835b4af..9d58027b2e 100644 --- a/include/bitcoin/system/data/memory.hpp +++ b/include/bitcoin/system/data/memory.hpp @@ -40,6 +40,9 @@ BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) // So in this case it constructs the shared_pointer which accepts the raw // pointer as its constructor argument, passed by std::vector.emplace_back. +/// shared_ptr +/// --------------------------------------------------------------------------- + /// Create empty shared pointer. template inline std::shared_ptr to_shared() NOEXCEPT @@ -88,6 +91,23 @@ template std::shared_ptr>> to_shareds(const std::vector& values) NOEXCEPT; +/// unique_ptr +/// --------------------------------------------------------------------------- + +/// Create unique pointer to const from moved instance. +template +inline std::unique_ptr to_unique(Type&& value) NOEXCEPT +{ + return std::make_unique(std::forward(value)); +} + +/// Create unique pointer to const from copied instance. +template +inline std::unique_ptr to_unique(const Type& value) NOEXCEPT +{ + return std::make_unique(value); +} + BC_POP_WARNING() } // namespace system diff --git a/src/chain/transaction.cpp b/src/chain/transaction.cpp index eaafa0ab77..d4b318afc2 100644 --- a/src/chain/transaction.cpp +++ b/src/chain/transaction.cpp @@ -98,11 +98,11 @@ transaction::transaction(const transaction& other) NOEXCEPT other.valid_) { if (other.nominal_hash_) - nominal_hash_ = std::make_unique(*other.nominal_hash_); + nominal_hash_ = to_unique(*other.nominal_hash_); if (other.witness_hash_) - witness_hash_ = std::make_unique(*other.witness_hash_); + witness_hash_ = to_unique(*other.witness_hash_); if (other.sighash_cache_) - sighash_cache_ = std::make_unique(*other.sighash_cache_); + sighash_cache_ = to_unique(*other.sighash_cache_); } transaction::transaction(uint32_t version, chain::inputs&& inputs, @@ -194,11 +194,11 @@ transaction& transaction::operator=(const transaction& other) NOEXCEPT valid_ = other.valid_; if (other.nominal_hash_) - nominal_hash_ = std::make_unique(*other.nominal_hash_); + nominal_hash_ = to_unique(*other.nominal_hash_); if (other.witness_hash_) - witness_hash_ = std::make_unique(*other.witness_hash_); + witness_hash_ = to_unique(*other.witness_hash_); if (other.sighash_cache_) - sighash_cache_ = std::make_unique(*other.sighash_cache_); + sighash_cache_ = to_unique(*other.sighash_cache_); return *this; } @@ -412,12 +412,12 @@ uint64_t transaction::fee() const NOEXCEPT void transaction::set_nominal_hash(hash_digest&& hash) const NOEXCEPT { - nominal_hash_ = std::make_unique(std::move(hash)); + nominal_hash_ = to_unique(std::move(hash)); } void transaction::set_witness_hash(hash_digest&& hash) const NOEXCEPT { - witness_hash_ = std::make_unique(std::move(hash)); + witness_hash_ = to_unique(std::move(hash)); } const hash_digest& transaction::get_hash(bool witness) const NOEXCEPT From 15ebdbafe569da68e79b7c2f12d7a55a11dea935 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Thu, 30 May 2024 15:08:23 -0400 Subject: [PATCH 11/13] Internal compiler error (not on CI). --- test/hash/sha/sha512.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test/hash/sha/sha512.cpp b/test/hash/sha/sha512.cpp index 5956291e94..0d3274fb73 100644 --- a/test/hash/sha/sha512.cpp +++ b/test/hash/sha/sha512.cpp @@ -247,9 +247,10 @@ BOOST_AUTO_TEST_CASE(sha512__merkle_root__three__expected) constexpr auto expected1 = sha512::double_hash({ 0 }, { 1 }); constexpr auto expected2 = sha512::double_hash({ 2 }, { 2 }); constexpr auto expected = sha512::double_hash(expected1, expected2); -#if defined (HAVE_VECTOR_CONSTEXPR) - static_assert(sha512::merkle_root({ { 0 }, { 1 }, { 2 } }) == expected); -#endif +// Internal compiler error vc++. +////#if defined (HAVE_VECTOR_CONSTEXPR) +//// static_assert(sha512::merkle_root({ { 0 }, { 1 }, { 2 } }) == expected); +////#endif BOOST_CHECK_EQUAL(sha512::merkle_root({ { 0 }, { 1 }, { 2 } }), expected); } From 0dd23496eb4351500879925189a9e2d2edc7aa85 Mon Sep 17 00:00:00 2001 From: evoskuil Date: Thu, 30 May 2024 19:06:41 -0400 Subject: [PATCH 12/13] Reorganize inline/constexpr static utils from script/ops. --- Makefile.am | 4 +- .../libbitcoin-system.vcxproj | 2 + .../libbitcoin-system.vcxproj.filters | 6 + include/bitcoin/system/chain/block.hpp | 2 + include/bitcoin/system/chain/input.hpp | 1 + include/bitcoin/system/chain/operation.hpp | 323 +------------- include/bitcoin/system/chain/output.hpp | 3 +- include/bitcoin/system/chain/script.hpp | 399 ++---------------- include/bitcoin/system/chain/transaction.hpp | 2 + .../bitcoin/system/impl/chain/operation.ipp | 343 +++++++++++++++ include/bitcoin/system/impl/chain/script.ipp | 376 +++++++++++++++++ .../system/impl/machine/interpreter.ipp | 2 +- src/chain/block.cpp | 2 +- src/chain/input.cpp | 13 +- src/chain/output.cpp | 10 +- src/chain/script.cpp | 29 +- 16 files changed, 815 insertions(+), 702 deletions(-) create mode 100644 include/bitcoin/system/impl/chain/operation.ipp create mode 100644 include/bitcoin/system/impl/chain/script.ipp diff --git a/Makefile.am b/Makefile.am index 584e9404f1..abe9a0f141 100755 --- a/Makefile.am +++ b/Makefile.am @@ -567,7 +567,9 @@ include_bitcoin_system_hash_sha_HEADERS = \ include_bitcoin_system_impl_chaindir = ${includedir}/bitcoin/system/impl/chain include_bitcoin_system_impl_chain_HEADERS = \ - include/bitcoin/system/impl/chain/compact.ipp + include/bitcoin/system/impl/chain/compact.ipp \ + include/bitcoin/system/impl/chain/operation.ipp \ + include/bitcoin/system/impl/chain/script.ipp include_bitcoin_system_impl_datadir = ${includedir}/bitcoin/system/impl/data include_bitcoin_system_impl_data_HEADERS = \ diff --git a/builds/msvc/vs2022/libbitcoin-system/libbitcoin-system.vcxproj b/builds/msvc/vs2022/libbitcoin-system/libbitcoin-system.vcxproj index beb89e37de..f379ff007b 100644 --- a/builds/msvc/vs2022/libbitcoin-system/libbitcoin-system.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-system/libbitcoin-system.vcxproj @@ -513,6 +513,8 @@ + + diff --git a/builds/msvc/vs2022/libbitcoin-system/libbitcoin-system.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-system/libbitcoin-system.vcxproj.filters index 2bde489668..258c374f9f 100644 --- a/builds/msvc/vs2022/libbitcoin-system/libbitcoin-system.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-system/libbitcoin-system.vcxproj.filters @@ -1414,6 +1414,12 @@ include\bitcoin\system\impl\chain + + include\bitcoin\system\impl\chain + + + include\bitcoin\system\impl\chain + include\bitcoin\system\impl\data diff --git a/include/bitcoin/system/chain/block.hpp b/include/bitcoin/system/chain/block.hpp index 7eac765b57..629a78cc8b 100644 --- a/include/bitcoin/system/chain/block.hpp +++ b/include/bitcoin/system/chain/block.hpp @@ -202,6 +202,8 @@ class BC_API block // Cache. bool valid_; + ////size_t nominal_size_; + ////size_t witness_size_; }; typedef std::vector blocks; diff --git a/include/bitcoin/system/chain/input.hpp b/include/bitcoin/system/chain/input.hpp index 50f71d63f2..016763ac3a 100644 --- a/include/bitcoin/system/chain/input.hpp +++ b/include/bitcoin/system/chain/input.hpp @@ -141,6 +141,7 @@ class BC_API input // Cache. bool valid_; + ////size_t size_; public: /// Public mutable metadata access, copied but not compared for equality. diff --git a/include/bitcoin/system/chain/operation.hpp b/include/bitcoin/system/chain/operation.hpp index 89250f0a69..1fe65078ec 100644 --- a/include/bitcoin/system/chain/operation.hpp +++ b/include/bitcoin/system/chain/operation.hpp @@ -22,7 +22,6 @@ #include #include #include -#include #include #include #include @@ -43,310 +42,30 @@ class BC_API operation /// Utilities. /// ----------------------------------------------------------------------- - /// Compute nominal data opcode based on size alone. - //************************************************************************* - // CONSENSUS: non-minial encoding consensus critical for find_and_delete. - //************************************************************************* - static constexpr opcode opcode_from_size(size_t size) NOEXCEPT - { - ////BC_ASSERT(size <= max_uint32); - constexpr auto op_75 = static_cast(opcode::push_size_75); - - if (size <= op_75) - return static_cast(size); - else if (size <= max_uint8) - return opcode::push_one_size; - else if (size <= max_uint16) - return opcode::push_two_size; - else - return opcode::push_four_size; - } - - // C++20: constexpr. - /// Compute the minimal data opcode for a given chunk of data. - /// Caller should clear data if converting to non-payload opcode. - static inline opcode minimal_opcode_from_data( - const data_chunk& data) NOEXCEPT - { - const auto size = data.size(); - - if (size == one) - { - const auto value = data.front(); - - if (value == numbers::negative_1) - return opcode::push_negative_1; - - if (value == numbers::number_0) - return opcode::push_size_0; - - if (value >= numbers::positive_1 && - value <= numbers::positive_16) - return opcode_from_positive(value); - } - - // Nominal encoding is minimal for multiple bytes and non-numerics. - return opcode_from_size(size); - } - - // C++20: constexpr. - /// Compute the nominal data opcode for a given chunk of data. - /// Restricted to sized data, avoids conversion to numeric opcodes. - static inline opcode nominal_opcode_from_data( - const data_chunk& data) NOEXCEPT - { - return opcode_from_size(data.size()); - } - - /// Convert the [0..16] value to the corresponding opcode (or undefined). - static constexpr opcode opcode_from_version(uint8_t value) NOEXCEPT - { - ////BC_ASSERT(value <= numbers::positive_16); - return (value == numbers::number_0) ? opcode::push_size_0 : - operation::opcode_from_positive(value); - } - - /// Convert the [1..16] value to the corresponding opcode (or undefined). - static constexpr opcode opcode_from_positive(uint8_t value) NOEXCEPT - { - ////BC_ASSERT(value >= numbers::positive_1); - ////BC_ASSERT(value <= numbers::positive_16); - constexpr auto op_81 = static_cast(opcode::push_positive_1); - return static_cast(value + sub1(op_81)); - } - - /// Convert the opcode to the corresponding [1..16] value (or undefined). - static constexpr uint8_t opcode_to_positive(opcode code) NOEXCEPT - { - ////BC_ASSERT(is_positive(code)); - constexpr auto op_81 = static_cast(opcode::push_positive_1); - return static_cast(code) - sub1(op_81); - } - - /// Compute maximum push data size for the opcode (without script limit). - static constexpr size_t opcode_to_maximum_size(opcode code) NOEXCEPT - { - constexpr auto op_75 = static_cast(opcode::push_size_75); - - switch (code) - { - case opcode::push_one_size: - return max_uint8; - case opcode::push_two_size: - return max_uint16; - case opcode::push_four_size: - return max_uint32; - default: - const auto byte = static_cast(code); - return byte <= op_75 ? byte : zero; - } - } + static constexpr uint8_t opcode_to_positive(opcode code) NOEXCEPT; + static constexpr size_t opcode_to_maximum_size(opcode code) NOEXCEPT; + static constexpr opcode opcode_from_size(size_t size) NOEXCEPT; + static constexpr opcode opcode_from_version(uint8_t value) NOEXCEPT; + static constexpr opcode opcode_from_positive(uint8_t value) NOEXCEPT; + static constexpr opcode minimal_opcode_from_data( + const data_chunk& data) NOEXCEPT; + static constexpr opcode nominal_opcode_from_data( + const data_chunk& data) NOEXCEPT; /// Categories of opcodes. /// ----------------------------------------------------------------------- - /// opcode: [0..96]. - //************************************************************************* - // CONSENSUS: this test explicitly includes the satoshi 'reserved' code. - // This affects the operation count in p2sh script evaluation. - // Presumably this was an unintended consequence of range testing enums. - //************************************************************************* - static constexpr bool is_relaxed_push(opcode code) NOEXCEPT - { - constexpr auto op_96 = opcode::push_positive_16; - return code <= op_96; - } - - /// opcode: [0..79, 81..96]. - static constexpr bool is_push(opcode code) NOEXCEPT - { - constexpr auto op_80 = opcode::reserved_80; - return is_relaxed_push(code) && code != op_80; - } - - /// opcode: [1..78]. - static constexpr bool is_payload(opcode code) NOEXCEPT - { - constexpr auto op_1 = opcode::push_size_1; - constexpr auto op_78 = opcode::push_four_size; - return code >= op_1 && code <= op_78; - } - - /// opcode: [97..255]. - static constexpr bool is_counted(opcode code) NOEXCEPT - { - constexpr auto op_97 = opcode::nop; - return code >= op_97; - } - - /// stack: [1..16]. - static constexpr bool is_positive(opcode code) NOEXCEPT - { - constexpr auto op_81 = opcode::push_positive_1; - constexpr auto op_96 = opcode::push_positive_16; - return code >= op_81 && code <= op_96; - } - - /// stack: [0, 1..16]. - static constexpr bool is_version(opcode code) NOEXCEPT - { - constexpr auto op_0 = opcode::push_size_0; - return code == op_0 || is_positive(code); - } - - /// stack: [-1, 1..16] (zero is not 'numeric' on the stack). - static constexpr bool is_numeric(opcode code) NOEXCEPT - { - constexpr auto op_79 = opcode::push_negative_1; - return code == op_79 || is_positive(code); - } - - /// stack: [-1, 0, 1..16]. - static constexpr bool is_number(opcode code) NOEXCEPT - { - constexpr auto op_79 = opcode::push_negative_1; - return code == op_79 || is_version(code); - } - - // opcode: [101, 102, 126..129, 131..134, 141, 142, 149..153] - // ************************************************************************ - // CONSENSUS: These fail script even if wrapped by a conditional operation. - // ************************************************************************ - static constexpr bool is_invalid(opcode code) NOEXCEPT - { - switch (code) - { - // Demoted to invalid by [0.3.6] soft fork. - case opcode::op_verif: - case opcode::op_vernotif: - - // Demoted to invalid by [0.3.10] soft fork. - case opcode::op_cat: - case opcode::op_substr: - case opcode::op_left: - case opcode::op_right: - case opcode::op_invert: - case opcode::op_and: - case opcode::op_or: - case opcode::op_xor: - case opcode::op_mul2: - case opcode::op_div2: - case opcode::op_mul: - case opcode::op_div: - case opcode::op_mod: - case opcode::op_lshift: - case opcode::op_rshift: - return true; - default: - return false; - } - } - - // opcode: [99..100, 103..104] - static constexpr bool is_conditional(opcode code) NOEXCEPT - { - switch (code) - { - case opcode::if_: - case opcode::notif: - case opcode::else_: - case opcode::endif: - return true; - default: - return false; - } - } - - /// opcode: [80, 98, 106, 137..138, 186..255] - /// ************************************************************************ - /// CONSENSUS: These fail script unless excluded by a conditional operation. - /// - /// Satoshi tests incorrectly refer to op_ver and op_verif as "reserved". - /// Reserved refers to unused but conditionally acceptable codes. When the - /// conditional operator skips over them, the script may be valid. On the - /// other hand, "disabled" codes are unconditionally invalid - such as - /// op_cat. The "disabled" codes are in a group outside of the evaluation - /// switch, which makes their unconditional invalidity obvious. The other - /// two disabled codes are not so obvious in behavior, and misidentified in - /// satoshi test vectors: - /// - /// These fail because the scripts are unconditional with both reserved and - /// disabled codes, yet they are all referred to as reserved. - /// { "1", "ver", "op_ver is reserved" } - /// { "1", "verif", "op_verif is reserved" } - /// { "1", "vernotif", "op_vernotif is reserved" } - /// { "1", "reserved", "op_reserved is reserved" } - /// { "1", "reserved1", "op_reserved1 is reserved" } - /// { "1", "reserved2", "op_reserved2 is reserved" } - /// - /// These fail because they either execute conditionally invalid codes - /// (op_ver) or include unconditionally invalid codes, without execution - /// (op_verif, op_vernotif). The comments are correct, contradicting above. - /// { "1", "if ver else 1 endif", "ver is reserved" } - /// { "0", "if verif else 1 endif", "verif illegal everywhere" } - /// { "0", "if else 1 else verif endif", "verif illegal everywhere" } - /// { "0", "if vernotif else 1 endif", "vernotif illegal everywhere" } - /// { "0", "if else 1 else vernotif endif", "vernotif illegal everywhere" } - /// - /// These fail regardless of conditional exclusion because they are also - /// disabled codes. - /// { "'a' 'b'", "cat", "cat disabled" } - /// { "'a' 'b' 0", "if cat else 1 endif", "cat disabled" } - /// { "'abc' 1 1", "substr", "substr disabled" } - /// { "'abc' 1 1 0", "if substr else 1 endif", "substr disabled" } - /// { "'abc' 2 0", "if left else 1 endif", "left disabled" } - /// { "'abc' 2 0", "if right else 1 endif", "right disabled" } - /// { "'abc'", "if invert else 1 endif", "invert disabled" } - /// { "1 2 0 if and else 1 endif", "nop", "and disabled" } - /// { "1 2 0 if or else 1 endif", "nop", "or disabled" } - /// { "1 2 0 if xor else 1 endif", "nop", "xor disabled" } - /// { "2 0 if 2mul else 1 endif", "nop", "2mul disabled" } - /// { "2 0 if 2div else 1 endif", "nop", "2div disabled" } - /// { "2 2 0 if mul else 1 endif", "nop", "mul disabled" } - /// { "2 2 0 if div else 1 endif", "nop", "div disabled" } - /// { "2 2 0 if mod else 1 endif", "nop", "mod disabled" } - /// { "2 2 0 if lshift else 1 endif", "nop", "lshift disabled" } - /// { "2 2 0 if rshift else 1 endif", "nop", "rshift disabled" } - /// - /// The reason op_verif and op_vernotif are unconditionally invalid (and - /// therefore behave exactly as "disabled" codes is they are conditionals. - /// Note that op_ver is not a conditional, so despite similar name, when - /// it was disabled it became a "reserved" code. The former conditonals - /// are not excludable by remaining conditions. They pass this condition: - /// else if (fExec || (OP_IF <= opcode && opcode <= OP_ENDIF)) - /// which evaluates all codes, yet there were removed from the evaluation. - /// They were part of this case, but now this break is not hit by them: - /// case OP_IF: - /// case OP_NOTIF: {...} - /// break; - /// So they fall through to the conditional default case, just as any - /// reserved code would, and halt the evaluation with the bad_opcode error: - /// default: - /// return set_error(serror, SCRIPT_ERR_BAD_OPCODE); - /// Yet because they do not bypass any conditional evaluation they hit this - /// default unconditionally. So they are "disabled" codes. We use the term - /// "invalid" above because of the confusion that can cause. The only truly - /// "reserved" codes are op_nop# codes, which were promoted by a hard fork. - /// ************************************************************************ - static constexpr bool is_reserved(opcode code) NOEXCEPT - { - constexpr auto op_185 = opcode::nop10; - - switch (code) - { - // Demoted to reserved by [0.3.6] soft fork. - case opcode::op_ver: - case opcode::op_return: - - // Unimplemented. - case opcode::reserved_80: - case opcode::reserved_137: - case opcode::reserved_138: - return true; - default: - return code > op_185; - } - } + static constexpr bool is_relaxed_push(opcode code) NOEXCEPT; + static constexpr bool is_push(opcode code) NOEXCEPT; + static constexpr bool is_payload(opcode code) NOEXCEPT; + static constexpr bool is_counted(opcode code) NOEXCEPT; + static constexpr bool is_positive(opcode code) NOEXCEPT; + static constexpr bool is_version(opcode code) NOEXCEPT; + static constexpr bool is_numeric(opcode code) NOEXCEPT; + static constexpr bool is_number(opcode code) NOEXCEPT; + static constexpr bool is_invalid(opcode code) NOEXCEPT; + static constexpr bool is_conditional(opcode code) NOEXCEPT; + static constexpr bool is_reserved(opcode code) NOEXCEPT; /// Constructors. /// ----------------------------------------------------------------------- @@ -472,6 +191,8 @@ DECLARE_JSON_VALUE_CONVERTORS(operation::cptr); } // namespace system } // namespace libbitcoin +#include + namespace std { template<> diff --git a/include/bitcoin/system/chain/output.hpp b/include/bitcoin/system/chain/output.hpp index 712c83342b..3b00639cb8 100644 --- a/include/bitcoin/system/chain/output.hpp +++ b/include/bitcoin/system/chain/output.hpp @@ -102,8 +102,9 @@ class BC_API output uint64_t value_; chain::script::cptr script_; - // cache + // Cache. bool valid_; + ////size_t size_; }; typedef std::vector outputs; diff --git a/include/bitcoin/system/chain/script.hpp b/include/bitcoin/system/chain/script.hpp index 1a1e51ceb1..321e8871d7 100644 --- a/include/bitcoin/system/chain/script.hpp +++ b/include/bitcoin/system/chain/script.hpp @@ -23,16 +23,13 @@ #include #include #include -#include #include -#include #include #include #include #include #include #include -#include #include #include #include @@ -52,382 +49,44 @@ class BC_API script /// ----------------------------------------------------------------------- /// Determine if the flag is enabled in the active flags set. - static constexpr bool is_enabled(uint32_t active_flags, - chain::flags flag) NOEXCEPT - { - return to_bool(flag & active_flags); - } - - static inline bool is_push_only(const operations& ops) NOEXCEPT - { - const auto push = [](const operation& op) NOEXCEPT - { - // !constexpr - return op.is_push(); - }; - - return std::all_of(ops.begin(), ops.end(), push); - } - - //************************************************************************* - // CONSENSUS: this pattern is used to activate bip16 validation rules. - //************************************************************************* - static inline bool is_relaxed_push(const operations& ops) NOEXCEPT - { - const auto push = [&](const operation& op) NOEXCEPT - { - // !constexpr - return op.is_relaxed_push(); - }; - - return std::all_of(ops.begin(), ops.end(), push); - } - - // More efficient [] dereferences are all guarded here. - BC_PUSH_WARNING(NO_ARRAY_INDEXING) - - //************************************************************************* - // CONSENSUS: BIP34 requires coinbase input script to begin with one byte - // that indicates height size. This is inconsistent with an extreme future - // where the size byte overflows. However satoshi actually requires nominal - // encoding. - //************************************************************************* - static bool is_coinbase_pattern(const operations& ops, - size_t height) NOEXCEPT; - - //************************************************************************* - // CONSENSUS: this pattern is used to commit to bip141 witness data. - //************************************************************************* - static inline bool is_commitment_pattern(const operations& ops) NOEXCEPT - { - constexpr auto header = to_big_endian(chain::witness_head); - - // C++14: remove && ops[1].data().size() >= header.size() guard. - // Bytes after commitment optional with no consensus meaning (bip141). - // Commitment not executable so invalid trailing operations are allowed. - return ops.size() > 1 - && ops[0].code() == opcode::op_return - && ops[1].code() == opcode::push_size_36 - && ops[1].data().size() >= header.size() - && std::equal(header.begin(), header.end(), ops[1].data().begin()); - } - - // C++20 constexpr. - //************************************************************************* - // CONSENSUS: this pattern is used in bip141 validation rules. - //************************************************************************* - static inline bool is_witness_program_pattern( - const operations& ops) NOEXCEPT - { - return ops.size() == 2 - && ops[0].is_version() - && ops[1].data().size() >= min_witness_program - && ops[1].data().size() <= max_witness_program; - } - - // C++20 constexpr. - // The satoshi client tests for 83 bytes total. This allows for waste of - // one byte to represent up to 75 bytes using the push_one_size opcode. - // It also allows any number of push ops and limits value to 0 and 1 per tx. - ////static inline bool is_pay_null_data_pattern(const operations& ops) - ////{ - //// constexpr auto op_76 = static_cast(opcode::push_one_size); - //// - //// return ops.size() >= 2 - //// && ops[0].code() == opcode::return_ - //// && static_cast(ops[1].code()) <= op_76 - //// && ops[1].data().size() <= max_null_data_size; - ////} - - // C++20 constexpr. - // Used by neutrino. - static inline bool is_pay_op_return_pattern( - const operations& ops) NOEXCEPT - { - return !ops.empty() - && ops[0].code() == opcode::op_return; - } - - // C++20 constexpr. - // The satoshi client enables configurable data size for policy. - static inline bool is_pay_null_data_pattern( - const operations& ops) NOEXCEPT - { - return ops.size() == 2 - && ops[0].code() == opcode::op_return - && ops[1].is_minimal_push() - && ops[1].data().size() <= max_null_data_size; - } - - // C++20 constexpr. - // The current 16 (or 20) limit does not affect server indexing because bare - // multisig is not indexable and p2sh multisig is byte-limited to 15 sigs. - // The satoshi client policy limit is 3 signatures for bare multisig. - static inline bool is_pay_multisig_pattern( - const operations& ops) NOEXCEPT - { - constexpr auto op_1 = static_cast(opcode::push_positive_1); - constexpr auto op_16 = static_cast(opcode::push_positive_16); - - const auto op_count = ops.size(); - - if (op_count < 4 || ops[op_count - 1].code() != opcode::checkmultisig) - return false; - - const auto op_m = static_cast(ops[0].code()); - const auto op_n = static_cast(ops[op_count - 2].code()); - - if (op_m < op_1 || op_m > op_n || op_n < op_1 || op_n > op_16) - return false; - - const auto number = op_n - op_1 + 1u; - const auto points = op_count - 3u; - - if (number != points) - return false; - - for (auto op = std::next(ops.begin()); - op != std::prev(ops.end(), 2); ++op) - { - if (!is_public_key(op->data())) - return false; - } - - return true; - } - - // C++20 constexpr. - // The satoshi client considers this non-standard for policy. - static inline bool is_pay_public_key_pattern( - const operations& ops) NOEXCEPT - { - return ops.size() == 2 - && is_public_key(ops[0].data()) - && ops[1].code() == opcode::checksig; - } - - // C++20 constexpr. - static inline bool is_pay_key_hash_pattern( - const operations& ops) NOEXCEPT - { - return ops.size() == 5 - && ops[0].code() == opcode::dup - && ops[1].code() == opcode::hash160 - && ops[2].data().size() == short_hash_size - && ops[3].code() == opcode::equalverify - && ops[4].code() == opcode::checksig; - } - - // C++20 constexpr. - //************************************************************************* - // CONSENSUS: this pattern is used to activate bip16 validation rules. - //************************************************************************* - static inline bool is_pay_script_hash_pattern( - const operations& ops) NOEXCEPT - { - return ops.size() == 3 - && ops[0].code() == opcode::hash160 - && ops[1].code() == opcode::push_size_20 - && ops[2].code() == opcode::equal; - } - - // C++20 constexpr. - static inline bool is_pay_witness_pattern( - const operations& ops) NOEXCEPT - { - return ops.size() == 2 - && ops[0].is_version() - && ops[1].is_push(); - } - - // C++20 constexpr. - static inline bool is_pay_witness_key_hash_pattern( - const operations& ops) NOEXCEPT - { - return ops.size() == 2 - && ops[0].code() == opcode::push_size_0 - && ops[1].code() == opcode::push_size_20; - } - - // C++20 constexpr. - //************************************************************************* - // CONSENSUS: this pattern is used to activate bip141 validation rules. - //************************************************************************* - static inline bool is_pay_witness_script_hash_pattern( - const operations& ops) NOEXCEPT - { - return ops.size() == 2 - && ops[0].code() == opcode::push_size_0 - && ops[1].code() == opcode::push_size_32; - } - - // C++20 constexpr. - // The first push is based on wacky satoshi op_check_multisig behavior that - // we must perpetuate, though it's appearance here is policy not consensus. - // Limiting to push_size_0 removes pattern ambiguity with little downside. - static inline bool is_sign_multisig_pattern( - const operations& ops) NOEXCEPT - { - const auto endorsement = [](const operation& op) NOEXCEPT - { - return is_endorsement(op.data()); - }; - - return ops.size() >= 2 - && ops[0].code() == opcode::push_size_0 - && std::all_of(std::next(ops.begin()), ops.end(), endorsement); - } - - // C++20 constexpr. - static inline bool is_sign_public_key_pattern( - const operations& ops) NOEXCEPT - { - return ops.size() == 1 - && is_endorsement(ops[0].data()); - } - - // C++20 constexpr. - //************************************************************************* - // CONSENSUS: this pattern is used to activate bip141 validation rules. - //************************************************************************* - static inline bool is_sign_key_hash_pattern( - const operations& ops) NOEXCEPT - { - return ops.size() == 2 - && is_endorsement(ops[0].data()) - && is_public_key(ops[1].data()); - } - - BC_POP_WARNING(/*NO_ARRAY_INDEXING*/) - - // C++20 constexpr. - // Ambiguous with is_sign_key_hash when second/last op is a public key. - // Ambiguous with is_sign_public_key_pattern when only op is endorsement. - static inline bool is_sign_script_hash_pattern( - const operations& ops) NOEXCEPT - { - return !ops.empty() - && is_push_only(ops) - && !ops.back().data().empty(); - } + static constexpr bool is_enabled(uint32_t active_flags, flags flag) NOEXCEPT; + static constexpr bool is_push_only_pattern(const operations& ops) NOEXCEPT; + static constexpr bool is_relaxed_push_pattern(const operations& ops) NOEXCEPT; + static constexpr bool is_commitment_pattern(const operations& ops) NOEXCEPT; + static constexpr bool is_witness_program_pattern(const operations& ops) NOEXCEPT; + static constexpr bool is_pay_null_data_pattern(const operations& ops) NOEXCEPT; + static constexpr bool is_pay_op_return_pattern(const operations& ops) NOEXCEPT; + static constexpr bool is_pay_multisig_pattern(const operations& ops) NOEXCEPT; + static constexpr bool is_pay_public_key_pattern(const operations& ops) NOEXCEPT; + static constexpr bool is_pay_key_hash_pattern(const operations& ops) NOEXCEPT; + static constexpr bool is_pay_script_hash_pattern(const operations& ops) NOEXCEPT; + static constexpr bool is_pay_witness_pattern(const operations& ops) NOEXCEPT; + static constexpr bool is_pay_witness_key_hash_pattern(const operations& ops) NOEXCEPT; + static constexpr bool is_pay_witness_script_hash_pattern(const operations& ops) NOEXCEPT; + static constexpr bool is_sign_multisig_pattern(const operations& ops) NOEXCEPT; + static constexpr bool is_sign_public_key_pattern(const operations& ops) NOEXCEPT; + static constexpr bool is_sign_key_hash_pattern(const operations& ops) NOEXCEPT; + static constexpr bool is_sign_script_hash_pattern(const operations& ops) NOEXCEPT; + static bool is_coinbase_pattern(const operations& ops, size_t height) NOEXCEPT; static inline operations to_pay_null_data_pattern( - const data_slice& data) NOEXCEPT - { - return data.size() > max_null_data_size ? operations{} : operations - { - { opcode::op_return }, - { to_chunk(data), false } - }; - } - + const data_slice& data) NOEXCEPT; static inline operations to_pay_public_key_pattern( - const data_slice& point) NOEXCEPT - { - return !is_public_key(point) ? operations{} : operations - { - { to_chunk(point), false }, - { opcode::checksig } - }; - } - + const data_slice& point) NOEXCEPT; static inline operations to_pay_key_hash_pattern( - const short_hash& hash) NOEXCEPT - { - return operations - { - { opcode::dup }, - { opcode::hash160 }, - { to_chunk(hash), false }, - { opcode::equalverify }, - { opcode::checksig } - }; - } - + const short_hash& hash) NOEXCEPT; static inline operations to_pay_script_hash_pattern( - const short_hash& hash) NOEXCEPT - { - return operations - { - { opcode::hash160 }, - { to_chunk(hash), false }, - { opcode::equal } - }; - } - + const short_hash& hash) NOEXCEPT; static inline operations to_pay_multisig_pattern(uint8_t signatures, - const compressed_list& points) NOEXCEPT - { - return to_pay_multisig_pattern(signatures, - to_stack(points)); - } - - // This supports up to 16 signatures, but check_multisig is limited to 20. - // The embedded script is limited to 520 bytes, an effective limit of 15 - // for p2sh multisig, which can be as low as 7 with all uncompressed keys. + const compressed_list& points) NOEXCEPT; static inline operations to_pay_multisig_pattern(uint8_t signatures, - const data_stack& points) NOEXCEPT - { - constexpr auto op_81 = static_cast(opcode::push_positive_1); - constexpr auto op_96 = static_cast(opcode::push_positive_16); - constexpr auto base = sub1(op_81); - constexpr auto max = op_96 - base; - - const auto m = signatures; - const auto n = points.size(); - - if (m < 1 || m > n || n < 1 || n > max) - return operations(); - - const auto op_m = static_cast(m + base); - const auto op_n = static_cast(points.size() + base); - - operations ops; - ops.reserve(points.size() + 3); - ops.emplace_back(op_m); - - for (const auto& point: points) - { - if (!is_public_key(point)) - return {}; - - ops.emplace_back(point, false); - } - - ops.emplace_back(op_n); - ops.emplace_back(opcode::checkmultisig); - return ops; - } - + const data_stack& points) NOEXCEPT; static inline operations to_pay_witness_pattern(uint8_t version, - const data_slice& data) NOEXCEPT - { - return - { - { operation::opcode_from_version(version) }, - { to_chunk(data), false }, - }; - } - + const data_slice& data) NOEXCEPT; static inline operations to_pay_witness_key_hash_pattern( - const short_hash& hash) NOEXCEPT - { - return - { - { opcode::push_size_0 }, - { to_chunk(hash), false }, - }; - } - + const short_hash& hash) NOEXCEPT; static inline operations to_pay_witness_script_hash_pattern( - const hash_digest& hash) NOEXCEPT - { - return - { - { opcode::push_size_0 }, - { to_chunk(hash), false } - }; - } + const hash_digest& hash) NOEXCEPT; /// Constructors. /// ----------------------------------------------------------------------- @@ -548,6 +207,8 @@ DECLARE_JSON_VALUE_CONVERTORS(script::cptr); } // namespace system } // namespace libbitcoin +#include + namespace std { template<> diff --git a/include/bitcoin/system/chain/transaction.hpp b/include/bitcoin/system/chain/transaction.hpp index 3bee26af6b..f7c64f8aea 100644 --- a/include/bitcoin/system/chain/transaction.hpp +++ b/include/bitcoin/system/chain/transaction.hpp @@ -268,6 +268,8 @@ class BC_API transaction // Cache. bool segregated_; bool valid_; + ////size_t nominal_size_; + ////size_t witness_size_; // TODO: use std::optional to avoid these pointer allocations (0.16%). // Signature and identity hash caching (witness hash if witnessed). diff --git a/include/bitcoin/system/impl/chain/operation.ipp b/include/bitcoin/system/impl/chain/operation.ipp new file mode 100644 index 0000000000..7a657aa977 --- /dev/null +++ b/include/bitcoin/system/impl/chain/operation.ipp @@ -0,0 +1,343 @@ +/** + * Copyright (c) 2011-2024 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#ifndef LIBBITCOIN_SYSTEM_CHAIN_OPERATION_IPP +#define LIBBITCOIN_SYSTEM_CHAIN_OPERATION_IPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace libbitcoin { +namespace system { +namespace chain { + +// Convert the opcode to the corresponding [1..16] value (or undefined). +constexpr uint8_t operation::opcode_to_positive(opcode code) NOEXCEPT +{ + ////BC_ASSERT(is_positive(code)); + constexpr auto op_81 = static_cast(opcode::push_positive_1); + return static_cast(code) - sub1(op_81); +} + +// Compute maximum push data size for the opcode (without script limit). +constexpr size_t operation::opcode_to_maximum_size(opcode code) NOEXCEPT +{ + constexpr auto op_75 = static_cast(opcode::push_size_75); + + switch (code) + { + case opcode::push_one_size: + return max_uint8; + case opcode::push_two_size: + return max_uint16; + case opcode::push_four_size: + return max_uint32; + default: + const auto byte = static_cast(code); + return byte <= op_75 ? byte : zero; + } +} + +// Compute nominal data opcode based on size alone. +// **************************************************************************** +// CONSENSUS: non-minial encoding consensus critical for find_and_delete. +// **************************************************************************** +constexpr opcode operation::opcode_from_size(size_t size) NOEXCEPT +{ + ////BC_ASSERT(size <= max_uint32); + constexpr auto op_75 = static_cast(opcode::push_size_75); + + if (size <= op_75) + return static_cast(size); + else if (size <= max_uint8) + return opcode::push_one_size; + else if (size <= max_uint16) + return opcode::push_two_size; + else + return opcode::push_four_size; +} + +// Convert the [0..16] value to the corresponding opcode (or undefined). +constexpr opcode operation::opcode_from_version(uint8_t value) NOEXCEPT +{ + ////BC_ASSERT(value <= numbers::positive_16); + return (value == numbers::number_0) ? opcode::push_size_0 : + operation::opcode_from_positive(value); +} + +// Convert the [1..16] value to the corresponding opcode (or undefined). +constexpr opcode operation::opcode_from_positive(uint8_t value) NOEXCEPT +{ + ////BC_ASSERT(value >= numbers::positive_1); + ////BC_ASSERT(value <= numbers::positive_16); + constexpr auto op_81 = static_cast(opcode::push_positive_1); + return static_cast(value + sub1(op_81)); +} + +// Compute the minimal data opcode for a given chunk of data. +// Caller should clear data if converting to non-payload opcode. +constexpr opcode operation::minimal_opcode_from_data( + const data_chunk& data) NOEXCEPT +{ + const auto size = data.size(); + + if (size == one) + { + const auto value = data.front(); + + if (value == numbers::negative_1) + return opcode::push_negative_1; + + if (value == numbers::number_0) + return opcode::push_size_0; + + if (value >= numbers::positive_1 && + value <= numbers::positive_16) + return opcode_from_positive(value); + } + + // Nominal encoding is minimal for multiple bytes and non-numerics. + return opcode_from_size(size); +} + +// Compute the nominal data opcode for a given chunk of data. +// Restricted to sized data, avoids conversion to numeric opcodes. +constexpr opcode operation::nominal_opcode_from_data( + const data_chunk& data) NOEXCEPT +{ + return opcode_from_size(data.size()); +} + + +// Categories of opcodes. +// ---------------------------------------------------------------------------- + +// opcode: [0..96]. +// **************************************************************************** +// CONSENSUS: this test explicitly includes the satoshi 'reserved' code. +// This affects the operation count in p2sh script evaluation. +// Presumably this was an unintended consequence of range testing enums. +// **************************************************************************** +constexpr bool operation::is_relaxed_push(opcode code) NOEXCEPT +{ + constexpr auto op_96 = opcode::push_positive_16; + return code <= op_96; +} + +// opcode: [0..79, 81..96]. +constexpr bool operation::is_push(opcode code) NOEXCEPT +{ + constexpr auto op_80 = opcode::reserved_80; + return is_relaxed_push(code) && code != op_80; +} + +// opcode: [1..78]. +constexpr bool operation::is_payload(opcode code) NOEXCEPT +{ + constexpr auto op_1 = opcode::push_size_1; + constexpr auto op_78 = opcode::push_four_size; + return code >= op_1 && code <= op_78; +} + +// opcode: [97..255]. +constexpr bool operation::is_counted(opcode code) NOEXCEPT +{ + constexpr auto op_97 = opcode::nop; + return code >= op_97; +} + +// stack: [1..16]. +constexpr bool operation::is_positive(opcode code) NOEXCEPT +{ + constexpr auto op_81 = opcode::push_positive_1; + constexpr auto op_96 = opcode::push_positive_16; + return code >= op_81 && code <= op_96; +} + +// stack: [0, 1..16]. +constexpr bool operation::is_version(opcode code) NOEXCEPT +{ + constexpr auto op_0 = opcode::push_size_0; + return code == op_0 || is_positive(code); +} + +// stack: [-1, 1..16] (zero is not 'numeric' on the stack). +constexpr bool operation::is_numeric(opcode code) NOEXCEPT +{ + constexpr auto op_79 = opcode::push_negative_1; + return code == op_79 || is_positive(code); +} + +// stack: [-1, 0, 1..16]. +constexpr bool operation::is_number(opcode code) NOEXCEPT +{ + constexpr auto op_79 = opcode::push_negative_1; + return code == op_79 || is_version(code); +} + +// opcode: [101, 102, 126..129, 131..134, 141, 142, 149..153] +// **************************************************************************** +// CONSENSUS: These fail script even if wrapped by a conditional operation. +// **************************************************************************** +constexpr bool operation::is_invalid(opcode code) NOEXCEPT +{ + switch (code) + { + // Demoted to invalid by [0.3.6] soft fork. + case opcode::op_verif: + case opcode::op_vernotif: + + // Demoted to invalid by [0.3.10] soft fork. + case opcode::op_cat: + case opcode::op_substr: + case opcode::op_left: + case opcode::op_right: + case opcode::op_invert: + case opcode::op_and: + case opcode::op_or: + case opcode::op_xor: + case opcode::op_mul2: + case opcode::op_div2: + case opcode::op_mul: + case opcode::op_div: + case opcode::op_mod: + case opcode::op_lshift: + case opcode::op_rshift: + return true; + default: + return false; + } +} + +// opcode: [99..100, 103..104] +constexpr bool operation::is_conditional(opcode code) NOEXCEPT +{ + switch (code) + { + case opcode::if_: + case opcode::notif: + case opcode::else_: + case opcode::endif: + return true; + default: + return false; + } +} + +// opcode: [80, 98, 106, 137..138, 186..255] +// **************************************************************************** +// CONSENSUS: These fail script unless excluded by a conditional operation. +// +// Satoshi tests incorrectly refer to op_ver and op_verif as "reserved". +// Reserved refers to unused but conditionally acceptable codes. When the +// conditional operator skips over them, the script may be valid. On the +// other hand, "disabled" codes are unconditionally invalid - such as +// op_cat. The "disabled" codes are in a group outside of the evaluation +// switch, which makes their unconditional invalidity obvious. The other +// two disabled codes are not so obvious in behavior, and misidentified in +// satoshi test vectors: +// +// These fail because the scripts are unconditional with both reserved and +// disabled codes, yet they are all referred to as reserved. +// { "1", "ver", "op_ver is reserved" } +// { "1", "verif", "op_verif is reserved" } +// { "1", "vernotif", "op_vernotif is reserved" } +// { "1", "reserved", "op_reserved is reserved" } +// { "1", "reserved1", "op_reserved1 is reserved" } +// { "1", "reserved2", "op_reserved2 is reserved" } +// +// These fail because they either execute conditionally invalid codes +// (op_ver) or include unconditionally invalid codes, without execution +// (op_verif, op_vernotif). The comments are correct, contradicting above. +// { "1", "if ver else 1 endif", "ver is reserved" } +// { "0", "if verif else 1 endif", "verif illegal everywhere" } +// { "0", "if else 1 else verif endif", "verif illegal everywhere" } +// { "0", "if vernotif else 1 endif", "vernotif illegal everywhere" } +// { "0", "if else 1 else vernotif endif", "vernotif illegal everywhere" } +// +// These fail regardless of conditional exclusion because they are also +// disabled codes. +// { "'a' 'b'", "cat", "cat disabled" } +// { "'a' 'b' 0", "if cat else 1 endif", "cat disabled" } +// { "'abc' 1 1", "substr", "substr disabled" } +// { "'abc' 1 1 0", "if substr else 1 endif", "substr disabled" } +// { "'abc' 2 0", "if left else 1 endif", "left disabled" } +// { "'abc' 2 0", "if right else 1 endif", "right disabled" } +// { "'abc'", "if invert else 1 endif", "invert disabled" } +// { "1 2 0 if and else 1 endif", "nop", "and disabled" } +// { "1 2 0 if or else 1 endif", "nop", "or disabled" } +// { "1 2 0 if xor else 1 endif", "nop", "xor disabled" } +// { "2 0 if 2mul else 1 endif", "nop", "2mul disabled" } +// { "2 0 if 2div else 1 endif", "nop", "2div disabled" } +// { "2 2 0 if mul else 1 endif", "nop", "mul disabled" } +// { "2 2 0 if div else 1 endif", "nop", "div disabled" } +// { "2 2 0 if mod else 1 endif", "nop", "mod disabled" } +// { "2 2 0 if lshift else 1 endif", "nop", "lshift disabled" } +// { "2 2 0 if rshift else 1 endif", "nop", "rshift disabled" } +// +// The reason op_verif and op_vernotif are unconditionally invalid (and +// therefore behave exactly as "disabled" codes is they are conditionals. +// Note that op_ver is not a conditional, so despite similar name, when +// it was disabled it became a "reserved" code. The former conditonals +// are not excludable by remaining conditions. They pass this condition: +// else if (fExec || (OP_IF <= opcode && opcode <= OP_ENDIF)) +// which evaluates all codes, yet there were removed from the evaluation. +// They were part of this case, but now this break is not hit by them: +// case OP_IF: +// case OP_NOTIF: {...} +// break; +// So they fall through to the conditional default case, just as any +// reserved code would, and halt the evaluation with the bad_opcode error: +// default: +// return set_error(serror, SCRIPT_ERR_BAD_OPCODE); +// Yet because they do not bypass any conditional evaluation they hit this +// default unconditionally. So they are "disabled" codes. We use the term +// "invalid" above because of the confusion that can cause. The only truly +// "reserved" codes are op_nop# codes, which were promoted by a hard fork. +// **************************************************************************** +constexpr bool operation::is_reserved(opcode code) NOEXCEPT +{ + constexpr auto op_185 = opcode::nop10; + + switch (code) + { + // Demoted to reserved by [0.3.6] soft fork. + case opcode::op_ver: + case opcode::op_return: + + // Unimplemented. + case opcode::reserved_80: + case opcode::reserved_137: + case opcode::reserved_138: + return true; + default: + return code > op_185; + } +} + +} // namespace chain +} // namespace system +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/system/impl/chain/script.ipp b/include/bitcoin/system/impl/chain/script.ipp new file mode 100644 index 0000000000..79b1d99ad6 --- /dev/null +++ b/include/bitcoin/system/impl/chain/script.ipp @@ -0,0 +1,376 @@ +/** + * Copyright (c) 2011-2024 libbitcoin developers (see AUTHORS) + * + * This file is part of libbitcoin. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ +#ifndef LIBBITCOIN_SYSTEM_CHAIN_SCRIPT_IPP +#define LIBBITCOIN_SYSTEM_CHAIN_SCRIPT_IPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace libbitcoin { +namespace system { +namespace chain { + +// More efficient [] dereferences are all guarded here. +BC_PUSH_WARNING(NO_ARRAY_INDEXING) + +constexpr bool script::is_enabled(uint32_t active_flags, flags flag) NOEXCEPT +{ + return to_bool(flag & active_flags); +} + +constexpr bool script::is_push_only_pattern(const operations& ops) NOEXCEPT +{ + const auto push = [](const operation& op) NOEXCEPT + { + return operation::is_push(op.code()); + }; + + return std::all_of(ops.begin(), ops.end(), push); +} + +// **************************************************************************** +// CONSENSUS: this pattern is used to activate bip16 validation rules. +// **************************************************************************** +constexpr bool script::is_relaxed_push_pattern(const operations& ops) NOEXCEPT +{ + const auto push = [&](const operation& op) NOEXCEPT + { + return operation::is_relaxed_push(op.code()); + }; + + return std::all_of(ops.begin(), ops.end(), push); +} + +// **************************************************************************** +// CONSENSUS: this pattern is used to commit to bip141 witness data. +// **************************************************************************** +constexpr bool script::is_commitment_pattern(const operations& ops) NOEXCEPT +{ + constexpr auto header = to_big_endian(chain::witness_head); + + // C++14: remove && ops[1].data().size() >= header.size() guard. + // Bytes after commitment optional with no consensus meaning (bip141). + // Commitment not executable so invalid trailing operations are allowed. + return ops.size() > 1 + && ops[0].code() == opcode::op_return + && ops[1].code() == opcode::push_size_36 + && ops[1].data().size() >= header.size() + && std::equal(header.begin(), header.end(), ops[1].data().begin()); +} + +// **************************************************************************** +// CONSENSUS: this pattern is used in bip141 validation rules. +// **************************************************************************** +constexpr bool script::is_witness_program_pattern( + const operations& ops) NOEXCEPT +{ + return ops.size() == 2 + && ops[0].is_version() + && ops[1].data().size() >= min_witness_program + && ops[1].data().size() <= max_witness_program; +} + +// The satoshi client now enables configurable data size for policy. +constexpr bool script::is_pay_null_data_pattern(const operations& ops) NOEXCEPT +{ + return ops.size() == 2 + && ops[0].code() == opcode::op_return + && ops[1].is_minimal_push() + && ops[1].data().size() <= max_null_data_size; +} + +// Used by neutrino. +constexpr bool script::is_pay_op_return_pattern(const operations& ops) NOEXCEPT +{ + return !ops.empty() + && ops[0].code() == opcode::op_return; +} + +// The current 16 (or 20) limit does not affect server indexing because bare +// multisig is not indexable and p2sh multisig is byte-limited to 15 sigs. +// The satoshi client policy limit is 3 signatures for bare multisig. +constexpr bool script::is_pay_multisig_pattern(const operations& ops) NOEXCEPT +{ + constexpr auto op_1 = static_cast(opcode::push_positive_1); + constexpr auto op_16 = static_cast(opcode::push_positive_16); + + const auto op_count = ops.size(); + + if (op_count < 4 || ops[op_count - 1].code() != opcode::checkmultisig) + return false; + + const auto op_m = static_cast(ops[0].code()); + const auto op_n = static_cast(ops[op_count - 2].code()); + + if (op_m < op_1 || op_m > op_n || op_n < op_1 || op_n > op_16) + return false; + + const auto number = op_n - op_1 + 1u; + const auto points = op_count - 3u; + + if (number != points) + return false; + + for (auto op = std::next(ops.begin()); + op != std::prev(ops.end(), 2); ++op) + { + if (!is_public_key(op->data())) + return false; + } + + return true; +} + +// The satoshi client considers this non-standard for policy. +constexpr bool script::is_pay_public_key_pattern( + const operations& ops) NOEXCEPT +{ + return ops.size() == 2 + && is_public_key(ops[0].data()) + && ops[1].code() == opcode::checksig; +} + +constexpr bool script::is_pay_key_hash_pattern(const operations& ops) NOEXCEPT +{ + return ops.size() == 5 + && ops[0].code() == opcode::dup + && ops[1].code() == opcode::hash160 + && ops[2].data().size() == short_hash_size + && ops[3].code() == opcode::equalverify + && ops[4].code() == opcode::checksig; +} + +// **************************************************************************** +// CONSENSUS: this pattern is used to activate bip16 validation rules. +// **************************************************************************** +constexpr bool script::is_pay_script_hash_pattern( + const operations& ops) NOEXCEPT +{ + return ops.size() == 3 + && ops[0].code() == opcode::hash160 + && ops[1].code() == opcode::push_size_20 + && ops[2].code() == opcode::equal; +} + +constexpr bool script::is_pay_witness_pattern(const operations& ops) NOEXCEPT +{ + return ops.size() == 2 + && ops[0].is_version() + && ops[1].is_push(); +} + +constexpr bool script::is_pay_witness_key_hash_pattern( + const operations& ops) NOEXCEPT +{ + return ops.size() == 2 + && ops[0].code() == opcode::push_size_0 + && ops[1].code() == opcode::push_size_20; +} + +// **************************************************************************** +// CONSENSUS: this pattern is used to activate bip141 validation rules. +// **************************************************************************** +constexpr bool script::is_pay_witness_script_hash_pattern( + const operations& ops) NOEXCEPT +{ + return ops.size() == 2 + && ops[0].code() == opcode::push_size_0 + && ops[1].code() == opcode::push_size_32; +} + +// The first push is based on wacky satoshi op_check_multisig behavior that +// we must perpetuate, though it's appearance here is policy not consensus. +// Limiting to push_size_0 removes pattern ambiguity with little downside. +constexpr bool script::is_sign_multisig_pattern(const operations& ops) NOEXCEPT +{ + const auto endorsement = [](const operation& op) NOEXCEPT + { + return is_endorsement(op.data()); + }; + + return ops.size() >= 2 + && ops[0].code() == opcode::push_size_0 + && std::all_of(std::next(ops.begin()), ops.end(), endorsement); +} + +constexpr bool script::is_sign_public_key_pattern( + const operations& ops) NOEXCEPT +{ + return ops.size() == 1 + && is_endorsement(ops[0].data()); +} + +// **************************************************************************** +// CONSENSUS: this pattern is used to activate bip141 validation rules. +// **************************************************************************** +constexpr bool script::is_sign_key_hash_pattern(const operations& ops) NOEXCEPT +{ + return ops.size() == 2 + && is_endorsement(ops[0].data()) + && is_public_key(ops[1].data()); +} + + +// Ambiguous with is_sign_key_hash when second/last op is a public key. +// Ambiguous with is_sign_public_key_pattern when only op is endorsement. +constexpr bool script::is_sign_script_hash_pattern( + const operations& ops) NOEXCEPT +{ + return !ops.empty() + && is_push_only_pattern(ops) + && !ops.back().data().empty(); +} + +inline operations script::to_pay_null_data_pattern( + const data_slice& data) NOEXCEPT +{ + if (data.size() > max_null_data_size) + return {}; + + return + { + { opcode::op_return }, + { to_chunk(data), false } + }; +} + +inline operations script::to_pay_public_key_pattern( + const data_slice& point) NOEXCEPT +{ + if (!is_public_key(point)) + return {}; + + return + { + { to_chunk(point), false }, + { opcode::checksig } + }; +} + +inline operations script::to_pay_key_hash_pattern( + const short_hash& hash) NOEXCEPT +{ + return + { + { opcode::dup }, + { opcode::hash160 }, + { to_chunk(hash), false }, + { opcode::equalverify }, + { opcode::checksig } + }; +} + +inline operations script::to_pay_script_hash_pattern( + const short_hash& hash) NOEXCEPT +{ + return + { + { opcode::hash160 }, + { to_chunk(hash), false }, + { opcode::equal } + }; +} + +inline operations script::to_pay_multisig_pattern(uint8_t signatures, + const compressed_list& points) NOEXCEPT +{ + return to_pay_multisig_pattern(signatures, + to_stack(points)); +} + +// This supports up to 16 signatures, but check_multisig is limited to 20. +// The embedded script is limited to 520 bytes, an effective limit of 15 +// for p2sh multisig, which can be as low as 7 with all uncompressed keys. +inline operations script::to_pay_multisig_pattern(uint8_t signatures, + const data_stack& points) NOEXCEPT +{ + constexpr auto op_81 = static_cast(opcode::push_positive_1); + constexpr auto op_96 = static_cast(opcode::push_positive_16); + constexpr auto base = sub1(op_81); + constexpr auto max = op_96 - base; + + const auto m = signatures; + const auto n = points.size(); + + if (m < 1 || m > n || n < 1 || n > max) + return {}; + + const auto op_m = static_cast(m + base); + const auto op_n = static_cast(points.size() + base); + + operations ops{}; + ops.reserve(points.size() + 3); + ops.emplace_back(op_m); + + for (const auto& point: points) + { + if (!is_public_key(point)) + return {}; + + ops.emplace_back(point, false); + } + + ops.emplace_back(op_n); + ops.emplace_back(opcode::checkmultisig); + return ops; +} + +inline operations script::to_pay_witness_pattern(uint8_t version, + const data_slice& data) NOEXCEPT +{ + return + { + { operation::opcode_from_version(version) }, + { to_chunk(data), false }, + }; +} + +inline operations script::to_pay_witness_key_hash_pattern( + const short_hash& hash) NOEXCEPT +{ + return + { + { opcode::push_size_0 }, + { to_chunk(hash), false }, + }; +} + +inline operations script::to_pay_witness_script_hash_pattern( + const hash_digest& hash) NOEXCEPT +{ + return + { + { opcode::push_size_0 }, + { to_chunk(hash), false } + }; +} + +BC_POP_WARNING() + +} // namespace chain +} // namespace system +} // namespace libbitcoin + +#endif diff --git a/include/bitcoin/system/impl/machine/interpreter.ipp b/include/bitcoin/system/impl/machine/interpreter.ipp index 68f6f74a45..cbb4668684 100644 --- a/include/bitcoin/system/impl/machine/interpreter.ipp +++ b/include/bitcoin/system/impl/machine/interpreter.ipp @@ -1660,7 +1660,7 @@ code interpreter::connect_embedded(const context& state, const auto& input = **it; // Input script is limited to relaxed push data operations (bip16). - if (!script::is_relaxed_push(input.script().ops())) + if (!script::is_relaxed_push_pattern(input.script().ops())) return error::invalid_script_embed; // Embedded script must be at the top of the stack (bip16). diff --git a/src/chain/block.cpp b/src/chain/block.cpp index 7913075557..503f944f98 100644 --- a/src/chain/block.cpp +++ b/src/chain/block.cpp @@ -376,7 +376,7 @@ bool block::is_invalid_merkle_root() const NOEXCEPT size_t block::weight() const NOEXCEPT { - // Block weight is 3 * Base size * + 1 * Total size (bip141). + // Block weight is 3 * nominal size * + 1 * witness size (bip141). return base_size_contribution * serialized_size(false) + total_size_contribution * serialized_size(true); } diff --git a/src/chain/input.cpp b/src/chain/input.cpp index 5bb8e7bf4a..6a13c96c9b 100644 --- a/src/chain/input.cpp +++ b/src/chain/input.cpp @@ -35,6 +35,8 @@ namespace libbitcoin { namespace system { namespace chain { +BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) + // Product overflows guarded by script size limit. static_assert(max_script_size < max_size_t / multisig_default_sigops / heavy_sigops_factor, @@ -111,9 +113,7 @@ input::input(const chain::point::cptr& point, const chain::script::cptr& script, } input::input(const data_slice& data) NOEXCEPT - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) : input(stream::in::copy(data)) - BC_POP_WARNING() { } @@ -197,11 +197,7 @@ input input::from_data(reader& source) NOEXCEPT data_chunk input::to_data() const NOEXCEPT { data_chunk data(serialized_size(false)); - - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) stream::out::copy ostream(data); - BC_POP_WARNING() - to_data(ostream); return data; } @@ -220,7 +216,6 @@ void input::to_data(writer& sink) const NOEXCEPT sink.write_4_bytes_little_endian(sequence_); } -// TODO: this is expensive. size_t input::serialized_size(bool witness) const NOEXCEPT { // input.serialized_size(witness) provides sizing for witness, however @@ -342,7 +337,7 @@ bool input::extract_sigop_script(chain::script& out, // There are no embedded sigops when the input script is not push only. const auto& ops = script_->ops(); - if (ops.empty() || !script::is_relaxed_push(ops)) + if (ops.empty() || !script::is_relaxed_push_pattern(ops)) return false; // Parse the embedded script from the last input script item (data). @@ -396,6 +391,8 @@ size_t input::signature_operations(bool bip16, bool bip141) const NOEXCEPT return sigops; } +BC_POP_WARNING() + // JSON value convertors. // ---------------------------------------------------------------------------- diff --git a/src/chain/output.cpp b/src/chain/output.cpp index 35bba72a49..6f2c176afd 100644 --- a/src/chain/output.cpp +++ b/src/chain/output.cpp @@ -30,6 +30,8 @@ namespace libbitcoin { namespace system { namespace chain { +BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) + // This is a consensus critical value that must be set on reset. const uint64_t output::not_found = sighash_null_value; @@ -59,9 +61,7 @@ output::output(uint64_t value, const chain::script::cptr& script) NOEXCEPT } output::output(const data_slice& data) NOEXCEPT - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) : output(stream::in::copy(data)) - BC_POP_WARNING() { } @@ -136,11 +136,7 @@ output output::from_data(reader& source) NOEXCEPT data_chunk output::to_data() const NOEXCEPT { data_chunk data(serialized_size()); - - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) stream::out::copy ostream(data); - BC_POP_WARNING() - to_data(ostream); return data; } @@ -228,6 +224,8 @@ bool output::is_dust(uint64_t minimum_value) const NOEXCEPT return value_ < minimum_value && !script_->is_unspendable(); } +BC_POP_WARNING() + // JSON value convertors. // ---------------------------------------------------------------------------- diff --git a/src/chain/script.cpp b/src/chain/script.cpp index 8c7b8da3ff..a9c231e874 100644 --- a/src/chain/script.cpp +++ b/src/chain/script.cpp @@ -47,14 +47,23 @@ namespace chain { using namespace bc::system::machine; -// static (should be inline or constexpr). -// TODO: Avoiding circular include on machine. +BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) + +// static +// TODO: would be inlined but machine is a circular include. +//***************************************************************************** +// CONSENSUS: BIP34 requires coinbase input script to begin with one byte +// that indicates height size. This is inconsistent with an extreme future +// where the size byte overflows. However satoshi actually requires nominal +// encoding. +//************************************************************************* bool script::is_coinbase_pattern(const operations& ops, size_t height) NOEXCEPT { - // TODO: number::chunk::from_int constexpr? + BC_PUSH_WARNING(NO_ARRAY_INDEXING) return !ops.empty() && ops[0].is_nominal_push() && ops[0].data() == number::chunk::from_integer(to_unsigned(height)); + BC_POP_WARNING() } // Constructors. @@ -104,9 +113,7 @@ script::script(const operations& ops, bool prefail) NOEXCEPT } script::script(const data_slice& data, bool prefix) NOEXCEPT - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) : script(stream::in::copy(data), prefix) - BC_POP_WARNING() { } @@ -300,11 +307,7 @@ script script::from_string(const std::string& mnemonic) NOEXCEPT data_chunk script::to_data(bool prefix) const NOEXCEPT { data_chunk data(serialized_size(prefix)); - - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) stream::out::copy ostream(data); - BC_POP_WARNING() - to_data(ostream, prefix); return data; } @@ -332,8 +335,6 @@ std::string script::to_string(uint32_t active_flags) const NOEXCEPT std::ostringstream text; // Throwing stream aborts. - BC_PUSH_WARNING(NO_THROW_IN_NOEXCEPT) - for (const auto& op: ops()) { text << (first ? "" : " ") << op.to_string(active_flags); @@ -342,8 +343,6 @@ std::string script::to_string(uint32_t active_flags) const NOEXCEPT // An invalid operation has a specialized serialization. return text.str(); - - BC_POP_WARNING() } @@ -410,7 +409,7 @@ size_t script::serialized_size(bool prefix) const NOEXCEPT const data_chunk& script::witness_program() const NOEXCEPT { - static const data_chunk empty; + static const data_chunk empty{}; BC_PUSH_WARNING(NO_ARRAY_INDEXING) return is_witness_program_pattern(ops()) ? ops()[1].data() : empty; @@ -556,6 +555,8 @@ bool script::is_unspendable() const NOEXCEPT return operation::is_reserved(code) || operation::is_invalid(code); } +BC_POP_WARNING() + // JSON value convertors. // ---------------------------------------------------------------------------- From 9701658e066b4ca49d31f4b51708fad322c4d41e Mon Sep 17 00:00:00 2001 From: evoskuil Date: Thu, 30 May 2024 19:39:37 -0400 Subject: [PATCH 13/13] Use VCONSTEXPR for g++11. --- include/bitcoin/system/chain/operation.hpp | 5 +-- include/bitcoin/system/chain/script.hpp | 34 +++++++++---------- .../bitcoin/system/impl/chain/operation.ipp | 4 +-- include/bitcoin/system/impl/chain/script.ipp | 34 +++++++++---------- 4 files changed, 39 insertions(+), 38 deletions(-) diff --git a/include/bitcoin/system/chain/operation.hpp b/include/bitcoin/system/chain/operation.hpp index 1fe65078ec..95c1116aa1 100644 --- a/include/bitcoin/system/chain/operation.hpp +++ b/include/bitcoin/system/chain/operation.hpp @@ -47,9 +47,10 @@ class BC_API operation static constexpr opcode opcode_from_size(size_t size) NOEXCEPT; static constexpr opcode opcode_from_version(uint8_t value) NOEXCEPT; static constexpr opcode opcode_from_positive(uint8_t value) NOEXCEPT; - static constexpr opcode minimal_opcode_from_data( + + static VCONSTEXPR opcode minimal_opcode_from_data( const data_chunk& data) NOEXCEPT; - static constexpr opcode nominal_opcode_from_data( + static VCONSTEXPR opcode nominal_opcode_from_data( const data_chunk& data) NOEXCEPT; /// Categories of opcodes. diff --git a/include/bitcoin/system/chain/script.hpp b/include/bitcoin/system/chain/script.hpp index 321e8871d7..1a857dea14 100644 --- a/include/bitcoin/system/chain/script.hpp +++ b/include/bitcoin/system/chain/script.hpp @@ -50,23 +50,23 @@ class BC_API script /// Determine if the flag is enabled in the active flags set. static constexpr bool is_enabled(uint32_t active_flags, flags flag) NOEXCEPT; - static constexpr bool is_push_only_pattern(const operations& ops) NOEXCEPT; - static constexpr bool is_relaxed_push_pattern(const operations& ops) NOEXCEPT; - static constexpr bool is_commitment_pattern(const operations& ops) NOEXCEPT; - static constexpr bool is_witness_program_pattern(const operations& ops) NOEXCEPT; - static constexpr bool is_pay_null_data_pattern(const operations& ops) NOEXCEPT; - static constexpr bool is_pay_op_return_pattern(const operations& ops) NOEXCEPT; - static constexpr bool is_pay_multisig_pattern(const operations& ops) NOEXCEPT; - static constexpr bool is_pay_public_key_pattern(const operations& ops) NOEXCEPT; - static constexpr bool is_pay_key_hash_pattern(const operations& ops) NOEXCEPT; - static constexpr bool is_pay_script_hash_pattern(const operations& ops) NOEXCEPT; - static constexpr bool is_pay_witness_pattern(const operations& ops) NOEXCEPT; - static constexpr bool is_pay_witness_key_hash_pattern(const operations& ops) NOEXCEPT; - static constexpr bool is_pay_witness_script_hash_pattern(const operations& ops) NOEXCEPT; - static constexpr bool is_sign_multisig_pattern(const operations& ops) NOEXCEPT; - static constexpr bool is_sign_public_key_pattern(const operations& ops) NOEXCEPT; - static constexpr bool is_sign_key_hash_pattern(const operations& ops) NOEXCEPT; - static constexpr bool is_sign_script_hash_pattern(const operations& ops) NOEXCEPT; + static VCONSTEXPR bool is_push_only_pattern(const operations& ops) NOEXCEPT; + static VCONSTEXPR bool is_relaxed_push_pattern(const operations& ops) NOEXCEPT; + static VCONSTEXPR bool is_commitment_pattern(const operations& ops) NOEXCEPT; + static VCONSTEXPR bool is_witness_program_pattern(const operations& ops) NOEXCEPT; + static VCONSTEXPR bool is_pay_null_data_pattern(const operations& ops) NOEXCEPT; + static VCONSTEXPR bool is_pay_op_return_pattern(const operations& ops) NOEXCEPT; + static VCONSTEXPR bool is_pay_multisig_pattern(const operations& ops) NOEXCEPT; + static VCONSTEXPR bool is_pay_public_key_pattern(const operations& ops) NOEXCEPT; + static VCONSTEXPR bool is_pay_key_hash_pattern(const operations& ops) NOEXCEPT; + static VCONSTEXPR bool is_pay_script_hash_pattern(const operations& ops) NOEXCEPT; + static VCONSTEXPR bool is_pay_witness_pattern(const operations& ops) NOEXCEPT; + static VCONSTEXPR bool is_pay_witness_key_hash_pattern(const operations& ops) NOEXCEPT; + static VCONSTEXPR bool is_pay_witness_script_hash_pattern(const operations& ops) NOEXCEPT; + static VCONSTEXPR bool is_sign_multisig_pattern(const operations& ops) NOEXCEPT; + static VCONSTEXPR bool is_sign_public_key_pattern(const operations& ops) NOEXCEPT; + static VCONSTEXPR bool is_sign_key_hash_pattern(const operations& ops) NOEXCEPT; + static VCONSTEXPR bool is_sign_script_hash_pattern(const operations& ops) NOEXCEPT; static bool is_coinbase_pattern(const operations& ops, size_t height) NOEXCEPT; static inline operations to_pay_null_data_pattern( diff --git a/include/bitcoin/system/impl/chain/operation.ipp b/include/bitcoin/system/impl/chain/operation.ipp index 7a657aa977..d3bc0c6826 100644 --- a/include/bitcoin/system/impl/chain/operation.ipp +++ b/include/bitcoin/system/impl/chain/operation.ipp @@ -97,7 +97,7 @@ constexpr opcode operation::opcode_from_positive(uint8_t value) NOEXCEPT // Compute the minimal data opcode for a given chunk of data. // Caller should clear data if converting to non-payload opcode. -constexpr opcode operation::minimal_opcode_from_data( +VCONSTEXPR opcode operation::minimal_opcode_from_data( const data_chunk& data) NOEXCEPT { const auto size = data.size(); @@ -123,7 +123,7 @@ constexpr opcode operation::minimal_opcode_from_data( // Compute the nominal data opcode for a given chunk of data. // Restricted to sized data, avoids conversion to numeric opcodes. -constexpr opcode operation::nominal_opcode_from_data( +VCONSTEXPR opcode operation::nominal_opcode_from_data( const data_chunk& data) NOEXCEPT { return opcode_from_size(data.size()); diff --git a/include/bitcoin/system/impl/chain/script.ipp b/include/bitcoin/system/impl/chain/script.ipp index 79b1d99ad6..d696d33017 100644 --- a/include/bitcoin/system/impl/chain/script.ipp +++ b/include/bitcoin/system/impl/chain/script.ipp @@ -40,7 +40,7 @@ constexpr bool script::is_enabled(uint32_t active_flags, flags flag) NOEXCEPT return to_bool(flag & active_flags); } -constexpr bool script::is_push_only_pattern(const operations& ops) NOEXCEPT +VCONSTEXPR bool script::is_push_only_pattern(const operations& ops) NOEXCEPT { const auto push = [](const operation& op) NOEXCEPT { @@ -53,7 +53,7 @@ constexpr bool script::is_push_only_pattern(const operations& ops) NOEXCEPT // **************************************************************************** // CONSENSUS: this pattern is used to activate bip16 validation rules. // **************************************************************************** -constexpr bool script::is_relaxed_push_pattern(const operations& ops) NOEXCEPT +VCONSTEXPR bool script::is_relaxed_push_pattern(const operations& ops) NOEXCEPT { const auto push = [&](const operation& op) NOEXCEPT { @@ -66,7 +66,7 @@ constexpr bool script::is_relaxed_push_pattern(const operations& ops) NOEXCEPT // **************************************************************************** // CONSENSUS: this pattern is used to commit to bip141 witness data. // **************************************************************************** -constexpr bool script::is_commitment_pattern(const operations& ops) NOEXCEPT +VCONSTEXPR bool script::is_commitment_pattern(const operations& ops) NOEXCEPT { constexpr auto header = to_big_endian(chain::witness_head); @@ -83,7 +83,7 @@ constexpr bool script::is_commitment_pattern(const operations& ops) NOEXCEPT // **************************************************************************** // CONSENSUS: this pattern is used in bip141 validation rules. // **************************************************************************** -constexpr bool script::is_witness_program_pattern( +VCONSTEXPR bool script::is_witness_program_pattern( const operations& ops) NOEXCEPT { return ops.size() == 2 @@ -93,7 +93,7 @@ constexpr bool script::is_witness_program_pattern( } // The satoshi client now enables configurable data size for policy. -constexpr bool script::is_pay_null_data_pattern(const operations& ops) NOEXCEPT +VCONSTEXPR bool script::is_pay_null_data_pattern(const operations& ops) NOEXCEPT { return ops.size() == 2 && ops[0].code() == opcode::op_return @@ -102,7 +102,7 @@ constexpr bool script::is_pay_null_data_pattern(const operations& ops) NOEXCEPT } // Used by neutrino. -constexpr bool script::is_pay_op_return_pattern(const operations& ops) NOEXCEPT +VCONSTEXPR bool script::is_pay_op_return_pattern(const operations& ops) NOEXCEPT { return !ops.empty() && ops[0].code() == opcode::op_return; @@ -111,7 +111,7 @@ constexpr bool script::is_pay_op_return_pattern(const operations& ops) NOEXCEPT // The current 16 (or 20) limit does not affect server indexing because bare // multisig is not indexable and p2sh multisig is byte-limited to 15 sigs. // The satoshi client policy limit is 3 signatures for bare multisig. -constexpr bool script::is_pay_multisig_pattern(const operations& ops) NOEXCEPT +VCONSTEXPR bool script::is_pay_multisig_pattern(const operations& ops) NOEXCEPT { constexpr auto op_1 = static_cast(opcode::push_positive_1); constexpr auto op_16 = static_cast(opcode::push_positive_16); @@ -144,7 +144,7 @@ constexpr bool script::is_pay_multisig_pattern(const operations& ops) NOEXCEPT } // The satoshi client considers this non-standard for policy. -constexpr bool script::is_pay_public_key_pattern( +VCONSTEXPR bool script::is_pay_public_key_pattern( const operations& ops) NOEXCEPT { return ops.size() == 2 @@ -152,7 +152,7 @@ constexpr bool script::is_pay_public_key_pattern( && ops[1].code() == opcode::checksig; } -constexpr bool script::is_pay_key_hash_pattern(const operations& ops) NOEXCEPT +VCONSTEXPR bool script::is_pay_key_hash_pattern(const operations& ops) NOEXCEPT { return ops.size() == 5 && ops[0].code() == opcode::dup @@ -165,7 +165,7 @@ constexpr bool script::is_pay_key_hash_pattern(const operations& ops) NOEXCEPT // **************************************************************************** // CONSENSUS: this pattern is used to activate bip16 validation rules. // **************************************************************************** -constexpr bool script::is_pay_script_hash_pattern( +VCONSTEXPR bool script::is_pay_script_hash_pattern( const operations& ops) NOEXCEPT { return ops.size() == 3 @@ -174,14 +174,14 @@ constexpr bool script::is_pay_script_hash_pattern( && ops[2].code() == opcode::equal; } -constexpr bool script::is_pay_witness_pattern(const operations& ops) NOEXCEPT +VCONSTEXPR bool script::is_pay_witness_pattern(const operations& ops) NOEXCEPT { return ops.size() == 2 && ops[0].is_version() && ops[1].is_push(); } -constexpr bool script::is_pay_witness_key_hash_pattern( +VCONSTEXPR bool script::is_pay_witness_key_hash_pattern( const operations& ops) NOEXCEPT { return ops.size() == 2 @@ -192,7 +192,7 @@ constexpr bool script::is_pay_witness_key_hash_pattern( // **************************************************************************** // CONSENSUS: this pattern is used to activate bip141 validation rules. // **************************************************************************** -constexpr bool script::is_pay_witness_script_hash_pattern( +VCONSTEXPR bool script::is_pay_witness_script_hash_pattern( const operations& ops) NOEXCEPT { return ops.size() == 2 @@ -203,7 +203,7 @@ constexpr bool script::is_pay_witness_script_hash_pattern( // The first push is based on wacky satoshi op_check_multisig behavior that // we must perpetuate, though it's appearance here is policy not consensus. // Limiting to push_size_0 removes pattern ambiguity with little downside. -constexpr bool script::is_sign_multisig_pattern(const operations& ops) NOEXCEPT +VCONSTEXPR bool script::is_sign_multisig_pattern(const operations& ops) NOEXCEPT { const auto endorsement = [](const operation& op) NOEXCEPT { @@ -215,7 +215,7 @@ constexpr bool script::is_sign_multisig_pattern(const operations& ops) NOEXCEPT && std::all_of(std::next(ops.begin()), ops.end(), endorsement); } -constexpr bool script::is_sign_public_key_pattern( +VCONSTEXPR bool script::is_sign_public_key_pattern( const operations& ops) NOEXCEPT { return ops.size() == 1 @@ -225,7 +225,7 @@ constexpr bool script::is_sign_public_key_pattern( // **************************************************************************** // CONSENSUS: this pattern is used to activate bip141 validation rules. // **************************************************************************** -constexpr bool script::is_sign_key_hash_pattern(const operations& ops) NOEXCEPT +VCONSTEXPR bool script::is_sign_key_hash_pattern(const operations& ops) NOEXCEPT { return ops.size() == 2 && is_endorsement(ops[0].data()) @@ -235,7 +235,7 @@ constexpr bool script::is_sign_key_hash_pattern(const operations& ops) NOEXCEPT // Ambiguous with is_sign_key_hash when second/last op is a public key. // Ambiguous with is_sign_public_key_pattern when only op is endorsement. -constexpr bool script::is_sign_script_hash_pattern( +VCONSTEXPR bool script::is_sign_script_hash_pattern( const operations& ops) NOEXCEPT { return !ops.empty()