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

View File

@@ -639,7 +639,6 @@ target_sources (rippled PRIVATE
src/ripple/rpc/handlers/WalletPropose.cpp
src/ripple/rpc/impl/DeliveredAmount.cpp
src/ripple/rpc/impl/Handler.cpp
src/ripple/rpc/impl/GRPCHelpers.cpp
src/ripple/rpc/impl/LegacyPathFind.cpp
src/ripple/rpc/impl/RPCHandler.cpp
src/ripple/rpc/impl/RPCHelpers.cpp
@@ -909,7 +908,6 @@ if (tests)
src/test/protocol/BuildInfo_test.cpp
src/test/protocol/InnerObjectFormats_test.cpp
src/test/protocol/Issue_test.cpp
src/test/protocol/KnownFormatToGRPC_test.cpp
src/test/protocol/Hooks_test.cpp
src/test/protocol/PublicKey_test.cpp
src/test/protocol/Quality_test.cpp
@@ -944,7 +942,6 @@ if (tests)
src/test/rpc/DepositAuthorized_test.cpp
src/test/rpc/DeliveredAmount_test.cpp
src/test/rpc/Feature_test.cpp
src/test/rpc/Fee_test.cpp
src/test/rpc/GatewayBalances_test.cpp
src/test/rpc/GetCounts_test.cpp
src/test/rpc/JSONRPC_test.cpp
@@ -967,12 +964,10 @@ if (tests)
src/test/rpc/ServerInfo_test.cpp
src/test/rpc/ShardArchiveHandler_test.cpp
src/test/rpc/Status_test.cpp
src/test/rpc/Submit_test.cpp
src/test/rpc/Subscribe_test.cpp
src/test/rpc/Transaction_test.cpp
src/test/rpc/TransactionEntry_test.cpp
src/test/rpc/TransactionHistory_test.cpp
src/test/rpc/Tx_test.cpp
src/test/rpc/ValidatorInfo_test.cpp
src/test/rpc/ValidatorRPC_test.cpp
src/test/rpc/Version_test.cpp

View File

@@ -113,7 +113,6 @@ test.consensus > ripple.basics
test.consensus > ripple.beast
test.consensus > ripple.consensus
test.consensus > ripple.ledger
test.consensus > ripple.rpc
test.consensus > test.csf
test.consensus > test.toplevel
test.consensus > test.unit_test

View File

@@ -1629,10 +1629,10 @@ ip = 127.0.0.1
admin = 127.0.0.1
protocol = ws
#[port_grpc]
#port = 50051
#ip = 0.0.0.0
#secure_gateway = 127.0.0.1
[port_grpc]
port = 50051
ip = 127.0.0.1
secure_gateway = 127.0.0.1
#[port_ws_public]
#port = 6005

View File

@@ -590,94 +590,6 @@ GRPCServerImpl::setupListeners()
requests.push_back(std::move(callData));
};
{
using cd = CallData<
org::xrpl::rpc::v1::GetFeeRequest,
org::xrpl::rpc::v1::GetFeeResponse>;
addToRequests(std::make_shared<cd>(
service_,
*cq_,
app_,
&org::xrpl::rpc::v1::XRPLedgerAPIService::AsyncService::
RequestGetFee,
doFeeGrpc,
&org::xrpl::rpc::v1::XRPLedgerAPIService::Stub::GetFee,
RPC::NEEDS_CURRENT_LEDGER,
Resource::feeReferenceRPC,
secureGatewayIPs_));
}
{
using cd = CallData<
org::xrpl::rpc::v1::GetAccountInfoRequest,
org::xrpl::rpc::v1::GetAccountInfoResponse>;
addToRequests(std::make_shared<cd>(
service_,
*cq_,
app_,
&org::xrpl::rpc::v1::XRPLedgerAPIService::AsyncService::
RequestGetAccountInfo,
doAccountInfoGrpc,
&org::xrpl::rpc::v1::XRPLedgerAPIService::Stub::GetAccountInfo,
RPC::NO_CONDITION,
Resource::feeReferenceRPC,
secureGatewayIPs_));
}
{
using cd = CallData<
org::xrpl::rpc::v1::GetTransactionRequest,
org::xrpl::rpc::v1::GetTransactionResponse>;
addToRequests(std::make_shared<cd>(
service_,
*cq_,
app_,
&org::xrpl::rpc::v1::XRPLedgerAPIService::AsyncService::
RequestGetTransaction,
doTxGrpc,
&org::xrpl::rpc::v1::XRPLedgerAPIService::Stub::GetTransaction,
RPC::NEEDS_NETWORK_CONNECTION,
Resource::feeReferenceRPC,
secureGatewayIPs_));
}
{
using cd = CallData<
org::xrpl::rpc::v1::SubmitTransactionRequest,
org::xrpl::rpc::v1::SubmitTransactionResponse>;
addToRequests(std::make_shared<cd>(
service_,
*cq_,
app_,
&org::xrpl::rpc::v1::XRPLedgerAPIService::AsyncService::
RequestSubmitTransaction,
doSubmitGrpc,
&org::xrpl::rpc::v1::XRPLedgerAPIService::Stub::SubmitTransaction,
RPC::NEEDS_CURRENT_LEDGER,
Resource::feeMediumBurdenRPC,
secureGatewayIPs_));
}
{
using cd = CallData<
org::xrpl::rpc::v1::GetAccountTransactionHistoryRequest,
org::xrpl::rpc::v1::GetAccountTransactionHistoryResponse>;
addToRequests(std::make_shared<cd>(
service_,
*cq_,
app_,
&org::xrpl::rpc::v1::XRPLedgerAPIService::AsyncService::
RequestGetAccountTransactionHistory,
doAccountTxGrpc,
&org::xrpl::rpc::v1::XRPLedgerAPIService::Stub::
GetAccountTransactionHistory,
RPC::NO_CONDITION,
Resource::feeMediumBurdenRPC,
secureGatewayIPs_));
}
{
using cd = CallData<
org::xrpl::rpc::v1::GetLedgerRequest,

View File

@@ -48,8 +48,6 @@ needCurrentOrClosed(Request& request)
{
// These are the only gRPC requests that specify a ledger
if constexpr (
std::is_same<Request, org::xrpl::rpc::v1::GetAccountInfoRequest>::
value ||
std::is_same<Request, org::xrpl::rpc::v1::GetLedgerRequest>::value ||
std::is_same<Request, org::xrpl::rpc::v1::GetLedgerDataRequest>::
value ||

View File

@@ -1,14 +0,0 @@
syntax = "proto3";
package org.xrpl.rpc.v1;
option java_package = "org.xrpl.rpc.v1";
option java_multiple_files = true;
// A representation of an account address
// Next field: 2
message AccountAddress
{
// base58 encoding of an account
string address = 1;
}

View File

@@ -1,48 +0,0 @@
syntax = "proto3";
package org.xrpl.rpc.v1;
option java_package = "org.xrpl.rpc.v1";
option java_multiple_files = true;
import "org/xrpl/rpc/v1/account.proto";
// Next field: 3
message CurrencyAmount
{
oneof amount
{
XRPDropsAmount xrp_amount = 1;
IssuedCurrencyAmount issued_currency_amount = 2;
}
}
// A representation of an amount of XRP.
// Next field: 2
message XRPDropsAmount
{
uint64 drops = 1 [jstype=JS_STRING];
}
// A representation of an amount of issued currency.
// Next field: 4
message IssuedCurrencyAmount
{
// The currency used to value the amount.
Currency currency = 1;
// The value of the amount. 8 bytes
string value = 2;
// Unique account address of the entity issuing the currency.
AccountAddress issuer = 3;
}
// Next field: 3
message Currency
{
// 3 character ASCII code
string name = 1;
// 160 bit currency code. 20 bytes
bytes code = 2;
}

View File

@@ -1,606 +0,0 @@
syntax = "proto3";
package org.xrpl.rpc.v1;
option java_package = "org.xrpl.rpc.v1";
option java_multiple_files = true;
import "org/xrpl/rpc/v1/amount.proto";
import "org/xrpl/rpc/v1/account.proto";
// These fields are used in many different message types. They can be present
// in one or more transactions, as well as metadata of one or more transactions.
// Each is defined as its own message type with a single field "value", to
// ensure the field is the correct type everywhere it's used
// *** Messages wrapping uint32 ***
message BurnedNFTokens
{
uint32 value = 1;
}
message CancelAfter
{
// time in seconds since Ripple epoch
uint32 value = 1;
}
message ClearFlag
{
uint32 value = 1;
}
message CloseTime
{
// time in seconds since Ripple epoch
uint32 value = 1;
}
message Date
{
// time in seconds since Ripple epoch
uint32 value = 1;
}
message DestinationTag
{
uint32 value = 1;
}
message Expiration
{
// time in seconds since Ripple epoch
uint32 value = 1;
}
message FinishAfter
{
// time in seconds since Ripple epoch
uint32 value = 1;
}
message Flags
{
uint32 value = 1;
}
message HighQualityIn
{
uint32 value = 1;
}
message HighQualityOut
{
uint32 value = 1;
}
message FirstLedgerSequence
{
uint32 value = 1;
}
message LastLedgerSequence
{
uint32 value = 1;
}
message LowQualityIn
{
uint32 value = 1;
}
message LowQualityOut
{
uint32 value = 1;
}
message MintedNFTokens
{
uint32 value = 1;
}
message OfferSequence
{
uint32 value = 1;
}
message OwnerCount
{
uint32 value = 1;
}
message PreviousTransactionLedgerSequence
{
uint32 value = 1;
}
message QualityIn
{
uint32 value = 1;
}
message QualityOut
{
uint32 value = 1;
}
message ReferenceFeeUnits
{
uint32 value = 1;
}
message ReserveBase
{
// in drops
uint32 value = 1;
}
message ReserveIncrement
{
// in drops
uint32 value = 1;
}
message Sequence
{
uint32 value = 1;
}
message SetFlag
{
uint32 value = 1;
}
message SettleDelay
{
uint32 value = 1;
}
message SignerListID
{
uint32 value = 1;
}
message SignerQuorum
{
uint32 value = 1;
}
message SignerWeight
{
// is actually uint16
uint32 value = 1;
}
message SourceTag
{
uint32 value = 1;
}
message TickSize
{
// is actually uint8
uint32 value = 1;
}
message Ticket
{
uint32 value = 1;
}
message TicketCount
{
uint32 value = 1;
}
message TicketSequence
{
uint32 value = 1;
}
message NFTokenTaxon
{
uint32 value = 1;
}
message TransferFee
{
// is actually uint16
uint32 value = 1;
}
message TransferRate
{
uint32 value = 1;
}
// *** Messages wrapping uint64 ***
message BaseFee
{
// in drops
uint64 value = 1 [jstype=JS_STRING];
}
message BookNode
{
uint64 value = 1 [jstype=JS_STRING];
}
message DestinationNode
{
uint64 value = 1 [jstype=JS_STRING];
}
message HighNode
{
uint64 value = 1 [jstype=JS_STRING];
}
message IndexNext
{
uint64 value = 1 [jstype=JS_STRING];
}
message IndexPrevious
{
uint64 value = 1 [jstype=JS_STRING];
}
message LowNode
{
uint64 value = 1 [jstype=JS_STRING];
}
message NFTokenOfferNode
{
uint64 value = 1 [jstype=JS_STRING];
}
message OwnerNode
{
uint64 value = 1 [jstype=JS_STRING];
}
// *** Messages wrapping 16 bytes ***
message EmailHash
{
bytes value = 1;
}
message NFTokenID
{
bytes value = 1;
}
// *** Messages wrapping 20 bytes ***
message TakerGetsIssuer
{
// 20 bytes
bytes value = 1;
}
message TakerPaysIssuer
{
// 20 bytes
bytes value = 1;
}
// *** Messages wrapping 32 bytes ***
message AccountTransactionID
{
// 32 bytes
bytes value = 1;
}
message BookDirectory
{
// 32 btes
bytes value = 1;
}
message Channel
{
// 32 bytes
bytes value = 1;
}
message CheckID
{
// 32 bytes
bytes value = 1;
}
message Hash
{
// 32 bytes
bytes value = 1;
}
message Index
{
// 32 bytes
bytes value = 1;
}
message InvoiceID
{
// 32 bytes
bytes value = 1;
}
message NextPageMin
{
// 32 bytes
bytes value = 1;
}
message NFTokenBuyOffer
{
// 32 bytes
bytes value = 1;
}
message NFTokenSellOffer
{
// 32 bytes
bytes value = 1;
}
message PreviousPageMin
{
// 32 bytes
bytes value = 1;
}
message PreviousTransactionID
{
// 32 bytes
bytes value = 1;
}
message RootIndex
{
// 32 bytes
bytes value = 1;
}
message WalletLocator
{
// 32 bytes
bytes value = 1;
}
// *** Messages wrapping variable length byte arrays ***
message Condition
{
bytes value = 1;
}
message Fulfillment
{
bytes value = 1;
}
message MemoData
{
bytes value = 1;
}
message MemoFormat
{
bytes value = 1;
}
message MemoType
{
bytes value = 1;
}
message MessageKey
{
bytes value = 1;
}
message PublicKey
{
bytes value = 1;
}
message PaymentChannelSignature
{
bytes value = 1;
}
message SigningPublicKey
{
bytes value = 1;
}
message TransactionSignature
{
bytes value = 1;
}
message ValidatorToDisable
{
bytes value = 1;
}
message ValidatorToReEnable
{
bytes value = 1;
}
// *** Messages wrapping a Currency value ***
//
// TODO: if there's a V2 of the API, fix this misspelling.
message TakerGetsCurreny
{
Currency value = 1;
}
message TakerPaysCurrency
{
Currency value = 1;
}
// *** Messages wrapping a CurrencyAmount ***
message Amount
{
// Note, CurrencyAmount is a oneof, that can represent an XRP drops amount
// or an Issued Currency amount. However, in some transaction types/ledger
// objects, this value can only be in drops. For instance, the Amount field
// of a Payment transaction can be specified in XRP drops or an Issued
// Currency amount, but the Amount field of a PaymentChannelClaim
// transaction can only be an XRP drops amount.
CurrencyAmount value = 1;
}
message Balance
{
CurrencyAmount value = 1;
}
message NFTokenBrokerFee
{
CurrencyAmount value = 1;
}
message DeliverMin
{
CurrencyAmount value = 1;
}
message DeliveredAmount
{
CurrencyAmount value = 1;
}
message HighLimit
{
CurrencyAmount value = 1;
}
message LimitAmount
{
CurrencyAmount value = 1;
}
message LowLimit
{
CurrencyAmount value = 1;
}
message SendMax
{
CurrencyAmount value = 1;
}
message TakerGets
{
CurrencyAmount value = 1;
}
message TakerPays
{
CurrencyAmount value = 1;
}
// *** Messages wrapping an AccountAddress ***
message Account
{
AccountAddress value = 1;
}
message Authorize
{
AccountAddress value = 1;
}
message Destination
{
AccountAddress value = 1;
}
message Issuer
{
AccountAddress value = 1;
}
message NFTokenMinter
{
AccountAddress value = 1;
}
message Owner
{
AccountAddress value = 1;
}
message RegularKey
{
AccountAddress value = 1;
}
message Unauthorize
{
AccountAddress value = 1;
}
// *** Messages wrapping a string ***
message Domain
{
string value = 1;
}
message URI
{
string value = 1;
}
// *** Aggregate type messages
// Next field: 3
message NFToken
{
NFTokenID nftoken_id = 1;
URI uri = 2;
}
// Next field: 3
message SignerEntry
{
Account account = 1;
SignerWeight signer_weight = 2;
WalletLocator wallet_locator = 3;
}
// Next field: 3
message DisabledValidator
{
PublicKey public_key = 1;
FirstLedgerSequence ledger_sequence = 2;
}

View File

@@ -1,93 +0,0 @@
syntax = "proto3";
package org.xrpl.rpc.v1;
option java_package = "org.xrpl.rpc.v1";
option java_multiple_files = true;
import "org/xrpl/rpc/v1/ledger_objects.proto";
import "org/xrpl/rpc/v1/amount.proto";
import "org/xrpl/rpc/v1/account.proto";
import "org/xrpl/rpc/v1/ledger.proto";
import "org/xrpl/rpc/v1/common.proto";
// A request to get info about an account.
// Next field: 6
message GetAccountInfoRequest
{
// The address to get info about.
AccountAddress account = 1;
bool strict = 2;
// Which ledger to use to retrieve data.
// If this field is not set, the server will use the open ledger.
// The open ledger includes data that is not validated or final.
// To retrieve the most up to date and validated data, use
// SHORTCUT_VALIDATED
LedgerSpecifier ledger = 3;
bool queue = 4;
bool signer_lists = 5;
string client_ip = 6;
}
// Response to GetAccountInfo RPC
// Next field: 6
message GetAccountInfoResponse
{
AccountRoot account_data = 1;
SignerList signer_list = 2;
uint32 ledger_index = 3;
QueueData queue_data = 4;
bool validated = 5;
}
// Aggregate data about queued transactions
// Next field: 11
message QueueData
{
uint32 txn_count = 1;
bool auth_change_queued = 2;
uint32 lowest_sequence = 3;
uint32 highest_sequence = 4;
XRPDropsAmount max_spend_drops_total = 5;
repeated QueuedTransaction transactions = 6;
uint32 lowest_ticket = 7;
uint32 highest_ticket = 8;
uint32 sequence_count = 9;
uint32 ticket_count = 10;
}
// Data about a single queued transaction
// Next field: 8
message QueuedTransaction
{
bool auth_change = 1;
XRPDropsAmount fee = 2;
uint64 fee_level = 3 [jstype=JS_STRING];
XRPDropsAmount max_spend_drops = 4;
Sequence sequence = 5;
LastLedgerSequence last_ledger_sequence = 6;
Ticket ticket = 7;
}

View File

@@ -1,75 +0,0 @@
syntax = "proto3";
import "org/xrpl/rpc/v1/get_transaction.proto";
import "org/xrpl/rpc/v1/account.proto";
import "org/xrpl/rpc/v1/ledger.proto";
package org.xrpl.rpc.v1;
option java_package = "org.xrpl.rpc.v1";
option java_multiple_files = true;
// Next field: 8
message GetAccountTransactionHistoryRequest
{
AccountAddress account = 1;
// What ledger to include results from. Specifying a not yet validated
// ledger results in an error. Not specifying a ledger uses the entire
// range of validated ledgers available to the server.
// Note, this parameter acts as a filter, and can only reduce the number of
// results. Specifying a single ledger will return only transactions from
// that ledger. This includes specifying a ledger with a Shortcut. For
// example, specifying SHORTCUT_VALIDATED will result in only transactions
// that were part of the most recently validated ledger being returned.
// Specifying a range of ledgers results in only transactions that were
// included in a ledger within the specified range being returned.
oneof ledger
{
LedgerSpecifier ledger_specifier = 2;
LedgerRange ledger_range = 3;
};
// Return results as binary blobs. Defaults to false.
bool binary = 4;
// If set to true, returns values indexed by older ledger first.
// Default to false.
bool forward = 5;
// Limit the number of results. Server may choose a lower limit.
// If this value is 0, the limit is ignored and the number of results
// returned is determined by the server
uint32 limit = 6;
// Marker to resume where previous request left off
// Used for pagination
Marker marker = 7;
}
// Next field: 8
message GetAccountTransactionHistoryResponse
{
AccountAddress account = 1;
uint32 ledger_index_min = 2;
uint32 ledger_index_max = 3;
uint32 limit = 4;
Marker marker = 5;
repeated GetTransactionResponse transactions = 6;
bool validated = 7;
}
// Next field: 3
message Marker
{
uint32 ledger_index = 1;
uint32 account_sequence = 2;
}

View File

@@ -1,58 +0,0 @@
syntax = "proto3";
package org.xrpl.rpc.v1;
option java_package = "org.xrpl.rpc.v1";
option java_multiple_files = true;
import "org/xrpl/rpc/v1/amount.proto";
// A request for the current transaction fee on the ledger.
// Next field: 1
message GetFeeRequest
{
string client_ip = 1;
}
// Response to a GetFee RPC
// Next field: 8
message GetFeeResponse
{
uint64 current_ledger_size = 1 [jstype=JS_STRING];
uint64 current_queue_size = 2 [jstype=JS_STRING];
Fee fee = 3;
uint64 expected_ledger_size = 4 [jstype=JS_STRING];
uint32 ledger_current_index = 5;
FeeLevels levels = 6;
uint64 max_queue_size = 7 [jstype=JS_STRING];
}
// Next field: 5
message Fee
{
XRPDropsAmount base_fee = 1;
XRPDropsAmount median_fee = 2;
XRPDropsAmount minimum_fee = 3;
XRPDropsAmount open_ledger_fee = 4;
}
// Next field: 5
message FeeLevels
{
uint64 median_level = 1 [jstype=JS_STRING];
uint64 minimum_level = 2 [jstype=JS_STRING];
uint64 open_ledger_level = 3 [jstype=JS_STRING];
uint64 reference_level = 4 [jstype=JS_STRING];
}

View File

@@ -1,62 +0,0 @@
syntax = "proto3";
package org.xrpl.rpc.v1;
option java_package = "org.xrpl.rpc.v1";
option java_multiple_files = true;
import "org/xrpl/rpc/v1/meta.proto";
import "org/xrpl/rpc/v1/ledger.proto";
import "org/xrpl/rpc/v1/transaction.proto";
import "org/xrpl/rpc/v1/common.proto";
// Next field: 4
message GetTransactionRequest {
// hash of the transaction. 32 bytes
// ATTN: this is in binary, not hex. The JSON API accepts a hex string for
// a transaction hash, but here we need that hex string converted into its
// binary form. Each pair of hex characters should be converted into its
// corresponding byte. For example, the 4 character hex string "00FF"
// should be converted to a 2 byte array: [0, 255]
bytes hash = 1;
// if true, return data in binary format. defaults to false
bool binary = 2;
// If the transaction was not found, server will report whether the entire
// specified range was searched. The value is contained in the error message.
// The error message is of the form:
// "txn not found. searched_all = [true,false]"
// If the transaction was found, this parameter is ignored.
LedgerRange ledger_range = 3;
string client_ip = 4;
}
// Next field: 9
message GetTransactionResponse {
oneof serialized_transaction {
Transaction transaction = 1;
// Variable length
bytes transaction_binary = 2;
};
// Sequence number of ledger that contains this transaction
uint32 ledger_index = 3;
// 32 bytes
bytes hash = 4;
// whether the ledger has been validated
bool validated = 5;
// metadata about the transaction
oneof serialized_meta {
Meta meta = 6;
// Variable length
bytes meta_binary = 7;
}
Date date = 8;
}

View File

@@ -25,16 +25,6 @@ message LedgerSpecifier
}
}
// Next field: 3
message LedgerRange
{
uint32 ledger_index_min = 1;
// Note, if ledger_index_min is non-zero and ledger_index_max is 0, the
// software will use the max validated ledger in place of ledger_index_max
uint32 ledger_index_max = 2;
};
// Next field: 3
message RawLedgerObject

View File

@@ -1,418 +0,0 @@
syntax = "proto3";
package org.xrpl.rpc.v1;
option java_package = "org.xrpl.rpc.v1";
option java_multiple_files = true;
import "org/xrpl/rpc/v1/common.proto";
// Next field: 17
message LedgerObject
{
oneof object
{
AccountRoot account_root = 1;
Amendments amendments = 2;
Check check = 3;
DepositPreauthObject deposit_preauth = 4;
DirectoryNode directory_node = 5;
Escrow escrow = 6;
FeeSettings fee_settings = 7;
LedgerHashes ledger_hashes = 8;
NFTokenOffer nftoken_offer = 15;
NFTokenPage nftoken_page = 16;
Offer offer = 9;
PayChannel pay_channel = 10;
RippleState ripple_state = 11;
SignerList signer_list = 12;
NegativeUNL negative_unl = 13;
TicketObject ticket = 14;
}
}
// Next field: 15
enum LedgerEntryType
{
LEDGER_ENTRY_TYPE_UNSPECIFIED = 0;
LEDGER_ENTRY_TYPE_ACCOUNT_ROOT = 1;
LEDGER_ENTRY_TYPE_AMENDMENTS = 2;
LEDGER_ENTRY_TYPE_CHECK = 3;
LEDGER_ENTRY_TYPE_DEPOSIT_PREAUTH = 4;
LEDGER_ENTRY_TYPE_DIRECTORY_NODE = 5;
LEDGER_ENTRY_TYPE_ESCROW = 6;
LEDGER_ENTRY_TYPE_FEE_SETTINGS = 7;
LEDGER_ENTRY_TYPE_LEDGER_HASHES = 8;
LEDGER_ENTRY_TYPE_OFFER = 9;
LEDGER_ENTRY_TYPE_PAY_CHANNEL = 10;
LEDGER_ENTRY_TYPE_RIPPLE_STATE = 11;
LEDGER_ENTRY_TYPE_SIGNER_LIST = 12;
LEDGER_ENTRY_TYPE_NEGATIVE_UNL = 13;
LEDGER_ENTRY_TYPE_TICKET = 14;
LEDGER_ENTRY_TYPE_NFTOKEN_OFFER = 15;
LEDGER_ENTRY_TYPE_NFTOKEN_PAGE = 16;
}
// Next field: 19
message AccountRoot
{
Account account = 1;
Balance balance = 2;
BurnedNFTokens burned_nftokens = 16;
Sequence sequence = 3;
Flags flags = 4;
OwnerCount owner_count = 5;
PreviousTransactionID previous_transaction_id = 6;
PreviousTransactionLedgerSequence previous_transaction_ledger_sequence = 7;
AccountTransactionID account_transaction_id = 8;
Domain domain = 9;
EmailHash email_hash = 10;
MessageKey message_key = 11;
MintedNFTokens minted_nftokens = 17;
NFTokenMinter nftoken_minter = 18;
RegularKey regular_key = 12;
TickSize tick_size = 13;
TicketCount ticket_count = 15;
TransferRate transfer_rate = 14;
}
// Next field: 4
message Amendments
{
// Next field: 2
message Amendment
{
// 32 bytes
bytes value = 1;
}
// Next field: 3
message Majority
{
Amendment amendment = 1;
CloseTime close_time = 2;
}
repeated Amendment amendments = 1;
repeated Majority majorities = 2;
Flags flags = 3;
}
// Next field: 14
message Check
{
Account account = 1;
Destination destination = 2;
Flags flags = 3;
OwnerNode owner_node = 4;
PreviousTransactionID previous_transaction_id = 5;
PreviousTransactionLedgerSequence previous_transaction_ledger_sequence = 6;
SendMax send_max = 7;
Sequence sequence = 8;
DestinationNode destination_node = 9;
DestinationTag destination_tag = 10;
Expiration expiration = 11;
InvoiceID invoice_id = 12;
SourceTag source_tag = 13;
}
// Next field: 7
message DepositPreauthObject
{
Account account = 1;
Authorize authorize = 2;
Flags flags = 3;
OwnerNode owner_node = 4;
PreviousTransactionID previous_transaction_id = 5;
PreviousTransactionLedgerSequence previous_transaction_ledger_sequence = 6;
}
// Next field: 12
message DirectoryNode
{
Flags flags = 1;
RootIndex root_index = 2;
repeated Index indexes = 3;
IndexNext index_next = 4;
IndexPrevious index_previous = 5;
Owner owner = 6;
TakerPaysCurrency taker_pays_currency = 7;
TakerPaysIssuer taker_pays_issuer = 8;
TakerGetsCurreny taker_gets_currency = 9;
TakerGetsIssuer taker_gets_issuer = 10;
NFTokenID nftoken_id = 11;
}
// Next field: 14
message Escrow
{
Account account = 1;
Destination destination = 2;
Amount amount = 3;
Condition condition = 4;
CancelAfter cancel_after = 5;
FinishAfter finish_after = 6;
Flags flags = 7;
SourceTag source_tag = 8;
DestinationTag destination_tag = 9;
OwnerNode owner_node = 10;
DestinationNode destination_node = 11;
PreviousTransactionID previous_transaction_id = 12;
PreviousTransactionLedgerSequence previous_transaction_ledger_sequence = 13;
}
// Next field: 6
message FeeSettings
{
BaseFee base_fee = 1;
ReferenceFeeUnits reference_fee_units = 2;
ReserveBase reserve_base = 3;
ReserveIncrement reserve_increment = 4;
Flags flags = 5;
}
// Next field: 4
message LedgerHashes
{
LastLedgerSequence last_ledger_sequence = 1;
repeated Hash hashes = 2;
Flags flags = 3;
}
// Next field: 12
message Offer
{
Account account = 1;
Sequence sequence = 2;
Flags flags = 3;
TakerPays taker_pays = 4;
TakerGets taker_gets = 5;
BookDirectory book_directory = 6;
BookNode book_node = 7;
OwnerNode owner_node = 8;
Expiration expiration = 9;
PreviousTransactionID previous_transaction_id = 10;
PreviousTransactionLedgerSequence previous_transaction_ledger_sequence = 11;
}
// Next field: 11
message NFTokenOffer
{
Flags flags = 1;
Owner owner = 2;
NFTokenID nftoken_id = 3;
Amount amount = 4;
OwnerNode owner_node = 5;
NFTokenOfferNode nftoken_offer_node = 6;
Destination destination = 7;
Expiration expiration = 8;
PreviousTransactionID previous_transaction_id = 9;
PreviousTransactionLedgerSequence previous_transaction_ledger_sequence = 10;
}
// Next field: 7
message NFTokenPage
{
Flags flags = 1;
PreviousPageMin previous_page_min = 2;
NextPageMin next_page_min = 3;
repeated NFToken nftokens = 4;
PreviousTransactionID previous_transaction_id = 5;
PreviousTransactionLedgerSequence previous_transaction_ledger_sequence = 6;
}
// Next field: 13
message PayChannel
{
Account account = 1;
Destination destination = 2;
Amount amount = 3;
Balance balance = 4;
PublicKey public_key = 5;
SettleDelay settle_delay = 6;
OwnerNode owner_node = 7;
PreviousTransactionID previous_transaction_id = 8;
PreviousTransactionLedgerSequence previous_transaction_ledger_sequence = 9;
Flags flags = 10;
Expiration expiration = 11;
CancelAfter cancel_after = 12;
SourceTag source_tag = 13;
DestinationTag destination_tag = 14;
DestinationNode destination_node = 15;
}
// Next field: 13
message RippleState
{
Balance balance = 1;
Flags flags = 2;
LowLimit low_limit = 3;
HighLimit high_limit = 4;
LowNode low_node = 5;
HighNode high_node = 6;
LowQualityIn low_quality_in = 7;
LowQualityOut low_quality_out = 8;
HighQualityIn high_quality_in = 9;
HighQualityOut high_quality_out = 10;
PreviousTransactionID previous_transaction_id = 11;
PreviousTransactionLedgerSequence previous_transaction_ledger_sequence = 12;
}
// Next field: 8
message SignerList
{
Flags flags = 1;
PreviousTransactionID previous_transaction_id = 2;
PreviousTransactionLedgerSequence previous_transaction_ledger_sequence = 3;
OwnerNode owner_node = 4;
repeated SignerEntry signer_entries = 5;
SignerListID signer_list_id = 6;
SignerQuorum signer_quorum = 7;
}
// Next field: 7
message TicketObject
{
Flags flags = 1;
Account account = 2;
OwnerNode owner_node = 3;
PreviousTransactionID previous_transaction_id = 4;
PreviousTransactionLedgerSequence previous_transaction_ledger_sequence = 5;
TicketSequence ticket_sequence = 6;
}
// Next field: 5
message NegativeUNL
{
repeated DisabledValidator disabled_validators = 1;
ValidatorToDisable validator_to_disable = 2;
ValidatorToReEnable validator_to_re_enable = 3;
Flags flags = 4;
}

View File

@@ -1,116 +0,0 @@
syntax = "proto3";
package org.xrpl.rpc.v1;
option java_package = "org.xrpl.rpc.v1";
option java_multiple_files = true;
import "org/xrpl/rpc/v1/ledger_objects.proto";
import "org/xrpl/rpc/v1/common.proto";
message SubmitMetadataRequest
{
repeated AffectedNode affected_nodes = 1;
uint32 ledger_sequence = 2;
}
message SubmitMetadataResponse
{
bool success = 1;
string msg = 2;
}
message PrepareLedgerRequest
{
uint32 ledger_index = 1;
}
message PrepareLedgerResponse
{
bool success = 1;
string msg = 2;
}
// Next field: 5
message Meta
{
// index in ledger
uint64 transaction_index = 1 [jstype=JS_STRING];
// result code indicating whether the transaction succeeded or failed
TransactionResult transaction_result = 2;
repeated AffectedNode affected_nodes = 3;
DeliveredAmount delivered_amount = 4;
}
// Next field: 3
message TransactionResult
{
// Next field: 7
enum ResultType
{
RESULT_TYPE_UNSPECIFIED = 0;
// Claimed cost only
RESULT_TYPE_TEC = 1;
// Failure
RESULT_TYPE_TEF = 2;
// Local error
RESULT_TYPE_TEL = 3;
// Malformed transaction
RESULT_TYPE_TEM = 4;
// Retry
RESULT_TYPE_TER = 5;
// Success
RESULT_TYPE_TES = 6;
}
// category of the transaction result
ResultType result_type = 1;
// full result string, i.e. tesSUCCESS
string result = 2;
}
// Next field: 6
message AffectedNode
{
LedgerEntryType ledger_entry_type = 1;
// 32 bytes
bytes ledger_index = 2;
oneof node
{
CreatedNode created_node = 3;
DeletedNode deleted_node = 4;
ModifiedNode modified_node = 5;
}
}
// Next field: 2
message CreatedNode
{
LedgerObject new_fields = 1;
}
// Next field: 2
message DeletedNode
{
LedgerObject final_fields = 1;
}
// Next field: 5
message ModifiedNode {
LedgerObject final_fields = 1;
LedgerObject previous_fields = 2;
PreviousTransactionID previous_transaction_id = 3;
PreviousTransactionLedgerSequence previous_transaction_ledger_sequence = 4;
}

View File

@@ -1,37 +0,0 @@
syntax = "proto3";
package org.xrpl.rpc.v1;
option java_package = "org.xrpl.rpc.v1";
option java_multiple_files = true;
import "org/xrpl/rpc/v1/meta.proto";
// A request to submit the signed transaction to the ledger.
// Next field: 3
message SubmitTransactionRequest
{
// The signed transaction to submit.
bytes signed_transaction = 1;
bool fail_hard = 2;
string client_ip = 3;
}
// A response when a signed transaction is submitted to the ledger.
// Next field: 5
message SubmitTransactionResponse
{
// Code indicating the preliminary result of the transaction.
TransactionResult engine_result = 1;
// Numeric code indicating the preliminary result of the transaction,
// directly correlated to engine_result.
int64 engine_result_code = 2;
// Human-readable explanation of the transaction's preliminary result.
string engine_result_message = 3;
// 32 bytes
bytes hash = 4;
}

View File

@@ -1,390 +0,0 @@
syntax = "proto3";
package org.xrpl.rpc.v1;
option java_package = "org.xrpl.rpc.v1";
option java_multiple_files = true;
import "org/xrpl/rpc/v1/common.proto";
import "org/xrpl/rpc/v1/amount.proto";
import "org/xrpl/rpc/v1/account.proto";
// A message encompassing all transaction types
// Next field: 37
message Transaction
{
Account account = 1;
XRPDropsAmount fee = 2;
Sequence sequence = 3;
// Data specific to the type of transaction
oneof transaction_data
{
Payment payment = 4;
AccountSet account_set = 13;
AccountDelete account_delete = 14;
CheckCancel check_cancel = 15;
CheckCash check_cash = 16;
CheckCreate check_create = 17;
DepositPreauth deposit_preauth = 18;
EscrowCancel escrow_cancel = 19;
EscrowCreate escrow_create = 20;
EscrowFinish escrow_finish = 21;
NFTokenAcceptOffer nftoken_accept_offer = 32;
NFTokenBurn nftoken_burn = 33;
NFTokenCancelOffer nftoken_cancel_offer = 34;
NFTokenCreateOffer nftoken_create_offer = 35;
NFTokenMint nftoken_mint = 36;
OfferCancel offer_cancel = 22;
OfferCreate offer_create = 23;
PaymentChannelClaim payment_channel_claim = 24;
PaymentChannelCreate payment_channel_create= 25;
PaymentChannelFund payment_channel_fund = 26;
SetRegularKey set_regular_key = 27;
SignerListSet signer_list_set = 28;
TicketCreate ticket_create = 30;
TrustSet trust_set = 29;
}
SigningPublicKey signing_public_key = 5;
TransactionSignature transaction_signature = 6;
Flags flags = 7;
LastLedgerSequence last_ledger_sequence = 8;
SourceTag source_tag = 9;
repeated Memo memos = 10;
repeated Signer signers = 11;
AccountTransactionID account_transaction_id = 12;
TicketSequence ticket_sequence = 31;
}
// Next field: 4
message Memo
{
MemoData memo_data = 1;
MemoFormat memo_format = 2;
MemoType memo_type = 3;
}
// Next field: 4
message Signer
{
Account account = 1;
TransactionSignature transaction_signature = 2;
SigningPublicKey signing_public_key = 3;
}
// Next field: 9
message AccountSet
{
ClearFlag clear_flag = 1;
Domain domain = 2;
EmailHash email_hash = 3;
MessageKey message_key = 4;
SetFlag set_flag = 5;
TransferRate transfer_rate = 6;
TickSize tick_size = 7;
NFTokenMinter nftoken_minter = 8;
}
// Next field: 3
message AccountDelete
{
Destination destination = 1;
DestinationTag destination_tag = 2;
}
// Next field: 2
message CheckCancel
{
CheckID check_id = 1;
}
// Next field: 4
message CheckCash
{
CheckID check_id = 1;
oneof amount_oneof
{
Amount amount = 2;
DeliverMin deliver_min = 3;
}
}
// Next field: 6
message CheckCreate
{
Destination destination = 1;
SendMax send_max = 2;
DestinationTag destination_tag = 3;
Expiration expiration = 4;
InvoiceID invoice_id = 5;
}
// Next field: 3
message DepositPreauth
{
oneof authorization_oneof
{
Authorize authorize = 1;
Unauthorize unauthorize = 2;
}
}
// Next field: 3
message EscrowCancel
{
Owner owner = 1;
OfferSequence offer_sequence = 2;
}
// Next field: 7
message EscrowCreate
{
Amount amount = 1;
Destination destination = 2;
CancelAfter cancel_after = 3;
FinishAfter finish_after = 4;
Condition condition = 5;
DestinationTag destination_tag = 6;
}
// Next field: 5
message EscrowFinish
{
Owner owner = 1;
OfferSequence offer_sequence = 2;
Condition condition = 3;
Fulfillment fulfillment = 4;
}
// Next field: 4
message NFTokenAcceptOffer
{
NFTokenBrokerFee nftoken_broker_fee = 1;
NFTokenBuyOffer nftoken_buy_offer = 2;
NFTokenSellOffer nftoken_sell_offer = 3;
}
// Next field: 3
message NFTokenBurn
{
Owner owner = 1;
NFTokenID nftoken_id = 2;
}
// Next field: 2
message NFTokenCancelOffer
{
repeated Index nftoken_offers = 1;
}
// Next field: 6
message NFTokenCreateOffer
{
Amount amount = 1;
Destination destination = 2;
Expiration expiration = 3;
Owner owner = 4;
NFTokenID nftoken_id = 5;
}
// Next field: 5
message NFTokenMint
{
Issuer issuer = 1;
NFTokenTaxon nftoken_taxon = 2;
TransferFee transfer_fee = 3;
URI uri = 4;
}
// Next field: 2
message OfferCancel
{
OfferSequence offer_sequence = 1;
}
// Next field: 5
message OfferCreate
{
Expiration expiration = 1;
OfferSequence offer_sequence = 2;
TakerGets taker_gets = 3;
TakerPays taker_pays = 4;
}
// Next field: 8
message Payment
{
// Next field: 4
message PathElement
{
AccountAddress account = 1;
Currency currency = 2;
AccountAddress issuer = 3;
}
// Next field: 2
message Path
{
repeated PathElement elements = 1;
}
Amount amount = 1;
Destination destination = 2;
DestinationTag destination_tag = 3;
InvoiceID invoice_id = 4;
repeated Path paths = 5;
SendMax send_max = 6;
DeliverMin deliver_min = 7;
}
// Next field: 6
message PaymentChannelClaim
{
Channel channel = 1;
Balance balance = 2;
Amount amount = 3;
PaymentChannelSignature payment_channel_signature = 4;
PublicKey public_key = 5;
}
// Next field: 7
message PaymentChannelCreate
{
Amount amount = 1;
Destination destination = 2;
SettleDelay settle_delay = 3;
PublicKey public_key = 4;
CancelAfter cancel_after = 5;
DestinationTag destination_tag = 6;
}
// Next field: 4
message PaymentChannelFund
{
Channel channel = 1;
Amount amount = 2;
Expiration expiration = 3;
}
// Next field: 2
message SetRegularKey
{
RegularKey regular_key = 1;
}
// Next field: 3
message SignerListSet
{
SignerQuorum signer_quorum = 1;
repeated SignerEntry signer_entries = 2;
}
// Next field: 2
message TicketCreate
{
TicketCount count = 1;
}
// Next field: 4
message TrustSet
{
LimitAmount limit_amount = 1;
QualityIn quality_in = 2;
QualityOut quality_out = 3;
}

View File

@@ -4,39 +4,18 @@ package org.xrpl.rpc.v1;
option java_package = "org.xrpl.rpc.v1";
option java_multiple_files = true;
import "org/xrpl/rpc/v1/get_account_info.proto";
import "org/xrpl/rpc/v1/get_fee.proto";
import "org/xrpl/rpc/v1/submit.proto";
import "org/xrpl/rpc/v1/get_transaction.proto";
import "org/xrpl/rpc/v1/get_account_transaction_history.proto";
import "org/xrpl/rpc/v1/get_ledger.proto";
import "org/xrpl/rpc/v1/get_ledger_entry.proto";
import "org/xrpl/rpc/v1/get_ledger_data.proto";
import "org/xrpl/rpc/v1/get_ledger_diff.proto";
// RPCs available to interact with the XRP Ledger.
// The gRPC API mimics the JSON API. Refer to xrpl.org for documentation
// These methods are binary only methods for retrieiving arbitrary ledger state
// via gRPC. These methods are used by clio and reporting mode, but can also be
// used by any client that wants to extract ledger state in an efficient manner.
// They do not directly mimic the JSON equivalent methods.
service XRPLedgerAPIService {
// Get account info for an account on the XRP Ledger.
rpc GetAccountInfo (GetAccountInfoRequest) returns (GetAccountInfoResponse);
// Get the fee for a transaction on the XRP Ledger.
rpc GetFee (GetFeeRequest) returns (GetFeeResponse);
// Submit a signed transaction to the XRP Ledger.
rpc SubmitTransaction (SubmitTransactionRequest) returns (SubmitTransactionResponse);
// Get the status of a transaction
rpc GetTransaction(GetTransactionRequest) returns (GetTransactionResponse);
// Get all validated transactions associated with a given account
rpc GetAccountTransactionHistory(GetAccountTransactionHistoryRequest) returns (GetAccountTransactionHistoryResponse);
/////////////////////////////////////////////////////////////////////////////
// The below methods do not mimic the JSON API exactly, and are mostly binary
// Get a specific ledger, optionally including transactions and any modified,
// added or deleted ledger objects
rpc GetLedger(GetLedgerRequest) returns (GetLedgerResponse);

View File

@@ -22,7 +22,6 @@
#include <ripple/protocol/Protocol.h>
#include <ripple/protocol/STAmount.h>
#include <org/xrpl/rpc/v1/amount.pb.h>
#include <functional>
#include <memory>

View File

@@ -34,28 +34,6 @@ namespace ripple {
* the status will be sent to the client, and the response will be ommitted
*/
std::pair<org::xrpl::rpc::v1::GetAccountInfoResponse, grpc::Status>
doAccountInfoGrpc(
RPC::GRPCContext<org::xrpl::rpc::v1::GetAccountInfoRequest>& context);
std::pair<org::xrpl::rpc::v1::GetFeeResponse, grpc::Status>
doFeeGrpc(RPC::GRPCContext<org::xrpl::rpc::v1::GetFeeRequest>& context);
std::pair<org::xrpl::rpc::v1::SubmitTransactionResponse, grpc::Status>
doSubmitGrpc(
RPC::GRPCContext<org::xrpl::rpc::v1::SubmitTransactionRequest>& context);
// NOTE, this only supports Payment transactions at this time
std::pair<org::xrpl::rpc::v1::GetTransactionResponse, grpc::Status>
doTxGrpc(RPC::GRPCContext<org::xrpl::rpc::v1::GetTransactionRequest>& context);
std::pair<
org::xrpl::rpc::v1::GetAccountTransactionHistoryResponse,
grpc::Status>
doAccountTxGrpc(
RPC::GRPCContext<org::xrpl::rpc::v1::GetAccountTransactionHistoryRequest>&
context);
std::pair<org::xrpl::rpc::v1::GetLedgerResponse, grpc::Status>
doLedgerGrpc(RPC::GRPCContext<org::xrpl::rpc::v1::GetLedgerRequest>& context);

View File

@@ -27,7 +27,6 @@
#include <ripple/protocol/jss.h>
#include <ripple/rpc/Context.h>
#include <ripple/rpc/GRPCHandlers.h>
#include <ripple/rpc/impl/GRPCHelpers.h>
#include <ripple/rpc/impl/RPCHelpers.h>
#include <grpc/status.h>
@@ -222,95 +221,4 @@ doAccountInfo(RPC::JsonContext& context)
return result;
}
std::pair<org::xrpl::rpc::v1::GetAccountInfoResponse, grpc::Status>
doAccountInfoGrpc(
RPC::GRPCContext<org::xrpl::rpc::v1::GetAccountInfoRequest>& context)
{
// Return values
org::xrpl::rpc::v1::GetAccountInfoResponse result;
grpc::Status status = grpc::Status::OK;
// input
org::xrpl::rpc::v1::GetAccountInfoRequest& params = context.params;
// get ledger
std::shared_ptr<ReadView const> ledger;
auto lgrStatus = RPC::ledgerFromRequest(ledger, context);
if (lgrStatus || !ledger)
{
grpc::Status errorStatus;
if (lgrStatus.toErrorCode() == rpcINVALID_PARAMS)
{
errorStatus = grpc::Status(
grpc::StatusCode::INVALID_ARGUMENT, lgrStatus.message());
}
else
{
errorStatus =
grpc::Status(grpc::StatusCode::NOT_FOUND, lgrStatus.message());
}
return {result, errorStatus};
}
result.set_ledger_index(ledger->info().seq);
result.set_validated(
RPC::isValidated(context.ledgerMaster, *ledger, context.app));
// decode account
AccountID accountID;
std::string strIdent = params.account().address();
error_code_i code =
RPC::accountFromStringWithCode(accountID, strIdent, params.strict());
if (code != rpcSUCCESS)
{
grpc::Status errorStatus{
grpc::StatusCode::INVALID_ARGUMENT, "invalid account"};
return {result, errorStatus};
}
// get account data
auto const sleAccepted = ledger->read(keylet::account(accountID));
if (sleAccepted)
{
RPC::convert(*result.mutable_account_data(), *sleAccepted);
// signer lists
if (params.signer_lists())
{
auto const sleSigners = ledger->read(keylet::signers(accountID));
if (sleSigners)
{
org::xrpl::rpc::v1::SignerList& signerListProto =
*result.mutable_signer_list();
RPC::convert(signerListProto, *sleSigners);
}
}
// queued transactions
if (params.queue())
{
if (!ledger->open())
{
grpc::Status errorStatus{
grpc::StatusCode::INVALID_ARGUMENT,
"requested queue but ledger is not open"};
return {result, errorStatus};
}
std::vector<TxQ::TxDetails> const txs =
context.app.getTxQ().getAccountTxs(accountID);
org::xrpl::rpc::v1::QueueData& queueData =
*result.mutable_queue_data();
RPC::convert(queueData, txs);
}
}
else
{
grpc::Status errorStatus{
grpc::StatusCode::NOT_FOUND, "account not found"};
return {result, errorStatus};
}
return {result, status};
}
} // namespace ripple

View File

@@ -35,7 +35,6 @@
#include <ripple/rpc/Context.h>
#include <ripple/rpc/DeliveredAmount.h>
#include <ripple/rpc/Role.h>
#include <ripple/rpc/impl/GRPCHelpers.h>
#include <ripple/rpc/impl/RPCHelpers.h>
#include <grpcpp/grpcpp.h>
@@ -51,69 +50,6 @@ using AccountTxResult = RelationalDatabase::AccountTxResult;
using LedgerShortcut = RelationalDatabase::LedgerShortcut;
using LedgerSpecifier = RelationalDatabase::LedgerSpecifier;
// parses args into a ledger specifier, or returns a grpc status object on error
std::variant<std::optional<LedgerSpecifier>, grpc::Status>
parseLedgerArgs(
org::xrpl::rpc::v1::GetAccountTransactionHistoryRequest const& params)
{
grpc::Status status;
if (params.has_ledger_range())
{
uint32_t min = params.ledger_range().ledger_index_min();
uint32_t max = params.ledger_range().ledger_index_max();
// if min is set but not max, need to set max
if (min != 0 && max == 0)
{
max = UINT32_MAX;
}
return LedgerRange{min, max};
}
else if (params.has_ledger_specifier())
{
LedgerSpecifier ledger;
auto& specifier = params.ledger_specifier();
using LedgerCase = org::xrpl::rpc::v1::LedgerSpecifier::LedgerCase;
LedgerCase ledgerCase = specifier.ledger_case();
if (ledgerCase == LedgerCase::kShortcut)
{
using LedgerSpecifier = org::xrpl::rpc::v1::LedgerSpecifier;
if (specifier.shortcut() == LedgerSpecifier::SHORTCUT_VALIDATED)
ledger = LedgerShortcut::VALIDATED;
else if (specifier.shortcut() == LedgerSpecifier::SHORTCUT_CLOSED)
ledger = LedgerShortcut::CLOSED;
else if (specifier.shortcut() == LedgerSpecifier::SHORTCUT_CURRENT)
ledger = LedgerShortcut::CURRENT;
else
return {};
}
else if (ledgerCase == LedgerCase::kSequence)
{
ledger = specifier.sequence();
}
else if (ledgerCase == LedgerCase::kHash)
{
if (auto hash = uint256::fromVoidChecked(specifier.hash()))
{
ledger = *hash;
}
else
{
grpc::Status errorStatus{
grpc::StatusCode::INVALID_ARGUMENT,
"ledger hash malformed"};
return errorStatus;
}
}
return ledger;
}
return std::optional<LedgerSpecifier>{};
}
// parses args into a ledger specifier, or returns a Json object on error
std::variant<std::optional<LedgerSpecifier>, Json::Value>
parseLedgerArgs(Json::Value const& params)
@@ -331,131 +267,6 @@ doAccountTxHelp(RPC::Context& context, AccountTxArgs const& args)
return {result, rpcSUCCESS};
}
std::pair<
org::xrpl::rpc::v1::GetAccountTransactionHistoryResponse,
grpc::Status>
populateProtoResponse(
std::pair<AccountTxResult, RPC::Status> const& res,
AccountTxArgs const& args,
RPC::GRPCContext<
org::xrpl::rpc::v1::GetAccountTransactionHistoryRequest> const& context)
{
org::xrpl::rpc::v1::GetAccountTransactionHistoryResponse response;
grpc::Status status = grpc::Status::OK;
RPC::Status const& error = res.second;
if (error.toErrorCode() != rpcSUCCESS)
{
if (error.toErrorCode() == rpcLGR_NOT_FOUND)
{
status = {grpc::StatusCode::NOT_FOUND, error.message()};
}
else if (error.toErrorCode() == rpcNOT_SYNCED)
{
status = {grpc::StatusCode::FAILED_PRECONDITION, error.message()};
}
else
{
status = {grpc::StatusCode::INVALID_ARGUMENT, error.message()};
}
}
else
{
AccountTxResult const& result = res.first;
// account_tx always returns validated data
response.set_validated(true);
response.set_limit(result.limit);
response.mutable_account()->set_address(
context.params.account().address());
response.set_ledger_index_min(result.ledgerRange.min);
response.set_ledger_index_max(result.ledgerRange.max);
if (auto txnsData = std::get_if<TxnsData>(&result.transactions))
{
assert(!args.binary);
for (auto const& [txn, txnMeta] : *txnsData)
{
if (txn)
{
auto txnProto = response.add_transactions();
RPC::convert(
*txnProto->mutable_transaction(),
txn->getSTransaction());
// account_tx always returns validated data
txnProto->set_validated(true);
txnProto->set_ledger_index(txn->getLedger());
auto& hash = txn->getID();
txnProto->set_hash(hash.data(), hash.size());
auto closeTime =
context.app.getLedgerMaster().getCloseTimeBySeq(
txn->getLedger());
if (closeTime)
txnProto->mutable_date()->set_value(
closeTime->time_since_epoch().count());
if (txnMeta)
{
RPC::convert(*txnProto->mutable_meta(), txnMeta);
if (!txnProto->meta().has_delivered_amount())
{
if (auto amt = getDeliveredAmount(
context,
txn->getSTransaction(),
*txnMeta,
txn->getLedger()))
{
RPC::convert(
*txnProto->mutable_meta()
->mutable_delivered_amount(),
*amt);
}
}
}
}
}
}
else
{
assert(args.binary);
for (auto const& binaryData :
std::get<TxnsDataBinary>(result.transactions))
{
auto txnProto = response.add_transactions();
Blob const& txnBlob = std::get<0>(binaryData);
txnProto->set_transaction_binary(
txnBlob.data(), txnBlob.size());
Blob const& metaBlob = std::get<1>(binaryData);
txnProto->set_meta_binary(metaBlob.data(), metaBlob.size());
txnProto->set_ledger_index(std::get<2>(binaryData));
// account_tx always returns validated data
txnProto->set_validated(true);
auto closeTime =
context.app.getLedgerMaster().getCloseTimeBySeq(
std::get<2>(binaryData));
if (closeTime)
txnProto->mutable_date()->set_value(
closeTime->time_since_epoch().count());
}
}
if (result.marker)
{
response.mutable_marker()->set_ledger_index(
result.marker->ledgerSeq);
response.mutable_marker()->set_account_sequence(
result.marker->txnSeq);
}
}
return {response, status};
}
Json::Value
populateJsonResponse(
std::pair<AccountTxResult, RPC::Status> const& res,
@@ -597,59 +408,4 @@ doAccountTxJson(RPC::JsonContext& context)
return populateJsonResponse(res, args, context);
}
std::pair<
org::xrpl::rpc::v1::GetAccountTransactionHistoryResponse,
grpc::Status>
doAccountTxGrpc(
RPC::GRPCContext<org::xrpl::rpc::v1::GetAccountTransactionHistoryRequest>&
context)
{
if (!context.app.config().useTxTables())
{
return {
{},
{grpc::StatusCode::UNIMPLEMENTED, "Not enabled in configuration."}};
}
// return values
org::xrpl::rpc::v1::GetAccountTransactionHistoryResponse response;
grpc::Status status = grpc::Status::OK;
AccountTxArgs args;
auto& request = context.params;
auto const account = parseBase58<AccountID>(request.account().address());
if (!account)
{
return {
{},
{grpc::StatusCode::INVALID_ARGUMENT, "Could not decode account"}};
}
args.account = *account;
args.limit = request.limit();
args.binary = request.binary();
args.forward = request.forward();
if (request.has_marker())
{
args.marker = {
request.marker().ledger_index(),
request.marker().account_sequence()};
}
auto parseRes = parseLedgerArgs(request);
if (auto stat = std::get_if<grpc::Status>(&parseRes))
{
return {response, *stat};
}
else
{
args.ledger = std::get<std::optional<LedgerSpecifier>>(parseRes);
}
auto res = doAccountTxHelp(context, args);
return populateProtoResponse(res, args, context);
}
} // namespace ripple

