Ticket issuing (RIPD-368):

* New CreateTicket transactor to create tickets
* New CancelTicket transactor to cancel tickets
* Ledger entries for tickets & associated functions
* First draft of M-of-N documentation
This commit is contained in:
Nik Bougalis
2014-09-04 15:12:59 -07:00
parent 889c0a0d0f
commit 39730fc13e
19 changed files with 472 additions and 43 deletions

View File

@@ -2389,6 +2389,9 @@
<ClCompile Include="..\..\src\ripple\module\app\transactors\CancelOffer.cpp">
<ExcludedFromBuild>True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\module\app\transactors\CancelTicket.cpp">
<ExcludedFromBuild>True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\module\app\transactors\Change.cpp">
<ExcludedFromBuild>True</ExcludedFromBuild>
</ClCompile>
@@ -2401,6 +2404,9 @@
<ClCompile Include="..\..\src\ripple\module\app\transactors\CreateOfferDirect.cpp">
<ExcludedFromBuild>True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\module\app\transactors\CreateTicket.cpp">
<ExcludedFromBuild>True</ExcludedFromBuild>
</ClCompile>
<ClCompile Include="..\..\src\ripple\module\app\transactors\Payment.cpp">
<ExcludedFromBuild>True</ExcludedFromBuild>
</ClCompile>

View File

@@ -3492,6 +3492,9 @@
<ClCompile Include="..\..\src\ripple\module\app\transactors\CancelOffer.cpp">
<Filter>ripple\module\app\transactors</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\module\app\transactors\CancelTicket.cpp">
<Filter>ripple\module\app\transactors</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\module\app\transactors\Change.cpp">
<Filter>ripple\module\app\transactors</Filter>
</ClCompile>
@@ -3504,6 +3507,9 @@
<ClCompile Include="..\..\src\ripple\module\app\transactors\CreateOfferDirect.cpp">
<Filter>ripple\module\app\transactors</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\module\app\transactors\CreateTicket.cpp">
<Filter>ripple\module\app\transactors</Filter>
</ClCompile>
<ClCompile Include="..\..\src\ripple\module\app\transactors\Payment.cpp">
<Filter>ripple\module\app\transactors</Filter>
</ClCompile>

View File

@@ -1758,7 +1758,7 @@ uint256 Ledger::getOfferIndex (Account const& account, std::uint32_t uSequence)
Serializer s (26);
s.add16 (spaceOffer); // 2
s.add160 (account); // 20
s.add160 (account); // 20
s.add32 (uSequence); // 4
return s.getSHA512Half ();
@@ -1791,6 +1791,18 @@ uint256 Ledger::getRippleStateIndex (
return s.getSHA512Half ();
}
uint256 Ledger::getTicketIndex (
Account const& account, std::uint32_t uSequence)
{
Serializer s (26);
s.add16 (spaceTicket); // 2
s.add160 (account); // 20
s.add32 (uSequence); // 4
return s.getSHA512Half ();
}
bool Ledger::walkLedger ()
{
std::vector <SHAMapMissingNode> missingNodes1;

View File

@@ -414,6 +414,13 @@ public:
Currency const& uTakerGetsCurrency, Account const& uTakerGetsIssuer,
const std::uint64_t & uRate);
//
// Tickets
//
static uint256 getTicketIndex (
Account const& account, std::uint32_t uSequence);
//
// Ripple functions : credit lines
//

View File

