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();
}
};