SignerListSet txn and InnerObjectFormats (RIPD-182):

Add support for the SignerListSet transaction as a step toward
multi-sign support.

As part of the SignerListSet implementation, add InnerObjectFormat
templates (similar to TxFormats and LedgerFormats) and enforce them
in STObject, STArray, and STParsedJSON.
This commit is contained in:
Scott Schurr
2015-02-06 10:22:36 -08:00
committed by Vinnie Falco
parent 92799187ed
commit 64ebd64d2b
36 changed files with 1290 additions and 135 deletions

View File

@@ -219,4 +219,15 @@ getRippleStateIndex (Account const& a, Issue const& issue)
return getRippleStateIndex (a, issue.account, issue.currency);
}
uint256
getSignerListIndex (Account const& account)
{
Serializer s (22);
s.add16 (spaceSignerList); // 2
s.add160 (account); // 20
return s.getSHA512Half ();
}
} // namespace ripple

View File

@@ -0,0 +1,55 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <BeastConfig.h>
#include <ripple/protocol/InnerObjectFormats.h>
namespace ripple {
InnerObjectFormats::InnerObjectFormats ()
{
add (sfSignerEntry.getJsonName ().c_str (), sfSignerEntry.getCode ())
<< SOElement (sfAccount, SOE_REQUIRED)
<< SOElement (sfSignerWeight, SOE_REQUIRED)
;
}
void InnerObjectFormats::addCommonFields (Item& item)
{
}
InnerObjectFormats const&
InnerObjectFormats::getInstance ()
{
static InnerObjectFormats instance;
return instance;
}
SOTemplate const*
InnerObjectFormats::findSOTemplateBySField (SField const& sField) const
{
SOTemplate const* ret = nullptr;
auto itemPtr = findByType (sField.getCode ());
if (itemPtr)
ret = &(itemPtr->elements);
return ret;
}
} // ripple

View File

@@ -105,6 +105,14 @@ LedgerFormats::LedgerFormats ()
<< SOElement (sfTarget, SOE_OPTIONAL)
<< SOElement (sfExpiration, SOE_OPTIONAL)
;
// All three fields are SOE_REQUIRED because there is always a
// SignerEntries. If there are no SignerEntries the node is deleted.
add ("SignerList", ltSIGNER_LIST)
<< SOElement (sfOwnerNode, SOE_REQUIRED)
<< SOElement (sfSignerQuorum, SOE_REQUIRED)
<< SOElement (sfSignerEntries, SOE_REQUIRED)
;
}
void LedgerFormats::addCommonFields (Item& item)

View File