@@ -0,0 +1,92 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
namespace ripple {
class CancelTicket
: public Transactor
{
public:
CancelTicket (
SerializedTransaction const& txn,
TransactionEngineParams params,
TransactionEngine* engine)
: Transactor (
txn,
params,
engine,
deprecatedLogs().journal("CancelTicket"))
{
}
TER doApply () override
{
assert (mTxnAccount);
uint256 const ticketId = mTxn.getFieldH256 (sfTicketID);
SLE::pointer sleTicket = mEngine->view ().entryCache (ltTICKET, ticketId);
if (!sleTicket)
return tecNO_ENTRY;
Account const ticket_owner (sleTicket->getFieldAccount160 (sfAccount));
bool authorized (mTxnAccountID == ticket_owner);
// The target can also always remove a ticket
if (!authorized && sleTicket->isFieldPresent (sfTarget))
authorized = (mTxnAccountID == sleTicket->getFieldAccount160 (sfTarget));
// And finally, anyone can remove an expired ticket
if (!authorized && sleTicket->isFieldPresent (sfExpiration))
{
std::uint32_t const expiration = sleTicket->getFieldU32 (sfExpiration);
if (mEngine->getLedger ()->getParentCloseTimeNC () >= expiration)
authorized = true;
}
if (!authorized)
return tecNO_PERMISSION;
std::uint64_t const hint (sleTicket->getFieldU64 (sfOwnerNode));
TER const result = mEngine->view ().dirDelete (false, hint,
Ledger::getOwnerDirIndex (ticket_owner), ticketId, false, (hint == 0));
mEngine->view ().ownerCountAdjust (ticket_owner, -1);
mEngine->view ().entryDelete (sleTicket);
return result;
}
};
TER
transact_CancelTicket (
SerializedTransaction const& txn,
TransactionEngineParams params,
TransactionEngine* engine)
{
return CancelTicket (txn, params, engine).apply ();
}
}

View File

@@ -0,0 +1,28 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#ifndef RIPPLE_TX_CANCELTICKET_H_INCLUDED
#define RIPPLE_TX_CANCELTICKET_H_INCLUDED
namespace ripple {
}
#endif

View File

@@ -0,0 +1,129 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
namespace ripple {
class CreateTicket
: public Transactor
{
public:
CreateTicket (
SerializedTransaction const& txn,
TransactionEngineParams params,
TransactionEngine* engine)
: Transactor (
txn,
params,
engine,
deprecatedLogs().journal("CreateTicket"))
{
}
TER doApply () override
{
assert (mTxnAccount);
// A ticket counts against the reserve of the issuing account, but we check
// the starting balance because we want to allow dipping into the reserve to
// pay fees.
auto const accountReserve (mEngine->getLedger ()->getReserve (
mTxnAccount->getFieldU32 (sfOwnerCount) + 1));
if (mPriorBalance.getNValue () < accountReserve)
return tecINSUFFICIENT_RESERVE;
std::uint32_t expiration (0);
if (mTxn.isFieldPresent (sfExpiration))
{
expiration = mTxn.getFieldU32 (sfExpiration);
if (!expiration)
{
m_journal.warning <<
"Malformed ticket requestion: bad expiration";
return temBAD_EXPIRATION;
}
if (mEngine->getLedger ()->getParentCloseTimeNC () >= expiration)
return tesSUCCESS;
}
SLE::pointer sleTicket = mEngine->entryCreate (ltTICKET,
Ledger::getTicketIndex (mTxnAccountID, mTxn.getSequence ()));
sleTicket->setFieldAccount (sfAccount, mTxnAccountID);
sleTicket->setFieldU32 (sfSequence, mTxn.getSequence ());
if (expiration != 0)
sleTicket->setFieldU32 (sfExpiration, expiration);
if (mTxn.isFieldPresent (sfTarget))
{
Account const target_account (mTxn.getFieldAccount160 (sfTarget));
SLE::pointer sleTarget = mEngine->entryCache (ltACCOUNT_ROOT,
Ledger::getAccountRootIndex (target_account));
// Destination account does not exist.
if (!sleTarget)
return tecNO_TARGET;
// The issuing account is the default account to which the ticket
// applies so don't bother saving it if that's what's specified.
if (target_account != mTxnAccountID)
sleTicket->setFieldAccount (sfTarget, target_account);
}
std::uint64_t hint;
TER result = mEngine->view ().dirAdd (hint,
Ledger::getOwnerDirIndex (mTxnAccountID), sleTicket->getIndex (),
std::bind (&Ledger::ownerDirDescriber,
std::placeholders::_1, std::placeholders::_2,
mTxnAccountID));
m_journal.trace <<
"Creating ticket " << to_string (sleTicket->getIndex ()) <<
": " << transHuman (result);
if (result != tesSUCCESS)
return result;
sleTicket->setFieldU64(sfOwnerNode, hint);
// If we succeeded, the new entry counts agains the creator's reserve.
mEngine->view ().ownerCountAdjust (mTxnAccountID, 1, mTxnAccount);
return result;
}
};
TER
transact_CreateTicket (
SerializedTransaction const& txn,
TransactionEngineParams params,
TransactionEngine* engine)
{
return CreateTicket (txn, params, engine).apply ();
}
}

