diff --git a/Builds/VisualStudio2013/RippleD.vcxproj b/Builds/VisualStudio2013/RippleD.vcxproj
index 258182b7d4..b74bbb82cb 100644
--- a/Builds/VisualStudio2013/RippleD.vcxproj
+++ b/Builds/VisualStudio2013/RippleD.vcxproj
@@ -2389,6 +2389,9 @@
True
+
+ True
+
True
@@ -2401,6 +2404,9 @@
True
+
+ True
+
True
diff --git a/Builds/VisualStudio2013/RippleD.vcxproj.filters b/Builds/VisualStudio2013/RippleD.vcxproj.filters
index ee50f7476f..4801708798 100644
--- a/Builds/VisualStudio2013/RippleD.vcxproj.filters
+++ b/Builds/VisualStudio2013/RippleD.vcxproj.filters
@@ -3492,6 +3492,9 @@
ripple\module\app\transactors
+
+ ripple\module\app\transactors
+
ripple\module\app\transactors
@@ -3504,6 +3507,9 @@
ripple\module\app\transactors
+
+ ripple\module\app\transactors
+
ripple\module\app\transactors
diff --git a/src/ripple/module/app/ledger/Ledger.cpp b/src/ripple/module/app/ledger/Ledger.cpp
index f66dbabc7f..b2e6828ab8 100644
--- a/src/ripple/module/app/ledger/Ledger.cpp
+++ b/src/ripple/module/app/ledger/Ledger.cpp
@@ -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 missingNodes1;
diff --git a/src/ripple/module/app/ledger/Ledger.h b/src/ripple/module/app/ledger/Ledger.h
index a15a734f21..67f5ea8f90 100644
--- a/src/ripple/module/app/ledger/Ledger.h
+++ b/src/ripple/module/app/ledger/Ledger.h
@@ -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
//
diff --git a/src/ripple/module/app/transactors/CancelTicket.cpp b/src/ripple/module/app/transactors/CancelTicket.cpp
new file mode 100644
index 0000000000..26210d516c
--- /dev/null
+++ b/src/ripple/module/app/transactors/CancelTicket.cpp
@@ -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 ();
+}
+
+
+}
diff --git a/src/ripple/module/app/transactors/CancelTicket.h b/src/ripple/module/app/transactors/CancelTicket.h
new file mode 100644
index 0000000000..971ad128aa
--- /dev/null
+++ b/src/ripple/module/app/transactors/CancelTicket.h
@@ -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
diff --git a/src/ripple/module/app/transactors/CreateTicket.cpp b/src/ripple/module/app/transactors/CreateTicket.cpp
new file mode 100644
index 0000000000..3df5d34da0
--- /dev/null
+++ b/src/ripple/module/app/transactors/CreateTicket.cpp
@@ -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 ();
+}
+
+}
diff --git a/src/ripple/module/app/transactors/CreateTicket.h b/src/ripple/module/app/transactors/CreateTicket.h
new file mode 100644
index 0000000000..5cadeb9893
--- /dev/null
+++ b/src/ripple/module/app/transactors/CreateTicket.h
@@ -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
diff --git a/src/ripple/module/app/transactors/Transactor.cpp b/src/ripple/module/app/transactors/Transactor.cpp
index 884030807c..cb0040ab72 100644
--- a/src/ripple/module/app/transactors/Transactor.cpp
+++ b/src/ripple/module/app/transactors/Transactor.cpp
@@ -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;
}
diff --git a/src/ripple/module/data/protocol/LedgerFormats.cpp b/src/ripple/module/data/protocol/LedgerFormats.cpp
index 5aba7c4e50..23eb3be86c 100644
--- a/src/ripple/module/data/protocol/LedgerFormats.cpp
+++ b/src/ripple/module/data/protocol/LedgerFormats.cpp
@@ -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)
diff --git a/src/ripple/module/data/protocol/LedgerFormats.h b/src/ripple/module/data/protocol/LedgerFormats.h
index 048448c9a4..d929702df6 100644
--- a/src/ripple/module/data/protocol/LedgerFormats.h
+++ b/src/ripple/module/data/protocol/LedgerFormats.h
@@ -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.
diff --git a/src/ripple/module/data/protocol/SField.cpp b/src/ripple/module/data/protocol/SField.cpp
index ef878c1f0d..5427cffac9 100644
--- a/src/ripple/module/data/protocol/SField.cpp
+++ b/src/ripple/module/data/protocol/SField.cpp
@@ -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");
diff --git a/src/ripple/module/data/protocol/SField.h b/src/ripple/module/data/protocol/SField.h
index dd0bb294c6..f4a4096ea5 100644
--- a/src/ripple/module/data/protocol/SField.h
+++ b/src/ripple/module/data/protocol/SField.h
@@ -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;
diff --git a/src/ripple/module/data/protocol/TER.cpp b/src/ripple/module/data/protocol/TER.cpp
index 8bc37e1c9b..cb2bc978dd 100644
--- a/src/ripple/module/data/protocol/TER.cpp
+++ b/src/ripple/module/data/protocol/TER.cpp
@@ -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." },
diff --git a/src/ripple/module/data/protocol/TER.h b/src/ripple/module/data/protocol/TER.h
index 2fc5319921..01897d5e5b 100644
--- a/src/ripple/module/data/protocol/TER.h
+++ b/src/ripple/module/data/protocol/TER.h
@@ -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)
diff --git a/src/ripple/module/data/protocol/TxFormats.cpp b/src/ripple/module/data/protocol/TxFormats.cpp
index 37f0bda37a..b859fc6aac 100644
--- a/src/ripple/module/data/protocol/TxFormats.cpp
+++ b/src/ripple/module/data/protocol/TxFormats.cpp
@@ -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)
diff --git a/src/ripple/module/data/protocol/TxFormats.h b/src/ripple/module/data/protocol/TxFormats.h
index 6301bf46a1..d823a3417d 100644
--- a/src/ripple/module/data/protocol/TxFormats.h
+++ b/src/ripple/module/data/protocol/TxFormats.h
@@ -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,
diff --git a/src/ripple/multisign/README.md b/src/ripple/multisign/README.md
new file mode 100644
index 0000000000..281b4a6f39
--- /dev/null
+++ b/src/ripple/multisign/README.md
@@ -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.
diff --git a/src/ripple/unity/app9.cpp b/src/ripple/unity/app9.cpp
index b844af5af5..b5f6a39e4f 100644
--- a/src/ripple/unity/app9.cpp
+++ b/src/ripple/unity/app9.cpp
@@ -38,3 +38,5 @@
#include
#include
#include
+#include
+#include