Skip to content

Commit

Permalink
Add RPC error checking support to unit tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ximinez committed Apr 9, 2024
1 parent c88166e commit 25d1e45
Show file tree
Hide file tree
Showing 10 changed files with 203 additions and 45 deletions.
11 changes: 8 additions & 3 deletions src/test/app/MultiSign_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -250,7 +250,8 @@ class MultiSign_test : public beast::unit_test::suite
env(noop(alice),
msig(demon, demon),
fee(3 * baseFee),
ter(telENV_RPC_FAILED));
rpc("invalidTransaction",
"fails local checks: Duplicate Signers not allowed."));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq);

Expand Down Expand Up @@ -361,7 +362,10 @@ class MultiSign_test : public beast::unit_test::suite
msig phantoms{bogie, demon};
std::reverse(phantoms.signers.begin(), phantoms.signers.end());
std::uint32_t const aliceSeq = env.seq(alice);
env(noop(alice), phantoms, ter(telENV_RPC_FAILED));
env(noop(alice),
phantoms,
rpc("invalidTransaction",
"fails local checks: Unsorted Signers array."));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq);
}
Expand Down Expand Up @@ -1640,7 +1644,8 @@ class MultiSign_test : public beast::unit_test::suite
env(noop(alice),
msig(demon, demon),
fee(3 * baseFee),
ter(telENV_RPC_FAILED));
rpc("invalidTransaction",
"fails local checks: Duplicate Signers not allowed."));
env.close();
BEAST_EXPECT(env.seq(alice) == aliceSeq);

Expand Down
4 changes: 3 additions & 1 deletion src/test/app/Regression_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,9 @@ struct Regression_test : public beast::unit_test::suite
secp256r1Sig->setFieldVL(sfSigningPubKey, *pubKeyBlob);
jt.stx.reset(secp256r1Sig.release());

env(jt, ter(telENV_RPC_FAILED));
env(jt,
rpc("invalidTransaction",
"fails local checks: Invalid signature."));
};

Account const alice{"alice", KeyType::secp256k1};
Expand Down
9 changes: 4 additions & 5 deletions src/test/app/TxQ_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1058,16 +1058,15 @@ class TxQPosNegFlows_test : public beast::unit_test::suite
auto const& jt = env.jt(noop(alice));
BEAST_EXPECT(jt.stx);

bool didApply;
TER ter;
Env::ParsedResult parsed;

env.app().openLedger().modify(
[&](OpenView& view, beast::Journal j) {
std::tie(ter, didApply) = ripple::apply(
std::tie(parsed.ter, parsed.didApply) = ripple::apply(
env.app(), view, *jt.stx, tapNONE, env.journal);
return didApply;
return parsed.didApply;
});
env.postconditions(jt, ter, didApply);
env.postconditions(jt, parsed);
}
checkMetrics(__LINE__, env, 1, std::nullopt, 4, 2, 256);

Expand Down
1 change: 1 addition & 0 deletions src/test/jtx.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
#include <test/jtx/regkey.h>
#include <test/jtx/require.h>
#include <test/jtx/requires.h>
#include <test/jtx/rpc.h>
#include <test/jtx/sendmax.h>
#include <test/jtx/seq.h>
#include <test/jtx/sig.h>
Expand Down
18 changes: 15 additions & 3 deletions src/test/jtx/Env.h
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,19 @@ class Env

Account const& master = Account::master;

/// Used by parseResult() and postConditions()
struct ParsedResult
{
TER ter;
// One way that RPC errors are returned
error_code_i rpcCode = rpcSUCCESS;
std::string rpcMessage;
// Another way that RPC errors are returned
std::string rpcError;
std::string rpcException;
bool didApply;
};

