Remove gRPC code previously used for the xpring SDK

This commit is contained in:
CJ Cobb
2022-08-10 15:44:59 -04:00
committed by manojsdoshi
parent 95fabd5762
commit 28f4cc7817
39 changed files with 8 additions and 9465 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -25,7 +25,6 @@
#include <ripple/basics/Log.h>
#include <ripple/beast/unit_test.h>
#include <ripple/ledger/View.h>
#include <ripple/rpc/impl/GRPCHelpers.h>
#include <test/jtx.h>
namespace ripple {
@@ -1883,114 +1882,6 @@ class NegativeUNLVoteFilterValidations_test : public beast::unit_test::suite
}
};
class NegativeUNLgRPC_test : public beast::unit_test::suite
{
template <class T>
std::string
toByteString(T const& data)
{
const char* bytes = reinterpret_cast<const char*>(data.data());
return {bytes, data.size()};
}
void
testGRPC()
{
testcase("gRPC test");
auto gRpcTest = [this](
std::uint32_t negUnlSize,
bool hasToDisable,
bool hasToReEnable) -> bool {
NetworkHistory history = {
*this, {20, negUnlSize, hasToDisable, hasToReEnable, {}}};
if (!history.goodHistory)
return false;
auto const& negUnlObject =
history.lastLedger()->read(keylet::negativeUNL());
if (!negUnlSize && !hasToDisable && !hasToReEnable && !negUnlObject)
return true;
if (!negUnlObject)
return false;
org::xrpl::rpc::v1::NegativeUNL to;
ripple::RPC::convert(to, *negUnlObject);
if (!to.has_flags() ||
to.flags().value() != negUnlObject->getFlags())
return false;
bool goodSize = to.disabled_validators_size() == negUnlSize &&
to.has_validator_to_disable() == hasToDisable &&
to.has_validator_to_re_enable() == hasToReEnable;
if (!goodSize)
return false;
if (negUnlSize)
{
if (!negUnlObject->isFieldPresent(sfDisabledValidators))
return false;
auto const& nUnlData =
negUnlObject->getFieldArray(sfDisabledValidators);
if (nUnlData.size() != negUnlSize)
return false;
int idx = 0;
for (auto const& n : nUnlData)
{
if (!n.isFieldPresent(sfPublicKey) ||
!n.isFieldPresent(sfFirstLedgerSequence))
return false;
if (!to.disabled_validators(idx).has_ledger_sequence() ||
!to.disabled_validators(idx).has_public_key())
return false;
if (to.disabled_validators(idx).public_key().value() !=
toByteString(n.getFieldVL(sfPublicKey)))
return false;
if (to.disabled_validators(idx).ledger_sequence().value() !=
n.getFieldU32(sfFirstLedgerSequence))
return false;
++idx;
}
}
if (hasToDisable)
{
if (!negUnlObject->isFieldPresent(sfValidatorToDisable))
return false;
if (to.validator_to_disable().value() !=
toByteString(
negUnlObject->getFieldVL(sfValidatorToDisable)))
return false;
}
if (hasToReEnable)
{
if (!negUnlObject->isFieldPresent(sfValidatorToReEnable))
return false;
if (to.validator_to_re_enable().value() !=
toByteString(
negUnlObject->getFieldVL(sfValidatorToReEnable)))
return false;
}
return true;
};
BEAST_EXPECT(gRpcTest(0, false, false));
BEAST_EXPECT(gRpcTest(2, true, true));
}
void
run() override
{
testGRPC();
}
};
BEAST_DEFINE_TESTSUITE(NegativeUNL, ledger, ripple);
BEAST_DEFINE_TESTSUITE(NegativeUNLNoAmendment, ledger, ripple);
@@ -2006,7 +1897,6 @@ BEAST_DEFINE_TESTSUITE_PRIO(
1);
BEAST_DEFINE_TESTSUITE(NegativeUNLVoteNewValidator, consensus, ripple);
BEAST_DEFINE_TESTSUITE(NegativeUNLVoteFilterValidations, consensus, ripple);
BEAST_DEFINE_TESTSUITE(NegativeUNLgRPC, ledger, ripple);
///////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////

View File

