diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index b6a10a52d..97deae136 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -3531,6 +3531,8 @@ + + diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index 86107664a..c215923e9 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -4005,6 +4005,9 @@ ripple\test\jtx + + ripple\test\jtx + ripple\test\jtx diff --git a/src/ripple/test/jtx.h b/src/ripple/test/jtx.h index 821716eaf..c30f0c8fd 100644 --- a/src/ripple/test/jtx.h +++ b/src/ripple/test/jtx.h @@ -28,6 +28,7 @@ #include #include #include +#include #include #include #include diff --git a/src/ripple/test/jtx/Env.h b/src/ripple/test/jtx/Env.h index 09c2e12ae..6a1059e8b 100644 --- a/src/ripple/test/jtx/Env.h +++ b/src/ripple/test/jtx/Env.h @@ -381,6 +381,12 @@ public: jtx::required(args...)(*this); } + /** Gets the TER result and `didApply` flag from a RPC Json result object. + */ + static + std::pair + parseResult(Json::Value const& jr); + /** Submit an existing JTx. This calls postconditions. */ @@ -388,6 +394,12 @@ public: void submit (JTx const& jt); + /** Use the submit RPC command with a provided JTx object. + This calls postconditions. + */ + void + sign_and_submit(JTx const& jt, Json::Value params = Json::nullValue); + /** Check expected postconditions of JTx submission. */ @@ -407,8 +419,7 @@ public: template void - operator()(JsonValue&& jv, - FN const&... fN) + operator()(JsonValue&& jv, FN const&... fN) { apply(std::forward< JsonValue>(jv), fN...); @@ -433,6 +444,20 @@ public: std::shared_ptr meta(); + /** Return the tx data for the last JTx. + + Effects: + + The tx data for the last transaction + ID, if any, is returned. No side + effects. + + @note Only necessary for JTx submitted + with via sign-and-submit method. + */ + std::shared_ptr + tx() const; + private: void fund (bool setDefaultRipple, diff --git a/src/ripple/test/jtx/Env_ss.h b/src/ripple/test/jtx/Env_ss.h new file mode 100644 index 000000000..80ede0daf --- /dev/null +++ b/src/ripple/test/jtx/Env_ss.h @@ -0,0 +1,86 @@ +//------------------------------------------------------------------------------ +/* + 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_ENV_SS_H_INCLUDED +#define RIPPLE_TEST_JTX_ENV_SS_H_INCLUDED + +#include + +namespace ripple { +namespace test { +namespace jtx { + +/** A transaction testing environment wrapper. + Transactions submitted in sign-and-submit mode + by default. +*/ +class Env_ss +{ +private: + Env& env_; + +private: + + class SignSubmitRunner + { + public: + SignSubmitRunner(SignSubmitRunner&&) = default; + SignSubmitRunner& operator= (SignSubmitRunner&&) = delete; + + SignSubmitRunner(Env& env, JTx&& jt) + : env_(env) + , jt_(jt) + { + } + + void operator()(Json::Value const& params = Json::nullValue) + { + env_.sign_and_submit(jt_, params); + } + + private: + Env& env_; + JTx const jt_; + }; + +public: + Env_ss (Env_ss const&) = delete; + Env_ss& operator= (Env_ss const&) = delete; + + Env_ss (Env& env) + : env_(env) + { + } + + template + SignSubmitRunner + operator()(JsonValue&& jv, FN const&... fN) + { + auto jtx = env_.jt(std::forward< + JsonValue>(jv), fN...); + return SignSubmitRunner(env_, std::move(jtx)); + } +}; + +} // jtx +} // test +} // ripple + +#endif diff --git a/src/ripple/test/jtx/impl/Env.cpp b/src/ripple/test/jtx/impl/Env.cpp index a0453f4dc..ef4cede5d 100644 --- a/src/ripple/test/jtx/impl/Env.cpp +++ b/src/ripple/test/jtx/impl/Env.cpp @@ -345,6 +345,20 @@ Env::trust (STAmount const& amount, test.expect(balance(account) == start); } +std::pair +Env::parseResult(Json::Value const& jr) +{ + TER ter; + if (jr.isObject() && jr.isMember(jss::result) && + jr[jss::result].isMember(jss::engine_result_code)) + ter = static_cast( + jr[jss::result][jss::engine_result_code].asInt()); + else + ter = temINVALID; + return std::make_pair(ter, + isTesSuccess(ter) || isTecClaim(ter)); +} + void Env::submit (JTx const& jt) { @@ -355,12 +369,8 @@ Env::submit (JTx const& jt) Serializer s; jt.stx->add(s); auto const jr = rpc("submit", strHex(s.slice())); - if (jr["result"].isMember("engine_result_code")) - ter_ = static_cast( - jr["result"]["engine_result_code"].asInt()); - else - ter_ = temINVALID; - didApply = isTesSuccess(ter_) || isTecClaim(ter_); + + std::tie(ter_, didApply) = parseResult(jr); } else { @@ -372,6 +382,46 @@ Env::submit (JTx const& jt) return postconditions(jt, ter_, didApply); } +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(); + + Json::Value jr; + if (params.isNull()) + { + // Use the command line interface + auto const jv = boost::lexical_cast(jt.jv); + jr = rpc("submit", passphrase, jv); + } + else + { + // Use the provided parameters, and go straight + // to the (RPC) client. + assert(params.isObject()); + if (!params.isMember(jss::secret) && + !params.isMember(jss::key_type) && + !params.isMember(jss::seed) && + !params.isMember(jss::seed_hex) && + !params.isMember(jss::passphrase)) + { + params[jss::secret] = passphrase; + } + params[jss::tx_json] = jt.jv; + jr = client().invoke("submit", params); + } + txid_.SetHex( + jr[jss::result][jss::tx_json][jss::hash].asString()); + + std::tie(ter_, didApply) = parseResult(jr); + + return postconditions(jt, ter_, didApply); +} + void Env::postconditions(JTx const& jt, TER ter, bool didApply) { @@ -404,6 +454,12 @@ Env::meta() return item.second; } +std::shared_ptr +Env::tx() const +{ + return current()->txRead(txid_).first; +} + void Env::autofill_sig (JTx& jt) { diff --git a/src/ripple/test/jtx/impl/Env_test.cpp b/src/ripple/test/jtx/impl/Env_test.cpp index 46b255d6d..ddc658f0d 100644 --- a/src/ripple/test/jtx/impl/Env_test.cpp +++ b/src/ripple/test/jtx/impl/Env_test.cpp @@ -22,6 +22,7 @@ #include #include #include +#include #include #include #include @@ -578,6 +579,54 @@ public: env (jt); } + void testSignAndSubmit() + { + using namespace jtx; + Env env(*this); + Env_ss envs(env); + + auto const alice = Account("alice"); + env.fund(XRP(10000), alice); + + { + envs(noop(alice), fee(none), seq(none))(); + + // Make sure we get the right account back. + auto tx = env.tx(); + if (expect(tx)) + { + expect(tx->getAccountID(sfAccount) == alice.id()); + expect(tx->getTxnType() == ttACCOUNT_SET); + } + } + + { + auto params = Json::Value(Json::nullValue); + envs(noop(alice), fee(none), seq(none))(params); + + // Make sure we get the right account back. + auto tx = env.tx(); + if (expect(tx)) + { + expect(tx->getAccountID(sfAccount) == alice.id()); + expect(tx->getTxnType() == ttACCOUNT_SET); + } + } + + { + auto params = Json::Value(Json::objectValue); + // Force the factor low enough to fail + params[jss::fee_mult_max] = 1; + params[jss::fee_div_max] = 2; + // RPC errors result in temINVALID + envs(noop(alice), fee(none), + seq(none), ter(temINVALID))(params); + + auto tx = env.tx(); + expect(!tx); + } + } + void run() { @@ -598,6 +647,7 @@ public: testClose(); testPath(); testResignSigned(); + testSignAndSubmit(); } };