Add ripple::Expected which simulates std::expected:

Also integrates use of ripple::Expected into the code base.
This commit is contained in:
Scott Schurr
2021-08-16 11:27:05 -07:00
committed by Nik Bougalis
parent de43d43560
commit c50d166c23
14 changed files with 557 additions and 69 deletions

View File

@@ -708,6 +708,7 @@ target_sources (rippled PRIVATE
#]===============================]
src/test/basics/Buffer_test.cpp
src/test/basics/DetectCrash_test.cpp
src/test/basics/Expected_test.cpp
src/test/basics/FileUtilities_test.cpp
src/test/basics/IOUAmount_test.cpp
src/test/basics/KeyCache_test.cpp

View File

@@ -58,13 +58,13 @@ SetSignerList::determineOperation(
{
auto signers = SignerEntries::deserialize(tx, j, "transaction");
if (signers.second != tesSUCCESS)
return std::make_tuple(signers.second, quorum, sign, op);
if (!signers)
return std::make_tuple(signers.error(), quorum, sign, op);
std::sort(signers.first.begin(), signers.first.end());
std::sort(signers->begin(), signers->end());
// Save deserialized list for later.
sign = std::move(signers.first);
sign = std::move(*signers);
op = set;
}
else if ((quorum == 0) && !hasSignerEntries)

View File

@@ -25,7 +25,7 @@
namespace ripple {
std::pair<std::vector<SignerEntries::SignerEntry>, NotTEC>
Expected<std::vector<SignerEntries::SignerEntry>, NotTEC>
SignerEntries::deserialize(
STObject const& obj,
beast::Journal journal,
@@ -37,11 +37,10 @@ SignerEntries::deserialize(
{
JLOG(journal.trace())
<< "Malformed " << annotation << ": Need signer entry array.";
s.second = temMALFORMED;
return s;
return Unexpected(temMALFORMED);
}
auto& accountVec = s.first;
std::vector<SignerEntry> accountVec;
accountVec.reserve(STTx::maxMultiSigners);
STArray const& sEntries(obj.getFieldArray(sfSignerEntries));
@@ -52,8 +51,7 @@ SignerEntries::deserialize(
{
JLOG(journal.trace())
<< "Malformed " << annotation << ": Expected SignerEntry.";
s.second = temMALFORMED;
return s;
return Unexpected(temMALFORMED);
}
// Extract SignerEntry fields.
@@ -61,9 +59,7 @@ SignerEntries::deserialize(
std::uint16_t const weight = sEntry.getFieldU16(sfSignerWeight);
accountVec.emplace_back(account, weight);
}
s.second = tesSUCCESS;
return s;
return accountVec;
}
} // namespace ripple

View File

@@ -21,6 +21,7 @@
#define RIPPLE_TX_IMPL_SIGNER_ENTRIES_H_INCLUDED
#include <ripple/app/tx/impl/Transactor.h> // NotTEC
#include <ripple/basics/Expected.h> //
#include <ripple/beast/utility/Journal.h> // beast::Journal
#include <ripple/protocol/STTx.h> // STTx::maxMultiSigners
#include <ripple/protocol/TER.h> // temMALFORMED
@@ -62,7 +63,7 @@ public:
};
// Deserialize a SignerEntries array from the network or from the ledger.
static std::pair<std::vector<SignerEntry>, NotTEC>
static Expected<std::vector<SignerEntry>, NotTEC>
deserialize(
STObject const& obj,
beast::Journal journal,

View File

@@ -554,8 +554,8 @@ Transactor::checkMultiSign(PreclaimContext const& ctx)
auto accountSigners =
SignerEntries::deserialize(*sleAccountSigners, ctx.j, "ledger");
if (accountSigners.second != tesSUCCESS)
return accountSigners.second;
if (!accountSigners)
return accountSigners.error();
// Get the array of transaction signers.
STArray const& txSigners(ctx.tx.getFieldArray(sfSigners));
@@ -567,7 +567,7 @@ Transactor::checkMultiSign(PreclaimContext const& ctx)
// matching multi-signers to account signers should be a simple
// linear walk. *All* signers must be valid or the transaction fails.
std::uint32_t weightSum = 0;
auto iter = accountSigners.first.begin();
auto iter = accountSigners->begin();
for (auto const& txSigner : txSigners)
{
AccountID const txSignerAcctID = txSigner.getAccountID(sfAccount);
@@ -575,7 +575,7 @@ Transactor::checkMultiSign(PreclaimContext const& ctx)
// Attempt to match the SignerEntry with a Signer;
while (iter->account < txSignerAcctID)
{
if (++iter == accountSigners.first.end())
if (++iter == accountSigners->end())
{
JLOG(ctx.j.trace())
<< "applyTransaction: Invalid SigningAccount.Account.";

View File

@@ -55,10 +55,10 @@ checkValidity(
: STTx::RequireFullyCanonicalSig::no;
auto const sigVerify = tx.checkSign(requireCanonicalSig);
if (!sigVerify.first)
if (!sigVerify)
{
router.setFlags(id, SF_SIGBAD);
return {Validity::SigBad, sigVerify.second};
return {Validity::SigBad, sigVerify.error()};
}
router.setFlags(id, SF_SIGGOOD);
}

View File

@@ -0,0 +1,243 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2021 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 RIPPLE_BASICS_EXPECTED_H_INCLUDED
#define RIPPLE_BASICS_EXPECTED_H_INCLUDED
#include <ripple/basics/contract.h>
#include <boost/outcome.hpp>
#include <stdexcept>
#include <type_traits>
namespace ripple {
/** Expected is an approximation of std::expected (hoped for in C++23)
See: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0323r10.html
The implementation is entirely based on boost::outcome_v2::result.
*/
// Exception thrown by an invalid access to Expected.
struct bad_expected_access : public std::runtime_error
{
bad_expected_access() : runtime_error("bad expected access")
{
}
};
namespace detail {
// Custom policy for Expected. Always throw on an invalid access.
struct throw_policy : public boost::outcome_v2::policy::base
{
template <class Impl>
static constexpr void
wide_value_check(Impl&& self)
{
if (!base::_has_value(std::forward<Impl>(self)))
Throw<bad_expected_access>();
}
template <class Impl>
static constexpr void
wide_error_check(Impl&& self)
{
if (!base::_has_error(std::forward<Impl>(self)))
Throw<bad_expected_access>();
}
template <class Impl>
static constexpr void
wide_exception_check(Impl&& self)
{
if (!base::_has_exception(std::forward<Impl>(self)))
Throw<bad_expected_access>();
}
};
} // namespace detail
// Definition of Unexpected, which is used to construct the unexpected
// return type of an Expected.
template <class E>
class Unexpected
{
public:
static_assert(!std::is_same<E, void>::value, "E must not be void");
Unexpected() = delete;
constexpr explicit Unexpected(E const& e) : val_(e)
{
}
constexpr explicit Unexpected(E&& e) : val_(std::move(e))
{
}
constexpr const E&
value() const&
{
return val_;
}
constexpr E&
value() &
{
return val_;
}
constexpr E&&
value() &&
{
return std::move(val_);
}
constexpr const E&&
value() const&&
{
return std::move(val_);
}
private:
E val_;
};
// Unexpected deduction guide that converts array to const*.
template <typename E, std::size_t N>
Unexpected(E (&)[N]) -> Unexpected<E const*>;
// Definition of Expected. All of the machinery comes from boost::result.
template <class T, class E>
class [[nodiscard]] Expected
: private boost::outcome_v2::result<T, E, detail::throw_policy>
{
using Base = boost::outcome_v2::result<T, E, detail::throw_policy>;
public:
template <
typename U,
typename = std::enable_if_t<std::is_convertible_v<U, T>>>
constexpr Expected(U r) : Base(T{std::forward<U>(r)})
{
}
template <
typename U,
typename = std::enable_if_t<std::is_convertible_v<U, E>>>
constexpr Expected(Unexpected<U> e) : Base(E{std::forward<U>(e.value())})
{
}
constexpr bool has_value() const
{
return Base::has_value();
}
constexpr T const& value() const
{
return Base::value();
}
constexpr T& value()
{
return Base::value();
}
constexpr E const& error() const
{
return Base::error();
}
constexpr E& error()
{
return Base::error();
}
constexpr explicit operator bool() const
{
return has_value();
}
// Add operator* and operator-> so the Expected API looks a bit more like
// what std::expected is likely to look like. See:
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p0323r10.html
[[nodiscard]] constexpr T& operator*()
{
return this->value();
}
[[nodiscard]] constexpr T const& operator*() const
{
return this->value();
}
[[nodiscard]] constexpr T* operator->()
{
return &this->value();
}
[[nodiscard]] constexpr T const* operator->() const
{
return &this->value();
}
};
// Specialization of Expected<void, E>. Allows returning either success
// (without a value) or the reason for the failure.
template <class E>
class [[nodiscard]] Expected<void, E>
: private boost::outcome_v2::result<void, E, detail::throw_policy>
{
using Base = boost::outcome_v2::result<void, E, detail::throw_policy>;
public:
// The default constructor makes a successful Expected<void, E>.
// This aligns with std::expected behavior proposed in P0323R10.
constexpr Expected() : Base(boost::outcome_v2::success())
{
}
template <
typename U,
typename = std::enable_if_t<std::is_convertible_v<U, E>>>
constexpr Expected(Unexpected<U> e) : Base(E{std::forward<U>(e.value())})
{
}
constexpr E const& error() const
{
return Base::error();
}
constexpr E& error()
{
return Base::error();
}
constexpr explicit operator bool() const
{
return Base::has_value();
}
};
} // namespace ripple
#endif // RIPPLE_BASICS_EXPECTED_H_INCLUDED

View File

@@ -25,6 +25,7 @@
#ifndef RIPPLE_BASICS_BASE_UINT_H_INCLUDED
#define RIPPLE_BASICS_BASE_UINT_H_INCLUDED
#include <ripple/basics/Expected.h>
#include <ripple/basics/contract.h>
#include <ripple/basics/hardened_hash.h>
#include <ripple/basics/strHex.h>
@@ -185,7 +186,7 @@ private:
badChar,
};
constexpr std::pair<ParseResult, decltype(data_)>
constexpr Expected<decltype(data_), ParseResult>
parseFromStringView(std::string_view sv) noexcept
{
// Local lambda that converts a single hex char to four bits and
@@ -212,7 +213,7 @@ private:
return ParseResult::okay;
};
std::pair<ParseResult, decltype(data_)> ret{ParseResult::okay, {}};
decltype(data_) ret{};
if (sv == "0")
{
@@ -220,15 +221,10 @@ private:
}
if (sv.size() != size() * 2)
{
ret.first = ParseResult::badLength;
return ret;
}
auto out = ret.second.data();
return Unexpected(ParseResult::badLength);
std::size_t i = 0u;
auto in = sv.begin();
while (in != sv.end())
{
std::uint32_t accum = {};
@@ -236,12 +232,9 @@ private:
{
if (auto const result = hexCharToUInt(*in++, shift, accum);
result != ParseResult::okay)
{
ret.first = result;
return ret;
}
return Unexpected(result);
}
*out++ = accum;
ret[i++] = accum;
}
return ret;
}
@@ -250,13 +243,14 @@ private:
parseFromStringViewThrows(std::string_view sv) noexcept(false)
{
auto const result = parseFromStringView(sv);
if (result.first == ParseResult::badLength)
Throw<std::invalid_argument>("invalid length for hex string");
if (!result)
{
if (result.error() == ParseResult::badLength)
Throw<std::invalid_argument>("invalid length for hex string");
if (result.first == ParseResult::badChar)
Throw<std::range_error>("invalid hex character");
return result.second;
}
return *result;
}
public:
@@ -481,10 +475,10 @@ public:
parseHex(std::string_view sv)
{
auto const result = parseFromStringView(sv);
if (result.first != ParseResult::okay)
if (!result)
return false;
data_ = result.second;
data_ = *result;
return true;
}

View File

@@ -20,6 +20,7 @@
#ifndef RIPPLE_PROTOCOL_STTX_H_INCLUDED
#define RIPPLE_PROTOCOL_STTX_H_INCLUDED
#include <ripple/basics/Expected.h>
#include <ripple/protocol/PublicKey.h>
#include <ripple/protocol/STObject.h>
#include <ripple/protocol/SecretKey.h>
@@ -131,7 +132,7 @@ public:
@return `true` if valid signature. If invalid, the error message string.
*/
enum class RequireFullyCanonicalSig : bool { no, yes };
std::pair<bool, std::string>
Expected<void, std::string>
checkSign(RequireFullyCanonicalSig requireCanonicalSig) const;
// SQL Functions with metadata.
@@ -150,10 +151,10 @@ public:
std::string const& escapedMetaData) const;
private:
std::pair<bool, std::string>
Expected<void, std::string>
checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const;
std::pair<bool, std::string>
Expected<void, std::string>
checkMultiSign(RequireFullyCanonicalSig requireCanonicalSig) const;
uint256 tid_;

View File

@@ -349,7 +349,10 @@ public:
}
// Trait tells enable_if which types are allowed for construction.
template <typename T, typename = std::enable_if_t<Trait<T>::value>>
template <
typename T,
typename = std::enable_if_t<
Trait<std::remove_cv_t<std::remove_reference_t<T>>>::value>>
constexpr TERSubset(T rhs) : code_(TERtoInt(rhs))
{
}

View File

@@ -186,24 +186,22 @@ STTx::sign(PublicKey const& publicKey, SecretKey const& secretKey)
tid_ = getHash(HashPrefix::transactionID);
}
std::pair<bool, std::string>
Expected<void, std::string>
STTx::checkSign(RequireFullyCanonicalSig requireCanonicalSig) const
{
std::pair<bool, std::string> ret{false, ""};
try
{
// Determine whether we're single- or multi-signing by looking
// at the SigningPubKey. If it's empty we must be
// multi-signing. Otherwise we're single-signing.
Blob const& signingPubKey = getFieldVL(sfSigningPubKey);
ret = signingPubKey.empty() ? checkMultiSign(requireCanonicalSig)
: checkSingleSign(requireCanonicalSig);
return signingPubKey.empty() ? checkMultiSign(requireCanonicalSig)
: checkSingleSign(requireCanonicalSig);
}
catch (std::exception const&)
{
ret = {false, "Internal signature check failure."};
}
return ret;
return Unexpected("Internal signature check failure.");
}
Json::Value STTx::getJson(JsonOptions) const
@@ -269,14 +267,14 @@ STTx::getMetaSQL(
getFieldU32(sfSequence) % inLedger % status % rTxn % escapedMetaData);
}
std::pair<bool, std::string>
Expected<void, std::string>
STTx::checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const
{
// We don't allow both a non-empty sfSigningPubKey and an sfSigners.
// That would allow the transaction to be signed two ways. So if both
// fields are present the signature is invalid.
if (isFieldPresent(sfSigners))
return {false, "Cannot both single- and multi-sign."};
return Unexpected("Cannot both single- and multi-sign.");
bool validSig = false;
try
@@ -304,29 +302,29 @@ STTx::checkSingleSign(RequireFullyCanonicalSig requireCanonicalSig) const
validSig = false;
}
if (validSig == false)
return {false, "Invalid signature."};
return {true, ""};
return Unexpected("Invalid signature.");
// Signature was verified.
return {};
}
std::pair<bool, std::string>
Expected<void, std::string>
STTx::checkMultiSign(RequireFullyCanonicalSig requireCanonicalSig) const
{
// Make sure the MultiSigners are present. Otherwise they are not
// attempting multi-signing and we just have a bad SigningPubKey.
if (!isFieldPresent(sfSigners))
return {false, "Empty SigningPubKey."};
return Unexpected("Empty SigningPubKey.");
// We don't allow both an sfSigners and an sfTxnSignature. Both fields
// being present would indicate that the transaction is signed both ways.
if (isFieldPresent(sfTxnSignature))
return {false, "Cannot both single- and multi-sign."};
return Unexpected("Cannot both single- and multi-sign.");
STArray const& signers{getFieldArray(sfSigners)};
// There are well known bounds that the number of signers must be within.
if (signers.size() < minMultiSigners || signers.size() > maxMultiSigners)
return {false, "Invalid Signers array size."};
return Unexpected("Invalid Signers array size.");
// We can ease the computational load inside the loop a bit by
// pre-constructing part of the data that we hash. Fill a Serializer
@@ -349,15 +347,15 @@ STTx::checkMultiSign(RequireFullyCanonicalSig requireCanonicalSig) const
// The account owner may not multisign for themselves.
if (accountID == txnAccountID)
return {false, "Invalid multisigner."};
return Unexpected("Invalid multisigner.");
// No duplicate signers allowed.
if (lastAccountID == accountID)
return {false, "Duplicate Signers not allowed."};
return Unexpected("Duplicate Signers not allowed.");
// Accounts must be in order by account ID. No duplicates allowed.
if (lastAccountID > accountID)
return {false, "Unsorted Signers array."};
return Unexpected("Unsorted Signers array.");
// The next signature must be greater than this one.
lastAccountID = accountID;
@@ -388,14 +386,12 @@ STTx::checkMultiSign(RequireFullyCanonicalSig requireCanonicalSig) const
validSig = false;
}
if (!validSig)
return {
false,
return Unexpected(
std::string("Invalid signature on account ") +
toBase58(accountID) + "."};
toBase58(accountID) + ".");
}
// All signatures verified.
return {true, ""};
return {};
}
//------------------------------------------------------------------------------

