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

@@ -87,6 +87,8 @@ getRippleStateIndex (Account const& a, Account const& b, Currency const& currenc
uint256
getRippleStateIndex (Account const& a, Issue const& issue);
uint256
getSignerListIndex (Account const& account);
}
#endif

View File

@@ -0,0 +1,47 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#ifndef RIPPLE_PROTOCOL_INNER_OBJECT_FORMATS_H_INCLUDED
#define RIPPLE_PROTOCOL_INNER_OBJECT_FORMATS_H_INCLUDED
#include <ripple/protocol/KnownFormats.h>
namespace ripple {
/** Manages the list of known inner object formats.
*/
class InnerObjectFormats : public KnownFormats <int>
{
private:
void addCommonFields (Item& item);
public:
/** Create the object.
This will load the object will all the known inner object formats.
*/
InnerObjectFormats ();
static InnerObjectFormats const& getInstance ();
SOTemplate const* findSOTemplateBySField (SField const& sField) const;
};
} // ripple
#endif

View File

@@ -56,6 +56,8 @@ enum LedgerEntryType
ltTICKET = 'T',
ltSIGNER_LIST = 'S',
/* Deprecated. */
ltOFFER = 'o',
@@ -90,6 +92,7 @@ enum LedgerNameSpace
spaceAmendment = 'f',
spaceFee = 'e',
spaceTicket = 'T',
spaceSignerList = 'S',
// No longer used or supported. Left here to reserve the space and
// avoid accidental reuse of the space.

View File

@@ -295,6 +295,7 @@ extern SField const sfTransactionResult;
// 16-bit integers
extern SField const sfLedgerEntryType;
extern SField const sfTransactionType;
extern SField const sfSignerWeight;
// 32-bit integers (common)
extern SField const sfFlags;
@@ -331,6 +332,7 @@ extern SField const sfReserveBase;
extern SField const sfReserveIncrement;
extern SField const sfSetFlag;
extern SField const sfClearFlag;
extern SField const sfSignerQuorum;
// 64-bit integers
extern SField const sfIndexNext;
@@ -427,12 +429,13 @@ extern SField const sfFinalFields;
extern SField const sfNewFields;
extern SField const sfTemplateEntry;
extern SField const sfMemo;
extern SField const sfSignerEntry;
// array of objects
// ARRAY/1 is reserved for end of array
extern SField const sfSigningAccounts;
extern SField const sfTxnSignatures;
extern SField const sfSignatures;
extern SField const sfSignerEntries;
extern SField const sfTemplate;
extern SField const sfNecessary;
extern SField const sfSufficient;

View File

@@ -40,7 +40,7 @@ private:
list_type v_;
public:
// Read-only iteration
// Read-only iteration
class Items;
static char const* getCountedObjectName () { return "STArray"; }
@@ -135,6 +135,10 @@ public:
{
v_.clear ();
}
void reserve (std::size_t n)
{
v_.reserve (n);
}
void swap (STArray & a) noexcept
{
v_.swap (a.v_);

View File

@@ -133,7 +133,18 @@ public:
return v_.empty();
}
void reserve (std::size_t n)
{
v_.reserve (n);
}
bool setType (const SOTemplate & type);
enum ResultOfSetTypeFromSField : unsigned char
{typeSetFail, typeIsSet, noTemplate};
ResultOfSetTypeFromSField setTypeFromSField (SField const&);
bool isValidForType ();
bool isFieldAllowed (SField const&);
bool isFree () const

View File

@@ -44,6 +44,8 @@ public:
typedef std::shared_ptr<STTx> pointer;
typedef const std::shared_ptr<STTx>& ref;
static std::size_t const maxMultiSigners = 8;
public:
STTx () = delete;
STTx& operator= (STTx const& other) = delete;

View File

@@ -81,6 +81,8 @@ enum TER // aka TransactionEngineResult
temREDUNDANT,
temRIPPLE_EMPTY,
temDISABLED,
temBAD_SIGNER,
temBAD_QUORUM,
// An intermediate result used internally, should never be returned.
temUNCERTAIN,

View File