View File

@@ -38,49 +38,4 @@ doFee(RPC::JsonContext& context)
return context.params;
}
std::pair<org::xrpl::rpc::v1::GetFeeResponse, grpc::Status>
doFeeGrpc(RPC::GRPCContext<org::xrpl::rpc::v1::GetFeeRequest>& context)
{
org::xrpl::rpc::v1::GetFeeResponse reply;
grpc::Status status = grpc::Status::OK;
Application& app = context.app;
auto const view = app.openLedger().current();
if (!view)
{
BOOST_ASSERT(false);
return {reply, status};
}
auto const metrics = app.getTxQ().getMetrics(*view);
// current ledger data
reply.set_current_ledger_size(metrics.txInLedger);
reply.set_current_queue_size(metrics.txCount);
reply.set_expected_ledger_size(metrics.txPerLedger);
reply.set_ledger_current_index(view->info().seq);
reply.set_max_queue_size(*metrics.txQMaxSize);
// fee levels data
org::xrpl::rpc::v1::FeeLevels& levels = *reply.mutable_levels();
levels.set_median_level(metrics.medFeeLevel.fee());
levels.set_minimum_level(metrics.minProcessingFeeLevel.fee());
levels.set_open_ledger_level(metrics.openLedgerFeeLevel.fee());
levels.set_reference_level(metrics.referenceFeeLevel.fee());
// fee data
org::xrpl::rpc::v1::Fee& fee = *reply.mutable_fee();
auto const baseFee = view->fees().base;
fee.mutable_base_fee()->set_drops(
toDrops(metrics.referenceFeeLevel, baseFee).drops());
fee.mutable_minimum_fee()->set_drops(
toDrops(metrics.minProcessingFeeLevel, baseFee).drops());
fee.mutable_median_fee()->set_drops(
toDrops(metrics.medFeeLevel, baseFee).drops());
fee.mutable_open_ledger_fee()->set_drops(
(toDrops(metrics.openLedgerFeeLevel - FeeLevel64{1}, baseFee) + 1)
.drops());
return {reply, status};
}
} // namespace ripple