View File

@@ -0,0 +1,212 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github0.com/ripple/rippled
Copyright (c) 2012-2016 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 <ripple/basics/Expected.h>
#include <ripple/beast/unit_test.h>
#include <ripple/protocol/TER.h>
#include <array>
#include <cstdint>
namespace ripple {
namespace test {
struct Expected_test : beast::unit_test::suite
{
void
run() override
{
// Test non-error const construction.
{
auto const expected = []() -> Expected<std::string, TER> {
return "Valid value";
}();
BEAST_EXPECT(expected);
BEAST_EXPECT(expected.has_value());
BEAST_EXPECT(expected.value() == "Valid value");
BEAST_EXPECT(*expected == "Valid value");
BEAST_EXPECT(expected->at(0) == 'V');
bool throwOccurred = false;
try
{
// There's no error, so should throw.
[[maybe_unused]] TER const t = expected.error();
}
catch (std::runtime_error const& e)
{
BEAST_EXPECT(e.what() == std::string("bad expected access"));
throwOccurred = true;
}
BEAST_EXPECT(throwOccurred);
}
// Test non-error non-const construction.
{
auto expected = []() -> Expected<std::string, TER> {
return "Valid value";
}();
BEAST_EXPECT(expected);
BEAST_EXPECT(expected.has_value());
BEAST_EXPECT(expected.value() == "Valid value");
BEAST_EXPECT(*expected == "Valid value");
BEAST_EXPECT(expected->at(0) == 'V');
std::string mv = std::move(*expected);
BEAST_EXPECT(mv == "Valid value");
bool throwOccurred = false;
try
{
// There's no error, so should throw.
[[maybe_unused]] TER const t = expected.error();
}
catch (std::runtime_error const& e)
{
BEAST_EXPECT(e.what() == std::string("bad expected access"));
throwOccurred = true;
}
BEAST_EXPECT(throwOccurred);
}
// Test error construction from rvalue.
{
auto const expected = []() -> Expected<std::string, TER> {
return Unexpected(telLOCAL_ERROR);
}();
BEAST_EXPECT(!expected);
BEAST_EXPECT(!expected.has_value());
BEAST_EXPECT(expected.error() == telLOCAL_ERROR);
bool throwOccurred = false;
try
{
// There's no result, so should throw.
[[maybe_unused]] std::string const s = *expected;
}
catch (std::runtime_error const& e)
{
BEAST_EXPECT(e.what() == std::string("bad expected access"));
throwOccurred = true;
}
BEAST_EXPECT(throwOccurred);
}
// Test error construction from lvalue.
{
auto const err(telLOCAL_ERROR);
auto expected = [&err]() -> Expected<std::string, TER> {
return Unexpected(err);
}();
BEAST_EXPECT(!expected);
BEAST_EXPECT(!expected.has_value());
BEAST_EXPECT(expected.error() == telLOCAL_ERROR);
bool throwOccurred = false;
try
{
// There's no result, so should throw.
[[maybe_unused]] std::size_t const s = expected->size();
}
catch (std::runtime_error const& e)
{
BEAST_EXPECT(e.what() == std::string("bad expected access"));
throwOccurred = true;
}
BEAST_EXPECT(throwOccurred);
}
// Test error construction from const char*.
{
auto const expected = []() -> Expected<int, char const*> {
return Unexpected("Not what is expected!");
}();
BEAST_EXPECT(!expected);
BEAST_EXPECT(!expected.has_value());
BEAST_EXPECT(
expected.error() == std::string("Not what is expected!"));
}
// Test error construction of string from const char*.
{
auto expected = []() -> Expected<int, std::string> {
return Unexpected("Not what is expected!");
}();
BEAST_EXPECT(!expected);
BEAST_EXPECT(!expected.has_value());
BEAST_EXPECT(expected.error() == "Not what is expected!");
std::string const s(std::move(expected.error()));
BEAST_EXPECT(s == "Not what is expected!");
}
// Test non-error const construction of Expected<void, T>.
{
auto const expected = []() -> Expected<void, std::string> {
return {};
}();
BEAST_EXPECT(expected);
bool throwOccurred = false;
try
{
// There's no error, so should throw.
[[maybe_unused]] std::size_t const s = expected.error().size();
}
catch (std::runtime_error const& e)
{
BEAST_EXPECT(e.what() == std::string("bad expected access"));
throwOccurred = true;
}
BEAST_EXPECT(throwOccurred);
}
// Test non-error non-const construction of Expected<void, T>.
{
auto expected = []() -> Expected<void, std::string> {
return {};
}();
BEAST_EXPECT(expected);
bool throwOccurred = false;
try
{
// There's no error, so should throw.
[[maybe_unused]] std::size_t const s = expected.error().size();
}
catch (std::runtime_error const& e)
{
BEAST_EXPECT(e.what() == std::string("bad expected access"));
throwOccurred = true;
}
BEAST_EXPECT(throwOccurred);
}
// Test error const construction of Expected<void, T>.
{
auto const expected = []() -> Expected<void, std::string> {
return Unexpected("Not what is expected!");
}();
BEAST_EXPECT(!expected);
BEAST_EXPECT(expected.error() == "Not what is expected!");
}
// Test error non-const construction of Expected<void, T>.
{
auto expected = []() -> Expected<void, std::string> {
return Unexpected("Not what is expected!");
}();
BEAST_EXPECT(!expected);
BEAST_EXPECT(expected.error() == "Not what is expected!");
std::string const s(std::move(expected.error()));
BEAST_EXPECT(s == "Not what is expected!");
}
}
};
BEAST_DEFINE_TESTSUITE(Expected, ripple_basics, ripple);
} // namespace test
} // namespace ripple

