Improvements to protocol serialization:

A few serialization changes coming from m-of-n development:

 o Improve readability of SField.cpp.
 o Better initialization of STObject.
 o Trimming of STObject public methods.
 o Add STObject::getFieldObject and STObject::setFieldObject.
 o Make STObject::isEquivalent more robust.
 o Improvements to whitespace, overrides, and virtuals.
This commit is contained in:
Scott Schurr
2015-02-03 18:17:11 -08:00
committed by Vinnie Falco
parent 2f3834359e
commit 92799187ed
18 changed files with 436 additions and 324 deletions

View File

@@ -81,7 +81,7 @@ bool ConsensusTransSetSF::haveNode (const SHAMapNodeID& id, uint256 const& nodeH
WriteLog (lsTRACE, TransactionAcquire) << "Node in our acquiring TX set is TXN we have";
Serializer s;
s.add32 (HashPrefix::transactionID);
txn->getSTransaction ()->add (s, true);
txn->getSTransaction ()->add (s);
assert (s.getSHA512Half () == nodeHash);
nodeData = s.peekData ();
return true;

View File

@@ -52,8 +52,8 @@ enum LedgerStateParms
class SqliteStatement;
// VFALCO TODO figure out exactly how this thing works.
// It seems like some ledger database is stored as a global, static in the
// class. But then what is the meaning of a Ledger object? Is this
// It seems like some ledger database is stored as a global, static in
// the class. But then what is the meaning of a Ledger object? Is this
// really two classes in one? StoreOfAllLedgers + SingleLedgerObject?
//
/** Holds some or all of a ledger.

View File

@@ -566,7 +566,7 @@ AmendmentTableImpl<AppApiFacade>::doVoting (Ledger::ref lastClosedLedger,
// Inject the transaction into our initial proposal
Serializer s;
trans.add (s, true);
trans.add (s);
#if RIPPLE_PROPOSE_AMENDMENTS
auto tItem = std::make_shared<SHAMapItem> (txID, s.peekData ());
if (!initialPosition->addGiveItem (tItem, true, false))

View File

@@ -226,7 +226,7 @@ FeeVoteImpl::doVoting (Ledger::ref lastClosedLedger,
journal_.warning << "Vote: " << txID;
Serializer s;
trans.add (s, true);
trans.add (s);
auto tItem = std::make_shared<SHAMapItem> (txID, s.peekData ());

View File

@@ -67,14 +67,14 @@
namespace ripple {
class NetworkOPsImp
class NetworkOPsImp final
: public NetworkOPs
, public beast::DeadlineTimer::Listener
{
public:
enum Fault
{
// exceptions these functions can throw
// Exceptions these functions can throw.
IO_ERROR = 1,
NO_NETWORK = 2,
};
@@ -116,109 +116,111 @@ public:
{
}
~NetworkOPsImp()
{
}
~NetworkOPsImp() override = default;
// network information
// Our best estimate of wall time in seconds from 1/1/2000
std::uint32_t getNetworkTimeNC () const;
// Network information.
// Our best estimate of wall time in seconds from 1/1/2000.
std::uint32_t getNetworkTimeNC () const override;
// Our best estimate of current ledger close time
std::uint32_t getCloseTimeNC () const;
// Our best estimate of current ledger close time.
std::uint32_t getCloseTimeNC () const override;
private:
std::uint32_t getCloseTimeNC (int& offset) const;
// Use *only* to timestamp our own validation
std::uint32_t getValidationTimeNC ();
void closeTimeOffset (int);
public:
// Use *only* to timestamp our own validation.
std::uint32_t getValidationTimeNC () override;
void closeTimeOffset (int) override;
/** On return the offset param holds the System time offset in seconds.
*/
boost::posix_time::ptime getNetworkTimePT(int& offset) const;
std::uint32_t getLedgerID (uint256 const& hash);
std::uint32_t getCurrentLedgerID ();
OperatingMode getOperatingMode () const
boost::posix_time::ptime getNetworkTimePT(int& offset) const override;
std::uint32_t getLedgerID (uint256 const& hash) override;
std::uint32_t getCurrentLedgerID () override;
OperatingMode getOperatingMode () const override
{
return mMode;
}
std::string strOperatingMode () const;
std::string strOperatingMode () const override;
Ledger::pointer getClosedLedger ()
Ledger::pointer getClosedLedger () override
{
return m_ledgerMaster.getClosedLedger ();
}
Ledger::pointer getValidatedLedger ()
Ledger::pointer getValidatedLedger () override
{
return m_ledgerMaster.getValidatedLedger ();
}
Ledger::pointer getPublishedLedger ()
Ledger::pointer getPublishedLedger () override
{
return m_ledgerMaster.getPublishedLedger ();
}
Ledger::pointer getCurrentLedger ()
Ledger::pointer getCurrentLedger () override
{
return m_ledgerMaster.getCurrentLedger ();
}
Ledger::pointer getLedgerByHash (uint256 const& hash)
Ledger::pointer getLedgerByHash (uint256 const& hash) override
{
return m_ledgerMaster.getLedgerByHash (hash);
}
Ledger::pointer getLedgerBySeq (const std::uint32_t seq);
void missingNodeInLedger (const std::uint32_t seq);
Ledger::pointer getLedgerBySeq (const std::uint32_t seq) override;
void missingNodeInLedger (const std::uint32_t seq) override;
uint256 getClosedLedgerHash ()
uint256 getClosedLedgerHash () override
{
return m_ledgerMaster.getClosedLedger ()->getHash ();
}
// Do we have this inclusive range of ledgers in our database
bool haveLedgerRange (std::uint32_t from, std::uint32_t to);
bool haveLedger (std::uint32_t seq);
std::uint32_t getValidatedSeq ();
bool isValidated (std::uint32_t seq);
bool isValidated (std::uint32_t seq, uint256 const& hash);
bool isValidated (Ledger::ref l)
bool haveLedgerRange (std::uint32_t from, std::uint32_t to) override;
bool haveLedger (std::uint32_t seq) override;
std::uint32_t getValidatedSeq () override;
bool isValidated (std::uint32_t seq) override;
bool isValidated (std::uint32_t seq, uint256 const& hash) override;
bool isValidated (Ledger::ref l) override
{
return isValidated (l->getLedgerSeq (), l->getHash ());
}
bool getValidatedRange (std::uint32_t& minVal, std::uint32_t& maxVal)
bool getValidatedRange (
std::uint32_t& minVal, std::uint32_t& maxVal) override
{
return m_ledgerMaster.getValidatedRange (minVal, maxVal);
}
bool getFullValidatedRange (std::uint32_t& minVal, std::uint32_t& maxVal)
bool getFullValidatedRange (
std::uint32_t& minVal, std::uint32_t& maxVal) override
{
return m_ledgerMaster.getFullValidatedRange (minVal, maxVal);
}
STValidation::ref getLastValidation ()
STValidation::ref getLastValidation () override
{
return mLastValidation;
}
void setLastValidation (STValidation::ref v)
void setLastValidation (STValidation::ref v) override
{
mLastValidation = v;
}
//
// Transaction operations
// Transaction operations.
//
// must complete immediately
// Must complete immediately.
typedef std::function<void (Transaction::pointer, TER)> stCallback;
void submitTransaction (
Job&, STTx::pointer,
stCallback callback = stCallback ());
stCallback callback = stCallback ()) override;
Transaction::pointer submitTransactionSync (
Transaction::ref tpTrans,
bool bAdmin, bool bLocal, bool bFailHard, bool bSubmit);
bool bAdmin, bool bLocal, bool bFailHard, bool bSubmit) override;
Transaction::pointer processTransactionCb (
Transaction::pointer,
bool bAdmin, bool bLocal, bool bFailHard, stCallback);
bool bAdmin, bool bLocal, bool bFailHard, stCallback) override;
Transaction::pointer processTransaction (
Transaction::pointer transaction,
bool bAdmin, bool bLocal, bool bFailHard)
bool bAdmin, bool bLocal, bool bFailHard) override
{
return processTransactionCb (
transaction, bAdmin, bLocal, bFailHard, stCallback ());
@@ -227,6 +229,7 @@ public:
// VFALCO Workaround for MSVC std::function which doesn't swallow return
// types.
//
private:
void processTransactionCbVoid (
Transaction::pointer p,
bool bAdmin, bool bLocal, bool bFailHard, stCallback cb)
@@ -234,77 +237,82 @@ public:
processTransactionCb (p, bAdmin, bLocal, bFailHard, cb);
}
Transaction::pointer findTransactionByID (uint256 const& transactionID);
public:
Transaction::pointer findTransactionByID (
uint256 const& transactionID) override;
int findTransactionsByDestination (
std::list<Transaction::pointer>&,
RippleAddress const& destinationAccount,
std::uint32_t startLedgerSeq, std::uint32_t endLedgerSeq,
int maxTransactions);
int maxTransactions) override;
//
// Account functions
// Account functions.
//
AccountState::pointer getAccountState (
Ledger::ref lrLedger, RippleAddress const& accountID);
Ledger::ref lrLedger, RippleAddress const& accountID) override;
//
// Directory functions
// Directory functions.
//
STVector256 getDirNodeInfo (
Ledger::ref lrLedger, uint256 const& uRootIndex,
std::uint64_t& uNodePrevious, std::uint64_t& uNodeNext);
std::uint64_t& uNodePrevious, std::uint64_t& uNodeNext) override;
//
// Owner functions
// Owner functions.
//
Json::Value getOwnerInfo (
Ledger::pointer lpLedger, RippleAddress const& naAccount);
Ledger::pointer lpLedger, RippleAddress const& naAccount) override;
//
// Book functions
// Book functions.
//
void getBookPage (bool bAdmin, Ledger::pointer lpLedger, Book const&,
Account const& uTakerID, const bool bProof, const unsigned int iLimit,
Json::Value const& jvMarker, Json::Value& jvResult);
Json::Value const& jvMarker, Json::Value& jvResult) override;
// ledger proposal/close functions
// Ledger proposal/close functions.
void processTrustedProposal (
LedgerProposal::pointer proposal,
std::shared_ptr<protocol::TMProposeSet> set,
RippleAddress nodePublic, uint256 checkLedger, bool sigGood);
RippleAddress nodePublic, uint256 checkLedger, bool sigGood) override;
bool recvValidation (
STValidation::ref val, std::string const& source);
void takePosition (int seq, std::shared_ptr<SHAMap> const& position);
STValidation::ref val, std::string const& source) override;
void takePosition (
int seq, std::shared_ptr<SHAMap> const& position) override;
std::shared_ptr<SHAMap> getTXMap (uint256 const& hash);
bool hasTXSet (
const std::shared_ptr<Peer>& peer, uint256 const& set,
protocol::TxSetStatus status);
void mapComplete (uint256 const& hash, std::shared_ptr<SHAMap> const& map);
void mapComplete (uint256 const& hash, std::shared_ptr<SHAMap> const& map) override;
void makeFetchPack (
Job&, std::weak_ptr<Peer> peer,
std::shared_ptr<protocol::TMGetObjectByHash> request,
uint256 haveLedger, std::uint32_t uUptime);
uint256 haveLedger, std::uint32_t uUptime) override;
bool shouldFetchPack (std::uint32_t seq);
void gotFetchPack (bool progress, std::uint32_t seq);
void addFetchPack (uint256 const& hash, std::shared_ptr< Blob >& data);
bool getFetchPack (uint256 const& hash, Blob& data);
int getFetchSize ();
void sweepFetchPack ();
bool shouldFetchPack (std::uint32_t seq) override;
void gotFetchPack (bool progress, std::uint32_t seq) override;
void addFetchPack (
uint256 const& hash, std::shared_ptr< Blob >& data) override;
bool getFetchPack (uint256 const& hash, Blob& data) override;
int getFetchSize () override;
void sweepFetchPack () override;
// network state machine
// Network state machine.
// VFALCO TODO Try to make all these private since they seem to be...private
//
// Used for the "jump" case
// Used for the "jump" case.
private:
void switchLastClosedLedger (
Ledger::pointer newLedger, bool duringConsensus);
bool checkLastClosedLedger (
@@ -312,8 +320,10 @@ public:
int beginConsensus (
uint256 const& networkClosed, Ledger::pointer closingLedger);
void tryStartConsensus ();
void endConsensus (bool correctLCL);
void setStandAlone ()
public:
void endConsensus (bool correctLCL) override;
void setStandAlone () override
{
setMode (omFULL);
}
@@ -321,73 +331,74 @@ public:
/** Called to initially start our timers.
Not called for stand-alone mode.
*/
void setStateTimer ();
void setStateTimer () override;
void newLCL (int proposers, int convergeTime, uint256 const& ledgerHash);
void needNetworkLedger ()
void newLCL (
int proposers, int convergeTime, uint256 const& ledgerHash) override;
void needNetworkLedger () override
{
mNeedNetworkLedger = true;
}
void clearNeedNetworkLedger ()
void clearNeedNetworkLedger () override
{
mNeedNetworkLedger = false;
}
bool isNeedNetworkLedger ()
bool isNeedNetworkLedger () override
{
return mNeedNetworkLedger;
}
bool isFull ()
bool isFull () override
{
return !mNeedNetworkLedger && (mMode == omFULL);
}
void setProposing (bool p, bool v)
void setProposing (bool p, bool v) override
{
mProposing = p;
mValidating = v;
}
bool isProposing ()
bool isProposing () override
{
return mProposing;
}
bool isValidating ()
bool isValidating () override
{
return mValidating;
}
bool isAmendmentBlocked ()
bool isAmendmentBlocked () override
{
return m_amendmentBlocked;
}
void setAmendmentBlocked ();
void consensusViewChange ();
int getPreviousProposers ()
void setAmendmentBlocked () override;
void consensusViewChange () override;
int getPreviousProposers () override
{
return mLastCloseProposers;
}
int getPreviousConvergeTime ()
int getPreviousConvergeTime () override
{
return mLastCloseConvergeTime;
}
std::uint32_t getLastCloseTime ()
std::uint32_t getLastCloseTime () override
{
return mLastCloseTime;
}
void setLastCloseTime (std::uint32_t t)
void setLastCloseTime (std::uint32_t t) override
{
mLastCloseTime = t;
}
Json::Value getConsensusInfo ();
Json::Value getServerInfo (bool human, bool admin);
void clearLedgerFetch ();
Json::Value getLedgerFetchInfo ();
std::uint32_t acceptLedger ();
Proposals & peekStoredProposals ()
Json::Value getConsensusInfo () override;
Json::Value getServerInfo (bool human, bool admin) override;
void clearLedgerFetch () override;
Json::Value getLedgerFetchInfo () override;
std::uint32_t acceptLedger () override;
Proposals & peekStoredProposals () override
{
return mStoredProposals;
}
void storeProposal (
LedgerProposal::ref proposal, RippleAddress const& peerPublic);
uint256 getConsensusLCL ();
void reportFeeChange ();
LedgerProposal::ref proposal, RippleAddress const& peerPublic) override;
uint256 getConsensusLCL () override;
void reportFeeChange () override;
void updateLocalTx (Ledger::ref newValidLedger) override
{
@@ -403,24 +414,24 @@ public:
return m_localTX->size ();
}
//Helper function to generate SQL query to get transactions
//Helper function to generate SQL query to get transactions.
std::string transactionsSQL (
std::string selection, RippleAddress const& account,
std::int32_t minLedger, std::int32_t maxLedger,
bool descending, std::uint32_t offset, int limit,
bool binary, bool count, bool bAdmin);
bool binary, bool count, bool bAdmin) override;
// client information retrieval functions
// Client information retrieval functions.
using NetworkOPs::AccountTxs;
AccountTxs getAccountTxs (
RippleAddress const& account,
std::int32_t minLedger, std::int32_t maxLedger, bool descending,
std::uint32_t offset, int limit, bool bAdmin);
std::uint32_t offset, int limit, bool bAdmin) override;
AccountTxs getTxsAccount (
RippleAddress const& account, std::int32_t minLedger,
std::int32_t maxLedger, bool forward, Json::Value& token, int limit,
bool bAdmin);
bool bAdmin) override;
using NetworkOPs::txnMetaLedgerType;
using NetworkOPs::MetaTxsList;
@@ -429,31 +440,31 @@ public:
getAccountTxsB (
RippleAddress const& account, std::int32_t minLedger,
std::int32_t maxLedger, bool descending, std::uint32_t offset,
int limit, bool bAdmin);
int limit, bool bAdmin) override;
MetaTxsList
getTxsAccountB (
RippleAddress const& account, std::int32_t minLedger,
std::int32_t maxLedger, bool forward, Json::Value& token,
int limit, bool bAdmin);
int limit, bool bAdmin) override;
std::vector<RippleAddress> getLedgerAffectedAccounts (
std::uint32_t ledgerSeq);
std::uint32_t ledgerSeq) override;
//
// Monitoring: publisher side
// Monitoring: publisher side.
//
void pubLedger (Ledger::ref lpAccepted);
void pubLedger (Ledger::ref lpAccepted) override;
void pubProposedTransaction (
Ledger::ref lpCurrent, STTx::ref stTxn, TER terResult);
Ledger::ref lpCurrent, STTx::ref stTxn, TER terResult) override;
//--------------------------------------------------------------------------
//
// InfoSub::Source
// InfoSub::Source.
//
void subAccount (
InfoSub::ref ispListener,
const hash_set<RippleAddress>& vnaAccountIDs, bool rt);
const hash_set<RippleAddress>& vnaAccountIDs, bool rt) override;
void unsubAccount (
InfoSub::ref ispListener,
const hash_set<RippleAddress>& vnaAccountIDs,
@@ -466,29 +477,31 @@ public:
const hash_set<RippleAddress>& vnaAccountIDs,
bool rt);
bool subLedger (InfoSub::ref ispListener, Json::Value& jvResult);
bool unsubLedger (std::uint64_t uListener);
bool subLedger (InfoSub::ref ispListener, Json::Value& jvResult) override;
bool unsubLedger (std::uint64_t uListener) override;
bool subServer (InfoSub::ref ispListener, Json::Value& jvResult, bool admin);
bool unsubServer (std::uint64_t uListener);
bool subServer (
InfoSub::ref ispListener, Json::Value& jvResult, bool admin) override;
bool unsubServer (std::uint64_t uListener) override;
bool subBook (InfoSub::ref ispListener, Book const&) override;
bool unsubBook (std::uint64_t uListener, Book const&) override;
bool subTransactions (InfoSub::ref ispListener);
bool unsubTransactions (std::uint64_t uListener);
bool subTransactions (InfoSub::ref ispListener) override;
bool unsubTransactions (std::uint64_t uListener) override;
bool subRTTransactions (InfoSub::ref ispListener);
bool unsubRTTransactions (std::uint64_t uListener);
bool subRTTransactions (InfoSub::ref ispListener) override;
bool unsubRTTransactions (std::uint64_t uListener) override;
InfoSub::pointer findRpcSub (std::string const& strUrl);
InfoSub::pointer addRpcSub (std::string const& strUrl, InfoSub::ref);
InfoSub::pointer findRpcSub (std::string const& strUrl) override;
InfoSub::pointer addRpcSub (
std::string const& strUrl, InfoSub::ref) override;
//--------------------------------------------------------------------------
//
// Stoppable
// Stoppable.
void onStop ()
void onStop () override
{
mAcquiringLedger.reset();
m_heartbeatTimer.cancel();
@@ -500,7 +513,7 @@ public:
private:
void setHeartbeatTimer ();
void setClusterTimer ();
void onDeadlineTimer (beast::DeadlineTimer& timer);
void onDeadlineTimer (beast::DeadlineTimer& timer) override;
void processHeartbeatTimer ();
void processClusterTimer ();
@@ -578,10 +591,10 @@ private:
subRpcMapType mRpcSubMap;
SubMapType mSubLedger; // accepted ledgers
SubMapType mSubServer; // when server changes connectivity state
SubMapType mSubTransactions; // all accepted transactions
SubMapType mSubRTTransactions; // all proposed and accepted transactions
SubMapType mSubLedger; // Accepted ledgers.
SubMapType mSubServer; // When server changes connectivity state.
SubMapType mSubTransactions; // All accepted transactions.
SubMapType mSubRTTransactions; // All proposed and accepted transactions.
TaggedCache<uint256, Blob> mFetchPack;
std::uint32_t mFetchSeq;
@@ -591,10 +604,10 @@ private:
JobQueue& m_job_queue;
// Whether we are in standalone mode
// Whether we are in standalone mode.
bool const m_standalone;
// The number of nodes that we need to consider ourselves connected
// The number of nodes that we need to consider ourselves connected.
std::size_t const m_network_quorum;
};

