mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-27 06:25:51 +00:00
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:
committed by
Vinnie Falco
parent
cf1638e6de
commit
d6ef66646f
@@ -2046,6 +2046,20 @@
|
|||||||
</ClInclude>
|
</ClInclude>
|
||||||
<ClInclude Include="..\..\src\ripple\app\tx\LocalTxs.h">
|
<ClInclude Include="..\..\src\ripple\app\tx\LocalTxs.h">
|
||||||
</ClInclude>
|
</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">
|
<ClCompile Include="..\..\src\ripple\app\tx\tests\OfferStream.test.cpp">
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||||
@@ -3217,6 +3231,9 @@
|
|||||||
<ClCompile Include="..\..\src\ripple\rpc\handlers\Submit.cpp">
|
<ClCompile Include="..\..\src\ripple\rpc\handlers\Submit.cpp">
|
||||||
<ExcludedFromBuild>True</ExcludedFromBuild>
|
<ExcludedFromBuild>True</ExcludedFromBuild>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\..\src\ripple\rpc\handlers\SubmitMultiSigned.cpp">
|
||||||
|
<ExcludedFromBuild>True</ExcludedFromBuild>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="..\..\src\ripple\rpc\handlers\Subscribe.cpp">
|
<ClCompile Include="..\..\src\ripple\rpc\handlers\Subscribe.cpp">
|
||||||
<ExcludedFromBuild>True</ExcludedFromBuild>
|
<ExcludedFromBuild>True</ExcludedFromBuild>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
|||||||
@@ -2574,6 +2574,15 @@
|
|||||||
<ClInclude Include="..\..\src\ripple\app\tx\LocalTxs.h">
|
<ClInclude Include="..\..\src\ripple\app\tx\LocalTxs.h">
|
||||||
<Filter>ripple\app\tx</Filter>
|
<Filter>ripple\app\tx</Filter>
|
||||||
</ClInclude>
|
</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">
|
<ClCompile Include="..\..\src\ripple\app\tx\tests\OfferStream.test.cpp">
|
||||||
<Filter>ripple\app\tx\tests</Filter>
|
<Filter>ripple\app\tx\tests</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
@@ -3747,6 +3756,9 @@
|
|||||||
<ClCompile Include="..\..\src\ripple\rpc\handlers\Submit.cpp">
|
<ClCompile Include="..\..\src\ripple\rpc\handlers\Submit.cpp">
|
||||||
<Filter>ripple\rpc\handlers</Filter>
|
<Filter>ripple\rpc\handlers</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="..\..\src\ripple\rpc\handlers\SubmitMultiSigned.cpp">
|
||||||
|
<Filter>ripple\rpc\handlers</Filter>
|
||||||
|
</ClCompile>
|
||||||
<ClCompile Include="..\..\src\ripple\rpc\handlers\Subscribe.cpp">
|
<ClCompile Include="..\..\src\ripple\rpc\handlers\Subscribe.cpp">
|
||||||
<Filter>ripple\rpc\handlers</Filter>
|
<Filter>ripple\rpc\handlers</Filter>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
|||||||
@@ -1028,7 +1028,7 @@ void LedgerEntrySet::incrementOwnerCount (Account const& owner)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
LedgerEntrySet::increaseOwnerCount (SLE::ref sleAccount, std::size_t howMuch)
|
LedgerEntrySet::increaseOwnerCount (SLE::ref sleAccount, std::uint32_t howMuch)
|
||||||
{
|
{
|
||||||
assert (sleAccount);
|
assert (sleAccount);
|
||||||
|
|
||||||
@@ -1059,7 +1059,7 @@ void LedgerEntrySet::decrementOwnerCount (Account const& owner)
|
|||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
LedgerEntrySet::decreaseOwnerCount (SLE::ref sleAccount, std::size_t howMuch)
|
LedgerEntrySet::decreaseOwnerCount (SLE::ref sleAccount, std::uint32_t howMuch)
|
||||||
{
|
{
|
||||||
assert (sleAccount);
|
assert (sleAccount);
|
||||||
|
|
||||||
|
|||||||
@@ -185,13 +185,13 @@ public:
|
|||||||
/** @{ */
|
/** @{ */
|
||||||
void incrementOwnerCount (SLE::ref sleAccount);
|
void incrementOwnerCount (SLE::ref sleAccount);
|
||||||
void incrementOwnerCount (Account const& owner);
|
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 (SLE::ref sleAccount);
|
||||||
void decrementOwnerCount (Account const& owner);
|
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.
|
// Offer functions.
|
||||||
|
|||||||
@@ -143,6 +143,9 @@ void printHelp (const po::options_description& desc)
|
|||||||
#endif // RIPPLE_ENABLE_MULTI_SIGN
|
#endif // RIPPLE_ENABLE_MULTI_SIGN
|
||||||
" stop\n"
|
" stop\n"
|
||||||
" submit\n"
|
" submit\n"
|
||||||
|
#if RIPPLE_ENABLE_MULTI_SIGN
|
||||||
|
" submit_multisigned\n"
|
||||||
|
#endif // RIPPLE_ENABLE_MULTI_SIGN
|
||||||
" tx <id>\n"
|
" tx <id>\n"
|
||||||
" unl_add <domain>|<public> [<comment>]\n"
|
" unl_add <domain>|<public> [<comment>]\n"
|
||||||
" unl_delete <domain>|<public_key>\n"
|
" unl_delete <domain>|<public_key>\n"
|
||||||
|
|||||||
@@ -29,8 +29,6 @@ classes the ability to replace portions of the default implementation.
|
|||||||
|
|
||||||
## SetSignerList ##
|
## 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
|
In order to enhance the flexibility of Ripple and provide support for enhanced
|
||||||
security of accounts, native support for "multi-signature" or "multi-sign"
|
security of accounts, native support for "multi-signature" or "multi-sign"
|
||||||
accounts is required.
|
accounts is required.
|
||||||
@@ -158,8 +156,6 @@ The data for a transaction that removes any signer list has this form:
|
|||||||
|
|
||||||
## Tickets ##
|
## 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
|
Currently transactions on the Ripple network require the use of sequence
|
||||||
numbers and sequence numbers must monotonically increase. Since the sequence
|
numbers and sequence numbers must monotonically increase. Since the sequence
|
||||||
number is part of the transaction, it is "covered" by the signature that
|
number is part of the transaction, it is "covered" by the signature that
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public:
|
|||||||
return temUNKNOWN;
|
return temUNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
TER checkSig () override
|
TER checkSign () override
|
||||||
{
|
{
|
||||||
if (mTxn.getFieldAccount160 (sfAccount).isNonZero ())
|
if (mTxn.getFieldAccount160 (sfAccount).isNonZero ())
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ public:
|
|||||||
{
|
{
|
||||||
if (!mSigMaster)
|
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;
|
return tecNEED_MASTER_KEY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -221,8 +221,8 @@ SetSignerList::replaceSignerList (uint256 const& index)
|
|||||||
return ter;
|
return ter;
|
||||||
|
|
||||||
// Compute new reserve. Verify the account has funds to meet the reserve.
|
// Compute new reserve. Verify the account has funds to meet the reserve.
|
||||||
std::size_t const oldOwnerCount = mTxnAccount->getFieldU32 (sfOwnerCount);
|
std::uint32_t const oldOwnerCount = mTxnAccount->getFieldU32 (sfOwnerCount);
|
||||||
std::size_t const addedOwnerCount = ownerCountDelta (signers_.size ());
|
std::uint32_t const addedOwnerCount = ownerCountDelta (signers_.size ());
|
||||||
|
|
||||||
std::uint64_t const newReserve =
|
std::uint64_t const newReserve =
|
||||||
mEngine->getLedger ()->getReserve (oldOwnerCount + addedOwnerCount);
|
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
|
// We have to examine the current SignerList so we know how much to
|
||||||
// reduce the OwnerCount.
|
// reduce the OwnerCount.
|
||||||
std::size_t removeFromOwnerCount = 0;
|
std::uint32_t removeFromOwnerCount = 0;
|
||||||
uint256 const signerListIndex = getSignerListIndex (mTxnAccountID);
|
uint256 const signerListIndex = getSignerListIndex (mTxnAccountID);
|
||||||
SLE::pointer accountSignersList =
|
SLE::pointer accountSignersList =
|
||||||
mEngine->view ().entryCache (ltSIGNER_LIST, signerListIndex);
|
mEngine->view ().entryCache (ltSIGNER_LIST, signerListIndex);
|
||||||
|
|||||||
@@ -19,6 +19,7 @@
|
|||||||
|
|
||||||
#include <BeastConfig.h>
|
#include <BeastConfig.h>
|
||||||
#include <ripple/app/tx/impl/Transactor.h>
|
#include <ripple/app/tx/impl/Transactor.h>
|
||||||
|
#include <ripple/app/tx/impl/SignerEntries.h>
|
||||||
#include <ripple/core/Config.h>
|
#include <ripple/core/Config.h>
|
||||||
#include <ripple/protocol/Indexes.h>
|
#include <ripple/protocol/Indexes.h>
|
||||||
|
|
||||||
@@ -157,35 +158,6 @@ TER Transactor::payFee ()
|
|||||||
return tesSUCCESS;
|
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 ()
|
TER Transactor::checkSeq ()
|
||||||
{
|
{
|
||||||
std::uint32_t const t_seq = mTxn.getSequence ();
|
std::uint32_t const t_seq = mTxn.getSequence ();
|
||||||
@@ -315,7 +287,7 @@ TER Transactor::apply ()
|
|||||||
|
|
||||||
if (terResult != tesSUCCESS) return (terResult);
|
if (terResult != tesSUCCESS) return (terResult);
|
||||||
|
|
||||||
terResult = checkSig ();
|
terResult = checkSign ();
|
||||||
|
|
||||||
if (terResult != tesSUCCESS) return (terResult);
|
if (terResult != tesSUCCESS) return (terResult);
|
||||||
|
|
||||||
@@ -325,4 +297,364 @@ TER Transactor::apply ()
|
|||||||
return doApply ();
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,7 +67,7 @@ protected:
|
|||||||
// Returns the fee, not scaled for load (Should be in fee units. FIXME)
|
// Returns the fee, not scaled for load (Should be in fee units. FIXME)
|
||||||
virtual std::uint64_t calculateBaseFee ();
|
virtual std::uint64_t calculateBaseFee ();
|
||||||
|
|
||||||
virtual TER checkSig ();
|
virtual TER checkSign ();
|
||||||
virtual TER doApply () = 0;
|
virtual TER doApply () = 0;
|
||||||
|
|
||||||
Transactor (
|
Transactor (
|
||||||
@@ -80,6 +80,10 @@ protected:
|
|||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
TER checkSingleSign ();
|
||||||
|
TER checkMultiSign ();
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
1618
src/ripple/app/tx/tests/MultiSign.test.cpp
Normal file
1618
src/ripple/app/tx/tests/MultiSign.test.cpp
Normal file
File diff suppressed because it is too large
Load Diff
430
src/ripple/app/tx/tests/common_transactor.cpp
Normal file
430
src/ripple/app/tx/tests/common_transactor.cpp
Normal 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
|
||||||
360
src/ripple/app/tx/tests/common_transactor.h
Normal file
360
src/ripple/app/tx/tests/common_transactor.h
Normal 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
|
||||||
@@ -659,6 +659,28 @@ private:
|
|||||||
return rpcError (rpcINVALID_PARAMS);
|
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>
|
// tx <transaction_id>
|
||||||
Json::Value parseTx (Json::Value const& jvParams)
|
Json::Value parseTx (Json::Value const& jvParams)
|
||||||
{
|
{
|
||||||
@@ -859,6 +881,9 @@ public:
|
|||||||
{ "sign_for", &RPCParser::parseSignFor, 4, 4 },
|
{ "sign_for", &RPCParser::parseSignFor, 4, 4 },
|
||||||
#endif // RIPPLE_ENABLE_MULTI_SIGN
|
#endif // RIPPLE_ENABLE_MULTI_SIGN
|
||||||
{ "submit", &RPCParser::parseSignSubmit, 1, 3 },
|
{ "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_info", &RPCParser::parseAsIs, 0, 0 },
|
||||||
{ "server_state", &RPCParser::parseAsIs, 0, 0 },
|
{ "server_state", &RPCParser::parseAsIs, 0, 0 },
|
||||||
{ "stop", &RPCParser::parseAsIs, 0, 0 },
|
{ "stop", &RPCParser::parseAsIs, 0, 0 },
|
||||||
|
|||||||
@@ -157,6 +157,9 @@ public:
|
|||||||
std::string const& escapedMetaData) const;
|
std::string const& escapedMetaData) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
bool checkSingleSign () const;
|
||||||
|
bool checkMultiSign () const;
|
||||||
|
|
||||||
TxType tx_type_;
|
TxType tx_type_;
|
||||||
|
|
||||||
mutable boost::tribool sig_state_;
|
mutable boost::tribool sig_state_;
|
||||||
|
|||||||
@@ -56,7 +56,6 @@ enum TER // aka TransactionEngineResult
|
|||||||
temMALFORMED = -299,
|
temMALFORMED = -299,
|
||||||
|
|
||||||
temBAD_AMOUNT,
|
temBAD_AMOUNT,
|
||||||
temBAD_AUTH_MASTER,
|
|
||||||
temBAD_CURRENCY,
|
temBAD_CURRENCY,
|
||||||
temBAD_EXPIRATION,
|
temBAD_EXPIRATION,
|
||||||
temBAD_FEE,
|
temBAD_FEE,
|
||||||
@@ -113,6 +112,10 @@ enum TER // aka TransactionEngineResult
|
|||||||
tefWRONG_PRIOR,
|
tefWRONG_PRIOR,
|
||||||
tefMASTER_DISABLED,
|
tefMASTER_DISABLED,
|
||||||
tefMAX_LEDGER,
|
tefMAX_LEDGER,
|
||||||
|
tefBAD_SIGNATURE,
|
||||||
|
tefBAD_QUORUM,
|
||||||
|
tefNOT_MULTI_SIGNING,
|
||||||
|
tefBAD_AUTH_MASTER,
|
||||||
|
|
||||||
// -99 .. -1: R Retry
|
// -99 .. -1: R Retry
|
||||||
// sequence too high, no funds for txn fee, originating -account
|
// sequence too high, no funds for txn fee, originating -account
|
||||||
|
|||||||
@@ -28,6 +28,17 @@ InnerObjectFormats::InnerObjectFormats ()
|
|||||||
<< SOElement (sfAccount, SOE_REQUIRED)
|
<< SOElement (sfAccount, SOE_REQUIRED)
|
||||||
<< SOElement (sfSignerWeight, 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)
|
void InnerObjectFormats::addCommonFields (Item& item)
|
||||||
|
|||||||
@@ -18,17 +18,18 @@
|
|||||||
//==============================================================================
|
//==============================================================================
|
||||||
|
|
||||||
#include <BeastConfig.h>
|
#include <BeastConfig.h>
|
||||||
#include <ripple/basics/Log.h>
|
#include <ripple/protocol/STTx.h>
|
||||||
#include <ripple/protocol/HashPrefix.h>
|
#include <ripple/protocol/HashPrefix.h>
|
||||||
#include <ripple/protocol/JsonFields.h>
|
#include <ripple/protocol/JsonFields.h>
|
||||||
#include <ripple/protocol/Protocol.h>
|
#include <ripple/protocol/Protocol.h>
|
||||||
#include <ripple/protocol/STAccount.h>
|
#include <ripple/protocol/STAccount.h>
|
||||||
#include <ripple/protocol/STArray.h>
|
#include <ripple/protocol/STArray.h>
|
||||||
#include <ripple/protocol/STTx.h>
|
|
||||||
#include <ripple/protocol/TxFlags.h>
|
#include <ripple/protocol/TxFlags.h>
|
||||||
|
#include <ripple/basics/Log.h>
|
||||||
#include <ripple/basics/StringUtilities.h>
|
#include <ripple/basics/StringUtilities.h>
|
||||||
#include <ripple/json/to_string.h>
|
#include <ripple/json/to_string.h>
|
||||||
#include <beast/unit_test/suite.h>
|
#include <beast/unit_test/suite.h>
|
||||||
|
#include <beast/cxx14/memory.h> // <memory>
|
||||||
#include <boost/format.hpp>
|
#include <boost/format.hpp>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
@@ -195,15 +196,16 @@ bool STTx::checkSign () const
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
ECDSA const fullyCanonical = (getFlags() & tfFullyCanonicalSig)
|
#if RIPPLE_ENABLE_MULTI_SIGN
|
||||||
? ECDSA::strict
|
// Determine whether we're single- or multi-signing by looking
|
||||||
: ECDSA::not_strict;
|
// at the SigningPubKey. It it's empty we must be multi-signing.
|
||||||
|
// Otherwise we're single-signing.
|
||||||
RippleAddress n;
|
Blob const& signingPubKey = getFieldVL (sfSigningPubKey);
|
||||||
n.setAccountPublic (getFieldVL (sfSigningPubKey));
|
sig_state_ = signingPubKey.empty () ?
|
||||||
|
checkMultiSign () : checkSingleSign ();
|
||||||
sig_state_ = n.accountPublicVerify (getSigningData (*this),
|
#else
|
||||||
getFieldVL (sfTxnSignature), fullyCanonical);
|
sig_state_ = checkSingleSign ();
|
||||||
|
#endif // RIPPLE_ENABLE_MULTI_SIGN
|
||||||
}
|
}
|
||||||
catch (...)
|
catch (...)
|
||||||
{
|
{
|
||||||
@@ -280,6 +282,217 @@ STTx::getMetaSQL (Serializer rawTxn,
|
|||||||
% getSequence () % inLedger % status % rTxn % escapedMetaData);
|
% 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
|
static
|
||||||
|
|||||||
@@ -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_ADD_AUTH, "tefBAD_ADD_AUTH", "Not authorized to add account." },
|
||||||
{ tefBAD_AUTH, "tefBAD_AUTH", "Transaction's public key is not authorized." },
|
{ tefBAD_AUTH, "tefBAD_AUTH", "Transaction's public key is not authorized." },
|
||||||
{ tefBAD_LEDGER, "tefBAD_LEDGER", "Ledger in unexpected state." },
|
{ 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." },
|
{ tefCREATED, "tefCREATED", "Can't add an already created account." },
|
||||||
{ tefEXCEPTION, "tefEXCEPTION", "Unexpected program state." },
|
{ tefEXCEPTION, "tefEXCEPTION", "Unexpected program state." },
|
||||||
{ tefFAILURE, "tefFAILURE", "Failed to apply." },
|
{ 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." },
|
{ tefMASTER_DISABLED, "tefMASTER_DISABLED", "Master key is disabled." },
|
||||||
{ tefMAX_LEDGER, "tefMAX_LEDGER", "Ledger sequence too high." },
|
{ tefMAX_LEDGER, "tefMAX_LEDGER", "Ledger sequence too high." },
|
||||||
{ tefNO_AUTH_REQUIRED, "tefNO_AUTH_REQUIRED", "Auth is not required." },
|
{ 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." },
|
{ tefPAST_SEQ, "tefPAST_SEQ", "This sequence number has already past." },
|
||||||
{ tefWRONG_PRIOR, "tefWRONG_PRIOR", "This previous transaction does not match." },
|
{ 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." },
|
{ telLOCAL_ERROR, "telLOCAL_ERROR", "Local failure." },
|
||||||
{ telBAD_DOMAIN, "telBAD_DOMAIN", "Domain too long." },
|
{ 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." },
|
{ temMALFORMED, "temMALFORMED", "Malformed transaction." },
|
||||||
{ temBAD_AMOUNT, "temBAD_AMOUNT", "Can only send positive amounts." },
|
{ 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_CURRENCY, "temBAD_CURRENCY", "Malformed: Bad currency." },
|
||||||
{ temBAD_EXPIRATION, "temBAD_EXPIRATION", "Malformed: Bad expiration." },
|
{ temBAD_EXPIRATION, "temBAD_EXPIRATION", "Malformed: Bad expiration." },
|
||||||
{ temBAD_FEE, "temBAD_FEE", "Invalid fee, negative or not XRP." },
|
{ temBAD_FEE, "temBAD_FEE", "Invalid fee, negative or not XRP." },
|
||||||
|
|||||||
@@ -112,6 +112,7 @@ void TxFormats::addCommonFields (Item& item)
|
|||||||
<< SOElement(sfMemos, SOE_OPTIONAL)
|
<< SOElement(sfMemos, SOE_OPTIONAL)
|
||||||
<< SOElement(sfSigningPubKey, SOE_REQUIRED)
|
<< SOElement(sfSigningPubKey, SOE_REQUIRED)
|
||||||
<< SOElement(sfTxnSignature, SOE_OPTIONAL)
|
<< SOElement(sfTxnSignature, SOE_OPTIONAL)
|
||||||
|
<< SOElement(sfMultiSigners, SOE_OPTIONAL) // submit_multisigned
|
||||||
;
|
;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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(STTx,ripple_app,ripple);
|
||||||
|
BEAST_DEFINE_TESTSUITE(InnerObjectFormatsSerializer,ripple_app,ripple);
|
||||||
|
|
||||||
} // ripple
|
} // ripple
|
||||||
|
|||||||
39
src/ripple/rpc/handlers/SubmitMultiSigned.cpp
Normal file
39
src/ripple/rpc/handlers/SubmitMultiSigned.cpp
Normal 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
|
||||||
@@ -137,6 +137,9 @@ HandlerTable HANDLERS({
|
|||||||
{ "sign_for", byRef (&doSignFor), Role::USER, NO_CONDITION },
|
{ "sign_for", byRef (&doSignFor), Role::USER, NO_CONDITION },
|
||||||
#endif // RIPPLE_ENABLE_MULTI_SIGN
|
#endif // RIPPLE_ENABLE_MULTI_SIGN
|
||||||
{ "submit", byRef (&doSubmit), Role::USER, NEEDS_CURRENT_LEDGER },
|
{ "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_info", byRef (&doServerInfo), Role::USER, NO_CONDITION },
|
||||||
{ "server_state", byRef (&doServerState), Role::USER, NO_CONDITION },
|
{ "server_state", byRef (&doServerState), Role::USER, NO_CONDITION },
|
||||||
{ "stop", byRef (&doStop), Role::ADMIN, NO_CONDITION },
|
{ "stop", byRef (&doStop), Role::ADMIN, NO_CONDITION },
|
||||||
|
|||||||
@@ -525,7 +525,6 @@ struct transactionPreProcessResult
|
|||||||
transactionPreProcessResult& operator=
|
transactionPreProcessResult& operator=
|
||||||
(transactionPreProcessResult&&) = delete;
|
(transactionPreProcessResult&&) = delete;
|
||||||
|
|
||||||
|
|
||||||
transactionPreProcessResult (Json::Value&& json)
|
transactionPreProcessResult (Json::Value&& json)
|
||||||
: first (std::move (json))
|
: first (std::move (json))
|
||||||
, second ()
|
, second ()
|
||||||
@@ -1019,5 +1018,276 @@ Json::Value transactionSignFor (
|
|||||||
return json;
|
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
|
} // RPC
|
||||||
} // ripple
|
} // ripple
|
||||||
|
|||||||
@@ -168,6 +168,24 @@ Json::Value transactionSignFor (
|
|||||||
return transactionSignFor (params, failType, apiFacade, role);
|
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
|
} // RPC
|
||||||
} // ripple
|
} // ripple
|
||||||
|
|
||||||
|
|||||||
@@ -963,7 +963,8 @@ public:
|
|||||||
{
|
{
|
||||||
TestStuff {transactionSign, "sign", 0},
|
TestStuff {transactionSign, "sign", 0},
|
||||||
TestStuff {transactionSubmit, "submit", 1},
|
TestStuff {transactionSubmit, "submit", 1},
|
||||||
TestStuff {transactionSignFor, "sign_for", 2}
|
TestStuff {transactionSignFor, "sign_for", 2},
|
||||||
|
TestStuff {transactionSubmitMultiSigned, "submit_multisigned", 3}
|
||||||
};
|
};
|
||||||
|
|
||||||
for (auto testFunc : testFuncs)
|
for (auto testFunc : testFuncs)
|
||||||
|
|||||||
@@ -47,5 +47,7 @@
|
|||||||
#include <ripple/app/tx/impl/SetSignerList.cpp>
|
#include <ripple/app/tx/impl/SetSignerList.cpp>
|
||||||
#include <ripple/app/tx/impl/SignerEntries.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/OfferStream.test.cpp>
|
||||||
#include <ripple/app/tx/tests/Taker.test.cpp>
|
#include <ripple/app/tx/tests/Taker.test.cpp>
|
||||||
|
|||||||
@@ -76,6 +76,7 @@
|
|||||||
#include <ripple/rpc/handlers/SignFor.cpp>
|
#include <ripple/rpc/handlers/SignFor.cpp>
|
||||||
#include <ripple/rpc/handlers/Stop.cpp>
|
#include <ripple/rpc/handlers/Stop.cpp>
|
||||||
#include <ripple/rpc/handlers/Submit.cpp>
|
#include <ripple/rpc/handlers/Submit.cpp>
|
||||||
|
#include <ripple/rpc/handlers/SubmitMultiSigned.cpp>
|
||||||
#include <ripple/rpc/handlers/Subscribe.cpp>
|
#include <ripple/rpc/handlers/Subscribe.cpp>
|
||||||
#include <ripple/rpc/handlers/TransactionEntry.cpp>
|
#include <ripple/rpc/handlers/TransactionEntry.cpp>
|
||||||
#include <ripple/rpc/handlers/Tx.cpp>
|
#include <ripple/rpc/handlers/Tx.cpp>
|
||||||
|
|||||||
Reference in New Issue
Block a user