Unify JSON serialization format of transactions (#4775)

* Remove include <ranges>

* Formatting fix

* Output for subscriptions

* Output from sign, submit etc.

* Output from ledger

* Output from account_tx

* Output from transaction_entry

* Output from tx

* Store close_time_iso in API v2 output

* Add small APIv2 unit test for subscribe

* Add unit test for transaction_entry

* Add unit test for tx

* Remove inLedger from API version 2

* Set ledger_hash and ledger_index

* Move isValidated from RPCHelpers to LedgerMaster

* Store closeTime in LedgerFill

* Time formatting fix

* additional tests for Subscribe unit tests

* Improved comments

* Rename mInLedger to mLedgerIndex

* Minor fixes

* Set ledger_hash on closed ledger, even if not validated

* Update API-CHANGELOG.md

* Add ledger_hash, ledger_index to transaction_entry

* Fix validated and close_time_iso in account_tx

* Fix typos

* Improve getJson for Transaction and STTx

* Minor improvements

* Replace class enum JsonOptions with struct

We may consider turning this into a general-purpose template and using it elsewhere

* simplify the extraction of transactionID from Transaction object

* Remove obsolete comments

* Unconditionally set validated in account_tx output

* Minor improvements

* Minor fixes

---------

Co-authored-by: Chenna Keshava <ckeshavabs@gmail.com>
This commit is contained in:
Bronek Kozicki
2023-11-08 18:36:24 +00:00
committed by GitHub
parent 09e0f103f4
commit 32ced493de
33 changed files with 763 additions and 234 deletions

View File

@@ -120,22 +120,56 @@ class AccountTx_test : public beast::unit_test::suite
// All other ledgers have no txs
auto hasTxs = [apiVersion](Json::Value const& j) {
return j.isMember(jss::result) &&
(j[jss::result][jss::status] == "success") &&
(j[jss::result][jss::transactions].size() == 2) &&
(j[jss::result][jss::transactions][0u][jss::tx]
[jss::TransactionType] == jss::AccountSet) &&
(j[jss::result][jss::transactions][1u][jss::tx]
[jss::TransactionType] == jss::Payment) &&
(j[jss::result][jss::transactions][1u][jss::tx]
[jss::DeliverMax] == "10000000010") &&
((apiVersion > 1 &&
!j[jss::result][jss::transactions][1u][jss::tx].isMember(
jss::Amount)) ||
(apiVersion <= 1 &&
j[jss::result][jss::transactions][1u][jss::tx][jss::Amount] ==
j[jss::result][jss::transactions][1u][jss::tx]
[jss::DeliverMax]));
switch (apiVersion)
{
case 1:
return j.isMember(jss::result) &&
(j[jss::result][jss::status] == "success") &&
(j[jss::result][jss::transactions].size() == 2) &&
(j[jss::result][jss::transactions][0u][jss::tx]
[jss::TransactionType] == jss::AccountSet) &&
(j[jss::result][jss::transactions][1u][jss::tx]
[jss::TransactionType] == jss::Payment) &&
(j[jss::result][jss::transactions][1u][jss::tx]
[jss::DeliverMax] == "10000000010") &&
(j[jss::result][jss::transactions][1u][jss::tx]
[jss::Amount] ==
j[jss::result][jss::transactions][1u][jss::tx]
[jss::DeliverMax]);
case 2:
if (j.isMember(jss::result) &&
(j[jss::result][jss::status] == "success") &&
(j[jss::result][jss::transactions].size() == 2) &&
(j[jss::result][jss::transactions][0u][jss::tx_json]
[jss::TransactionType] == jss::AccountSet))
{
auto const& payment =
j[jss::result][jss::transactions][1u];
return (payment.isMember(jss::tx_json)) &&
(payment[jss::tx_json][jss::TransactionType] ==
jss::Payment) &&
(payment[jss::tx_json][jss::DeliverMax] ==
"10000000010") &&
(!payment[jss::tx_json].isMember(jss::Amount)) &&
(!payment[jss::tx_json].isMember(jss::hash)) &&
(payment[jss::hash] ==
"9F3085D85F472D1CC29627F260DF68EDE59D42D1D0C33E345"
"ECF0D4CE981D0A8") &&
(payment[jss::validated] == true) &&
(payment[jss::ledger_index] == 3) &&
(payment[jss::ledger_hash] ==
"5476DCD816EA04CBBA57D47BBF1FC58A5217CC93A5ADD79CB"
"580A5AFDD727E33") &&
(payment[jss::close_time_iso] ==
"2000-01-01T00:00:10Z");
}
else
return false;
default:
return false;
}
};
auto noTxs = [](Json::Value const& j) {

View File

@@ -2536,17 +2536,19 @@ public:
// A list of all the functions we want to test.
using signFunc = Json::Value (*)(
Json::Value params,
unsigned int apiVersion,
NetworkOPs::FailHard failType,
Role role,
std::chrono::seconds validatedLedgerAge,
Application & app);
Application& app);
using submitFunc = Json::Value (*)(
Json::Value params,
unsigned int apiVersion,
NetworkOPs::FailHard failType,
Role role,
std::chrono::seconds validatedLedgerAge,
Application & app,
Application& app,
ProcessTransactionFn const& processTransaction,
RPC::SubmitSync sync);
@@ -2586,6 +2588,7 @@ public:
assert(get<1>(testFunc) == nullptr);
result = signFn(
req,
1,
NetworkOPs::FailHard::yes,
testRole,
1s,
@@ -2597,6 +2600,7 @@ public:
assert(submitFn != nullptr);
result = submitFn(
req,
1,
NetworkOPs::FailHard::yes,
testRole,
1s,

View File

@@ -20,6 +20,7 @@
#include <ripple/app/misc/NetworkOPs.h>
#include <ripple/beast/unit_test.h>
#include <ripple/core/ConfigSections.h>
#include <ripple/json/json_value.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/jss.h>
#include <test/jtx.h>
@@ -163,7 +164,7 @@ public:
}
void
testTransactions()
testTransactions_APIv1()
{
using namespace std::chrono_literals;
using namespace jtx;
@@ -307,6 +308,85 @@ public:
BEAST_EXPECT(jv[jss::status] == "success");
}
void
testTransactions_APIv2()
{
testcase("transactions API version 2");
using namespace std::chrono_literals;
using namespace jtx;
Env env(*this);
auto wsc = makeWSClient(env.app().config());
Json::Value stream{Json::objectValue};
{
// RPC subscribe to transactions stream
stream[jss::api_version] = 2;
stream[jss::streams] = Json::arrayValue;
stream[jss::streams].append("transactions");
auto jv = wsc->invoke("subscribe", stream);
if (wsc->version() == 2)
{
BEAST_EXPECT(
jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(
jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::status] == "success");
}
{
env.fund(XRP(10000), "alice");
env.close();
// Check stream update for payment transaction
BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
return jv[jss::meta]["AffectedNodes"][1u]["CreatedNode"]
["NewFields"][jss::Account] //
== Account("alice").human() &&
jv[jss::close_time_iso] //
== "2000-01-01T00:00:10Z" &&
jv[jss::validated] == true && //
jv[jss::ledger_hash] ==
"0F1A9E0C109ADEF6DA2BDE19217C12BBEC57174CBDBD212B0EBDC1CEDB"
"853185" && //
!jv[jss::inLedger] &&
jv[jss::ledger_index] == 3 && //
jv[jss::tx_json][jss::TransactionType] //
== jss::Payment &&
jv[jss::tx_json][jss::DeliverMax] //
== "10000000010" &&
!jv[jss::tx_json].isMember(jss::Amount) &&
jv[jss::tx_json][jss::Fee] //
== "10" &&
jv[jss::tx_json][jss::Sequence] //
== 1;
}));
// Check stream update for accountset transaction
BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) {
return jv[jss::meta]["AffectedNodes"][0u]["ModifiedNode"]
["FinalFields"][jss::Account] ==
Account("alice").human();
}));
}
{
// RPC unsubscribe
auto jv = wsc->invoke("unsubscribe", stream);
if (wsc->version() == 2)
{
BEAST_EXPECT(
jv.isMember(jss::jsonrpc) && jv[jss::jsonrpc] == "2.0");
BEAST_EXPECT(
jv.isMember(jss::ripplerpc) && jv[jss::ripplerpc] == "2.0");
BEAST_EXPECT(jv.isMember(jss::id) && jv[jss::id] == 5);
}
BEAST_EXPECT(jv[jss::status] == "success");
}
}
void
testManifests()
{
@@ -1222,7 +1302,8 @@ public:
testServer();
testLedger();
testTransactions();
testTransactions_APIv1();
testTransactions_APIv2();
testManifests();
testValidations(all - xrpFees);
testValidations(all);

View File

@@ -20,9 +20,12 @@
#include <ripple/json/json_reader.h>
#include <ripple/json/json_value.h>
#include <ripple/protocol/jss.h>
#include <ripple/rpc/impl/RPCHelpers.h>
#include <test/jtx.h>
#include <test/jtx/Env.h>
#include <functional>
namespace ripple {
class TransactionEntry_test : public beast::unit_test::suite
@@ -143,29 +146,51 @@ class TransactionEntry_test : public beast::unit_test::suite
}
void
testRequest()
testRequest(unsigned apiVersion)
{
testcase("Basic request");
testcase("Basic request API version " + std::to_string(apiVersion));
using namespace test::jtx;
Env env{*this};
auto check_tx = [this, &env](
auto check_tx = [this, &env, apiVersion](
int index,
std::string const txhash,
std::string const expected_json = "") {
std::string const expected_json = "",
std::string const expected_ledger_hash = "",
std::string const close_time_iso = "") {
// first request using ledger_index to lookup
Json::Value const resIndex{[&env, index, &txhash]() {
Json::Value const resIndex{[&env, index, &txhash, apiVersion]() {
Json::Value params{Json::objectValue};
params[jss::ledger_index] = index;
params[jss::tx_hash] = txhash;
params[jss::api_version] = apiVersion;
return env.client().invoke(
"transaction_entry", params)[jss::result];
}()};
if (!BEAST_EXPECTS(resIndex.isMember(jss::tx_json), txhash))
if (!BEAST_EXPECT(resIndex.isMember(jss::tx_json)))
return;
BEAST_EXPECT(resIndex[jss::tx_json][jss::hash] == txhash);
BEAST_EXPECT(resIndex[jss::validated] == true);
BEAST_EXPECT(resIndex[jss::ledger_index] == index);
BEAST_EXPECT(resIndex[jss::ledger_hash] == expected_ledger_hash);
if (apiVersion > 1)
{
BEAST_EXPECT(resIndex[jss::hash] == txhash);
BEAST_EXPECT(!resIndex[jss::tx_json].isMember(jss::hash));
BEAST_EXPECT(!resIndex[jss::tx_json].isMember(jss::Amount));
if (BEAST_EXPECT(!close_time_iso.empty()))
BEAST_EXPECT(
resIndex[jss::close_time_iso] == close_time_iso);
}
else
{
BEAST_EXPECT(resIndex[jss::tx_json][jss::hash] == txhash);
BEAST_EXPECT(!resIndex.isMember(jss::hash));
BEAST_EXPECT(!resIndex.isMember(jss::close_time_iso));
}
if (!expected_json.empty())
{
Json::Value expected;
@@ -198,12 +223,14 @@ class TransactionEntry_test : public beast::unit_test::suite
Json::Value params{Json::objectValue};
params[jss::ledger_hash] = resIndex[jss::ledger_hash];
params[jss::tx_hash] = txhash;
params[jss::api_version] = apiVersion;
Json::Value const resHash = env.client().invoke(
"transaction_entry", params)[jss::result];
BEAST_EXPECT(resHash == resIndex);
}
// Use the command line form with the index.
if (apiVersion == RPC::apiMaximumSupportedVersion)
{
Json::Value const clIndex{env.rpc(
"transaction_entry", txhash, std::to_string(index))};
@@ -211,6 +238,7 @@ class TransactionEntry_test : public beast::unit_test::suite
}
// Use the command line form with the ledger_hash.
if (apiVersion == RPC::apiMaximumSupportedVersion)
{
Json::Value const clHash{env.rpc(
"transaction_entry",
@@ -226,39 +254,49 @@ class TransactionEntry_test : public beast::unit_test::suite
env.fund(XRP(10000), A1);
auto fund_1_tx =
boost::lexical_cast<std::string>(env.tx()->getTransactionID());
BEAST_EXPECT(
fund_1_tx ==
"F4E9DF90D829A9E8B423FF68C34413E240D8D8BB0EFD080DF08114ED398E2506");
env.fund(XRP(10000), A2);
auto fund_2_tx =
boost::lexical_cast<std::string>(env.tx()->getTransactionID());
BEAST_EXPECT(
fund_2_tx ==
"6853CD8226A05068C951CB1F54889FF4E40C5B440DC1C5BA38F114C4E0B1E705");
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, R"(
{
"Account" : "r4nmQNH4Fhjfh6cHDbvVSsBv7KySbj4cBf",
"Fee" : "10",
"Sequence" : 3,
"SetFlag" : 8,
"SigningPubKey" : "0324CAAFA2212D2AEAB9D42D481535614AED486293E1FB1380FF070C3DD7FB4264",
"TransactionType" : "AccountSet",
"TxnSignature" : "3044022007B35E3B99460534FF6BC3A66FBBA03591C355CC38E38588968E87CCD01BE229022071A443026DE45041B55ABB1CC76812A87EA701E475BBB7E165513B4B242D3474",
"hash" : "F4E9DF90D829A9E8B423FF68C34413E240D8D8BB0EFD080DF08114ED398E2506"
}
)");
check_tx(env.closed()->seq(), fund_2_tx, R"(
{
"Account" : "rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD",
"Fee" : "10",
"Sequence" : 3,
"SetFlag" : 8,
"SigningPubKey" : "03CFF28E067A2CCE6CC5A598C0B845CBD3F30A7863BE9C0DD55F4960EFABCCF4D0",
"TransactionType" : "AccountSet",
"TxnSignature" : "3045022100C8857FC0759A2AC0D2F320684691A66EAD252EAED9EF88C79791BC58BFCC9D860220421722286487DD0ED6BBA626CE6FCBDD14289F7F4726870C3465A4054C2702D7",
"hash" : "6853CD8226A05068C951CB1F54889FF4E40C5B440DC1C5BA38F114C4E0B1E705"
}
)");
check_tx(
env.closed()->seq(),
fund_1_tx,
R"({
"Account" : "r4nmQNH4Fhjfh6cHDbvVSsBv7KySbj4cBf",
"Fee" : "10",
"Sequence" : 3,
"SetFlag" : 8,
"SigningPubKey" : "0324CAAFA2212D2AEAB9D42D481535614AED486293E1FB1380FF070C3DD7FB4264",
"TransactionType" : "AccountSet",
"TxnSignature" : "3044022007B35E3B99460534FF6BC3A66FBBA03591C355CC38E38588968E87CCD01BE229022071A443026DE45041B55ABB1CC76812A87EA701E475BBB7E165513B4B242D3474",
})",
"ADB727BCC74B29421BB01B847740B179B8A0ED3248D76A89ED2E39B02C427784",
"2000-01-01T00:00:10Z");
check_tx(
env.closed()->seq(),
fund_2_tx,
R"({
"Account" : "rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD",
"Fee" : "10",
"Sequence" : 3,
"SetFlag" : 8,
"SigningPubKey" : "03CFF28E067A2CCE6CC5A598C0B845CBD3F30A7863BE9C0DD55F4960EFABCCF4D0",
"TransactionType" : "AccountSet",
"TxnSignature" : "3045022100C8857FC0759A2AC0D2F320684691A66EAD252EAED9EF88C79791BC58BFCC9D860220421722286487DD0ED6BBA626CE6FCBDD14289F7F4726870C3465A4054C2702D7",
})",
"ADB727BCC74B29421BB01B847740B179B8A0ED3248D76A89ED2E39B02C427784",
"2000-01-01T00:00:10Z");
env.trust(A2["USD"](1000), A1);
// the trust tx is actually a payment since the trust method
@@ -266,76 +304,85 @@ class TransactionEntry_test : public beast::unit_test::suite
// in the check below
auto trust_tx =
boost::lexical_cast<std::string>(env.tx()->getTransactionID());
BEAST_EXPECT(
trust_tx ==
"C992D97D88FF444A1AB0C06B27557EC54B7F7DA28254778E60238BEA88E0C101");
env(pay(A2, A1, A2["USD"](5)));
auto pay_tx =
boost::lexical_cast<std::string>(env.tx()->getTransactionID());
env.close();
BEAST_EXPECT(
pay_tx ==
"988046D484ACE9F5F6A8C792D89C6EA2DB307B5DDA9864AEBA88E6782ABD0865");
check_tx(env.closed()->seq(), trust_tx, R"(
{
"Account" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"DeliverMax" : "10",
"Destination" : "r4nmQNH4Fhjfh6cHDbvVSsBv7KySbj4cBf",
"Fee" : "10",
"Flags" : 2147483648,
"Sequence" : 3,
"SigningPubKey" : "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020",
"TransactionType" : "Payment",
"TxnSignature" : "3044022033D9EBF7F02950AF2F6B13C07AEE641C8FEBDD540A338FCB9027A965A4AED35B02206E4E227DCC226A3456C0FEF953449D21645A24EB63CA0BB7C5B62470147FD1D1",
"hash" : "C992D97D88FF444A1AB0C06B27557EC54B7F7DA28254778E60238BEA88E0C101"
}
)");
check_tx(
env.closed()->seq(),
trust_tx,
R"({
"Account" : "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"DeliverMax" : "10",
"Destination" : "r4nmQNH4Fhjfh6cHDbvVSsBv7KySbj4cBf",
"Fee" : "10",
"Flags" : 2147483648,
"Sequence" : 3,
"SigningPubKey" : "0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020",
"TransactionType" : "Payment",
"TxnSignature" : "3044022033D9EBF7F02950AF2F6B13C07AEE641C8FEBDD540A338FCB9027A965A4AED35B02206E4E227DCC226A3456C0FEF953449D21645A24EB63CA0BB7C5B62470147FD1D1",
})",
"39AA166131D56622EFD96CB4B2BD58003ACD37091C90977FF6B81419DB451775",
"2000-01-01T00:00:20Z");
check_tx(
env.closed()->seq(),
pay_tx,
R"(
{
"Account" : "rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD",
"DeliverMax" :
{
"currency" : "USD",
"issuer" : "rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD",
"value" : "5"
},
"Destination" : "r4nmQNH4Fhjfh6cHDbvVSsBv7KySbj4cBf",
"Fee" : "10",
"Flags" : 2147483648,
"Sequence" : 4,
"SigningPubKey" : "03CFF28E067A2CCE6CC5A598C0B845CBD3F30A7863BE9C0DD55F4960EFABCCF4D0",
"TransactionType" : "Payment",
"TxnSignature" : "30450221008A722B7F16EDB2348886E88ED4EC682AE9973CC1EE0FF37C93BB2CEC821D3EDF022059E464472031BA5E0D88A93E944B6A8B8DB3E1D5E5D1399A805F615789DB0BED",
"hash" : "988046D484ACE9F5F6A8C792D89C6EA2DB307B5DDA9864AEBA88E6782ABD0865"
}
)");
R"({
"Account" : "rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD",
"DeliverMax" :
{
"currency" : "USD",
"issuer" : "rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD",
"value" : "5"
},
"Destination" : "r4nmQNH4Fhjfh6cHDbvVSsBv7KySbj4cBf",
"Fee" : "10",
"Flags" : 2147483648,
"Sequence" : 4,
"SigningPubKey" : "03CFF28E067A2CCE6CC5A598C0B845CBD3F30A7863BE9C0DD55F4960EFABCCF4D0",
"TransactionType" : "Payment",
"TxnSignature" : "30450221008A722B7F16EDB2348886E88ED4EC682AE9973CC1EE0FF37C93BB2CEC821D3EDF022059E464472031BA5E0D88A93E944B6A8B8DB3E1D5E5D1399A805F615789DB0BED",
})",
"39AA166131D56622EFD96CB4B2BD58003ACD37091C90977FF6B81419DB451775",
"2000-01-01T00:00:20Z");
env(offer(A2, XRP(100), A2["USD"](1)));
auto offer_tx =
boost::lexical_cast<std::string>(env.tx()->getTransactionID());
BEAST_EXPECT(
offer_tx ==
"5FCC1A27A7664F82A0CC4BE5766FBBB7C560D52B93AA7B550CD33B27AEC7EFFB");
env.close();
check_tx(
env.closed()->seq(),
offer_tx,
R"(
{
"Account" : "rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD",
"Fee" : "10",
"Sequence" : 5,
"SigningPubKey" : "03CFF28E067A2CCE6CC5A598C0B845CBD3F30A7863BE9C0DD55F4960EFABCCF4D0",
"TakerGets" :
{
"currency" : "USD",
"issuer" : "rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD",
"value" : "1"
},
"TakerPays" : "100000000",
"TransactionType" : "OfferCreate",
"TxnSignature" : "304502210093FC93ACB77B4E3DE3315441BD010096734859080C1797AB735EB47EBD541BD102205020BB1A7C3B4141279EE4C287C13671E2450EA78914EFD0C6DB2A18344CD4F2",
"hash" : "5FCC1A27A7664F82A0CC4BE5766FBBB7C560D52B93AA7B550CD33B27AEC7EFFB"
}
)");
R"({
"Account" : "rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD",
"Fee" : "10",
"Sequence" : 5,
"SigningPubKey" : "03CFF28E067A2CCE6CC5A598C0B845CBD3F30A7863BE9C0DD55F4960EFABCCF4D0",
"TakerGets" :
{
"currency" : "USD",
"issuer" : "rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD",
"value" : "1"
},
"TakerPays" : "100000000",
"TransactionType" : "OfferCreate",
"TxnSignature" : "304502210093FC93ACB77B4E3DE3315441BD010096734859080C1797AB735EB47EBD541BD102205020BB1A7C3B4141279EE4C287C13671E2450EA78914EFD0C6DB2A18344CD4F2",
})",
"0589B876DF5AFE335781E8FC12C2EC62A80151DF13BBAFE9EB2DA62E798ED434",
"2000-01-01T00:00:30Z");
}
public:
@@ -343,7 +390,8 @@ public:
run() override
{
testBadInput();
testRequest();
test::jtx::forAllApiVersions(
std::bind_front(&TransactionEntry_test::testRequest, this));
}
};