View File

@@ -0,0 +1,27 @@
//------------------------------------------------------------------------------
/*
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.
*/
//==============================================================================
#ifndef RIPPLE_TX_CREATETICKET_H_INCLUDED
#define RIPPLE_TX_CREATETICKET_H_INCLUDED
namespace ripple {
}
#endif

View File

@@ -29,6 +29,8 @@ TER transact_CreateOffer (SerializedTransaction const& txn, TransactionEnginePar
TER transact_CancelOffer (SerializedTransaction const& txn, TransactionEngineParams params, TransactionEngine* engine);
TER transact_AddWallet (SerializedTransaction const& txn, TransactionEngineParams params, TransactionEngine* engine);
TER transact_Change (SerializedTransaction const& txn, TransactionEngineParams params, TransactionEngine* engine);
TER transact_CreateTicket (SerializedTransaction const& txn, TransactionEngineParams params, TransactionEngine* engine);
TER transact_CancelTicket (SerializedTransaction const& txn, TransactionEngineParams params, TransactionEngine* engine);
TER
Transactor::transact (
@@ -63,6 +65,12 @@ Transactor::transact (
case ttFEE:
return transact_Change (txn, params, engine);
case ttTICKET_CREATE:
return transact_CreateTicket (txn, params, engine);
case ttTICKET_CANCEL:
return transact_CancelTicket (txn, params, engine);
default:
return temUNKNOWN;
}

View File

@@ -100,6 +100,14 @@ LedgerFormats::LedgerFormats ()
<< SOElement (sfReserveBase, SOE_REQUIRED)
<< SOElement (sfReserveIncrement, SOE_REQUIRED)
;
add ("Ticket", ltTICKET)
<< SOElement (sfAccount, SOE_REQUIRED)
<< SOElement (sfSequence, SOE_REQUIRED)
<< SOElement (sfOwnerNode, SOE_REQUIRED)
<< SOElement (sfTarget, SOE_OPTIONAL)
<< SOElement (sfExpiration, SOE_OPTIONAL)
;
}
void LedgerFormats::addCommonFields (Item& item)

View File

@@ -52,14 +52,12 @@ enum LedgerEntryType
/** Describes a trust line.
*/
// VFALCO TODO Rename to TrustLine or something similar.
ltRIPPLE_STATE = 'r',
/** Deprecated.
*/
ltOFFER = 'o',
ltTICKET = 'T',
ltNotUsed01 = 'c',
/* Deprecated. */
ltOFFER = 'o',
ltLEDGER_HASHES = 'h',
@@ -70,13 +68,14 @@ enum LedgerEntryType
// No longer used or supported. Left here to prevent accidental
// reassignment of the ledger type.
ltNICKNAME = 'n',
ltNotUsed01 = 'c',
};
/**
@ingroup protocol
*/
// Used as a prefix for computing ledger indexes (keys).
// VFALCO TODO Why are there a separate set of prefixes? i.e. class HashPrefix
enum LedgerNameSpace
{
spaceAccount = 'a',
@@ -90,6 +89,7 @@ enum LedgerNameSpace
spaceSkipList = 's',
spaceAmendment = 'f',
spaceFee = 'e',
spaceTicket = 'T',
// No longer used or supported. Left here to reserve the space and
// avoid accidental reuse of the space.

View File

@@ -173,6 +173,7 @@ SField const sfBookDirectory = make::one(&sfBookDirectory, STI_HASH256, 16, "Boo
SField const sfInvoiceID = make::one(&sfInvoiceID, STI_HASH256, 17, "InvoiceID");
SField const sfNickname = make::one(&sfNickname, STI_HASH256, 18, "Nickname");
SField const sfAmendment = make::one(&sfAmendment, STI_HASH256, 19, "Amendment");
SField const sfTicketID = make::one(&sfTicketID, STI_HASH256, 20, "TicketID");
// 160-bit (common)
SField const sfTakerPaysCurrency = make::one(&sfTakerPaysCurrency, STI_HASH160, 1, "TakerPaysCurrency");

View File

@@ -333,6 +333,7 @@ extern SField const sfBookDirectory;
extern SField const sfInvoiceID;
extern SField const sfNickname;
extern SField const sfAmendment;
extern SField const sfTicketID;
// 160-bit (common)
extern SField const sfTakerPaysCurrency;

View File

@@ -52,6 +52,10 @@ bool transResultInfo (TER terCode, std::string& strToken, std::string& strHuman)
{ tecNO_LINE, "tecNO_LINE", "No such line." },
{ tecINSUFF_FEE, "tecINSUFF_FEE", "Insufficient balance to pay fee." },
{ tecFROZEN, "tecFROZEN", "Asset is frozen." },
{ tecNO_TARGET, "tecNO_TARGET", "Target account does not exist." },
{ tecNO_PERMISSION, "tecNO_PERMISSION", "No permission to perform requested operation." },
{ tecNO_ENTRY, "tecNO_ENTRY", "No matching entry found." },
{ tecINSUFFICIENT_RESERVE,"tecINSUFFICIENT_RESERVE", "Insufficient reserve to complete requested operation." },
{ tefALREADY, "tefALREADY", "The exact transaction was already in this ledger." },
{ tefBAD_ADD_AUTH, "tefBAD_ADD_AUTH", "Not authorized to add account." },

View File

@@ -185,6 +185,10 @@ enum TER // aka TransactionEngineResult
tecNO_LINE = 135,
tecINSUFF_FEE = 136,
tecFROZEN = 137,
tecNO_TARGET = 138,
tecNO_PERMISSION = 139,
tecNO_ENTRY = 140,
tecINSUFFICIENT_RESERVE = 141,
};
inline bool isTelLocal(TER x)

View File

@@ -22,56 +22,65 @@ namespace ripple {
TxFormats::TxFormats ()
{
add ("AccountSet", ttACCOUNT_SET)
<< SOElement (sfEmailHash, SOE_OPTIONAL)
<< SOElement (sfWalletLocator, SOE_OPTIONAL)
<< SOElement (sfWalletSize, SOE_OPTIONAL)
<< SOElement (sfMessageKey, SOE_OPTIONAL)
<< SOElement (sfDomain, SOE_OPTIONAL)
<< SOElement (sfTransferRate, SOE_OPTIONAL)
<< SOElement (sfSetFlag, SOE_OPTIONAL)
<< SOElement (sfClearFlag, SOE_OPTIONAL)
;
<< SOElement (sfEmailHash, SOE_OPTIONAL)
<< SOElement (sfWalletLocator, SOE_OPTIONAL)
<< SOElement (sfWalletSize, SOE_OPTIONAL)
<< SOElement (sfMessageKey, SOE_OPTIONAL)
<< SOElement (sfDomain, SOE_OPTIONAL)
<< SOElement (sfTransferRate, SOE_OPTIONAL)
<< SOElement (sfSetFlag, SOE_OPTIONAL)
<< SOElement (sfClearFlag, SOE_OPTIONAL)
;
add ("TrustSet", ttTRUST_SET)
<< SOElement (sfLimitAmount, SOE_OPTIONAL)
<< SOElement (sfQualityIn, SOE_OPTIONAL)
<< SOElement (sfQualityOut, SOE_OPTIONAL)
;
<< SOElement (sfLimitAmount, SOE_OPTIONAL)
<< SOElement (sfQualityIn, SOE_OPTIONAL)
<< SOElement (sfQualityOut, SOE_OPTIONAL)
;
add ("OfferCreate", ttOFFER_CREATE)
<< SOElement (sfTakerPays, SOE_REQUIRED)
<< SOElement (sfTakerGets, SOE_REQUIRED)
<< SOElement (sfExpiration, SOE_OPTIONAL)
<< SOElement (sfOfferSequence, SOE_OPTIONAL)
;
<< SOElement (sfTakerPays, SOE_REQUIRED)
<< SOElement (sfTakerGets, SOE_REQUIRED)
<< SOElement (sfExpiration, SOE_OPTIONAL)
<< SOElement (sfOfferSequence, SOE_OPTIONAL)
;
add ("OfferCancel", ttOFFER_CANCEL)
<< SOElement (sfOfferSequence, SOE_REQUIRED)
;
<< SOElement (sfOfferSequence, SOE_REQUIRED)
;
add ("SetRegularKey", ttREGULAR_KEY_SET)
<< SOElement (sfRegularKey, SOE_OPTIONAL)
;
<< SOElement (sfRegularKey, SOE_OPTIONAL)
;
add ("Payment", ttPAYMENT)
<< SOElement (sfDestination, SOE_REQUIRED)
<< SOElement (sfAmount, SOE_REQUIRED)
<< SOElement (sfSendMax, SOE_OPTIONAL)
<< SOElement (sfPaths, SOE_DEFAULT)
<< SOElement (sfInvoiceID, SOE_OPTIONAL)
<< SOElement (sfDestinationTag, SOE_OPTIONAL)
;
<< SOElement (sfDestination, SOE_REQUIRED)
<< SOElement (sfAmount, SOE_REQUIRED)
<< SOElement (sfSendMax, SOE_OPTIONAL)
<< SOElement (sfPaths, SOE_DEFAULT)
<< SOElement (sfInvoiceID, SOE_OPTIONAL)
<< SOElement (sfDestinationTag, SOE_OPTIONAL)
;
add ("EnableAmendment", ttAMENDMENT)
<< SOElement (sfAmendment, SOE_REQUIRED)
;
<< SOElement (sfAmendment, SOE_REQUIRED)
;
add ("SetFee", ttFEE)
<< SOElement (sfBaseFee, SOE_REQUIRED)
<< SOElement (sfReferenceFeeUnits, SOE_REQUIRED)
<< SOElement (sfReserveBase, SOE_REQUIRED)
<< SOElement (sfReserveIncrement, SOE_REQUIRED)
;
<< SOElement (sfBaseFee, SOE_REQUIRED)
<< SOElement (sfReferenceFeeUnits, SOE_REQUIRED)
<< SOElement (sfReserveBase, SOE_REQUIRED)
<< SOElement (sfReserveIncrement, SOE_REQUIRED)
;
add ("TicketCreate", ttTICKET_CREATE)
<< SOElement (sfTarget, SOE_OPTIONAL)
<< SOElement (sfExpiration, SOE_OPTIONAL)
;
add ("TicketCancel", ttTICKET_CANCEL)
<< SOElement (sfTicketID, SOE_REQUIRED)
;
}
void TxFormats::addCommonFields (Item& item)

View File

@@ -42,6 +42,8 @@ enum TxType
ttOFFER_CREATE = 7,
ttOFFER_CANCEL = 8,
no_longer_used = 9,
ttTICKET_CREATE = 10,
ttTICKET_CANCEL = 11,
ttTRUST_SET = 20,

View File

@@ -0,0 +1,83 @@
# M-of-N / Multi-Signature Support on Ripple
In order to enhance the flexibility of Ripple and provide support for enhanced security of accounts, native support for "multi-signature" or "multisign" accounts is required.
Transactions on an account which is designated as multisign 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 preauthorized 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.
## Steps to MultiSign
The implementation of multisign is a protocol breaking change which will require the coordinated rollout and deployment of the feature on the Ripple network.
Critical components for MultiSign are:
* Ticket Support
* Authorized Signer List Management
* Verification of Multiple Signatures during TX processing.
### Ticket Support
**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 multisignature 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.
##### Proposed Transaction Cancelation
A transaction that has been proposed against a multi-sign account using a ticket can be positively cancelled if a quorum of authorized signers sign and issue a transaction that consumes that ticket.
### Authorized Signer List Management
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.
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. It is an unsigned integer that is at least 2.
Each authorized account can be given a weight, from 1 to 32 inclusive. The weight is used when to determine whether a given number of signatures are sufficient for a quorum.
### 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 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 each signer should extract and sign the transaction as he normally would if this were a regular transaction. The resulting signature will be stored as a pair of { signing-account, 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.

View File

@@ -38,3 +38,5 @@
#include <ripple/module/app/transactors/CreateOffer.cpp>
#include <ripple/module/app/transactors/CreateOfferDirect.cpp>
#include <ripple/module/app/transactors/CreateOfferBridged.cpp>
#include <ripple/module/app/transactors/CreateTicket.cpp>
#include <ripple/module/app/transactors/CancelTicket.cpp>