@@ -1,975 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2020 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 <ripple/basics/safe_cast.h>
#include <ripple/beast/unit_test.h>
#include <ripple/protocol/InnerObjectFormats.h>
#include <ripple/protocol/LedgerFormats.h>
#include <ripple/protocol/TxFormats.h>
#include "org/xrpl/rpc/v1/ledger_objects.pb.h"
#include "org/xrpl/rpc/v1/transaction.pb.h"
#include <cctype>
#include <map>
#include <string>
#include <type_traits>
namespace ripple {
// This test suite uses the google::protobuf::Descriptor class to do runtime
// reflection on our gRPC stuff. At the time of this writing documentation
// for Descriptor could be found here:
//
// https://developers.google.com/protocol-buffers/docs/reference/cpp/google.protobuf.descriptor#Descriptor
class KnownFormatToGRPC_test : public beast::unit_test::suite
{
private:
static constexpr auto fieldTYPE_UINT32 =
google::protobuf::FieldDescriptor::Type::TYPE_UINT32;
static constexpr auto fieldTYPE_UINT64 =
google::protobuf::FieldDescriptor::Type::TYPE_UINT64;
static constexpr auto fieldTYPE_BYTES =
google::protobuf::FieldDescriptor::Type::TYPE_BYTES;
static constexpr auto fieldTYPE_STRING =
google::protobuf::FieldDescriptor::Type::TYPE_STRING;
static constexpr auto fieldTYPE_MESSAGE =
google::protobuf::FieldDescriptor::Type::TYPE_MESSAGE;
// Format names are CamelCase and FieldDescriptor names are snake_case.
// Convert from CamelCase to snake_case. Do not be fooled by consecutive
// capital letters like in NegativeUNL.
static std::string
formatNameToEntryTypeName(std::string const& fmtName)
{
std::string entryName;
entryName.reserve(fmtName.size());
bool prevUpper = false;
for (std::size_t i = 0; i < fmtName.size(); i++)
{
char const ch = fmtName[i];
bool const upper = std::isupper(ch);
if (i > 0 && !prevUpper && upper)
entryName.push_back('_');
prevUpper = upper;
entryName.push_back(std::tolower(ch));
}
return entryName;
};
// Create a map of (most) all the SFields in an SOTemplate. This map
// can be used to correlate a gRPC Descriptor to its corresponding SField.
template <typename KeyType>
static std::map<std::string, SField const*>
soTemplateToSFields(
SOTemplate const& soTemplate,
[[maybe_unused]] KeyType fmtId)
{
std::map<std::string, SField const*> sFields;
for (SOElement const& element : soTemplate)
{
SField const& sField = element.sField();
// Fields that gRPC never includes.
//
// o sfLedgerIndex and
// o sfLedgerEntryType are common to all ledger objects, so
// gRPC includes them at a higher level than the ledger
// object itself.
//
// o sfOperationLimit is an optional field in all transactions,
// but no one knows what it was intended for.
using FieldCode_t =
std::remove_const<decltype(SField::fieldCode)>::type;
static const std::set<FieldCode_t> excludedSFields{
sfLedgerIndex.fieldCode,
sfLedgerEntryType.fieldCode,
sfOperationLimit.fieldCode};
if (excludedSFields.count(sField.fieldCode))
continue;
// There are certain fields that gRPC never represents in
// transactions. Exclude those.
//
// o sfPreviousTxnID is obsolete and was replaced by
// sfAccountTxnID some time before November of 2014.
//
// o sfWalletLocator and
// o sfWalletSize have been deprecated for six years or more.
//
// o sfTransactionType is not needed by gRPC, since the typing
// is handled using protobuf message types.
if constexpr (std::is_same_v<KeyType, TxType>)
{
static const std::set<FieldCode_t> excludedTxFields{
sfPreviousTxnID.fieldCode,
sfTransactionType.fieldCode,
sfWalletLocator.fieldCode,
sfWalletSize.fieldCode};
if (excludedTxFields.count(sField.fieldCode))
continue;
}
// If fmtId is a LedgerEntryType, exclude certain fields.
if constexpr (std::is_same_v<KeyType, LedgerEntryType>)
{
// Fields that gRPC does not include in certain LedgerFormats.
//
// o sfWalletLocator,
// o sfWalletSize,
// o sfExchangeRate, and
// o sfFirstLedgerSequence are all deprecated fields in
// their respective ledger objects.
static const std::
map<LedgerEntryType, std::vector<SField const*>>
gRPCOmitFields{
{ltACCOUNT_ROOT, {&sfWalletLocator, &sfWalletSize}},
{ltDIR_NODE, {&sfExchangeRate}},
{ltLEDGER_HASHES, {&sfFirstLedgerSequence}},
};
if (auto const iter = gRPCOmitFields.find(fmtId);
iter != gRPCOmitFields.end())
{
std::vector<SField const*> const& omits = iter->second;
// Check for fields that gRPC omits from this type.
if (std::find_if(
omits.begin(),
omits.end(),
[&sField](SField const* const omit) {
return *omit == sField;
}) != omits.end())
{
// This is one of the fields that gRPC omits.
continue;
}
}
}
// The SFields and gRPC disagree on the names of some fields.
// Provide a mapping from SField names to gRPC names for the
// known exceptions.
//
// clang-format off
//
// The implementers of the gRPC interface made the decision not
// to abbreviate anything. This accounts for the following
// field name differences:
//
// "AccountTxnID", "AccountTransactionID"
// "PreviousTxnID", "PreviousTransactionID"
// "PreviousTxnLgrSeq", "PreviousTransactionLedgerSequence"
// "SigningPubKey", "SigningPublicKey"
// "TxnSignature", "TransactionSignature"
//
// gRPC adds typing information for Fee, which accounts for
// "Fee", "XRPDropsAmount"
//
// There's one misspelling which accounts for
// "TakerGetsCurrency", "TakerGetsCurreny"
//
// The implementers of the gRPC interface observed that a
// PaymentChannelClaim transaction has a TxnSignature field at the
// upper level and a Signature field at the lever level. They
// felt that was confusing, which is the reason for
// "Signature", "PaymentChannelSignature"
//
static const std::map<std::string, std::string> sFieldToGRPC{
{"AccountTxnID", "AccountTransactionID"},
{"Fee", "XRPDropsAmount"},
{"PreviousTxnID", "PreviousTransactionID"},
{"PreviousTxnLgrSeq", "PreviousTransactionLedgerSequence"},
{"Signature", "PaymentChannelSignature"},
{"SigningPubKey", "SigningPublicKey"},
{"TakerGetsCurrency", "TakerGetsCurreny"},
{"TxnSignature", "TransactionSignature"},
};
// clang-format on
auto const iter = sFieldToGRPC.find(sField.getName());
std::string gRPCName =
iter != sFieldToGRPC.end() ? iter->second : sField.getName();
sFields.insert({std::move(gRPCName), &sField});
}
return sFields;
}
// Given a Descriptor for a KnownFormat and a map of the SFields of that
// KnownFormat, make sure the fields are aligned.
void
validateDescriptorAgainstSFields(
google::protobuf::Descriptor const* const pbufDescriptor,
google::protobuf::Descriptor const* const commonFields,
std::string const& knownFormatName,
std::map<std::string, SField const*>&& sFields)
{
// Create namespace aliases for shorter names.
namespace pbuf = google::protobuf;
// We'll be running through two sets of pbuf::Descriptors: the ones in
// the OneOf and the common fields. Here is a lambda that factors out
// the common checking code for these two cases.
auto checkFieldDesc = [this, &sFields, &knownFormatName](
pbuf::FieldDescriptor const* const
fieldDesc) {
// gRPC has different handling for repeated vs non-repeated
// types. So we need to do that too.
std::string name;
if (fieldDesc->is_repeated())
{
// Repeated-type handling.
// Munge the fieldDescriptor name so it looks like the
// name in sFields.
name = fieldDesc->camelcase_name();
name[0] = toupper(name[0]);
// The ledger gives UNL all caps. Adapt to that.
if (size_t const i = name.find("Unl"); i != std::string::npos)
{
name[i + 1] = 'N';
name[i + 2] = 'L';
}
// The ledger gives the NFT part of NFToken all caps.
// Adapt to that.
if (size_t const i = name.find("Nft"); i != std::string::npos)
{
name[i + 1] = 'F';
name[i + 2] = 'T';
}
if (!sFields.count(name))
{
fail(
std::string("Repeated Protobuf Descriptor '") + name +
"' expected in KnownFormat '" + knownFormatName +
"' and not found",
__FILE__,
__LINE__);
return;
}
pass();
validateRepeatedField(fieldDesc, sFields.at(name));
}
else
{
// Non-repeated handling.
pbuf::Descriptor const* const entryDesc =
fieldDesc->message_type();
if (entryDesc == nullptr)
return;
name = entryDesc->name();
if (!sFields.count(name))
{
fail(
std::string("Protobuf Descriptor '") +
entryDesc->name() + "' expected in KnownFormat '" +
knownFormatName + "' and not found",
__FILE__,
__LINE__);
return;
}
pass();
validateDescriptor(entryDesc, sFields.at(entryDesc->name()));
}
// Remove the validated field from the map so we can tell if
// there are left over fields at the end of all comparisons.
sFields.erase(name);
};
// Compare the SFields to the FieldDescriptor->Descriptors.
for (int i = 0; i < pbufDescriptor->field_count(); ++i)
{
pbuf::FieldDescriptor const* const fieldDesc =
pbufDescriptor->field(i);
if (fieldDesc == nullptr || fieldDesc->type() != fieldTYPE_MESSAGE)
continue;
checkFieldDesc(fieldDesc);
}
// Now all of the OneOf-specific fields have been removed from
// sFields. But there may be common fields left in there. Process
// the commonFields next.
if (commonFields)
{
for (int i = 0; i < commonFields->field_count(); ++i)
{
// If the field we picked up is a OneOf, skip it. Common
// fields are never OneOfs.
pbuf::FieldDescriptor const* const fieldDesc =
commonFields->field(i);
if (fieldDesc == nullptr ||
fieldDesc->containing_oneof() != nullptr ||
fieldDesc->type() != fieldTYPE_MESSAGE)
continue;
checkFieldDesc(fieldDesc);
}
}
// All SFields in the KnownFormat have corresponding gRPC fields
// if the sFields map is now empty.
if (!sFields.empty())
{
fail(
std::string("Protobuf Descriptor '") + pbufDescriptor->name() +
"' did not account for all fields in KnownFormat '" +
knownFormatName + "'. Left over field: `" +
sFields.begin()->first + "'",
__FILE__,
__LINE__);
return;
}
pass();
}
// Compare a protobuf descriptor with multiple oneOfFields to choose from
// to an SField.
void
validateOneOfDescriptor(
google::protobuf::Descriptor const* const entryDesc,
SField const* const sField)
{
// Create namespace aliases for shorter names.
namespace pbuf = google::protobuf;
// Note that it's not okay to compare names because SFields and
// gRPC do not always agree on the names.
if (entryDesc->field_count() == 0 || entryDesc->oneof_decl_count() != 1)
{
fail(
std::string("Protobuf Descriptor '") + entryDesc->name() +
"' expected to have multiple OneOf fields and nothing else",
__FILE__,
__LINE__);
return;
}
pbuf::FieldDescriptor const* const fieldDesc = entryDesc->field(0);
if (fieldDesc == nullptr)
{
fail(
std::string("Internal test failure. Unhandled nullptr "
"in FieldDescriptor for '") +
entryDesc->name() + "'",
__FILE__,
__LINE__);
return;
}
// Special handling for CurrencyAmount
if (sField->fieldType == STI_AMOUNT &&
entryDesc->name() == "CurrencyAmount")
{
// SFields of type STI_AMOUNT are represented in gRPC by a
// multi-field CurrencyAmount. We don't really learn anything
// by diving into the interior of CurrencyAmount, so we stop here
// and call it good.
pass();
return;
}
fail(
std::string("Unhandled OneOf Protobuf Descriptor '") +
entryDesc->name() + "'",
__FILE__,
__LINE__);
}
void
validateMultiFieldDescriptor(
google::protobuf::Descriptor const* const entryDesc,
SField const* const sField)
{
// Create namespace aliases for shorter names.
namespace pbuf = google::protobuf;
if (entryDesc->field_count() <= 1 || entryDesc->oneof_decl_count() != 0)
{
fail(
std::string("Protobuf Descriptor '") + entryDesc->name() +
"' expected to have multiple fields and nothing else",
__FILE__,
__LINE__);
return;
}
// There are composite fields that the SFields handle differently
// from gRPC. Handle those here.
{
struct FieldContents
{
std::string_view fieldName;
google::protobuf::FieldDescriptor::Type fieldType;
bool
operator<(FieldContents const& other) const
{
return this->fieldName < other.fieldName;
}
bool
operator==(FieldContents const& other) const
{
return this->fieldName == other.fieldName &&
this->fieldType == other.fieldType;
}
};
struct SpecialEntry
{
std::string_view const descriptorName;
SerializedTypeID const sFieldType;
std::set<FieldContents> const fields;
};
// clang-format off
static const std::array specialEntries{
SpecialEntry{
"Currency", STI_UINT160,
{
{"name", fieldTYPE_STRING},
{"code", fieldTYPE_BYTES}
}
},
SpecialEntry{
"Memo", STI_OBJECT,
{
{"memo_data", fieldTYPE_BYTES},
{"memo_format", fieldTYPE_BYTES},
{"memo_type", fieldTYPE_BYTES}
}
}
};
// clang-format on
// If we're handling a SpecialEntry...
if (auto const iter = std::find_if(
specialEntries.begin(),
specialEntries.end(),
[entryDesc, sField](SpecialEntry const& entry) {
return entryDesc->name() == entry.descriptorName &&
sField->fieldType == entry.sFieldType;
});
iter != specialEntries.end())
{
// Verify the SField.
if (!BEAST_EXPECT(sField->fieldType == iter->sFieldType))
return;
// Verify all of the fields in the entryDesc.
if (!BEAST_EXPECT(
entryDesc->field_count() == iter->fields.size()))
return;
for (int i = 0; i < entryDesc->field_count(); ++i)
{
pbuf::FieldDescriptor const* const fieldDesc =
entryDesc->field(i);
FieldContents const contents{
fieldDesc->name(), fieldDesc->type()};
if (!BEAST_EXPECT(
iter->fields.find(contents) != iter->fields.end()))
return;
}
// This field is good.
pass();
return;
}
}
// If the field was not one of the SpecialEntries, we expect it to be
// an InnerObjectFormat.
SOTemplate const* const innerFormat =
InnerObjectFormats::getInstance().findSOTemplateBySField(*sField);
if (innerFormat == nullptr)
{
fail(
"SOTemplate for field '" + sField->getName() + "' not found",
__FILE__,
__LINE__);
return;
}
// Create a map we can use use to correlate each field in the
// gRPC Descriptor to its corresponding SField.
std::map<std::string, SField const*> sFields =
soTemplateToSFields(*innerFormat, 0);
// Compare the SFields to the FieldDescriptor->Descriptors.
validateDescriptorAgainstSFields(
entryDesc, nullptr, sField->getName(), std::move(sFields));
}
// Compare a protobuf descriptor with only one field to an SField.
void
validateOneDescriptor(
google::protobuf::Descriptor const* const entryDesc,
SField const* const sField)
{
// Create namespace aliases for shorter names.
namespace pbuf = google::protobuf;
// Note that it's not okay to compare names because SFields and
// gRPC do not always agree on the names.
if (entryDesc->field_count() != 1 || entryDesc->oneof_decl_count() != 0)
{
fail(
std::string("Protobuf Descriptor '") + entryDesc->name() +
"' expected to be one field and nothing else",
__FILE__,
__LINE__);
return;
}
pbuf::FieldDescriptor const* const fieldDesc = entryDesc->field(0);
if (fieldDesc == nullptr)
{
fail(
std::string("Internal test failure. Unhandled nullptr "
"in FieldDescriptor for '") +
entryDesc->name() + "'",
__FILE__,
__LINE__);
return;
}
// Create a map from SerializedTypeID to pbuf::FieldDescriptor::Type.
//
// This works for most, but not all, types because of divergence
// between the gRPC and LedgerFormat implementations. We deal
// with the special cases later.
// clang-format off
static const std::map<SerializedTypeID, pbuf::FieldDescriptor::Type>
sTypeToFieldDescType{
{STI_UINT8, fieldTYPE_UINT32},
{STI_UINT16, fieldTYPE_UINT32},
{STI_UINT32, fieldTYPE_UINT32},
{STI_UINT64, fieldTYPE_UINT64},
{STI_ACCOUNT, fieldTYPE_STRING},
{STI_AMOUNT, fieldTYPE_BYTES},
{STI_UINT128, fieldTYPE_BYTES},
{STI_UINT160, fieldTYPE_BYTES},
{STI_UINT256, fieldTYPE_BYTES},
{STI_VL, fieldTYPE_BYTES},
};
//clang-format on
// If the SField and FieldDescriptor::Type correlate we're good.
if (auto const iter = sTypeToFieldDescType.find(sField->fieldType);
iter != sTypeToFieldDescType.end() &&
iter->second == fieldDesc->type())
{
pass();
return;
}
// Handle special cases for specific SFields.
static const std::map<int, pbuf::FieldDescriptor::Type>
sFieldCodeToFieldDescType{
{sfDomain.fieldCode, fieldTYPE_STRING},
{sfFee.fieldCode, fieldTYPE_UINT64},
{sfURI.fieldCode, fieldTYPE_STRING}};
if (auto const iter = sFieldCodeToFieldDescType.find(sField->fieldCode);
iter != sFieldCodeToFieldDescType.end() &&
iter->second == fieldDesc->type())
{
pass();
return;
}
// Special handling for all Message types.
if (fieldDesc->type() == fieldTYPE_MESSAGE)
{
// We need to recurse to get to the bottom of the field(s)
// in question.
// Start by identifying which fields we need to be handling.
// clang-format off
static const std::map<int, std::string> messageMap{
{sfAccount.fieldCode, "AccountAddress"},
{sfAmount.fieldCode, "CurrencyAmount"},
{sfAuthorize.fieldCode, "AccountAddress"},
{sfBalance.fieldCode, "CurrencyAmount"},
{sfDestination.fieldCode, "AccountAddress"},
{sfFee.fieldCode, "XRPDropsAmount"},
{sfHighLimit.fieldCode, "CurrencyAmount"},
{sfLowLimit.fieldCode, "CurrencyAmount"},
{sfOwner.fieldCode, "AccountAddress"},
{sfRegularKey.fieldCode, "AccountAddress"},
{sfSendMax.fieldCode, "CurrencyAmount"},
{sfTakerGets.fieldCode, "CurrencyAmount"},
{sfTakerGetsCurrency.fieldCode, "Currency"},
{sfTakerPays.fieldCode, "CurrencyAmount"},
{sfTakerPaysCurrency.fieldCode, "Currency"},
};
// clang-format on
if (messageMap.count(sField->fieldCode))
{
pbuf::Descriptor const* const entry2Desc =
fieldDesc->message_type();
if (entry2Desc == nullptr)
{
fail(
std::string("Unexpected gRPC. ") + fieldDesc->name() +
" MESSAGE with null Descriptor",
__FILE__,
__LINE__);
return;
}
// The Descriptor name should match the messageMap name.
if (messageMap.at(sField->fieldCode) != entry2Desc->name())
{
fail(
std::string(
"Internal test error. Mismatch between SField '") +
sField->getName() + "' and gRPC Descriptor name '" +
entry2Desc->name() + "'",
__FILE__,
__LINE__);
return;
}
pass();
// Recurse to the next lower Descriptor.
validateDescriptor(entry2Desc, sField);
}
return;
}
fail(
std::string("Internal test error. Unhandled FieldDescriptor '") +
entryDesc->name() + "' has type `" + fieldDesc->type_name() +
"` and label " + std::to_string(fieldDesc->label()),
__FILE__,
__LINE__);
}
// Compare a repeated protobuf FieldDescriptor to an SField.
void
validateRepeatedField(
google::protobuf::FieldDescriptor const* const fieldDesc,
SField const* const sField)
{
// Create namespace aliases for shorter names.
namespace pbuf = google::protobuf;
pbuf::Descriptor const* const entryDesc = fieldDesc->message_type();
if (entryDesc == nullptr)
{
fail(
std::string("Expected Descriptor for repeated type ") +
sField->getName(),
__FILE__,
__LINE__);
return;
}
// The following repeated types provide no further structure for their
// in-ledger representation. We just have to trust that the gRPC
// representation is reasonable for what the ledger implements.
static const std::set<std::string> noFurtherDetail{
{sfPaths.getName()},
};
if (noFurtherDetail.count(sField->getName()))
{
// There is no Format representation for further details of this
// repeated type. We've done the best we can.
pass();
return;
}
// All of the repeated types that the test currently supports.
static const std::map<std::string, SField const*> repeatsWhat{
{sfAmendments.getName(), &sfAmendment},
{sfDisabledValidators.getName(), &sfDisabledValidator},
{sfHashes.getName(), &sfLedgerHash},
{sfIndexes.getName(), &sfLedgerIndex},
{sfMajorities.getName(), &sfMajority},
{sfMemos.getName(), &sfMemo},
{sfNFTokens.getName(), &sfNFToken},
{sfSignerEntries.getName(), &sfSignerEntry},
{sfSigners.getName(), &sfSigner},
{sfNFTokenOffers.getName(), &sfLedgerIndex}};
if (!repeatsWhat.count(sField->getName()))
{
fail(
std::string("Unexpected repeated type ") + fieldDesc->name(),
__FILE__,
__LINE__);
return;
}
pass();
// Process the type contained by the repeated type.
validateDescriptor(entryDesc, repeatsWhat.at(sField->getName()));
}
// Determine which of the Descriptor validators to dispatch to.
void
validateDescriptor(
google::protobuf::Descriptor const* const entryDesc,
SField const* const sField)
{
if (entryDesc->nested_type_count() != 0 ||
entryDesc->enum_type_count() != 0 ||
entryDesc->extension_range_count() != 0 ||
entryDesc->reserved_range_count() != 0)
{
fail(
std::string("Protobuf Descriptor '") + entryDesc->name() +
"' uses unsupported protobuf features",
__FILE__,
__LINE__);
return;
}
// Dispatch to the correct validator
if (entryDesc->oneof_decl_count() > 0)
return validateOneOfDescriptor(entryDesc, sField);
if (entryDesc->field_count() > 1)
return validateMultiFieldDescriptor(entryDesc, sField);
return validateOneDescriptor(entryDesc, sField);
}
// Compare a protobuf descriptor to a KnownFormat::Item
template <typename FmtType, typename FmtName>
void
validateFields(
google::protobuf::Descriptor const* const pbufDescriptor,
google::protobuf::Descriptor const* const commonFields,
typename KnownFormats<FmtType, FmtName>::Item const* const
knownFormatItem)
{
// Create namespace aliases for shorter names.
namespace pbuf = google::protobuf;
// The names should usually be the same, but the bpufDescriptor
// name might have "Object" appended.
if (knownFormatItem->getName() != pbufDescriptor->name() &&
knownFormatItem->getName() + "Object" != pbufDescriptor->name())
{
fail(
std::string("Protobuf Descriptor '") + pbufDescriptor->name() +
"' and KnownFormat::Item '" + knownFormatItem->getName() +
"' don't have the same name",
__FILE__,
__LINE__);
return;
}
pass();
// Create a map we can use use to correlate each field in the
// gRPC Descriptor to its corresponding SField.
std::map<std::string, SField const*> sFields = soTemplateToSFields(
knownFormatItem->getSOTemplate(), knownFormatItem->getType());
// Compare the SFields to the FieldDescriptor->Descriptors.
validateDescriptorAgainstSFields(
pbufDescriptor,
commonFields,
knownFormatItem->getName(),
std::move(sFields));
}
template <typename FmtType, typename FmtName>
void
testKnownFormats(
KnownFormats<FmtType, FmtName> const& knownFormat,
std::string const& knownFormatName,
google::protobuf::Descriptor const* const commonFields,
google::protobuf::OneofDescriptor const* const oneOfDesc)
{
// Create namespace aliases for shorter names.
namespace grpc = org::xrpl::rpc::v1;
namespace pbuf = google::protobuf;
if (!BEAST_EXPECT(oneOfDesc != nullptr))
return;
// Get corresponding names for all KnownFormat Items.
std::map<
std::string,
typename KnownFormats<FmtType, FmtName>::Item const*>
formatTypes;
for (auto const& item : knownFormat)
{
if constexpr (std::is_same_v<FmtType, LedgerEntryType>)
{
// Skip LedgerEntryTypes that gRPC does not currently support.
static constexpr std::array<LedgerEntryType, 0> notSupported{};
if (std::find(
notSupported.begin(),
notSupported.end(),
item.getType()) != notSupported.end())
continue;
}
if constexpr (std::is_same_v<FmtType, TxType>)
{
// Skip TxTypes that gRPC does not currently support.
static constexpr std::array notSupported{
ttAMENDMENT, ttFEE, ttUNL_MODIFY};
if (std::find(
notSupported.begin(),
notSupported.end(),
item.getType()) != notSupported.end())
continue;
}
BEAST_EXPECT(
formatTypes
.insert({formatNameToEntryTypeName(item.getName()), &item})
.second == true);
}
// Verify that the OneOf objects match. Start by comparing
// KnownFormat vs gRPC OneOf counts.
{
BEAST_EXPECT(formatTypes.size() == oneOfDesc->field_count());
}
// This loop
// 1. Iterates through the gRPC OneOfs,
// 2. Finds each gRPC OneOf's matching KnownFormat::Item,
// 3. Sanity checks that the fields of the objects align well.
for (auto i = 0; i < oneOfDesc->field_count(); ++i)
{
pbuf::FieldDescriptor const* const fieldDesc = oneOfDesc->field(i);
// The Field should be a TYPE_MESSAGE, which means we can get its
// descriptor.
if (fieldDesc->type() != fieldTYPE_MESSAGE)
{
fail(
std::string("gRPC OneOf '") + fieldDesc->name() +
"' is not TYPE_MESSAGE",
__FILE__,
__LINE__);
continue;
}
auto const fmtIter = formatTypes.find(fieldDesc->name());
if (fmtIter == formatTypes.cend())
{
fail(
std::string("gRPC OneOf '") + fieldDesc->name() +
"' not found in " + knownFormatName,
__FILE__,
__LINE__);
continue;
}
// Validate that the gRPC and KnownFormat fields align.
validateFields<FmtType, FmtName>(
fieldDesc->message_type(), commonFields, fmtIter->second);
// Remove the checked KnownFormat from the map. This way we
// can check for leftovers when we're done processing.
formatTypes.erase(fieldDesc->name());
}
// Report any KnownFormats that don't have gRPC OneOfs.
for (auto const& spare : formatTypes)
{
fail(
knownFormatName + " '" + spare.second->getName() +
"' does not have a corresponding gRPC OneOf",
__FILE__,
__LINE__);
}
}
public:
void
testLedgerObjectGRPCOneOfs()
{
testcase("Ledger object validation");
org::xrpl::rpc::v1::LedgerObject const ledgerObject;
testKnownFormats(
LedgerFormats::getInstance(),
"LedgerFormats",
ledgerObject.GetDescriptor(),
ledgerObject.GetDescriptor()->FindOneofByName("object"));
return;
}
void
testTransactionGRPCOneOfs()
{
testcase("Transaction validation");
org::xrpl::rpc::v1::Transaction const txData;
testKnownFormats(
TxFormats::getInstance(),
"TxFormats",
txData.GetDescriptor(),
txData.GetDescriptor()->FindOneofByName("transaction_data"));
return;
}
void
run() override
{
testLedgerObjectGRPCOneOfs();
testTransactionGRPCOneOfs();
}
};
BEAST_DEFINE_TESTSUITE(KnownFormatToGRPC, protocol, ripple);
} // namespace ripple

