Remove TxnSignApiFacade (RIPD-945):

Replace TxnSignApiFacade with separate passed in arguments to
the various sign/submit RPC commands.

Also increase unit test coverage of the submit_multisign RPC
command.
This commit is contained in:
Scott Schurr
2015-09-02 16:58:49 -07:00
committed by Edward Hennis
parent 9b787434c9
commit eed1a891a7
7 changed files with 872 additions and 496 deletions

View File

@@ -18,8 +18,8 @@
//============================================================================== //==============================================================================
#include <BeastConfig.h> #include <BeastConfig.h>
#include <ripple/app/main/Application.h>
#include <ripple/app/ledger/LedgerMaster.h> #include <ripple/app/ledger/LedgerMaster.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/Feature.h> #include <ripple/protocol/Feature.h>
#include <ripple/resource/Fees.h> #include <ripple/resource/Fees.h>
#include <ripple/rpc/Context.h> #include <ripple/rpc/Context.h>
@@ -46,7 +46,12 @@ Json::Value doSignFor (RPC::Context& context)
auto const failType = NetworkOPs::doFailHard (failHard); auto const failType = NetworkOPs::doFailHard (failHard);
return RPC::transactionSignFor ( return RPC::transactionSignFor (
context.params, failType, context.netOps, context.role); context.params,
failType,
context.role,
context.ledgerMaster.getValidatedLedgerAge(),
context.app.getFeeTrack(),
context.ledgerMaster.getCurrentLedger());
} }
} // ripple } // ripple

View File

@@ -18,11 +18,8 @@
//============================================================================== //==============================================================================
#include <BeastConfig.h> #include <BeastConfig.h>
#include <ripple/app/misc/NetworkOPs.h> #include <ripple/app/ledger/LedgerMaster.h>
#include <ripple/json/json_value.h>
#include <ripple/net/RPCErr.h>
#include <ripple/protocol/ErrorCodes.h> #include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/resource/Fees.h> #include <ripple/resource/Fees.h>
#include <ripple/rpc/Context.h> #include <ripple/rpc/Context.h>
#include <ripple/rpc/impl/TransactionSign.h> #include <ripple/rpc/impl/TransactionSign.h>
@@ -42,7 +39,12 @@ Json::Value doSign (RPC::Context& context)
&& context.params[jss::fail_hard].asBool ()); && context.params[jss::fail_hard].asBool ());
return RPC::transactionSign ( return RPC::transactionSign (
context.params, failType, context.netOps, context.role); context.params,
failType,
context.role,
context.ledgerMaster.getValidatedLedgerAge(),
context.app.getFeeTrack(),
context.ledgerMaster.getCurrentLedger());
} }
} // ripple } // ripple

View File

@@ -18,18 +18,13 @@
//============================================================================== //==============================================================================
#include <BeastConfig.h> #include <BeastConfig.h>
#include <ripple/app/main/Application.h> #include <ripple/app/ledger/LedgerMaster.h>
#include <ripple/app/misc/NetworkOPs.h>
#include <ripple/app/misc/HashRouter.h> #include <ripple/app/misc/HashRouter.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/basics/strHex.h>
#include <ripple/net/RPCErr.h> #include <ripple/net/RPCErr.h>
#include <ripple/protocol/ErrorCodes.h> #include <ripple/protocol/ErrorCodes.h>
#include <ripple/resource/Fees.h> #include <ripple/resource/Fees.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/rpc/Context.h> #include <ripple/rpc/Context.h>
#include <ripple/rpc/impl/TransactionSign.h> #include <ripple/rpc/impl/TransactionSign.h>
#include <ripple/server/Role.h>
namespace ripple { namespace ripple {
@@ -53,7 +48,13 @@ Json::Value doSubmit (RPC::Context& context)
auto const failType = getFailHard (context); auto const failType = getFailHard (context);
return RPC::transactionSubmit ( return RPC::transactionSubmit (
context.params, failType, context.netOps, context.role); context.params,
failType,
context.role,
context.ledgerMaster.getValidatedLedgerAge(),
context.app.getFeeTrack(),
context.ledgerMaster.getCurrentLedger(),
RPC::getProcessTxnFn (context.netOps));
} }
Json::Value jvResult; Json::Value jvResult;

View File

@@ -18,8 +18,8 @@
//============================================================================== //==============================================================================
#include <BeastConfig.h> #include <BeastConfig.h>
#include <ripple/app/main/Application.h>
#include <ripple/app/ledger/LedgerMaster.h> #include <ripple/app/ledger/LedgerMaster.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/Feature.h> #include <ripple/protocol/Feature.h>
#include <ripple/resource/Fees.h> #include <ripple/resource/Fees.h>
#include <ripple/rpc/Context.h> #include <ripple/rpc/Context.h>
@@ -45,7 +45,13 @@ Json::Value doSubmitMultiSigned (RPC::Context& context)
auto const failType = NetworkOPs::doFailHard (failHard); auto const failType = NetworkOPs::doFailHard (failHard);
return RPC::transactionSubmitMultiSigned ( return RPC::transactionSubmitMultiSigned (
context.params, failType, context.netOps, context.role); context.params,
failType,
context.role,
context.ledgerMaster.getValidatedLedgerAge(),
context.app.getFeeTrack(),
context.ledgerMaster.getCurrentLedger(),
RPC::getProcessTxnFn (context.netOps));
} }
} // ripple } // ripple

View File

