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

@@ -2046,6 +2046,20 @@
</ClInclude>
<ClInclude Include="..\..\src\ripple\app\tx\LocalTxs.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\tx\tests\common_transactor.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='debug.classic|x64'">..\..\src\soci\src\core;..\..\src\sqlite;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='release.classic|x64'">..\..\src\soci\src\core;..\..\src\sqlite;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<ClInclude Include="..\..\src\ripple\app\tx\tests\common_transactor.h">
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\tx\tests\MultiSign.test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='debug.classic|x64'">..\..\src\soci\src\core;..\..\src\sqlite;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
<AdditionalIncludeDirectories Condition="'$(Configuration)|$(Platform)'=='release.classic|x64'">..\..\src\soci\src\core;..\..\src\sqlite;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
</ClCompile>
<ClCompile Include="..\..\src\ripple\app\tx\tests\OfferStream.test.cpp">
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
@@ -3217,6 +3231,9 @@
<ClCompile Include="..\..\src\ripple\rpc\handlers\Submit.cpp">
<ExcludedFromBuild>True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\rpc\handlers\SubmitMultiSigned.cpp">
<ExcludedFromBuild>True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\rpc\handlers\Subscribe.cpp">
<ExcludedFromBuild>True</ExcludedFromBuild>
</ClCompile>

View File

@@ -2574,6 +2574,15 @@
<ClInclude Include="..\..\src\ripple\app\tx\LocalTxs.h">
<Filter>ripple\app\tx</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\tx\tests\common_transactor.cpp">
<Filter>ripple\app\tx\tests</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ripple\app\tx\tests\common_transactor.h">
<Filter>ripple\app\tx\tests</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ripple\app\tx\tests\MultiSign.test.cpp">
<Filter>ripple\app\tx\tests</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\app\tx\tests\OfferStream.test.cpp">
<Filter>ripple\app\tx\tests</Filter>
</ClCompile>
@@ -3747,6 +3756,9 @@
<ClCompile Include="..\..\src\ripple\rpc\handlers\Submit.cpp">
<Filter>ripple\rpc\handlers</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\rpc\handlers\SubmitMultiSigned.cpp">
<Filter>ripple\rpc\handlers</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\rpc\handlers\Subscribe.cpp">
<Filter>ripple\rpc\handlers</Filter>
</ClCompile>

View File

