mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-19 18:15:50 +00:00
SignerListSet txn and InnerObjectFormats (RIPD-182):
Add support for the SignerListSet transaction as a step toward multi-sign support. As part of the SignerListSet implementation, add InnerObjectFormat templates (similar to TxFormats and LedgerFormats) and enforce them in STObject, STArray, and STParsedJSON.
This commit is contained in:
committed by
Vinnie Falco
parent
92799187ed
commit
64ebd64d2b
@@ -1976,12 +1976,26 @@
|
||||
<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\impl\SetSignerList.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\impl\SetTrust.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\impl\SignerEntries.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\impl\SignerEntries.h">
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\app\tx\impl\Taker.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
@@ -2794,6 +2808,10 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\protocol\impl\InnerObjectFormats.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\protocol\impl\LedgerFormats.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
@@ -2894,6 +2912,8 @@
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\protocol\Indexes.h">
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\protocol\InnerObjectFormats.h">
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\protocol\Issue.h">
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\protocol\JsonFields.h">
|
||||
@@ -2960,6 +2980,10 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\protocol\tests\InnerObjectFormats.test.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\protocol\tests\Issue.test.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release|x64'">True</ExcludedFromBuild>
|
||||
@@ -3499,10 +3523,6 @@
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug.classic|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release.classic|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\unity\app9.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug.classic|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release.classic|x64'">True</ExcludedFromBuild>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\unity\basics.cpp">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='debug.classic|x64'">True</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='release.classic|x64'">True</ExcludedFromBuild>
|
||||
|
||||
@@ -2529,9 +2529,18 @@
|
||||
<ClCompile Include="..\..\src\ripple\app\tx\impl\SetRegularKey.cpp">
|
||||
<Filter>ripple\app\tx\impl</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\tx\impl\SetSignerList.cpp">
|
||||
<Filter>ripple\app\tx\impl</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\tx\impl\SetTrust.cpp">
|
||||
<Filter>ripple\app\tx\impl</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\app\tx\impl\SignerEntries.cpp">
|
||||
<Filter>ripple\app\tx\impl</Filter>
|
||||
</ClCompile>
|
||||
<ClInclude Include="..\..\src\ripple\app\tx\impl\SignerEntries.h">
|
||||
<Filter>ripple\app\tx\impl</Filter>
|
||||
</ClInclude>
|
||||
<ClCompile Include="..\..\src\ripple\app\tx\impl\Taker.cpp">
|
||||
<Filter>ripple\app\tx\impl</Filter>
|
||||
</ClCompile>
|
||||
@@ -3327,6 +3336,9 @@
|
||||
<ClCompile Include="..\..\src\ripple\protocol\impl\Indexes.cpp">
|
||||
<Filter>ripple\protocol\impl</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\protocol\impl\InnerObjectFormats.cpp">
|
||||
<Filter>ripple\protocol\impl</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\protocol\impl\LedgerFormats.cpp">
|
||||
<Filter>ripple\protocol\impl</Filter>
|
||||
</ClCompile>
|
||||
@@ -3405,6 +3417,9 @@
|
||||
<ClInclude Include="..\..\src\ripple\protocol\Indexes.h">
|
||||
<Filter>ripple\protocol</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\protocol\InnerObjectFormats.h">
|
||||
<Filter>ripple\protocol</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="..\..\src\ripple\protocol\Issue.h">
|
||||
<Filter>ripple\protocol</Filter>
|
||||
</ClInclude>
|
||||
@@ -3501,6 +3516,9 @@
|
||||
<ClCompile Include="..\..\src\ripple\protocol\tests\BuildInfo.test.cpp">
|
||||
<Filter>ripple\protocol\tests</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\protocol\tests\InnerObjectFormats.test.cpp">
|
||||
<Filter>ripple\protocol\tests</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\protocol\tests\Issue.test.cpp">
|
||||
<Filter>ripple\protocol\tests</Filter>
|
||||
</ClCompile>
|
||||
@@ -4068,9 +4086,6 @@
|
||||
<ClCompile Include="..\..\src\ripple\unity\app8.cpp">
|
||||
<Filter>ripple\unity</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\unity\app9.cpp">
|
||||
<Filter>ripple\unity</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="..\..\src\ripple\unity\basics.cpp">
|
||||
<Filter>ripple\unity</Filter>
|
||||
</ClCompile>
|
||||
|
||||
@@ -678,7 +678,6 @@ def get_unity_sources():
|
||||
'src/ripple/unity/app6.cpp',
|
||||
'src/ripple/unity/app7.cpp',
|
||||
'src/ripple/unity/app8.cpp',
|
||||
'src/ripple/unity/app9.cpp',
|
||||
'src/ripple/unity/core.cpp',
|
||||
'src/ripple/unity/basics.cpp',
|
||||
'src/ripple/unity/crypto.cpp',
|
||||
|
||||
@@ -202,4 +202,13 @@
|
||||
#define RIPPLE_ENABLE_TICKETS 0
|
||||
#endif
|
||||
|
||||
/** Config: RIPPLE_ENABLE_MULTI_SIGN
|
||||
When set, activates the current state of the multi-sign feature which is
|
||||
under development. When the feature is complete and released this
|
||||
#define should be removed.
|
||||
*/
|
||||
#ifndef RIPPLE_ENABLE_MULTI_SIGN
|
||||
#define RIPPLE_ENABLE_MULTI_SIGN 0
|
||||
#endif
|
||||
|
||||
#endif
|
||||
|
||||
@@ -1018,50 +1018,65 @@ uint256 LedgerEntrySet::getNextLedgerIndex (
|
||||
|
||||
void LedgerEntrySet::incrementOwnerCount (SLE::ref sleAccount)
|
||||
{
|
||||
assert (sleAccount);
|
||||
|
||||
std::uint32_t const current_count = sleAccount->getFieldU32 (sfOwnerCount);
|
||||
|
||||
if (current_count == std::numeric_limits<std::uint32_t>::max ())
|
||||
{
|
||||
WriteLog (lsFATAL, LedgerEntrySet) <<
|
||||
"Account " << sleAccount->getFieldAccount160 (sfAccount) <<
|
||||
" owner count exceeds max!";
|
||||
return;
|
||||
}
|
||||
|
||||
sleAccount->setFieldU32 (sfOwnerCount, current_count + 1);
|
||||
entryModify (sleAccount);
|
||||
increaseOwnerCount (sleAccount, 1);
|
||||
}
|
||||
|
||||
void LedgerEntrySet::incrementOwnerCount (Account const& owner)
|
||||
{
|
||||
incrementOwnerCount(entryCache (ltACCOUNT_ROOT,
|
||||
getAccountRootIndex (owner)));
|
||||
increaseOwnerCount(
|
||||
entryCache (ltACCOUNT_ROOT, getAccountRootIndex (owner)), 1);
|
||||
}
|
||||
|
||||
void LedgerEntrySet::decrementOwnerCount (SLE::ref sleAccount)
|
||||
void
|
||||
LedgerEntrySet::increaseOwnerCount (SLE::ref sleAccount, std::size_t howMuch)
|
||||
{
|
||||
assert (sleAccount);
|
||||
|
||||
std::uint32_t const current_count = sleAccount->getFieldU32 (sfOwnerCount);
|
||||
std::uint32_t new_count = current_count + howMuch;
|
||||
|
||||
if (current_count == 0)
|
||||
// Check for integer overflow -- well defined behavior on unsigned.
|
||||
if (new_count < current_count)
|
||||
{
|
||||
WriteLog (lsFATAL, LedgerEntrySet) <<
|
||||
"Account " << sleAccount->getFieldAccount160 (sfAccount) <<
|
||||
" owner count is already 0!";
|
||||
return;
|
||||
" owner count exceeds max!";
|
||||
new_count = std::numeric_limits<std::uint32_t>::max ();
|
||||
}
|
||||
|
||||
sleAccount->setFieldU32 (sfOwnerCount, current_count - 1);
|
||||
sleAccount->setFieldU32 (sfOwnerCount, new_count);
|
||||
entryModify (sleAccount);
|
||||
}
|
||||
|
||||
void LedgerEntrySet::decrementOwnerCount (SLE::ref sleAccount)
|
||||
{
|
||||
decreaseOwnerCount (sleAccount, 1);
|
||||
}
|
||||
|
||||
void LedgerEntrySet::decrementOwnerCount (Account const& owner)
|
||||
{
|
||||
decrementOwnerCount(entryCache (ltACCOUNT_ROOT,
|
||||
getAccountRootIndex (owner)));
|
||||
decreaseOwnerCount(
|
||||
entryCache (ltACCOUNT_ROOT, getAccountRootIndex (owner)), 1);
|
||||
}
|
||||
|
||||
void
|
||||
LedgerEntrySet::decreaseOwnerCount (SLE::ref sleAccount, std::size_t howMuch)
|
||||
{
|
||||
assert (sleAccount);
|
||||
|
||||
std::uint32_t const current_count = sleAccount->getFieldU32 (sfOwnerCount);
|
||||
std::uint32_t new_count = current_count - howMuch;
|
||||
|
||||
// Check for integer underflow -- well defined behavior on unsigned.
|
||||
if (new_count > current_count)
|
||||
{
|
||||
WriteLog (lsFATAL, LedgerEntrySet) <<
|
||||
"Account " << sleAccount->getFieldAccount160 (sfAccount) <<
|
||||
" owner count set below 0!";
|
||||
new_count = 0;
|
||||
assert (false); // "This is a dangerous place." Stop in a debug build.
|
||||
}
|
||||
sleAccount->setFieldU32 (sfOwnerCount, new_count);
|
||||
entryModify (sleAccount);
|
||||
}
|
||||
|
||||
TER LedgerEntrySet::offerDelete (SLE::pointer sleOffer)
|
||||
|
||||
@@ -185,11 +185,13 @@ public:
|
||||
/** @{ */
|
||||
void incrementOwnerCount (SLE::ref sleAccount);
|
||||
void incrementOwnerCount (Account const& owner);
|
||||
void increaseOwnerCount (SLE::ref sleAccount, std::size_t howMuch);
|
||||
/** @} */
|
||||
|
||||
/** @{ */
|
||||
void decrementOwnerCount (SLE::ref sleAccount);
|
||||
void decrementOwnerCount (Account const& owner);
|
||||
void decreaseOwnerCount (SLE::ref sleAccount, std::size_t howMuch);
|
||||
/** @} */
|
||||
|
||||
// Offer functions.
|
||||
|
||||
@@ -137,7 +137,9 @@ void printHelp (const po::options_description& desc)
|
||||
" ripple_path_find <json> [<ledger>]\n"
|
||||
" version\n"
|
||||
" server_info\n"
|
||||
" sign\n"
|
||||
" stop\n"
|
||||
" submit\n"
|
||||
" tx <id>\n"
|
||||
" unl_add <domain>|<public> [<comment>]\n"
|
||||
" unl_delete <domain>|<public_key>\n"
|
||||
|
||||
@@ -1,90 +0,0 @@
|
||||
|
||||
# Transactors #
|
||||
|
||||
## Introduction ##
|
||||
|
||||
Each separate kind of transaction is handled by it's own Transactor.
|
||||
The Transactor base class provides functionality that is common to
|
||||
all derived Transactors. The Transactor base class also gives derived
|
||||
classes the ability to replace portions of the default implementation.
|
||||
|
||||
|
||||
# Details on Specific Transactors #
|
||||
|
||||
## AddWallet ##
|
||||
|
||||
## Change ##
|
||||
|
||||
## Offers ##
|
||||
|
||||
### CreateOffer ###
|
||||
|
||||
### CancelOffer ###
|
||||
|
||||
## Payment ##
|
||||
|
||||
## SetAccount ##
|
||||
|
||||
## SetRegularKey ##
|
||||
|
||||
## SetTrust ##
|
||||
|
||||
## Tickets ##
|
||||
|
||||
**Associated JIRA task is [RIPD-368](https://ripplelabs.atlassian.net/browse/RIPD-368)**
|
||||
|
||||
Currently transactions on the Ripple network require the use of sequence
|
||||
numbers and sequence numbers must monotonically increase. Since the sequence
|
||||
number is part of the transaction, it is "covered" by the signature that
|
||||
authorizes the transaction, which means that the sequence number would have
|
||||
to be decided at the time of transaction issuance. This would mean that
|
||||
multi-signature transactions could only be processed in strict "first-in,
|
||||
first-out" order which is not practical.
|
||||
|
||||
Tickets can be used in lieu of sequence number. A ticket is a special token
|
||||
which, through a transaction, can be issued by any account and can be
|
||||
configured with an optional expiry date and an optional associated account.
|
||||
|
||||
### Specifics ###
|
||||
|
||||
The expiry date can be used to constrain the validity of the ticket. If
|
||||
specified, the ticket will be considered invalid and unusable if the closing
|
||||
time of the last *validated* ledger is greater than or equal to the expiration
|
||||
time of the ticket.
|
||||
|
||||
The associated account can be used to specify an account, other than the
|
||||
issuing account, that is allowed to "consume" the ticket. Consuming a ticket
|
||||
means to use the ticket in a transaction that is accepted by the network and
|
||||
makes its way into a validated ledger. If not present, the ticket can only be
|
||||
consumed by the issuing account.
|
||||
|
||||
*Corner Case:* It is possible that two or more transactions reference the same
|
||||
ticket and that both go into the same consensus set. During final application
|
||||
of transactions from the consensus set at most one of these transactions may
|
||||
succeed; others must fail with the indication that the ticket has been consumed.
|
||||
|
||||
*Reserve:* While a ticket is outstanding, it should count against the reserve
|
||||
of the *issuer*.
|
||||
|
||||
##### Issuance
|
||||
We should decide whether, in the case of multi-signature accounts, any single
|
||||
authorized signer can issue a ticket on the multi-signature accounts' behalf.
|
||||
This approach has both advantages and disadvantages.
|
||||
|
||||
Advantages include:
|
||||
|
||||
+ Simpler logic for tickets and reduced data - no need to store or consider an associated account.
|
||||
+ Owner reserves for issued tickets count against the multi-signature account instead of the account of the signer proposing a transaction.
|
||||
+ Cleaner meta-data: easier to follow who issued a ticket and how many tickets are outstanding and associated with a particular account.
|
||||
|
||||
Disadvantages are:
|
||||
|
||||
+ Any single authorized signer can issue multiple tickets, each counting against the account's reserve.
|
||||
+ Special-case logic for authorizing tickets on multi-sign accounts.
|
||||
|
||||
I believe that the disadvantages outweigh the advantages, but debate is welcome.
|
||||
|
||||
|
||||
### CreateTicket ###
|
||||
|
||||
### CancelTicket ###
|
||||
217
src/ripple/app/tx/README.md
Normal file
217
src/ripple/app/tx/README.md
Normal file
@@ -0,0 +1,217 @@
|
||||
|
||||
# Transactors #
|
||||
|
||||
## Introduction ##
|
||||
|
||||
Each separate kind of transaction is handled by it's own Transactor.
|
||||
The Transactor base class provides functionality that is common to
|
||||
all derived Transactors. The Transactor base class also gives derived
|
||||
classes the ability to replace portions of the default implementation.
|
||||
|
||||
|
||||
# Details on Specific Transactors #
|
||||
|
||||
## AddWallet ##
|
||||
|
||||
## Change ##
|
||||
|
||||
## Offers ##
|
||||
|
||||
### CreateOffer ###
|
||||
|
||||
### CancelOffer ###
|
||||
|
||||
## Payment ##
|
||||
|
||||
## SetAccount ##
|
||||
|
||||
## SetRegularKey ##
|
||||
|
||||
## SetSignerList ##
|
||||
|
||||
**Associated JIRA task is [RIPD-182](https://ripplelabs.atlassian.net/browse/RIPD-182)**
|
||||
|
||||
In order to enhance the flexibility of Ripple and provide support for enhanced
|
||||
security of accounts, native support for "multi-signature" or "multi-sign"
|
||||
accounts is required.
|
||||
|
||||
Transactions on an account which is designated as multi-sign can be authorized
|
||||
either by using the master or regular keys (unless those are disabled) or by
|
||||
being signed by a certain number (a quorum) of pre-authorized accounts.
|
||||
|
||||
Some technical details, including tables indicating some of the Ripple
|
||||
commands and ledger entries to be used for implementing multi-signature, are
|
||||
currently listed on the [wiki](https://ripple.com/wiki/Multisign) but will
|
||||
eventually be migrated into this document as well.
|
||||
|
||||
For accounts which are designated as multi-sign, there must be a list which
|
||||
specifies which accounts are authorized to sign and the quorum threshold.
|
||||
|
||||
- Each authorized account has a weight. The sum of the weights of the signers is used to determine whether a given set of signatures is sufficient for a quorum.
|
||||
|
||||
- The quorum threshold indicates the minimum required weight that the sum of the weights of all signatures must have before a transaction can be authorized.
|
||||
|
||||
### Verification of Multiple Signatures During TX Processing
|
||||
The current approach to adding multi-signature support is to require that a
|
||||
transaction is to be signed outside the Ripple network and only submitted
|
||||
after the quorum has been reached.
|
||||
|
||||
This reduces the implementation footprint and the load imposed on the network,
|
||||
and mirrors the way transaction signing is currently handled. It will require
|
||||
some messaging mechanism outside the Ripple network to disseminate the proposed
|
||||
transaction to the authorized signers and to allow them to apply signatures.
|
||||
|
||||
Supporting in-ledger voting should be considered, but it has both advantages
|
||||
and disadvantages.
|
||||
|
||||
One of the advantages is increased transparency - transactions are visible as
|
||||
are the "votes" (the authorized accounts signing the transaction) on the
|
||||
ledger. However, transactions may also languish for a long time in the ledger,
|
||||
never reaching a quorum and consuming network resources.
|
||||
|
||||
### Signature Format
|
||||
We should not develop a new format for multi-sign signatures. Instead every
|
||||
signer should extract and sign the transaction as they normally would if this
|
||||
were a regular transaction. The resulting signature will be stored as a triple
|
||||
of { signing-account, signer-public-key, signature } in an array of signatures
|
||||
associated with this transaction.
|
||||
|
||||
The advantage of this is that we can reuse the existing signing and
|
||||
verification code, and leverage the work that will go towards implementing
|
||||
support for the Ed25519 elliptic curve.
|
||||
|
||||
### Fees ###
|
||||
Multi-signature transactions impose a heavier load on the network and should
|
||||
claim higher fees.
|
||||
|
||||
The fee structure is not yet decided, but an escalating fee structure is laid
|
||||
out and discussed on the [wiki](https://ripple.com/wiki/Multisign). This
|
||||
might need to be revisited and designed in light of discussions about changing
|
||||
how fees are handled.
|
||||
|
||||
### Proposed Transaction Cancellation ###
|
||||
A transaction that has been proposed against a multi-sign account using a
|
||||
ticket can be positively canceled if a quorum of authorized signers sign and
|
||||
issue a transaction that consumes that ticket.
|
||||
|
||||
### Implementation ###
|
||||
|
||||
Any account can have one SignerList attached to it. A SignerList contains the
|
||||
following elements:
|
||||
|
||||
- A list of from 2 to a protocol-defined maximum of 8 signers. Each signer in the array consists of:
|
||||
- The signer's 160-bit account ID and
|
||||
- The signer's 16-bit weight (used to calculate whether a quorum is met).
|
||||
- And, for the entire list, a single 32-bit quorum value.
|
||||
|
||||
Giving the signers different weights allows an account to organize signers so
|
||||
some are more important than others. A signer with a larger weight has more
|
||||
significance in achieving the quorum.
|
||||
|
||||
A multi-signed transaction is validated like this:
|
||||
|
||||
- Each signer of the transaction has their signature validated.
|
||||
- The weights of all valid signers are summed.
|
||||
- If the sum of the weights equals or exceeds the quorum value then the entire transaction is considered signed. If the sum is below the quorum, then the signature fails with a tefBAD_QUORUM.
|
||||
|
||||
|
||||
By making the signer weights 16 bits and the quorum value 32 bits we avoid
|
||||
concerns about overflows and still have plenty of resolution.
|
||||
|
||||
This transactor allows two operations:
|
||||
|
||||
- Create (or replace) a signer list for the target account.
|
||||
- Remove any signer list from the target account.
|
||||
|
||||
The data for a transaction creating or replacing a signer list has this
|
||||
general form:
|
||||
|
||||
{
|
||||
"TransactionType": "SignerListSet",
|
||||
"Account": "rDg53Haik2475DJx8bjMDSDPj4VX7htaMd",
|
||||
"SignerQuorum": 7,
|
||||
"SignerEntries": [
|
||||
{
|
||||
"SignerEntry": {
|
||||
"Account": "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
|
||||
"SignerWeight": 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"SignerEntry": {
|
||||
"Account": "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
|
||||
"SignerWeight": 3
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
The data for a transaction that removes any signer list has this form:
|
||||
|
||||
{
|
||||
"TransactionType": "SignerListSet",
|
||||
"Account": "rDg53Haik2475DJx8bjMDSDPj4VX7htaMd",
|
||||
"SignerQuorum": 0
|
||||
}
|
||||
|
||||
## SetTrust ##
|
||||
|
||||
## Tickets ##
|
||||
|
||||
**Associated JIRA task is [RIPD-368](https://ripplelabs.atlassian.net/browse/RIPD-368)**
|
||||
|
||||
Currently transactions on the Ripple network require the use of sequence
|
||||
numbers and sequence numbers must monotonically increase. Since the sequence
|
||||
number is part of the transaction, it is "covered" by the signature that
|
||||
authorizes the transaction, which means that the sequence number would have
|
||||
to be decided at the time of transaction issuance. This would mean that
|
||||
multi-signature transactions could only be processed in strict "first-in,
|
||||
first-out" order which is not practical.
|
||||
|
||||
Tickets can be used in lieu of sequence number. A ticket is a special token
|
||||
which, through a transaction, can be issued by any account and can be
|
||||
configured with an optional expiry date and an optional associated account.
|
||||
|
||||
### Specifics ###
|
||||
|
||||
The expiry date can be used to constrain the validity of the ticket. If
|
||||
specified, the ticket will be considered invalid and unusable if the closing
|
||||
time of the last *validated* ledger is greater than or equal to the expiration
|
||||
time of the ticket.
|
||||
|
||||
The associated account can be used to specify an account, other than the
|
||||
issuing account, that is allowed to "consume" the ticket. Consuming a ticket
|
||||
means to use the ticket in a transaction that is accepted by the network and
|
||||
makes its way into a validated ledger. If not present, the ticket can only be
|
||||
consumed by the issuing account.
|
||||
|
||||
*Corner Case:* It is possible that two or more transactions reference the same
|
||||
ticket and that both go into the same consensus set. During final application
|
||||
of transactions from the consensus set at most one of these transactions may
|
||||
succeed; others must fail with the indication that the ticket has been consumed.
|
||||
|
||||
*Reserve:* While a ticket is outstanding, it should count against the reserve
|
||||
of the *issuer*.
|
||||
|
||||
##### Issuance
|
||||
We should decide whether, in the case of multi-signature accounts, any single
|
||||
authorized signer can issue a ticket on the multi-signature accounts' behalf.
|
||||
This approach has both advantages and disadvantages.
|
||||
|
||||
Advantages include:
|
||||
|
||||
+ Simpler logic for tickets and reduced data - no need to store or consider an associated account.
|
||||
+ Owner reserves for issued tickets count against the multi-signature account instead of the account of the signer proposing a transaction.
|
||||
+ Cleaner meta-data: easier to follow who issued a ticket and how many tickets are outstanding and associated with a particular account.
|
||||
|
||||
Disadvantages are:
|
||||
|
||||
+ Any single authorized signer can issue multiple tickets, each counting against the account's reserve.
|
||||
+ Special-case logic for authorizing tickets on multi-sign accounts.
|
||||
|
||||
I believe that the disadvantages outweigh the advantages, but debate is welcome.
|
||||
|
||||
|
||||
### CreateTicket ###
|
||||
|
||||
### CancelTicket ###
|
||||
363
src/ripple/app/tx/impl/SetSignerList.cpp
Normal file
363
src/ripple/app/tx/impl/SetSignerList.cpp
Normal file
@@ -0,0 +1,363 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 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/app/tx/impl/Transactor.h>
|
||||
#include <ripple/app/tx/impl/SignerEntries.h>
|
||||
#include <ripple/protocol/STObject.h>
|
||||
#include <ripple/protocol/STArray.h>
|
||||
#include <ripple/protocol/STTx.h>
|
||||
#include <ripple/protocol/STAccount.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <cstdint>
|
||||
#include <algorithm>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/**
|
||||
See the README.md for an overview of the SetSignerList transaction that
|
||||
this class implements.
|
||||
*/
|
||||
class SetSignerList final
|
||||
: public Transactor
|
||||
{
|
||||
private:
|
||||
// Values determined during preCheck for use later.
|
||||
enum Operation {unknown, set, destroy};
|
||||
Operation do_ {unknown};
|
||||
std::uint32_t quorum_ {0};
|
||||
std::vector<SignerEntries::SignerEntry> signers_;
|
||||
|
||||
public:
|
||||
SetSignerList (
|
||||
STTx const& txn,
|
||||
TransactionEngineParams params,
|
||||
TransactionEngine* engine)
|
||||
: Transactor (
|
||||
txn,
|
||||
params,
|
||||
engine,
|
||||
deprecatedLogs().journal("SetSignerList"))
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
Applies the transaction if it is well formed and the ledger state permits.
|
||||
*/
|
||||
TER doApply () override;
|
||||
|
||||
protected:
|
||||
/**
|
||||
Check anything that can be checked without the ledger.
|
||||
*/
|
||||
TER preCheck () override;
|
||||
|
||||
private:
|
||||
// signers are not const because method (intentionally) sorts vector.
|
||||
TER validateQuorumAndSignerEntries (
|
||||
std::uint32_t quorum,
|
||||
std::vector<SignerEntries::SignerEntry>& signers) const;
|
||||
|
||||
// Methods called by doApply()
|
||||
TER replaceSignerList (uint256 const& index);
|
||||
TER destroySignerList (uint256 const& index);
|
||||
|
||||
void writeSignersToLedger (SLE::pointer ledgerEntry);
|
||||
|
||||
static std::size_t ownerCountDelta (std::size_t entryCount);
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
TER
|
||||
SetSignerList::doApply ()
|
||||
{
|
||||
assert (mTxnAccount);
|
||||
|
||||
// All operations require our ledger index. Compute that once and pass it
|
||||
// to our handlers.
|
||||
uint256 const index = getSignerListIndex (mTxnAccountID);
|
||||
|
||||
// Perform the operation preCheck() decided on.
|
||||
switch (do_)
|
||||
{
|
||||
case set:
|
||||
return replaceSignerList (index);
|
||||
|
||||
case destroy:
|
||||
return destroySignerList (index);
|
||||
|
||||
default:
|
||||
// Fall through intentionally
|
||||
break;
|
||||
}
|
||||
assert (false); // Should not be possible to get here.
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
TER
|
||||
SetSignerList::preCheck()
|
||||
{
|
||||
// We need the account ID later, so do this check first.
|
||||
preCheckAccount ();
|
||||
|
||||
// Check the quorum. A non-zero quorum means we're creating or replacing
|
||||
// the list. A zero quorum means we're destroying the list.
|
||||
quorum_ = (mTxn.getFieldU32 (sfSignerQuorum));
|
||||
|
||||
bool const hasSignerEntries (mTxn.isFieldPresent (sfSignerEntries));
|
||||
if (quorum_ && hasSignerEntries)
|
||||
{
|
||||
SignerEntries::Decoded signers (
|
||||
SignerEntries::deserialize (mTxn, m_journal, "transaction"));
|
||||
|
||||
if (signers.ter != tesSUCCESS)
|
||||
return signers.ter;
|
||||
|
||||
// Validate our settings.
|
||||
if (TER const ter =
|
||||
validateQuorumAndSignerEntries (quorum_, signers.vec))
|
||||
{
|
||||
return ter;
|
||||
}
|
||||
|
||||
// Save deserialized and validated list for later.
|
||||
signers_ = std::move (signers.vec);
|
||||
do_ = set;
|
||||
}
|
||||
else if ((quorum_ == 0) && !hasSignerEntries)
|
||||
{
|
||||
do_ = destroy;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Neither a set nor a destroy. Malformed.
|
||||
if (m_journal.trace) m_journal.trace <<
|
||||
"Malformed transaction: Invalid signer set list format.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
return preCheckSigningKey ();
|
||||
}
|
||||
|
||||
TER
|
||||
SetSignerList::validateQuorumAndSignerEntries (
|
||||
std::uint32_t quorum,
|
||||
std::vector<SignerEntries::SignerEntry>& signers) const
|
||||
{
|
||||
// Reject if there are too many or too few entries in the list.
|
||||
{
|
||||
std::size_t const signerCount = signers.size ();
|
||||
if ((signerCount < SignerEntries::minEntries)
|
||||
|| (signerCount > SignerEntries::maxEntries))
|
||||
{
|
||||
if (m_journal.trace) m_journal.trace <<
|
||||
"Too many or too few signers in signer list.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure there are no duplicate signers.
|
||||
std::sort (signers.begin (), signers.end ());
|
||||
if (std::adjacent_find (
|
||||
signers.begin (), signers.end ()) != signers.end ())
|
||||
{
|
||||
if (m_journal.trace) m_journal.trace <<
|
||||
"Duplicate signers in signer list";
|
||||
return temBAD_SIGNER;
|
||||
}
|
||||
|
||||
// Make sure no signers reference this account. Also make sure the
|
||||
// quorum can be reached.
|
||||
std::uint64_t allSignersWeight (0);
|
||||
for (auto const& signer : signers)
|
||||
{
|
||||
allSignersWeight += signer.weight;
|
||||
|
||||
if (signer.account == mTxnAccountID)
|
||||
{
|
||||
if (m_journal.trace) m_journal.trace <<
|
||||
"A signer may not self reference account.";
|
||||
return temBAD_SIGNER;
|
||||
}
|
||||
|
||||
// Don't verify that the signer accounts exist. Non-existent accounts
|
||||
// may be phantom accounts (which are permitted).
|
||||
}
|
||||
if ((quorum <= 0) || (allSignersWeight < quorum))
|
||||
{
|
||||
if (m_journal.trace) m_journal.trace <<
|
||||
"Quorum is unreachable";
|
||||
return temBAD_QUORUM;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
SetSignerList::replaceSignerList (uint256 const& index)
|
||||
{
|
||||
// This may be either a create or a replace. Preemptively destroy any
|
||||
// old signer list. May reduce the reserve, so this is done before
|
||||
// checking the reserve.
|
||||
if (TER const ter = destroySignerList (index))
|
||||
return ter;
|
||||
|
||||
// Compute new reserve. Verify the account has funds to meet the reserve.
|
||||
std::size_t const oldOwnerCount = mTxnAccount->getFieldU32 (sfOwnerCount);
|
||||
std::size_t const addedOwnerCount = ownerCountDelta (signers_.size ());
|
||||
|
||||
std::uint64_t const newReserve =
|
||||
mEngine->getLedger ()->getReserve (oldOwnerCount + addedOwnerCount);
|
||||
|
||||
// We check the reserve against the starting balance because we want to
|
||||
// allow dipping into the reserve to pay fees. This behavior is consistent
|
||||
// with CreateTicket.
|
||||
if (mPriorBalance < newReserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
// Everything's ducky. Add the ltSIGNER_LIST to the ledger.
|
||||
SLE::pointer signerList (
|
||||
mEngine->view().entryCreate (ltSIGNER_LIST, index));
|
||||
writeSignersToLedger (signerList);
|
||||
|
||||
// Lambda for call to dirAdd.
|
||||
auto describer = [&] (SLE::ref sle, bool dummy)
|
||||
{
|
||||
Ledger::ownerDirDescriber (sle, dummy, mTxnAccountID);
|
||||
};
|
||||
|
||||
// Add the signer list to the account's directory.
|
||||
std::uint64_t hint;
|
||||
TER result = mEngine->view ().dirAdd (
|
||||
hint, getOwnerDirIndex (mTxnAccountID), index, describer);
|
||||
|
||||
if (m_journal.trace) m_journal.trace <<
|
||||
"Create signer list for account " <<
|
||||
mTxnAccountID << ": " << transHuman (result);
|
||||
|
||||
if (result != tesSUCCESS)
|
||||
return result;
|
||||
|
||||
signerList->setFieldU64 (sfOwnerNode, hint);
|
||||
|
||||
// If we succeeded, the new entry counts against the creator's reserve.
|
||||
mEngine->view ().increaseOwnerCount (mTxnAccount, addedOwnerCount);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
TER
|
||||
SetSignerList::destroySignerList (uint256 const& index)
|
||||
{
|
||||
// See if there's an ltSIGNER_LIST for this account.
|
||||
SLE::pointer signerList =
|
||||
mEngine->view ().entryCache (ltSIGNER_LIST, index);
|
||||
|
||||
// If the signer list doesn't exist we've already succeeded in deleting it.
|
||||
if (!signerList)
|
||||
return tesSUCCESS;
|
||||
|
||||
// We have to examine the current SignerList so we know how much to
|
||||
// reduce the OwnerCount.
|
||||
std::size_t removeFromOwnerCount = 0;
|
||||
uint256 const signerListIndex = getSignerListIndex (mTxnAccountID);
|
||||
SLE::pointer accountSignersList =
|
||||
mEngine->view ().entryCache (ltSIGNER_LIST, signerListIndex);
|
||||
if (accountSignersList)
|
||||
{
|
||||
STArray const& actualList =
|
||||
accountSignersList->getFieldArray (sfSignerEntries);
|
||||
removeFromOwnerCount = ownerCountDelta (actualList.size ());
|
||||
}
|
||||
|
||||
// Remove the node from the account directory.
|
||||
std::uint64_t const hint (signerList->getFieldU64 (sfOwnerNode));
|
||||
|
||||
TER const result = mEngine->view ().dirDelete (false, hint,
|
||||
getOwnerDirIndex (mTxnAccountID), index, false, (hint == 0));
|
||||
|
||||
if (result == tesSUCCESS)
|
||||
mEngine->view ().decreaseOwnerCount (mTxnAccount, removeFromOwnerCount);
|
||||
|
||||
mEngine->view ().entryDelete (signerList);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
void
|
||||
SetSignerList::writeSignersToLedger (SLE::pointer ledgerEntry)
|
||||
{
|
||||
// Assign the quorum.
|
||||
ledgerEntry->setFieldU32 (sfSignerQuorum, quorum_);
|
||||
|
||||
// Create the SignerListArray one SignerEntry at a time.
|
||||
STArray toLedger (signers_.size ());
|
||||
for (auto const& entry : signers_)
|
||||
{
|
||||
toLedger.emplace_back(sfSignerEntry);
|
||||
STObject& obj = toLedger.back();
|
||||
obj.reserve (2);
|
||||
obj.setFieldAccount (sfAccount, entry.account);
|
||||
obj.setFieldU16 (sfSignerWeight, entry.weight);
|
||||
}
|
||||
|
||||
// Assign the SignerEntries.
|
||||
ledgerEntry->setFieldArray (sfSignerEntries, toLedger);
|
||||
}
|
||||
|
||||
std::size_t
|
||||
SetSignerList::ownerCountDelta (std::size_t entryCount)
|
||||
{
|
||||
// We always compute the full change in OwnerCount, taking into account:
|
||||
// o The fact that we're adding/removing a SignerList and
|
||||
// o Accounting for the number of entries in the list.
|
||||
// We can get away with that because lists are not adjusted incrementally;
|
||||
// we add or remove an entire list.
|
||||
|
||||
// The wiki (https://wiki.ripple.com/Multisign#Fees_2) currently says
|
||||
// (December 2014) the reserve should be
|
||||
// Reserve * (N + 1) / 2
|
||||
// That's not making sense to me right now, since I'm working in
|
||||
// integral OwnerCount units. If, say, N is 4 I don't know how to return
|
||||
// 4.5 units as an integer.
|
||||
//
|
||||
// So, just to get started, I'm saying that:
|
||||
// o Simply having a SignerList costs 2 OwnerCount units.
|
||||
// o And each signer in the list costs 1 more OwnerCount unit.
|
||||
// So, at a minimum, adding a SignerList with 2 entries costs 4 OwnerCount
|
||||
// units. A SignerList with 8 entries would cost 10 OwnerCount units.
|
||||
//
|
||||
// It's worth noting that once this reserve policy has gotten into the
|
||||
// wild it will be very difficult to change. So think hard about what
|
||||
// we want for the long term.
|
||||
return 2 + entryCount;
|
||||
}
|
||||
|
||||
TER
|
||||
transact_SetSignerList (
|
||||
STTx const& txn,
|
||||
TransactionEngineParams params,
|
||||
TransactionEngine* engine)
|
||||
{
|
||||
return SetSignerList (txn, params, engine).apply ();
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
67
src/ripple/app/tx/impl/SignerEntries.cpp
Normal file
67
src/ripple/app/tx/impl/SignerEntries.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <BeastConfig.h>
|
||||
#include <ripple/app/tx/impl/SignerEntries.h>
|
||||
#include <ripple/protocol/STObject.h>
|
||||
#include <ripple/protocol/STArray.h>
|
||||
#include <ripple/protocol/STAccount.h>
|
||||
#include <cstdint>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
SignerEntries::Decoded
|
||||
SignerEntries::deserialize (
|
||||
STObject const& obj, beast::Journal journal, std::string const& annotation)
|
||||
{
|
||||
Decoded s;
|
||||
auto& accountVec (s.vec);
|
||||
accountVec.reserve (maxEntries);
|
||||
|
||||
if (!obj.isFieldPresent (sfSignerEntries))
|
||||
{
|
||||
if (journal.trace) journal.trace <<
|
||||
"Malformed " << annotation << ": Need signer entry array.";
|
||||
s.ter = temMALFORMED;
|
||||
return s;
|
||||
}
|
||||
|
||||
STArray const& sEntries (obj.getFieldArray (sfSignerEntries));
|
||||
for (STObject const& sEntry : sEntries)
|
||||
{
|
||||
// Validate the SignerEntry.
|
||||
if (sEntry.getFName () != sfSignerEntry)
|
||||
{
|
||||
journal.trace <<
|
||||
"Malformed " << annotation << ": Expected SignerEntry.";
|
||||
s.ter = temMALFORMED;
|
||||
return s;
|
||||
}
|
||||
|
||||
// Extract SignerEntry fields.
|
||||
Account const account = sEntry.getFieldAccount160 (sfAccount);
|
||||
std::uint16_t const weight = sEntry.getFieldU16 (sfSignerWeight);
|
||||
accountVec.emplace_back (account, weight);
|
||||
}
|
||||
|
||||
s.ter = tesSUCCESS;
|
||||
return s;
|
||||
}
|
||||
|
||||
} // ripple
|
||||
77
src/ripple/app/tx/impl/SignerEntries.h
Normal file
77
src/ripple/app/tx/impl/SignerEntries.h
Normal file
@@ -0,0 +1,77 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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_TX_IMPL_SIGNER_ENTRIES_H_INCLUDED
|
||||
#define RIPPLE_TX_IMPL_SIGNER_ENTRIES_H_INCLUDED
|
||||
|
||||
#include <ripple/protocol/STTx.h> // STTx::maxMultiSigners
|
||||
#include <ripple/protocol/UintTypes.h> // Account
|
||||
#include <ripple/protocol/TER.h> // temMALFORMED
|
||||
#include <beast/utility/Journal.h> // beast::Journal
|
||||
|
||||
namespace ripple {
|
||||
|
||||
// Forward declarations
|
||||
class STObject;
|
||||
|
||||
// Support for SignerEntries that is needed by a few Transactors
|
||||
class SignerEntries
|
||||
{
|
||||
public:
|
||||
struct SignerEntry
|
||||
{
|
||||
Account account;
|
||||
std::uint16_t weight;
|
||||
|
||||
SignerEntry (Account const& inAccount, std::uint16_t inWeight)
|
||||
: account (inAccount)
|
||||
, weight (inWeight)
|
||||
{ }
|
||||
|
||||
// For sorting to look for duplicate accounts
|
||||
friend bool operator< (SignerEntry const& lhs, SignerEntry const& rhs)
|
||||
{
|
||||
return lhs.account < rhs.account;
|
||||
}
|
||||
|
||||
friend bool operator== (SignerEntry const& lhs, SignerEntry const& rhs)
|
||||
{
|
||||
return lhs.account == rhs.account;
|
||||
}
|
||||
};
|
||||
|
||||
struct Decoded
|
||||
{
|
||||
std::vector<SignerEntry> vec;
|
||||
TER ter = temMALFORMED;
|
||||
};
|
||||
|
||||
// Deserialize a SignerEntries array from the network or from the ledger.
|
||||
static Decoded deserialize (
|
||||
STObject const& obj,
|
||||
beast::Journal journal,
|
||||
std::string const& annotation);
|
||||
|
||||
static std::size_t const minEntries = 2;
|
||||
static std::size_t const maxEntries = STTx::maxMultiSigners;
|
||||
};
|
||||
|
||||
} // ripple
|
||||
|
||||
#endif // RIPPLE_TX_IMPL_SIGNER_ENTRIES_H_INCLUDED
|
||||
@@ -18,8 +18,8 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/core/Config.h>
|
||||
#include <ripple/app/tx/impl/Transactor.h>
|
||||
#include <ripple/core/Config.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
|
||||
namespace ripple {
|
||||
@@ -33,6 +33,7 @@ TER transact_CancelOffer (STTx const& txn, TransactionEngineParams params, Trans
|
||||
TER transact_Change (STTx const& txn, TransactionEngineParams params, TransactionEngine* engine);
|
||||
TER transact_CreateTicket (STTx const& txn, TransactionEngineParams params, TransactionEngine* engine);
|
||||
TER transact_CancelTicket (STTx const& txn, TransactionEngineParams params, TransactionEngine* engine);
|
||||
TER transact_SetSignerList (STTx const& txn, TransactionEngineParams params, TransactionEngine* engine);
|
||||
|
||||
TER
|
||||
Transactor::transact (
|
||||
@@ -70,6 +71,13 @@ Transactor::transact (
|
||||
case ttTICKET_CANCEL:
|
||||
return transact_CancelTicket (txn, params, engine);
|
||||
|
||||
#if RIPPLE_ENABLE_MULTI_SIGN
|
||||
|
||||
case ttSIGNER_LIST_SET:
|
||||
return transact_SetSignerList (txn, params, engine);
|
||||
|
||||
#endif // RIPPLE_ENABLE_MULTI_SIGN
|
||||
|
||||
default:
|
||||
return temUNKNOWN;
|
||||
}
|
||||
@@ -219,6 +227,15 @@ TER Transactor::checkSeq ()
|
||||
|
||||
// check stuff before you bother to lock the ledger
|
||||
TER Transactor::preCheck ()
|
||||
{
|
||||
TER result = preCheckAccount ();
|
||||
if (result != tesSUCCESS)
|
||||
return result;
|
||||
|
||||
return preCheckSigningKey ();
|
||||
}
|
||||
|
||||
TER Transactor::preCheckAccount ()
|
||||
{
|
||||
mTxnAccountID = mTxn.getSourceAccount ().getAccountID ();
|
||||
|
||||
@@ -227,14 +244,19 @@ TER Transactor::preCheck ()
|
||||
m_journal.warning << "applyTransaction: bad transaction source id";
|
||||
return temBAD_SRC_ACCOUNT;
|
||||
}
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER Transactor::preCheckSigningKey ()
|
||||
{
|
||||
// Extract signing key
|
||||
// Transactions contain a signing key. This allows us to trivially verify a
|
||||
// transaction has at least been properly signed without going to disk.
|
||||
// Each transaction also notes a source account id. This is used to verify
|
||||
// that the signing key is associated with the account.
|
||||
// XXX This could be a lot cleaner to prevent unnecessary copying.
|
||||
mSigningPubKey = RippleAddress::createAccountPublic (mTxn.getSigningPubKey ());
|
||||
mSigningPubKey =
|
||||
RippleAddress::createAccountPublic (mTxn.getSigningPubKey ());
|
||||
|
||||
// Consistency: really signed.
|
||||
if (!mTxn.isKnownGood ())
|
||||
|
||||
@@ -54,6 +54,11 @@ protected:
|
||||
beast::Journal m_journal;
|
||||
|
||||
virtual TER preCheck ();
|
||||
|
||||
// Non-virtual components of preCheck()
|
||||
TER preCheckAccount ();
|
||||
TER preCheckSigningKey ();
|
||||
|
||||
virtual TER checkSeq ();
|
||||
virtual TER payFee ();
|
||||
|
||||
|
||||
@@ -87,6 +87,8 @@ getRippleStateIndex (Account const& a, Account const& b, Currency const& currenc
|
||||
uint256
|
||||
getRippleStateIndex (Account const& a, Issue const& issue);
|
||||
|
||||
uint256
|
||||
getSignerListIndex (Account const& account);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
29
src/ripple/unity/app9.cpp → src/ripple/protocol/InnerObjectFormats.h
Normal file → Executable file
29
src/ripple/unity/app9.cpp → src/ripple/protocol/InnerObjectFormats.h
Normal file → Executable file
@@ -17,4 +17,31 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#ifndef RIPPLE_PROTOCOL_INNER_OBJECT_FORMATS_H_INCLUDED
|
||||
#define RIPPLE_PROTOCOL_INNER_OBJECT_FORMATS_H_INCLUDED
|
||||
|
||||
#include <ripple/protocol/KnownFormats.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
/** Manages the list of known inner object formats.
|
||||
*/
|
||||
class InnerObjectFormats : public KnownFormats <int>
|
||||
{
|
||||
private:
|
||||
void addCommonFields (Item& item);
|
||||
|
||||
public:
|
||||
/** Create the object.
|
||||
This will load the object will all the known inner object formats.
|
||||
*/
|
||||
InnerObjectFormats ();
|
||||
|
||||
static InnerObjectFormats const& getInstance ();
|
||||
|
||||
SOTemplate const* findSOTemplateBySField (SField const& sField) const;
|
||||
};
|
||||
|
||||
} // ripple
|
||||
|
||||
#endif
|
||||
@@ -56,6 +56,8 @@ enum LedgerEntryType
|
||||
|
||||
ltTICKET = 'T',
|
||||
|
||||
ltSIGNER_LIST = 'S',
|
||||
|
||||
/* Deprecated. */
|
||||
ltOFFER = 'o',
|
||||
|
||||
@@ -90,6 +92,7 @@ enum LedgerNameSpace
|
||||
spaceAmendment = 'f',
|
||||
spaceFee = 'e',
|
||||
spaceTicket = 'T',
|
||||
spaceSignerList = 'S',
|
||||
|
||||
// No longer used or supported. Left here to reserve the space and
|
||||
// avoid accidental reuse of the space.
|
||||
|
||||
@@ -295,6 +295,7 @@ extern SField const sfTransactionResult;
|
||||
// 16-bit integers
|
||||
extern SField const sfLedgerEntryType;
|
||||
extern SField const sfTransactionType;
|
||||
extern SField const sfSignerWeight;
|
||||
|
||||
// 32-bit integers (common)
|
||||
extern SField const sfFlags;
|
||||
@@ -331,6 +332,7 @@ extern SField const sfReserveBase;
|
||||
extern SField const sfReserveIncrement;
|
||||
extern SField const sfSetFlag;
|
||||
extern SField const sfClearFlag;
|
||||
extern SField const sfSignerQuorum;
|
||||
|
||||
// 64-bit integers
|
||||
extern SField const sfIndexNext;
|
||||
@@ -427,12 +429,13 @@ extern SField const sfFinalFields;
|
||||
extern SField const sfNewFields;
|
||||
extern SField const sfTemplateEntry;
|
||||
extern SField const sfMemo;
|
||||
extern SField const sfSignerEntry;
|
||||
|
||||
// array of objects
|
||||
// ARRAY/1 is reserved for end of array
|
||||
extern SField const sfSigningAccounts;
|
||||
extern SField const sfTxnSignatures;
|
||||
extern SField const sfSignatures;
|
||||
extern SField const sfSignerEntries;
|
||||
extern SField const sfTemplate;
|
||||
extern SField const sfNecessary;
|
||||
extern SField const sfSufficient;
|
||||
|
||||
@@ -40,7 +40,7 @@ private:
|
||||
list_type v_;
|
||||
|
||||
public:
|
||||
// Read-only iteration
|
||||
// Read-only iteration
|
||||
class Items;
|
||||
|
||||
static char const* getCountedObjectName () { return "STArray"; }
|
||||
@@ -135,6 +135,10 @@ public:
|
||||
{
|
||||
v_.clear ();
|
||||
}
|
||||
void reserve (std::size_t n)
|
||||
{
|
||||
v_.reserve (n);
|
||||
}
|
||||
void swap (STArray & a) noexcept
|
||||
{
|
||||
v_.swap (a.v_);
|
||||
|
||||
@@ -133,7 +133,18 @@ public:
|
||||
return v_.empty();
|
||||
}
|
||||
|
||||
void reserve (std::size_t n)
|
||||
{
|
||||
v_.reserve (n);
|
||||
}
|
||||
|
||||
bool setType (const SOTemplate & type);
|
||||
|
||||
enum ResultOfSetTypeFromSField : unsigned char
|
||||
{typeSetFail, typeIsSet, noTemplate};
|
||||
|
||||
ResultOfSetTypeFromSField setTypeFromSField (SField const&);
|
||||
|
||||
bool isValidForType ();
|
||||
bool isFieldAllowed (SField const&);
|
||||
bool isFree () const
|
||||
|
||||
@@ -44,6 +44,8 @@ public:
|
||||
typedef std::shared_ptr<STTx> pointer;
|
||||
typedef const std::shared_ptr<STTx>& ref;
|
||||
|
||||
static std::size_t const maxMultiSigners = 8;
|
||||
|
||||
public:
|
||||
STTx () = delete;
|
||||
STTx& operator= (STTx const& other) = delete;
|
||||
|
||||
@@ -81,6 +81,8 @@ enum TER // aka TransactionEngineResult
|
||||
temREDUNDANT,
|
||||
temRIPPLE_EMPTY,
|
||||
temDISABLED,
|
||||
temBAD_SIGNER,
|
||||
temBAD_QUORUM,
|
||||
|
||||
// An intermediate result used internally, should never be returned.
|
||||
temUNCERTAIN,
|
||||
|
||||
@@ -46,6 +46,7 @@ enum TxType
|
||||
no_longer_used = 9,
|
||||
ttTICKET_CREATE = 10,
|
||||
ttTICKET_CANCEL = 11,
|
||||
ttSIGNER_LIST_SET = 12,
|
||||
|
||||
ttTRUST_SET = 20,
|
||||
|
||||
|
||||
@@ -219,4 +219,15 @@ getRippleStateIndex (Account const& a, Issue const& issue)
|
||||
return getRippleStateIndex (a, issue.account, issue.currency);
|
||||
}
|
||||
|
||||
uint256
|
||||
getSignerListIndex (Account const& account)
|
||||
{
|
||||
Serializer s (22);
|
||||
|
||||
s.add16 (spaceSignerList); // 2
|
||||
s.add160 (account); // 20
|
||||
|
||||
return s.getSHA512Half ();
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
55
src/ripple/protocol/impl/InnerObjectFormats.cpp
Executable file
55
src/ripple/protocol/impl/InnerObjectFormats.cpp
Executable file
@@ -0,0 +1,55 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <BeastConfig.h>
|
||||
#include <ripple/protocol/InnerObjectFormats.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
InnerObjectFormats::InnerObjectFormats ()
|
||||
{
|
||||
add (sfSignerEntry.getJsonName ().c_str (), sfSignerEntry.getCode ())
|
||||
<< SOElement (sfAccount, SOE_REQUIRED)
|
||||
<< SOElement (sfSignerWeight, SOE_REQUIRED)
|
||||
;
|
||||
}
|
||||
|
||||
void InnerObjectFormats::addCommonFields (Item& item)
|
||||
{
|
||||
}
|
||||
|
||||
InnerObjectFormats const&
|
||||
InnerObjectFormats::getInstance ()
|
||||
{
|
||||
static InnerObjectFormats instance;
|
||||
return instance;
|
||||
}
|
||||
|
||||
SOTemplate const*
|
||||
InnerObjectFormats::findSOTemplateBySField (SField const& sField) const
|
||||
{
|
||||
SOTemplate const* ret = nullptr;
|
||||
auto itemPtr = findByType (sField.getCode ());
|
||||
if (itemPtr)
|
||||
ret = &(itemPtr->elements);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
} // ripple
|
||||
@@ -105,6 +105,14 @@ LedgerFormats::LedgerFormats ()
|
||||
<< SOElement (sfTarget, SOE_OPTIONAL)
|
||||
<< SOElement (sfExpiration, SOE_OPTIONAL)
|
||||
;
|
||||
|
||||
// All three fields are SOE_REQUIRED because there is always a
|
||||
// SignerEntries. If there are no SignerEntries the node is deleted.
|
||||
add ("SignerList", ltSIGNER_LIST)
|
||||
<< SOElement (sfOwnerNode, SOE_REQUIRED)
|
||||
<< SOElement (sfSignerQuorum, SOE_REQUIRED)
|
||||
<< SOElement (sfSignerEntries, SOE_REQUIRED)
|
||||
;
|
||||
}
|
||||
|
||||
void LedgerFormats::addCommonFields (Item& item)
|
||||
|
||||
@@ -83,6 +83,7 @@ SField const sfTransactionResult = make::one(&sfTransactionResult, STI_UINT8, 3,
|
||||
// 16-bit integers
|
||||
SField const sfLedgerEntryType = make::one(&sfLedgerEntryType, STI_UINT16, 1, "LedgerEntryType", SField::sMD_Never);
|
||||
SField const sfTransactionType = make::one(&sfTransactionType, STI_UINT16, 2, "TransactionType");
|
||||
SField const sfSignerWeight = make::one(&sfSignerWeight, STI_UINT16, 3, "SignerWeight");
|
||||
|
||||
// 32-bit integers (common)
|
||||
SField const sfFlags = make::one(&sfFlags, STI_UINT32, 2, "Flags");
|
||||
@@ -119,6 +120,7 @@ SField const sfReserveBase = make::one(&sfReserveBase, STI_UINT3
|
||||
SField const sfReserveIncrement = make::one(&sfReserveIncrement, STI_UINT32, 32, "ReserveIncrement");
|
||||
SField const sfSetFlag = make::one(&sfSetFlag, STI_UINT32, 33, "SetFlag");
|
||||
SField const sfClearFlag = make::one(&sfClearFlag, STI_UINT32, 34, "ClearFlag");
|
||||
SField const sfSignerQuorum = make::one(&sfSignerQuorum, STI_UINT32, 35, "SignerQuorum");
|
||||
|
||||
// 64-bit integers
|
||||
SField const sfIndexNext = make::one(&sfIndexNext, STI_UINT64, 1, "IndexNext");
|
||||
@@ -215,12 +217,13 @@ SField const sfFinalFields = make::one(&sfFinalFields, STI_OBJEC
|
||||
SField const sfNewFields = make::one(&sfNewFields, STI_OBJECT, 8, "NewFields");
|
||||
SField const sfTemplateEntry = make::one(&sfTemplateEntry, STI_OBJECT, 9, "TemplateEntry");
|
||||
SField const sfMemo = make::one(&sfMemo, STI_OBJECT, 10, "Memo");
|
||||
SField const sfSignerEntry = make::one(&sfSignerEntry, STI_OBJECT, 11, "SignerEntry");
|
||||
|
||||
// array of objects
|
||||
// ARRAY/1 is reserved for end of array
|
||||
SField const sfSigningAccounts = make::one(&sfSigningAccounts, STI_ARRAY, 2, "SigningAccounts");
|
||||
SField const sfTxnSignatures = make::one(&sfTxnSignatures, STI_ARRAY, 3, "TxnSignatures", SField::sMD_Default, SField::notSigning);
|
||||
SField const sfSignatures = make::one(&sfSignatures, STI_ARRAY, 4, "Signatures");
|
||||
SField const sfSignerEntries = make::one(&sfSignerEntries, STI_ARRAY, 4, "SignerEntries");
|
||||
SField const sfTemplate = make::one(&sfTemplate, STI_ARRAY, 5, "Template");
|
||||
SField const sfNecessary = make::one(&sfNecessary, STI_ARRAY, 6, "Necessary");
|
||||
SField const sfSufficient = make::one(&sfSufficient, STI_ARRAY, 7, "Sufficient");
|
||||
|
||||
@@ -97,6 +97,11 @@ STArray::STArray (SerialIter& sit, SField const& f)
|
||||
|
||||
v_.emplace_back(fn);
|
||||
v_.back().set (sit, 1);
|
||||
|
||||
if (v_.back().setTypeFromSField (fn) == STObject::typeSetFail)
|
||||
{
|
||||
throw std::runtime_error ("Malformed object in array");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#include <ripple/basics/Log.h>
|
||||
#include <ripple/json/json_reader.h>
|
||||
#include <ripple/json/to_string.h>
|
||||
#include <ripple/protocol/InnerObjectFormats.h>
|
||||
#include <ripple/protocol/STBase.h>
|
||||
#include <ripple/protocol/STAccount.h>
|
||||
#include <ripple/protocol/STArray.h>
|
||||
@@ -119,7 +120,7 @@ bool STObject::setType (const SOTemplate& type)
|
||||
{
|
||||
WriteLog (lsWARNING, STObject) <<
|
||||
"setType( " << getFName ().getName () <<
|
||||
") invalid default " << e->e_field.fieldName;
|
||||
" ) invalid default " << e->e_field.fieldName;
|
||||
valid = false;
|
||||
}
|
||||
v.emplace_back(std::move(*iter));
|
||||
@@ -131,7 +132,7 @@ bool STObject::setType (const SOTemplate& type)
|
||||
{
|
||||
WriteLog (lsWARNING, STObject) <<
|
||||
"setType( " << getFName ().getName () <<
|
||||
") invalid missing " << e->e_field.fieldName;
|
||||
" ) invalid missing " << e->e_field.fieldName;
|
||||
valid = false;
|
||||
}
|
||||
v.emplace_back(detail::nonPresentObject, e->e_field);
|
||||
@@ -144,7 +145,7 @@ bool STObject::setType (const SOTemplate& type)
|
||||
{
|
||||
WriteLog (lsWARNING, STObject) <<
|
||||
"setType( " << getFName ().getName () <<
|
||||
") invalid leftover " << e->getFName ().getName ();
|
||||
" ) invalid leftover " << e->getFName ().getName ();
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
@@ -154,6 +155,20 @@ bool STObject::setType (const SOTemplate& type)
|
||||
return valid;
|
||||
}
|
||||
|
||||
STObject::ResultOfSetTypeFromSField
|
||||
STObject::setTypeFromSField (SField const& sField)
|
||||
{
|
||||
ResultOfSetTypeFromSField ret = noTemplate;
|
||||
|
||||
SOTemplate const* elements =
|
||||
InnerObjectFormats::getInstance ().findSOTemplateBySField (sField);
|
||||
if (elements)
|
||||
{
|
||||
ret = setType (*elements) ? typeIsSet : typeSetFail;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool STObject::isValidForType ()
|
||||
{
|
||||
auto it = v_.begin();
|
||||
@@ -219,6 +234,13 @@ bool STObject::set (SerialIter& sit, int depth)
|
||||
|
||||
// Unflatten the field
|
||||
v_.emplace_back(sit, fn);
|
||||
|
||||
// If the object type has a known SOTemplate then set it.
|
||||
STObject* const obj = dynamic_cast <STObject*> (&(v_.back().get()));
|
||||
if (obj && (obj->setTypeFromSField (fn) == typeSetFail))
|
||||
{
|
||||
throw std::runtime_error ("field deserialization error");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -138,6 +138,26 @@ static Json::Value singleton_expected (std::string const& object,
|
||||
"]' must be an object with a single key/object value.");
|
||||
}
|
||||
|
||||
static Json::Value serialization_error (SField const& sField)
|
||||
{
|
||||
return RPC::make_error (rpcINVALID_PARAMS,
|
||||
"Object '" + sField.getName () + "' failed to serialize.");
|
||||
}
|
||||
|
||||
static Json::Value template_mismatch (SField const& sField)
|
||||
{
|
||||
return RPC::make_error (rpcINVALID_PARAMS,
|
||||
"Object '" + sField.getName () +
|
||||
"' contents did not meet requirements for that type.");
|
||||
}
|
||||
|
||||
static Json::Value
|
||||
non_object_in_array (std::string const& item, Json::UInt index)
|
||||
{
|
||||
return RPC::make_error (rpcINVALID_PARAMS,
|
||||
"Item '" + item + "' at index " + std::to_string (index) +
|
||||
" is not an object. Arrays may only contain objects.");
|
||||
}
|
||||
|
||||
// This function is used by parseObject to parse any JSON type that doesn't
|
||||
// recurse. Everything represented here is a leaf-type.
|
||||
@@ -766,6 +786,13 @@ static boost::optional <STObject> parseObject (
|
||||
}
|
||||
}
|
||||
|
||||
// Some inner object types have templates. Attempt to apply that.
|
||||
if (data.setTypeFromSField (inName) == STObject::typeSetFail)
|
||||
{
|
||||
error = template_mismatch (inName);
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
return std::move (data);
|
||||
}
|
||||
|
||||
@@ -823,10 +850,17 @@ static boost::optional <detail::STVar> parseArray (
|
||||
|
||||
auto ret = parseObject (ss.str (), objectFields,
|
||||
nameField, depth + 1, error);
|
||||
if (! ret)
|
||||
{
|
||||
std::string errMsg = error["error_message"].asString ();
|
||||
error["error_message"] = "Error at '" + ss.str () +
|
||||
"'. " + errMsg;
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
if (! ret ||
|
||||
(ret->getFName().fieldType != STI_OBJECT))
|
||||
if (ret->getFName().fieldType != STI_OBJECT)
|
||||
{
|
||||
error = non_object_in_array (ss.str(), i);
|
||||
return boost::none;
|
||||
}
|
||||
|
||||
|
||||
@@ -102,6 +102,7 @@ bool transResultInfo (TER code, std::string& token, std::string& text)
|
||||
{ temBAD_OFFER, "temBAD_OFFER", "Malformed: Bad offer." },
|
||||
{ temBAD_PATH, "temBAD_PATH", "Malformed: Bad path." },
|
||||
{ temBAD_PATH_LOOP, "temBAD_PATH_LOOP", "Malformed: Loop in path." },
|
||||
{ temBAD_QUORUM, "temBAD_QUORUM", "Malformed: Quorum is unreachable." },
|
||||
{ temBAD_SEND_XRP_LIMIT, "temBAD_SEND_XRP_LIMIT", "Malformed: Limit quality is not allowed for XRP to XRP." },
|
||||
{ temBAD_SEND_XRP_MAX, "temBAD_SEND_XRP_MAX", "Malformed: Send max is not allowed for XRP to XRP." },
|
||||
{ temBAD_SEND_XRP_NO_DIRECT,"temBAD_SEND_XRP_NO_DIRECT","Malformed: No Ripple direct is not allowed for XRP to XRP." },
|
||||
@@ -109,6 +110,7 @@ bool transResultInfo (TER code, std::string& token, std::string& text)
|
||||
{ temBAD_SEND_XRP_PATHS, "temBAD_SEND_XRP_PATHS", "Malformed: Paths are not allowed for XRP to XRP." },
|
||||
{ temBAD_SEQUENCE, "temBAD_SEQUENCE", "Malformed: Sequence is not in the past." },
|
||||
{ temBAD_SIGNATURE, "temBAD_SIGNATURE", "Malformed: Bad signature." },
|
||||
{ temBAD_SIGNER, "temBAD_SIGNER", "Malformed: A signer may not duplicate account or other signers"},
|
||||
{ temBAD_SRC_ACCOUNT, "temBAD_SRC_ACCOUNT", "Malformed: Bad source account." },
|
||||
{ temBAD_TRANSFER_RATE, "temBAD_TRANSFER_RATE", "Malformed: Transfer rate must be >= 1.0" },
|
||||
{ temDST_IS_SRC, "temDST_IS_SRC", "Destination may not be source." },
|
||||
|
||||
@@ -87,6 +87,13 @@ TxFormats::TxFormats ()
|
||||
add ("TicketCancel", ttTICKET_CANCEL)
|
||||
<< SOElement (sfTicketID, SOE_REQUIRED)
|
||||
;
|
||||
|
||||
// The SignerEntries are optional because a SignerList is deleted by
|
||||
// setting the SignerQuorum to zero and omitting SignerEntries.
|
||||
add ("SignerListSet", ttSIGNER_LIST_SET)
|
||||
<< SOElement (sfSignerQuorum, SOE_REQUIRED)
|
||||
<< SOElement (sfSignerEntries, SOE_OPTIONAL)
|
||||
;
|
||||
}
|
||||
|
||||
void TxFormats::addCommonFields (Item& item)
|
||||
|
||||
204
src/ripple/protocol/tests/InnerObjectFormats.test.cpp
Executable file
204
src/ripple/protocol/tests/InnerObjectFormats.test.cpp
Executable file
@@ -0,0 +1,204 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <BeastConfig.h>
|
||||
#include <ripple/protocol/InnerObjectFormats.h>
|
||||
#include <ripple/protocol/ErrorCodes.h> // RPC::containsError
|
||||
#include <ripple/json/json_reader.h> // Json::Reader
|
||||
#include <ripple/protocol/STParsedJSON.h> // STParsedJSONObject
|
||||
#include <beast/unit_test/suite.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
namespace InnerObjectFormatsUnitTestDetail
|
||||
{
|
||||
|
||||
struct TestJSONTxt
|
||||
{
|
||||
std::string const txt;
|
||||
bool const expectFail;
|
||||
};
|
||||
|
||||
static TestJSONTxt const testArray[] =
|
||||
{
|
||||
|
||||
// Valid SignerEntry
|
||||
{R"({
|
||||
"Account" : "rDg53Haik2475DJx8bjMDSDPj4VX7htaMd",
|
||||
"SignerEntries" :
|
||||
[
|
||||
{
|
||||
"SignerEntry" :
|
||||
{
|
||||
"Account" : "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
|
||||
"SignerWeight" : 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"SignerEntry" :
|
||||
{
|
||||
"Account" : "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
|
||||
"SignerWeight" : 3
|
||||
}
|
||||
}
|
||||
],
|
||||
"SignerQuorum" : 7,
|
||||
"TransactionType" : "SignerListSet"
|
||||
})", false
|
||||
},
|
||||
|
||||
// SignerEntry missing Account
|
||||
{R"({
|
||||
"Account" : "rDg53Haik2475DJx8bjMDSDPj4VX7htaMd",
|
||||
"SignerEntries" :
|
||||
[
|
||||
{
|
||||
"SignerEntry" :
|
||||
{
|
||||
"Account" : "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
|
||||
"SignerWeight" : 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"SignerEntry" :
|
||||
{
|
||||
"SignerWeight" : 3
|
||||
}
|
||||
}
|
||||
],
|
||||
"SignerQuorum" : 7,
|
||||
"TransactionType" : "SignerListSet"
|
||||
})", true
|
||||
},
|
||||
|
||||
// SignerEntry missing SignerWeight
|
||||
{R"({
|
||||
"Account" : "rDg53Haik2475DJx8bjMDSDPj4VX7htaMd",
|
||||
"SignerEntries" :
|
||||
[
|
||||
{
|
||||
"SignerEntry" :
|
||||
{
|
||||
"Account" : "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
|
||||
"SignerWeight" : 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"SignerEntry" :
|
||||
{
|
||||
"Account" : "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
|
||||
}
|
||||
}
|
||||
],
|
||||
"SignerQuorum" : 7,
|
||||
"TransactionType" : "SignerListSet"
|
||||
})", true
|
||||
},
|
||||
|
||||
// SignerEntry with unexpected Amount
|
||||
{R"({
|
||||
"Account" : "rDg53Haik2475DJx8bjMDSDPj4VX7htaMd",
|
||||
"SignerEntries" :
|
||||
[
|
||||
{
|
||||
"SignerEntry" :
|
||||
{
|
||||
"Account" : "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
|
||||
"SignerWeight" : 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"SignerEntry" :
|
||||
{
|
||||
"Amount" : "1000000",
|
||||
"Account" : "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
|
||||
"SignerWeight" : 3
|
||||
}
|
||||
}
|
||||
],
|
||||
"SignerQuorum" : 7,
|
||||
"TransactionType" : "SignerListSet"
|
||||
})", true
|
||||
},
|
||||
|
||||
// SignerEntry with no Account and unexpected Amount
|
||||
{R"({
|
||||
"Account" : "rDg53Haik2475DJx8bjMDSDPj4VX7htaMd",
|
||||
"SignerEntries" :
|
||||
[
|
||||
{
|
||||
"SignerEntry" :
|
||||
{
|
||||
"Account" : "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
|
||||
"SignerWeight" : 4
|
||||
}
|
||||
},
|
||||
{
|
||||
"SignerEntry" :
|
||||
{
|
||||
"Amount" : "10000000",
|
||||
"SignerWeight" : 3
|
||||
}
|
||||
}
|
||||
],
|
||||
"SignerQuorum" : 7,
|
||||
"TransactionType" : "SignerListSet"
|
||||
})", true
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
} // namespace InnerObjectFormatsUnitTestDetail
|
||||
|
||||
|
||||
class InnerObjectFormatsParsedJSON_test : public beast::unit_test::suite
|
||||
{
|
||||
public:
|
||||
void run()
|
||||
{
|
||||
using namespace InnerObjectFormatsUnitTestDetail;
|
||||
|
||||
for (auto const& test : testArray)
|
||||
{
|
||||
Json::Value req;
|
||||
Json::Reader ().parse (test.txt, req);
|
||||
if (RPC::contains_error (req))
|
||||
{
|
||||
throw std::runtime_error (
|
||||
"Internal InnerObjectFormatsParsedJSON error. Bad JSON.");
|
||||
}
|
||||
STParsedJSONObject parsed ("request", req);
|
||||
bool const noObj = parsed.object == boost::none;
|
||||
if ( noObj == test.expectFail )
|
||||
{
|
||||
pass ();
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string errStr ("Unexpected STParsedJSON result on:\n");
|
||||
errStr += test.txt;
|
||||
fail (errStr);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(InnerObjectFormatsParsedJSON,ripple_app,ripple);
|
||||
|
||||
} // ripple
|
||||
@@ -18,6 +18,7 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <BeastConfig.h>
|
||||
#include <ripple/protocol/Indexes.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -60,12 +61,32 @@ Json::Value doAccountInfo (RPC::Context& context)
|
||||
if (!jvAccepted.empty ())
|
||||
return jvAccepted;
|
||||
|
||||
auto asAccepted = context.netOps.getAccountState (ledger, naAccount);
|
||||
AccountState::pointer asAccepted =
|
||||
context.netOps.getAccountState (ledger, naAccount);
|
||||
|
||||
if (asAccepted)
|
||||
{
|
||||
asAccepted->addJson (jvAccepted);
|
||||
result[jss::account_data] = jvAccepted;
|
||||
|
||||
// See if there's a SignerEntries for this account.
|
||||
Account const account = naAccount.getAccountID ();
|
||||
uint256 const signerListIndex = getSignerListIndex (account);
|
||||
SLE::pointer signerList = ledger->getSLEi (signerListIndex);
|
||||
|
||||
if (signerList)
|
||||
{
|
||||
// Return multi-signing information if there are multi-signers.
|
||||
static const Json::StaticString multiSignersName("multisigners");
|
||||
jvAccepted[multiSignersName] = signerList->getJson (0);
|
||||
Json::Value& multiSignerJson = jvAccepted[multiSignersName];
|
||||
|
||||
// Remove unwanted fields.
|
||||
multiSignerJson.removeMember (sfFlags.getName ());
|
||||
multiSignerJson.removeMember (sfLedgerEntryType.getName ());
|
||||
multiSignerJson.removeMember (sfOwnerNode.getName ());
|
||||
multiSignerJson.removeMember ("index");
|
||||
}
|
||||
result[jss::account_data] = jvAccepted;
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -44,5 +44,8 @@
|
||||
#include <ripple/app/tx/impl/CreateOffer.cpp>
|
||||
#include <ripple/app/tx/impl/CreateTicket.cpp>
|
||||
#include <ripple/app/tx/impl/CancelTicket.cpp>
|
||||
#include <ripple/app/tx/impl/SetSignerList.cpp>
|
||||
#include <ripple/app/tx/impl/SignerEntries.cpp>
|
||||
|
||||
#include <ripple/app/tx/tests/OfferStream.test.cpp>
|
||||
#include <ripple/app/tx/tests/Taker.test.cpp>
|
||||
|
||||
@@ -45,6 +45,7 @@
|
||||
#include <ripple/protocol/impl/STLedgerEntry.cpp>
|
||||
#include <ripple/protocol/impl/STObject.cpp>
|
||||
#include <ripple/protocol/impl/STParsedJSON.cpp>
|
||||
#include <ripple/protocol/impl/InnerObjectFormats.cpp>
|
||||
#include <ripple/protocol/impl/STPathSet.cpp>
|
||||
#include <ripple/protocol/impl/STTx.cpp>
|
||||
#include <ripple/protocol/impl/STValidation.cpp>
|
||||
@@ -53,6 +54,7 @@
|
||||
|
||||
|
||||
#include <ripple/protocol/tests/BuildInfo.test.cpp>
|
||||
#include <ripple/protocol/tests/InnerObjectFormats.test.cpp>
|
||||
#include <ripple/protocol/tests/Issue.test.cpp>
|
||||
#include <ripple/protocol/tests/Quality.test.cpp>
|
||||
#include <ripple/protocol/tests/RippleAddress.test.cpp>
|
||||
|
||||
Reference in New Issue
Block a user