private:
struct AppBundle
{
Expand Down Expand Up @@ -493,7 +506,7 @@ class Env

/** Gets the TER result and `didApply` flag from a RPC Json result object.
*/
static std::pair<TER, bool>
static ParsedResult
parseResult(Json::Value const& jr);

/** Submit an existing JTx.
Expand All @@ -514,8 +527,7 @@ class Env
void
postconditions(
JTx const& jt,
TER ter,
bool didApply,
ParsedResult const& parsed,
Json::Value const& jr = Json::Value());

/** Apply funclets and submit. */
Expand Down
8 changes: 6 additions & 2 deletions src/test/jtx/Env_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -748,8 +748,12 @@ class Env_test : public beast::unit_test::suite
params[jss::fee_mult_max] = 1;
params[jss::fee_div_max] = 2;
// RPC errors result in telENV_RPC_FAILED
envs(noop(alice), fee(none), seq(none), ter(telENV_RPC_FAILED))(
params);
envs(
noop(alice),
fee(none),
seq(none),
rpc(rpcHIGH_FEE,
"Fee of 10 exceeds the requested tx limit of 5"))(params);

auto tx = env.tx();
BEAST_EXPECT(!tx);
Expand Down
3 changes: 3 additions & 0 deletions src/test/jtx/JTx.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ struct JTx
Json::Value jv;
requires_t require;
std::optional<TER> ter = TER{tesSUCCESS};
std::optional<std::pair<error_code_i, std::string>> rpcCode = std::nullopt;
std::optional<std::pair<std::string, std::optional<std::string>>>
rpcException = std::nullopt;
bool fill_fee = true;
bool fill_seq = true;
bool fill_sig = true;
Expand Down
95 changes: 69 additions & 26 deletions src/test/jtx/impl/Env.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -272,24 +272,46 @@ Env::trust(STAmount const& amount, Account const& account)
test.expect(balance(account) == start);
}

std::pair<TER, bool>
Env::ParsedResult
Env::parseResult(Json::Value const& jr)
{
TER ter;
if (jr.isObject() && jr.isMember(jss::result) &&
jr[jss::result].isMember(jss::engine_result_code))
ter = TER::fromInt(jr[jss::result][jss::engine_result_code].asInt());
auto error = [](ParsedResult& parsed, Json::Value const& object) {
// Use an error code that is not used anywhere in the transaction
// engine to distinguish this case.
parsed.ter = telENV_RPC_FAILED;
// Extract information about the error
if (!object.isObject())
return;
if (object.isMember(jss::error_code))
parsed.rpcCode =
safe_cast<error_code_i>(object[jss::error_code].asInt());
if (object.isMember(jss::error_message))
parsed.rpcMessage = object[jss::error_message].asString();
if (object.isMember(jss::error))
parsed.rpcError = object[jss::error].asString();
if (object.isMember(jss::error_exception))
parsed.rpcException = object[jss::error_exception].asString();
};
ParsedResult parsed;
if (jr.isObject() && jr.isMember(jss::result))
{
auto const& result = jr[jss::result];
if (result.isMember(jss::engine_result_code))
parsed.ter = TER::fromInt(result[jss::engine_result_code].asInt());
else
error(parsed, result);
}
else
// Use an error code that is not used anywhere in the transaction engine
// to distinguish this case.
ter = telENV_RPC_FAILED;
return std::make_pair(ter, isTesSuccess(ter) || isTecClaim(ter));
error(parsed, jr);

parsed.didApply = isTesSuccess(parsed.ter) || isTecClaim(parsed.ter);
return parsed;
}

void
Env::submit(JTx const& jt)
{
bool didApply;
ParsedResult parsedResult;
auto const jr = [&]() {
if (jt.stx)
{
Expand All @@ -298,28 +320,27 @@ Env::submit(JTx const& jt)
jt.stx->add(s);
auto const jr = rpc("submit", strHex(s.slice()));

std::tie(ter_, didApply) = parseResult(jr);
parsedResult = parseResult(jr);
ter_ = parsedResult.ter;

return jr;
}
else
{
// Parsing failed or the JTx is
// otherwise missing the stx field.
ter_ = temMALFORMED;
didApply = false;
parsedResult.ter = ter_ = temMALFORMED;
parsedResult.didApply = false;

return Json::Value();
}
}();
return postconditions(jt, ter_, didApply, jr);
return postconditions(jt, parsedResult, jr);
}