View File

@@ -223,6 +223,47 @@ struct base_uint_test : beast::unit_test::suite
static_assert(test96("00000000000000000000000~").signum() == 1);
#endif // 0
// Using the constexpr constructor in a non-constexpr context
// with an error in the parsing throws an exception.
{
// Invalid length for string.
bool caught = false;
try
{
// Try to prevent constant evaluation.
std::vector<char> str(23, '7');
std::string_view sView(str.data(), str.size());
[[maybe_unused]] test96 t96(sView);
}
catch (std::invalid_argument const& e)
{
BEAST_EXPECT(
e.what() ==
std::string("invalid length for hex string"));
caught = true;
}
BEAST_EXPECT(caught);
}
{
// Invalid character in string.
bool caught = false;
try
{
// Try to prevent constant evaluation.
std::vector<char> str(23, '7');
str.push_back('G');
std::string_view sView(str.data(), str.size());
[[maybe_unused]] test96 t96(sView);
}
catch (std::range_error const& e)
{
BEAST_EXPECT(
e.what() == std::string("invalid hex character"));
caught = true;
}
BEAST_EXPECT(caught);
}
// Verify that constexpr base_uints interpret a string the same
// way parseHex() does.
struct StrBaseUint

View File

@@ -1592,7 +1592,7 @@ public:
j.sign(keypair.first, keypair.second);
unexpected(
!j.checkSign(STTx::RequireFullyCanonicalSig::yes).first,
!j.checkSign(STTx::RequireFullyCanonicalSig::yes),
"Transaction fails signature test");
Serializer rawTxn;