diff --git a/Builds/VisualStudio2015/RippleD.vcxproj b/Builds/VisualStudio2015/RippleD.vcxproj index 96a509995..b6dc1bc0c 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj +++ b/Builds/VisualStudio2015/RippleD.vcxproj @@ -4899,6 +4899,10 @@ True True + + True + True + True True diff --git a/Builds/VisualStudio2015/RippleD.vcxproj.filters b/Builds/VisualStudio2015/RippleD.vcxproj.filters index 695674530..3113a973c 100644 --- a/Builds/VisualStudio2015/RippleD.vcxproj.filters +++ b/Builds/VisualStudio2015/RippleD.vcxproj.filters @@ -5574,6 +5574,9 @@ test\rpc + + test\rpc + test\server diff --git a/src/ripple/rpc/handlers/TransactionEntry.cpp b/src/ripple/rpc/handlers/TransactionEntry.cpp index 13ea07e20..8987ab096 100644 --- a/src/ripple/rpc/handlers/TransactionEntry.cpp +++ b/src/ripple/rpc/handlers/TransactionEntry.cpp @@ -35,18 +35,17 @@ namespace ripple { // means any ledger. Json::Value doTransactionEntry (RPC::Context& context) { - std::shared_ptr lpLedger; + std::shared_ptr lpLedger; Json::Value jvResult = RPC::lookupLedger (lpLedger, context); - if (!lpLedger) + if(! lpLedger) return jvResult; - if (!context.params.isMember (jss::tx_hash)) + if(! context.params.isMember (jss::tx_hash)) { - jvResult[jss::error] = "fieldNotFoundTransaction"; + jvResult[jss::error] = "fieldNotFoundTransaction"; } - else if (!context.params.isMember (jss::ledger_hash) - && !context.params.isMember (jss::ledger_index)) + else if(jvResult.get(jss::ledger_hash, Json::nullValue).isNull()) { // We don't work on ledger current. @@ -60,28 +59,19 @@ Json::Value doTransactionEntry (RPC::Context& context) // routine, returning success or failure. uTransID.SetHex (context.params[jss::tx_hash].asString ()); - if (!lpLedger) + auto tx = lpLedger->txRead (uTransID); + if(! tx.first) { - jvResult[jss::error] = "ledgerNotFound"; + jvResult[jss::error] = "transactionNotFound"; } else { - TxMeta::pointer tmTrans; - - auto tx = lpLedger->txRead (uTransID); - if (!tx.first) - { - jvResult[jss::error] = "transactionNotFound"; - } - else - { - jvResult[jss::tx_json] = tx.first->getJson (0); - if (tx.second) - jvResult[jss::metadata] = tx.second->getJson (0); - // 'accounts' - // 'engine_...' - // 'ledger_...' - } + jvResult[jss::tx_json] = tx.first->getJson (0); + if (tx.second) + jvResult[jss::metadata] = tx.second->getJson (0); + // 'accounts' + // 'engine_...' + // 'ledger_...' } } diff --git a/src/test/rpc/TransactionEntry_test.cpp b/src/test/rpc/TransactionEntry_test.cpp new file mode 100644 index 000000000..0f3d49176 --- /dev/null +++ b/src/test/rpc/TransactionEntry_test.cpp @@ -0,0 +1,165 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2012-2017 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 +#include +#include + +namespace ripple { + +class TransactionEntry_test : public beast::unit_test::suite +{ + void + testBadInput() + { + testcase("Invalid request params"); + using namespace test::jtx; + Env env {*this}; + + { + //no params + auto const result = env.client() + .invoke("transaction_entry", {})[jss::result]; + BEAST_EXPECT(result[jss::error] == "fieldNotFoundTransaction"); + BEAST_EXPECT(result[jss::status] == "error"); + } + + { + Json::Value params {Json::objectValue}; + params[jss::ledger] = 20; + auto const result = env.client() + .invoke("transaction_entry", params)[jss::result]; + BEAST_EXPECT(result[jss::error] == "lgrNotFound"); + BEAST_EXPECT(result[jss::status] == "error"); + } + + { + Json::Value params {Json::objectValue}; + params[jss::ledger] = "current"; + params[jss::tx_hash] = "DEADBEEF"; + auto const result = env.client() + .invoke("transaction_entry", params)[jss::result]; + BEAST_EXPECT(result[jss::error] == "notYetImplemented"); + BEAST_EXPECT(result[jss::status] == "error"); + } + + { + Json::Value params {Json::objectValue}; + params[jss::ledger] = "closed"; + params[jss::tx_hash] = "DEADBEEF"; + auto const result = env.client() + .invoke("transaction_entry", params)[jss::result]; + BEAST_EXPECT(! result[jss::ledger_hash].asString().empty()); + BEAST_EXPECT(result[jss::error] == "transactionNotFound"); + BEAST_EXPECT(result[jss::status] == "error"); + } + } + + void testRequest() + { + testcase("Basic request"); + using namespace test::jtx; + Env env {*this}; + + auto check_tx = [this, &env] + (int index, std::string txhash, std::string type = "") + { + Json::Value resIndex, resHash; + // first request using ledger_index to lookup + { + Json::Value params {Json::objectValue}; + params[jss::ledger_index] = index; + params[jss::tx_hash] = txhash; + resIndex = env.client() + .invoke("transaction_entry", params)[jss::result]; + if(! BEAST_EXPECTS(resIndex.isMember(jss::tx_json), txhash)) + return; + BEAST_EXPECT(resIndex[jss::tx_json][jss::hash] == txhash); + if(! type.empty()) + BEAST_EXPECTS( + resIndex[jss::tx_json][jss::TransactionType] == type, + txhash + " is " + + resIndex[jss::tx_json][jss::TransactionType].asString()); + } + + // second request using ledger_hash to lookup and verify + // both responses match + { + Json::Value params {Json::objectValue}; + params[jss::ledger_hash] = resIndex[jss::ledger_hash]; + params[jss::tx_hash] = txhash; + resHash = env.client() + .invoke("transaction_entry", params)[jss::result]; + BEAST_EXPECT(resHash == resIndex); + } + }; + + Account A1 {"A1"}; + Account A2 {"A2"}; + + env.fund(XRP(10000), A1); + auto fund_1_tx = + boost::lexical_cast(env.tx()->getTransactionID()); + + env.fund(XRP(10000), A2); + auto fund_2_tx = + boost::lexical_cast(env.tx()->getTransactionID()); + + env.close(); + + // these are actually AccountSet txs because fund does two txs and + // env.tx only reports the last one + check_tx(env.closed()->seq(), fund_1_tx); + check_tx(env.closed()->seq(), fund_2_tx); + + env.trust(A2["USD"](1000), A1); + // the trust tx is actually a payment since the trust method + // refunds fees with a payment after TrustSet..so just ignore the type + // in the check below + auto trust_tx = + boost::lexical_cast(env.tx()->getTransactionID()); + + env(pay(A2, A1, A2["USD"](5))); + auto pay_tx = + boost::lexical_cast(env.tx()->getTransactionID()); + env.close(); + + check_tx(env.closed()->seq(), trust_tx); + check_tx(env.closed()->seq(), pay_tx, "Payment"); + + env(offer(A2, XRP(100), A2["USD"](1))); + auto offer_tx = + boost::lexical_cast(env.tx()->getTransactionID()); + + env.close(); + + check_tx(env.closed()->seq(), offer_tx, "OfferCreate"); + } + +public: + void run () + { + testBadInput(); + testRequest(); + } +}; + +BEAST_DEFINE_TESTSUITE (TransactionEntry, rpc, ripple); + +} // ripple diff --git a/src/test/unity/rpc_test_unity.cpp b/src/test/unity/rpc_test_unity.cpp index 17c9c61d2..2ab89c9d1 100644 --- a/src/test/unity/rpc_test_unity.cpp +++ b/src/test/unity/rpc_test_unity.cpp @@ -38,3 +38,4 @@ #include #include #include +#include