View File

@@ -25,7 +25,6 @@
#include <ripple/rpc/Context.h>
#include <ripple/rpc/GRPCHandlers.h>
#include <ripple/rpc/Role.h>
#include <ripple/rpc/impl/GRPCHelpers.h>
#include <ripple/rpc/impl/RPCHelpers.h>
#include <ripple/rpc/impl/Tuning.h>

View File

@@ -27,7 +27,6 @@
#include <ripple/protocol/jss.h>
#include <ripple/rpc/Context.h>
#include <ripple/rpc/GRPCHandlers.h>
#include <ripple/rpc/impl/GRPCHelpers.h>
#include <ripple/rpc/impl/RPCHelpers.h>
namespace ripple {

View File

@@ -27,7 +27,6 @@
#include <ripple/rpc/GRPCHandlers.h>
#include <ripple/rpc/Role.h>
#include <ripple/rpc/handlers/LedgerHandler.h>
#include <ripple/rpc/impl/GRPCHelpers.h>
#include <ripple/rpc/impl/RPCHelpers.h>
namespace ripple {

View File

@@ -26,7 +26,6 @@
#include <ripple/resource/Fees.h>
#include <ripple/rpc/Context.h>
#include <ripple/rpc/GRPCHandlers.h>
#include <ripple/rpc/impl/GRPCHelpers.h>
#include <ripple/rpc/impl/RPCHelpers.h>
#include <ripple/rpc/impl/TransactionSign.h>
@@ -194,102 +193,4 @@ doSubmit(RPC::JsonContext& context)
}
}
std::pair<org::xrpl::rpc::v1::SubmitTransactionResponse, grpc::Status>
doSubmitGrpc(
RPC::GRPCContext<org::xrpl::rpc::v1::SubmitTransactionRequest>& context)
{
// return values
org::xrpl::rpc::v1::SubmitTransactionResponse result;
grpc::Status status = grpc::Status::OK;
// input
auto request = context.params;
std::string const& tx = request.signed_transaction();
// convert to blob
Blob blob{tx.begin(), tx.end()};
// serialize
SerialIter sitTrans(makeSlice(blob));
std::shared_ptr<STTx const> stpTrans;
try
{
stpTrans = std::make_shared<STTx const>(std::ref(sitTrans));
}
catch (std::exception& e)
{
grpc::Status errorStatus{
grpc::StatusCode::INVALID_ARGUMENT,
"invalid transaction: " + std::string(e.what())};
return {result, errorStatus};
}
// check validity
{
if (!context.app.checkSigs())
forceValidity(
context.app.getHashRouter(),
stpTrans->getTransactionID(),
Validity::SigGoodOnly);
auto [validity, reason] = checkValidity(
context.app.getHashRouter(),
*stpTrans,
context.ledgerMaster.getCurrentLedger()->rules(),
context.app.config());
if (validity != Validity::Valid)
{
grpc::Status errorStatus{
grpc::StatusCode::INVALID_ARGUMENT,
"invalid transaction: " + reason};
return {result, errorStatus};
}
}
std::string reason;
auto tpTrans = std::make_shared<Transaction>(stpTrans, reason, context.app);
if (tpTrans->getStatus() != NEW)
{
grpc::Status errorStatus{
grpc::StatusCode::INVALID_ARGUMENT,
"invalid transaction: " + reason};
return {result, errorStatus};
}
try
{
auto const failType = NetworkOPs::doFailHard(request.fail_hard());
// submit to network
context.netOps.processTransaction(
tpTrans, isUnlimited(context.role), true, failType);
}
catch (std::exception& e)
{
grpc::Status errorStatus{
grpc::StatusCode::INVALID_ARGUMENT,
"invalid transaction : " + std::string(e.what())};
return {result, errorStatus};
}
// return preliminary result
if (temUNCERTAIN != tpTrans->getResult())
{
RPC::convert(*result.mutable_engine_result(), tpTrans->getResult());
std::string sToken;
std::string sHuman;
transResultInfo(tpTrans->getResult(), sToken, sHuman);
result.mutable_engine_result()->set_result(sToken);
result.set_engine_result_code(TERtoInt(tpTrans->getResult()));
result.set_engine_result_message(sHuman);
uint256 hash = tpTrans->getID();
result.set_hash(hash.data(), hash.size());
}
return {result, status};
}
} // namespace ripple