View File

@@ -491,227 +491,6 @@ public:
}
}
// gRPC stuff
class GetAccountInfoClient : public GRPCTestClientBase
{
public:
org::xrpl::rpc::v1::GetAccountInfoRequest request;
org::xrpl::rpc::v1::GetAccountInfoResponse reply;
explicit GetAccountInfoClient(std::string const& port)
: GRPCTestClientBase(port)
{
}
void
GetAccountInfo()
{
status = stub_->GetAccountInfo(&context, request, &reply);
}
};
void
testSimpleGrpc()
{
testcase("gRPC simple");
using namespace jtx;
std::unique_ptr<Config> config = envconfig(addGrpcConfig);
std::string grpcPort = *(*config)["port_grpc"].get<std::string>("port");
Env env(*this, std::move(config));
Account const alice{"alice"};
env.fund(drops(1000 * 1000 * 1000), alice);
{
// most simple case
GetAccountInfoClient client(grpcPort);
client.request.mutable_account()->set_address(alice.human());
client.GetAccountInfo();
if (!BEAST_EXPECT(client.status.ok()))
{
return;
}
BEAST_EXPECT(
client.reply.account_data().account().value().address() ==
alice.human());
}
{
GetAccountInfoClient client(grpcPort);
client.request.mutable_account()->set_address(alice.human());
client.request.set_queue(true);
client.request.mutable_ledger()->set_sequence(3);
client.GetAccountInfo();
if (!BEAST_EXPECT(client.status.ok()))
return;
BEAST_EXPECT(
client.reply.account_data()
.balance()
.value()
.xrp_amount()
.drops() == 1000 * 1000 * 1000);
BEAST_EXPECT(
client.reply.account_data().account().value().address() ==
alice.human());
BEAST_EXPECT(
client.reply.account_data().sequence().value() ==
env.seq(alice));
BEAST_EXPECT(client.reply.queue_data().txn_count() == 0);
}
}
void
testErrorsGrpc()
{
testcase("gRPC errors");
using namespace jtx;
std::unique_ptr<Config> config = envconfig(addGrpcConfig);
std::string grpcPort = *(*config)["port_grpc"].get<std::string>("port");
Env env(*this, std::move(config));
auto getClient = [&grpcPort]() {
return GetAccountInfoClient(grpcPort);
};
Account const alice{"alice"};
env.fund(drops(1000 * 1000 * 1000), alice);
{
// bad address
auto client = getClient();
client.request.mutable_account()->set_address("deadbeef");
client.GetAccountInfo();
BEAST_EXPECT(!client.status.ok());
}
{
// no account
Account const bogie{"bogie"};
auto client = getClient();
client.request.mutable_account()->set_address(bogie.human());
client.GetAccountInfo();
BEAST_EXPECT(!client.status.ok());
}
{
// bad ledger_index
auto client = getClient();
client.request.mutable_account()->set_address(alice.human());
client.request.mutable_ledger()->set_sequence(0);
client.GetAccountInfo();
BEAST_EXPECT(!client.status.ok());
}
}
void
testSignerListsGrpc()
{
testcase("gRPC singer lists");
using namespace jtx;
std::unique_ptr<Config> config = envconfig(addGrpcConfig);
std::string grpcPort = *(*config)["port_grpc"].get<std::string>("port");
Env env(*this, std::move(config));
auto getClient = [&grpcPort]() {
return GetAccountInfoClient(grpcPort);
};
Account const alice{"alice"};
env.fund(drops(1000 * 1000 * 1000), alice);
{
auto client = getClient();
client.request.mutable_account()->set_address(alice.human());
client.request.set_signer_lists(true);
client.GetAccountInfo();
if (!BEAST_EXPECT(client.status.ok()))
return;
BEAST_EXPECT(client.reply.signer_list().signer_entries_size() == 0);
}
// Give alice a SignerList.
Account const bogie{"bogie"};
Json::Value const smallSigners = signers(alice, 2, {{bogie, 3}});
env(smallSigners);
{
auto client = getClient();
client.request.mutable_account()->set_address(alice.human());
client.request.set_signer_lists(false);
client.GetAccountInfo();
if (!BEAST_EXPECT(client.status.ok()))
return;
BEAST_EXPECT(client.reply.signer_list().signer_entries_size() == 0);
}
{
auto client = getClient();
client.request.mutable_account()->set_address(alice.human());
client.request.set_signer_lists(true);
client.GetAccountInfo();
if (!BEAST_EXPECT(client.status.ok()))
{
return;
}
BEAST_EXPECT(
client.reply.account_data().owner_count().value() == 1);
BEAST_EXPECT(client.reply.signer_list().signer_entries_size() == 1);
}
// Give alice a big signer list
Account const demon{"demon"};
Account const ghost{"ghost"};
Account const haunt{"haunt"};
Account const jinni{"jinni"};
Account const phase{"phase"};
Account const shade{"shade"};
Account const spook{"spook"};
Json::Value const bigSigners = signers(
alice,
4,
{
{bogie, 1},
{demon, 1},
{ghost, 1},
{haunt, 1},
{jinni, 1},
{phase, 1},
{shade, 1},
{spook, 1},
});
env(bigSigners);
std::set<std::string> accounts;
accounts.insert(bogie.human());
accounts.insert(demon.human());
accounts.insert(ghost.human());
accounts.insert(haunt.human());
accounts.insert(jinni.human());
accounts.insert(phase.human());
accounts.insert(shade.human());
accounts.insert(spook.human());
{
auto client = getClient();
client.request.mutable_account()->set_address(alice.human());
client.request.set_signer_lists(true);
client.GetAccountInfo();
if (!BEAST_EXPECT(client.status.ok()))
{
return;
}
BEAST_EXPECT(
client.reply.account_data().owner_count().value() == 1);
auto& signerList = client.reply.signer_list();
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().value() == 1);
BEAST_EXPECT(
accounts.erase(signerList.signer_entries(i)
.account()
.value()
.address()) == 1);
}
BEAST_EXPECT(accounts.size() == 0);
}
}
void
run() override
{
@@ -719,9 +498,6 @@ public:
testSignerLists();
testSignerListsApiVersion2();
testSignerListsV2();
testSimpleGrpc();
testErrorsGrpc();
testSignerListsGrpc();
}
};