@@ -46,6 +46,7 @@ enum TxType
no_longer_used = 9,
ttTICKET_CREATE = 10,
ttTICKET_CANCEL = 11,
ttSIGNER_LIST_SET = 12,
ttTRUST_SET = 20,

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)

View File

@@ -0,0 +1,204 @@
//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
//==============================================================================
#include <BeastConfig.h>
#include <ripple/protocol/InnerObjectFormats.h>
#include <ripple/protocol/ErrorCodes.h> // RPC::containsError
#include <ripple/json/json_reader.h> // Json::Reader
#include <ripple/protocol/STParsedJSON.h> // STParsedJSONObject
#include <beast/unit_test/suite.h>
namespace ripple {
namespace InnerObjectFormatsUnitTestDetail
{
struct TestJSONTxt
{
std::string const txt;
bool const expectFail;
};
static TestJSONTxt const testArray[] =
{
// Valid SignerEntry
{R"({
"Account" : "rDg53Haik2475DJx8bjMDSDPj4VX7htaMd",
"SignerEntries" :
[
{
"SignerEntry" :
{
"Account" : "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"SignerWeight" : 4
}
},
{
"SignerEntry" :
{
"Account" : "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
"SignerWeight" : 3
}
}
],
"SignerQuorum" : 7,
"TransactionType" : "SignerListSet"
})", false
},
// SignerEntry missing Account
{R"({
"Account" : "rDg53Haik2475DJx8bjMDSDPj4VX7htaMd",
"SignerEntries" :
[
{
"SignerEntry" :
{
"Account" : "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"SignerWeight" : 4
}
},
{
"SignerEntry" :
{
"SignerWeight" : 3
}
}
],
"SignerQuorum" : 7,
"TransactionType" : "SignerListSet"
})", true
},
// SignerEntry missing SignerWeight
{R"({
"Account" : "rDg53Haik2475DJx8bjMDSDPj4VX7htaMd",
"SignerEntries" :
[
{
"SignerEntry" :
{
"Account" : "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"SignerWeight" : 4
}
},
{
"SignerEntry" :
{
"Account" : "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
}
}
],
"SignerQuorum" : 7,
"TransactionType" : "SignerListSet"
})", true
},
// SignerEntry with unexpected Amount
{R"({
"Account" : "rDg53Haik2475DJx8bjMDSDPj4VX7htaMd",
"SignerEntries" :
[
{
"SignerEntry" :
{
"Account" : "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"SignerWeight" : 4
}
},
{
"SignerEntry" :
{
"Amount" : "1000000",
"Account" : "rPcNzota6B8YBokhYtcTNqQVCngtbnWfux",
"SignerWeight" : 3
}
}
],
"SignerQuorum" : 7,
"TransactionType" : "SignerListSet"
})", true
},
// SignerEntry with no Account and unexpected Amount
{R"({
"Account" : "rDg53Haik2475DJx8bjMDSDPj4VX7htaMd",
"SignerEntries" :
[
{
"SignerEntry" :
{
"Account" : "rnUy2SHTrB9DubsPmkJZUXTf5FcNDGrYEA",
"SignerWeight" : 4
}
},
{
"SignerEntry" :
{
"Amount" : "10000000",
"SignerWeight" : 3
}
}
],
"SignerQuorum" : 7,
"TransactionType" : "SignerListSet"
})", true
},
};
} // namespace InnerObjectFormatsUnitTestDetail
class InnerObjectFormatsParsedJSON_test : public beast::unit_test::suite
{
public:
void run()
{
using namespace InnerObjectFormatsUnitTestDetail;
for (auto const& test : testArray)
{
Json::Value req;
Json::Reader ().parse (test.txt, req);
if (RPC::contains_error (req))
{
throw std::runtime_error (
"Internal InnerObjectFormatsParsedJSON error. Bad JSON.");
}
STParsedJSONObject parsed ("request", req);
bool const noObj = parsed.object == boost::none;
if ( noObj == test.expectFail )
{
pass ();
}
else
{
std::string errStr ("Unexpected STParsedJSON result on:\n");
errStr += test.txt;
fail (errStr);
}
}
}
};
BEAST_DEFINE_TESTSUITE(InnerObjectFormatsParsedJSON,ripple_app,ripple);
} // ripple