From f419c18056889c857dbfa1f9a97c8ca4779ffa7d Mon Sep 17 00:00:00 2001 From: Elliot Lee Date: Mon, 25 Nov 2024 12:12:35 -0800 Subject: [PATCH 1/3] Add a new serialized type: STNumber (#5121) `STNumber` lets objects and transactions contain multiple fields for quantities of XRP, IOU, or MPT without duplicating information about the "issue" (represented by `STIssue`). It is a straightforward serialization of the `Number` type that uniformly represents those quantities. --------- Co-authored-by: John Freeman Co-authored-by: Howard Hinnant --- include/xrpl/basics/Number.h | 16 ++-- include/xrpl/protocol/SField.h | 5 +- include/xrpl/protocol/STNumber.h | 88 +++++++++++++++++ include/xrpl/protocol/STObject.h | 4 + include/xrpl/protocol/Serializer.h | 39 +++++++- include/xrpl/protocol/detail/sfields.macro | 3 + include/xrpl/protocol/st.h | 1 + src/libxrpl/protocol/STNumber.cpp | 105 +++++++++++++++++++++ src/libxrpl/protocol/STObject.cpp | 14 +++ src/libxrpl/protocol/STVar.cpp | 1 + src/libxrpl/protocol/Serializer.cpp | 51 +++++----- src/test/protocol/STNumber_test.cpp | 93 ++++++++++++++++++ src/test/protocol/Serializer_test.cpp | 69 ++++++++++++++ src/xrpld/app/tx/detail/Payment.cpp | 18 ++-- src/xrpld/ledger/detail/CachedView.cpp | 21 ++--- 15 files changed, 470 insertions(+), 58 deletions(-) create mode 100644 include/xrpl/protocol/STNumber.h create mode 100644 src/libxrpl/protocol/STNumber.cpp create mode 100644 src/test/protocol/STNumber_test.cpp create mode 100644 src/test/protocol/Serializer_test.cpp diff --git a/include/xrpl/basics/Number.h b/include/xrpl/basics/Number.h index 01b3adb22d4..70baf5d1e47 100644 --- a/include/xrpl/basics/Number.h +++ b/include/xrpl/basics/Number.h @@ -41,6 +41,14 @@ class Number int exponent_{std::numeric_limits::lowest()}; public: + // The range for the mantissa when normalized + constexpr static std::int64_t minMantissa = 1'000'000'000'000'000LL; + constexpr static std::int64_t maxMantissa = 9'999'999'999'999'999LL; + + // The range for the exponent when normalized + constexpr static int minExponent = -32768; + constexpr static int maxExponent = 32768; + struct unchecked { explicit unchecked() = default; @@ -191,14 +199,6 @@ class Number constexpr bool isnormal() const noexcept; - // The range for the mantissa when normalized - constexpr static std::int64_t minMantissa = 1'000'000'000'000'000LL; - constexpr static std::int64_t maxMantissa = 9'999'999'999'999'999LL; - - // The range for the exponent when normalized - constexpr static int minExponent = -32768; - constexpr static int maxExponent = 32768; - class Guard; }; diff --git a/include/xrpl/protocol/SField.h b/include/xrpl/protocol/SField.h index 942f2a8654b..01909b19862 100644 --- a/include/xrpl/protocol/SField.h +++ b/include/xrpl/protocol/SField.h @@ -49,6 +49,7 @@ template class STBitString; template class STInteger; +class STNumber; class STXChainBridge; class STVector256; class STCurrency; @@ -70,8 +71,9 @@ class STCurrency; STYPE(STI_AMOUNT, 6) \ STYPE(STI_VL, 7) \ STYPE(STI_ACCOUNT, 8) \ + STYPE(STI_NUMBER, 9) \ \ - /* 9-13 are reserved */ \ + /* 10-13 are reserved */ \ STYPE(STI_OBJECT, 14) \ STYPE(STI_ARRAY, 15) \ \ @@ -355,6 +357,7 @@ using SF_ACCOUNT = TypedField; using SF_AMOUNT = TypedField; using SF_ISSUE = TypedField; using SF_CURRENCY = TypedField; +using SF_NUMBER = TypedField; using SF_VL = TypedField; using SF_VECTOR256 = TypedField; using SF_XCHAIN_BRIDGE = TypedField; diff --git a/include/xrpl/protocol/STNumber.h b/include/xrpl/protocol/STNumber.h new file mode 100644 index 00000000000..c0fce572c8c --- /dev/null +++ b/include/xrpl/protocol/STNumber.h @@ -0,0 +1,88 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef XRPL_PROTOCOL_STNUMBER_H_INCLUDED +#define XRPL_PROTOCOL_STNUMBER_H_INCLUDED + +#include +#include +#include + +#include + +namespace ripple { + +/** + * A serializable number. + * + * This type is-a `Number`, and can be used everywhere that is accepted. + * This type simply integrates `Number` with the serialization framework, + * letting it be used for fields in ledger entries and transactions. + * It is effectively an `STAmount` sans `Asset`: + * it can represent a value of any token type (XRP, IOU, or MPT) + * without paying the storage cost of duplicating asset information + * that may be deduced from the context. + */ +class STNumber : public STBase, public CountedObject +{ +private: + Number value_; + +public: + using value_type = Number; + + STNumber() = default; + explicit STNumber(SField const& field, Number const& value = Number()); + STNumber(SerialIter& sit, SField const& field); + + SerializedTypeID + getSType() const override; + std::string + getText() const override; + void + add(Serializer& s) const override; + + Number const& + value() const; + void + setValue(Number const& v); + + bool + isEquivalent(STBase const& t) const override; + bool + isDefault() const override; + + operator Number() const + { + return value_; + } + +private: + STBase* + copy(std::size_t n, void* buf) const override; + STBase* + move(std::size_t n, void* buf) override; +}; + +std::ostream& +operator<<(std::ostream& out, STNumber const& rhs); + +} // namespace ripple + +#endif diff --git a/include/xrpl/protocol/STObject.h b/include/xrpl/protocol/STObject.h index c06f109dc56..748a2b5d685 100644 --- a/include/xrpl/protocol/STObject.h +++ b/include/xrpl/protocol/STObject.h @@ -245,6 +245,8 @@ class STObject : public STBase, public CountedObject getFieldArray(SField const& field) const; const STCurrency& getFieldCurrency(SField const& field) const; + STNumber const& + getFieldNumber(SField const& field) const; /** Get the value of a field. @param A TypedField built from an SField value representing the desired @@ -376,6 +378,8 @@ class STObject : public STBase, public CountedObject void setFieldCurrency(SField const& field, STCurrency const&); void + setFieldNumber(SField const& field, STNumber const&); + void setFieldPathSet(SField const& field, STPathSet const&); void setFieldV256(SField const& field, STVector256 const& v); diff --git a/include/xrpl/protocol/Serializer.h b/include/xrpl/protocol/Serializer.h index d8d0b9222e3..0e96078ed14 100644 --- a/include/xrpl/protocol/Serializer.h +++ b/include/xrpl/protocol/Serializer.h @@ -83,12 +83,43 @@ class Serializer add8(unsigned char i); int add16(std::uint16_t i); + + template + requires(std::is_same_v< + std::make_unsigned_t>, + std::uint32_t>) int - add32(std::uint32_t i); // ledger indexes, account sequence, timestamps + add32(T i) + { + int ret = mData.size(); + mData.push_back(static_cast((i >> 24) & 0xff)); + mData.push_back(static_cast((i >> 16) & 0xff)); + mData.push_back(static_cast((i >> 8) & 0xff)); + mData.push_back(static_cast(i & 0xff)); + return ret; + } + int add32(HashPrefix p); + + template + requires(std::is_same_v< + std::make_unsigned_t>, + std::uint64_t>) int - add64(std::uint64_t i); // native currency amounts + add64(T i) + { + int ret = mData.size(); + mData.push_back(static_cast((i >> 56) & 0xff)); + mData.push_back(static_cast((i >> 48) & 0xff)); + mData.push_back(static_cast((i >> 40) & 0xff)); + mData.push_back(static_cast((i >> 32) & 0xff)); + mData.push_back(static_cast((i >> 24) & 0xff)); + mData.push_back(static_cast((i >> 16) & 0xff)); + mData.push_back(static_cast((i >> 8) & 0xff)); + mData.push_back(static_cast(i & 0xff)); + return ret; + } template int addInteger(Integer); @@ -353,9 +384,13 @@ class SerialIter std::uint32_t get32(); + std::int32_t + geti32(); std::uint64_t get64(); + std::int64_t + geti64(); template base_uint diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index ccf6350cbfc..8384025ee3b 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -191,6 +191,9 @@ TYPED_SFIELD(sfHookHash, UINT256, 31) TYPED_SFIELD(sfHookNamespace, UINT256, 32) TYPED_SFIELD(sfHookSetTxnID, UINT256, 33) +// number (common) +TYPED_SFIELD(sfNumber, NUMBER, 1) + // currency amount (common) TYPED_SFIELD(sfAmount, AMOUNT, 1) TYPED_SFIELD(sfBalance, AMOUNT, 2) diff --git a/include/xrpl/protocol/st.h b/include/xrpl/protocol/st.h index 7b0e3a3b2df..0035deaa1bc 100644 --- a/include/xrpl/protocol/st.h +++ b/include/xrpl/protocol/st.h @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include diff --git a/src/libxrpl/protocol/STNumber.cpp b/src/libxrpl/protocol/STNumber.cpp new file mode 100644 index 00000000000..3a92bbb02f9 --- /dev/null +++ b/src/libxrpl/protocol/STNumber.cpp @@ -0,0 +1,105 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2023 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include + +#include + +namespace ripple { + +STNumber::STNumber(SField const& field, Number const& value) + : STBase(field), value_(value) +{ +} + +STNumber::STNumber(SerialIter& sit, SField const& field) : STBase(field) +{ + // We must call these methods in separate statements + // to guarantee their order of execution. + auto mantissa = sit.geti64(); + auto exponent = sit.geti32(); + value_ = Number{mantissa, exponent}; +} + +SerializedTypeID +STNumber::getSType() const +{ + return STI_NUMBER; +} + +std::string +STNumber::getText() const +{ + return to_string(value_); +} + +void +STNumber::add(Serializer& s) const +{ + assert(getFName().isBinary()); + assert(getFName().fieldType == getSType()); + s.add64(value_.mantissa()); + s.add32(value_.exponent()); +} + +Number const& +STNumber::value() const +{ + return value_; +} + +void +STNumber::setValue(Number const& v) +{ + value_ = v; +} + +STBase* +STNumber::copy(std::size_t n, void* buf) const +{ + return emplace(n, buf, *this); +} + +STBase* +STNumber::move(std::size_t n, void* buf) +{ + return emplace(n, buf, std::move(*this)); +} + +bool +STNumber::isEquivalent(STBase const& t) const +{ + assert(t.getSType() == this->getSType()); + STNumber const& v = dynamic_cast(t); + return value_ == v; +} + +bool +STNumber::isDefault() const +{ + return value_ == Number(); +} + +std::ostream& +operator<<(std::ostream& out, STNumber const& rhs) +{ + return out << rhs.getText(); +} + +} // namespace ripple diff --git a/src/libxrpl/protocol/STObject.cpp b/src/libxrpl/protocol/STObject.cpp index 7e62fc25bd6..c8fc88348e9 100644 --- a/src/libxrpl/protocol/STObject.cpp +++ b/src/libxrpl/protocol/STObject.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include namespace ripple { @@ -665,6 +666,13 @@ STObject::getFieldCurrency(SField const& field) const return getFieldByConstRef(field, empty); } +STNumber const& +STObject::getFieldNumber(SField const& field) const +{ + static STNumber const empty{}; + return getFieldByConstRef(field, empty); +} + void STObject::set(std::unique_ptr v) { @@ -765,6 +773,12 @@ STObject::setFieldIssue(SField const& field, STIssue const& v) setFieldUsingAssignment(field, v); } +void +STObject::setFieldNumber(SField const& field, STNumber const& v) +{ + setFieldUsingAssignment(field, v); +} + void STObject::setFieldPathSet(SField const& field, STPathSet const& v) { diff --git a/src/libxrpl/protocol/STVar.cpp b/src/libxrpl/protocol/STVar.cpp index f185595eadb..55927cb33aa 100644 --- a/src/libxrpl/protocol/STVar.cpp +++ b/src/libxrpl/protocol/STVar.cpp @@ -29,6 +29,7 @@ #include #include #include +#include #include #include #include diff --git a/src/libxrpl/protocol/Serializer.cpp b/src/libxrpl/protocol/Serializer.cpp index b99375f80dd..ceaf76faf34 100644 --- a/src/libxrpl/protocol/Serializer.cpp +++ b/src/libxrpl/protocol/Serializer.cpp @@ -21,6 +21,7 @@ #include #include #include +#include #include namespace ripple { @@ -34,17 +35,6 @@ Serializer::add16(std::uint16_t i) return ret; } -int -Serializer::add32(std::uint32_t i) -{ - int ret = mData.size(); - mData.push_back(static_cast(i >> 24)); - mData.push_back(static_cast((i >> 16) & 0xff)); - mData.push_back(static_cast((i >> 8) & 0xff)); - mData.push_back(static_cast(i & 0xff)); - return ret; -} - int Serializer::add32(HashPrefix p) { @@ -56,21 +46,6 @@ Serializer::add32(HashPrefix p) return add32(safe_cast(p)); } -int -Serializer::add64(std::uint64_t i) -{ - int ret = mData.size(); - mData.push_back(static_cast(i >> 56)); - mData.push_back(static_cast((i >> 48) & 0xff)); - mData.push_back(static_cast((i >> 40) & 0xff)); - mData.push_back(static_cast((i >> 32) & 0xff)); - mData.push_back(static_cast((i >> 24) & 0xff)); - mData.push_back(static_cast((i >> 16) & 0xff)); - mData.push_back(static_cast((i >> 8) & 0xff)); - mData.push_back(static_cast(i & 0xff)); - return ret; -} - template <> int Serializer::addInteger(unsigned char i) @@ -410,6 +385,30 @@ SerialIter::get64() (std::uint64_t(t[6]) << 8) + std::uint64_t(t[7]); } +std::int32_t +SerialIter::geti32() +{ + if (remain_ < 4) + Throw("invalid SerialIter geti32"); + auto t = p_; + p_ += 4; + used_ += 4; + remain_ -= 4; + return boost::endian::load_big_s32(t); +} + +std::int64_t +SerialIter::geti64() +{ + if (remain_ < 8) + Throw("invalid SerialIter geti64"); + auto t = p_; + p_ += 8; + used_ += 8; + remain_ -= 8; + return boost::endian::load_big_s64(t); +} + void SerialIter::getFieldID(int& type, int& name) { diff --git a/src/test/protocol/STNumber_test.cpp b/src/test/protocol/STNumber_test.cpp new file mode 100644 index 00000000000..ed255e32f1c --- /dev/null +++ b/src/test/protocol/STNumber_test.cpp @@ -0,0 +1,93 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include +#include +#include + +#include +#include + +namespace ripple { + +struct STNumber_test : public beast::unit_test::suite +{ + void + testCombo(Number number) + { + STNumber const before{sfNumber, number}; + BEAST_EXPECT(number == before); + Serializer s; + before.add(s); + BEAST_EXPECT(s.size() == 12); + SerialIter sit(s.slice()); + STNumber const after{sit, sfNumber}; + BEAST_EXPECT(after.isEquivalent(before)); + BEAST_EXPECT(number == after); + } + + void + run() override + { + static_assert(!std::is_convertible_v); + + { + STNumber const stnum{sfNumber}; + BEAST_EXPECT(stnum.getSType() == STI_NUMBER); + BEAST_EXPECT(stnum.getText() == "0"); + BEAST_EXPECT(stnum.isDefault() == true); + BEAST_EXPECT(stnum.value() == Number{0}); + } + + std::initializer_list const mantissas = { + std::numeric_limits::min(), + -1, + 0, + 1, + std::numeric_limits::max()}; + for (std::int64_t mantissa : mantissas) + testCombo(Number{mantissa}); + + std::initializer_list const exponents = { + Number::minExponent, -1, 0, 1, Number::maxExponent - 1}; + for (std::int32_t exponent : exponents) + testCombo(Number{123, exponent}); + + { + STAmount const strikePrice{noIssue(), 100}; + STNumber const factor{sfNumber, 100}; + auto const iouValue = strikePrice.iou(); + IOUAmount totalValue{iouValue * factor}; + STAmount const totalAmount{totalValue, strikePrice.issue()}; + BEAST_EXPECT(totalAmount == Number{10'000}); + } + } +}; + +BEAST_DEFINE_TESTSUITE(STNumber, protocol, ripple); + +void +testCompile(std::ostream& out) +{ + STNumber number{sfNumber, 42}; + out << number; +} + +} // namespace ripple diff --git a/src/test/protocol/Serializer_test.cpp b/src/test/protocol/Serializer_test.cpp new file mode 100644 index 00000000000..d707943856f --- /dev/null +++ b/src/test/protocol/Serializer_test.cpp @@ -0,0 +1,69 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2024 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#include +#include + +#include + +namespace ripple { + +struct Serializer_test : public beast::unit_test::suite +{ + void + run() override + { + { + std::initializer_list const values = { + std::numeric_limits::min(), + -1, + 0, + 1, + std::numeric_limits::max()}; + for (std::int32_t value : values) + { + Serializer s; + s.add32(value); + BEAST_EXPECT(s.size() == 4); + SerialIter sit(s.slice()); + BEAST_EXPECT(sit.geti32() == value); + } + } + { + std::initializer_list const values = { + std::numeric_limits::min(), + -1, + 0, + 1, + std::numeric_limits::max()}; + for (std::int64_t value : values) + { + Serializer s; + s.add64(value); + BEAST_EXPECT(s.size() == 8); + SerialIter sit(s.slice()); + BEAST_EXPECT(sit.geti64() == value); + } + } + } +}; + +BEAST_DEFINE_TESTSUITE(Serializer, protocol, ripple); + +} // namespace ripple diff --git a/src/xrpld/app/tx/detail/Payment.cpp b/src/xrpld/app/tx/detail/Payment.cpp index 2ea13ffabc8..2be784306cd 100644 --- a/src/xrpld/app/tx/detail/Payment.cpp +++ b/src/xrpld/app/tx/detail/Payment.cpp @@ -88,7 +88,7 @@ Payment::preflight(PreflightContext const& ctx) if (txFlags & paymentMask) { - JLOG(j.trace()) << "Malformed transaction: " << "Invalid flags set."; + JLOG(j.trace()) << "Malformed transaction: Invalid flags set."; return temINVALID_FLAG; } @@ -110,9 +110,9 @@ Payment::preflight(PreflightContext const& ctx) if ((mptDirect && dstAmount.asset() != maxSourceAmount.asset()) || (!mptDirect && maxSourceAmount.holds())) { - JLOG(j.trace()) << "Malformed transaction: " - << "inconsistent issues: " << dstAmount.getFullText() - << " " << maxSourceAmount.getFullText() << " " + JLOG(j.trace()) << "Malformed transaction: inconsistent issues: " + << dstAmount.getFullText() << " " + << maxSourceAmount.getFullText() << " " << deliverMin.value_or(STAmount{}).getFullText(); return temMALFORMED; } @@ -135,19 +135,19 @@ Payment::preflight(PreflightContext const& ctx) } if (hasMax && maxSourceAmount <= beast::zero) { - JLOG(j.trace()) << "Malformed transaction: " << "bad max amount: " + JLOG(j.trace()) << "Malformed transaction: bad max amount: " << maxSourceAmount.getFullText(); return temBAD_AMOUNT; } if (dstAmount <= beast::zero) { - JLOG(j.trace()) << "Malformed transaction: " - << "bad dst amount: " << dstAmount.getFullText(); + JLOG(j.trace()) << "Malformed transaction: bad dst amount: " + << dstAmount.getFullText(); return temBAD_AMOUNT; } if (badCurrency() == srcAsset || badCurrency() == dstAsset) { - JLOG(j.trace()) << "Malformed transaction: " << "Bad currency."; + JLOG(j.trace()) << "Malformed transaction: Bad currency."; return temBAD_CURRENCY; } if (account == dstAccountID && equalTokens(srcAsset, dstAsset) && !hasPaths) @@ -550,7 +550,7 @@ Payment::doApply() // Vote no. However the transaction might succeed, if applied in // a different order. JLOG(j_.trace()) << "Delay transaction: Insufficient funds: " - << " " << to_string(mPriorBalance) << " / " + << to_string(mPriorBalance) << " / " << to_string(dstAmount.xrp() + mmm) << " (" << to_string(reserve) << ")"; diff --git a/src/xrpld/ledger/detail/CachedView.cpp b/src/xrpld/ledger/detail/CachedView.cpp index 5502c40e6d5..645a2c79c13 100644 --- a/src/xrpld/ledger/detail/CachedView.cpp +++ b/src/xrpld/ledger/detail/CachedView.cpp @@ -63,20 +63,17 @@ CachedViewImpl::read(Keylet const& k) const hits.increment(); else misses.increment(); - std::lock_guard lock(mutex_); - auto const er = map_.emplace(k.key, *digest); - bool const inserted = er.second; - if (sle && !k.check(*sle)) + + if (!cacheHit) { - if (!inserted) - { - // On entry, this function did not find this key in map_. Now - // something (another thread?) has inserted the sle into the map and - // it has the wrong type. - LogicError("CachedView::read: wrong type"); - } - return nullptr; + // Avoid acquiring this lock unless necessary. It is only necessary if + // the key was not found in the map_. The lock is needed to add the key + // and digest. + std::lock_guard lock(mutex_); + map_.emplace(k.key, *digest); } + if (!sle || !k.check(*sle)) + return nullptr; return sle; } From b54d85d8620e622d567423a1a87dfe79ecdff71c Mon Sep 17 00:00:00 2001 From: Elliot Lee Date: Mon, 25 Nov 2024 11:40:11 -0800 Subject: [PATCH 2/3] refactor(AMMClawback): move tfClawTwoAssets check (#5201) Move tfClawTwoAssets check to preflight and return error temINVALID_FLAG --------- Co-authored-by: yinyiqian1 --- include/xrpl/protocol/detail/features.macro | 2 +- src/test/app/AMMClawback_test.cpp | 6 +++--- src/xrpld/app/tx/detail/AMMClawback.cpp | 21 +++++++++++---------- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 24c6e72ae34..31fc90cef80 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -32,9 +32,9 @@ XRPL_FEATURE(Credentials, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(AMMClawback, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (AMMv1_2, Supported::yes, VoteBehavior::DefaultNo) +XRPL_FEATURE(MPTokensV1, Supported::yes, VoteBehavior::DefaultNo) // InvariantsV1_1 will be changes to Supported::yes when all the // invariants expected to be included under it are complete. -XRPL_FEATURE(MPTokensV1, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(InvariantsV1_1, Supported::no, VoteBehavior::DefaultNo) XRPL_FIX (NFTokenPageLinks, Supported::yes, VoteBehavior::DefaultNo) XRPL_FIX (InnerObjTemplate2, Supported::yes, VoteBehavior::DefaultNo) diff --git a/src/test/app/AMMClawback_test.cpp b/src/test/app/AMMClawback_test.cpp index 705a1274073..c547a537bfb 100644 --- a/src/test/app/AMMClawback_test.cpp +++ b/src/test/app/AMMClawback_test.cpp @@ -303,13 +303,13 @@ class AMMClawback_test : public jtx::AMMTest // gw creates AMM pool of XRP/USD. AMM amm(env, gw, XRP(100), USD(100), ter(tesSUCCESS)); - // Return tecNO_PERMISSION because the issuer set tfClawTwoAssets, + // Return temINVALID_FLAG because the issuer set tfClawTwoAssets, // but the issuer only issues USD in the pool. The issuer is not // allowed to set tfClawTwoAssets flag if he did not issue both - // assts in the pool. + // assets in the pool. env(amm::ammClawback(gw, alice, USD, XRP, std::nullopt), txflags(tfClawTwoAssets), - ter(tecNO_PERMISSION)); + ter(temINVALID_FLAG)); } // Test clawing back XRP is being prohibited. diff --git a/src/xrpld/app/tx/detail/AMMClawback.cpp b/src/xrpld/app/tx/detail/AMMClawback.cpp index 64150ded6da..cd1e3008e97 100644 --- a/src/xrpld/app/tx/detail/AMMClawback.cpp +++ b/src/xrpld/app/tx/detail/AMMClawback.cpp @@ -42,7 +42,8 @@ AMMClawback::preflight(PreflightContext const& ctx) if (auto const ret = preflight1(ctx); !isTesSuccess(ret)) return ret; // LCOV_EXCL_LINE - if (ctx.tx.getFlags() & tfAMMClawbackMask) + auto const flags = ctx.tx.getFlags(); + if (flags & tfAMMClawbackMask) return temINVALID_FLAG; AccountID const issuer = ctx.tx[sfAccount]; @@ -57,10 +58,19 @@ AMMClawback::preflight(PreflightContext const& ctx) std::optional const clawAmount = ctx.tx[~sfAmount]; auto const asset = ctx.tx[sfAsset]; + auto const asset2 = ctx.tx[sfAsset2]; if (isXRP(asset)) return temMALFORMED; + if (flags & tfClawTwoAssets && asset.account != asset2.account) + { + JLOG(ctx.j.trace()) + << "AMMClawback: tfClawTwoAssets can only be enabled when two " + "assets in the AMM pool are both issued by the issuer"; + return temINVALID_FLAG; + } + if (asset.account != issuer) { JLOG(ctx.j.trace()) << "AMMClawback: Asset's account does not " @@ -108,15 +118,6 @@ AMMClawback::preclaim(PreclaimContext const& ctx) (issuerFlagsIn & lsfNoFreeze)) return tecNO_PERMISSION; - auto const flags = ctx.tx.getFlags(); - if (flags & tfClawTwoAssets && asset.account != asset2.account) - { - JLOG(ctx.j.trace()) - << "AMMClawback: tfClawTwoAssets can only be enabled when two " - "assets in the AMM pool are both issued by the issuer"; - return tecNO_PERMISSION; - } - return tesSUCCESS; } From f64cf9187affd69650907d0d92e097eb29693945 Mon Sep 17 00:00:00 2001 From: Elliot Lee Date: Mon, 25 Nov 2024 12:27:17 -0800 Subject: [PATCH 3/3] Set version to 2.3.0 --- RELEASENOTES.md | 73 ++++++++++++++++++++++++++++++ src/libxrpl/protocol/BuildInfo.cpp | 2 +- 2 files changed, 74 insertions(+), 1 deletion(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index c6a43f4aa7d..c6e8266e348 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -6,6 +6,79 @@ This document contains the release notes for `rippled`, the reference server imp Have new ideas? Need help with setting up your node? [Please open an issue here](https://github.com/xrplf/rippled/issues/new/choose). +# Version 2.3.0 + +Version 2.3.0 of `rippled`, the reference server implementation of the XRP Ledger protocol, is now available. This release includes 8 new amendments, including Multi-Purpose Tokens, Credentials, Clawback support for AMMs, and the ability to make offers as part of minting NFTs. Additionally, this release includes important fixes for stability, so server operators are encouraged to upgrade as soon as possible. + + +## Action Required + +If you run an XRP Ledger server, upgrade to version 2.3.0 as soon as possible to ensure service continuity. + +Additionally, new amendments are now open for voting according to the XRP Ledger's [amendment process](https://xrpl.org/amendments.html), which enables protocol changes following two weeks of >80% support from trusted validators. The exact time that protocol changes take effect depends on the voting decisions of the decentralized network. + +## Full Changelog + +### Amendments + +The following amendments are open for voting with this release: + +- **XLS-70 Credentials** - Users can issue Credentials on the ledger and use Credentials to pre-approve incoming payments when using Deposit Authorization instead of individually approving payers. ([#5103](https://github.com/XRPLF/rippled/pull/5103)) + - related fix: #5189 (https://github.com/XRPLF/rippled/pull/5189) +- **XLS-33 Multi-Purpose Tokens** - A new type of fungible token optimized for institutional DeFi including stablecoins. ([#5143](https://github.com/XRPLF/rippled/pull/5143)) +- **XLS-37 AMM Clawback** - Allows clawback-enabled tokens to be used in AMMs, with appropriate guardrails. ([#5142](https://github.com/XRPLF/rippled/pull/5142)) +- **XLS-52 NFTokenMintOffer** - Allows creating an NFT sell offer as part of minting a new NFT. ([#4845](https://github.com/XRPLF/rippled/pull/4845)) +- **fixAMMv1_2** - Fixes two bugs in Automated Market Maker (AMM) transaction processing. ([#5176](https://github.com/XRPLF/rippled/pull/5176)) +- **fixNFTokenPageLinks** - Fixes a bug that can cause NFT directories to have missing links, and introduces a transaction to repair corrupted ledger state. ([#4945](https://github.com/XRPLF/rippled/pull/4945)) +- **fixEnforceNFTokenTrustline** - Fixes two bugs in the interaction between NFT offers and trust lines. ([#4946](https://github.com/XRPLF/rippled/pull/4946)) +- **fixInnerObjTemplate2** - Standardizes the way inner objects are enforced across all transaction and ledger data. ([#5047](https://github.com/XRPLF/rippled/pull/5047)) + +The following amendment is partially implemented but not open for voting: + +- **InvariantsV1_1** - Adds new invariants to ensure transactions process as intended, starting with an invariant to ensure that ledger entries owned by an account are deleted when the account is deleted. ([#4663](https://github.com/XRPLF/rippled/pull/4663)) + +### New Features + +- Allow configuration of SQLite database page size. ([#5135](https://github.com/XRPLF/rippled/pull/5135), [#5140](https://github.com/XRPLF/rippled/pull/5140)) +- In the `libxrpl` C++ library, provide a list of known amendments. ([#5026](https://github.com/XRPLF/rippled/pull/5026)) + +### Deprecations + +- History Shards are removed. ([#5066](https://github.com/XRPLF/rippled/pull/5066)) +- Reporting mode is removed. ([#5092](https://github.com/XRPLF/rippled/pull/5092)) + +For users wanting to store more ledger history, it is recommended to run a Clio server instead. + +### Bug fixes + +- Fix a crash in debug builds when amm_info request contains an invalid AMM account ID. ([#5188](https://github.com/XRPLF/rippled/pull/5188)) +- Fix a crash caused by a race condition in peer-to-peer code. ([#5071](https://github.com/XRPLF/rippled/pull/5071)) +- Fix a crash in certain situations +- Fix several bugs in the book_changes API method. ([#5096](https://github.com/XRPLF/rippled/pull/5096)) +- Fix bug triggered by providing an invalid marker to the account_nfts API method. ([#5045](https://github.com/XRPLF/rippled/pull/5045)) +- Accept lower-case hexadecimal in compact transaction identifier (CTID) parameters in API methods. ([#5049](https://github.com/XRPLF/rippled/pull/5049)) +- Disallow filtering by types that an account can't own in the account_objects API method. ([#5056](https://github.com/XRPLF/rippled/pull/5056)) +- Fix error code returned by the feature API method when providing an invalid parameter. ([#5063](https://github.com/XRPLF/rippled/pull/5063)) +- (API v3) Fix error code returned by amm_info when providing invalid parameters. ([#4924](https://github.com/XRPLF/rippled/pull/4924)) + +### Other Improvements + +- Adds a new default hub, hubs.xrpkuwait.com, to the config file and bootstrapping code. ([#5169](https://github.com/XRPLF/rippled/pull/5169)) +- Improve error message when commandline interface fails with `rpcInternal` because there was no response from the server. ([#4959](https://github.com/XRPLF/rippled/pull/4959)) +- Add tools for debugging specific transactions via replay. ([#5027](https://github.com/XRPLF/rippled/pull/5027), [#5087](https://github.com/XRPLF/rippled/pull/5087)) +- Major reorganization of source code files. ([#4997](https://github.com/XRPLF/rippled/pull/4997)) +- Add new unit tests. ([#4886](https://github.com/XRPLF/rippled/pull/4886)) +- Various improvements to build tools and contributor documentation. ([#5001](https://github.com/XRPLF/rippled/pull/5001), [#5028](https://github.com/XRPLF/rippled/pull/5028), [#5052](https://github.com/XRPLF/rippled/pull/5052), [#5091](https://github.com/XRPLF/rippled/pull/5091), [#5084](https://github.com/XRPLF/rippled/pull/5084), [#5120](https://github.com/XRPLF/rippled/pull/5120), [#5010](https://github.com/XRPLF/rippled/pull/5010). [#5055](https://github.com/XRPLF/rippled/pull/5055), [#5067](https://github.com/XRPLF/rippled/pull/5067), [#5061](https://github.com/XRPLF/rippled/pull/5061), [#5072](https://github.com/XRPLF/rippled/pull/5072), [#5044](https://github.com/XRPLF/rippled/pull/5044) ) +- Various code cleanup and refactoring. ([#4509](https://github.com/XRPLF/rippled/pull/4509), [#4521](https://github.com/XRPLF/rippled/pull/4521), [#4856](https://github.com/XRPLF/rippled/pull/4856), [#5190](https://github.com/XRPLF/rippled/pull/5190), [#5081](https://github.com/XRPLF/rippled/pull/5081), [#5053](https://github.com/XRPLF/rippled/pull/5053), [#5058](https://github.com/XRPLF/rippled/pull/5058), [#5122](https://github.com/XRPLF/rippled/pull/5122), [#5059](https://github.com/XRPLF/rippled/pull/5059), [#5041](https://github.com/XRPLF/rippled/pull/5041)) + + +Bug Bounties and Responsible Disclosures: + +We welcome reviews of the `rippled` code and urge researchers to responsibly disclose any issues they may find. + +To report a bug, please send a detailed report to: + + # Version 2.2.3 Version 2.2.3 of `rippled`, the reference server implementation of the XRP Ledger protocol, is now available. This release fixes a problem that can cause full-history servers to run out of space in their SQLite databases, depending on configuration. There are no new amendments in this release. diff --git a/src/libxrpl/protocol/BuildInfo.cpp b/src/libxrpl/protocol/BuildInfo.cpp index c3d0bd1f92d..995df262885 100644 --- a/src/libxrpl/protocol/BuildInfo.cpp +++ b/src/libxrpl/protocol/BuildInfo.cpp @@ -33,7 +33,7 @@ namespace BuildInfo { // and follow the format described at http://semver.org/ //------------------------------------------------------------------------------ // clang-format off -char const* const versionString = "2.3.0-rc2" +char const* const versionString = "2.3.0" // clang-format on #if defined(DEBUG) || defined(SANITIZER)