View File

@@ -1,138 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2020 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 <ripple/app/ledger/LedgerMaster.h>
#include <ripple/app/misc/TxQ.h>
#include <ripple/basics/mulDiv.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/jss.h>
#include <ripple/rpc/GRPCHandlers.h>
#include <ripple/rpc/impl/RPCHelpers.h>
#include <test/jtx.h>
#include <test/jtx/Env.h>
#include <test/jtx/envconfig.h>
#include <test/rpc/GRPCTestClientBase.h>
namespace ripple {
namespace test {
class Fee_test : public beast::unit_test::suite
{
class GrpcFeeClient : public GRPCTestClientBase
{
public:
org::xrpl::rpc::v1::GetFeeRequest request;
org::xrpl::rpc::v1::GetFeeResponse reply;
explicit GrpcFeeClient(std::string const& grpcPort)
: GRPCTestClientBase(grpcPort)
{
}
void
GetFee()
{
status = stub_->GetFee(&context, request, &reply);
}
};
std::pair<bool, org::xrpl::rpc::v1::GetFeeResponse>
grpcGetFee(std::string const& grpcPort)
{
GrpcFeeClient client(grpcPort);
client.GetFee();
return std::pair<bool, org::xrpl::rpc::v1::GetFeeResponse>(
client.status.ok(), client.reply);
}
void
testFeeGrpc()
{
testcase("Test Fee Grpc");
using namespace test::jtx;
std::unique_ptr<Config> config = envconfig(addGrpcConfig);
std::string grpcPort = *(*config)["port_grpc"].get<std::string>("port");
Env env(*this, std::move(config));
Account A1{"A1"};
Account A2{"A2"};
env.fund(XRP(10000), A1);
env.fund(XRP(10000), A2);
env.close();
env.trust(A2["USD"](1000), A1);
env.close();
for (int i = 0; i < 7; ++i)
{
env(pay(A2, A1, A2["USD"](100)));
if (i == 4)
env.close();
}
auto view = env.current();
auto const metrics = env.app().getTxQ().getMetrics(*env.current());
auto const result = grpcGetFee(grpcPort);
BEAST_EXPECT(result.first == true);
auto reply = result.second;
// current ledger data
BEAST_EXPECT(reply.current_ledger_size() == metrics.txInLedger);
BEAST_EXPECT(reply.current_queue_size() == metrics.txCount);
BEAST_EXPECT(reply.expected_ledger_size() == metrics.txPerLedger);
BEAST_EXPECT(reply.ledger_current_index() == view->info().seq);
BEAST_EXPECT(reply.max_queue_size() == *metrics.txQMaxSize);
// fee levels data
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
org::xrpl::rpc::v1::Fee& fee = *reply.mutable_fee();
auto const baseFee = view->fees().base;
BEAST_EXPECT(
fee.base_fee().drops() ==
toDrops(metrics.referenceFeeLevel, baseFee));
BEAST_EXPECT(
fee.minimum_fee().drops() ==
toDrops(metrics.minProcessingFeeLevel, baseFee));
BEAST_EXPECT(
fee.median_fee().drops() == toDrops(metrics.medFeeLevel, baseFee));
auto openLedgerFee =
toDrops(metrics.openLedgerFeeLevel - FeeLevel64{1}, baseFee) + 1;
BEAST_EXPECT(fee.open_ledger_fee().drops() == openLedgerFee.drops());
}
public:
void
run() override
{
testFeeGrpc();
}
};
BEAST_DEFINE_TESTSUITE(Fee, app, ripple);
} // namespace test
} // namespace ripple