@@ -1028,7 +1028,7 @@ void LedgerEntrySet::incrementOwnerCount (Account const& owner)
}
void
LedgerEntrySet::increaseOwnerCount (SLE::ref sleAccount, std::size_t howMuch)
LedgerEntrySet::increaseOwnerCount (SLE::ref sleAccount, std::uint32_t howMuch)
{
assert (sleAccount);
@@ -1059,7 +1059,7 @@ void LedgerEntrySet::decrementOwnerCount (Account const& owner)
}
void
LedgerEntrySet::decreaseOwnerCount (SLE::ref sleAccount, std::size_t howMuch)
LedgerEntrySet::decreaseOwnerCount (SLE::ref sleAccount, std::uint32_t howMuch)
{
assert (sleAccount);

View File

@@ -185,13 +185,13 @@ public:
/** @{ */
void incrementOwnerCount (SLE::ref sleAccount);
void incrementOwnerCount (Account const& owner);
void increaseOwnerCount (SLE::ref sleAccount, std::size_t howMuch);
void increaseOwnerCount (SLE::ref sleAccount, std::uint32_t howMuch);
/** @} */
/** @{ */
void decrementOwnerCount (SLE::ref sleAccount);
void decrementOwnerCount (Account const& owner);
void decreaseOwnerCount (SLE::ref sleAccount, std::size_t howMuch);
void decreaseOwnerCount (SLE::ref sleAccount, std::uint32_t howMuch);
/** @} */
// Offer functions.

View File

@@ -143,6 +143,9 @@ void printHelp (const po::options_description& desc)
#endif // RIPPLE_ENABLE_MULTI_SIGN
" stop\n"
" submit\n"
#if RIPPLE_ENABLE_MULTI_SIGN
" submit_multisigned\n"
#endif // RIPPLE_ENABLE_MULTI_SIGN
" tx <id>\n"
" unl_add <domain>|<public> [<comment>]\n"
" unl_delete <domain>|<public_key>\n"

View File

@@ -29,8 +29,6 @@ classes the ability to replace portions of the default implementation.
## SetSignerList ##
**Associated JIRA task is [RIPD-182](https://ripplelabs.atlassian.net/browse/RIPD-182)**
In order to enhance the flexibility of Ripple and provide support for enhanced
security of accounts, native support for "multi-signature" or "multi-sign"
accounts is required.
@@ -158,8 +156,6 @@ The data for a transaction that removes any signer list has this form:
## Tickets ##
**Associated JIRA task is [RIPD-368](https://ripplelabs.atlassian.net/browse/RIPD-368)**
Currently transactions on the Ripple network require the use of sequence
numbers and sequence numbers must monotonically increase. Since the sequence
number is part of the transaction, it is "covered" by the signature that

View File

@@ -54,7 +54,7 @@ public:
return temUNKNOWN;
}
TER checkSig () override
TER checkSign () override
{
if (mTxn.getFieldAccount160 (sfAccount).isNonZero ())
{

View File

@@ -193,7 +193,7 @@ public:
{
if (!mSigMaster)
{
m_journal.trace << "Can't use regular key to disable master key.";
m_journal.trace << "Must use master key to disable master key.";
return tecNEED_MASTER_KEY;
}

View File

@@ -221,8 +221,8 @@ SetSignerList::replaceSignerList (uint256 const& index)
return ter;
// Compute new reserve. Verify the account has funds to meet the reserve.
std::size_t const oldOwnerCount = mTxnAccount->getFieldU32 (sfOwnerCount);
std::size_t const addedOwnerCount = ownerCountDelta (signers_.size ());
std::uint32_t const oldOwnerCount = mTxnAccount->getFieldU32 (sfOwnerCount);
std::uint32_t const addedOwnerCount = ownerCountDelta (signers_.size ());
std::uint64_t const newReserve =
mEngine->getLedger ()->getReserve (oldOwnerCount + addedOwnerCount);
@@ -277,7 +277,7 @@ SetSignerList::destroySignerList (uint256 const& index)
// We have to examine the current SignerList so we know how much to
// reduce the OwnerCount.
std::size_t removeFromOwnerCount = 0;
std::uint32_t removeFromOwnerCount = 0;
uint256 const signerListIndex = getSignerListIndex (mTxnAccountID);
SLE::pointer accountSignersList =
mEngine->view ().entryCache (ltSIGNER_LIST, signerListIndex);

View File

@@ -19,6 +19,7 @@
#include <BeastConfig.h>
#include <ripple/app/tx/impl/Transactor.h>
#include <ripple/app/tx/impl/SignerEntries.h>
#include <ripple/core/Config.h>
#include <ripple/protocol/Indexes.h>
@@ -157,35 +158,6 @@ TER Transactor::payFee ()
return tesSUCCESS;
}
TER Transactor::checkSig ()
{
// Consistency: Check signature and verify the transaction's signing public
// key is the key authorized for signing.
auto const signing_account = mSigningPubKey.getAccountID ();
if (signing_account == mTxnAccountID)
{
if (mTxnAccount->isFlag(lsfDisableMaster))
return tefMASTER_DISABLED;
mSigMaster = true;
return tesSUCCESS;
}
if (!mHasAuthKey)
{
m_journal.trace << "Invalid: Not authorized to use account.";
return temBAD_AUTH_MASTER;
}
if (signing_account == mTxnAccount->getFieldAccount160 (sfRegularKey))
return tesSUCCESS;
m_journal.trace << "Delay: Not authorized to use account.";
return tefBAD_AUTH;
}
TER Transactor::checkSeq ()
{
std::uint32_t const t_seq = mTxn.getSequence ();
@@ -315,7 +287,7 @@ TER Transactor::apply ()
if (terResult != tesSUCCESS) return (terResult);
terResult = checkSig ();
terResult = checkSign ();
if (terResult != tesSUCCESS) return (terResult);
@@ -325,4 +297,364 @@ TER Transactor::apply ()
return doApply ();
}
TER Transactor::checkSign ()
{
#if RIPPLE_ENABLE_MULTI_SIGN
// If the mSigningPubKey is empty, then we must be multi-signing.
TER const signingTER = mSigningPubKey.getAccountPublic ().empty () ?
checkMultiSign () : checkSingleSign ();
#else
TER const signingTER = checkSingleSign ();
#endif // RIPPLE_ENABLE_MULTI_SIGN
return signingTER;
}
TER Transactor::checkSingleSign ()
{
// Consistency: Check signature
// Verify the transaction's signing public key is authorized for signing.
if (mSigningPubKey.getAccountID () == mTxnAccountID)
{
// Authorized to continue.
mSigMaster = true;
if (mTxnAccount->isFlag(lsfDisableMaster))
return tefMASTER_DISABLED;
}
else if (mHasAuthKey &&
(mSigningPubKey.getAccountID () ==
mTxnAccount->getFieldAccount160 (sfRegularKey)))
{
// Authorized to continue.
}
else if (mHasAuthKey)
{
m_journal.trace <<
"applyTransaction: Delay: Not authorized to use account.";
return tefBAD_AUTH;
}
else
{
m_journal.trace <<
"applyTransaction: Invalid: Not authorized to use account.";
return tefBAD_AUTH_MASTER;
}
return tesSUCCESS;
}
namespace TransactorDetail
{
struct GetSignerListResult
{
TER ter = tefFAILURE;
std::uint32_t quorum = std::numeric_limits <std::uint32_t>::max ();
std::vector<SignerEntries::SignerEntry> signerEntries;
};
// We need the SignerList for every SigningFor while multi-signing.
GetSignerListResult
getSignerList (
Account signingForAcctID, TransactionEngine* engine, beast::Journal journal)
{
GetSignerListResult ret;
uint256 const index = getSignerListIndex (signingForAcctID);
SLE::pointer accountSignersList =
engine->view ().entryCache (ltSIGNER_LIST, index);
// If the signer list doesn't exist the account is not multi-signing.
if (!accountSignersList)
{
journal.trace <<
"applyTransaction: Invalid: Not a multi-signing account.";
ret.ter = tefNOT_MULTI_SIGNING;
return ret;
}
ret.quorum = accountSignersList->getFieldU32 (sfSignerQuorum);
SignerEntries::Decoded signersOnAccountDecode =
SignerEntries::deserialize (*accountSignersList, journal, "ledger");
ret.ter = signersOnAccountDecode.ter;
if (signersOnAccountDecode.ter == tesSUCCESS)
{
ret.signerEntries = std::move (signersOnAccountDecode.vec);
}
return ret;
}
struct CheckSigningAccountsResult
{
TER ter = tefFAILURE;
std::uint32_t weightSum = 0;
};
// Verify that every SigningAccount is a valid SignerEntry. Sum signer weights.
CheckSigningAccountsResult
checkSigningAccounts (
std::vector<SignerEntries::SignerEntry> signerEntries,
STArray const& signingAccounts,
TransactionEngine* engine,
beast::Journal journal)
{
CheckSigningAccountsResult ret;
// Both the signerEntries and signingAccounts are sorted by account. So
// matching signerEntries to signingAccounts should be a simple
// linear walk. *All* signers must be valid or the transaction fails.
std::uint32_t weightSum = 0;
auto signerEntriesItr = signerEntries.begin ();
for (auto const& signingAccount : signingAccounts)
{
Account const signingAcctID =
signingAccount.getFieldAccount (sfAccount).getAccountID ();
// Attempt to match the SignerEntry with a SigningAccount;
while (signerEntriesItr->account < signingAcctID)
{
if (++signerEntriesItr == signerEntries.end ())
{
journal.trace <<
"applyTransaction: Invalid SigningAccount.Account.";
ret.ter = tefBAD_SIGNATURE;
return ret;
}
}
if (signerEntriesItr->account != signingAcctID)
{
// The SigningAccount is not in the SignerEntries.
journal.trace <<
"applyTransaction: Invalid SigningAccount.Account.";
ret.ter = tefBAD_SIGNATURE;
return ret;
}
if (signerEntriesItr->weight <= 0)
{
// The SigningAccount has a weight of zero and may not sign.
journal.trace <<
"applyTransaction: SigningAccount.Account needs weight > 0.";
ret.ter = tefBAD_SIGNATURE;
return ret;
}
// We found the SigningAccount in the list of valid signers. Now we
// need to compute the accountID that is associated with the signer's
// public key.
Account const signingAcctIDFromPubKey =
RippleAddress::createAccountPublic (
signingAccount.getFieldVL (sfSigningPubKey)).getAccountID ();
// Verify that the signingAcctID and the signingAcctIDFromPubKey
// belong together. Here is are the rules:
//
// 1. "Phantom account": an account that is not in the ledger
// A. If signingAcctID == signingAcctIDFromPubKey and the
// signingAcctID is not in the ledger then we have a phantom
// account.
// B. Phantom accounts are always allowed as multi-signers.
//
// 2. "Master Key"
// A. signingAcctID == signingAcctIDFromPubKey, and signingAcctID
// is in the ledger.
// B. If the signingAcctID in the ledger does not have the
// asfDisableMaster flag set, then the signature is allowed.
//
// 3. "Regular Key"
// A. signingAcctID != signingAcctIDFromPubKey, and signingAcctID
// is in the ledger.
// B. If signingAcctIDFromPubKey == signingAcctID.RegularKey (from
// ledger) then the signature is allowed.
//
// No other signatures are allowed. (January 2015)
// In any of these cases we need to know whether the account is in
// the ledger. Determine that now.
uint256 const signerAccountIndex = getAccountRootIndex (signingAcctID);
SLE::pointer signersAccountRoot =
engine->view ().entryCache (ltACCOUNT_ROOT, signerAccountIndex);
if (signingAcctIDFromPubKey == signingAcctID)
{
// Either Phantom or Master. Phantom's automatically pass.
if (signersAccountRoot)
{
// Master Key. Account may not have asfDisableMaster set.
std::uint32_t const signerAccountFlags =
signersAccountRoot->getFieldU32 (sfFlags);
if (signerAccountFlags & lsfDisableMaster)
{
journal.trace <<
"applyTransaction: MultiSignature lsfDisableMaster.";
ret.ter = tefMASTER_DISABLED;
return ret;
}
}
}
else
{
// May be a Regular Key. Let's find out.
// Public key must hash to the account's regular key.
if (!signersAccountRoot)
{
journal.trace <<
"applyTransaction: Non-phantom signer lacks account root.";
ret.ter = tefBAD_SIGNATURE;
return ret;
}
if (!signersAccountRoot->isFieldPresent (sfRegularKey))
{
journal.trace <<
"applyTransaction: Account lacks RegularKey.";
ret.ter = tefBAD_SIGNATURE;
return ret;
}
if (signingAcctIDFromPubKey !=
signersAccountRoot->getFieldAccount160 (sfRegularKey))
{
journal.trace <<
"applyTransaction: Account doesn't match RegularKey.";
ret.ter = tefBAD_SIGNATURE;
return ret;
}
}
// The signer is legitimate. Add their weight toward the quorum.
weightSum += signerEntriesItr->weight;
}
ret.weightSum = weightSum;
ret.ter = tesSUCCESS;
return ret;
}
} // namespace TransactorDetail
TER Transactor::checkMultiSign ()
{
// Get mTxnAccountID's SignerList and Quorum.
using namespace TransactorDetail;
GetSignerListResult const outer =
getSignerList (mTxnAccountID, mEngine, m_journal);
if (outer.ter != tesSUCCESS)
return outer.ter;
// Get the actual array of transaction signers.
STArray const& multiSigners (mTxn.getFieldArray (sfMultiSigners));
// Walk the accountSigners performing a variety of checks and see if
// the quorum is met.
// Both the multiSigners and accountSigners are sorted by account. So
// matching multi-signers to account signers should be a simple
// linear walk. *All* signers must be valid or the transaction fails.
std::uint32_t weightSum = 0;
auto signerEntriesItr = outer.signerEntries.begin ();
for (auto const& signingFor : multiSigners)
{
Account const signingForID =
signingFor.getFieldAccount (sfAccount).getAccountID ();
STArray const& signingAccounts =
signingFor.getFieldArray (sfSigningAccounts);
// There are two possibilities:
// o The signers are direct multi-signers for this account.
// o The signers are signing for a multi-signer on this account.
// Handle those two cases separately.
if (signingForID == mTxnAccountID)
{
// The signers are direct multi-signers for this account. Results
// from these signers directly effect the quorum.
CheckSigningAccountsResult const outerSigningAccountsResult =
checkSigningAccounts (
outer.signerEntries, signingAccounts, mEngine, m_journal);
if (outerSigningAccountsResult.ter != tesSUCCESS)
return outerSigningAccountsResult.ter;
weightSum += outerSigningAccountsResult.weightSum;
}
else
{
// The signers are signing for a multi-signer on this account.
// Attempt to match the signingForID with a SignerEntry
while (signerEntriesItr->account < signingForID)
{
if (++signerEntriesItr == outer.signerEntries.end ())
{
m_journal.trace <<
"applyTransaction: Invalid SigningFor.Account.";
return tefBAD_SIGNATURE;
}
}
if (signerEntriesItr->account != signingForID)
{
// The signingForID is not in the SignerEntries.
m_journal.trace <<
"applyTransaction: Invalid SigningFor.Account.";
return tefBAD_SIGNATURE;
}
if (signerEntriesItr->weight <= 0)
{
// The SigningFor entry needs a weight greater than zero.
m_journal.trace <<
"applyTransaction: SigningFor.Account needs weight > 0.";
return tefBAD_SIGNATURE;
}
// See if the signingForID has a SignerList.
GetSignerListResult const inner =
getSignerList (signingForID, mEngine, m_journal);
if (inner.ter != tesSUCCESS)
return inner.ter;
// Results from these signers indirectly effect the quorum.
CheckSigningAccountsResult const innerSigningAccountsResult =
checkSigningAccounts (
inner.signerEntries, signingAccounts, mEngine, m_journal);
if (innerSigningAccountsResult.ter != tesSUCCESS)
return innerSigningAccountsResult.ter;
// There's a policy question here. If the SigningAccounts are
// all valid but fail to reach this signingFor's Quorum do we:
//
// 1. Say the signature is valid but contributes 0 toward the
// quorum?
//
// 2. Say that any SigningFor that doesn't meet quorum is an
// invalid signature and fails?
//
// The choice is not obvious to me. I'm picking 2 for now, since
// it's more restrictive. We can switch to policy 1 later without
// causing transactions that would have worked before to fail.
// -- January 2015
if (innerSigningAccountsResult.weightSum < inner.quorum)
{
m_journal.trace <<
"applyTransaction: Level-2 SigningFor did not make quorum.";
return tefBAD_QUORUM;
}
// This SigningFor met quorum. Add its weight.
weightSum += signerEntriesItr->weight;
}
}
// Cannot perform transaction if quorum is not met.
if (weightSum < outer.quorum)
{
m_journal.trace <<
"applyTransaction: MultiSignature failed to meet quorum.";
return tefBAD_QUORUM;
}
// Met the quorum. Continue.
return tesSUCCESS;
}
}

View File

@@ -67,7 +67,7 @@ protected:
// Returns the fee, not scaled for load (Should be in fee units. FIXME)
virtual std::uint64_t calculateBaseFee ();
virtual TER checkSig ();
virtual TER checkSign ();
virtual TER doApply () = 0;
Transactor (
@@ -80,6 +80,10 @@ protected:
{
return true;
}
private:
TER checkSingleSign ();
TER checkMultiSign ();
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,430 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 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/tx/tests/common_transactor.h>
#include <ripple/app/consensus/LedgerConsensus.h>
#include <ripple/app/ledger/LedgerTiming.h>
#include <ripple/app/tests/common_ledger.h>
#include <ripple/basics/seconds_clock.h>
#include <ripple/protocol/TxFormats.h>
#include <ripple/protocol/TxFlags.h>
namespace ripple {
namespace test {
UserAccount::UserAccount (KeyType kType, std::string const& passphrase)
{
RippleAddress const seed = RippleAddress::createSeedGeneric (passphrase);
master_ = generateKeysFromSeed (kType, seed);
acctID_ = master_.publicKey.getAccountID ();
}
void UserAccount::setRegKey (
TestLedger& ledger, KeyType kType, std::string const& passphrase)
{
// Get information for the new regular key.
RippleAddress const seed = RippleAddress::createSeedGeneric(passphrase);
KeyPair regular = generateKeysFromSeed (kType, seed);
// Tell the ledger what we're up to.
STTx tx = getSetRegularKeyTx (*this, regular.publicKey.getAccountID());
singleSign (tx, *this);
ledger.applyGoodTransaction (tx);
// Remember what changed.
regular_ = regular;
}
void UserAccount::clrRegKey (TestLedger& ledger)
{
// Tell the ledger what we're up to.
STTx tx = getClearRegularKeyTx (*this);
singleSign (tx, *this);
ledger.applyGoodTransaction (tx);
// Remember what changed.
RippleAddress temp;
regular_.secretKey = temp;
regular_.publicKey = temp;
}
void UserAccount::disableMaster (TestLedger& ledger, bool doDisable)
{
STTx tx = getAccountSetTx (*this);
SField const& field = doDisable ? sfSetFlag : sfClearFlag;
tx.setFieldU32 (field, asfDisableMaster);
singleSign (tx, *this);
ledger.applyGoodTransaction (tx);
}
//------------------------------------------------------------------------------
TestLedger::TestLedger (
std::uint64_t startAmountDrops,
UserAccount const& master,
beast::unit_test::suite& suite)
: lastClosedLedger_()
, openLedger_()
, suite_(suite)
{
// To leverage createGenesisLedger from the Ledger tests, we must match
// its interface.
TestAccount const masterAcct {master.publicKey(), master.secretKey(), 0};
std::tie (lastClosedLedger_, openLedger_) =
createGenesisLedger(startAmountDrops, masterAcct);
}
std::pair<TER, bool> TestLedger::applyTransaction (STTx const& tx, bool check)
{
// Apply the transaction to the open ledger.
TransactionEngine engine(openLedger_);
auto r = engine.applyTransaction (
tx, tapOPEN_LEDGER | (check ? tapNONE : tapNO_CHECK_SIGN));
// Close the open ledger to see if the transaction was real committed.
//
// In part we close the open ledger so we don't have to think about the
// time sequencing of transactions. Every transaction applied by a
// call to this method gets applied individually. So this transaction
// is guaranteed to be applied before the next one.
close_and_advance(openLedger_, lastClosedLedger_);
suite_.expect (lastClosedLedger_->assertSane() == true);
// Check for the transaction in the closed ledger.
bool const foundTx =
lastClosedLedger_->hasTransaction(tx.getTransactionID());
suite_.expect (r.second == foundTx);
return {r.first, r.second && foundTx};
}
void TestLedger::applyGoodTransaction (STTx const& tx, bool check)
{
auto ret = applyTransaction (tx, check);
suite_.expect (ret.first == tesSUCCESS);
suite_.expect (ret.second == true);
}
void TestLedger::applyBadTransaction (STTx const& tx, TER err, bool check)
{
auto ret = applyTransaction (tx, check);
suite_.expect (ret.first == err);
suite_.expect (ret.second == false);
}
void TestLedger::applyTecTransaction (STTx const& tx, TER err, bool check)
{
auto ret = applyTransaction (tx, check);
suite_.expect (ret.first == err);
suite_.expect (ret.second == true);
}
AccountState::pointer
TestLedger::getAccountState (UserAccount const& acct) const
{
return lastClosedLedger_->getAccountState (acct.acctPublicKey ());
}
//------------------------------------------------------------------------------
MultiSig::MultiSig(UserAccount const& signingFor,
UserAccount const& signer, STTx const& tx)
: signingFor_(&signingFor)
, signer_(&signer)
, multiSig_()
{
Serializer s = tx.getMultiSigningData (
signingFor.acctPublicKey(), signer.acctPublicKey());
multiSig_ = signer.secretKey().accountPrivateSign (s.getData());
}
//------------------------------------------------------------------------------
void SignerList::injectInto (STTx& tx) const
{
// Create the SignerListArray one STObject at a time.
STArray list (list_.size ());
for (auto const& entry : list_)
{
list.emplace_back(sfSignerEntry);
STObject& obj = list.back();
obj.reserve (2);
obj.setFieldAccount (sfAccount, entry.acct->getID ());
obj.setFieldU16 (sfSignerWeight, entry.weight);
obj.setTypeFromSField (sfSignerEntry);
}
// Insert the SignerEntries.
tx.setFieldArray (sfSignerEntries, list);
}
//------------------------------------------------------------------------------
// Single-sign the passed transaction using acct.
void singleSign (STTx& tx, UserAccount& acct)
{
tx.setFieldVL (sfSigningPubKey, acct.publicKey().getAccountPublic ());
tx.sign (acct.secretKey ());
}
// Multi-sign the passed transaction using multiSigs.
void multiSign (STTx& tx, std::vector<MultiSig>& multiSigs)
{
// multiSigs must be sorted or the signature will fail.
std::sort(multiSigs.begin(), multiSigs.end());
insertMultiSigs(tx, multiSigs);
}
// Insert the multiSigs into tx without sorting. Allows testing error cases.
void insertMultiSigs (STTx& tx, std::vector<MultiSig> const& multiSigs)
{
// Know when to change out SigningFor containers.
Account prevSigningForID;
// Create the MultiSigners array one STObject at a time.
STArray multiSigners;
boost::optional <STObject> signingFor;
for (auto const& entry : multiSigs)
{
if (entry.signingForAccount() != prevSigningForID)
{
if (signingFor != boost::none)
multiSigners.push_back (std::move(signingFor.get()));
// Construct the next SigningFor object and fill it in.
prevSigningForID = entry.signingForAccount();
signingFor.emplace (sfSigningFor);
signingFor->reserve (2);
signingFor->setFieldAccount (sfAccount, entry.signingForAccount());
signingFor->setFieldArray (sfSigningAccounts, STArray());
}
assert(signingFor);
// Construct this SigningAccount object and fill it in.
STArray& signingAccounts = signingFor->peekFieldArray(
sfSigningAccounts);
signingAccounts.emplace_back (sfSigningAccount);
STObject& signingAccount (signingAccounts.back());
signingAccount.reserve (3);
signingAccount.setFieldAccount (sfAccount, entry.signingAccount());
signingAccount.setFieldVL (sfMultiSignature, entry.multiSignature());
signingAccount.setFieldVL (sfSigningPubKey, entry.signingPubKey());
}
// Remember to put in the final SigningFor object.
if (signingFor)
multiSigners.push_back (std::move(signingFor.get()));
// Inject the multiSigners into tx.
tx.setFieldArray (sfMultiSigners, multiSigners);
}
//------------------------------------------------------------------------------
// Return a transaction with an SOTemplate, sfTransactionType, sfAccount,
// sfFee, sfFlags, and sfSequence.
STTx getSeqTx (UserAccount& acct, TxType type)
{
STTx tx (type); // Sets SOTemplate and sfTransactionType.
tx.setFieldAccount (sfAccount, acct.getID());
tx.setFieldAmount (sfFee, STAmount (10));
tx.setFieldU32 (sfFlags, tfUniversal);
tx.setFieldU32 (sfSequence, acct.consumeSeq());
return tx;
}
// Return an unsigned AccountSet transaction.
STTx getAccountSetTx (UserAccount& acct)
{
STTx tx = getSeqTx (acct, ttACCOUNT_SET);
return tx;
}
// Return an unsigned OfferCreate transaction.
STTx getOfferCreateTx (UserAccount& acct,
STAmount const& takerGets, STAmount const& takerPays)
{
STTx tx = getSeqTx (acct, ttOFFER_CREATE);
tx.setFieldAmount (sfTakerGets, takerGets);
tx.setFieldAmount (sfTakerPays, takerPays);
return tx;
}
// Return an unsigned OfferCancel transaction.
STTx getOfferCancelTx (UserAccount& acct, std::uint32_t offerSeq)
{
STTx tx = getSeqTx (acct, ttOFFER_CANCEL);
tx.setFieldU32 (sfOfferSequence, offerSeq);
return tx;
}
// Return an unsigned transaction good for making a payment.
STTx getPaymentTx (
UserAccount& from, UserAccount const& to, std::uint64_t amountDrops)
{
STTx tx = getSeqTx (from, ttPAYMENT);
tx.setFieldAccount (sfDestination, to.getID());
tx.setFieldAmount (sfAmount, amountDrops);
return tx;
}
STTx getPaymentTx (
UserAccount& from, UserAccount const& to, STAmount const& amount)
{
STTx tx = getSeqTx (from, ttPAYMENT);
tx.setFieldAccount (sfDestination, to.getID());
tx.setFieldAmount (sfAmount, amount);
return tx;
}
// Return a transaction that sets a regular key
STTx getSetRegularKeyTx (UserAccount& acct, Account const& regKey)
{
STTx tx = getSeqTx (acct, ttREGULAR_KEY_SET);
tx.setFieldAccount (sfRegularKey, regKey);
return tx;
}
// Return a transaction that clears a regular key
STTx getClearRegularKeyTx (UserAccount& acct)
{
STTx tx = getSeqTx (acct, ttREGULAR_KEY_SET);
return tx;
}
// Return a SignerListSet transaction.
STTx getSignerListSetTx (
UserAccount& acct, SignerList const& signers, std::uint32_t quorum)
{
STTx tx = getSeqTx (acct, ttSIGNER_LIST_SET);
tx.setFieldU32 (sfSignerQuorum, quorum);
if (! signers.empty ())
{
signers.injectInto (tx);
}
return tx;
}
// Return a transaction that creates an un-targeted ticket.
STTx getCreateTicketTx (UserAccount& acct)
{
STTx tx = getSeqTx (acct, ttTICKET_CREATE);
return tx;
}
// Return a transaction that creates a targeted ticket.
STTx getCreateTicketTx (UserAccount& acct, UserAccount const& target)
{
STTx tx = getSeqTx (acct, ttTICKET_CREATE);
tx.setFieldAccount (sfTarget, target.getID());
return tx;
}
// Return a transaction that cancels a ticket.
STTx getCancelTicketTx (UserAccount& acct, uint256 const& ticketID)
{
STTx tx = getSeqTx (acct, ttTICKET_CANCEL);
tx.setFieldH256 (sfTicketID, ticketID);
return tx;
}
// Return a trust set transaction.
STTx getTrustSetTx (UserAccount& from, Issue const& issuer, int limit)
{
STTx tx = getSeqTx (from, ttTRUST_SET);
STAmount const stLimit (issuer, limit);
tx.setFieldAmount (sfLimitAmount, stLimit);
return tx;
}
//------------------------------------------------------------------------------
void payInDrops (TestLedger& ledger,
UserAccount& from, UserAccount const& to, std::uint64_t amountDrops)
{
STTx tx = getPaymentTx (from, to, amountDrops);
singleSign (tx, from);
ledger.applyGoodTransaction (tx);
}
std::uint64_t getNativeBalance(TestLedger& ledger, UserAccount& acct)
{
return getNValue(ledger.getAccountState(acct)->getBalance());
}
std::uint32_t getOwnerCount(TestLedger& ledger, UserAccount& acct)
{
return ledger.getAccountState(acct)->getSLE()->getFieldU32 (sfOwnerCount);
}
std::vector<RippleState::pointer> getRippleStates (
TestLedger& ledger, UserAccount const& acct, UserAccount const& peer)
{
std::vector <RippleState::pointer> states;
ledger.openLedger()->visitAccountItems (
acct.getID(),
[&states, &acct, &peer](SLE::ref sleCur)
{
// See whether this SLE is a lt_RIPPLE_STATE
if (!sleCur || sleCur->getType () != ltRIPPLE_STATE)
return;
// It's a lt_RIPPLE_STATE. See if it's one we want to return.
RippleState::pointer const state (
RippleState::makeItem (acct.getID(), sleCur));
if ((state) && (state->getAccountIDPeer() == peer.getID()))
states.emplace_back (std::move (state));
});
return states;
}
std::vector <SLE::pointer>
getOffersOnAccount (TestLedger& ledger, UserAccount const& acct)
{
std::vector <SLE::pointer> offers;
ledger.openLedger()->visitAccountItems (
acct.getID(),
[&offers, &acct](SLE::ref sleCur)
{
// If sleCur is an ltOFFER save it.
if (sleCur && sleCur->getType () == ltOFFER)
offers.emplace_back (sleCur);
});
return offers;
}
std::vector <SLE::pointer>
getTicketsOnAccount (TestLedger& ledger, UserAccount const& acct)
{
std::vector <SLE::pointer> offers;
ledger.openLedger()->visitAccountItems (
acct.getID(),
[&offers, &acct](SLE::ref sleCur)
{
// If sleCur is an ltTICKET save it.
if (sleCur && sleCur->getType () == ltTICKET)
offers.emplace_back (sleCur);
});
return offers;
}
} // test
} // ripple

View File

@@ -0,0 +1,360 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 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_APP_TRANSACTORS_TESTS_COMMON_TRANSACTOR_H_INCLUDED
#define RIPPLE_APP_TRANSACTORS_TESTS_COMMON_TRANSACTOR_H_INCLUDED
#include <ripple/app/ledger/Ledger.h>
#include <ripple/app/paths/RippleState.h>
#include <ripple/protocol/RippleAddress.h>
#include <ripple/protocol/STAmount.h>
#include <beast/unit_test/suite.h>
#include <cstdint>
namespace ripple {
namespace test {
// Forward declare TestLedger so UserAccount can take it as a parameter.
class TestLedger;
// This class makes it easier to write unit tests that involve user accounts.
class UserAccount
{
private:
KeyPair master_;
Account acctID_;
KeyPair regular_;
bool useRegKey_ = false;
std::uint32_t sequence_ = 0;
public:
UserAccount () = delete;
UserAccount (UserAccount const& rhs) = default;
UserAccount& operator= (UserAccount const& rhs) = default;
UserAccount (KeyType kType, std::string const& passphrase);
Account const& getID () const
{
return acctID_;
}
// Sets the regular key on the account, but does not disable master.
void setRegKey (
TestLedger& ledger, KeyType kType, std::string const& passphrase);
// Removes the regular key.
void clrRegKey (TestLedger& ledger);
// Either disables or enables the master key.
void disableMaster (TestLedger& ledger, bool doDisable);
// Select to use either the regular (true) or master (false) key.
void useRegKey (bool useReg)
{
useRegKey_ = useReg;
}
std::uint32_t consumeSeq ()
{
return ++sequence_;
}
// If a transaction fails we have to back up the sequence number, since.
// the last sequence wasn't consumed.
void decrSeq ()
{
--sequence_;
}
RippleAddress const& acctPublicKey () const
{
return master_.publicKey;
}
RippleAddress const& publicKey () const
{
return useRegKey_ ? regular_.publicKey : master_.publicKey;
}
RippleAddress const& secretKey () const
{
return useRegKey_ ? regular_.secretKey : master_.secretKey;
}
};
// This class collects a bunch of the ledger shenanigans tests have to do.
class TestLedger
{
private:
Ledger::pointer lastClosedLedger_;
Ledger::pointer openLedger_;
beast::unit_test::suite& suite_;
public:
TestLedger () = delete;
TestLedger (const TestLedger& lhs) = delete;
TestLedger (
std::uint64_t startAmountDrops,
UserAccount const& master,
beast::unit_test::suite& suite);
// Return.first : transaction's TEC
// Return.second : transaction successfully applied and in ledger.
std::pair<TER, bool> applyTransaction (STTx const& tx, bool check = true);
// Apply a transaction that we expect to succeed.
void applyGoodTransaction (STTx const& tx, bool check = true);
// Apply a transaction that we expect to fail. Pass the expected error code.
void applyBadTransaction (STTx const& tx, TER err, bool check = true);
// apply a transaction that we expect to fail but charges a fee. Pass the
// expected error code.
void applyTecTransaction (STTx const& tx, TER err, bool check = true);
// Return the AccountState for a UserAccount
AccountState::pointer getAccountState (UserAccount const& acct) const;
// Return the current open ledger.
Ledger::pointer openLedger ()
{
return openLedger_;
}
};
// This class makes it easier to construct unit tests with SignerLists.
//
// Typically construct this class with an initializer list. Like this:
//
// UserAccount alice;
// UserAccount becky;
// SignerList s {{alice, 5}, {becky, 2}};
//
// where '5' and '2' are the weights of the accounts in the SignerList.
class SignerList
{
private:
// Our initializer list takes references that we turn into pointers.
struct InitListEntry
{
UserAccount& acct;
std::uint16_t weight;
};
struct SignerAndWeight
{
UserAccount* acct;
std::uint16_t weight;
SignerAndWeight(InitListEntry& entry)
: acct (&entry.acct)
, weight (entry.weight) { }
};
std::vector<SignerAndWeight> list_;
public:
SignerList() = default;
SignerList(SignerList const& rhs) = default;
SignerList(SignerList&& rhs) // Visual Studio 2013 won't default move ctor
: list_(std::move(rhs.list_))
{ }
SignerList(std::initializer_list<InitListEntry> args)
{
list_.reserve(args.size());
for (auto arg : args)
{
list_.emplace_back(arg);
}
}
SignerList& operator=(SignerList const& rhs) = default;
SignerList& operator=(SignerList&& rhs) // VS 2013 won't default move assign
{
list_ = std::move(rhs.list_);
return *this;
}
bool empty() const
{
return list_.empty ();
}
// Inject this SignerList into the passed transaction.
void injectInto (STTx& tx) const;
};
// This type makes it easier to write unit tests that use multi-signatures.
//
// The MultiSig type is intended to be short-lived (any user account it
// references should out-live it). It is also intended to be easily
// moved in a std::vector, so it uses pointers not references.
class MultiSig
{
UserAccount const* signingFor_;
UserAccount const* signer_;
Blob multiSig_;
public:
MultiSig() = delete;
MultiSig(MultiSig const& rhs) = default;
MultiSig(MultiSig&& rhs) // Visual Studio 2013 won't default move ctor
: signingFor_ (std::move(rhs.signingFor_))
, signer_ (std::move(rhs.signer_))
, multiSig_ (std::move(rhs.multiSig_))
{ }
MultiSig& operator=(MultiSig const& rhs) = delete;
MultiSig& operator=(MultiSig&& rhs) // VS 2013 won't default move assign
{
signingFor_ = std::move(rhs.signingFor_);
signer_ = std::move(rhs.signer_);
multiSig_ = std::move(rhs.multiSig_);
return *this;
}
MultiSig(UserAccount const& signingFor,
UserAccount const& signer, STTx const& tx);
friend bool operator==(MultiSig const& lhs, MultiSig const& rhs)
{
return ((lhs.signingFor_->getID() == rhs.signingFor_->getID()) &&
(lhs.signer_->getID() == rhs.signer_->getID()));
}
friend bool operator<(MultiSig const& lhs, MultiSig const& rhs)
{
Account const& lhsSigingFor = lhs.signingFor_->getID();
Account const& rhsSigningFor = rhs.signingFor_->getID();
if (lhsSigingFor < rhsSigningFor)
return true;
if (lhsSigingFor > rhsSigningFor)
return false;
if (lhs.signer_->getID() < rhs.signer_->getID())
return true;
return false;
}
Account const& signingForAccount() const
{
return signingFor_->getID();
}
Account const& signingAccount() const
{
return signer_->getID();
}
Blob const& multiSignature() const
{
return multiSig_;
}
Blob const& signingPubKey() const
{
return signer_->publicKey().getAccountPublic();
}
};
//------------------------------------------------------------------------------
// Single-sign the passed transaction using acct.
void singleSign (STTx& tx, UserAccount& acct);
// Multi-sign the passed transaction using multiSigs.
void multiSign (STTx& tx, std::vector<MultiSig>& multiSigs);
// Insert the multiSigs into tx without sorting. Allows testing error cases.
void insertMultiSigs (STTx& tx, std::vector<MultiSig> const& multiSigs);
//------------------------------------------------------------------------------
// Return a transaction with an SOTemplate, sfTransactionType, sfAccount,
// sfFee, sfFlags, and sfSequence.
STTx getSeqTx (UserAccount& acct, TxType type);
// Get an AccountSet transaction.
STTx getAccountSetTx (UserAccount& acct);
// Return an unsigned OfferCreate transaction.
STTx getOfferCreateTx (UserAccount& acct,
STAmount const& takerGets, STAmount const& takerPays);
// Return an unsigned OfferCancel transaction.
STTx getOfferCancelTx (UserAccount& acct, std::uint32_t offerSeq);
// Return an unsigned transaction good for making a payment in XRP.
STTx getPaymentTx (
UserAccount& from, UserAccount const& to, std::uint64_t amountDrops);
// Return an unsigned transaction good for making a payment.
STTx getPaymentTx (
UserAccount& from, UserAccount const& to, STAmount const& amount);
// Return a transaction that sets a regular key.
STTx getSetRegularKeyTx (UserAccount& acct, Account const& regKey);
// Return a transaction that clears a regular key.
STTx getClearRegularKeyTx (UserAccount& acct);
// Return a SignerListSet transaction. If the quorum is zero and signers
// is empty, then any signer list is removed from the account.
STTx getSignerListSetTx (
UserAccount& acct, SignerList const& signers, std::uint32_t quorum);
// Return a transaction that creates an un-targeted ticket.
STTx getCreateTicketTx (UserAccount& acct);
// Return a transaction that creates an targeted ticket.
STTx getCreateTicketTx (UserAccount& acct, UserAccount const& target);
// Return a transaction that cancels a ticket.
STTx getCancelTicketTx (UserAccount& acct, uint256 const& ticketID);
// Return an unsigned trust set transaction.
STTx getTrustSetTx (UserAccount& from, Issue const& issuer, int limit);
//------------------------------------------------------------------------------
// Complete the a simple Payment transaction in drops. Expected to succeed.
void payInDrops (TestLedger& ledger,
UserAccount& from, UserAccount const& to, std::uint64_t amountDrops);
// Return the native balance on an account.
std::uint64_t getNativeBalance(TestLedger& ledger, UserAccount& acct);
// Return the owner count of an account.
std::uint32_t getOwnerCount(TestLedger& ledger, UserAccount& acct);
// Get all RippleStates between two accounts.
std::vector<RippleState::pointer> getRippleStates (
TestLedger& ledger, UserAccount const& from, UserAccount const& peer);
// Get all Offers on an account.
std::vector <SLE::pointer>
getOffersOnAccount (TestLedger& ledger, UserAccount const& acct);
// Get all Tickets on an account.
std::vector <SLE::pointer>
getTicketsOnAccount (TestLedger& ledger, UserAccount const& acct);
} // test
} // ripple
#endif // RIPPLE_APP_TRANSACTORS_TESTS_COMMON_TRANSACTOR_H_INCLUDED

View File

@@ -659,6 +659,28 @@ private:
return rpcError (rpcINVALID_PARAMS);
}
// submit any multisigned transaction to the network
//
// submit_multisigned <json>
Json::Value parseSubmitMultiSigned (Json::Value const& jvParams)
{
Json::Value jvRequest;
Json::Reader reader;
bool const bOffline = 2 == jvParams.size () && jvParams[1u].asString () == "offline";
if ((1 == jvParams.size () || bOffline)
&& reader.parse (jvParams[0u].asString (), jvRequest))
{
// Multisigned.
if (bOffline)
jvRequest["offline"] = true;
return jvRequest;
}
return rpcError (rpcINVALID_PARAMS);
}
// tx <transaction_id>
Json::Value parseTx (Json::Value const& jvParams)
{
@@ -859,6 +881,9 @@ public:
{ "sign_for", &RPCParser::parseSignFor, 4, 4 },
#endif // RIPPLE_ENABLE_MULTI_SIGN
{ "submit", &RPCParser::parseSignSubmit, 1, 3 },
#if RIPPLE_ENABLE_MULTI_SIGN
{ "submit_multisigned", &RPCParser::parseSubmitMultiSigned, 1, 1 },
#endif // RIPPLE_ENABLE_MULTI_SIGN
{ "server_info", &RPCParser::parseAsIs, 0, 0 },
{ "server_state", &RPCParser::parseAsIs, 0, 0 },
{ "stop", &RPCParser::parseAsIs, 0, 0 },

View File

@@ -157,6 +157,9 @@ public:
std::string const& escapedMetaData) const;
private:
bool checkSingleSign () const;
bool checkMultiSign () const;
TxType tx_type_;
mutable boost::tribool sig_state_;

View File

@@ -56,7 +56,6 @@ enum TER // aka TransactionEngineResult
temMALFORMED = -299,
temBAD_AMOUNT,
temBAD_AUTH_MASTER,
temBAD_CURRENCY,
temBAD_EXPIRATION,
temBAD_FEE,
@@ -113,6 +112,10 @@ enum TER // aka TransactionEngineResult
tefWRONG_PRIOR,
tefMASTER_DISABLED,
tefMAX_LEDGER,
tefBAD_SIGNATURE,
tefBAD_QUORUM,
tefNOT_MULTI_SIGNING,
tefBAD_AUTH_MASTER,
// -99 .. -1: R Retry
// sequence too high, no funds for txn fee, originating -account

View File

@@ -28,6 +28,17 @@ InnerObjectFormats::InnerObjectFormats ()
<< SOElement (sfAccount, SOE_REQUIRED)
<< SOElement (sfSignerWeight, SOE_REQUIRED)
;
add (sfSigningFor.getJsonName ().c_str (), sfSigningFor.getCode ())
<< SOElement (sfAccount, SOE_REQUIRED)
<< SOElement (sfSigningAccounts, SOE_REQUIRED)
;
add (sfSigningAccount.getJsonName ().c_str (), sfSigningAccount.getCode ())
<< SOElement (sfAccount, SOE_REQUIRED)
<< SOElement (sfSigningPubKey, SOE_REQUIRED)
<< SOElement (sfMultiSignature, SOE_REQUIRED)
;
}
void InnerObjectFormats::addCommonFields (Item& item)

View File

@@ -18,17 +18,18 @@
//==============================================================================
#include <BeastConfig.h>
#include <ripple/basics/Log.h>
#include <ripple/protocol/STTx.h>
#include <ripple/protocol/HashPrefix.h>
#include <ripple/protocol/JsonFields.h>
#include <ripple/protocol/Protocol.h>
#include <ripple/protocol/STAccount.h>
#include <ripple/protocol/STArray.h>
#include <ripple/protocol/STTx.h>
#include <ripple/protocol/TxFlags.h>
#include <ripple/basics/Log.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/json/to_string.h>
#include <beast/unit_test/suite.h>
#include <beast/cxx14/memory.h> // <memory>
#include <boost/format.hpp>
#include <array>
@@ -195,15 +196,16 @@ bool STTx::checkSign () const
{
try
{
ECDSA const fullyCanonical = (getFlags() & tfFullyCanonicalSig)
? ECDSA::strict
: ECDSA::not_strict;
RippleAddress n;
n.setAccountPublic (getFieldVL (sfSigningPubKey));
sig_state_ = n.accountPublicVerify (getSigningData (*this),
getFieldVL (sfTxnSignature), fullyCanonical);
#if RIPPLE_ENABLE_MULTI_SIGN
// Determine whether we're single- or multi-signing by looking
// at the SigningPubKey. It it's empty we must be multi-signing.
// Otherwise we're single-signing.
Blob const& signingPubKey = getFieldVL (sfSigningPubKey);
sig_state_ = signingPubKey.empty () ?
checkMultiSign () : checkSingleSign ();
#else
sig_state_ = checkSingleSign ();
#endif // RIPPLE_ENABLE_MULTI_SIGN
}
catch (...)
{
@@ -280,6 +282,217 @@ STTx::getMetaSQL (Serializer rawTxn,
% getSequence () % inLedger % status % rTxn % escapedMetaData);
}
bool
STTx::checkSingleSign () const
{
// We don't allow both a non-empty sfSigningPubKey and an sfMultiSigners.
// That would allow the transaction to be signed two ways. So if both
// fields are present the signature is invalid.
if (isFieldPresent (sfMultiSigners))
return false;
bool ret = false;
try
{
ECDSA const fullyCanonical = (getFlags() & tfFullyCanonicalSig)
? ECDSA::strict
: ECDSA::not_strict;
RippleAddress n;
n.setAccountPublic (getFieldVL (sfSigningPubKey));
ret = n.accountPublicVerify (getSigningData (*this),
getFieldVL (sfTxnSignature), fullyCanonical);
}
catch (...)
{
// Assume it was a signature failure.
ret = false;
}
return ret;
}
bool
STTx::checkMultiSign () const
{
// Make sure the MultiSigners are present. Otherwise they are not
// attempting multi-signing and we just have a bad SigningPubKey.
if (!isFieldPresent (sfMultiSigners))
return false;
STArray const& multiSigners (getFieldArray (sfMultiSigners));
// There are well known bounds that the number of signers must be within.
{
std::size_t const multiSignerCount = multiSigners.size ();
if ((multiSignerCount < 1) || (multiSignerCount > maxMultiSigners))
return false;
}
// We can ease the computational load inside the loop a bit by
// pre-constructing part of the data that we hash. Fill a Serializer
// with the stuff that stays constant from signature to signature.
Serializer const dataStart (startMultiSigningData ());
// We also use the sfAccount field inside the loop. Get it once.
RippleAddress const txnAccountID = getFieldAccount (sfAccount);
// Determine whether signatures must be full canonical.
ECDSA const fullyCanonical = (getFlags() & tfFullyCanonicalSig)
? ECDSA::strict
: ECDSA::not_strict;
// We need to detect (and reject) if a multi-signer is both signing
// directly and using a SigningFor. Here's an example:
//
// {
// ...
// "MultiSigners": [
// {
// "SigningFor": {
// "Account": "<alice>",
// "SigningAccounts": [
// {
// "SigningAccount": {
// // * becky says that becky signs for alice. *
// "Account": "<becky>",
// ...
// "SigningFor": {
// "Account": "<becky>",
// "SigningAccounts": [
// {
// "SigningAccount": {
// // * cheri says that becky signs for alice. *
// "Account": "<cheri>",
// ...
// "tx_json": {
// "Account": "<alice>",
// ...
// }
// }
//
// Why is this way of signing a problem? Alice has a signer list, and
// Becky can show up in that list only once. By design. So if Becky
// signs twice -- once directly and once indirectly -- we have three
// options:
//
// 1. We can add Becky's weight toward Alice's quorum twice, once for
// each signature. This seems both unexpected and counter to Alice's
// intention.
//
// 2. We could allow both signatures, but only add Becky's weight
// toward Alice's quorum once. This seems a bit better. But it allows
// our clients to ask rippled to do more work than necessary. We
// should also let the client know that only one of the signatures
// was necessary.
//
// 3. The only way to tell the client that they have done more work
// than necessary (and that one of the signatures will be ignored) is
// to declare the transaction malformed. This behavior also aligns
// well with rippled's behavior if Becky had signed directly twice:
// the transaction would be marked as malformed.
//
// We use this std::set to detect this form of double-signing.
std::set<RippleAddress> firstLevelSigners;
// SigningFors must be in sorted order by AccountID.
RippleAddress lastSigningForID;
lastSigningForID.setAccountID ("");
// Every signature must verify or we reject the transaction.
for (auto const& signingFor : multiSigners)
{
RippleAddress const signingForID =
signingFor.getFieldAccount (sfAccount);
// SigningFors must be in order by account ID. No duplicates allowed.
if (lastSigningForID >= signingForID)
return false;
// The next SigningFor must be greater than this one.
lastSigningForID = signingForID;
// If signingForID is *not* txnAccountID, then look for duplicates.
bool const directSigning = (signingForID == txnAccountID);
if (! directSigning)
{
if (! firstLevelSigners.insert (signingForID).second)
// This is a duplicate signer. Fail.
return false;
}
STArray const& signingAccounts (
signingFor.getFieldArray (sfSigningAccounts));
// There are bounds that the number of signers must be within.
{
std::size_t const signingAccountsCount = signingAccounts.size ();
if ((signingAccountsCount < 1) ||
(signingAccountsCount > maxMultiSigners))
{
return false;
}
}
// SingingAccounts must be in sorted order by AccountID.
RippleAddress lastSigningAcctID;
lastSigningAcctID.setAccountID ("");
for (auto const& signingAcct : signingAccounts)
{
RippleAddress const signingAcctID =
signingAcct.getFieldAccount (sfAccount);
// None of the multi-signers may sign for themselves.
if (signingForID == signingAcctID)
return false;
// Accounts must be in order by account ID. No duplicates allowed.
if (lastSigningAcctID >= signingAcctID)
return false;
// The next signature must be greater than this one.
lastSigningAcctID = signingAcctID;
// If signingForID *is* txnAccountID, then look for duplicates.
if (directSigning)
{
if (! firstLevelSigners.insert (signingAcctID).second)
// This is a duplicate signer. Fail.
return false;
}
// Verify the signature.
bool validSig = false;
try
{
Serializer s = dataStart;
finishMultiSigningData (signingForID, signingAcctID, s);
RippleAddress const pubKey =
RippleAddress::createAccountPublic (
signingAcct.getFieldVL (sfSigningPubKey));
Blob const signature =
signingAcct.getFieldVL (sfMultiSignature);
validSig = pubKey.accountPublicVerify (
s.getData(), signature, fullyCanonical);
}
catch (...)
{
// We assume any problem lies with the signature.
validSig = false;
}
if (!validSig)
return false;
}
}
// All signatures verified.
return true;
}
//------------------------------------------------------------------------------
static

View File

@@ -73,6 +73,8 @@ bool transResultInfo (TER code, std::string& token, std::string& text)
{ tefBAD_ADD_AUTH, "tefBAD_ADD_AUTH", "Not authorized to add account." },
{ tefBAD_AUTH, "tefBAD_AUTH", "Transaction's public key is not authorized." },
{ tefBAD_LEDGER, "tefBAD_LEDGER", "Ledger in unexpected state." },
{ tefBAD_QUORUM, "tefBAD_QUORUM", "Signatures provided do not meet the quorum." },
{ tefBAD_SIGNATURE, "tefBAD_SIGNATURE", "A signature is provided for a non-signer." },
{ tefCREATED, "tefCREATED", "Can't add an already created account." },
{ tefEXCEPTION, "tefEXCEPTION", "Unexpected program state." },
{ tefFAILURE, "tefFAILURE", "Failed to apply." },
@@ -80,8 +82,10 @@ bool transResultInfo (TER code, std::string& token, std::string& text)
{ tefMASTER_DISABLED, "tefMASTER_DISABLED", "Master key is disabled." },
{ tefMAX_LEDGER, "tefMAX_LEDGER", "Ledger sequence too high." },
{ tefNO_AUTH_REQUIRED, "tefNO_AUTH_REQUIRED", "Auth is not required." },
{ tefNOT_MULTI_SIGNING, "tefNOT_MULTI_SIGNING", "Account has no appropriate list of multi-signers." },
{ tefPAST_SEQ, "tefPAST_SEQ", "This sequence number has already past." },
{ tefWRONG_PRIOR, "tefWRONG_PRIOR", "This previous transaction does not match." },
{ tefBAD_AUTH_MASTER, "tefBAD_AUTH_MASTER", "Auth for unclaimed account needs correct master key." },
{ telLOCAL_ERROR, "telLOCAL_ERROR", "Local failure." },
{ telBAD_DOMAIN, "telBAD_DOMAIN", "Domain too long." },
@@ -93,7 +97,6 @@ bool transResultInfo (TER code, std::string& token, std::string& text)
{ temMALFORMED, "temMALFORMED", "Malformed transaction." },
{ temBAD_AMOUNT, "temBAD_AMOUNT", "Can only send positive amounts." },
{ temBAD_AUTH_MASTER, "temBAD_AUTH_MASTER", "Auth for unclaimed account needs correct master key." },
{ temBAD_CURRENCY, "temBAD_CURRENCY", "Malformed: Bad currency." },
{ temBAD_EXPIRATION, "temBAD_EXPIRATION", "Malformed: Bad expiration." },
{ temBAD_FEE, "temBAD_FEE", "Invalid fee, negative or not XRP." },

View File

@@ -112,6 +112,7 @@ void TxFormats::addCommonFields (Item& item)
<< SOElement(sfMemos, SOE_OPTIONAL)
<< SOElement(sfSigningPubKey, SOE_REQUIRED)
<< SOElement(sfTxnSignature, SOE_OPTIONAL)
<< SOElement(sfMultiSigners, SOE_OPTIONAL) // submit_multisigned
;
}

View File

@@ -77,6 +77,134 @@ public:
}
};
class InnerObjectFormatsSerializer_test : public beast::unit_test::suite
{
public:
void run()
{
// Create a transaction
RippleAddress txnSeed;
txnSeed.setSeedRandom ();
RippleAddress txnGenerator = txnSeed.createGeneratorPublic (txnSeed);
RippleAddress txnPublicAcct = txnSeed.createAccountPublic (txnGenerator, 1);
STTx txn (ttACCOUNT_SET);
txn.setSourceAccount (txnPublicAcct);
txn.setSigningPubKey (txnPublicAcct);
txn.setFieldVL (sfMessageKey, txnPublicAcct.getAccountPublic ());
Blob const emptyBlob; // Make empty signature for multi-signing
txn.setFieldVL (sfSigningPubKey, emptyBlob);
// Create fields for a SigningAccount
RippleAddress saSeed;
saSeed.setSeedGeneric ("masterpassphrase");
RippleAddress const saGenerator = saSeed.createGeneratorPublic (saSeed);
RippleAddress const saPublicAcct =
saSeed.createAccountPublic (saGenerator, 1);
Account const saID = saPublicAcct.getAccountID ();
// Create a field for SigningFor
Account const signingForID = txnPublicAcct.getAccountID ();
RippleAddress saPrivateAcct =
saSeed.createAccountPrivate(saGenerator, saSeed, 0);
// Get the stream of the transaction for use in multi-signing.
Serializer s = txn.getMultiSigningData (saPublicAcct, saPublicAcct);
Blob saMultiSignature =
saPrivateAcct.accountPrivateSign (s.getData());
// The InnerObjectFormats say a SigningAccount is supposed to look
// like this:
// SigningAccount {
// Account: "...",
// MultiSignature: "...",
// PublicKey: "...""
// }
// Make one well formed SigningAccount and several mal-formed ones.
// See whether the serializer lets the good one through and catches
// the bad ones.
// This lambda contains the bulk of the test code.
auto testMalformedSigningAccount =
[this, &txn, &signingForID]
(STObject const& signingAcct, bool expectPass)
{
// Create SigningAccounts array.
STArray signingAccts (sfSigningAccounts, 1);
signingAccts.push_back (signingAcct);
// Insert SigningAccounts array into SigningFor object.
STObject signingFor (sfSigningFor);
signingFor.setFieldAccount (sfAccount, signingForID);
signingFor.setFieldArray (sfSigningAccounts, signingAccts);
// Insert SigningFor into MultiSigners.
STArray multiSigners (sfMultiSigners, 1);
multiSigners.push_back (signingFor);
// Insert MultiSigners into transaction.
STTx tempTxn (txn);
tempTxn.setFieldArray (sfMultiSigners, multiSigners);
Serializer rawTxn;
tempTxn.add (rawTxn);
SerialIter sit (rawTxn);
bool serialized = false;
try
{
STTx copy (sit);
serialized = true;
}
catch (...)
{
; // If it threw then serialization failed.
}
expect (serialized == expectPass,
"Unexpected serialized = " + std::to_string (serialized) +
". Object:\n" + signingAcct.getFullText () + "\n");
};
{
// Test case 1. Make a valid SigningAccount object.
STObject soTest1 (sfSigningAccount);
soTest1.setFieldAccount (sfAccount, saID);
soTest1.setFieldVL (sfSigningPubKey,
txnPublicAcct.getAccountPublic ());
soTest1.setFieldVL (sfMultiSignature, saMultiSignature);
testMalformedSigningAccount (soTest1, true);
}
{
// Test case 2. Omit sfSigningPubKey from SigningAccount.
STObject soTest2 (sfSigningAccount);
soTest2.setFieldAccount (sfAccount, saID);
soTest2.setFieldVL (sfMultiSignature, saMultiSignature);
testMalformedSigningAccount (soTest2, false);
}
{
// Test case 3. Extra sfAmount in SigningAccount.
STObject soTest3 (sfSigningAccount);
soTest3.setFieldAccount (sfAccount, saID);
soTest3.setFieldVL (sfSigningPubKey,
txnPublicAcct.getAccountPublic ());
soTest3.setFieldVL (sfMultiSignature, saMultiSignature);
soTest3.setFieldAmount (sfAmount, STAmount (10000));
testMalformedSigningAccount (soTest3, false);
}
{
// Test case 4. Right number of fields, but wrong ones.
STObject soTest4 (sfSigningAccount);
soTest4.setFieldVL (sfSigningPubKey,
txnPublicAcct.getAccountPublic ());
soTest4.setFieldVL (sfMultiSignature, saMultiSignature);
soTest4.setFieldAmount (sfAmount, STAmount (10000));
testMalformedSigningAccount (soTest4, false);
}
}
};
BEAST_DEFINE_TESTSUITE(STTx,ripple_app,ripple);
BEAST_DEFINE_TESTSUITE(InnerObjectFormatsSerializer,ripple_app,ripple);
} // ripple

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)

View File

@@ -47,5 +47,7 @@
#include <ripple/app/tx/impl/SetSignerList.cpp>
#include <ripple/app/tx/impl/SignerEntries.cpp>
#include <ripple/app/tx/tests/common_transactor.cpp>
#include <ripple/app/tx/tests/MultiSign.test.cpp>
#include <ripple/app/tx/tests/OfferStream.test.cpp>
#include <ripple/app/tx/tests/Taker.test.cpp>

View File

@@ -76,6 +76,7 @@
#include <ripple/rpc/handlers/SignFor.cpp>
#include <ripple/rpc/handlers/Stop.cpp>
#include <ripple/rpc/handlers/Submit.cpp>
#include <ripple/rpc/handlers/SubmitMultiSigned.cpp>
#include <ripple/rpc/handlers/Subscribe.cpp>
#include <ripple/rpc/handlers/TransactionEntry.cpp>
#include <ripple/rpc/handlers/Tx.cpp>