mirror of
https://github.com/XRPLF/rippled.git
synced 2026-07-30 02:20:39 +00:00
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 <jfreeman08@gmail.com> Co-authored-by: Howard Hinnant <howard.hinnant@gmail.com>
This commit is contained in:
105
src/libxrpl/protocol/STNumber.cpp
Normal file
105
src/libxrpl/protocol/STNumber.cpp
Normal file
@@ -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 <xrpl/protocol/STNumber.h>
|
||||
|
||||
#include <xrpl/protocol/SField.h>
|
||||
|
||||
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<STNumber const&>(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
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <xrpl/protocol/STArray.h>
|
||||
#include <xrpl/protocol/STBlob.h>
|
||||
#include <xrpl/protocol/STCurrency.h>
|
||||
#include <xrpl/protocol/STNumber.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
|
||||
namespace ripple {
|
||||
@@ -665,6 +666,13 @@ STObject::getFieldCurrency(SField const& field) const
|
||||
return getFieldByConstRef<STCurrency>(field, empty);
|
||||
}
|
||||
|
||||
STNumber const&
|
||||
STObject::getFieldNumber(SField const& field) const
|
||||
{
|
||||
static STNumber const empty{};
|
||||
return getFieldByConstRef<STNumber>(field, empty);
|
||||
}
|
||||
|
||||
void
|
||||
STObject::set(std::unique_ptr<STBase> 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)
|
||||
{
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
#include <xrpl/protocol/STCurrency.h>
|
||||
#include <xrpl/protocol/STInteger.h>
|
||||
#include <xrpl/protocol/STIssue.h>
|
||||
#include <xrpl/protocol/STNumber.h>
|
||||
#include <xrpl/protocol/STObject.h>
|
||||
#include <xrpl/protocol/STPathSet.h>
|
||||
#include <xrpl/protocol/STVector256.h>
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <xrpl/basics/contract.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
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<unsigned char>(i >> 24));
|
||||
mData.push_back(static_cast<unsigned char>((i >> 16) & 0xff));
|
||||
mData.push_back(static_cast<unsigned char>((i >> 8) & 0xff));
|
||||
mData.push_back(static_cast<unsigned char>(i & 0xff));
|
||||
return ret;
|
||||
}
|
||||
|
||||
int
|
||||
Serializer::add32(HashPrefix p)
|
||||
{
|
||||
@@ -56,21 +46,6 @@ Serializer::add32(HashPrefix p)
|
||||
return add32(safe_cast<std::uint32_t>(p));
|
||||
}
|
||||
|
||||
int
|
||||
Serializer::add64(std::uint64_t i)
|
||||
{
|
||||
int ret = mData.size();
|
||||
mData.push_back(static_cast<unsigned char>(i >> 56));
|
||||
mData.push_back(static_cast<unsigned char>((i >> 48) & 0xff));
|
||||
mData.push_back(static_cast<unsigned char>((i >> 40) & 0xff));
|
||||
mData.push_back(static_cast<unsigned char>((i >> 32) & 0xff));
|
||||
mData.push_back(static_cast<unsigned char>((i >> 24) & 0xff));
|
||||
mData.push_back(static_cast<unsigned char>((i >> 16) & 0xff));
|
||||
mData.push_back(static_cast<unsigned char>((i >> 8) & 0xff));
|
||||
mData.push_back(static_cast<unsigned char>(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<std::runtime_error>("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<std::runtime_error>("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)
|
||||
{
|
||||
|
||||
93
src/test/protocol/STNumber_test.cpp
Normal file
93
src/test/protocol/STNumber_test.cpp
Normal file
@@ -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 <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/STNumber.h>
|
||||
|
||||
#include <limits>
|
||||
#include <ostream>
|
||||
|
||||
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*, Number*>);
|
||||
|
||||
{
|
||||
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<std::int64_t> const mantissas = {
|
||||
std::numeric_limits<std::int64_t>::min(),
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
std::numeric_limits<std::int64_t>::max()};
|
||||
for (std::int64_t mantissa : mantissas)
|
||||
testCombo(Number{mantissa});
|
||||
|
||||
std::initializer_list<std::int32_t> 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
|
||||
69
src/test/protocol/Serializer_test.cpp
Normal file
69
src/test/protocol/Serializer_test.cpp
Normal file
@@ -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 <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/protocol/Serializer.h>
|
||||
|
||||
#include <limits>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
struct Serializer_test : public beast::unit_test::suite
|
||||
{
|
||||
void
|
||||
run() override
|
||||
{
|
||||
{
|
||||
std::initializer_list<std::int32_t> const values = {
|
||||
std::numeric_limits<std::int32_t>::min(),
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
std::numeric_limits<std::int32_t>::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<std::int64_t> const values = {
|
||||
std::numeric_limits<std::int64_t>::min(),
|
||||
-1,
|
||||
0,
|
||||
1,
|
||||
std::numeric_limits<std::int64_t>::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
|
||||
@@ -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<MPTIssue>()))
|
||||
{
|
||||
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) << ")";
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user