View File

@@ -773,25 +773,6 @@ class ReportingETL_test : public beast::unit_test::suite
testNeedCurrentOrClosed()
{
testcase("NeedCurrentOrClosed");
{
org::xrpl::rpc::v1::GetAccountInfoRequest request;
request.mutable_ledger()->set_sequence(1);
BEAST_EXPECT(!needCurrentOrClosed(request));
request.mutable_ledger()->set_hash("");
BEAST_EXPECT(!needCurrentOrClosed(request));
request.mutable_ledger()->set_shortcut(
org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_VALIDATED);
BEAST_EXPECT(!needCurrentOrClosed(request));
request.mutable_ledger()->set_shortcut(
org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_UNSPECIFIED);
BEAST_EXPECT(!needCurrentOrClosed(request));
request.mutable_ledger()->set_shortcut(
org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CURRENT);
BEAST_EXPECT(needCurrentOrClosed(request));
request.mutable_ledger()->set_shortcut(
org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CLOSED);
BEAST_EXPECT(needCurrentOrClosed(request));
}
{
org::xrpl::rpc::v1::GetLedgerRequest request;
@@ -904,18 +885,6 @@ class ReportingETL_test : public beast::unit_test::suite
org::xrpl::rpc::v1::LedgerSpecifier::SHORTCUT_CURRENT);
BEAST_EXPECT(needCurrentOrClosed(request));
}
{
org::xrpl::rpc::v1::GetFeeRequest feeRequest;
BEAST_EXPECT(!needCurrentOrClosed(feeRequest));
org::xrpl::rpc::v1::GetAccountTransactionHistoryRequest
accountTxRequest;
BEAST_EXPECT(!needCurrentOrClosed(accountTxRequest));
org::xrpl::rpc::v1::GetTransactionRequest txRequest;
BEAST_EXPECT(!needCurrentOrClosed(txRequest));
}
}
void

