gRPC support for account_tx and tx

- Add support for all transaction types and ledger object types to gRPC
  implementation of tx and account_tx.

- Create common handlers for tx and account_tx.

- Remove mutex and abort() from gRPC server. JobQueue is stopped before
  gRPC server, with all coroutines executed to completion, so no need for
  synchronization.
This commit is contained in:
CJ Cobb
2020-02-04 12:31:17 -08:00
committed by Mike Ellery
parent acf4b78892
commit e7ce3909d2
57 changed files with 7498 additions and 2106 deletions

View File

@@ -324,8 +324,8 @@ public:
class GetAccountInfoClient : public GRPCTestClientBase
{
public:
rpc::v1::GetAccountInfoRequest request;
rpc::v1::GetAccountInfoResponse reply;
org::xrpl::rpc::v1::GetAccountInfoRequest request;
org::xrpl::rpc::v1::GetAccountInfoResponse reply;
explicit GetAccountInfoClient(std::string const& port)
: GRPCTestClientBase(port)
@@ -358,11 +358,10 @@ public:
client.GetAccountInfo();
if (!BEAST_EXPECT(client.status.ok()))
{
std::cout << client.reply.DebugString() << std::endl;
return;
}
BEAST_EXPECT(
client.reply.account_data().account().address() ==
client.reply.account_data().account().value().address() ==
alice.human());
}
{
@@ -374,13 +373,13 @@ public:
if (!BEAST_EXPECT(client.status.ok()))
return;
BEAST_EXPECT(
client.reply.account_data().balance().drops() ==
client.reply.account_data().balance().value().xrp_amount().drops() ==
1000 * 1000 * 1000);
BEAST_EXPECT(
client.reply.account_data().account().address() ==
client.reply.account_data().account().value().address() ==
alice.human());
BEAST_EXPECT(
client.reply.account_data().sequence() == env.seq(alice));
client.reply.account_data().sequence().value() == env.seq(alice));
BEAST_EXPECT(client.reply.queue_data().txn_count() == 0);
}
}
@@ -473,7 +472,7 @@ public:
{
return;
}
BEAST_EXPECT(client.reply.account_data().owner_count() == 1);
BEAST_EXPECT(client.reply.account_data().owner_count().value() == 1);
BEAST_EXPECT(client.reply.signer_list().signer_entries_size() == 1);
}
@@ -518,16 +517,16 @@ public:
{
return;
}
BEAST_EXPECT(client.reply.account_data().owner_count() == 1);
BEAST_EXPECT(client.reply.account_data().owner_count().value() == 1);
auto& signerList = client.reply.signer_list();
BEAST_EXPECT(signerList.signer_quorum() == 4);
BEAST_EXPECT(signerList.signer_quorum().value() == 4);
BEAST_EXPECT(signerList.signer_entries_size() == 8);
for (int i = 0; i < 8; ++i)
{
BEAST_EXPECT(signerList.signer_entries(i).signer_weight() == 1);
BEAST_EXPECT(signerList.signer_entries(i).signer_weight().value() == 1);
BEAST_EXPECT(
accounts.erase(
signerList.signer_entries(i).account().address()) == 1);
signerList.signer_entries(i).account().value().address()) == 1);
}
BEAST_EXPECT(accounts.size() == 0);
}

View File