View File

@@ -694,15 +694,13 @@ class Transaction_test : public beast::unit_test::suite
}
void
testRequest(FeatureBitset features)
testRequest(FeatureBitset features, unsigned apiVersion)
{
testcase("Test Request");
testcase("Test Request API version " + std::to_string(apiVersion));
using namespace test::jtx;
using std::to_string;
const char* COMMAND = jss::tx.c_str();
Env env{*this};
Account const alice{"alice"};
Account const alie{"alie"};
@@ -725,18 +723,47 @@ class Transaction_test : public beast::unit_test::suite
Json::Value expected = txn->getJson(JsonOptions::none);
expected[jss::DeliverMax] = expected[jss::Amount];
if (apiVersion > 1)
{
expected.removeMember(jss::hash);
expected.removeMember(jss::Amount);
}
Json::Value const result = {[&env, txn, apiVersion]() {
Json::Value params{Json::objectValue};
params[jss::transaction] = to_string(txn->getTransactionID());
params[jss::binary] = false;
params[jss::api_version] = apiVersion;
return env.client().invoke("tx", params);
}()};
auto const result =
env.rpc(COMMAND, to_string(txn->getTransactionID()));
BEAST_EXPECT(result[jss::result][jss::status] == jss::success);
if (apiVersion > 1)
{
BEAST_EXPECT(
result[jss::result][jss::close_time_iso] ==
"2000-01-01T00:00:20Z");
BEAST_EXPECT(
result[jss::result][jss::hash] ==
to_string(txn->getTransactionID()));
BEAST_EXPECT(result[jss::result][jss::validated] == true);
BEAST_EXPECT(result[jss::result][jss::ledger_index] == 4);
BEAST_EXPECT(
result[jss::result][jss::ledger_hash] ==
"B41882E20F0EC6228417D28B9AE0F33833645D35F6799DFB782AC97FC4BB51"
"D2");
}
for (auto memberIt = expected.begin(); memberIt != expected.end();
memberIt++)
{
std::string const name = memberIt.memberName();
if (BEAST_EXPECT(result[jss::result].isMember(name)))
auto const& result_transaction =
(apiVersion > 1 ? result[jss::result][jss::tx_json]
: result[jss::result]);
if (BEAST_EXPECT(result_transaction.isMember(name)))
{
auto const received = result[jss::result][name];
auto const received = result_transaction[name];
BEAST_EXPECTS(
received == *memberIt,
"Transaction contains \n\"" + name + "\": " //
@@ -763,7 +790,8 @@ public:
testRangeCTIDRequest(features);
testCTIDValidation(features);
testCTIDRPC(features);
testRequest(features);
test::jtx::forAllApiVersions(
std::bind_front(&Transaction_test::testRequest, this, features));
}
};