View File

@@ -1,276 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2020 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 <ripple/basics/StringUtilities.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/jss.h>
#include <test/jtx.h>
#include <test/jtx/WSClient.h>
#include <ripple/resource/Charge.h>
#include <ripple/resource/Fees.h>
#include <ripple/rpc/GRPCHandlers.h>
#include <test/rpc/GRPCTestClientBase.h>
namespace ripple {
namespace test {
class Submit_test : public beast::unit_test::suite
{
public:
class SubmitClient : public GRPCTestClientBase
{
public:
org::xrpl::rpc::v1::SubmitTransactionRequest request;
org::xrpl::rpc::v1::SubmitTransactionResponse reply;
explicit SubmitClient(std::string const& port)
: GRPCTestClientBase(port)
{
}
void
SubmitTransaction()
{
status = stub_->SubmitTransaction(&context, request, &reply);
}
};
struct TestData
{
std::string xrpTxBlob;
std::string xrpTxHash;
std::string usdTxBlob;
std::string usdTxHash;
const static int fund = 10000;
} testData;
void
fillTestData()
{
testcase("fill test data");
using namespace jtx;
Env env(*this, envconfig(addGrpcConfig));
auto const alice = Account("alice");
auto const bob = Account("bob");
env.fund(XRP(TestData::fund), "alice", "bob");
env.trust(bob["USD"](TestData::fund), alice);
env.close();
auto toBinary = [this](std::string const& text) {
auto blob = strUnHex(text);
BEAST_EXPECT(blob);
return std::string{
reinterpret_cast<char const*>(blob->data()), blob->size()};
};
// use a websocket client to fill transaction blobs
auto wsc = makeWSClient(env.app().config());
{
Json::Value jrequestXrp;
jrequestXrp[jss::secret] = toBase58(generateSeed("alice"));
jrequestXrp[jss::tx_json] =
pay("alice", "bob", XRP(TestData::fund / 2));
Json::Value jreply_xrp = wsc->invoke("sign", jrequestXrp);
if (!BEAST_EXPECT(jreply_xrp.isMember(jss::result)))
return;
if (!BEAST_EXPECT(jreply_xrp[jss::result].isMember(jss::tx_blob)))
return;
testData.xrpTxBlob =
toBinary(jreply_xrp[jss::result][jss::tx_blob].asString());
if (!BEAST_EXPECT(jreply_xrp[jss::result].isMember(jss::tx_json)))
return;
if (!BEAST_EXPECT(
jreply_xrp[jss::result][jss::tx_json].isMember(jss::hash)))
return;
testData.xrpTxHash = toBinary(
jreply_xrp[jss::result][jss::tx_json][jss::hash].asString());
}
{
Json::Value jrequestUsd;
jrequestUsd[jss::secret] = toBase58(generateSeed("bob"));
jrequestUsd[jss::tx_json] =
pay("bob", "alice", bob["USD"](TestData::fund / 2));
Json::Value jreply_usd = wsc->invoke("sign", jrequestUsd);
if (!BEAST_EXPECT(jreply_usd.isMember(jss::result)))
return;
if (!BEAST_EXPECT(jreply_usd[jss::result].isMember(jss::tx_blob)))
return;
testData.usdTxBlob =
toBinary(jreply_usd[jss::result][jss::tx_blob].asString());
if (!BEAST_EXPECT(jreply_usd[jss::result].isMember(jss::tx_json)))
return;
if (!BEAST_EXPECT(
jreply_usd[jss::result][jss::tx_json].isMember(jss::hash)))
return;
testData.usdTxHash = toBinary(
jreply_usd[jss::result][jss::tx_json][jss::hash].asString());
}
}
void
testSubmitGoodBlobGrpc()
{
testcase("Submit good blobs, XRP, USD, and same transaction twice");
using namespace jtx;
std::unique_ptr<Config> config = envconfig(addGrpcConfig);
std::string grpcPort = *(*config)["port_grpc"].get<std::string>("port");
Env env(*this, std::move(config));
auto const alice = Account("alice");
auto const bob = Account("bob");
env.fund(XRP(TestData::fund), "alice", "bob");
env.trust(bob["USD"](TestData::fund), alice);
env.close();
auto getClient = [&grpcPort]() { return SubmitClient(grpcPort); };
// XRP
{
auto client = getClient();
client.request.set_signed_transaction(testData.xrpTxBlob);
client.SubmitTransaction();
if (!BEAST_EXPECT(client.status.ok()))
{
return;
}
BEAST_EXPECT(client.reply.engine_result().result() == "tesSUCCESS");
BEAST_EXPECT(client.reply.engine_result_code() == 0);
BEAST_EXPECT(client.reply.hash() == testData.xrpTxHash);
}
// USD
{
auto client = getClient();
client.request.set_signed_transaction(testData.usdTxBlob);
client.SubmitTransaction();
if (!BEAST_EXPECT(client.status.ok()))
{
return;
}
BEAST_EXPECT(client.reply.engine_result().result() == "tesSUCCESS");
BEAST_EXPECT(client.reply.engine_result_code() == 0);
BEAST_EXPECT(client.reply.hash() == testData.usdTxHash);
}
// USD, error, same transaction again
{
auto client = getClient();
client.request.set_signed_transaction(testData.usdTxBlob);
client.SubmitTransaction();
if (!BEAST_EXPECT(client.status.ok()))
{
return;
}
BEAST_EXPECT(
client.reply.engine_result().result() == "tefPAST_SEQ");
BEAST_EXPECT(client.reply.engine_result_code() == -190);
}
}
void
testSubmitErrorBlobGrpc()
{
testcase("Submit error, bad blob, no account");
using namespace jtx;
std::unique_ptr<Config> config = envconfig(addGrpcConfig);
std::string grpcPort = *(*config)["port_grpc"].get<std::string>("port");
Env env(*this, std::move(config));
auto getClient = [&grpcPort]() { return SubmitClient(grpcPort); };
// short transaction blob, cannot parse
{
auto client = getClient();
client.request.set_signed_transaction("deadbeef");
client.SubmitTransaction();
BEAST_EXPECT(!client.status.ok());
}
// bad blob with correct length, cannot parse
{
auto client = getClient();
auto xrpTxBlobCopy(testData.xrpTxBlob);
std::reverse(xrpTxBlobCopy.begin(), xrpTxBlobCopy.end());
client.request.set_signed_transaction(xrpTxBlobCopy);
client.SubmitTransaction();
BEAST_EXPECT(!client.status.ok());
}
// good blob, can parse but no account
{
auto client = getClient();
client.request.set_signed_transaction(testData.xrpTxBlob);
client.SubmitTransaction();
if (!BEAST_EXPECT(client.status.ok()))
{
return;
}
BEAST_EXPECT(
client.reply.engine_result().result() == "terNO_ACCOUNT");
BEAST_EXPECT(client.reply.engine_result_code() == -96);
}
}
void
testSubmitInsufficientFundsGrpc()
{
testcase("Submit good blobs but insufficient funds");
using namespace jtx;
std::unique_ptr<Config> config = envconfig(addGrpcConfig);
std::string grpcPort = *(*config)["port_grpc"].get<std::string>("port");
Env env(*this, std::move(config));
auto const alice = Account("alice");
auto const bob = Account("bob");
// fund 1000 (TestData::fund/10) XRP, the transaction sends 5000
// (TestData::fund/2) XRP, so insufficient funds
env.fund(XRP(TestData::fund / 10), "alice", "bob");
env.trust(bob["USD"](TestData::fund), alice);
env.close();
{
SubmitClient client(grpcPort);
client.request.set_signed_transaction(testData.xrpTxBlob);
client.SubmitTransaction();
if (!BEAST_EXPECT(client.status.ok()))
{
return;
}
BEAST_EXPECT(
client.reply.engine_result().result() == "tecUNFUNDED_PAYMENT");
BEAST_EXPECT(client.reply.engine_result_code() == 104);
}
}
void
run() override
{
fillTestData();
testSubmitGoodBlobGrpc();
testSubmitErrorBlobGrpc();
testSubmitInsufficientFundsGrpc();
}
};
BEAST_DEFINE_TESTSUITE(Submit, app, ripple);
} // namespace test
} // namespace ripple