View File

@@ -28,7 +28,6 @@
#include <ripple/rpc/Context.h>
#include <ripple/rpc/DeliveredAmount.h>
#include <ripple/rpc/GRPCHandlers.h>
#include <ripple/rpc/impl/GRPCHelpers.h>
#include <ripple/rpc/impl/RPCHelpers.h>
namespace ripple {
@@ -251,101 +250,6 @@ doTxHelp(RPC::Context& context, TxArgs const& args)
return {result, rpcSUCCESS};
}
std::pair<org::xrpl::rpc::v1::GetTransactionResponse, grpc::Status>
populateProtoResponse(
std::pair<TxResult, RPC::Status> const& res,
TxArgs const& args,
RPC::GRPCContext<org::xrpl::rpc::v1::GetTransactionRequest> const& context)
{
org::xrpl::rpc::v1::GetTransactionResponse response;
grpc::Status status = grpc::Status::OK;
RPC::Status const& error = res.second;
TxResult const& result = res.first;
// handle errors
if (error.toErrorCode() != rpcSUCCESS)
{
if (error.toErrorCode() == rpcTXN_NOT_FOUND &&
result.searchedAll != TxSearched::unknown)
{
status = {
grpc::StatusCode::NOT_FOUND,
"txn not found. searched_all = " +
to_string(
(result.searchedAll == TxSearched::all ? "true"
: "false"))};
}
else
{
if (error.toErrorCode() == rpcTXN_NOT_FOUND)
status = {grpc::StatusCode::NOT_FOUND, "txn not found"};
else
status = {grpc::StatusCode::INTERNAL, error.message()};
}
}
// no errors
else if (result.txn)
{
auto& txn = result.txn;
std::shared_ptr<STTx const> stTxn = txn->getSTransaction();
if (args.binary)
{
Serializer s = stTxn->getSerializer();
response.set_transaction_binary(s.data(), s.size());
}
else
{
RPC::convert(*response.mutable_transaction(), stTxn);
}
response.set_hash(context.params.hash());
auto ledgerIndex = txn->getLedger();
response.set_ledger_index(ledgerIndex);
if (ledgerIndex)
{
auto ct =
context.app.getLedgerMaster().getCloseTimeBySeq(ledgerIndex);
if (ct)
response.mutable_date()->set_value(
ct->time_since_epoch().count());
}
RPC::convert(
*response.mutable_meta()->mutable_transaction_result(),
txn->getResult());
response.mutable_meta()->mutable_transaction_result()->set_result(
transToken(txn->getResult()));
// populate binary metadata
if (auto blob = std::get_if<Blob>(&result.meta))
{
assert(args.binary);
Slice slice = makeSlice(*blob);
response.set_meta_binary(slice.data(), slice.size());
}
// populate meta data
else if (auto m = std::get_if<std::shared_ptr<TxMeta>>(&result.meta))
{
auto& meta = *m;
if (meta)
{
RPC::convert(*response.mutable_meta(), meta);
auto amt =
getDeliveredAmount(context, stTxn, *meta, txn->getLedger());
if (amt)
{
RPC::convert(
*response.mutable_meta()->mutable_delivered_amount(),
*amt);
}
}
}
response.set_validated(result.validated);
}
return {response, status};
}
Json::Value
populateJsonResponse(
std::pair<TxResult, RPC::Status> const& res,
@@ -437,48 +341,4 @@ doTxJson(RPC::JsonContext& context)
return populateJsonResponse(res, args, context);
}
std::pair<org::xrpl::rpc::v1::GetTransactionResponse, grpc::Status>
doTxGrpc(RPC::GRPCContext<org::xrpl::rpc::v1::GetTransactionRequest>& context)
{
if (!context.app.config().useTxTables())
{
return {
{},
{grpc::StatusCode::UNIMPLEMENTED, "Not enabled in configuration."}};
}
// return values
org::xrpl::rpc::v1::GetTransactionResponse response;
grpc::Status status = grpc::Status::OK;
// input
org::xrpl::rpc::v1::GetTransactionRequest& request = context.params;
TxArgs args;
if (auto hash = uint256::fromVoidChecked(request.hash()))
{
args.hash = *hash;
}
else
{
grpc::Status errorStatus{
grpc::StatusCode::INVALID_ARGUMENT, "tx hash malformed"};
return {response, errorStatus};
}
args.binary = request.binary();
if (request.ledger_range().ledger_index_min() != 0 &&
request.ledger_range().ledger_index_max() != 0)
{
args.ledgerRange = std::make_pair(
request.ledger_range().ledger_index_min(),
request.ledger_range().ledger_index_max());
}
std::pair<TxResult, RPC::Status> res = doTxHelp(context, args);
return populateProtoResponse(res, args, context);
}
} // namespace ripple