void
Env::sign_and_submit(JTx const& jt, Json::Value params)
{
bool didApply;

auto const account = lookup(jt.jv[jss::Account].asString());
auto const& passphrase = account.name();

Expand Down Expand Up @@ -348,24 +369,46 @@ Env::sign_and_submit(JTx const& jt, Json::Value params)
if (!txid_.parseHex(jr[jss::result][jss::tx_json][jss::hash].asString()))
txid_.zero();

std::tie(ter_, didApply) = parseResult(jr);
ParsedResult const parsedResult = parseResult(jr);
ter_ = parsedResult.ter;

return postconditions(jt, ter_, didApply, jr);
return postconditions(jt, parsedResult, jr);
}

void
Env::postconditions(
JTx const& jt,
TER ter,
bool didApply,
ParsedResult const& parsed,
Json::Value const& jr)
{
if (jt.ter &&
!test.expect(
ter == *jt.ter,
"apply: Got " + transToken(ter) + " (" + transHuman(ter) +
"); Expected " + transToken(*jt.ter) + " (" +
transHuman(*jt.ter) + ")"))
bool bad =
(jt.ter &&
!test.expect(
parsed.ter == *jt.ter,
"apply: Got " + transToken(parsed.ter) + " (" +
transHuman(parsed.ter) + "); Expected " + transToken(*jt.ter) +
" (" + transHuman(*jt.ter) + ")"));
using namespace std::string_literals;
bad = (jt.rpcCode &&
!test.expect(
parsed.rpcCode == jt.rpcCode->first,
"apply: Got RPC result "s +
RPC::get_error_info(parsed.rpcCode).token.c_str() + " (" +
parsed.rpcMessage + "); Expected " +
RPC::get_error_info(jt.rpcCode->first).token.c_str() + " (" +
jt.rpcCode->second + ")")) ||
bad;
bad =
(jt.rpcException &&
!test.expect(
parsed.rpcError == jt.rpcException->first &&
(!jt.rpcException->second ||
parsed.rpcException == *jt.rpcException->second),
"apply: Got RPC result "s + parsed.rpcError + " (" +
parsed.rpcException + "); Expected " + jt.rpcException->first +
" (" + jt.rpcException->second.value_or("n/a") + ")")) ||
bad;
if (bad)
{
test.log << pretty(jt.jv) << std::endl;
if (jr)
Expand Down
72 changes: 72 additions & 0 deletions src/test/jtx/rpc.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 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_TEST_JTX_RPC_H_INCLUDED
#define RIPPLE_TEST_JTX_RPC_H_INCLUDED

#include <test/jtx/Env.h>
#include <tuple>

namespace ripple {
namespace test {
namespace jtx {

/** Set the expected result code for a JTx
The test will fail if the code doesn't match.
*/
class rpc
{
private:
std::optional<error_code_i> code_;
std::optional<std::string> errorMessage_;
std::optional<std::string> error_;
std::optional<std::string> errorException_;

public:
/// If there's an error code, we expect an error message
explicit rpc(error_code_i code, std::optional<std::string> m = {})
: code_(code), errorMessage_(m)
{
}

/// If there is not a code, we expect an exception message
explicit rpc(std::string error, std::optional<std::string> exception = {})
: error_(error), errorException_(exception)
{
}

void
operator()(Env&, JTx& jt) const
{
jt.ter = telENV_RPC_FAILED;
if (code_)
jt.rpcCode = {
*code_,
errorMessage_ ? *errorMessage_
: RPC::get_error_info(*code_).message.c_str()};
if (error_)
jt.rpcException = {*error_, errorException_};
}
};

} // namespace jtx
} // namespace test
} // namespace ripple

#endif
Loading

0 comments on commit 25d1e45

Please sign in to comment.