View File

@@ -1,829 +0,0 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2020 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 <ripple/app/ledger/LedgerMaster.h>
#include <ripple/app/misc/TxQ.h>
#include <ripple/app/rdb/backend/SQLiteDatabase.h>
#include <ripple/basics/mulDiv.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/jss.h>
#include <ripple/rpc/impl/RPCHelpers.h>
#include <test/jtx.h>
#include <test/jtx/Env.h>
#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 {
class Tx_test : public beast::unit_test::suite
{
template <class T>
std::string
toByteString(T const& data)
{
const char* bytes = reinterpret_cast<const char*>(data.data());
return {bytes, data.size()};
}
void
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
{
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;
BEAST_EXPECT(
issuedCurrency.currency().name() == to_string(currency));
BEAST_EXPECT(
issuedCurrency.currency().code() == toByteString(currency));
BEAST_EXPECT(issuedCurrency.value() == to_string(amount.iou()));
BEAST_EXPECT(
issuedCurrency.issuer().address() == toBase58(issue.account));
}
}
void
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);
if (!BEAST_EXPECT(proto.has_account()))
return;
BEAST_EXPECT(proto.account().value().address() == toBase58(account));
STAmount amount = txnSt->getFieldAmount(sfAmount);
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().value().address() ==
toBase58(accountDest));
STAmount fee = txnSt->getFieldAmount(sfFee);
if (!BEAST_EXPECT(proto.has_fee()))
return;
BEAST_EXPECT(proto.fee().drops() == fee.xrp().drops());
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().value() == toByteString(signingPubKey));
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().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().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());
}
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 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 org::xrpl::rpc::v1::Payment_PathElement& protoElement =
protoPath.elements(ind2++);
STPathElement const& elt = *it2;
if (elt.isOffer())
{
if (elt.hasCurrency())
{
Currency const& currency = elt.getCurrency();
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();
if (BEAST_EXPECT(protoElement.has_issuer()))
{
BEAST_EXPECT(
protoElement.issuer().address() ==
toBase58(issuer));
}
}
else
{
BEAST_EXPECT(!protoElement.has_issuer());
}
}
else
{
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 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()));
org::xrpl::rpc::v1::TransactionResult r;
RPC::convert(r, txMeta->getResultTER());
BEAST_EXPECT(
proto.transaction_result().result_type() == r.result_type());
}
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())
{
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());
}
}
}
// gRPC stuff
class GrpcTxClient : public GRPCTestClientBase
{
public:
org::xrpl::rpc::v1::GetTransactionRequest request;
org::xrpl::rpc::v1::GetTransactionResponse reply;
explicit GrpcTxClient(std::string const& port)
: GRPCTestClientBase(port)
{
}
void
Tx()
{
status = stub_->GetTransaction(&context, request, &reply);
}
};
class GrpcAccountTxClient : public GRPCTestClientBase
{
public:
org::xrpl::rpc::v1::GetAccountTransactionHistoryRequest request;
org::xrpl::rpc::v1::GetAccountTransactionHistoryResponse reply;
explicit GrpcAccountTxClient(std::string const& port)
: GRPCTestClientBase(port)
{
}
void
AccountTx()
{
status =
stub_->GetAccountTransactionHistory(&context, request, &reply);
}
};
void
testTxGrpc()
{
testcase("Test Tx Grpc");
using namespace test::jtx;
std::unique_ptr<Config> config = envconfig(addGrpcConfig);
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, 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)
{
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
{
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
auto& ledgerMaster = env.app().getLedgerMaster();
int index = startLegSeq;
for (auto&& tx : txns)
{
auto id = tx->getTransactionID();
auto ledger = ledgerMaster.getLedgerBySeq(index);
for (bool b : {false, true})
{
auto const result = grpcTx(id, b);
BEAST_EXPECT(result.first == true);
BEAST_EXPECT(result.second.ledger_index() == index);
BEAST_EXPECT(result.second.validated() == true);
if (b)
{
Serializer s = tx->getSerializer();
BEAST_EXPECT(
result.second.transaction_binary() == toByteString(s));
}
else
{
cmpPaymentTx(result.second.transaction(), tx);
}
if (!ledger || b)
continue;
auto rawMeta = ledger->txRead(id).second;
if (!rawMeta)
continue;
auto txMeta =
std::make_shared<TxMeta>(id, ledger->seq(), *rawMeta);
cmpMeta(result.second.meta(), txMeta);
cmpDeliveredAmount(
result.second.meta(),
result.second.transaction(),
txMeta,
tx);
auto grpcAccountTx = [&grpcPort](
uint256 const& id,
bool binary,
AccountID const& account)
-> std::
pair<bool, org::xrpl::rpc::v1::GetTransactionResponse> {
GrpcAccountTxClient client(grpcPort);
client.request.set_binary(binary);
client.request.mutable_account()->set_address(
toBase58(account));
client.AccountTx();
org::xrpl::rpc::v1::GetTransactionResponse res;
for (auto const& tx : client.reply.transactions())
{
if (uint256::fromVoid(tx.hash().data()) == id)
{
return {client.status.ok(), tx};
}
}
return {false, res};
};
// Compare result to result from account_tx
auto mentioned = tx->getMentionedAccounts();
if (!BEAST_EXPECT(mentioned.size()))
continue;
auto account = *mentioned.begin();
auto const accountTxResult = grpcAccountTx(id, b, account);
if (!BEAST_EXPECT(accountTxResult.first))
continue;
cmpPaymentTx(accountTxResult.second.transaction(), tx);
cmpMeta(accountTxResult.second.meta(), txMeta);
cmpDeliveredAmount(
accountTxResult.second.meta(),
accountTxResult.second.transaction(),
txMeta,
tx);
}
index++;
}
// Find not existing transaction
auto const tx = env.jt(noop(A1), seq(env.seq(A1))).stx;
for (bool b : {false, true})
{
auto const result = grpcTx(tx->getTransactionID(), b);
BEAST_EXPECT(result.first == false);
}
// Delete one transaction
const auto deletedLedger = (startLegSeq + endLegSeq) / 2;
{
// Remove one of the ledgers from the database directly
dynamic_cast<SQLiteDatabase*>(&env.app().getRelationalDatabase())
->deleteTransactionByLedgerSeq(deletedLedger);
}
for (bool b : {false, true})
{
auto const result = grpcTx(tx->getTransactionID(), b);
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:
void
run() override
{
testTxGrpc();
}
};
BEAST_DEFINE_TESTSUITE(Tx, app, ripple);
} // namespace test
} // namespace ripple