View File

@@ -0,0 +1,90 @@
# 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 ###

View File

@@ -187,7 +187,8 @@ TER Transactor::checkSeq ()
{
if (a_seq < t_seq)
{
m_journal.trace << "Transaction has future sequence number " <<
m_journal.trace <<
"applyTransaction: has future sequence number " <<
"a_seq=" << a_seq << " t_seq=" << t_seq;
return terPRE_SEQ;
}
@@ -195,7 +196,7 @@ TER Transactor::checkSeq ()
if (mEngine->getLedger ()->hasTransaction (mTxn.getTransactionID ()))
return tefALREADY;
m_journal.trace << "Transaction has past sequence number " <<
m_journal.trace << "applyTransaction: has past sequence number " <<
"a_seq=" << a_seq << " t_seq=" << t_seq;
return tefPAST_SEQ;
}
@@ -223,7 +224,7 @@ TER Transactor::preCheck ()
if (!mTxnAccountID)
{
m_journal.warning << "apply: bad transaction source id";
m_journal.warning << "applyTransaction: bad transaction source id";
return temBAD_SRC_ACCOUNT;
}
@@ -272,7 +273,7 @@ TER Transactor::apply ()
if (mustHaveValidAccount ())
{
m_journal.trace <<
"apply: delay transaction: source account does not exist " <<
"applyTransaction: delay: source account does not exist " <<
mTxn.getSourceAccount ().humanAccountID ();
return terNO_ACCOUNT;
}

