diff --git a/Makefile.am b/Makefile.am index 3685234d22..37c66c2723 100755 --- a/Makefile.am +++ b/Makefile.am @@ -46,6 +46,7 @@ src_libbitcoin_system_la_SOURCES = \ src/chain/header.cpp \ src/chain/input.cpp \ src/chain/operation.cpp \ + src/chain/outpoint.cpp \ src/chain/output.cpp \ src/chain/point.cpp \ src/chain/script.cpp \ @@ -234,6 +235,7 @@ test_libbitcoin_system_test_SOURCES = \ test/chain/header.cpp \ test/chain/input.cpp \ test/chain/operation.cpp \ + test/chain/outpoint.cpp \ test/chain/output.cpp \ test/chain/point.cpp \ test/chain/satoshi_words.cpp \ @@ -482,6 +484,7 @@ include_bitcoin_system_chain_HEADERS = \ include/bitcoin/system/chain/header.hpp \ include/bitcoin/system/chain/input.hpp \ include/bitcoin/system/chain/operation.hpp \ + include/bitcoin/system/chain/outpoint.hpp \ include/bitcoin/system/chain/output.hpp \ include/bitcoin/system/chain/point.hpp \ include/bitcoin/system/chain/prevout.hpp \ diff --git a/builds/cmake/CMakeLists.txt b/builds/cmake/CMakeLists.txt index 19b988f59f..5c4ddbe36a 100644 --- a/builds/cmake/CMakeLists.txt +++ b/builds/cmake/CMakeLists.txt @@ -479,6 +479,7 @@ add_library( ${CANONICAL_LIB_NAME} "../../src/chain/header.cpp" "../../src/chain/input.cpp" "../../src/chain/operation.cpp" + "../../src/chain/outpoint.cpp" "../../src/chain/output.cpp" "../../src/chain/point.cpp" "../../src/chain/script.cpp" @@ -713,6 +714,7 @@ if (with-tests) "../../test/chain/header.cpp" "../../test/chain/input.cpp" "../../test/chain/operation.cpp" + "../../test/chain/outpoint.cpp" "../../test/chain/output.cpp" "../../test/chain/point.cpp" "../../test/chain/satoshi_words.cpp" diff --git a/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj b/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj index ede84f0802..bf96c9857f 100644 --- a/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj @@ -135,6 +135,7 @@ + diff --git a/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj.filters index f28e674d60..43f0d7bbaf 100644 --- a/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-system-test/libbitcoin-system-test.vcxproj.filters @@ -147,6 +147,9 @@ src\chain + + src\chain + src\chain diff --git a/builds/msvc/vs2022/libbitcoin-system/libbitcoin-system.vcxproj b/builds/msvc/vs2022/libbitcoin-system/libbitcoin-system.vcxproj index 627ebdde83..29d3fa8ec5 100644 --- a/builds/msvc/vs2022/libbitcoin-system/libbitcoin-system.vcxproj +++ b/builds/msvc/vs2022/libbitcoin-system/libbitcoin-system.vcxproj @@ -138,6 +138,7 @@ $(IntDir)src_chain_input.obj + $(IntDir)src_chain_output.obj @@ -323,6 +324,7 @@ + diff --git a/builds/msvc/vs2022/libbitcoin-system/libbitcoin-system.vcxproj.filters b/builds/msvc/vs2022/libbitcoin-system/libbitcoin-system.vcxproj.filters index 0758b0430e..05e1e4e235 100644 --- a/builds/msvc/vs2022/libbitcoin-system/libbitcoin-system.vcxproj.filters +++ b/builds/msvc/vs2022/libbitcoin-system/libbitcoin-system.vcxproj.filters @@ -276,6 +276,9 @@ src\chain + + src\chain + src\chain @@ -719,6 +722,9 @@ include\bitcoin\system\chain + + include\bitcoin\system\chain + include\bitcoin\system\chain diff --git a/include/bitcoin/system.hpp b/include/bitcoin/system.hpp index 77254596e3..3a9f0209fd 100755 --- a/include/bitcoin/system.hpp +++ b/include/bitcoin/system.hpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include diff --git a/include/bitcoin/system/chain/chain.hpp b/include/bitcoin/system/chain/chain.hpp index 45efecb2fd..8b8da93f8a 100644 --- a/include/bitcoin/system/chain/chain.hpp +++ b/include/bitcoin/system/chain/chain.hpp @@ -41,6 +41,7 @@ #include #include #include +#include #include #include #include diff --git a/include/bitcoin/system/chain/outpoint.hpp b/include/bitcoin/system/chain/outpoint.hpp new file mode 100644 index 0000000000..82f5fd7e04 --- /dev/null +++ b/include/bitcoin/system/chain/outpoint.hpp @@ -0,0 +1,123 @@ +/** + * Copyright (c) 2011-2025 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_OUTPOINT_HPP +#define LIBBITCOIN_SYSTEM_CHAIN_OUTPOINT_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace libbitcoin { +namespace system { +namespace chain { + +class BC_API outpoint +{ +public: + DEFAULT_COPY_MOVE_DESTRUCT(outpoint); + + typedef std::shared_ptr cptr; + + static constexpr size_t serialized_size() NOEXCEPT + { + return point::serialized_size() + sizeof(uint32_t); + } + + /// Constructors. + /// ----------------------------------------------------------------------- + + /// Default outpoint is an invalid null outpoint (null_hash/null_index/0). + outpoint() NOEXCEPT; + + outpoint(chain::point&& point, uint64_t value) NOEXCEPT; + outpoint(const chain::point& point, uint64_t value) NOEXCEPT; + + outpoint(const data_slice& data) NOEXCEPT; + outpoint(stream::in::fast& stream) NOEXCEPT; + outpoint(std::istream& stream) NOEXCEPT; + outpoint(reader& source) NOEXCEPT; + + /// Operators. + /// ----------------------------------------------------------------------- + + bool operator==(const outpoint& other) const NOEXCEPT; + bool operator!=(const outpoint& other) const NOEXCEPT; + + /// Serialization. + /// ----------------------------------------------------------------------- + + data_chunk to_data() const NOEXCEPT; + void to_data(std::ostream& stream) const NOEXCEPT; + void to_data(writer& sink) const NOEXCEPT; + + /// Properties. + /// ----------------------------------------------------------------------- + + /// Native properties. + bool is_valid() const NOEXCEPT; + const chain::point& point() const NOEXCEPT; + uint64_t value() const NOEXCEPT; + + /// Computed properties. + bool is_null() const NOEXCEPT; + +protected: + outpoint(stream::in::fast&& stream) NOEXCEPT; + outpoint(reader&& source) NOEXCEPT; + outpoint(chain::point&& point, uint64_t value, bool valid) NOEXCEPT; + outpoint(const chain::point& point, uint64_t value, bool valid) NOEXCEPT; + +private: + chain::point point_; + uint64_t value_; + + // Cache. + bool valid_; +}; + +/// Arbitrary compare, for uniqueness sorting. +bool operator<(const outpoint& left, const outpoint& right) NOEXCEPT; + +typedef std_vector outpoints; + +DECLARE_JSON_TAG_INVOKE(outpoint); +DECLARE_JSON_TAG_INVOKE(outpoint::cptr); + +} // namespace chain +} // namespace system +} // namespace libbitcoin + +namespace std +{ +template<> +struct hash +{ + size_t operator()(const bc::system::chain::outpoint& value) const NOEXCEPT + { + // Value does not contribute to identity. + return std::hash{}(value.point()); + } +}; +} // namespace std + +#endif diff --git a/include/bitcoin/system/chain/output.hpp b/include/bitcoin/system/chain/output.hpp index be3dda317b..30253ea098 100644 --- a/include/bitcoin/system/chain/output.hpp +++ b/include/bitcoin/system/chain/output.hpp @@ -20,6 +20,7 @@ #define LIBBITCOIN_SYSTEM_CHAIN_OUTPUT_HPP #include +#include #include #include #include diff --git a/src/chain/outpoint.cpp b/src/chain/outpoint.cpp new file mode 100644 index 0000000000..35ba3373a6 --- /dev/null +++ b/src/chain/outpoint.cpp @@ -0,0 +1,198 @@ +/** + * Copyright (c) 2011-2025 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 . + */ +#include + +#include +#include +#include +#include + +namespace libbitcoin { +namespace system { +namespace chain { + +// Constructors. +// ---------------------------------------------------------------------------- + +// Invalid default used in signature hashing. +outpoint::outpoint() NOEXCEPT + : outpoint({}, zero, false) +{ +} + +outpoint::outpoint(chain::point&& point, uint64_t value) NOEXCEPT + : outpoint(std::move(point), value, true) +{ +} + +outpoint::outpoint(const chain::point& point, uint64_t value) NOEXCEPT + : outpoint(point, value, true) +{ +} + +outpoint::outpoint(const data_slice& data) NOEXCEPT + : outpoint(stream::in::fast(data)) +{ +} + +// protected +outpoint::outpoint(stream::in::fast&& stream) NOEXCEPT + : outpoint(read::bytes::fast(stream)) +{ +} + +outpoint::outpoint(stream::in::fast& stream) NOEXCEPT + : outpoint(read::bytes::fast(stream)) +{ +} +outpoint::outpoint(std::istream& stream) NOEXCEPT + : outpoint(read::bytes::istream(stream)) +{ +} + +// protected +outpoint::outpoint(reader&& source) NOEXCEPT + : outpoint(source) +{ +} + +// outpoint is not part of block, so doesn't use custom allocator. +outpoint::outpoint(reader& source) NOEXCEPT + : point_{ source }, + value_{ source.read_8_bytes_little_endian() }, + valid_{ source } +{ +} + +// protected +outpoint::outpoint(chain::point&& point, uint64_t value, bool valid) NOEXCEPT + : point_(std::move(point)), value_(value), valid_(valid) +{ +} + +// protected +outpoint::outpoint(const chain::point& point, uint64_t value, + bool valid) NOEXCEPT + : point_(point), value_(value), valid_(valid) +{ +} + +// Operators. +// ---------------------------------------------------------------------------- +// Value does not contribute to identity. + +bool outpoint::operator==(const outpoint& other) const NOEXCEPT +{ + return point() == other.point(); +} + +bool outpoint::operator!=(const outpoint& other) const NOEXCEPT +{ + return !(*this == other); +} + +bool operator<(const outpoint& left, const outpoint& right) NOEXCEPT +{ + return left.point() < right.point(); +} + +// Serialization. +// ---------------------------------------------------------------------------- + +data_chunk outpoint::to_data() const NOEXCEPT +{ + data_chunk data(serialized_size()); + stream::out::fast ostream(data); + write::bytes::fast out(ostream); + to_data(out); + return data; +} + +void outpoint::to_data(std::ostream& stream) const NOEXCEPT +{ + write::bytes::ostream out(stream); + to_data(out); +} + +void outpoint::to_data(writer& sink) const NOEXCEPT +{ + point_.to_data(sink); + sink.write_8_bytes_little_endian(value_); +} + +// Properties. +// ---------------------------------------------------------------------------- + +bool outpoint::is_valid() const NOEXCEPT +{ + return valid_; +} + +const chain::point& outpoint::point() const NOEXCEPT +{ + return point_; +} + +uint64_t outpoint::value() const NOEXCEPT +{ + return value_; +} + +// Validation. +// ---------------------------------------------------------------------------- + +bool outpoint::is_null() const NOEXCEPT +{ + return point_.is_null(); +} + +// JSON value convertors. +// ---------------------------------------------------------------------------- + +DEFINE_JSON_TO_TAG(outpoint) +{ + return + { + value_to(value.at("point")), + value.at("value").to_number() + }; +} + +DEFINE_JSON_FROM_TAG(outpoint) +{ + value = + { + { "point", value_from(instance.point()) }, + { "value", instance.value() } + }; +} + +DEFINE_JSON_TO_TAG(outpoint::cptr) +{ + return to_shared(tag_invoke(to_tag{}, value)); +} + +DEFINE_JSON_FROM_TAG(outpoint::cptr) +{ + tag_invoke(from_tag{}, value, *instance); +} + +} // namespace chain +} // namespace system +} // namespace libbitcoin diff --git a/src/chain/output.cpp b/src/chain/output.cpp index 6a3aa71fb5..72400d075e 100644 --- a/src/chain/output.cpp +++ b/src/chain/output.cpp @@ -264,7 +264,7 @@ DEFINE_JSON_FROM_TAG(output) value = { { "value", instance.value() }, - { "script", value_from(instance.script()) }, + { "script", value_from(instance.script()) } }; } diff --git a/test/chain/outpoint.cpp b/test/chain/outpoint.cpp new file mode 100644 index 0000000000..2831e86c14 --- /dev/null +++ b/test/chain/outpoint.cpp @@ -0,0 +1,217 @@ +/** + * Copyright (c) 2011-2025 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 . + */ +#include "../test.hpp" + +BOOST_AUTO_TEST_SUITE(outpoint_tests) + +namespace json = boost::json; +using namespace system::chain; + +static const auto outpoint_hash = base16_hash( + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f"); + +static const auto outpoint_data = base16_chunk( + "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f000000150000000000000042"); + +static const outpoint expected_outpoint{ outpoint_data }; + +// constructors +// ---------------------------------------------------------------------------- +// tests construction, native properties, is_valid + +BOOST_AUTO_TEST_CASE(output__constructor__default__invalid) +{ + const outpoint instance; + BOOST_REQUIRE(!instance.is_valid()); +} + +BOOST_AUTO_TEST_CASE(output__constructor__move__valid) +{ + auto copy = expected_outpoint; + const outpoint instance{ std::move(copy) }; + BOOST_REQUIRE(instance.is_valid()); + BOOST_REQUIRE(instance == expected_outpoint); +} + +BOOST_AUTO_TEST_CASE(output__constructor__copy__valid) +{ + const outpoint instance{ expected_outpoint }; + BOOST_REQUIRE(instance.is_valid()); + BOOST_REQUIRE(instance == expected_outpoint); +} + +BOOST_AUTO_TEST_CASE(output__constructor__move_parameters__valid) +{ + constexpr uint32_t value{ 42 }; + constexpr uint32_t index{ 1234 }; + outpoint instance{ { outpoint_hash, index }, value }; + BOOST_REQUIRE(instance.is_valid()); + BOOST_REQUIRE_EQUAL(instance.point().hash(), outpoint_hash); + BOOST_REQUIRE_EQUAL(instance.point().index(), index); + BOOST_REQUIRE_EQUAL(instance.value(), value); +} + +BOOST_AUTO_TEST_CASE(output__constructor__copy_parameters__valid) +{ + constexpr uint32_t value{ 42 }; + constexpr uint32_t index{ 1234 }; + point copy{ outpoint_hash, index }; + outpoint instance{ std::move(copy), value }; + BOOST_REQUIRE(instance.is_valid()); + BOOST_REQUIRE_EQUAL(instance.point().hash(), outpoint_hash); + BOOST_REQUIRE_EQUAL(instance.point().index(), index); + BOOST_REQUIRE_EQUAL(instance.value(), value); +} + +// operators +// ---------------------------------------------------------------------------- + +BOOST_AUTO_TEST_CASE(outpoint__assign__copy__expected) +{ + const auto& alpha = expected_outpoint; + auto gamma = alpha; + const auto beta = std::move(gamma); + BOOST_REQUIRE(alpha == beta); +} + +BOOST_AUTO_TEST_CASE(outpoint__assign__move__expected) +{ + const auto& alpha = expected_outpoint; + const auto beta = alpha; + BOOST_REQUIRE(alpha == beta); +} + +BOOST_AUTO_TEST_CASE(outpoint__equality__same__true) +{ + const auto& alpha = expected_outpoint; + const outpoint beta(alpha); + BOOST_REQUIRE(alpha == beta); +} + +BOOST_AUTO_TEST_CASE(outpoint__equality__different__false) +{ + const auto& alpha = expected_outpoint; + const outpoint beta; + BOOST_REQUIRE(!(alpha == beta)); +} + +BOOST_AUTO_TEST_CASE(outpoint__inequality__same__false) +{ + const auto& alpha = expected_outpoint; + const outpoint beta(alpha); + BOOST_REQUIRE(!(alpha != beta)); +} + +BOOST_AUTO_TEST_CASE(outpoint__inequality__different__true) +{ + const auto& alpha = expected_outpoint; + const outpoint beta; + BOOST_REQUIRE(alpha != beta); +} + +// to_data +// ---------------------------------------------------------------------------- + +BOOST_AUTO_TEST_CASE(outpoint__to_data__data__expected) +{ + const auto size = expected_outpoint.to_data().size(); + BOOST_REQUIRE_EQUAL(size, expected_outpoint.serialized_size()); +} + +BOOST_AUTO_TEST_CASE(outpoint__to_data__stream__expected) +{ + // Write outpoint to stream. + std::stringstream iostream; + expected_outpoint.to_data(iostream); + BOOST_REQUIRE(iostream); + + // Verify stream contents. + const outpoint copy(iostream); + BOOST_REQUIRE(iostream); + BOOST_REQUIRE(copy.is_valid()); + BOOST_REQUIRE(copy == expected_outpoint); +} + +BOOST_AUTO_TEST_CASE(outpoint__to_data__writer__expected) +{ + // Write outpoint to stream. + std::stringstream iostream; + write::bytes::ostream out(iostream); + expected_outpoint.to_data(out); + BOOST_REQUIRE(iostream); + + // Verify stream contents. + const outpoint copy(iostream); + BOOST_REQUIRE(iostream); + BOOST_REQUIRE(copy.is_valid()); + BOOST_REQUIRE(copy == expected_outpoint); +} + +// properties +// ---------------------------------------------------------------------------- + +BOOST_AUTO_TEST_CASE(outpoint__is_null__not_null__false) +{ + BOOST_REQUIRE(!expected_outpoint.is_null()); +} + +BOOST_AUTO_TEST_CASE(outpoint__is_null__default_null__true) +{ + BOOST_REQUIRE(outpoint{}.is_null()); +} + +BOOST_AUTO_TEST_CASE(outpoint__serialized_size__always__expected) +{ + static_assert(outpoint::serialized_size() == point::serialized_size() + sizeof(uint32_t)); + BOOST_REQUIRE_EQUAL(outpoint::serialized_size(), point::serialized_size() + sizeof(uint32_t)); +} + +// json +// ---------------------------------------------------------------------------- + +BOOST_AUTO_TEST_CASE(outpoint__json__conversions__expected) +{ + const std::string text + { + "{" + "\"point\":" + "{" + "\"hash\":\"0000000000000000000000000000000000000000000000000000000000000001\"," + "\"index\":42" + "}," + "\"value\":24" + "}" + }; + + const chain::outpoint instance + { + chain::point{ one_hash, 42 }, + 24 + }; + + const auto value = json::value_from(instance); + + BOOST_REQUIRE_EQUAL(json::serialize(value), text); + BOOST_REQUIRE(json::parse(text) == value); + + BOOST_REQUIRE(json::value_from(instance) == value); + BOOST_REQUIRE(json::value_to(value) == instance); +} + +BOOST_AUTO_TEST_SUITE_END()