@@ -83,6 +83,7 @@ SField const sfTransactionResult = make::one(&sfTransactionResult, STI_UINT8, 3,
// 16-bit integers
SField const sfLedgerEntryType = make::one(&sfLedgerEntryType, STI_UINT16, 1, "LedgerEntryType", SField::sMD_Never);
SField const sfTransactionType = make::one(&sfTransactionType, STI_UINT16, 2, "TransactionType");
SField const sfSignerWeight = make::one(&sfSignerWeight, STI_UINT16, 3, "SignerWeight");
// 32-bit integers (common)
SField const sfFlags = make::one(&sfFlags, STI_UINT32, 2, "Flags");
@@ -119,6 +120,7 @@ SField const sfReserveBase = make::one(&sfReserveBase, STI_UINT3
SField const sfReserveIncrement = make::one(&sfReserveIncrement, STI_UINT32, 32, "ReserveIncrement");
SField const sfSetFlag = make::one(&sfSetFlag, STI_UINT32, 33, "SetFlag");
SField const sfClearFlag = make::one(&sfClearFlag, STI_UINT32, 34, "ClearFlag");
SField const sfSignerQuorum = make::one(&sfSignerQuorum, STI_UINT32, 35, "SignerQuorum");
// 64-bit integers
SField const sfIndexNext = make::one(&sfIndexNext, STI_UINT64, 1, "IndexNext");
@@ -215,12 +217,13 @@ SField const sfFinalFields = make::one(&sfFinalFields, STI_OBJEC
SField const sfNewFields = make::one(&sfNewFields, STI_OBJECT, 8, "NewFields");
SField const sfTemplateEntry = make::one(&sfTemplateEntry, STI_OBJECT, 9, "TemplateEntry");
SField const sfMemo = make::one(&sfMemo, STI_OBJECT, 10, "Memo");
SField const sfSignerEntry = make::one(&sfSignerEntry, STI_OBJECT, 11, "SignerEntry");
// array of objects
// ARRAY/1 is reserved for end of array
SField const sfSigningAccounts = make::one(&sfSigningAccounts, STI_ARRAY, 2, "SigningAccounts");
SField const sfTxnSignatures = make::one(&sfTxnSignatures, STI_ARRAY, 3, "TxnSignatures", SField::sMD_Default, SField::notSigning);
SField const sfSignatures = make::one(&sfSignatures, STI_ARRAY, 4, "Signatures");
SField const sfSignerEntries = make::one(&sfSignerEntries, STI_ARRAY, 4, "SignerEntries");
SField const sfTemplate = make::one(&sfTemplate, STI_ARRAY, 5, "Template");
SField const sfNecessary = make::one(&sfNecessary, STI_ARRAY, 6, "Necessary");
SField const sfSufficient = make::one(&sfSufficient, STI_ARRAY, 7, "Sufficient");

View File

@@ -97,6 +97,11 @@ STArray::STArray (SerialIter& sit, SField const& f)
v_.emplace_back(fn);
v_.back().set (sit, 1);
if (v_.back().setTypeFromSField (fn) == STObject::typeSetFail)
{
throw std::runtime_error ("Malformed object in array");
}
}
}

View File

@@ -21,6 +21,7 @@
#include <ripple/basics/Log.h>
#include <ripple/json/json_reader.h>
#include <ripple/json/to_string.h>
#include <ripple/protocol/InnerObjectFormats.h>
#include <ripple/protocol/STBase.h>
#include <ripple/protocol/STAccount.h>
#include <ripple/protocol/STArray.h>
@@ -119,7 +120,7 @@ bool STObject::setType (const SOTemplate& type)
{
WriteLog (lsWARNING, STObject) <<
"setType( " << getFName ().getName () <<
") invalid default " << e->e_field.fieldName;
" ) invalid default " << e->e_field.fieldName;
valid = false;
}
v.emplace_back(std::move(*iter));
@@ -131,7 +132,7 @@ bool STObject::setType (const SOTemplate& type)
{
WriteLog (lsWARNING, STObject) <<
"setType( " << getFName ().getName () <<
") invalid missing " << e->e_field.fieldName;
" ) invalid missing " << e->e_field.fieldName;
valid = false;
}
v.emplace_back(detail::nonPresentObject, e->e_field);
@@ -144,7 +145,7 @@ bool STObject::setType (const SOTemplate& type)
{
WriteLog (lsWARNING, STObject) <<
"setType( " << getFName ().getName () <<
") invalid leftover " << e->getFName ().getName ();
" ) invalid leftover " << e->getFName ().getName ();
valid = false;
}
}
@@ -154,6 +155,20 @@ bool STObject::setType (const SOTemplate& type)
return valid;
}
STObject::ResultOfSetTypeFromSField
STObject::setTypeFromSField (SField const& sField)
{
ResultOfSetTypeFromSField ret = noTemplate;
SOTemplate const* elements =
InnerObjectFormats::getInstance ().findSOTemplateBySField (sField);
if (elements)
{
ret = setType (*elements) ? typeIsSet : typeSetFail;
}
return ret;
}
bool STObject::isValidForType ()
{
auto it = v_.begin();
@@ -219,6 +234,13 @@ bool STObject::set (SerialIter& sit, int depth)
// Unflatten the field
v_.emplace_back(sit, fn);
// If the object type has a known SOTemplate then set it.
STObject* const obj = dynamic_cast <STObject*> (&(v_.back().get()));
if (obj && (obj->setTypeFromSField (fn) == typeSetFail))
{
throw std::runtime_error ("field deserialization error");
}
}
}

View File

@@ -138,6 +138,26 @@ static Json::Value singleton_expected (std::string const& object,
"]' must be an object with a single key/object value.");
}
static Json::Value serialization_error (SField const& sField)
{
return RPC::make_error (rpcINVALID_PARAMS,
"Object '" + sField.getName () + "' failed to serialize.");
}
static Json::Value template_mismatch (SField const& sField)
{
return RPC::make_error (rpcINVALID_PARAMS,
"Object '" + sField.getName () +
"' contents did not meet requirements for that type.");
}
static Json::Value
non_object_in_array (std::string const& item, Json::UInt index)
{
return RPC::make_error (rpcINVALID_PARAMS,
"Item '" + item + "' at index " + std::to_string (index) +
" is not an object. Arrays may only contain objects.");
}
// This function is used by parseObject to parse any JSON type that doesn't
// recurse. Everything represented here is a leaf-type.
@@ -766,6 +786,13 @@ static boost::optional <STObject> parseObject (
}
}
// Some inner object types have templates. Attempt to apply that.
if (data.setTypeFromSField (inName) == STObject::typeSetFail)
{
error = template_mismatch (inName);
return boost::none;
}
return std::move (data);
}
@@ -823,10 +850,17 @@ static boost::optional <detail::STVar> parseArray (
auto ret = parseObject (ss.str (), objectFields,
nameField, depth + 1, error);
if (! ret)
{
std::string errMsg = error["error_message"].asString ();
error["error_message"] = "Error at '" + ss.str () +
"'. " + errMsg;
return boost::none;
}
if (! ret ||
(ret->getFName().fieldType != STI_OBJECT))
if (ret->getFName().fieldType != STI_OBJECT)
{
error = non_object_in_array (ss.str(), i);
return boost::none;
}

View File

@@ -102,6 +102,7 @@ bool transResultInfo (TER code, std::string& token, std::string& text)
{ temBAD_OFFER, "temBAD_OFFER", "Malformed: Bad offer." },
{ temBAD_PATH, "temBAD_PATH", "Malformed: Bad path." },
{ temBAD_PATH_LOOP, "temBAD_PATH_LOOP", "Malformed: Loop in path." },
{ temBAD_QUORUM, "temBAD_QUORUM", "Malformed: Quorum is unreachable." },
{ temBAD_SEND_XRP_LIMIT, "temBAD_SEND_XRP_LIMIT", "Malformed: Limit quality is not allowed for XRP to XRP." },
{ temBAD_SEND_XRP_MAX, "temBAD_SEND_XRP_MAX", "Malformed: Send max is not allowed for XRP to XRP." },
{ temBAD_SEND_XRP_NO_DIRECT,"temBAD_SEND_XRP_NO_DIRECT","Malformed: No Ripple direct is not allowed for XRP to XRP." },
@@ -109,6 +110,7 @@ bool transResultInfo (TER code, std::string& token, std::string& text)
{ temBAD_SEND_XRP_PATHS, "temBAD_SEND_XRP_PATHS", "Malformed: Paths are not allowed for XRP to XRP." },
{ temBAD_SEQUENCE, "temBAD_SEQUENCE", "Malformed: Sequence is not in the past." },
{ temBAD_SIGNATURE, "temBAD_SIGNATURE", "Malformed: Bad signature." },
{ temBAD_SIGNER, "temBAD_SIGNER", "Malformed: A signer may not duplicate account or other signers"},
{ temBAD_SRC_ACCOUNT, "temBAD_SRC_ACCOUNT", "Malformed: Bad source account." },
{ temBAD_TRANSFER_RATE, "temBAD_TRANSFER_RATE", "Malformed: Transfer rate must be >= 1.0" },
{ temDST_IS_SRC, "temDST_IS_SRC", "Destination may not be source." },

View File

@@ -87,6 +87,13 @@ TxFormats::TxFormats ()
add ("TicketCancel", ttTICKET_CANCEL)
<< SOElement (sfTicketID, SOE_REQUIRED)
;
// The SignerEntries are optional because a SignerList is deleted by
// setting the SignerQuorum to zero and omitting SignerEntries.
add ("SignerListSet", ttSIGNER_LIST_SET)
<< SOElement (sfSignerQuorum, SOE_REQUIRED)
<< SOElement (sfSignerEntries, SOE_OPTIONAL)
;
}
void TxFormats::addCommonFields (Item& item)