@@ -23,13 +23,13 @@
#include <ripple/core/DatabaseCon.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/jss.h>
#include <ripple/rpc/GRPCHandlers.h>
#include <ripple/rpc/impl/RPCHelpers.h>
#include <test/rpc/GRPCTestClientBase.h>
#include <test/jtx.h>
#include <test/jtx/Env.h>
#include <test/jtx/envconfig.h>
#include <ripple/rpc/GRPCHandlers.h>
#include <test/rpc/GRPCTestClientBase.h>
namespace ripple {
namespace test {
@@ -39,8 +39,8 @@ class Fee_test : public beast::unit_test::suite
class GrpcFeeClient : public GRPCTestClientBase
{
public:
rpc::v1::GetFeeRequest request;
rpc::v1::GetFeeResponse reply;
org::xrpl::rpc::v1::GetFeeRequest request;
org::xrpl::rpc::v1::GetFeeResponse reply;
explicit GrpcFeeClient(std::string const& grpcPort)
: GRPCTestClientBase(grpcPort)
@@ -54,12 +54,12 @@ class Fee_test : public beast::unit_test::suite
}
};
std::pair<bool, rpc::v1::GetFeeResponse>
std::pair<bool, org::xrpl::rpc::v1::GetFeeResponse>
grpcGetFee(std::string const& grpcPort)
{
GrpcFeeClient client(grpcPort);
client.GetFee();
return std::pair<bool, rpc::v1::GetFeeResponse>(
return std::pair<bool, org::xrpl::rpc::v1::GetFeeResponse>(
client.status.ok(), client.reply);
}
@@ -104,29 +104,29 @@ class Fee_test : public beast::unit_test::suite
BEAST_EXPECT(reply.max_queue_size() == *metrics.txQMaxSize);
// fee levels data
rpc::v1::FeeLevels& levels = *reply.mutable_levels();
org::xrpl::rpc::v1::FeeLevels& levels = *reply.mutable_levels();
BEAST_EXPECT(levels.median_level() == metrics.medFeeLevel);
BEAST_EXPECT(levels.minimum_level() == metrics.minProcessingFeeLevel);
BEAST_EXPECT(levels.open_ledger_level() == metrics.openLedgerFeeLevel);
BEAST_EXPECT(levels.reference_level() == metrics.referenceFeeLevel);
// fee data
rpc::v1::Fee& drops = *reply.mutable_drops();
org::xrpl::rpc::v1::Fee& fee = *reply.mutable_fee();
auto const baseFee = view->fees().base;
BEAST_EXPECT(
drops.base_fee().drops() ==
fee.base_fee().drops() ==
toDrops(metrics.referenceFeeLevel, baseFee).second);
BEAST_EXPECT(
drops.minimum_fee().drops() ==
fee.minimum_fee().drops() ==
toDrops(metrics.minProcessingFeeLevel, baseFee).second);
BEAST_EXPECT(
drops.median_fee().drops() ==
fee.median_fee().drops() ==
toDrops(metrics.medFeeLevel, baseFee).second);
auto openLedgerFee =
toDrops(metrics.openLedgerFeeLevel - FeeLevel64{1}, baseFee)
.second +
1;
BEAST_EXPECT(drops.open_ledger_fee().drops() == openLedgerFee.drops());
BEAST_EXPECT(fee.open_ledger_fee().drops() == openLedgerFee.drops());
}
public:

View File

@@ -20,7 +20,7 @@
#ifndef RIPPLED_GRPCTESTCLIENTBASE_H
#define RIPPLED_GRPCTESTCLIENTBASE_H
#include <rpc/v1/xrp_ledger.grpc.pb.h>
#include <org/xrpl/rpc/v1/xrp_ledger.grpc.pb.h>
#include <test/jtx/envconfig.h>
namespace ripple {
@@ -29,7 +29,7 @@ namespace test {
struct GRPCTestClientBase
{
explicit GRPCTestClientBase(std::string const& port)
: stub_(rpc::v1::XRPLedgerAPIService::NewStub(grpc::CreateChannel(
: stub_(org::xrpl::rpc::v1::XRPLedgerAPIService::NewStub(grpc::CreateChannel(
beast::IP::Endpoint(
boost::asio::ip::make_address(getEnvLocalhostAddr()),
std::stoi(port))
@@ -40,7 +40,7 @@ struct GRPCTestClientBase
grpc::Status status;
grpc::ClientContext context;
std::unique_ptr<rpc::v1::XRPLedgerAPIService::Stub> stub_;
std::unique_ptr<org::xrpl::rpc::v1::XRPLedgerAPIService::Stub> stub_;
};
} // namespace test

View File

@@ -36,8 +36,8 @@ public:
class SubmitClient : public GRPCTestClientBase
{
public:
rpc::v1::SubmitTransactionRequest request;
rpc::v1::SubmitTransactionResponse reply;
org::xrpl::rpc::v1::SubmitTransactionRequest request;
org::xrpl::rpc::v1::SubmitTransactionResponse reply;
explicit SubmitClient(std::string const& port)
: GRPCTestClientBase(port)

View File

@@ -29,9 +29,12 @@
#include <test/jtx/envconfig.h>
#include <ripple/rpc/GRPCHandlers.h>
#include <ripple/rpc/impl/GRPCHelpers.h>
#include <ripple/rpc/impl/RPCHelpers.h>
#include <test/rpc/GRPCTestClientBase.h>
#include <string>
namespace ripple {
namespace test {
@@ -46,16 +49,23 @@ class Tx_test : public beast::unit_test::suite
}
void
cmpAmount(const rpc::v1::CurrencyAmount& proto_amount, STAmount amount)
cmpAmount(
const org::xrpl::rpc::v1::CurrencyAmount& proto_amount,
STAmount amount)
{
if (amount.native())
{
if (!BEAST_EXPECT(proto_amount.has_xrp_amount()))
return;
BEAST_EXPECT(
proto_amount.xrp_amount().drops() == amount.xrp().drops());
}
else
{
rpc::v1::IssuedCurrencyAmount issuedCurrency =
if (!BEAST_EXPECT(proto_amount.has_issued_currency_amount()))
return;
org::xrpl::rpc::v1::IssuedCurrencyAmount issuedCurrency =
proto_amount.issued_currency_amount();
Issue const& issue = amount.issue();
Currency currency = issue.currency;
@@ -70,60 +80,183 @@ class Tx_test : public beast::unit_test::suite
}
void
cmpTx(const rpc::v1::Transaction& proto, std::shared_ptr<STTx const> txnSt)
cmpPaymentTx(
const org::xrpl::rpc::v1::Transaction& proto,
std::shared_ptr<STTx const> txnSt)
{
if (!BEAST_EXPECT(proto.has_payment()))
return;
if (!BEAST_EXPECT(
safe_cast<TxType>(txnSt->getFieldU16(sfTransactionType)) ==
TxType::ttPAYMENT))
return;
AccountID account = txnSt->getAccountID(sfAccount);
BEAST_EXPECT(proto.account().address() == toBase58(account));
if (!BEAST_EXPECT(proto.has_account()))
return;
BEAST_EXPECT(proto.account().value().address() == toBase58(account));
STAmount amount = txnSt->getFieldAmount(sfAmount);
cmpAmount(proto.payment().amount(), amount);
if (!BEAST_EXPECT(proto.payment().has_amount()))
return;
cmpAmount(proto.payment().amount().value(), amount);
AccountID accountDest = txnSt->getAccountID(sfDestination);
if (!BEAST_EXPECT(proto.payment().has_destination()))
return;
BEAST_EXPECT(
proto.payment().destination().address() == toBase58(accountDest));
proto.payment().destination().value().address() ==
toBase58(accountDest));
STAmount fee = txnSt->getFieldAmount(sfFee);
if (!BEAST_EXPECT(proto.has_fee()))
return;
BEAST_EXPECT(proto.fee().drops() == fee.xrp().drops());
BEAST_EXPECT(proto.sequence() == txnSt->getFieldU32(sfSequence));
if (!BEAST_EXPECT(proto.has_sequence()))
return;
BEAST_EXPECT(
proto.sequence().value() == txnSt->getFieldU32(sfSequence));
if (!BEAST_EXPECT(proto.has_signing_public_key()))
return;
Blob signingPubKey = txnSt->getFieldVL(sfSigningPubKey);
BEAST_EXPECT(proto.signing_public_key() == toByteString(signingPubKey));
BEAST_EXPECT(proto.flags() == txnSt->getFieldU32(sfFlags));
BEAST_EXPECT(
proto.last_ledger_sequence() ==
txnSt->getFieldU32(sfLastLedgerSequence));
proto.signing_public_key().value() == toByteString(signingPubKey));
Blob blob = txnSt->getFieldVL(sfTxnSignature);
BEAST_EXPECT(proto.signature() == toByteString(blob));
if (txnSt->isFieldPresent(sfFlags))
{
if (!BEAST_EXPECT(proto.has_flags()))
return;
BEAST_EXPECT(proto.flags().value() == txnSt->getFieldU32(sfFlags));
}
else
{
BEAST_EXPECT(!proto.has_flags());
}
if (txnSt->isFieldPresent(sfLastLedgerSequence))
{
if (!BEAST_EXPECT(proto.has_last_ledger_sequence()))
return;
BEAST_EXPECT(
proto.last_ledger_sequence().value() ==
txnSt->getFieldU32(sfLastLedgerSequence));
}
else
{
BEAST_EXPECT(!proto.has_last_ledger_sequence());
}
if (txnSt->isFieldPresent(sfTxnSignature))
{
if (!BEAST_EXPECT(proto.has_transaction_signature()))
return;
Blob blob = txnSt->getFieldVL(sfTxnSignature);
BEAST_EXPECT(
proto.transaction_signature().value() == toByteString(blob));
}
if (txnSt->isFieldPresent(sfSendMax))
{
if (!BEAST_EXPECT(proto.payment().has_send_max()))
return;
STAmount const& send_max = txnSt->getFieldAmount(sfSendMax);
cmpAmount(proto.payment().send_max(), send_max);
cmpAmount(proto.payment().send_max().value(), send_max);
}
else
{
BEAST_EXPECT(!proto.payment().has_send_max());
}
if (txnSt->isFieldPresent(sfAccountTxnID))
{
if (!BEAST_EXPECT(proto.has_account_transaction_id()))
return;
auto field = txnSt->getFieldH256(sfAccountTxnID);
BEAST_EXPECT(proto.account_transaction_id() == toByteString(field));
BEAST_EXPECT(
proto.account_transaction_id().value() == toByteString(field));
}
else
{
BEAST_EXPECT(!proto.has_account_transaction_id());
}
if (txnSt->isFieldPresent(sfSourceTag))
{
if (!BEAST_EXPECT(proto.has_source_tag()))
return;
BEAST_EXPECT(
proto.source_tag().value() == txnSt->getFieldU32(sfSourceTag));
}
else
{
BEAST_EXPECT(!proto.has_source_tag());
}
if (txnSt->isFieldPresent(sfDestinationTag))
{
if (!BEAST_EXPECT(proto.payment().has_destination_tag()))
return;
BEAST_EXPECT(
proto.payment().destination_tag().value() ==
txnSt->getFieldU32(sfDestinationTag));
}
else
{
BEAST_EXPECT(!proto.payment().has_destination_tag());
}
if (txnSt->isFieldPresent(sfInvoiceID))
{
if (!BEAST_EXPECT(proto.payment().has_invoice_id()))
return;
auto field = txnSt->getFieldH256(sfInvoiceID);
BEAST_EXPECT(
proto.payment().invoice_id().value() == toByteString(field));
}
else
{
BEAST_EXPECT(!proto.payment().has_invoice_id());
}
if (txnSt->isFieldPresent(sfDeliverMin))
{
if (!BEAST_EXPECT(proto.payment().has_deliver_min()))
return;
STAmount const& deliverMin = txnSt->getFieldAmount(sfDeliverMin);
cmpAmount(proto.payment().deliver_min().value(), deliverMin);
}
else
{
BEAST_EXPECT(!proto.payment().has_deliver_min());
}
// populate path data
STPathSet const& pathset = txnSt->getFieldPathSet(sfPaths);
if (!BEAST_EXPECT(pathset.size() == proto.payment().paths_size()))
return;
int ind = 0;
for (auto it = pathset.begin(); it < pathset.end(); ++it)
{
STPath const& path = *it;
const rpc::v1::Path& protoPath = proto.payment().paths(ind++);
const org::xrpl::rpc::v1::Payment_Path& protoPath =
proto.payment().paths(ind++);
if (!BEAST_EXPECT(protoPath.elements_size() == path.size()))
continue;
int ind2 = 0;
for (auto it2 = path.begin(); it2 != path.end(); ++it2)
{
const rpc::v1::PathElement& protoElement =
const org::xrpl::rpc::v1::Payment_PathElement& protoElement =
protoPath.elements(ind2++);
STPathElement const& elt = *it2;
@@ -132,47 +265,224 @@ class Tx_test : public beast::unit_test::suite
if (elt.hasCurrency())
{
Currency const& currency = elt.getCurrency();
BEAST_EXPECT(
protoElement.currency().name() ==
to_string(currency));
if (BEAST_EXPECT(protoElement.has_currency()))
{
BEAST_EXPECT(
protoElement.currency().name() ==
to_string(currency));
}
}
else
{
BEAST_EXPECT(!protoElement.has_currency());
}
if (elt.hasIssuer())
{
AccountID const& issuer = elt.getIssuerID();
BEAST_EXPECT(
protoElement.issuer().address() ==
toBase58(issuer));
if (BEAST_EXPECT(protoElement.has_issuer()))
{
BEAST_EXPECT(
protoElement.issuer().address() ==
toBase58(issuer));
}
}
else
{
BEAST_EXPECT(!protoElement.has_issuer());
}
}
else
{
AccountID const& path_account = elt.getAccountID();
BEAST_EXPECT(
protoElement.account().address() ==
toBase58(path_account));
if (BEAST_EXPECT(protoElement.has_account()))
{
AccountID const& path_account = elt.getAccountID();
BEAST_EXPECT(
protoElement.account().address() ==
toBase58(path_account));
}
else
{
BEAST_EXPECT(!protoElement.has_account());
}
BEAST_EXPECT(!protoElement.has_issuer());
BEAST_EXPECT(!protoElement.has_currency());
}
}
}
if (txnSt->isFieldPresent(sfMemos))
{
auto arr = txnSt->getFieldArray(sfMemos);
if (BEAST_EXPECT(proto.memos_size() == arr.size()))
{
for (size_t i = 0; i < arr.size(); ++i)
{
auto protoMemo = proto.memos(i);
auto stMemo = arr[i];
if (stMemo.isFieldPresent(sfMemoData))
{
if (BEAST_EXPECT(protoMemo.has_memo_data()))
{
BEAST_EXPECT(
protoMemo.memo_data().value() ==
toByteString(stMemo.getFieldVL(sfMemoData)));
}
}
else
{
BEAST_EXPECT(!protoMemo.has_memo_data());
}
if (stMemo.isFieldPresent(sfMemoType))
{
if (BEAST_EXPECT(protoMemo.has_memo_type()))
{
BEAST_EXPECT(
protoMemo.memo_type().value() ==
toByteString(stMemo.getFieldVL(sfMemoType)));
}
}
else
{
BEAST_EXPECT(!protoMemo.has_memo_type());
}
if (stMemo.isFieldPresent(sfMemoFormat))
{
if (BEAST_EXPECT(protoMemo.has_memo_format()))
{
BEAST_EXPECT(
protoMemo.memo_format().value() ==
toByteString(stMemo.getFieldVL(sfMemoFormat)));
}
}
else
{
BEAST_EXPECT(!protoMemo.has_memo_format());
}
}
}
}
else
{
BEAST_EXPECT(proto.memos_size() == 0);
}
if (txnSt->isFieldPresent(sfSigners))
{
auto arr = txnSt->getFieldArray(sfSigners);
if (BEAST_EXPECT(proto.signers_size() == arr.size()))
{
for (size_t i = 0; i < arr.size(); ++i)
{
auto protoSigner = proto.signers(i);
auto stSigner = arr[i];
if (stSigner.isFieldPresent(sfAccount))
{
if (BEAST_EXPECT(protoSigner.has_account()))
{
BEAST_EXPECT(
protoSigner.account().value().address() ==
toBase58(stSigner.getAccountID(sfAccount)));
}
}
else
{
BEAST_EXPECT(!protoSigner.has_account());
}
if (stSigner.isFieldPresent(sfTxnSignature))
{
if (BEAST_EXPECT(
protoSigner.has_transaction_signature()))
{
Blob blob = stSigner.getFieldVL(sfTxnSignature);
BEAST_EXPECT(
protoSigner.transaction_signature().value() ==
toByteString(blob));
}
}
else
{
BEAST_EXPECT(!protoSigner.has_transaction_signature());
}
if (stSigner.isFieldPresent(sfSigningPubKey))
{
if (BEAST_EXPECT(protoSigner.has_signing_public_key()))
{
Blob signingPubKey =
stSigner.getFieldVL(sfSigningPubKey);
BEAST_EXPECT(
protoSigner.signing_public_key().value() ==
toByteString(signingPubKey));
}
}
else
{
BEAST_EXPECT(!protoSigner.has_signing_public_key());
}
}
}
}
else
{
BEAST_EXPECT(proto.signers_size() == 0);
}
}
void
cmpMeta(const rpc::v1::Meta& proto, std::shared_ptr<TxMeta> txMeta)
cmpMeta(
const org::xrpl::rpc::v1::Meta& proto,
std::shared_ptr<TxMeta> txMeta)
{
BEAST_EXPECT(proto.transaction_index() == txMeta->getIndex());
BEAST_EXPECT(
proto.transaction_result().result() ==
transToken(txMeta->getResultTER()));
rpc::v1::TransactionResult r;
org::xrpl::rpc::v1::TransactionResult r;
RPC::populateTransactionResultType(r, txMeta->getResultTER());
RPC::convert(r, txMeta->getResultTER());
BEAST_EXPECT(
proto.transaction_result().result_type() == r.result_type());
}
if (txMeta->hasDeliveredAmount())
void
cmpDeliveredAmount(
const org::xrpl::rpc::v1::Meta& meta,
const org::xrpl::rpc::v1::Transaction& txn,
const std::shared_ptr<TxMeta> expMeta,
const std::shared_ptr<STTx const> expTxn,
bool checkAmount = true)
{
if (expMeta->hasDeliveredAmount())
{
cmpAmount(proto.delivered_amount(), txMeta->getDeliveredAmount());
if (!BEAST_EXPECT(meta.has_delivered_amount()))
return;
cmpAmount(
meta.delivered_amount().value(), expMeta->getDeliveredAmount());
}
else
{
if (expTxn->isFieldPresent(sfAmount))
{
using namespace std::chrono_literals;
if (checkAmount)
{
cmpAmount(
meta.delivered_amount().value(),
expTxn->getFieldAmount(sfAmount));
}
}
else
{
BEAST_EXPECT(!meta.has_delivered_amount());
}
}
}
@@ -180,8 +490,8 @@ class Tx_test : public beast::unit_test::suite
class GrpcTxClient : public GRPCTestClientBase
{
public:
rpc::v1::GetTxRequest request;
rpc::v1::GetTxResponse reply;
org::xrpl::rpc::v1::GetTransactionRequest request;
org::xrpl::rpc::v1::GetTransactionResponse reply;
explicit GrpcTxClient(std::string const& port)
: GRPCTestClientBase(port)
@@ -191,7 +501,7 @@ class Tx_test : public beast::unit_test::suite
void
Tx()
{
status = stub_->GetTx(&context, request, &reply);
status = stub_->GetTransaction(&context, request, &reply);
}
};
@@ -205,33 +515,137 @@ class Tx_test : public beast::unit_test::suite
std::string grpcPort = *(*config)["port_grpc"].get<std::string>("port");
Env env(*this, std::move(config));
using namespace std::chrono_literals;
// Set time to this value (or greater) to get delivered_amount in meta
env.timeKeeper().set(NetClock::time_point{446000001s});
auto grpcTx = [&grpcPort](auto hash, auto binary) {
GrpcTxClient client(grpcPort);
client.request.set_hash(&hash, sizeof(hash));
client.request.set_binary(binary);
client.Tx();
return std::pair<bool, rpc::v1::GetTxResponse>(
return std::pair<bool, org::xrpl::rpc::v1::GetTransactionResponse>(
client.status.ok(), client.reply);
};
Account A1{"A1"};
Account A2{"A2"};
Account A3{"A3"};
env.fund(XRP(10000), A1);
env.fund(XRP(10000), A2);
env.close();
env.trust(A2["USD"](1000), A1);
env.close();
env(fset(A2, 5)); // set asfAccountTxnID flag
// SignerListSet
env(signers(A2, 1, {{"bogie", 1}, {"demon", 1}, {A1, 1}, {A3, 1}}),
sig(A2));
env.close();
std::vector<std::shared_ptr<STTx const>> txns;
auto const startLegSeq = env.current()->info().seq;
uint256 prevHash;
for (int i = 0; i < 14; ++i)
{
auto const baseFee = env.current()->fees().base;
auto txfee = fee(i + (2 * baseFee));
auto lls = last_ledger_seq(i + startLegSeq + 20);
auto dsttag = dtag(i * 456);
auto srctag = stag(i * 321);
auto sm = sendmax(A2["USD"](1000));
auto dm = delivermin(A2["USD"](50));
auto txf = txflags(131072); // partial payment flag
auto txnid = account_txn_id(prevHash);
auto inv = invoice_id(prevHash);
auto mem1 = memo("foo", "bar", "baz");
auto mem2 = memo("dragons", "elves", "goblins");
if (i & 1)
env(pay(A2, A1, A2["USD"](100)));
{
if (i & 2)
{
env(pay(A2, A1, A2["USD"](100)),
txfee,
srctag,
dsttag,
lls,
sm,
dm,
txf,
txnid,
inv,
mem1,
mem2,
sig(A2));
}
else
{
env(pay(A2, A1, A2["USD"](100)),
txfee,
srctag,
dsttag,
lls,
sm,
dm,
txf,
txnid,
inv,
mem1,
mem2,
msig(A3));
}
}
else
env(pay(A2, A1, A2["XRP"](200)));
{
if (i & 2)
{
env(pay(A2, A1, A2["XRP"](200)),
txfee,
srctag,
dsttag,
lls,
txnid,
inv,
mem1,
mem2,
sig(A2));
}
else
{
env(pay(A2, A1, A2["XRP"](200)),
txfee,
srctag,
dsttag,
lls,
txnid,
inv,
mem1,
mem2,
msig(A3));
}
}
txns.emplace_back(env.tx());
prevHash = txns.back()->getTransactionID();
env.close();
}
// Payment with Paths
auto const gw = Account("gateway");
auto const USD = gw["USD"];
env.fund(XRP(10000), "alice", "bob", gw);
env.trust(USD(600), "alice");
env.trust(USD(700), "bob");
env(pay(gw, "alice", USD(70)));
txns.emplace_back(env.tx());
env.close();
env(pay(gw, "bob", USD(50)));
txns.emplace_back(env.tx());
env.close();
env(pay("alice", "bob", Account("bob")["USD"](5)), path(gw));
txns.emplace_back(env.tx());
env.close();
auto const endLegSeq = env.closed()->info().seq;
// Find the existing transactions
@@ -257,7 +671,7 @@ class Tx_test : public beast::unit_test::suite
}
else
{
cmpTx(result.second.transaction(), tx);
cmpPaymentTx(result.second.transaction(), tx);
}
if (ledger && !b)
@@ -269,6 +683,11 @@ class Tx_test : public beast::unit_test::suite
id, ledger->seq(), *rawMeta);
cmpMeta(result.second.meta(), txMeta);
cmpDeliveredAmount(
result.second.meta(),
result.second.transaction(),
txMeta,
tx);
}
}
}
@@ -299,6 +718,41 @@ class Tx_test : public beast::unit_test::suite
BEAST_EXPECT(result.first == false);
}
// non final transaction
env(pay(A2, A1, A2["XRP"](200)));
auto res = grpcTx(env.tx()->getTransactionID(), false);
BEAST_EXPECT(res.first);
BEAST_EXPECT(res.second.has_transaction());
if (!BEAST_EXPECT(res.second.has_meta()))
return;
if (!BEAST_EXPECT(res.second.meta().has_transaction_result()))
return;
BEAST_EXPECT(
res.second.meta().transaction_result().result() == "tesSUCCESS");
BEAST_EXPECT(
res.second.meta().transaction_result().result_type() ==
org::xrpl::rpc::v1::TransactionResult::RESULT_TYPE_TES);
BEAST_EXPECT(!res.second.validated());
BEAST_EXPECT(!res.second.meta().has_delivered_amount());
env.close();
res = grpcTx(env.tx()->getTransactionID(), false);
BEAST_EXPECT(res.first);
BEAST_EXPECT(res.second.has_transaction());
if (!BEAST_EXPECT(res.second.has_meta()))
return;
if (!BEAST_EXPECT(res.second.meta().has_transaction_result()))
return;
BEAST_EXPECT(
res.second.meta().transaction_result().result() == "tesSUCCESS");
BEAST_EXPECT(
res.second.meta().transaction_result().result_type() ==
org::xrpl::rpc::v1::TransactionResult::RESULT_TYPE_TES);
BEAST_EXPECT(res.second.validated());
BEAST_EXPECT(res.second.meta().has_delivered_amount());
}
public: