2-level transaction multi-signatures (RIPD-182):

This commit provides support for 2-level multi-signing of
transactions.  The ability is usually compiled out, since other
aspects of multi-signing are not yet complete.

Here are the missing parts:

 o Full support for Tickets in transactions.
 o Variable fees based on the number of signers,
 o Multiple SignerLists with access control flags on accounts,
 o Enable / disable operations based on access control flags,
 o Enable / disable all of multi-signing based on an amendment,
 o Integration tests, and
 o Documentation.
This commit is contained in:
Scott Schurr
2015-02-09 10:35:24 -08:00
committed by Vinnie Falco
parent cf1638e6de
commit d6ef66646f
29 changed files with 3552 additions and 59 deletions

View File

@@ -0,0 +1,39 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012-2014 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 <BeastConfig.h>
#include <ripple/basics/StringUtilities.h>
namespace ripple {
// {
// SigningAccounts <array>,
// tx_json: <object>,
// }
Json::Value doSubmitMultiSigned (RPC::Context& context)
{
context.loadType = Resource::feeHighBurdenRPC;
NetworkOPs::FailHard const failType = NetworkOPs::doFailHard (
context.params.isMember ("fail_hard")
&& context.params["fail_hard"].asBool ());
return RPC::transactionSubmitMultiSigned (
context.params, failType, context.netOps, context.role);
}
} // ripple

View File

@@ -137,6 +137,9 @@ HandlerTable HANDLERS({
{ "sign_for", byRef (&doSignFor), Role::USER, NO_CONDITION },
#endif // RIPPLE_ENABLE_MULTI_SIGN
{ "submit", byRef (&doSubmit), Role::USER, NEEDS_CURRENT_LEDGER },
#if RIPPLE_ENABLE_MULTI_SIGN
{ "submit_multisigned", byRef (&doSubmitMultiSigned), Role::USER, NEEDS_CURRENT_LEDGER },
#endif // RIPPLE_ENABLE_MULTI_SIGN
{ "server_info", byRef (&doServerInfo), Role::USER, NO_CONDITION },
{ "server_state", byRef (&doServerState), Role::USER, NO_CONDITION },
{ "stop", byRef (&doStop), Role::ADMIN, NO_CONDITION },

View File

@@ -525,7 +525,6 @@ struct transactionPreProcessResult
transactionPreProcessResult& operator=
(transactionPreProcessResult&&) = delete;
transactionPreProcessResult (Json::Value&& json)
: first (std::move (json))
, second ()
@@ -1019,5 +1018,276 @@ Json::Value transactionSignFor (
return json;
}
/** Returns a Json::objectValue. */
Json::Value transactionSubmitMultiSigned (
Json::Value jvRequest,
NetworkOPs::FailHard failType,
detail::TxnSignApiFacade& apiFacade,
Role role)
{
WriteLog (lsDEBUG, RPCHandler)
<< "transactionSubmitMultiSigned: " << jvRequest;
// When multi-signing, the "Sequence" and "SigningPubKey" fields must
// be passed in by the caller.
using namespace detail;
{
Json::Value err = checkMultiSignFields (jvRequest);
if (RPC::contains_error (err))
return std::move (err);
}
Json::Value& tx_json (jvRequest ["tx_json"]);
auto const txJsonResult = checkTxJsonFields(tx_json, apiFacade, role, true);
if (RPC::contains_error (txJsonResult.first))
return std::move (txJsonResult.first);
RippleAddress const raSrcAddressID = std::move (txJsonResult.second);
apiFacade.snapshotAccountState (raSrcAddressID);
if (!apiFacade.isValidAccount ())
{
// If not offline and did not find account, error.
WriteLog (lsDEBUG, RPCHandler)
<< "transactionSubmitMultiSigned: Failed to find source account "
<< "in current ledger: "
<< raSrcAddressID.humanAccountID ();
return rpcError (rpcSRC_ACT_NOT_FOUND);
}
{
Json::Value err = checkFee (jvRequest, apiFacade, role, AutoFill::dont);
if (RPC::contains_error(err))
return std::move (err);
err = checkPayment (
jvRequest,
tx_json,
raSrcAddressID,
apiFacade,
role,
PathFind::dont);
if (RPC::contains_error(err))
return std::move (err);
}
// Grind through the JSON in tx_json to produce a STTx
STTx::pointer stpTrans;
{
STParsedJSONObject parsedTx_json ("tx_json", tx_json);
if (!parsedTx_json.object)
{
Json::Value jvResult;
jvResult ["error"] = parsedTx_json.error ["error"];
jvResult ["error_code"] = parsedTx_json.error ["error_code"];
jvResult ["error_message"] = parsedTx_json.error ["error_message"];
return jvResult;
}
try
{
stpTrans =
std::make_shared<STTx> (std::move(parsedTx_json.object.get()));
}
catch (std::exception& ex)
{
std::string reason (ex.what ());
return RPC::make_error (rpcINTERNAL,
"Exception while serializing transaction: " + reason);
}
std::string reason;
if (!passesLocalChecks (*stpTrans, reason))
return RPC::make_error (rpcINVALID_PARAMS, reason);
}
// Validate the fields in the serialized transaction.
{
// We now have the transaction text serialized and in the right format.
// Verify the values of select fields.
//
// The SigningPubKey must be present but empty.
if (!stpTrans->getFieldVL (sfSigningPubKey).empty ())
{
std::ostringstream err;
err << "Invalid " << sfSigningPubKey.fieldName
<< " field. Field must be empty when multi-signing.";
return RPC::make_error (rpcINVALID_PARAMS, err.str ());
}
// The Fee field must be greater than zero.
if (stpTrans->getFieldAmount (sfFee) <= 0)
{
std::ostringstream err;
err << "Invalid " << sfFee.fieldName
<< " field. Value must be greater than zero.";
return RPC::make_error (rpcINVALID_PARAMS, err.str ());
}
}
// Check MultiSigners for valid entries.
const char* multiSignersArrayName {sfMultiSigners.getJsonName ().c_str ()};
STArray multiSigners;
{
// Verify that the MultiSigners field is present and an array.
if (! jvRequest.isMember (multiSignersArrayName))
return RPC::missing_field_error (multiSignersArrayName);
Json::Value& multiSignersValue (
jvRequest [multiSignersArrayName]);
if (! multiSignersValue.isArray ())
{
std::ostringstream err;
err << "Expected "
<< multiSignersArrayName << " to be an array";
return RPC::make_param_error (err.str ());
}
// Convert the MultiSigners into SerializedTypes.
STParsedJSONArray parsedMultiSigners (
multiSignersArrayName, multiSignersValue);
if (!parsedMultiSigners.array)
{
Json::Value jvResult;
jvResult ["error"] = parsedMultiSigners.error ["error"];
jvResult ["error_code"] =
parsedMultiSigners.error ["error_code"];
jvResult ["error_message"] =
parsedMultiSigners.error ["error_message"];
return jvResult;
}
multiSigners = std::move (parsedMultiSigners.array.get());
}
if (multiSigners.empty ())
return RPC::make_param_error ("MultiSigners array may not be empty.");
for (STObject const& signingFor : multiSigners)
{
if (signingFor.getFName () != sfSigningFor)
return RPC::make_param_error (
"MultiSigners array has a non-SigningFor entry");
// Each SigningAccounts array must have at least one entry.
STArray const& signingAccounts =
signingFor.getFieldArray (sfSigningAccounts);
if (signingAccounts.empty ())
return RPC::make_param_error (
"A SigningAccounts array may not be empty");
// Each SigningAccounts array may only contain SigningAccount objects.
auto const signingAccountsEnd = signingAccounts.end ();
auto const findItr = std::find_if (
signingAccounts.begin (), signingAccountsEnd,
[](STObject const& obj)
{ return obj.getFName () != sfSigningAccount; });
if (findItr != signingAccountsEnd)
return RPC::make_param_error (
"SigningAccounts may only contain SigningAccount objects.");
}
// Lambdas for sorting arrays and finding duplicates.
auto byFieldAccountID =
[] (STObject const& a, STObject const& b) {
return (a.getFieldAccount (sfAccount).getAccountID () <
b.getFieldAccount (sfAccount).getAccountID ()); };
auto ifDuplicateAccountID =
[] (STObject const& a, STObject const& b) {
return (a.getFieldAccount (sfAccount).getAccountID () ==
b.getFieldAccount (sfAccount).getAccountID ()); };
{
// MultiSigners are submitted sorted in Account order. This
// assures that the same list will always have the same hash.
multiSigners.sort (byFieldAccountID);
// There may be no duplicate Accounts in MultiSigners
auto const multiSignersEnd = multiSigners.end ();
auto const dupAccountItr = std::adjacent_find (
multiSigners.begin (), multiSignersEnd, ifDuplicateAccountID);
if (dupAccountItr != multiSignersEnd)
{
std::ostringstream err;
err << "Duplicate SigningFor:Account entries ("
<< dupAccountItr->getFieldAccount (sfAccount).humanAccountID ()
<< ") are not allowed.";
return RPC::make_param_error(err.str ());
}
}
// All SigningAccounts inside the MultiSigners must also be sorted and
// contain no duplicates.
for (STObject& signingFor : multiSigners)
{
STArray& signingAccts = signingFor.peekFieldArray (sfSigningAccounts);
signingAccts.sort (byFieldAccountID);
auto const signingAcctsEnd = signingAccts.end ();
auto const dupAccountItr = std::adjacent_find (
signingAccts.begin (), signingAcctsEnd, ifDuplicateAccountID);
if (dupAccountItr != signingAcctsEnd)
{
std::ostringstream err;
err << "Duplicate SigningAccounts:SigningAccount:Account entries ("
<< dupAccountItr->getFieldAccount (sfAccount).humanAccountID ()
<< ") are not allowed.";
return RPC::make_param_error(err.str ());
}
// An account may not sign for itself.
RippleAddress const signingForAcct =
signingFor.getFieldAccount (sfAccount);
auto const selfSigningItr = std::find_if(
signingAccts.begin (), signingAcctsEnd,
[&signingForAcct](STObject const& elem)
{ return elem.getFieldAccount (sfAccount) == signingForAcct; });
if (selfSigningItr != signingAcctsEnd)
{
std::ostringstream err;
err << "A SigningAccount may not SignFor itself ("
<< signingForAcct.humanAccountID () << ").";
return RPC::make_param_error(err.str ());
}
}
// Insert the MultiSigners into the transaction.
stpTrans->setFieldArray (sfMultiSigners, std::move(multiSigners));
// Make sure the SerializedTransaction makes a legitimate Transaction.
std::pair <Json::Value, Transaction::pointer> txn =
transactionConstructImpl (stpTrans);
if (!txn.second)
return txn.first;
// Finally, submit the transaction.
try
{
// FIXME: For performance, should use asynch interface
apiFacade.processTransaction (
txn.second, role == Role::ADMIN, true, failType);
}
catch (std::exception&)
{
return RPC::make_error (rpcINTERNAL,
"Exception occurred during transaction submission.");
}
return transactionFormatResultImpl (txn.second);
}
} // RPC
} // ripple

View File

@@ -168,6 +168,24 @@ Json::Value transactionSignFor (
return transactionSignFor (params, failType, apiFacade, role);
}
/** Returns a Json::objectValue. */
Json::Value transactionSubmitMultiSigned (
Json::Value params, // Passed by value so it can be modified locally.
NetworkOPs::FailHard failType,
detail::TxnSignApiFacade& apiFacade,
Role role);
/** Returns a Json::objectValue. */
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
} // ripple

View File

@@ -963,7 +963,8 @@ public:
{
TestStuff {transactionSign, "sign", 0},
TestStuff {transactionSubmit, "submit", 1},
TestStuff {transactionSignFor, "sign_for", 2}
TestStuff {transactionSignFor, "sign_for", 2},
TestStuff {transactionSubmitMultiSigned, "submit_multisigned", 3}
};
for (auto testFunc : testFuncs)