File diff suppressed because it is too large Load Diff

View File

@@ -1,90 +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.
*/
//==============================================================================
#ifndef RIPPLE_RPC_GRPCHELPERS_H_INCLUDED
#define RIPPLE_RPC_GRPCHELPERS_H_INCLUDED
#include "org/xrpl/rpc/v1/get_account_info.pb.h"
#include "org/xrpl/rpc/v1/ledger_objects.pb.h"
#include "org/xrpl/rpc/v1/meta.pb.h"
#include "org/xrpl/rpc/v1/transaction.pb.h"
#include <ripple/app/misc/TxQ.h>
#include <ripple/protocol/Protocol.h>
#include <ripple/protocol/STAmount.h>
#include <ripple/protocol/STTx.h>
#include <ripple/protocol/TxMeta.h>
#include <functional>
namespace ripple {
namespace RPC {
void
convert(org::xrpl::rpc::v1::Meta& to, std::shared_ptr<TxMeta> const& from);
void
convert(
org::xrpl::rpc::v1::QueueData& to,
std::vector<TxQ::TxDetails> const& from);
void
convert(
org::xrpl::rpc::v1::Transaction& to,
std::shared_ptr<STTx const> const& from);
void
convert(org::xrpl::rpc::v1::TransactionResult& to, TER from);
void
convert(org::xrpl::rpc::v1::AccountRoot& to, STObject const& from);
void
convert(org::xrpl::rpc::v1::SignerList& to, STObject const& from);
void
convert(org::xrpl::rpc::v1::NegativeUNL& to, STObject const& from);
template <class T>
void
convert(T& to, STAmount const& from)
{
if (from.native())
{
to.mutable_value()->mutable_xrp_amount()->set_drops(from.xrp().drops());
}
else
{
Issue const& issue = from.issue();
org::xrpl::rpc::v1::IssuedCurrencyAmount* issued =
to.mutable_value()->mutable_issued_currency_amount();
issued->mutable_currency()->set_name(to_string(issue.currency));
issued->mutable_currency()->set_code(
issue.currency.data(), Currency::size());
issued->mutable_issuer()->set_address(toBase58(issue.account));
issued->set_value(to_string(from.iou()));
}
}
} // namespace RPC
} // namespace ripple
#endif

View File

@@ -33,7 +33,6 @@
#include <boost/algorithm/string/case_conv.hpp>
#include <ripple/resource/Fees.h>
#include <ripple/rpc/impl/GRPCHelpers.h>
namespace ripple {
namespace RPC {
@@ -300,12 +299,6 @@ ledgerFromRequest(T& ledger, GRPCContext<R>& context)
return ledgerFromSpecifier(ledger, request.ledger(), context);
}
// explicit instantiation of above function
template Status
ledgerFromRequest<>(
std::shared_ptr<ReadView const>&,
GRPCContext<org::xrpl::rpc::v1::GetAccountInfoRequest>&);
// explicit instantiation of above function
template Status
ledgerFromRequest<>(

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