View File

@@ -111,7 +111,7 @@ inline bool operator!= (StaticString x, std::string const& y)
/** \brief Represents a <a HREF="http://www.json.org">JSON</a> value.
*
* This class is a discriminated union wrapper that can represents a:
* This class is a discriminated union wrapper that can represent a:
* - signed integer [range: Value::minInt - Value::maxInt]
* - unsigned integer (range: 0 - Value::maxUInt)
* - double

View File

@@ -1,83 +0,0 @@
# 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

@@ -115,13 +115,20 @@ public:
sMD_Default = sMD_ChangeOrig | sMD_ChangeNew | sMD_DeleteFinal | sMD_Create
};
const int fieldCode; // (type<<16)|index
const SerializedTypeID fieldType; // STI_*
const int fieldValue; // Code number for protocol
enum class IsSigning : unsigned char
{
no,
yes
};
static IsSigning const notSigning = IsSigning::no;
int const fieldCode; // (type<<16)|index
SerializedTypeID const fieldType; // STI_*
int const fieldValue; // Code number for protocol
std::string fieldName;
int fieldMeta;
int fieldNum;
bool signingField;
IsSigning const signingField;
std::string rawJsonName;
Json::StaticString jsonName;
@@ -146,7 +153,7 @@ public:
protected:
// These constructors can only be called from FieldNames.cpp
SField (SerializedTypeID tid, int fv, const char* fn,
int meta = sMD_Default, bool signing = true);
int meta = sMD_Default, IsSigning signing = IsSigning::yes);
explicit SField (int fc);
SField (SerializedTypeID id, int val);
@@ -219,11 +226,7 @@ public:
bool isSigningField () const
{
return signingField;
}
void notSigningField ()
{
signingField = false;
return signingField == IsSigning::yes;
}
bool shouldMeta (int c) const
{
@@ -236,7 +239,8 @@ public:
bool shouldInclude (bool withSigningField) const
{
return (fieldValue < 256) && (withSigningField || signingField);
return (fieldValue < 256) &&
(withSigningField || (signingField == IsSigning::yes));
}
bool operator== (const SField& f) const

View File

@@ -159,7 +159,10 @@ public:
add (s, true); // just inner elements
}
void add (Serializer & s, bool withSignature) const;
void addWithoutSigningFields (Serializer & s) const
{
add (s, false);
}
// VFALCO NOTE does this return an expensive copy of an object with a
// dynamic buffer?
@@ -167,7 +170,7 @@ public:
Serializer getSerializer () const
{
Serializer s;
add (s);
add (s, true);
return s;
}
@@ -242,6 +245,7 @@ public:
STPathSet const& getFieldPathSet (SField const& field) const;
const STVector256& getFieldV256 (SField const& field) const;
const STArray& getFieldArray (SField const& field) const;
const STObject& getFieldObject (SField const& field) const;
/** Set a field.
if the field already exists, it is replaced.
@@ -265,6 +269,7 @@ public:
void setFieldPathSet (SField const& field, STPathSet const&);
void setFieldV256 (SField const& field, STVector256 const& v);
void setFieldArray (SField const& field, STArray const& v);
void setFieldObject (SField const& field, STObject const& v);
template <class Tag>
void setFieldH160 (SField const& field, base_uint<160, Tag> const& v)
@@ -285,6 +290,7 @@ public:
}
STObject& peekFieldObject (SField const& field);
STArray& peekFieldArray (SField const& field);
bool isFieldPresent (SField const& field) const;
STBase* makeFieldPresent (SField const& field);
@@ -301,6 +307,24 @@ public:
}
private:
void add (Serializer & s, bool withSigningFields) const;
// Sort the entries in an STObject into the order that they will be
// serialized. Note: they are not sorted into pointer value order, they
// are sorted by SField::fieldCode.
static std::vector<STBase const*>
getSortedFields (STObject const& objToSort);
// Two different ways to compare STObjects.
//
// This one works only if the SOTemplates are the same. Presumably it
// runs faster since there's no sorting.
static bool equivalentSTObjectSameTemplate (
STObject const& obj1, STObject const& obj2);
// This way of comparing STObjects always works, but is slower.
static bool equivalentSTObject (STObject const& obj1, STObject const& obj2);
// Implementation for getting (most) fields that return by value.
//
// The remove_cv and remove_reference are necessitated by the STBitString
@@ -396,6 +420,26 @@ private:
(*cf) = value;
}
// Implementation for peeking STObjects and STArrays
template <typename T>
T& peekField (SField const& field)
{
STBase* rf = getPField (field, true);
if (!rf)
throw std::runtime_error ("Field not found");
if (rf->getSType () == STI_NOTPRESENT)
rf = makeFieldPresent (field);
T* cf = dynamic_cast<T*> (rf);
if (!cf)
throw std::runtime_error ("Wrong field type");
return *cf;
}
};
} // ripple

View File

@@ -67,14 +67,14 @@ public:
return emplace(n, buf, std::move(*this));
}
// STObject functions
// STObject functions.
SerializedTypeID getSType () const override
{
return STI_TRANSACTION;
}
std::string getFullText () const override;
// outer transaction functions / signature functions
// Outer transaction functions / signature functions.
Blob getSignature () const;
uint256 getSigningHash () const;
@@ -116,8 +116,8 @@ public:
uint256 getTransactionID () const;
virtual Json::Value getJson (int options) const override;
virtual Json::Value getJson (int options, bool binary) const;
Json::Value getJson (int options) const override;
Json::Value getJson (int options, bool binary) const;
void sign (RippleAddress const& private_key);
@@ -140,7 +140,7 @@ public:
sig_state_ = false;
}
// SQL Functions with metadata
// SQL Functions with metadata.
static
std::string const&
getMetaSQLInsertReplaceHeader ();

View File

@@ -34,6 +34,9 @@ static std::mutex SField_mutex;
static std::map<int, SField const*> knownCodeToField;
static std::map<int, std::unique_ptr<SField const>> unknownCodeToField;
// Storage for static const member.
SField::IsSigning const SField::notSigning;
int SField::num = 0;
typedef std::lock_guard <std::mutex> StaticScopedLockType;
@@ -173,9 +176,9 @@ SField const sfDeliveredAmount = make::one(&sfDeliveredAmount, STI_AMOUNT, 18, "
// variable length
TypedField<STBlob> const sfPublicKey = make::one<STBlob>(&sfPublicKey, STI_VL, 1, "PublicKey");
TypedField<STBlob> const sfSigningPubKey = make::one<STBlob>(&sfSigningPubKey, STI_VL, 3, "SigningPubKey");
TypedField<STBlob> const sfSignature = make::one<STBlob>(&sfSignature, STI_VL, 6, "Signature", SField::sMD_Default, false);
TypedField<STBlob> const sfSignature = make::one<STBlob>(&sfSignature, STI_VL, 6, "Signature", SField::sMD_Default, SField::notSigning);
SField const sfMessageKey = make::one(&sfMessageKey, STI_VL, 2, "MessageKey");
SField const sfTxnSignature = make::one(&sfTxnSignature, STI_VL, 4, "TxnSignature", SField::sMD_Default, false);
SField const sfTxnSignature = make::one(&sfTxnSignature, STI_VL, 4, "TxnSignature", SField::sMD_Default, SField::notSigning);
SField const sfDomain = make::one(&sfDomain, STI_VL, 7, "Domain");
SField const sfFundCode = make::one(&sfFundCode, STI_VL, 8, "FundCode");
SField const sfRemoveCode = make::one(&sfRemoveCode, STI_VL, 9, "RemoveCode");
@@ -216,7 +219,7 @@ SField const sfMemo = make::one(&sfMemo, STI_OBJEC
// 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, false);
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 sfTemplate = make::one(&sfTemplate, STI_ARRAY, 5, "Template");
SField const sfNecessary = make::one(&sfNecessary, STI_ARRAY, 6, "Necessary");
@@ -225,7 +228,7 @@ SField const sfAffectedNodes = make::one(&sfAffectedNodes, STI_ARRAY, 8, "Af
SField const sfMemos = make::one(&sfMemos, STI_ARRAY, 9, "Memos");
SField::SField (SerializedTypeID tid, int fv, const char* fn,
int meta, bool signing)
int meta, IsSigning signing)
: fieldCode (field_code (tid, fv))
, fieldType (tid)
, fieldValue (fv)
@@ -244,7 +247,7 @@ SField::SField (int fc)
, fieldValue (0)
, fieldMeta (sMD_Never)
, fieldNum (++num)
, signingField (true)
, signingField (IsSigning::yes)
, rawJsonName (getName ())
, jsonName (rawJsonName.c_str ())
{
@@ -257,7 +260,7 @@ SField::SField (SerializedTypeID tid, int fv)
: fieldCode (field_code (tid, fv)), fieldType (tid), fieldValue (fv),
fieldMeta (sMD_Default),
fieldNum (++num),
signingField (true),
signingField (IsSigning::yes),
jsonName (nullptr)
{
fieldName = std::to_string (tid) + '/' + std::to_string (fv);

View File

@@ -264,39 +264,6 @@ std::string STObject::getFullText () const
return ret;
}
void STObject::add (Serializer& s, bool withSigningFields) const
{
std::map<int, STBase const*> fields;
for (auto const& e : v_)
{
// pick out the fields and sort them
if ((e->getSType() != STI_NOTPRESENT) &&
e->getFName().shouldInclude (withSigningFields))
{
fields.insert (std::make_pair (
e->getFName().fieldCode, &e.get()));
}
}
// insert sorted
for (auto const& e : fields)
{
auto const field = e.second;
// When we serialize an object inside another object,
// the type associated by rule with this field name
// must be OBJECT, or the object cannot be deserialized
assert ((field->getSType() != STI_OBJECT) ||
(field->getFName().fieldType == STI_OBJECT));
field->addFieldID (s);
field->add (s);
if (dynamic_cast<const STArray*> (field) != nullptr)
s.addFieldID (STI_ARRAY, 1);
else if (dynamic_cast<const STObject*> (field) != nullptr)
s.addFieldID (STI_OBJECT, 1);
}
}
std::string STObject::getText () const
{
std::string ret = "{";
@@ -326,32 +293,10 @@ bool STObject::isEquivalent (const STBase& t) const
return false;
}
auto it1 = v_.begin (), end1 = v_.end ();
auto it2 = v->v_.begin (), end2 = v->v_.end ();
if (mType != nullptr && (v->mType == mType))
return equivalentSTObjectSameTemplate (*this, *v);
while ((it1 != end1) && (it2 != end2))
{
if ((it1->get().getSType () != it2->get().getSType ()) ||
!it1->get().isEquivalent (it2->get()))
{
if (it1->get().getSType () != it2->get().getSType ())
{
WriteLog (lsDEBUG, STObject) << "notEquiv type " <<
it1->get().getFullText() << " != " << it2->get().getFullText();
}
else
{
WriteLog (lsDEBUG, STObject) << "notEquiv " <<
it1->get().getFullText() << " != " << it2->get().getFullText();
}
return false;
}
++it1;
++it2;
}
return (it1 == end1) && (it2 == end2);
return equivalentSTObject (*this, *v);
}
uint256 STObject::getHash (std::uint32_t prefix) const
@@ -448,20 +393,12 @@ bool STObject::isFieldPresent (SField const& field) const
STObject& STObject::peekFieldObject (SField const& field)
{
STBase* rf = getPField (field, true);
return peekField<STObject> (field);
}
if (!rf)
throw std::runtime_error ("Field not found");
if (rf->getSType () == STI_NOTPRESENT)
rf = makeFieldPresent (field);
STObject* cf = dynamic_cast<STObject*> (rf);
if (!cf)
throw std::runtime_error ("Wrong field type");
return *cf;
STArray& STObject::peekFieldArray (SField const& field)
{
return peekField<STArray> (field);
}
bool STObject::setFlag (std::uint32_t f)
@@ -650,12 +587,6 @@ STAmount const& STObject::getFieldAmount (SField const& field) const
return getFieldByConstRef <STAmount> (field, empty);
}
const STArray& STObject::getFieldArray (SField const& field) const
{
static STArray const empty{};
return getFieldByConstRef <STArray> (field, empty);
}
STPathSet const& STObject::getFieldPathSet (SField const& field) const
{
static STPathSet const empty{};
@@ -668,6 +599,18 @@ const STVector256& STObject::getFieldV256 (SField const& field) const
return getFieldByConstRef <STVector256> (field, empty);
}
const STArray& STObject::getFieldArray (SField const& field) const
{
static STArray const empty{};
return getFieldByConstRef <STArray> (field, empty);
}
const STObject& STObject::getFieldObject (SField const& field) const
{
static STObject const empty{sfInvalid};
return getFieldByConstRef <STObject> (field, empty);
}
void
STObject::set (std::unique_ptr<STBase> v)
{
@@ -760,6 +703,11 @@ void STObject::setFieldArray (SField const& field, STArray const& v)
setFieldUsingAssignment (field, v);
}
void STObject::setFieldObject (SField const& field, STObject const& v)
{
setFieldUsingAssignment (field, v);
}
Json::Value STObject::getJson (int options) const
{
Json::Value ret (Json::objectValue);
@@ -830,4 +778,96 @@ bool STObject::operator== (const STObject& obj) const
return true;
}
void STObject::add (Serializer& s, bool withSigningFields) const
{
std::map<int, STBase const*> fields;
for (auto const& e : v_)
{
// pick out the fields and sort them
if ((e->getSType() != STI_NOTPRESENT) &&
e->getFName().shouldInclude (withSigningFields))
{
fields.insert (std::make_pair (
e->getFName().fieldCode, &e.get()));
}
}
// insert sorted
for (auto const& e : fields)
{
auto const field = e.second;
// When we serialize an object inside another object,
// the type associated by rule with this field name
// must be OBJECT, or the object cannot be deserialized
assert ((field->getSType() != STI_OBJECT) ||
(field->getFName().fieldType == STI_OBJECT));
field->addFieldID (s);
field->add (s);
if (dynamic_cast<const STArray*> (field) != nullptr)
s.addFieldID (STI_ARRAY, 1);
else if (dynamic_cast<const STObject*> (field) != nullptr)
s.addFieldID (STI_OBJECT, 1);
}
}
std::vector<STBase const*>
STObject::getSortedFields (STObject const& objToSort)
{
std::vector<STBase const*> sf;
sf.reserve (objToSort.getCount ());
// Choose the fields that we need to sort.
for (detail::STVar const& elem : objToSort.v_)
{
// Pick out the fields and sort them.
STBase const& base = elem.get();
if ((base.getSType () != STI_NOTPRESENT) &&
base.getFName ().shouldInclude (true))
{
sf.push_back (&base);
}
}
// Sort the fields by fieldCode.
std::sort (sf.begin (), sf.end (),
[] (STBase const* a, STBase const* b) -> bool
{
return a->getFName ().fieldCode < b->getFName ().fieldCode;
});
// There should never be duplicate fields in an STObject. Verify that
// in debug mode.
assert (std::adjacent_find (sf.cbegin (), sf.cend ()) == sf.cend ());
return sf;
}
bool STObject::equivalentSTObjectSameTemplate (
STObject const& obj1, STObject const& obj2)
{
assert (obj1.mType != nullptr);
assert (obj1.mType == obj2.mType);
return std::equal (obj1.begin (), obj1.end (), obj2.begin (), obj2.end (),
[] (STBase const& st1, STBase const& st2)
{
return (st1.getSType() == st2.getSType()) &&
st1.isEquivalent (st2);
});
}
bool STObject::equivalentSTObject (STObject const& obj1, STObject const& obj2)
{
auto sf1 = getSortedFields (obj1);
auto sf2 = getSortedFields (obj2);
return std::equal (sf1.begin (), sf1.end (), sf2.begin (), sf2.end (),
[] (STBase const* st1, STBase const* st2)
{
return (st1->getSType() == st2->getSType()) &&
st1->isEquivalent (*st2);
});
}
} // ripple

View File

@@ -155,7 +155,7 @@ static Blob getSigningData (STTx const& that)
{
Serializer s;
s.add32 (HashPrefix::txSign);
that.add (s, false);
that.addWithoutSigningFields (s);
return s.getData();
}

View File

@@ -27,7 +27,7 @@ sign (STObject& st, HashPrefix const& prefix,
{
Serializer ss;
ss.add32(prefix);
st.add(ss, false);
st.addWithoutSigningFields(ss);
set(st, sfSignature,
sk.sign(ss.data(), ss.size()));
}
@@ -42,7 +42,7 @@ verify (STObject const& st,
return false;
Serializer ss;
ss.add32(prefix);
st.add(ss, false);
st.addWithoutSigningFields(ss);
return pk.verify(
ss.data(), ss.size(),
sig->data(), sig->size());