@@ -18,23 +18,19 @@
//============================================================================== //==============================================================================
#include <BeastConfig.h> #include <BeastConfig.h>
#include <ripple/rpc/impl/TransactionSign.h>
#include <ripple/app/ledger/LedgerMaster.h> #include <ripple/app/ledger/LedgerMaster.h>
#include <ripple/app/main/Application.h>
#include <ripple/app/paths/FindPaths.h> #include <ripple/app/paths/FindPaths.h>
#include <ripple/basics/Log.h> #include <ripple/basics/Log.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/core/LoadFeeTrack.h> #include <ripple/core/LoadFeeTrack.h>
#include <ripple/json/json_reader.h>
#include <ripple/json/json_writer.h> #include <ripple/json/json_writer.h>
#include <ripple/net/RPCErr.h> #include <ripple/net/RPCErr.h>
#include <ripple/protocol/Sign.h> #include <ripple/protocol/Sign.h>
#include <ripple/protocol/ErrorCodes.h> #include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/STParsedJSON.h> #include <ripple/protocol/STParsedJSON.h>
#include <ripple/protocol/TxFlags.h> #include <ripple/protocol/TxFlags.h>
#include <ripple/protocol/UintTypes.h>
#include <ripple/rpc/impl/KeypairForSignature.h> #include <ripple/rpc/impl/KeypairForSignature.h>
#include <ripple/rpc/impl/LegacyPathFind.h> #include <ripple/rpc/impl/LegacyPathFind.h>
#include <ripple/rpc/impl/TransactionSign.h>
#include <ripple/rpc/impl/Tuning.h> #include <ripple/rpc/impl/Tuning.h>
namespace ripple { namespace ripple {
@@ -99,109 +95,7 @@ public:
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
// TxnSignApiFacade methods static error_code_i acctMatchesPubKey (
void TxnSignApiFacade::snapshotAccountState (AccountID const& accountID)
{
if (!netOPs_) // Unit testing.
return;
ledger_ = getApp().getLedgerMaster ().getCurrentLedger ();
accountID_ = accountID;
sle_ = cachedRead(*ledger_,
keylet::account(accountID_).key, ltACCOUNT_ROOT);
}
bool TxnSignApiFacade::isValidAccount () const
{
if (!ledger_) // Unit testing.
return true;
return static_cast <bool> (sle_);
}
std::uint32_t TxnSignApiFacade::getSeq () const
{
if (!ledger_) // Unit testing.
return 0;
return sle_->getFieldU32(sfSequence);
}
void TxnSignApiFacade::processTransaction (
Transaction::pointer& transaction,
bool bAdmin,
bool bLocal,
NetworkOPs::FailHard failType)
{
if (!netOPs_) // Unit testing.
return;
netOPs_->processTransaction(transaction, bAdmin, bLocal, failType);
}
boost::optional<STPathSet>
TxnSignApiFacade::findPathsForOneIssuer (
AccountID const& dstAccountID,
Issue const& srcIssue,
STAmount const& dstAmount,
int searchLevel,
unsigned int const maxPaths,
STPathSet const& paths,
STPath& fullLiquidityPath) const
{
if (! ledger_) // Unit testing.
{
// Note that unit tests don't (yet) need paths or fullLiquidityPath.
boost::optional<STPathSet> result;
result.emplace();
return result;
}
auto cache = std::make_shared<RippleLineCache> (ledger_);
return ripple::findPathsForOneIssuer(
cache,
accountID_,
dstAccountID,
srcIssue,
dstAmount,
searchLevel,
maxPaths,
paths,
fullLiquidityPath);
}
std::uint64_t TxnSignApiFacade::scaleFeeBase (std::uint64_t fee) const
{
if (!ledger_) // Unit testing.
return fee;
// VFALCO Audit
return getApp().getFeeTrack().scaleFeeBase(
fee, ledger_->fees().base, ledger_->fees().units);
}
std::uint64_t
TxnSignApiFacade::scaleFeeLoad (std::uint64_t fee, bool bAdmin) const
{
if (!ledger_) // Unit testing.
return fee;
// VFALCO Audit
return getApp().getFeeTrack().scaleFeeLoad(
fee, ledger_->fees().base, ledger_->fees().units,
bAdmin);
}
bool TxnSignApiFacade::hasAccountRoot () const
{
if (!netOPs_) // Unit testing.
return true;
return ledger_->exists(
keylet::account(accountID_));
}
error_code_i acctMatchesPubKey (
std::shared_ptr<SLE const> accountState, std::shared_ptr<SLE const> accountState,
AccountID const& accountID, AccountID const& accountID,
RippleAddress const& publicKey) RippleAddress const& publicKey)
@@ -236,134 +130,13 @@ error_code_i acctMatchesPubKey (
return rpcBAD_SECRET; return rpcBAD_SECRET;
} }
error_code_i TxnSignApiFacade::singleAcctMatchesPubKey (
RippleAddress const& publicKey) const
{
if (!netOPs_) // Unit testing.
return rpcSUCCESS;
return acctMatchesPubKey (sle_, accountID_, publicKey);
}
error_code_i TxnSignApiFacade::multiAcctMatchesPubKey (
AccountID const& accountID,
RippleAddress const& publicKey) const
{
std::shared_ptr<SLE const> accountState;
// VFALCO Do we need to check netOPs_?
if (netOPs_ && ledger_)
{
// If it's available, get the account root for the multi-signer's
// accountID. It's okay if the account root is not available,
// since they might be signing with a phantom (unfunded) account.
accountState = cachedRead(*ledger_,
keylet::account(accountID).key, ltACCOUNT_ROOT);
}
return acctMatchesPubKey (accountState, accountID, publicKey);
}
int TxnSignApiFacade::getValidatedLedgerAge () const
{
if (!netOPs_) // Unit testing.
return 0;
return getApp().getLedgerMaster ().getValidatedLedgerAge ();
}
bool TxnSignApiFacade::isLoadedCluster () const
{
if (!netOPs_) // Unit testing.
return false;
return getApp().getFeeTrack().isLoadedCluster();
}
//------------------------------------------------------------------------------
/** Fill in the fee on behalf of the client.
This is called when the client does not explicitly specify the fee.
The client may also put a ceiling on the amount of the fee. This ceiling
is expressed as a multiplier based on the current ledger's fee schedule.
JSON fields
"Fee" The fee paid by the transaction. Omitted when the client
wants the fee filled in.
"fee_mult_max" A multiplier applied to the current ledger's transaction
fee that caps the maximum the fee server should auto fill.
If this optional field is not specified, then a default
multiplier is used.
@param tx The JSON corresponding to the transaction to fill in.
@param ledger A ledger for retrieving the current fee schedule.
@param roll Identifies if this is called by an administrative endpoint.
@return A JSON object containing the error results, if any
*/
Json::Value checkFee (
Json::Value& request,
TxnSignApiFacade& apiFacade,
Role const role,
AutoFill const doAutoFill)
{
Json::Value& tx (request[jss::tx_json]);
if (tx.isMember (jss::Fee))
return Json::Value();
if (doAutoFill == AutoFill::dont)
return RPC::missing_field_error ("tx_json.Fee");
int mult = Tuning::defaultAutoFillFeeMultiplier;
if (request.isMember (jss::fee_mult_max))
{
if (request[jss::fee_mult_max].isNumeric ())
{
mult = request[jss::fee_mult_max].asInt();
}
else
{
return RPC::make_error (rpcHIGH_FEE,
RPC::expected_field_message (jss::fee_mult_max, "a number"));
}
}
// Default fee in fee units.
std::uint64_t const feeDefault = getConfig().TRANSACTION_FEE_BASE;
// Administrative endpoints are exempt from local fees.
std::uint64_t const fee =
apiFacade.scaleFeeLoad (feeDefault, role == Role::ADMIN);
std::uint64_t const limit = mult * apiFacade.scaleFeeBase (feeDefault);
if (fee > limit)
{
std::stringstream ss;
ss << "Fee of " << fee
<< " exceeds the requested tx limit of " << limit;
return RPC::make_error (rpcHIGH_FEE, ss.str());
}
tx [jss::Fee] = static_cast<int>(fee);
return Json::Value();
}
enum class PathFind : unsigned char
{
dont,
might
};
static Json::Value checkPayment( static Json::Value checkPayment(
Json::Value const& params, Json::Value const& params,
Json::Value& tx_json, Json::Value& tx_json,
AccountID const& srcAddressID, AccountID const& srcAddressID,
TxnSignApiFacade const& apiFacade,
Role const role, Role const role,
PathFind const doPath) std::shared_ptr<ReadView const>& ledger,
bool doPath)
{ {
// Only path find for Payments. // Only path find for Payments.
if (tx_json[jss::TransactionType].asString () != "Payment") if (tx_json[jss::TransactionType].asString () != "Payment")
@@ -385,7 +158,7 @@ static Json::Value checkPayment(
if (! dstAccountID) if (! dstAccountID)
return RPC::invalid_field_error ("tx_json.Destination"); return RPC::invalid_field_error ("tx_json.Destination");
if ((doPath == PathFind::dont) && params.isMember (jss::build_path)) if ((doPath == false) && params.isMember (jss::build_path))
return RPC::make_error (rpcINVALID_PARAMS, return RPC::make_error (rpcINVALID_PARAMS,
"Field 'build_path' not allowed in this context."); "Field 'build_path' not allowed in this context.");
@@ -419,11 +192,14 @@ static Json::Value checkPayment(
return rpcError (rpcTOO_BUSY); return rpcError (rpcTOO_BUSY);
STPath fullLiquidityPath; STPath fullLiquidityPath;
auto result = apiFacade.findPathsForOneIssuer ( auto cache = std::make_shared<RippleLineCache> (ledger);
auto result = findPathsForOneIssuer (
cache,
srcAddressID,
*dstAccountID, *dstAccountID,
sendMax.issue (), sendMax.issue(),
amount, amount,
getConfig ().PATH_SEARCH_OLD, getConfig().PATH_SEARCH_OLD,
4, // iMaxPaths 4, // iMaxPaths
{}, {},
fullLiquidityPath); fullLiquidityPath);
@@ -458,9 +234,10 @@ static Json::Value checkPayment(
static std::pair<Json::Value, AccountID> static std::pair<Json::Value, AccountID>
checkTxJsonFields ( checkTxJsonFields (
Json::Value const& tx_json, Json::Value const& tx_json,
TxnSignApiFacade const& apiFacade,
Role const role, Role const role,
bool const verify) bool const verify,
int validatedLedgerAge,
LoadFeeTrack const& feeTrack)
{ {
std::pair<Json::Value, AccountID> ret; std::pair<Json::Value, AccountID> ret;
@@ -495,15 +272,14 @@ checkTxJsonFields (
// Check for current ledger. // Check for current ledger.
if (verify && !getConfig ().RUN_STANDALONE && if (verify && !getConfig ().RUN_STANDALONE &&
(apiFacade.getValidatedLedgerAge() > (validatedLedgerAge > Tuning::maxValidatedLedgerAge))
Tuning::maxValidatedLedgerAge))
{ {
ret.first = rpcError (rpcNO_CURRENT); ret.first = rpcError (rpcNO_CURRENT);
return ret; return ret;
} }
// Check for load. // Check for load.
if (apiFacade.isLoadedCluster() && (role != Role::ADMIN)) if (feeTrack.isLoadedCluster() && (role != Role::ADMIN))
{ {
ret.first = rpcError (rpcTOO_BUSY); ret.first = rpcError (rpcTOO_BUSY);
return ret; return ret;
@@ -550,9 +326,11 @@ static
transactionPreProcessResult transactionPreProcessResult
transactionPreProcessImpl ( transactionPreProcessImpl (
Json::Value& params, Json::Value& params,
TxnSignApiFacade& apiFacade,
Role role, Role role,
SigningForParams& signingArgs) SigningForParams& signingArgs,
int validatedLedgerAge,
LoadFeeTrack const& feeTrack,
std::shared_ptr<ReadView const> ledger)
{ {
KeyPair keypair; KeyPair keypair;
{ {
@@ -571,7 +349,8 @@ transactionPreProcessImpl (
Json::Value& tx_json (params [jss::tx_json]); Json::Value& tx_json (params [jss::tx_json]);
// Check tx_json fields, but don't add any. // Check tx_json fields, but don't add any.
auto txJsonResult = checkTxJsonFields (tx_json, apiFacade, role, verify); auto txJsonResult =
checkTxJsonFields (tx_json, role, verify, validatedLedgerAge, feeTrack);
if (RPC::contains_error (txJsonResult.first)) if (RPC::contains_error (txJsonResult.first))
return std::move (txJsonResult.first); return std::move (txJsonResult.first);
@@ -583,10 +362,10 @@ transactionPreProcessImpl (
if (!verify && !tx_json.isMember (jss::Sequence)) if (!verify && !tx_json.isMember (jss::Sequence))
return RPC::missing_field_error ("tx_json.Sequence"); return RPC::missing_field_error ("tx_json.Sequence");
apiFacade.snapshotAccountState (srcAddressID); std::shared_ptr<SLE const> sle = cachedRead(*ledger,
keylet::account(srcAddressID).key, ltACCOUNT_ROOT);
if (verify) { if (verify && !sle)
if (!apiFacade.isValidAccount())
{ {
// If not offline and did not find account, error. // If not offline and did not find account, error.
WriteLog (lsDEBUG, RPCHandler) WriteLog (lsDEBUG, RPCHandler)
@@ -596,14 +375,14 @@ transactionPreProcessImpl (
return rpcError (rpcSRC_ACT_NOT_FOUND); return rpcError (rpcSRC_ACT_NOT_FOUND);
} }
}
{ {
Json::Value err = checkFee ( Json::Value err = checkFee (
params, params,
apiFacade,
role, role,
signingArgs.editFields() ? AutoFill::might : AutoFill::dont); signingArgs.editFields(),
feeTrack,
ledger);
if (RPC::contains_error (err)) if (RPC::contains_error (err))
return std::move (err); return std::move (err);
@@ -612,9 +391,9 @@ transactionPreProcessImpl (
params, params,
tx_json, tx_json,
srcAddressID, srcAddressID,
apiFacade,
role, role,
signingArgs.editFields() ? PathFind::might : PathFind::dont); ledger,
signingArgs.editFields());
if (RPC::contains_error(err)) if (RPC::contains_error(err))
return std::move (err); return std::move (err);
@@ -623,7 +402,18 @@ transactionPreProcessImpl (
if (signingArgs.editFields()) if (signingArgs.editFields())
{ {
if (!tx_json.isMember (jss::Sequence)) if (!tx_json.isMember (jss::Sequence))
tx_json[jss::Sequence] = apiFacade.getSeq(); {
if (! sle)
{
WriteLog (lsDEBUG, RPCHandler)
<< "transactionSign: Failed to find source account "
<< "in current ledger: "
<< toBase58(srcAddressID);
return rpcError (rpcSRC_ACT_NOT_FOUND);
}
tx_json[jss::Sequence] = (*sle)[sfSequence];
}
if (!tx_json.isMember (jss::Flags)) if (!tx_json.isMember (jss::Flags))
tx_json[jss::Flags] = tfFullyCanonicalSig; tx_json[jss::Flags] = tfFullyCanonicalSig;
@@ -631,7 +421,7 @@ transactionPreProcessImpl (
if (verify) if (verify)
{ {
if (!apiFacade.hasAccountRoot()) if (! sle)
// XXX Ignore transactions for accounts not created. // XXX Ignore transactions for accounts not created.
return rpcError (rpcSRC_ACT_NOT_FOUND); return rpcError (rpcSRC_ACT_NOT_FOUND);
@@ -648,7 +438,7 @@ transactionPreProcessImpl (
{ {
// Make sure the account and secret belong together. // Make sure the account and secret belong together.
error_code_i const err = error_code_i const err =
apiFacade.singleAcctMatchesPubKey (keypair.publicKey); acctMatchesPubKey (sle, srcAddressID, keypair.publicKey);
if (err != rpcSUCCESS) if (err != rpcSUCCESS)
return rpcError (err); return rpcError (err);
@@ -763,7 +553,7 @@ transactionConstructImpl (STTx::pointer stpTrans)
return ret; return ret;
} }
Json::Value transactionFormatResultImpl (Transaction::pointer tpTrans) static Json::Value transactionFormatResultImpl (Transaction::pointer tpTrans)
{ {
Json::Value jvResult; Json::Value jvResult;
try try
@@ -796,12 +586,67 @@ Json::Value transactionFormatResultImpl (Transaction::pointer tpTrans)
//------------------------------------------------------------------------------ //------------------------------------------------------------------------------
Json::Value checkFee (
Json::Value& request,
Role const role,
bool doAutoFill,
LoadFeeTrack const& feeTrack,
std::shared_ptr<ReadView const>& ledger)
{
Json::Value& tx (request[jss::tx_json]);
if (tx.isMember (jss::Fee))
return Json::Value();
if (! doAutoFill)
return RPC::missing_field_error ("tx_json.Fee");
int mult = Tuning::defaultAutoFillFeeMultiplier;
if (request.isMember (jss::fee_mult_max))
{
if (request[jss::fee_mult_max].isNumeric ())
{
mult = request[jss::fee_mult_max].asInt();
}
else
{
return RPC::make_error (rpcHIGH_FEE,
RPC::expected_field_message (jss::fee_mult_max, "a number"));
}
}
// Default fee in fee units.
std::uint64_t const feeDefault = getConfig().TRANSACTION_FEE_BASE;
// Administrative endpoints are exempt from local fees.
std::uint64_t const fee =
feeTrack.scaleFeeLoad (feeDefault,
ledger->fees().base, ledger->fees().units, role == Role::ADMIN);
std::uint64_t const limit = mult * feeTrack.scaleFeeBase (
feeDefault, ledger->fees().base, ledger->fees().units);
if (fee > limit)
{
std::stringstream ss;
ss << "Fee of " << fee
<< " exceeds the requested tx limit of " << limit;
return RPC::make_error (rpcHIGH_FEE, ss.str());
}
tx [jss::Fee] = static_cast<int>(fee);
return Json::Value();
}
//------------------------------------------------------------------------------
/** Returns a Json::objectValue. */ /** Returns a Json::objectValue. */
Json::Value transactionSign ( Json::Value transactionSign (
Json::Value jvRequest, Json::Value jvRequest,
NetworkOPs::FailHard failType, NetworkOPs::FailHard failType,
detail::TxnSignApiFacade& apiFacade, Role role,
Role role) int validatedLedgerAge,
LoadFeeTrack const& feeTrack,
std::shared_ptr<ReadView const> ledger)
{ {
WriteLog (lsDEBUG, RPCHandler) << "transactionSign: " << jvRequest; WriteLog (lsDEBUG, RPCHandler) << "transactionSign: " << jvRequest;
@@ -809,8 +654,8 @@ Json::Value transactionSign (
// Add and amend fields based on the transaction type. // Add and amend fields based on the transaction type.
SigningForParams signForParams; SigningForParams signForParams;
transactionPreProcessResult preprocResult = transactionPreProcessResult preprocResult = transactionPreProcessImpl (
transactionPreProcessImpl (jvRequest, apiFacade, role, signForParams); jvRequest, role, signForParams, validatedLedgerAge, feeTrack, ledger);
if (!preprocResult.second) if (!preprocResult.second)
return preprocResult.first; return preprocResult.first;
@@ -829,8 +674,11 @@ Json::Value transactionSign (
Json::Value transactionSubmit ( Json::Value transactionSubmit (
Json::Value jvRequest, Json::Value jvRequest,
NetworkOPs::FailHard failType, NetworkOPs::FailHard failType,
detail::TxnSignApiFacade& apiFacade, Role role,
Role role) int validatedLedgerAge,
LoadFeeTrack const& feeTrack,
std::shared_ptr<ReadView const> ledger,
ProcessTransactionFn const& processTransaction)
{ {
WriteLog (lsDEBUG, RPCHandler) << "transactionSubmit: " << jvRequest; WriteLog (lsDEBUG, RPCHandler) << "transactionSubmit: " << jvRequest;
@@ -838,8 +686,8 @@ Json::Value transactionSubmit (
// Add and amend fields based on the transaction type. // Add and amend fields based on the transaction type.
SigningForParams signForParams; SigningForParams signForParams;
transactionPreProcessResult preprocResult = transactionPreProcessResult preprocResult = transactionPreProcessImpl (
transactionPreProcessImpl (jvRequest, apiFacade, role, signForParams); jvRequest, role, signForParams, validatedLedgerAge, feeTrack, ledger);
if (!preprocResult.second) if (!preprocResult.second)
return preprocResult.first; return preprocResult.first;
@@ -855,7 +703,7 @@ Json::Value transactionSubmit (
try try
{ {
// FIXME: For performance, should use asynch interface // FIXME: For performance, should use asynch interface
apiFacade.processTransaction ( processTransaction (
txn.second, role == Role::ADMIN, true, failType); txn.second, role == Role::ADMIN, true, failType);
} }
catch (std::exception&) catch (std::exception&)
@@ -900,8 +748,10 @@ Json::Value checkMultiSignFields (Json::Value const& jvRequest)
Json::Value transactionSignFor ( Json::Value transactionSignFor (
Json::Value jvRequest, Json::Value jvRequest,
NetworkOPs::FailHard failType, NetworkOPs::FailHard failType,
detail::TxnSignApiFacade& apiFacade, Role role,
Role role) int validatedLedgerAge,
LoadFeeTrack const& feeTrack,
std::shared_ptr<ReadView const> ledger)
{ {
WriteLog (lsDEBUG, RPCHandler) << "transactionSignFor: " << jvRequest; WriteLog (lsDEBUG, RPCHandler) << "transactionSignFor: " << jvRequest;
@@ -935,20 +785,25 @@ Json::Value transactionSignFor (
SigningForParams signForParams( SigningForParams signForParams(
*signerAccountID, multiSignPubKey, multiSignature); *signerAccountID, multiSignPubKey, multiSignature);
transactionPreProcessResult preprocResult = transactionPreProcessResult preprocResult = transactionPreProcessImpl (
transactionPreProcessImpl (
jvRequest, jvRequest,
apiFacade,
role, role,
signForParams); signForParams,
validatedLedgerAge,
feeTrack,
ledger);
if (!preprocResult.second) if (!preprocResult.second)
return preprocResult.first; return preprocResult.first;
// Make sure the multiSignAddrID can legitimately multi-sign. // Make sure the multiSignAddrID can legitimately multi-sign.
{ {
// Make sure the account and secret belong together.
std::shared_ptr<SLE const> sle = cachedRead(*ledger,
keylet::account(*signerAccountID).key, ltACCOUNT_ROOT);
error_code_i const err = error_code_i const err =
apiFacade.multiAcctMatchesPubKey (*signerAccountID, multiSignPubKey); acctMatchesPubKey (sle, *signerAccountID, multiSignPubKey);
if (err != rpcSUCCESS) if (err != rpcSUCCESS)
return rpcError (err); return rpcError (err);
@@ -993,8 +848,11 @@ Json::Value transactionSignFor (
Json::Value transactionSubmitMultiSigned ( Json::Value transactionSubmitMultiSigned (
Json::Value jvRequest, Json::Value jvRequest,
NetworkOPs::FailHard failType, NetworkOPs::FailHard failType,
detail::TxnSignApiFacade& apiFacade, Role role,
Role role) int validatedLedgerAge,
LoadFeeTrack const& feeTrack,
std::shared_ptr<ReadView const> ledger,
ProcessTransactionFn const& processTransaction)
{ {
WriteLog (lsDEBUG, RPCHandler) WriteLog (lsDEBUG, RPCHandler)
<< "transactionSubmitMultiSigned: " << jvRequest; << "transactionSubmitMultiSigned: " << jvRequest;
@@ -1010,16 +868,19 @@ Json::Value transactionSubmitMultiSigned (
Json::Value& tx_json (jvRequest ["tx_json"]); Json::Value& tx_json (jvRequest ["tx_json"]);
auto const txJsonResult = checkTxJsonFields(tx_json, apiFacade, role, true); auto const txJsonResult =
checkTxJsonFields (tx_json, role, true, validatedLedgerAge, feeTrack);
if (RPC::contains_error (txJsonResult.first)) if (RPC::contains_error (txJsonResult.first))
return std::move (txJsonResult.first); return std::move (txJsonResult.first);
auto const srcAddressID = txJsonResult.second; auto const srcAddressID = txJsonResult.second;
apiFacade.snapshotAccountState (srcAddressID); std::shared_ptr<SLE const> sle = cachedRead(*ledger,
if (!apiFacade.isValidAccount ()) keylet::account(srcAddressID).key, ltACCOUNT_ROOT);
if (!sle)
{ {
// If not offline and did not find account, error. // If did not find account, error.
WriteLog (lsDEBUG, RPCHandler) WriteLog (lsDEBUG, RPCHandler)
<< "transactionSubmitMultiSigned: Failed to find source account " << "transactionSubmitMultiSigned: Failed to find source account "
<< "in current ledger: " << "in current ledger: "
@@ -1029,7 +890,8 @@ Json::Value transactionSubmitMultiSigned (
} }
{ {
Json::Value err = checkFee (jvRequest, apiFacade, role, AutoFill::dont); Json::Value err = checkFee (jvRequest, role, false, feeTrack, ledger);
if (RPC::contains_error(err)) if (RPC::contains_error(err))
return std::move (err); return std::move (err);
@@ -1037,9 +899,9 @@ Json::Value transactionSubmitMultiSigned (
jvRequest, jvRequest,
tx_json, tx_json,
srcAddressID, srcAddressID,
apiFacade,
role, role,
PathFind::dont); ledger,
false);
if (RPC::contains_error(err)) if (RPC::contains_error(err))
return std::move (err); return std::move (err);
@@ -1121,7 +983,7 @@ Json::Value transactionSubmitMultiSigned (
{ {
std::ostringstream err; std::ostringstream err;
err << "Expected " err << "Expected "
<< signersArrayName << " to be an array"; << signersArrayName << " to be an array.";
return RPC::make_param_error (err.str ()); return RPC::make_param_error (err.str ());
} }
@@ -1160,8 +1022,7 @@ Json::Value transactionSubmitMultiSigned (
{ {
std::ostringstream err; std::ostringstream err;
err << "Duplicate Signers:Signer:Account entries (" err << "Duplicate Signers:Signer:Account entries ("
<< getApp().accountIDCache().toBase58( << toBase58(dupIter->getAccountID(sfAccount))
dupIter->getAccountID(sfAccount))
<< ") are not allowed."; << ") are not allowed.";
return RPC::make_param_error(err.str ()); return RPC::make_param_error(err.str ());
} }
@@ -1175,7 +1036,7 @@ Json::Value transactionSubmitMultiSigned (
{ {
std::ostringstream err; std::ostringstream err;
err << "A Signer may not be the transaction's Account (" err << "A Signer may not be the transaction's Account ("
<< getApp().accountIDCache().toBase58(srcAddressID) << ")."; << toBase58(srcAddressID) << ").";
return RPC::make_param_error(err.str ()); return RPC::make_param_error(err.str ());
} }
@@ -1193,7 +1054,7 @@ Json::Value transactionSubmitMultiSigned (
try try
{ {
// FIXME: For performance, should use asynch interface // FIXME: For performance, should use asynch interface
apiFacade.processTransaction ( processTransaction (
txn.second, role == Role::ADMIN, true, failType); txn.second, role == Role::ADMIN, true, failType);
} }
catch (std::exception&) catch (std::exception&)

View File

@@ -21,180 +21,94 @@
#define RIPPLE_RPC_TRANSACTIONSIGN_H_INCLUDED #define RIPPLE_RPC_TRANSACTIONSIGN_H_INCLUDED
#include <ripple/app/misc/NetworkOPs.h> #include <ripple/app/misc/NetworkOPs.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/server/Role.h> #include <ripple/server/Role.h>
namespace ripple { namespace ripple {
// Forward declarations
class LoadFeeTrack;
namespace RPC { namespace RPC {
namespace detail { /** Fill in the fee on behalf of the client.
This is called when the client does not explicitly specify the fee.
The client may also put a ceiling on the amount of the fee. This ceiling
is expressed as a multiplier based on the current ledger's fee schedule.
// A class that allows these methods to be called with or without a JSON fields
// real NetworkOPs instance. This allows for unit testing.
class TxnSignApiFacade
{
private:
NetworkOPs* const netOPs_;
std::shared_ptr<ReadView const> ledger_;
AccountID accountID_;
std::shared_ptr<SLE const> sle_;
public: "Fee" The fee paid by the transaction. Omitted when the client
// Enum used to construct a Facade for unit tests. wants the fee filled in.
enum NoNetworkOPs{
noNetOPs
};
TxnSignApiFacade () = delete; "fee_mult_max" A multiplier applied to the current ledger's transaction
TxnSignApiFacade (TxnSignApiFacade const&) = delete; fee that caps the maximum the fee server should auto fill.
TxnSignApiFacade& operator= (TxnSignApiFacade const&) = delete; If this optional field is not specified, then a default
multiplier is used.
// For use in non unit testing circumstances. @param tx The JSON corresponding to the transaction to fill in.
explicit TxnSignApiFacade (NetworkOPs& netOPs) @param ledger A ledger for retrieving the current fee schedule.
: netOPs_ (&netOPs) @param roll Identifies if this is called by an administrative endpoint.
{ }
// For testTransactionRPC unit tests.
explicit TxnSignApiFacade (NoNetworkOPs noOPs)
: netOPs_ (nullptr) { }
// For testAutoFillFees unit tests.
TxnSignApiFacade (NoNetworkOPs noOPs, std::shared_ptr<ReadView const> ledger)
: netOPs_ (nullptr)
, ledger_ (ledger)
{ }
void snapshotAccountState (AccountID const& accountID);
bool isValidAccount () const;
std::uint32_t getSeq () const;
boost::optional<STPathSet>
findPathsForOneIssuer (
AccountID const& dstAccountID,
Issue const& srcIssue,
STAmount const& dstAmount,
int searchLevel,
unsigned int const maxPaths,
STPathSet const& paths,
STPath& fullLiquidityPath) const;
void processTransaction (
Transaction::pointer& transaction,
bool bAdmin,
bool bLocal,
NetworkOPs::FailHard failType);
std::uint64_t scaleFeeBase (std::uint64_t fee) const;
std::uint64_t scaleFeeLoad (std::uint64_t fee, bool bAdmin) const;
bool hasAccountRoot () const;
error_code_i singleAcctMatchesPubKey (
RippleAddress const& publicKey) const;
error_code_i multiAcctMatchesPubKey (
AccountID const& acctID,
RippleAddress const& publicKey) const;
int getValidatedLedgerAge () const;
bool isLoadedCluster () const;
};
// A function to auto-fill fees.
enum class AutoFill : unsigned char
{
dont,
might
};
@return A JSON object containing the error results, if any
*/
Json::Value checkFee ( Json::Value checkFee (
Json::Value& request, Json::Value& request,
TxnSignApiFacade& apiFacade,
Role const role, Role const role,
AutoFill const doAutoFill); bool doAutoFill,
LoadFeeTrack const& feeTrack,
std::shared_ptr<ReadView const>& ledger);
} // namespace detail // Return a std::function<> that calls NetworkOPs::processTransaction.
using ProcessTransactionFn =
std::function<void (Transaction::pointer& transaction,
bool bAdmin, bool bLocal, NetworkOPs::FailHard failType)>;
inline ProcessTransactionFn getProcessTxnFn (NetworkOPs& netOPs)
{
return [&netOPs](Transaction::pointer& transaction,
bool bAdmin, bool bLocal, NetworkOPs::FailHard failType)
{
netOPs.processTransaction(transaction, bAdmin, bLocal, failType);
};
}
/** Returns a Json::objectValue. */ /** Returns a Json::objectValue. */
Json::Value transactionSign ( Json::Value transactionSign (
Json::Value params, // Passed by value so it can be modified locally. Json::Value params, // Passed by value so it can be modified locally.
NetworkOPs::FailHard failType, NetworkOPs::FailHard failType,
detail::TxnSignApiFacade& apiFacade, Role role,
Role role); int validatedLedgerAge,
LoadFeeTrack const& feeTrack,
/** Returns a Json::objectValue. */ std::shared_ptr<ReadView const> ledger);
inline
Json::Value transactionSign (
Json::Value const& params,
NetworkOPs::FailHard failType,
NetworkOPs& netOPs,
Role role)
{
detail::TxnSignApiFacade apiFacade (netOPs);
return transactionSign (params, failType, apiFacade, role);
}
/** Returns a Json::objectValue. */ /** Returns a Json::objectValue. */
Json::Value transactionSubmit ( Json::Value transactionSubmit (
Json::Value params, // Passed by value so it can be modified locally. Json::Value params, // Passed by value so it can be modified locally.
NetworkOPs::FailHard failType, NetworkOPs::FailHard failType,
detail::TxnSignApiFacade& apiFacade, Role role,
Role role); int validatedLedgerAge,
LoadFeeTrack const& feeTrack,
/** Returns a Json::objectValue. */ std::shared_ptr<ReadView const> ledger,
inline ProcessTransactionFn const& processTransaction);
Json::Value transactionSubmit (
Json::Value const& params,
NetworkOPs::FailHard failType,
NetworkOPs& netOPs,
Role role)
{
detail::TxnSignApiFacade apiFacade (netOPs);
return transactionSubmit (params, failType, apiFacade, role);
}
/** Returns a Json::objectValue. */ /** Returns a Json::objectValue. */
Json::Value transactionSignFor ( Json::Value transactionSignFor (
Json::Value params, // Passed by value so it can be modified locally. Json::Value params, // Passed by value so it can be modified locally.
NetworkOPs::FailHard failType, NetworkOPs::FailHard failType,
detail::TxnSignApiFacade& apiFacade, Role role,
Role role); int validatedLedgerAge,
LoadFeeTrack const& feeTrack,
/** Returns a Json::objectValue. */ std::shared_ptr<ReadView const> ledger);
inline
Json::Value transactionSignFor (
Json::Value const& params,
NetworkOPs::FailHard failType,
NetworkOPs& netOPs,
Role role)
{
detail::TxnSignApiFacade apiFacade (netOPs);
return transactionSignFor (params, failType, apiFacade, role);
}
/** Returns a Json::objectValue. */ /** Returns a Json::objectValue. */
Json::Value transactionSubmitMultiSigned ( Json::Value transactionSubmitMultiSigned (
Json::Value params, // Passed by value so it can be modified locally. Json::Value params, // Passed by value so it can be modified locally.
NetworkOPs::FailHard failType, NetworkOPs::FailHard failType,
detail::TxnSignApiFacade& apiFacade, Role role,
Role role); int validatedLedgerAge,
LoadFeeTrack const& feeTrack,
/** Returns a Json::objectValue. */ std::shared_ptr<ReadView const> ledger,
inline ProcessTransactionFn const& processTransaction);
Json::Value transactionSubmitMultiSigned (
Json::Value const& params,
NetworkOPs::FailHard failType,
NetworkOPs& netOPs,
Role role)
{
detail::TxnSignApiFacade apiFacade (netOPs);
return transactionSubmitMultiSigned (params, failType, apiFacade, role);
}
} // RPC } // RPC
} // ripple } // ripple

View File

@@ -18,12 +18,11 @@
//============================================================================== //==============================================================================
#include <BeastConfig.h> #include <BeastConfig.h>
#include <ripple/app/paths/FindPaths.h> #include <ripple/core/LoadFeeTrack.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/json/json_reader.h> #include <ripple/json/json_reader.h>
#include <ripple/protocol/SecretKey.h> #include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/rpc/impl/TransactionSign.h> #include <ripple/rpc/impl/TransactionSign.h>
#include <ripple/test/jtx.h>
#include <beast/unit_test/suite.h> #include <beast/unit_test/suite.h>
namespace ripple { namespace ripple {
@@ -303,7 +302,7 @@ R"({
"Amount": { "Amount": {
"value": "10", "value": "10",
"currency": "USD", "currency": "USD",
"issuer": "0123456789012345678901234567890123456789" "issuer": "rLPwWB1itaUGMV8kbMLLysjGkEpTM2Soy4"
}, },
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA", "Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"TransactionType": "Payment" "TransactionType": "Payment"
@@ -326,7 +325,7 @@ R"({
"Amount": { "Amount": {
"value": "10", "value": "10",
"currency": "USD", "currency": "USD",
"issuer": "0123456789012345678901234567890123456789" "issuer": "rLPwWB1itaUGMV8kbMLLysjGkEpTM2Soy4"
}, },
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA", "Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"Paths": "", "Paths": "",
@@ -350,12 +349,12 @@ R"({
"Amount": { "Amount": {
"value": "10", "value": "10",
"currency": "USD", "currency": "USD",
"issuer": "0123456789012345678901234567890123456789" "issuer": "rLPwWB1itaUGMV8kbMLLysjGkEpTM2Soy4"
}, },
"SendMax": { "SendMax": {
"value": "5", "value": "5",
"currency": "USD", "currency": "USD",
"issuer": "0123456789012345678901234567890123456789" "issuer": "rLPwWB1itaUGMV8kbMLLysjGkEpTM2Soy4"
}, },
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA", "Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"TransactionType": "Payment" "TransactionType": "Payment"
@@ -378,7 +377,7 @@ R"({
"Amount": { "Amount": {
"value": "10", "value": "10",
"currency": "USD", "currency": "USD",
"issuer": "0123456789012345678901234567890123456789" "issuer": "rLPwWB1itaUGMV8kbMLLysjGkEpTM2Soy4"
}, },
"SendMax": 10000, "SendMax": 10000,
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA", "Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
@@ -663,8 +662,8 @@ R"({
} }
})", })",
{ {
"", "Secret does not match account.",
"", "Secret does not match account.",
"", "",
"Missing field 'Signers'."}}, "Missing field 'Signers'."}},
@@ -743,8 +742,8 @@ R"({
} }
})", })",
{ {
"", "Secret does not match account.",
"", "Secret does not match account.",
"Missing field 'tx_json.Fee'.", "Missing field 'tx_json.Fee'.",
"Missing field 'tx_json.Fee'."}}, "Missing field 'tx_json.Fee'."}},
@@ -763,8 +762,8 @@ R"({
} }
})", })",
{ {
"", "Secret does not match account.",
"", "Secret does not match account.",
"Missing field 'tx_json.Sequence'.", "Missing field 'tx_json.Sequence'.",
"Missing field 'tx_json.Sequence'."}}, "Missing field 'tx_json.Sequence'."}},
@@ -783,8 +782,8 @@ R"({
} }
})", })",
{ {
"", "Secret does not match account.",
"", "Secret does not match account.",
"Missing field 'tx_json.SigningPubKey'.", "Missing field 'tx_json.SigningPubKey'.",
"Missing field 'tx_json.SigningPubKey'."}}, "Missing field 'tx_json.SigningPubKey'."}},
@@ -804,8 +803,8 @@ R"({
} }
})", })",
{ {
"", "Secret does not match account.",
"", "Secret does not match account.",
"When multi-signing 'tx_json.SigningPubKey' must be empty.", "When multi-signing 'tx_json.SigningPubKey' must be empty.",
"When multi-signing 'tx_json.SigningPubKey' must be empty."}}, "When multi-signing 'tx_json.SigningPubKey' must be empty."}},
@@ -857,6 +856,538 @@ R"({
"Missing field 'account'.", "Missing field 'account'.",
""}}, ""}},
{ "Missing tx_json in submit_multisigned.",
R"({
"command": "submit_multisigned",
"Signers": [
{
"Signer": {
"Account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
"TxnSignature": "3045022100F9ED357606932697A4FAB2BE7F222C21DD93CA4CFDD90357AADD07465E8457D6022038173193E3DFFFB5D78DD738CC0905395F885DA65B98FDB9793901FE3FD26ECE",
"SigningPubKey": "02FE36A690D6973D55F88553F5D2C4202DE75F2CF8A6D0E17C70AC223F044501F8"
}
}
]
})",
{
"Missing field 'secret'.",
"Missing field 'secret'.",
"Missing field 'account'.",
"Missing field 'tx_json'."}},
{ "Missing sequence in submit_multisigned.",
R"({
"command": "submit_multisigned",
"Signers": [
{
"Signer": {
"Account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
"TxnSignature": "3045022100F9ED357606932697A4FAB2BE7F222C21DD93CA4CFDD90357AADD07465E8457D6022038173193E3DFFFB5D78DD738CC0905395F885DA65B98FDB9793901FE3FD26ECE",
"SigningPubKey": "02FE36A690D6973D55F88553F5D2C4202DE75F2CF8A6D0E17C70AC223F044501F8"
}
}
],
"tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Amount": "1000000000",
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"Fee": 50,
"SigningPubKey": "",
"TransactionType": "Payment"
}
})",
{
"Missing field 'secret'.",
"Missing field 'secret'.",
"Missing field 'account'.",
"Missing field 'tx_json.Sequence'."}},
{ "Missing SigningPubKey in submit_multisigned.",
R"({
"command": "submit_multisigned",
"Signers": [
{
"Signer": {
"Account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
"TxnSignature": "3045022100F9ED357606932697A4FAB2BE7F222C21DD93CA4CFDD90357AADD07465E8457D6022038173193E3DFFFB5D78DD738CC0905395F885DA65B98FDB9793901FE3FD26ECE",
"SigningPubKey": "02FE36A690D6973D55F88553F5D2C4202DE75F2CF8A6D0E17C70AC223F044501F8"
}
}
],
"tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Amount": "1000000000",
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"Fee": 50,
"Sequence": 0,
"TransactionType": "Payment"
}
})",
{
"Missing field 'secret'.",
"Missing field 'secret'.",
"Missing field 'account'.",
"Missing field 'tx_json.SigningPubKey'."}},
{ "Non-empty SigningPubKey in submit_multisigned.",
R"({
"command": "submit_multisigned",
"Signers": [
{
"Signer": {
"Account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
"TxnSignature": "3045022100F9ED357606932697A4FAB2BE7F222C21DD93CA4CFDD90357AADD07465E8457D6022038173193E3DFFFB5D78DD738CC0905395F885DA65B98FDB9793901FE3FD26ECE",
"SigningPubKey": "02FE36A690D6973D55F88553F5D2C4202DE75F2CF8A6D0E17C70AC223F044501F8"
}
}
],
"tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Amount": "1000000000",
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"Fee": 50,
"Sequence": 0,
"SigningPubKey": "02FE36A690D6973D55F88553F5D2C4202DE75F2CF8A6D0E17C70AC223F044501F8",
"TransactionType": "Payment"
}
})",
{
"Missing field 'secret'.",
"Missing field 'secret'.",
"Missing field 'account'.",
"When multi-signing 'tx_json.SigningPubKey' must be empty."}},
{ "Missing TransactionType in submit_multisigned.",
R"({
"command": "submit_multisigned",
"Signers": [
{
"Signer": {
"Account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
"TxnSignature": "3045022100F9ED357606932697A4FAB2BE7F222C21DD93CA4CFDD90357AADD07465E8457D6022038173193E3DFFFB5D78DD738CC0905395F885DA65B98FDB9793901FE3FD26ECE",
"SigningPubKey": "02FE36A690D6973D55F88553F5D2C4202DE75F2CF8A6D0E17C70AC223F044501F8"
}
}
],
"tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Amount": "1000000000",
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"Fee": 50,
"Sequence": 0,
"SigningPubKey": "",
}
})",
{
"Missing field 'secret'.",
"Missing field 'secret'.",
"Missing field 'account'.",
"Missing field 'tx_json.TransactionType'."}},
{ "Missing Account in submit_multisigned.",
R"({
"command": "submit_multisigned",
"Signers": [
{
"Signer": {
"Account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
"TxnSignature": "3045022100F9ED357606932697A4FAB2BE7F222C21DD93CA4CFDD90357AADD07465E8457D6022038173193E3DFFFB5D78DD738CC0905395F885DA65B98FDB9793901FE3FD26ECE",
"SigningPubKey": "02FE36A690D6973D55F88553F5D2C4202DE75F2CF8A6D0E17C70AC223F044501F8"
}
}
],
"tx_json": {
"Amount": "1000000000",
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"Fee": 50,
"Sequence": 0,
"SigningPubKey": "",
"TransactionType": "Payment"
}
})",
{
"Missing field 'secret'.",
"Missing field 'secret'.",
"Missing field 'account'.",
"Missing field 'tx_json.Account'."}},
{ "Malformed Account in submit_multisigned.",
R"({
"command": "submit_multisigned",
"Signers": [
{
"Signer": {
"Account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
"TxnSignature": "3045022100F9ED357606932697A4FAB2BE7F222C21DD93CA4CFDD90357AADD07465E8457D6022038173193E3DFFFB5D78DD738CC0905395F885DA65B98FDB9793901FE3FD26ECE",
"SigningPubKey": "02FE36A690D6973D55F88553F5D2C4202DE75F2CF8A6D0E17C70AC223F044501F8"
}
}
],
"tx_json": {
"Account": "NotAnAccount",
"Amount": "1000000000",
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"Fee": 50,
"Sequence": 0,
"SigningPubKey": "",
"TransactionType": "Payment"
}
})",
{
"Missing field 'secret'.",
"Missing field 'secret'.",
"Missing field 'account'.",
"Invalid field 'tx_json.Account'."}},
{ "Account not in ledger in submit_multisigned.",
R"({
"command": "submit_multisigned",
"Signers": [
{
"Signer": {
"Account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
"TxnSignature": "3045022100F9ED357606932697A4FAB2BE7F222C21DD93CA4CFDD90357AADD07465E8457D6022038173193E3DFFFB5D78DD738CC0905395F885DA65B98FDB9793901FE3FD26ECE",
"SigningPubKey": "02FE36A690D6973D55F88553F5D2C4202DE75F2CF8A6D0E17C70AC223F044501F8"
}
}
],
"tx_json": {
"Account": "rDg53Haik2475DJx8bjMDSDPj4VX7htaMd",
"Amount": "1000000000",
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"Fee": 50,
"Sequence": 0,
"SigningPubKey": "",
"TransactionType": "Payment"
}
})",
{
"Missing field 'secret'.",
"Missing field 'secret'.",
"Missing field 'account'.",
"Source account not found."}},
{ "Missing Fee in submit_multisigned.",
R"({
"command": "submit_multisigned",
"Signers": [
{
"Signer": {
"Account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
"TxnSignature": "3045022100F9ED357606932697A4FAB2BE7F222C21DD93CA4CFDD90357AADD07465E8457D6022038173193E3DFFFB5D78DD738CC0905395F885DA65B98FDB9793901FE3FD26ECE",
"SigningPubKey": "02FE36A690D6973D55F88553F5D2C4202DE75F2CF8A6D0E17C70AC223F044501F8"
}
}
],
"tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Amount": "1000000000",
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"Sequence": 0,
"SigningPubKey": "",
"TransactionType": "Payment"
}
})",
{
"Missing field 'secret'.",
"Missing field 'secret'.",
"Missing field 'account'.",
"Missing field 'tx_json.Fee'."}},
{ "Non-numeric Fee in submit_multisigned.",
R"({
"command": "submit_multisigned",
"Signers": [
{
"Signer": {
"Account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
"TxnSignature": "3045022100F9ED357606932697A4FAB2BE7F222C21DD93CA4CFDD90357AADD07465E8457D6022038173193E3DFFFB5D78DD738CC0905395F885DA65B98FDB9793901FE3FD26ECE",
"SigningPubKey": "02FE36A690D6973D55F88553F5D2C4202DE75F2CF8A6D0E17C70AC223F044501F8"
}
}
],
"tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Amount": "1000000000",
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"Fee": 50.1,
"Sequence": 0,
"SigningPubKey": "",
"TransactionType": "Payment"
}
})",
{
"Missing field 'secret'.",
"Missing field 'secret'.",
"Missing field 'account'.",
"Field 'tx_json.Fee' has invalid data."}},
{ "Missing Amount in submit_multisigned Payment.",
R"({
"command": "submit_multisigned",
"Signers": [
{
"Signer": {
"Account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
"TxnSignature": "3045022100F9ED357606932697A4FAB2BE7F222C21DD93CA4CFDD90357AADD07465E8457D6022038173193E3DFFFB5D78DD738CC0905395F885DA65B98FDB9793901FE3FD26ECE",
"SigningPubKey": "02FE36A690D6973D55F88553F5D2C4202DE75F2CF8A6D0E17C70AC223F044501F8"
}
}
],
"tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"Fee": 50000000,
"Sequence": 0,
"SigningPubKey": "",
"TransactionType": "Payment"
}
})",
{
"Missing field 'secret'.",
"Missing field 'secret'.",
"Missing field 'account'.",
"Missing field 'tx_json.Amount'."}},
{ "Invalid Amount in submit_multisigned Payment.",
R"({
"command": "submit_multisigned",
"Signers": [
{
"Signer": {
"Account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
"TxnSignature": "3045022100F9ED357606932697A4FAB2BE7F222C21DD93CA4CFDD90357AADD07465E8457D6022038173193E3DFFFB5D78DD738CC0905395F885DA65B98FDB9793901FE3FD26ECE",
"SigningPubKey": "02FE36A690D6973D55F88553F5D2C4202DE75F2CF8A6D0E17C70AC223F044501F8"
}
}
],
"tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Amount": "NotANumber",
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"Fee": 50,
"Sequence": 0,
"SigningPubKey": "",
"TransactionType": "Payment"
}
})",
{
"Missing field 'secret'.",
"Missing field 'secret'.",
"Missing field 'account'.",
"Invalid field 'tx_json.Amount'."}},
{ "No build_path in submit_multisigned.",
R"({
"command": "submit_multisigned",
"build_path": 1,
"Signers": [
{
"Signer": {
"Account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
"TxnSignature": "3045022100F9ED357606932697A4FAB2BE7F222C21DD93CA4CFDD90357AADD07465E8457D6022038173193E3DFFFB5D78DD738CC0905395F885DA65B98FDB9793901FE3FD26ECE",
"SigningPubKey": "02FE36A690D6973D55F88553F5D2C4202DE75F2CF8A6D0E17C70AC223F044501F8"
}
}
],
"tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Amount": "1000000000",
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"Fee": 50,
"Sequence": 0,
"SigningPubKey": "",
"TransactionType": "Payment"
}
})",
{
"Missing field 'secret'.",
"Missing field 'secret'.",
"Missing field 'account'.",
"Field 'build_path' not allowed in this context."}},
{ "Missing Destination in submit_multisigned Payment.",
R"({
"command": "submit_multisigned",
"Signers": [
{
"Signer": {
"Account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
"TxnSignature": "3045022100F9ED357606932697A4FAB2BE7F222C21DD93CA4CFDD90357AADD07465E8457D6022038173193E3DFFFB5D78DD738CC0905395F885DA65B98FDB9793901FE3FD26ECE",
"SigningPubKey": "02FE36A690D6973D55F88553F5D2C4202DE75F2CF8A6D0E17C70AC223F044501F8"
}
}
],
"tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Amount": "1000000000",
"Fee": 50,
"Sequence": 0,
"SigningPubKey": "",
"TransactionType": "Payment"
}
})",
{
"Missing field 'secret'.",
"Missing field 'secret'.",
"Missing field 'account'.",
"Missing field 'tx_json.Destination'."}},
{ "Malformed Destination in submit_multisigned Payment.",
R"({
"command": "submit_multisigned",
"Signers": [
{
"Signer": {
"Account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
"TxnSignature": "3045022100F9ED357606932697A4FAB2BE7F222C21DD93CA4CFDD90357AADD07465E8457D6022038173193E3DFFFB5D78DD738CC0905395F885DA65B98FDB9793901FE3FD26ECE",
"SigningPubKey": "02FE36A690D6973D55F88553F5D2C4202DE75F2CF8A6D0E17C70AC223F044501F8"
}
}
],
"tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Amount": "1000000000",
"Destination": "NotADestination",
"Fee": 50,
"Sequence": 0,
"SigningPubKey": "",
"TransactionType": "Payment"
}
})",
{
"Missing field 'secret'.",
"Missing field 'secret'.",
"Missing field 'account'.",
"Invalid field 'tx_json.Destination'."}},
{ "Missing Signers field in submit_multisigned.",
R"({
"command": "submit_multisigned",
"tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Amount": "1000000000",
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"Fee": 50,
"Sequence": 0,
"SigningPubKey": "",
"TransactionType": "Payment"
}
})",
{
"Missing field 'secret'.",
"Missing field 'secret'.",
"Missing field 'account'.",
"Missing field 'Signers'."}},
{ "Signers not an array in submit_multisigned.",
R"({
"command": "submit_multisigned",
"Signers": {
"Account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
"TxnSignature": "3045022100F9ED357606932697A4FAB2BE7F222C21DD93CA4CFDD90357AADD07465E8457D6022038173193E3DFFFB5D78DD738CC0905395F885DA65B98FDB9793901FE3FD26ECE",
"SigningPubKey": "02FE36A690D6973D55F88553F5D2C4202DE75F2CF8A6D0E17C70AC223F044501F8"
},
"tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Amount": "1000000000",
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"Fee": 50,
"Sequence": 0,
"SigningPubKey": "",
"TransactionType": "Payment"
}
})",
{
"Missing field 'secret'.",
"Missing field 'secret'.",
"Missing field 'account'.",
"Expected Signers to be an array."}},
{ "Empty Signers array in submit_multisigned.",
R"({
"command": "submit_multisigned",
"Signers": [
],
"tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Amount": "1000000000",
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"Fee": 50,
"Sequence": 0,
"SigningPubKey": "",
"TransactionType": "Payment"
}
})",
{
"Missing field 'secret'.",
"Missing field 'secret'.",
"Missing field 'account'.",
"Signers array may not be empty."}},
{ "Duplicate Signer in submit_multisigned.",
R"({
"command": "submit_multisigned",
"Signers": [
{
"Signer": {
"Account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
"TxnSignature": "3045022100F9ED357606932697A4FAB2BE7F222C21DD93CA4CFDD90357AADD07465E8457D6022038173193E3DFFFB5D78DD738CC0905395F885DA65B98FDB9793901FE3FD26ECE",
"SigningPubKey": "02FE36A690D6973D55F88553F5D2C4202DE75F2CF8A6D0E17C70AC223F044501F8"
}
},
{
"Signer": {
"Account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
"TxnSignature": "3045022100F9ED357606932697A4FAB2BE7F222C21DD93CA4CFDD90357AADD07465E8457D6022038173193E3DFFFB5D78DD738CC0905395F885DA65B98FDB9793901FE3FD26ECE",
"SigningPubKey": "02FE36A690D6973D55F88553F5D2C4202DE75F2CF8A6D0E17C70AC223F044501F8"
}
}
],
"tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Amount": "1000000000",
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"Fee": 50,
"Sequence": 0,
"SigningPubKey": "",
"TransactionType": "Payment"
}
})",
{
"Missing field 'secret'.",
"Missing field 'secret'.",
"Missing field 'account'.",
"Duplicate Signers:Signer:Account entries (rPcNzota6B8YBokhYtcTNqQVCngtbnWfux) are not allowed."}},
{ "Signer is tx_json Account in submit_multisigned.",
R"({
"command": "submit_multisigned",
"Signers": [
{
"Signer": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"TxnSignature": "3045022100F9ED357606932697A4FAB2BE7F222C21DD93CA4CFDD90357AADD07465E8457D6022038173193E3DFFFB5D78DD738CC0905395F885DA65B98FDB9793901FE3FD26ECE",
"SigningPubKey": "02FE36A690D6973D55F88553F5D2C4202DE75F2CF8A6D0E17C70AC223F044501F8"
}
}
],
"tx_json": {
"Account": "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh",
"Amount": "1000000000",
"Destination": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"Fee": 50,
"Sequence": 0,
"SigningPubKey": "",
"TransactionType": "Payment"
}
})",
{
"Missing field 'secret'.",
"Missing field 'secret'.",
"Missing field 'account'.",
"A Signer may not be the transaction's Account (rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh)."}},
}; };
@@ -866,19 +1397,16 @@ public:
void testAutoFillFees () void testAutoFillFees ()
{ {
Config const config; Config const config;
auto const ledger = std::shared_ptr<const ReadView> ledger =
std::make_shared<Ledger>( std::make_shared<Ledger>(create_genesis, config);
create_genesis, config); LoadFeeTrack const feeTrack;
using namespace detail;
TxnSignApiFacade apiFacade (TxnSignApiFacade::noNetOPs, ledger);
{ {
Json::Value req; Json::Value req;
Json::Reader ().parse ( Json::Reader ().parse (
"{ \"fee_mult_max\" : 1, \"tx_json\" : { } } ", req); "{ \"fee_mult_max\" : 1, \"tx_json\" : { } } ", req);
Json::Value result = Json::Value result =
checkFee (req, apiFacade, Role::ADMIN, AutoFill::might); checkFee (req, Role::ADMIN, true, feeTrack, ledger);
expect (! RPC::contains_error (result), "Legal checkFee"); expect (! RPC::contains_error (result), "Legal checkFee");
} }
@@ -888,30 +1416,72 @@ public:
Json::Reader ().parse ( Json::Reader ().parse (
"{ \"fee_mult_max\" : 0, \"tx_json\" : { } } ", req); "{ \"fee_mult_max\" : 0, \"tx_json\" : { } } ", req);
Json::Value result = Json::Value result =
checkFee (req, apiFacade, Role::ADMIN, AutoFill::might); checkFee (req, Role::ADMIN, true, feeTrack, ledger);
expect (RPC::contains_error (result), "Invalid checkFee"); expect (RPC::contains_error (result), "Invalid checkFee");
} }
} }
// A function that can be called as though it would process a transaction.
static void fakeProcessTransaction (
Transaction::pointer&, bool, bool, NetworkOPs::FailHard)
{
;
}
void testTransactionRPC () void testTransactionRPC ()
{ {
// A list of all the functions we want to test and their fail bits. // Use jtx to set up a ledger so the tests will do the right thing.
using transactionFunc = Json::Value (*) ( test::jtx::Account const a {"a"}; // rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA
test::jtx::Account const g {"g"}; // rLPwWB1itaUGMV8kbMLLysjGkEpTM2Soy4
auto const USD = g["USD"];
// master is rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh.
// "b" (not in the ledger) is rDg53Haik2475DJx8bjMDSDPj4VX7htaMd.
// "c" (phantom signer) is rPcNzota6B8YBokhYtcTNqQVCngtbnWfux.
test::jtx::Env env(*this);
env.fund(test::jtx::XRP(100000), a, g);
env.close();
env(trust(a, USD(1000)));
env(trust(env.master, USD(1000)));
env(pay(g, a, USD(50)));
env(pay(g, env.master, USD(50)));
env.close();
auto const ledger = env.open();
LoadFeeTrack const feeTrack;
ProcessTransactionFn processTxn = fakeProcessTransaction;
// A list of all the functions we want to test.
using signFunc = Json::Value (*) (
Json::Value params, Json::Value params,
NetworkOPs::FailHard failType, NetworkOPs::FailHard failType,
detail::TxnSignApiFacade& apiFacade, Role role,
Role role); int validatedLedgerAge,
LoadFeeTrack const& feeTrack,
std::shared_ptr<ReadView const> ledger);
using submitFunc = Json::Value (*) (
Json::Value params,
NetworkOPs::FailHard failType,
Role role,
int validatedLedgerAge,
LoadFeeTrack const& feeTrack,
std::shared_ptr<ReadView const> ledger,
ProcessTransactionFn const& processTransaction);
using TestStuff = using TestStuff =
std::tuple <transactionFunc, char const*, unsigned int>; std::tuple <signFunc, submitFunc, char const*, unsigned int>;
static TestStuff const testFuncs [] = static TestStuff const testFuncs [] =
{ {
TestStuff {transactionSign, "sign", 0}, TestStuff {transactionSign, nullptr, "sign", 0},
TestStuff {transactionSubmit, "submit", 1}, TestStuff {nullptr, transactionSubmit, "submit", 1},
TestStuff {transactionSignFor, "sign_for", 2}, TestStuff {transactionSignFor, nullptr, "sign_for", 2},
TestStuff {transactionSubmitMultiSigned, "submit_multisigned", 3} TestStuff {nullptr, transactionSubmitMultiSigned, "submit_multisigned", 3}
}; };
for (auto testFunc : testFuncs) for (auto testFunc : testFuncs)
@@ -930,24 +1500,41 @@ public:
for (Role testRole : testedRoles) for (Role testRole : testedRoles)
{ {
// Mock so we can run without a ledger. Json::Value result;
detail::TxnSignApiFacade apiFacade ( auto const signFn = get<0>(testFunc);
detail::TxnSignApiFacade::noNetOPs); if (signFn != nullptr)
{
Json::Value result = get<0>(testFunc) ( assert (get<1>(testFunc) == nullptr);
result = signFn (
req, req,
NetworkOPs::FailHard::yes, NetworkOPs::FailHard::yes,
apiFacade, testRole,
testRole); 1,
feeTrack,
ledger);
}
else
{
auto const submitFn = get<1>(testFunc);
assert (submitFn != nullptr);
result = submitFn (
req,
NetworkOPs::FailHard::yes,
testRole,
1,
feeTrack,
ledger,
processTxn);
}
std::string errStr; std::string errStr;
if (RPC::contains_error (result)) if (RPC::contains_error (result))
errStr = result["error_message"].asString (); errStr = result["error_message"].asString ();
std::string const expStr (txnTest.expMsg[get<2>(testFunc)]); std::string const expStr (txnTest.expMsg[get<3>(testFunc)]);
expect (errStr == expStr, expect (errStr == expStr,
"Expected: \"" + expStr + "\"\n Got: \"" + errStr + "Expected: \"" + expStr + "\"\n Got: \"" + errStr +
"\"\nIn " + std::string (get<1>(testFunc)) + "\"\nIn " + std::string (get<2>(testFunc)) +
": " + txnTest.description); ": " + txnTest.description);
} }
} }