Allow manifests to include an optional 'domain' field:

The new 'Domain' field allows validator operators to associate a domain
name with their manifest in a transparent and independently verifiable
fashion.

It is important to point out that while this system can cryptographically
prove that a particular validator claims to be associated with a domain
it does *NOT* prove that the validator is, actually, associated with that
domain.

Domain owners will have to cryptographically attest to operating particular
validators that claim to be associated with that domain. One option for
doing so would be by making available a file over HTTPS under the domain
being claimed, which is verified separately (e.g. by ensuring that the
certificate used to serve the file matches the domain being claimed) and
which contains the long-term master public keys of validator(s) associated
with that domain.

Credit for an early prototype of this idea goes to GitHub user @cryptobrad
who introduced a PR that would allow a validator list publisher to attest
that a particular validator was associated with a domain. The idea may be
worth revisiting as a way of verifying the domain name claimed by the
validator's operator.
This commit is contained in:
Nik Bougalis
2018-08-23 22:42:34 -07:00
parent e239eed6de
commit 88cb0e5928
20 changed files with 782 additions and 478 deletions

View File

@@ -77,40 +77,27 @@ namespace ripple {
struct Manifest
{
/// The manifest in serialized form.
std::string serialized;
/// The master key associated with this manifest.
PublicKey masterKey;
/// The ephemeral key associated with this manifest.
PublicKey signingKey;
std::uint32_t sequence;
Manifest(std::string s, PublicKey pk, PublicKey spk, std::uint32_t seq);
/// The sequence number of this manifest.
std::uint32_t sequence = 0;
/// The domain, if one was specified in the manifest; empty otherwise.
std::string domain;
Manifest() = default;
Manifest(Manifest const& other) = delete;
Manifest& operator=(Manifest const& other) = delete;
Manifest(Manifest&& other) = default;
Manifest& operator=(Manifest&& other) = default;
inline bool
operator==(Manifest const& rhs) const
{
return sequence == rhs.sequence && masterKey == rhs.masterKey &&
signingKey == rhs.signingKey && serialized == rhs.serialized;
}
inline bool
operator!=(Manifest const& rhs) const
{
return !(*this == rhs);
}
/** Constructs Manifest from serialized string
@param s Serialized manifest string
@return `boost::none` if string is invalid
@note This does not verify manifest signatures.
`Manifest::verify` should be called after constructing manifest.
*/
static boost::optional<Manifest> make_Manifest(std::string s);
/// Returns `true` if manifest signature is valid
bool verify () const;
@@ -127,6 +114,55 @@ struct Manifest
Blob getMasterSignature () const;
};
/** Constructs Manifest from serialized string
@param s Serialized manifest string
@return `boost::none` if string is invalid
@note This does not verify manifest signatures.
`Manifest::verify` should be called after constructing manifest.
*/
/** @{ */
boost::optional<Manifest>
deserializeManifest(Slice s);
inline
boost::optional<Manifest>
deserializeManifest(std::string const& s)
{
return deserializeManifest(makeSlice(s));
}
template <class T, class = std::enable_if_t<
std::is_same<T, char>::value || std::is_same<T, unsigned char>::value>>
boost::optional<Manifest>
deserializeManifest(std::vector<T> const& v)
{
return deserializeManifest(makeSlice(v));
}
/** @} */
inline
bool
operator==(Manifest const& lhs, Manifest const& rhs)
{
// In theory, comparing the two serialized strings should be
// sufficient.
return lhs.sequence == rhs.sequence &&
lhs.masterKey == rhs.masterKey &&
lhs.signingKey == rhs.signingKey &&
lhs.domain == rhs.domain &&
lhs.serialized == rhs.serialized;
}
inline
bool
operator!=(Manifest const& lhs, Manifest const& rhs)
{
return !(lhs == rhs);
}
struct ValidatorToken
{
std::string manifest;

View File

@@ -27,44 +27,128 @@
#include <ripple/json/json_reader.h>
#include <ripple/protocol/PublicKey.h>
#include <ripple/protocol/Sign.h>
#include <boost/algorithm/clamp.hpp>
#include <boost/regex.hpp>
#include <numeric>
#include <stdexcept>
namespace ripple {
boost::optional<Manifest>
Manifest::make_Manifest (std::string s)
boost::optional<Manifest> deserializeManifest(Slice s)
{
if (s.empty())
return boost::none;
static SOTemplate const manifestFormat (
[](SOTemplate& t)
{
// A manifest must include:
// - the master public key
t.push_back (SOElement (sfPublicKey, SOE_REQUIRED));
// - a signature with that public key
t.push_back (SOElement (sfMasterSignature, SOE_REQUIRED));
// - a sequence number
t.push_back (SOElement (sfSequence, SOE_REQUIRED));
// It may, optionally, contain:
// - a version number which defaults to 0
t.push_back (SOElement (sfVersion, SOE_DEFAULT));
// - a domain name
t.push_back (SOElement (sfDomain, SOE_OPTIONAL));
// - an ephemeral signing key that can be changed as necessary
t.push_back (SOElement (sfSigningPubKey, SOE_OPTIONAL));
// - a signature using the ephemeral signing key, if it is present
t.push_back (SOElement (sfSignature, SOE_OPTIONAL));
});
try
{
STObject st (sfGeneric);
SerialIter sit (s.data (), s.size ());
st.set (sit);
SerialIter sit{ s };
STObject st{ sit, sfGeneric };
st.applyTemplate(manifestFormat);
// We only understand "version 0" manifests at this time:
if (st.isFieldPresent(sfVersion) && st.getFieldU16(sfVersion) != 0)
return boost::none;
auto const pk = st.getFieldVL (sfPublicKey);
if (! publicKeyType (makeSlice(pk)))
return boost::none;
auto const opt_seq = get (st, sfSequence);
auto const opt_msig = get (st, sfMasterSignature);
if (!opt_seq || !opt_msig)
return boost::none;
Manifest m;
m.serialized.assign(reinterpret_cast<char const*>(s.data()), s.size());
m.masterKey = PublicKey(makeSlice(pk));
m.sequence = st.getFieldU32 (sfSequence);
// Signing key and signature are not required for
// master key revocations
if (*opt_seq != std::numeric_limits<std::uint32_t>::max ())
if (st.isFieldPresent(sfDomain))
{
auto const spk = st.getFieldVL (sfSigningPubKey);
if (! publicKeyType (makeSlice(spk)))
auto const d = st.getFieldVL(sfDomain);
// The domain must be between 4 and 128 characters long
if (boost::algorithm::clamp(d.size(), 4, 128) != d.size())
return boost::none;
if (! get (st, sfSignature))
m.domain.assign (reinterpret_cast<char const*>(d.data()), d.size());
// This regular expression should do a decent job of weeding out
// obviously wrong domain names but it isn't perfect. It does not
// really support IDNs. If this turns out to be an issue, a more
// thorough regex can be used or this check can just be removed.
static boost::regex const re(
"^" // Beginning of line
"(" // Beginning of a segment
"(?!-)" // - must not begin with '-'
"[a-zA-Z0-9-]{1,63}" // - only alphanumeric and '-'
"(?<!-)" // - must not end with '-'
"\\." // segment separator
")+" // 1 or more segments
"[A-Za-z]{2,63}" // TLD
"$" // End of line
, boost::regex_constants::optimize);
if (!boost::regex_match(m.domain, re))
return boost::none;
return Manifest (std::move (s), PublicKey (makeSlice(pk)),
PublicKey (makeSlice(spk)), *opt_seq);
}
return Manifest (std::move (s), PublicKey (makeSlice(pk)),
PublicKey(), *opt_seq);
bool const hasEphemeralKey = st.isFieldPresent(sfSigningPubKey);
bool const hasEphemeralSig = st.isFieldPresent(sfSignature);
if (m.revoked())
{
// Revocation manifests should not specify a new signing key
// or a signing key signature.
if (hasEphemeralKey)
return boost::none;
if (hasEphemeralSig)
return boost::none;
}
else
{
// Regular manifests should contain a signing key and an
// associated signature.
if (!hasEphemeralKey)
return boost::none;
if (!hasEphemeralSig)
return boost::none;
auto const spk = st.getFieldVL(sfSigningPubKey);
if (!publicKeyType (makeSlice(spk)))
return boost::none;
m.signingKey = PublicKey(makeSlice(spk));
}
return std::move(m);
}
catch (std::exception const&)
{
@@ -101,17 +185,6 @@ Stream& logMftAct (
return s;
}
Manifest::Manifest (std::string s,
PublicKey pk,
PublicKey spk,
std::uint32_t seq)
: serialized (std::move (s))
, masterKey (std::move (pk))
, signingKey (std::move (spk))
, sequence (seq)
{
}
bool Manifest::verify () const
{
STObject st (sfGeneric);
@@ -194,7 +267,7 @@ ValidatorToken::make_ValidatorToken(std::vector<std::string> const& tokenBlob)
token["validation_secret_key"].isString())
{
auto const ret = strUnHex (token["validation_secret_key"].asString());
if (! ret.second || ! ret.first.size ())
if (! ret.second || ret.first.empty())
return boost::none;
return ValidatorToken(
@@ -355,7 +428,7 @@ ManifestCache::load (
{
std::string serialized;
convert (sociRawData, serialized);
if (auto mo = Manifest::make_Manifest (std::move (serialized)))
if (auto mo = deserializeManifest(serialized))
{
if (!mo->verify())
{
@@ -384,8 +457,7 @@ ManifestCache::load (
if (! configManifest.empty())
{
auto mo = Manifest::make_Manifest (
base64_decode(configManifest));
auto mo = deserializeManifest(base64_decode(configManifest));
if (! mo)
{
JLOG (j_.error()) << "Malformed validator_token in config";
@@ -419,8 +491,7 @@ ManifestCache::load (
for (auto const& line : configRevocation)
revocationStr += beast::rfc2616::trim(line);
auto mo = Manifest::make_Manifest (
base64_decode(revocationStr));
auto mo = deserializeManifest(base64_decode(revocationStr));
if (! mo || ! mo->revoked() ||
applyManifest (std::move(*mo)) == ManifestDisposition::invalid)

View File

@@ -44,8 +44,7 @@ ValidatorKeys::ValidatorKeys(Config const& config, beast::Journal j)
{
auto const pk = derivePublicKey(
KeyType::secp256k1, token->validationSecret);
auto const m = Manifest::make_Manifest(
base64_decode(token->manifest));
auto const m = deserializeManifest(base64_decode(token->manifest));
if (! m || pk != m->signingKey)
{

View File

@@ -291,8 +291,7 @@ ValidatorList::applyList (
for (auto const& valManifest : manifests)
{
auto m = Manifest::make_Manifest (
base64_decode(valManifest));
auto m = deserializeManifest(base64_decode(valManifest));
if (! m || ! keyListings_.count (m->masterKey))
{
@@ -322,7 +321,7 @@ ValidatorList::verify (
std::string const& blob,
std::string const& signature)
{
auto m = Manifest::make_Manifest (base64_decode(manifest));
auto m = deserializeManifest(base64_decode(manifest));
if (! m || ! publisherLists_.count (m->masterKey))
return ListDisposition::untrusted;

View File

@@ -44,27 +44,27 @@ invoke_preflight (PreflightContext const& ctx)
{
switch(ctx.tx.getTxnType())
{
case ttACCOUNT_SET: return SetAccount ::preflight(ctx);
case ttCHECK_CANCEL: return CancelCheck ::preflight(ctx);
case ttCHECK_CASH: return CashCheck ::preflight(ctx);
case ttCHECK_CREATE: return CreateCheck ::preflight(ctx);
case ttDEPOSIT_PREAUTH: return DepositPreauth ::preflight(ctx);
case ttOFFER_CANCEL: return CancelOffer ::preflight(ctx);
case ttOFFER_CREATE: return CreateOffer ::preflight(ctx);
case ttESCROW_CREATE: return EscrowCreate ::preflight(ctx);
case ttESCROW_FINISH: return EscrowFinish ::preflight(ctx);
case ttESCROW_CANCEL: return EscrowCancel ::preflight(ctx);
case ttPAYCHAN_CLAIM: return PayChanClaim ::preflight(ctx);
case ttPAYCHAN_CREATE: return PayChanCreate ::preflight(ctx);
case ttPAYCHAN_FUND: return PayChanFund ::preflight(ctx);
case ttPAYMENT: return Payment ::preflight(ctx);
case ttREGULAR_KEY_SET: return SetRegularKey ::preflight(ctx);
case ttSIGNER_LIST_SET: return SetSignerList ::preflight(ctx);
case ttTICKET_CANCEL: return CancelTicket ::preflight(ctx);
case ttTICKET_CREATE: return CreateTicket ::preflight(ctx);
case ttTRUST_SET: return SetTrust ::preflight(ctx);
case ttACCOUNT_SET: return SetAccount ::preflight(ctx);
case ttCHECK_CANCEL: return CancelCheck ::preflight(ctx);
case ttCHECK_CASH: return CashCheck ::preflight(ctx);
case ttCHECK_CREATE: return CreateCheck ::preflight(ctx);
case ttDEPOSIT_PREAUTH: return DepositPreauth ::preflight(ctx);
case ttOFFER_CANCEL: return CancelOffer ::preflight(ctx);
case ttOFFER_CREATE: return CreateOffer ::preflight(ctx);
case ttESCROW_CREATE: return EscrowCreate ::preflight(ctx);
case ttESCROW_FINISH: return EscrowFinish ::preflight(ctx);
case ttESCROW_CANCEL: return EscrowCancel ::preflight(ctx);
case ttPAYCHAN_CLAIM: return PayChanClaim ::preflight(ctx);
case ttPAYCHAN_CREATE: return PayChanCreate ::preflight(ctx);
case ttPAYCHAN_FUND: return PayChanFund ::preflight(ctx);
case ttPAYMENT: return Payment ::preflight(ctx);
case ttREGULAR_KEY_SET: return SetRegularKey ::preflight(ctx);
case ttSIGNER_LIST_SET: return SetSignerList ::preflight(ctx);
case ttTICKET_CANCEL: return CancelTicket ::preflight(ctx);
case ttTICKET_CREATE: return CreateTicket ::preflight(ctx);
case ttTRUST_SET: return SetTrust ::preflight(ctx);
case ttAMENDMENT:
case ttFEE: return Change ::preflight(ctx);
case ttFEE: return Change ::preflight(ctx);
default:
assert(false);
return temUNKNOWN;
@@ -113,27 +113,27 @@ invoke_preclaim (PreclaimContext const& ctx)
{
switch(ctx.tx.getTxnType())
{
case ttACCOUNT_SET: return invoke_preclaim<SetAccount>(ctx);
case ttCHECK_CANCEL: return invoke_preclaim<CancelCheck>(ctx);
case ttCHECK_CASH: return invoke_preclaim<CashCheck>(ctx);
case ttCHECK_CREATE: return invoke_preclaim<CreateCheck>(ctx);
case ttDEPOSIT_PREAUTH: return invoke_preclaim<DepositPreauth>(ctx);
case ttOFFER_CANCEL: return invoke_preclaim<CancelOffer>(ctx);
case ttOFFER_CREATE: return invoke_preclaim<CreateOffer>(ctx);
case ttESCROW_CREATE: return invoke_preclaim<EscrowCreate>(ctx);
case ttESCROW_FINISH: return invoke_preclaim<EscrowFinish>(ctx);
case ttESCROW_CANCEL: return invoke_preclaim<EscrowCancel>(ctx);
case ttPAYCHAN_CLAIM: return invoke_preclaim<PayChanClaim>(ctx);
case ttPAYCHAN_CREATE: return invoke_preclaim<PayChanCreate>(ctx);
case ttPAYCHAN_FUND: return invoke_preclaim<PayChanFund>(ctx);
case ttPAYMENT: return invoke_preclaim<Payment>(ctx);
case ttREGULAR_KEY_SET: return invoke_preclaim<SetRegularKey>(ctx);
case ttSIGNER_LIST_SET: return invoke_preclaim<SetSignerList>(ctx);
case ttTICKET_CANCEL: return invoke_preclaim<CancelTicket>(ctx);
case ttTICKET_CREATE: return invoke_preclaim<CreateTicket>(ctx);
case ttTRUST_SET: return invoke_preclaim<SetTrust>(ctx);
case ttACCOUNT_SET: return invoke_preclaim<SetAccount>(ctx);
case ttCHECK_CANCEL: return invoke_preclaim<CancelCheck>(ctx);
case ttCHECK_CASH: return invoke_preclaim<CashCheck>(ctx);
case ttCHECK_CREATE: return invoke_preclaim<CreateCheck>(ctx);
case ttDEPOSIT_PREAUTH: return invoke_preclaim<DepositPreauth>(ctx);
case ttOFFER_CANCEL: return invoke_preclaim<CancelOffer>(ctx);
case ttOFFER_CREATE: return invoke_preclaim<CreateOffer>(ctx);
case ttESCROW_CREATE: return invoke_preclaim<EscrowCreate>(ctx);
case ttESCROW_FINISH: return invoke_preclaim<EscrowFinish>(ctx);
case ttESCROW_CANCEL: return invoke_preclaim<EscrowCancel>(ctx);
case ttPAYCHAN_CLAIM: return invoke_preclaim<PayChanClaim>(ctx);
case ttPAYCHAN_CREATE: return invoke_preclaim<PayChanCreate>(ctx);
case ttPAYCHAN_FUND: return invoke_preclaim<PayChanFund>(ctx);
case ttPAYMENT: return invoke_preclaim<Payment>(ctx);
case ttREGULAR_KEY_SET: return invoke_preclaim<SetRegularKey>(ctx);
case ttSIGNER_LIST_SET: return invoke_preclaim<SetSignerList>(ctx);
case ttTICKET_CANCEL: return invoke_preclaim<CancelTicket>(ctx);
case ttTICKET_CREATE: return invoke_preclaim<CreateTicket>(ctx);
case ttTRUST_SET: return invoke_preclaim<SetTrust>(ctx);
case ttAMENDMENT:
case ttFEE: return invoke_preclaim<Change>(ctx);
case ttFEE: return invoke_preclaim<Change>(ctx);
default:
assert(false);
return temUNKNOWN;
@@ -148,27 +148,27 @@ invoke_calculateBaseFee(
{
switch (tx.getTxnType())
{
case ttACCOUNT_SET: return SetAccount::calculateBaseFee(view, tx);
case ttCHECK_CANCEL: return CancelCheck::calculateBaseFee(view, tx);
case ttCHECK_CASH: return CashCheck::calculateBaseFee(view, tx);
case ttCHECK_CREATE: return CreateCheck::calculateBaseFee(view, tx);
case ttDEPOSIT_PREAUTH: return DepositPreauth::calculateBaseFee(view, tx);
case ttOFFER_CANCEL: return CancelOffer::calculateBaseFee(view, tx);
case ttOFFER_CREATE: return CreateOffer::calculateBaseFee(view, tx);
case ttESCROW_CREATE: return EscrowCreate::calculateBaseFee(view, tx);
case ttESCROW_FINISH: return EscrowFinish::calculateBaseFee(view, tx);
case ttESCROW_CANCEL: return EscrowCancel::calculateBaseFee(view, tx);
case ttPAYCHAN_CLAIM: return PayChanClaim::calculateBaseFee(view, tx);
case ttPAYCHAN_CREATE: return PayChanCreate::calculateBaseFee(view, tx);
case ttPAYCHAN_FUND: return PayChanFund::calculateBaseFee(view, tx);
case ttPAYMENT: return Payment::calculateBaseFee(view, tx);
case ttREGULAR_KEY_SET: return SetRegularKey::calculateBaseFee(view, tx);
case ttSIGNER_LIST_SET: return SetSignerList::calculateBaseFee(view, tx);
case ttTICKET_CANCEL: return CancelTicket::calculateBaseFee(view, tx);
case ttTICKET_CREATE: return CreateTicket::calculateBaseFee(view, tx);
case ttTRUST_SET: return SetTrust::calculateBaseFee(view, tx);
case ttACCOUNT_SET: return SetAccount::calculateBaseFee(view, tx);
case ttCHECK_CANCEL: return CancelCheck::calculateBaseFee(view, tx);
case ttCHECK_CASH: return CashCheck::calculateBaseFee(view, tx);
case ttCHECK_CREATE: return CreateCheck::calculateBaseFee(view, tx);
case ttDEPOSIT_PREAUTH: return DepositPreauth::calculateBaseFee(view, tx);
case ttOFFER_CANCEL: return CancelOffer::calculateBaseFee(view, tx);
case ttOFFER_CREATE: return CreateOffer::calculateBaseFee(view, tx);
case ttESCROW_CREATE: return EscrowCreate::calculateBaseFee(view, tx);
case ttESCROW_FINISH: return EscrowFinish::calculateBaseFee(view, tx);
case ttESCROW_CANCEL: return EscrowCancel::calculateBaseFee(view, tx);
case ttPAYCHAN_CLAIM: return PayChanClaim::calculateBaseFee(view, tx);
case ttPAYCHAN_CREATE: return PayChanCreate::calculateBaseFee(view, tx);
case ttPAYCHAN_FUND: return PayChanFund::calculateBaseFee(view, tx);
case ttPAYMENT: return Payment::calculateBaseFee(view, tx);
case ttREGULAR_KEY_SET: return SetRegularKey::calculateBaseFee(view, tx);
case ttSIGNER_LIST_SET: return SetSignerList::calculateBaseFee(view, tx);
case ttTICKET_CANCEL: return CancelTicket::calculateBaseFee(view, tx);
case ttTICKET_CREATE: return CreateTicket::calculateBaseFee(view, tx);
case ttTRUST_SET: return SetTrust::calculateBaseFee(view, tx);
case ttAMENDMENT:
case ttFEE: return Change::calculateBaseFee(view, tx);
case ttFEE: return Change::calculateBaseFee(view, tx);
default:
assert(false);
return 0;
@@ -194,25 +194,25 @@ invoke_calculateConsequences(STTx const& tx)
{
switch (tx.getTxnType())
{
case ttACCOUNT_SET: return invoke_calculateConsequences<SetAccount>(tx);
case ttCHECK_CANCEL: return invoke_calculateConsequences<CancelCheck>(tx);
case ttCHECK_CASH: return invoke_calculateConsequences<CashCheck>(tx);
case ttCHECK_CREATE: return invoke_calculateConsequences<CreateCheck>(tx);
case ttDEPOSIT_PREAUTH: return invoke_calculateConsequences<DepositPreauth>(tx);
case ttOFFER_CANCEL: return invoke_calculateConsequences<CancelOffer>(tx);
case ttOFFER_CREATE: return invoke_calculateConsequences<CreateOffer>(tx);
case ttESCROW_CREATE: return invoke_calculateConsequences<EscrowCreate>(tx);
case ttESCROW_FINISH: return invoke_calculateConsequences<EscrowFinish>(tx);
case ttESCROW_CANCEL: return invoke_calculateConsequences<EscrowCancel>(tx);
case ttPAYCHAN_CLAIM: return invoke_calculateConsequences<PayChanClaim>(tx);
case ttPAYCHAN_CREATE: return invoke_calculateConsequences<PayChanCreate>(tx);
case ttPAYCHAN_FUND: return invoke_calculateConsequences<PayChanFund>(tx);
case ttPAYMENT: return invoke_calculateConsequences<Payment>(tx);
case ttREGULAR_KEY_SET: return invoke_calculateConsequences<SetRegularKey>(tx);
case ttSIGNER_LIST_SET: return invoke_calculateConsequences<SetSignerList>(tx);
case ttTICKET_CANCEL: return invoke_calculateConsequences<CancelTicket>(tx);
case ttTICKET_CREATE: return invoke_calculateConsequences<CreateTicket>(tx);
case ttTRUST_SET: return invoke_calculateConsequences<SetTrust>(tx);
case ttACCOUNT_SET: return invoke_calculateConsequences<SetAccount>(tx);
case ttCHECK_CANCEL: return invoke_calculateConsequences<CancelCheck>(tx);
case ttCHECK_CASH: return invoke_calculateConsequences<CashCheck>(tx);
case ttCHECK_CREATE: return invoke_calculateConsequences<CreateCheck>(tx);
case ttDEPOSIT_PREAUTH: return invoke_calculateConsequences<DepositPreauth>(tx);
case ttOFFER_CANCEL: return invoke_calculateConsequences<CancelOffer>(tx);
case ttOFFER_CREATE: return invoke_calculateConsequences<CreateOffer>(tx);
case ttESCROW_CREATE: return invoke_calculateConsequences<EscrowCreate>(tx);
case ttESCROW_FINISH: return invoke_calculateConsequences<EscrowFinish>(tx);
case ttESCROW_CANCEL: return invoke_calculateConsequences<EscrowCancel>(tx);
case ttPAYCHAN_CLAIM: return invoke_calculateConsequences<PayChanClaim>(tx);
case ttPAYCHAN_CREATE: return invoke_calculateConsequences<PayChanCreate>(tx);
case ttPAYCHAN_FUND: return invoke_calculateConsequences<PayChanFund>(tx);
case ttPAYMENT: return invoke_calculateConsequences<Payment>(tx);
case ttREGULAR_KEY_SET: return invoke_calculateConsequences<SetRegularKey>(tx);
case ttSIGNER_LIST_SET: return invoke_calculateConsequences<SetSignerList>(tx);
case ttTICKET_CANCEL: return invoke_calculateConsequences<CancelTicket>(tx);
case ttTICKET_CREATE: return invoke_calculateConsequences<CreateTicket>(tx);
case ttTRUST_SET: return invoke_calculateConsequences<SetTrust>(tx);
case ttAMENDMENT:
case ttFEE:
// fall through to default
@@ -229,27 +229,27 @@ invoke_apply (ApplyContext& ctx)
{
switch(ctx.tx.getTxnType())
{
case ttACCOUNT_SET: { SetAccount p(ctx); return p(); }
case ttCHECK_CANCEL: { CancelCheck p(ctx); return p(); }
case ttCHECK_CASH: { CashCheck p(ctx); return p(); }
case ttCHECK_CREATE: { CreateCheck p(ctx); return p(); }
case ttDEPOSIT_PREAUTH: { DepositPreauth p(ctx); return p(); }
case ttOFFER_CANCEL: { CancelOffer p(ctx); return p(); }
case ttOFFER_CREATE: { CreateOffer p(ctx); return p(); }
case ttESCROW_CREATE: { EscrowCreate p(ctx); return p(); }
case ttESCROW_FINISH: { EscrowFinish p(ctx); return p(); }
case ttESCROW_CANCEL: { EscrowCancel p(ctx); return p(); }
case ttPAYCHAN_CLAIM: { PayChanClaim p(ctx); return p(); }
case ttPAYCHAN_CREATE: { PayChanCreate p(ctx); return p(); }
case ttPAYCHAN_FUND: { PayChanFund p(ctx); return p(); }
case ttPAYMENT: { Payment p(ctx); return p(); }
case ttREGULAR_KEY_SET: { SetRegularKey p(ctx); return p(); }
case ttSIGNER_LIST_SET: { SetSignerList p(ctx); return p(); }
case ttTICKET_CANCEL: { CancelTicket p(ctx); return p(); }
case ttTICKET_CREATE: { CreateTicket p(ctx); return p(); }
case ttTRUST_SET: { SetTrust p(ctx); return p(); }
case ttACCOUNT_SET: { SetAccount p(ctx); return p(); }
case ttCHECK_CANCEL: { CancelCheck p(ctx); return p(); }
case ttCHECK_CASH: { CashCheck p(ctx); return p(); }
case ttCHECK_CREATE: { CreateCheck p(ctx); return p(); }
case ttDEPOSIT_PREAUTH: { DepositPreauth p(ctx); return p(); }
case ttOFFER_CANCEL: { CancelOffer p(ctx); return p(); }
case ttOFFER_CREATE: { CreateOffer p(ctx); return p(); }
case ttESCROW_CREATE: { EscrowCreate p(ctx); return p(); }
case ttESCROW_FINISH: { EscrowFinish p(ctx); return p(); }
case ttESCROW_CANCEL: { EscrowCancel p(ctx); return p(); }
case ttPAYCHAN_CLAIM: { PayChanClaim p(ctx); return p(); }
case ttPAYCHAN_CREATE: { PayChanCreate p(ctx); return p(); }
case ttPAYCHAN_FUND: { PayChanFund p(ctx); return p(); }
case ttPAYMENT: { Payment p(ctx); return p(); }
case ttREGULAR_KEY_SET : { SetRegularKey p(ctx); return p(); }
case ttSIGNER_LIST_SET : { SetSignerList p(ctx); return p(); }
case ttTICKET_CANCEL: { CancelTicket p(ctx); return p(); }
case ttTICKET_CREATE: { CreateTicket p(ctx); return p(); }
case ttTRUST_SET: { SetTrust p(ctx); return p(); }
case ttAMENDMENT:
case ttFEE: { Change p(ctx); return p(); }
case ttFEE: { Change p(ctx); return p(); }
default:
assert(false);
return { temUNKNOWN, false };

View File

@@ -669,7 +669,7 @@ OverlayImpl::onManifests (
{
auto& s = m->list ().Get (i).stobject ();
if (auto mo = Manifest::make_Manifest (s))
if (auto mo = deserializeManifest(s))
{
uint256 const hash = mo->hash ();
if (!hashRouter.addSuppressionPeer (hash, from->id ())) {
@@ -691,8 +691,7 @@ OverlayImpl::onManifests (
if (result == ManifestDisposition::accepted)
{
app_.getOPs().pubManifest (
*Manifest::make_Manifest(serialized));
app_.getOPs().pubManifest (*deserializeManifest(serialized));
}
if (result == ManifestDisposition::accepted)

View File

@@ -194,7 +194,6 @@ JSS ( frozen_balances ); // out: GatewayBalances
JSS ( full ); // in: LedgerClearer, handlers/Ledger
JSS ( full_reply ); // out: PathFind
JSS ( fullbelow_size ); // in: GetCounts
JSS ( generator ); // in: LedgerEntry
JSS ( good ); // out: RPCVersion
JSS ( hash ); // out: NetworkOPs, InboundLedger,
// LedgerToJson, STTx; field

View File

@@ -142,8 +142,7 @@ operator== (PublicKey const& lhs,
PublicKey const& rhs)
{
return lhs.size() == rhs.size() &&
std::memcmp(lhs.data(),
rhs.data(), rhs.size()) == 0;
std::memcmp(lhs.data(), rhs.data(), rhs.size()) == 0;
}
inline
@@ -153,7 +152,7 @@ operator< (PublicKey const& lhs,
{
return std::lexicographical_compare(
lhs.data(), lhs.data() + lhs.size(),
rhs.data(), rhs.data() + rhs.size());
rhs.data(), rhs.data() + rhs.size());
}
template <class Hasher>

View File

@@ -338,6 +338,9 @@ extern SF_U16 const sfLedgerEntryType;
extern SF_U16 const sfTransactionType;
extern SF_U16 const sfSignerWeight;
// 16-bit integers (uncommon)
extern SF_U16 const sfVersion;
// 32-bit integers (common)
extern SF_U32 const sfFlags;
extern SF_U32 const sfSourceTag;

View File

@@ -77,6 +77,20 @@ public:
{
}
/** Create a template and pass it to the callback to be populated.
The callback will typically consist of one or more calls to the
@ref push_back member function on the object that it is passed.
@see push_back
*/
SOTemplate(std::function<void(SOTemplate&)> callback)
: SOTemplate()
{
if (callback)
callback(*this);
}
/* Provide for the enumeration of fields */
iterator_range all () const
{

View File

@@ -264,7 +264,7 @@ enum TECcodes : TERUnderlyingType
tecINVARIANT_FAILED = 147,
tecEXPIRED = 148,
tecDUPLICATE = 149,
tecKILLED = 150
tecKILLED = 150,
};
//------------------------------------------------------------------------------

View File

@@ -32,32 +32,32 @@ namespace ripple {
*/
enum TxType
{
ttINVALID = -1,
ttINVALID = -1,
ttPAYMENT = 0,
ttESCROW_CREATE = 1,
ttESCROW_FINISH = 2,
ttACCOUNT_SET = 3,
ttESCROW_CANCEL = 4,
ttREGULAR_KEY_SET = 5,
ttNICKNAME_SET = 6, // open
ttOFFER_CREATE = 7,
ttOFFER_CANCEL = 8,
no_longer_used = 9,
ttTICKET_CREATE = 10,
ttTICKET_CANCEL = 11,
ttSIGNER_LIST_SET = 12,
ttPAYCHAN_CREATE = 13,
ttPAYCHAN_FUND = 14,
ttPAYCHAN_CLAIM = 15,
ttCHECK_CREATE = 16,
ttCHECK_CASH = 17,
ttCHECK_CANCEL = 18,
ttDEPOSIT_PREAUTH = 19,
ttTRUST_SET = 20,
ttPAYMENT = 0,
ttESCROW_CREATE = 1,
ttESCROW_FINISH = 2,
ttACCOUNT_SET = 3,
ttESCROW_CANCEL = 4,
ttREGULAR_KEY_SET = 5,
ttNICKNAME_SET = 6, // open
ttOFFER_CREATE = 7,
ttOFFER_CANCEL = 8,
no_longer_used = 9,
ttTICKET_CREATE = 10,
ttTICKET_CANCEL = 11,
ttSIGNER_LIST_SET = 12,
ttPAYCHAN_CREATE = 13,
ttPAYCHAN_FUND = 14,
ttPAYCHAN_CLAIM = 15,
ttCHECK_CREATE = 16,
ttCHECK_CASH = 17,
ttCHECK_CANCEL = 18,
ttDEPOSIT_PREAUTH = 19,
ttTRUST_SET = 20,
ttAMENDMENT = 100,
ttFEE = 101,
ttAMENDMENT = 100,
ttFEE = 101,
};
/** Manages the list of known transaction formats.

View File

@@ -116,7 +116,7 @@ detail::supportedAmendments ()
"fix1515",
"fix1578",
"MultiSignReserve",
"fixTakerDryOfferRemoval"
"fixTakerDryOfferRemoval",
};
return supported;
}

View File

@@ -188,14 +188,16 @@ PublicKey::PublicKey (Slice const& slice)
PublicKey::PublicKey (PublicKey const& other)
: size_ (other.size_)
{
std::memcpy(buf_, other.buf_, size_);
if (size_)
std::memcpy(buf_, other.buf_, size_);
};
PublicKey&
PublicKey::operator=(PublicKey const& other)
{
size_ = other.size_;
std::memcpy(buf_, other.buf_, size_);
if (size_)
std::memcpy(buf_, other.buf_, size_);
return *this;
}

View File

@@ -88,9 +88,12 @@ SF_U8 const sfTransactionResult = make::one<SF_U8::type>(&sfTransactionResult, S
SF_U8 const sfTickSize = make::one<SF_U8::type>(&sfTickSize, STI_UINT8, 16, "TickSize");
// 16-bit integers
SF_U16 const sfLedgerEntryType = make::one<SF_U16::type>(&sfLedgerEntryType, STI_UINT16, 1, "LedgerEntryType", SField::sMD_Never);
SF_U16 const sfTransactionType = make::one<SF_U16::type>(&sfTransactionType, STI_UINT16, 2, "TransactionType");
SF_U16 const sfSignerWeight = make::one<SF_U16::type>(&sfSignerWeight, STI_UINT16, 3, "SignerWeight");
SF_U16 const sfLedgerEntryType = make::one<SF_U16::type>(&sfLedgerEntryType, STI_UINT16, 1, "LedgerEntryType", SField::sMD_Never);
SF_U16 const sfTransactionType = make::one<SF_U16::type>(&sfTransactionType, STI_UINT16, 2, "TransactionType");
SF_U16 const sfSignerWeight = make::one<SF_U16::type>(&sfSignerWeight, STI_UINT16, 3, "SignerWeight");
// 16-bit integers (uncommon)
SF_U16 const sfVersion = make::one<SF_U16::type>(&sfVersion, STI_UINT16, 16, "Version");
// 32-bit integers (common)
SF_U32 const sfFlags = make::one<SF_U32::type>(&sfFlags, STI_UINT32, 2, "Flags");
@@ -196,10 +199,10 @@ SF_Amount const sfDeliveredAmount = make::one<SF_Amount::type>(&sfDeliveredAmoun
// variable length (common)
SF_Blob const sfPublicKey = make::one<SF_Blob::type>(&sfPublicKey, STI_VL, 1, "PublicKey");
SF_Blob const sfSigningPubKey = make::one<SF_Blob::type>(&sfSigningPubKey, STI_VL, 3, "SigningPubKey");
SF_Blob const sfSignature = make::one<SF_Blob::type>(&sfSignature, STI_VL, 6, "Signature", SField::sMD_Default, SField::notSigning);
SF_Blob const sfMessageKey = make::one<SF_Blob::type>(&sfMessageKey, STI_VL, 2, "MessageKey");
SF_Blob const sfSigningPubKey = make::one<SF_Blob::type>(&sfSigningPubKey, STI_VL, 3, "SigningPubKey");
SF_Blob const sfTxnSignature = make::one<SF_Blob::type>(&sfTxnSignature, STI_VL, 4, "TxnSignature", SField::sMD_Default, SField::notSigning);
SF_Blob const sfSignature = make::one<SF_Blob::type>(&sfSignature, STI_VL, 6, "Signature", SField::sMD_Default, SField::notSigning);
SF_Blob const sfDomain = make::one<SF_Blob::type>(&sfDomain, STI_VL, 7, "Domain");
SF_Blob const sfFundCode = make::one<SF_Blob::type>(&sfFundCode, STI_VL, 8, "FundCode");
SF_Blob const sfRemoveCode = make::one<SF_Blob::type>(&sfRemoveCode, STI_VL, 9, "RemoveCode");
@@ -215,7 +218,6 @@ SF_Blob const sfFulfillment = make::one<SF_Blob::type>(&sfFulfillment, S
SF_Blob const sfCondition = make::one<SF_Blob::type>(&sfCondition, STI_VL, 17, "Condition");
SF_Blob const sfMasterSignature = make::one<SF_Blob::type>(&sfMasterSignature, STI_VL, 18, "MasterSignature", SField::sMD_Default, SField::notSigning);
// account
SF_Account const sfAccount = make::one<SF_Account::type>(&sfAccount, STI_ACCOUNT, 1, "Account");
SF_Account const sfOwner = make::one<SF_Account::type>(&sfOwner, STI_ACCOUNT, 2, "Owner");

View File

@@ -186,10 +186,6 @@ Json::Value doLedgerEntry (RPC::Context& context)
context.params[jss::escrow][jss::seq].asUInt()).key;
}
}
else if (context.params.isMember (jss::generator))
{
jvResult[jss::error] = "deprecatedFeature";
}
else if (context.params.isMember (jss::offer))
{
expectedType = ltOFFER;

View File

@@ -127,9 +127,58 @@ public:
static_cast<char const*> (s.data()), s.size()));
}
std::string
makeRevocationString (
SecretKey const& sk,
KeyType type,
bool invalidSig = false)
{
auto const pk = derivePublicKey(type, sk);
STObject st(sfGeneric);
st[sfSequence] = std::numeric_limits<std::uint32_t>::max ();
st[sfPublicKey] = pk;
sign(st, HashPrefix::manifest, type,
invalidSig ? randomSecretKey() : sk, sfMasterSignature);
BEAST_EXPECT(invalidSig ^ verify(
st, HashPrefix::manifest, pk, sfMasterSignature));
Serializer s;
st.add(s);
return base64_encode (std::string(
static_cast<char const*> (s.data()), s.size()));
}
Manifest
make_Manifest
(SecretKey const& sk, KeyType type, SecretKey const& ssk, KeyType stype,
makeRevocation (
SecretKey const& sk, KeyType type, bool invalidSig = false)
{
auto const pk = derivePublicKey(type, sk);
STObject st(sfGeneric);
st[sfSequence] = std::numeric_limits<std::uint32_t>::max ();
st[sfPublicKey] = pk;
sign(st, HashPrefix::manifest, type,
invalidSig ? randomSecretKey() : sk, sfMasterSignature);
BEAST_EXPECT(invalidSig ^ verify(
st, HashPrefix::manifest, pk, sfMasterSignature));
Serializer s;
st.add(s);
std::string const m (static_cast<char const*> (s.data()), s.size());
if (auto r = deserializeManifest(std::move(m)))
return std::move(*r);
Throw<std::runtime_error> ("Could not create a revocation manifest");
return *deserializeManifest(std::move(m)); // Silence compiler warning.
}
Manifest
makeManifest (
SecretKey const& sk, KeyType type, SecretKey const& ssk, KeyType stype,
int seq, bool invalidSig = false)
{
auto const pk = derivePublicKey(type, sk);
@@ -152,38 +201,22 @@ public:
st.add(s);
std::string const m (static_cast<char const*> (s.data()), s.size());
if (auto r = Manifest::make_Manifest (std::move (m)))
if (auto r = deserializeManifest(std::move(m)))
return std::move (*r);
Throw<std::runtime_error> ("Could not create a manifest");
return *Manifest::make_Manifest(std::move(m)); // Silence compiler warning.
}
std::string
makeRevocation
(SecretKey const& sk, KeyType type, bool invalidSig = false)
{
auto const pk = derivePublicKey(type, sk);
STObject st(sfGeneric);
st[sfSequence] = std::numeric_limits<std::uint32_t>::max ();
st[sfPublicKey] = pk;
sign(st, HashPrefix::manifest, type,
invalidSig ? randomSecretKey() : sk, sfMasterSignature);
BEAST_EXPECT(invalidSig ^ verify(
st, HashPrefix::manifest, pk, sfMasterSignature));
Serializer s;
st.add(s);
return base64_encode (std::string(
static_cast<char const*> (s.data()), s.size()));
return *deserializeManifest(std::move(m)); // Silence compiler warning.
}
Manifest
clone (Manifest const& m)
{
return Manifest (m.serialized, m.masterKey, m.signingKey, m.sequence);
Manifest m2;
m2.serialized = m.serialized;
m2.masterKey = m.masterKey;
m2.signingKey = m.signingKey;
m2.sequence = m.sequence;
m2.domain = m.domain;
return m2;
}
void testLoadStore (ManifestCache& m)
@@ -197,24 +230,22 @@ public:
DatabaseCon dbCon(setup, dbName, WalletDBInit, WalletDBCount);
auto getPopulatedManifests =
[](ManifestCache const& cache) -> std::vector<Manifest const*>
{
std::vector<Manifest const*> result;
result.reserve (32);
cache.for_each_manifest (
[&result](Manifest const& m)
{result.push_back (&m);});
return result;
};
[](ManifestCache const& cache) -> std::vector<Manifest const*>
{
std::vector<Manifest const*> result;
result.reserve (32);
cache.for_each_manifest (
[&result](Manifest const& m) {result.push_back (&m);});
return result;
};
auto sort =
[](std::vector<Manifest const*> mv) -> std::vector<Manifest const*>
{
std::sort (mv.begin (),
mv.end (),
[](Manifest const* lhs, Manifest const* rhs)
{return lhs->serialized < rhs->serialized;});
return mv;
};
[](std::vector<Manifest const*> mv) -> std::vector<Manifest const*>
{
std::sort (mv.begin (), mv.end (),
[](Manifest const* lhs, Manifest const* rhs)
{ return lhs->serialized < rhs->serialized; });
return mv;
};
std::vector<Manifest const*> const inManifests (
sort (getPopulatedManifests (m)));
@@ -318,13 +349,13 @@ public:
BEAST_EXPECT(! loaded.revoked(pk));
std::vector<std::string> const badSigRevocation =
{ makeRevocation (sk, keyType, true /* invalidSig */) };
{ makeRevocationString (sk, keyType, true) };
BEAST_EXPECT(! loaded.load (
dbCon, "ValidatorManifests", emptyManifest, badSigRevocation));
BEAST_EXPECT(! loaded.revoked(pk));
std::vector<std::string> const cfgRevocation =
{ makeRevocation (sk, keyType) };
{ makeRevocationString (sk, keyType) };
BEAST_EXPECT(loaded.load (
dbCon, "ValidatorManifests", emptyManifest, cfgRevocation));
@@ -341,7 +372,7 @@ public:
auto const sk = randomSecretKey();
auto const pk = derivePublicKey(KeyType::ed25519, sk);
auto const kp = randomKeyPair(KeyType::secp256k1);
auto const m = make_Manifest (
auto const m = makeManifest (
sk, KeyType::ed25519, kp.second, KeyType::secp256k1, 0);
STObject st(sfGeneric);
@@ -374,10 +405,9 @@ public:
// getMasterKey should return the listed validator master key
// for that ephemeral public key
auto const kp0 = randomKeyPair(KeyType::secp256k1);
auto const m0 = make_Manifest (
sk, KeyType::ed25519, kp0.second, KeyType::secp256k1, 0);
BEAST_EXPECT(cache.applyManifest(clone (m0)) ==
ManifestDisposition::accepted);
BEAST_EXPECT(ManifestDisposition::accepted ==
cache.applyManifest(makeManifest (
sk, KeyType::ed25519, kp0.second, KeyType::secp256k1, 0)));
BEAST_EXPECT(cache.getSigningKey(pk) == kp0.first);
BEAST_EXPECT(cache.getMasterKey(kp0.first) == pk);
@@ -386,10 +416,9 @@ public:
// getMasterKey should only return a master key for the latest
// ephemeral public key
auto const kp1 = randomKeyPair(KeyType::secp256k1);
auto const m1 = make_Manifest (
sk, KeyType::ed25519, kp1.second, KeyType::secp256k1, 1);
BEAST_EXPECT(cache.applyManifest(clone (m1)) ==
ManifestDisposition::accepted);
BEAST_EXPECT(ManifestDisposition::accepted ==
cache.applyManifest(makeManifest (
sk, KeyType::ed25519, kp1.second, KeyType::secp256k1, 1)));
BEAST_EXPECT(cache.getSigningKey(pk) == kp1.first);
BEAST_EXPECT(cache.getMasterKey(kp1.first) == pk);
BEAST_EXPECT(cache.getMasterKey(kp0.first) == kp0.first);
@@ -397,27 +426,22 @@ public:
// getSigningKey and getMasterKey should return the same keys if
// a new manifest is applied with the same signing key but a higher
// sequence
auto const m2 = make_Manifest (
sk, KeyType::ed25519, kp1.second, KeyType::secp256k1, 2);
BEAST_EXPECT(cache.applyManifest(clone (m2)) ==
ManifestDisposition::accepted);
BEAST_EXPECT(ManifestDisposition::accepted ==
cache.applyManifest(makeManifest (
sk, KeyType::ed25519, kp1.second, KeyType::secp256k1, 2)));
BEAST_EXPECT(cache.getSigningKey(pk) == kp1.first);
BEAST_EXPECT(cache.getMasterKey(kp1.first) == pk);
BEAST_EXPECT(cache.getMasterKey(kp0.first) == kp0.first);
// getSigningKey should return boost::none for a
// revoked master public key
// getSigningKey should return boost::none for a revoked master public key
// getMasterKey should return boost::none for an ephemeral public key
// from a revoked master public key
auto const kpMax = randomKeyPair(KeyType::secp256k1);
auto const mMax = make_Manifest (
sk, KeyType::ed25519, kpMax.second, KeyType::secp256k1,
std::numeric_limits<std::uint32_t>::max ());
BEAST_EXPECT(cache.applyManifest(clone (mMax)) ==
ManifestDisposition::accepted);
BEAST_EXPECT(ManifestDisposition::accepted ==
cache.applyManifest(makeRevocation (
sk, KeyType::ed25519)));
BEAST_EXPECT(cache.revoked(pk));
BEAST_EXPECT(cache.getSigningKey(pk) == pk);
BEAST_EXPECT(cache.getMasterKey(kpMax.first) == kpMax.first);
BEAST_EXPECT(cache.getMasterKey(kp0.first) == kp0.first);
BEAST_EXPECT(cache.getMasterKey(kp1.first) == kp1.first);
}
@@ -460,10 +484,45 @@ public:
}
}
void testMakeManifest()
void testManifestVersioning()
{
testcase ("make_Manifest");
testcase ("Versioning");
auto const sk = generateSecretKey (KeyType::ed25519, randomSeed ());
auto const pk = derivePublicKey(KeyType::ed25519, sk);
auto const ssk = generateSecretKey (KeyType::secp256k1, randomSeed ());
auto const spk = derivePublicKey(KeyType::secp256k1, ssk);
auto buildManifestObject = [&](std::uint16_t version)
{
STObject st(sfGeneric);
st[sfSequence] = 3;
st[sfPublicKey] = pk;
st[sfSigningPubKey] = spk;
if (version != 0)
st[sfVersion] = version;
sign(st, HashPrefix::manifest, KeyType::ed25519, sk, sfMasterSignature);
sign(st, HashPrefix::manifest, KeyType::secp256k1, ssk);
Serializer s;
st.add(s);
return std::string (static_cast<char const*>(s.data()), s.size());
};
// We understand version 0 manifests:
BEAST_EXPECT(deserializeManifest(buildManifestObject(0)));
// We don't understand any other versions:
BEAST_EXPECT(!deserializeManifest(buildManifestObject(1)));
BEAST_EXPECT(!deserializeManifest(buildManifestObject(2001)));
}
void testManifestDeserialization()
{
std::array<KeyType, 2> const keyTypes {{
KeyType::ed25519,
KeyType::secp256k1 }};
@@ -497,7 +556,8 @@ public:
auto const spk = derivePublicKey(sKeyType, ssk);
auto buildManifestObject = [&](
std::uint32_t const& seq,
std::uint32_t seq,
boost::optional<std::string> domain,
bool noSigningPublic = false,
bool noSignature = false)
{
@@ -505,11 +565,13 @@ public:
st[sfSequence] = seq;
st[sfPublicKey] = pk;
if (domain)
st[sfDomain] = makeSlice(*domain);
if (! noSigningPublic)
st[sfSigningPubKey] = spk;
sign(st, HashPrefix::manifest, keyType, sk,
sfMasterSignature);
sign(st, HashPrefix::manifest, keyType, sk, sfMasterSignature);
if (! noSignature)
sign(st, HashPrefix::manifest, sKeyType, ssk);
@@ -517,201 +579,329 @@ public:
return st;
};
auto const st = buildManifestObject(++sequence);
{
testcase << "deserializeManifest: normal manifest (" <<
to_string(keyType) << " + " <<
to_string(sKeyType) << ")";
{ // valid manifest without domain
auto const st = buildManifestObject(
++sequence, boost::none);
auto const m = toString(st);
auto const manifest = deserializeManifest(m);
BEAST_EXPECT(manifest);
BEAST_EXPECT(manifest->masterKey == pk);
BEAST_EXPECT(manifest->signingKey == spk);
BEAST_EXPECT(manifest->sequence == sequence);
BEAST_EXPECT(manifest->serialized == m);
BEAST_EXPECT(manifest->domain.empty());
BEAST_EXPECT(manifest->verify());
}
{ // invalid manifest (empty domain)
auto const st = buildManifestObject(
++sequence, std::string{});
BEAST_EXPECT(!deserializeManifest(toString(st)));
}
{ // invalid manifest (domain too short)
auto const st = buildManifestObject(
++sequence, std::string{"a.b"});
BEAST_EXPECT(!deserializeManifest(toString(st)));
}
{ // invalid manifest (domain too long)
std::string s(254, 'a');
auto const st = buildManifestObject(
++sequence, s + ".example.com");
BEAST_EXPECT(!deserializeManifest(toString(st)));
}
{ // invalid manifest (domain component too long)
std::string s(72, 'a');
auto const st = buildManifestObject(
++sequence, s + ".example.com");
BEAST_EXPECT(!deserializeManifest(toString(st)));
}
auto const st = buildManifestObject(
++sequence, std::string{"example.com"});
{
// valid manifest with domain
auto const m = toString(st);
auto const manifest = deserializeManifest(m);
BEAST_EXPECT(manifest);
BEAST_EXPECT(manifest->masterKey == pk);
BEAST_EXPECT(manifest->signingKey == spk);
BEAST_EXPECT(manifest->sequence == sequence);
BEAST_EXPECT(manifest->serialized == m);
BEAST_EXPECT(manifest->domain == "example.com");
BEAST_EXPECT(manifest->verify());
}
{
// valid manifest with invalid signature
auto badSigSt = st;
badSigSt[sfPublicKey] = badSigSt[sfSigningPubKey];
auto const m = toString(badSigSt);
auto const manifest = deserializeManifest(m);
BEAST_EXPECT(manifest);
BEAST_EXPECT(manifest->masterKey == spk);
BEAST_EXPECT(manifest->signingKey == spk);
BEAST_EXPECT(manifest->sequence == sequence);
BEAST_EXPECT(manifest->serialized == m);
BEAST_EXPECT(manifest->domain == "example.com");
BEAST_EXPECT(!manifest->verify());
}
{
// reject missing sequence
auto badSt = st;
BEAST_EXPECT(badSt.delField(sfSequence));
BEAST_EXPECT(!deserializeManifest(toString(badSt)));
}
{
// reject missing public key
auto badSt = st;
BEAST_EXPECT(badSt.delField(sfPublicKey));
BEAST_EXPECT(!deserializeManifest(toString(badSt)));
}
{
// reject invalid public key type
auto badSt = st;
badSt[sfPublicKey] = badKey;
BEAST_EXPECT(!deserializeManifest(toString(badSt)));
}
{
// reject short public key
auto badSt = st;
badSt[sfPublicKey] = shortKey;
BEAST_EXPECT(!deserializeManifest(toString(badSt)));
}
{
// reject missing signing public key
auto badSt = st;
BEAST_EXPECT(badSt.delField(sfSigningPubKey));
BEAST_EXPECT(!deserializeManifest(toString(badSt)));
}
{
// reject invalid signing public key type
auto badSt = st;
badSt[sfSigningPubKey] = badKey;
BEAST_EXPECT(!deserializeManifest(toString(badSt)));
}
{
// reject short signing public key
auto badSt = st;
badSt[sfSigningPubKey] = shortKey;
BEAST_EXPECT(!deserializeManifest(toString(badSt)));
}
{
// reject missing signature
auto badSt = st;
BEAST_EXPECT(badSt.delField(sfMasterSignature));
BEAST_EXPECT(!deserializeManifest(toString(badSt)));
}
{
// reject missing signing key signature
auto badSt = st;
BEAST_EXPECT(badSt.delField(sfSignature));
BEAST_EXPECT(!deserializeManifest(toString(badSt)));
}
}
{
// valid manifest
auto const m = toString(st);
testcase << "deserializeManifest: revocation manifest (" <<
to_string(keyType) << " + " <<
to_string(sKeyType) << ")";
auto const manifest = Manifest::make_Manifest (m);
// valid revocation
{
auto const st = buildManifestObject(
std::numeric_limits<std::uint32_t>::max(),
boost::none, true, true);
BEAST_EXPECT(manifest);
BEAST_EXPECT(manifest->masterKey == pk);
BEAST_EXPECT(manifest->signingKey == spk);
BEAST_EXPECT(manifest->sequence == sequence);
BEAST_EXPECT(manifest->serialized == m);
BEAST_EXPECT(manifest->verify());
}
{
// valid manifest with invalid signature
auto badSigSt = st;
badSigSt[sfPublicKey] = badSigSt[sfSigningPubKey];
auto const m = toString(st);
auto const manifest = deserializeManifest(m);
auto const m = toString(badSigSt);
auto const manifest = Manifest::make_Manifest (m);
BEAST_EXPECT(manifest);
BEAST_EXPECT(manifest->masterKey == pk);
BEAST_EXPECT(manifest->signingKey == PublicKey());
BEAST_EXPECT(manifest->revoked());
BEAST_EXPECT(manifest->domain.empty());
BEAST_EXPECT(manifest->serialized == m);
BEAST_EXPECT(manifest->verify());
}
BEAST_EXPECT(manifest);
BEAST_EXPECT(manifest->masterKey == spk);
BEAST_EXPECT(manifest->signingKey == spk);
BEAST_EXPECT(manifest->sequence == sequence);
BEAST_EXPECT(manifest->serialized == m);
BEAST_EXPECT(! manifest->verify());
}
{
// reject missing sequence
auto badSt = st;
BEAST_EXPECT(badSt.delField(sfSequence));
BEAST_EXPECT(! Manifest::make_Manifest (toString(badSt)));
}
{
// reject missing public key
auto badSt = st;
BEAST_EXPECT(badSt.delField(sfPublicKey));
BEAST_EXPECT(! Manifest::make_Manifest (toString(badSt)));
}
{
// reject invalid public key type
auto badSt = st;
badSt[sfPublicKey] = badKey;
BEAST_EXPECT(! Manifest::make_Manifest (toString(badSt)));
}
{
// reject short public key
auto badSt = st;
badSt[sfPublicKey] = shortKey;
BEAST_EXPECT(! Manifest::make_Manifest (toString(badSt)));
}
{
// reject missing signing public key
auto badSt = st;
BEAST_EXPECT(badSt.delField(sfSigningPubKey));
BEAST_EXPECT(! Manifest::make_Manifest (toString(badSt)));
}
{
// reject invalid signing public key type
auto badSt = st;
badSt[sfSigningPubKey] = badKey;
BEAST_EXPECT(! Manifest::make_Manifest (toString(badSt)));
}
{
// reject short signing public key
auto badSt = st;
badSt[sfSigningPubKey] = shortKey;
BEAST_EXPECT(! Manifest::make_Manifest (toString(badSt)));
}
{
// reject missing signature
auto badSt = st;
BEAST_EXPECT(badSt.delField(sfMasterSignature));
BEAST_EXPECT(! Manifest::make_Manifest (toString(badSt)));
}
{
// reject missing signing key signature
auto badSt = st;
BEAST_EXPECT(badSt.delField(sfSignature));
BEAST_EXPECT(! Manifest::make_Manifest (toString(badSt)));
}
{ // can't specify an ephemeral signing key
auto const st = buildManifestObject(
std::numeric_limits<std::uint32_t>::max(),
boost::none, true, false);
// test revocations (max sequence revoking the master key)
auto testRevocation = [&](STObject const& st)
{
auto const m = toString(st);
auto const manifest = Manifest::make_Manifest (m);
BEAST_EXPECT(!deserializeManifest(toString(st)));
}
{ // can't specify an ephemeral signature
auto const st = buildManifestObject(
std::numeric_limits<std::uint32_t>::max(),
boost::none, false, true);
BEAST_EXPECT(manifest);
BEAST_EXPECT(manifest->masterKey == pk);
BEAST_EXPECT(manifest->signingKey == PublicKey());
BEAST_EXPECT(manifest->sequence ==
std::numeric_limits<std::uint32_t>::max ());
BEAST_EXPECT(manifest->serialized == m);
BEAST_EXPECT(manifest->verify());
};
BEAST_EXPECT(!deserializeManifest(toString(st)));
}
{ // can't specify an ephemeral key & signature
auto const st = buildManifestObject(
std::numeric_limits<std::uint32_t>::max (),
boost::none, false, false);
// valid revocation
{
auto const revSt = buildManifestObject(
std::numeric_limits<std::uint32_t>::max ());
testRevocation(revSt);
}
// signing key and signature are optional in revocation
{
auto const revSt = buildManifestObject(
std::numeric_limits<std::uint32_t>::max (),
true /* no signing key */);
testRevocation(revSt);
}
{
auto const revSt = buildManifestObject(
std::numeric_limits<std::uint32_t>::max (),
false, true /* no signature */);
testRevocation(revSt);
}
{
auto const revSt = buildManifestObject(
std::numeric_limits<std::uint32_t>::max (),
true /* no signing key */,
true /* no signature */);
testRevocation(revSt);
BEAST_EXPECT(!deserializeManifest(toString(st)));
}
}
}
}
}
void testManifestDomainNames()
{
testcase ("Manifest Domain Names");
auto const sk1 = generateSecretKey (KeyType::secp256k1, randomSeed());
auto const pk1 = derivePublicKey(KeyType::secp256k1, sk1);
auto const sk2 = generateSecretKey (KeyType::secp256k1, randomSeed());
auto const pk2 = derivePublicKey(KeyType::secp256k1, sk2);
auto test = [&](std::string domain)
{
STObject st(sfGeneric);
st[sfSequence] = 7;
st[sfPublicKey] = pk1;
st[sfDomain] = makeSlice(domain);
st[sfSigningPubKey] = pk2;
sign(st, HashPrefix::manifest, KeyType::secp256k1, sk1, sfMasterSignature);
sign(st, HashPrefix::manifest, KeyType::secp256k1, sk2);
Serializer s;
st.add(s);
return deserializeManifest(
std::string(static_cast<char const*> (s.data()), s.size()));
};
BEAST_EXPECT(test("example.com"));
BEAST_EXPECT(test("test.example.com"));
BEAST_EXPECT(test("example-domain.com"));
BEAST_EXPECT(test("xn--mxavchb.gr"));
BEAST_EXPECT(test("test.xn--mxavchb.gr"));
BEAST_EXPECT(test("123.gr"));
BEAST_EXPECT(test("x.yz"));
BEAST_EXPECT(test(std::string(63, 'a') + ".example.com"));
BEAST_EXPECT(test(std::string(63, 'a') + "." + std::string(63, 'b')));
// No period
BEAST_EXPECT(!test("example"));
// Leading period:
BEAST_EXPECT(!test(".com"));
BEAST_EXPECT(!test(".example.com"));
// A trailing period is technically valid but we don't allow it
BEAST_EXPECT(!test("example.com."));
// A component can't start or end with a dash
BEAST_EXPECT(!test("-example.com"));
BEAST_EXPECT(!test("example-.com"));
// Empty component:
BEAST_EXPECT(!test("double..periods.example.com"));
// TLD too short or too long:
BEAST_EXPECT(!test("example.x"));
BEAST_EXPECT(!test("example." + std::string(64, 'a')));
// Invalid characters:
BEAST_EXPECT(!test("example.com-org"));
BEAST_EXPECT(!test("bang!.com"));
BEAST_EXPECT(!test("bang!.example.com"));
// Too short
BEAST_EXPECT(!test("a.b"));
// Single component too long:
BEAST_EXPECT(!test(std::string(64, 'a') + ".com"));
BEAST_EXPECT(!test(std::string(64, 'a') + ".example.com"));
// Multiple components too long:
BEAST_EXPECT(!test(std::string(64, 'a') + "." + std::string(64, 'b')));
BEAST_EXPECT(!test(std::string(64, 'a') + "." + std::string(64, 'b')));
// Overall too long:
BEAST_EXPECT(!test(std::string(63, 'a') + "." + std::string(63, 'b') + ".example.com"));
}
void
run() override
{
ManifestCache cache;
{
testcase ("apply");
auto const accepted = ManifestDisposition::accepted;
auto const stale = ManifestDisposition::stale;
auto const invalid = ManifestDisposition::invalid;
auto const sk_a = randomSecretKey();
auto const pk_a = derivePublicKey(KeyType::ed25519, sk_a);
auto const kp_a = randomKeyPair(KeyType::secp256k1);
auto const s_a0 = make_Manifest (
auto const s_a0 = makeManifest (
sk_a, KeyType::ed25519, kp_a.second, KeyType::secp256k1, 0);
auto const s_a1 = make_Manifest (
auto const s_a1 = makeManifest (
sk_a, KeyType::ed25519, kp_a.second, KeyType::secp256k1, 1);
auto const s_aMax = make_Manifest (
sk_a, KeyType::ed25519, kp_a.second, KeyType::secp256k1,
std::numeric_limits<std::uint32_t>::max ());
auto const s_aMax = makeRevocation (sk_a, KeyType::ed25519);
auto const sk_b = randomSecretKey();
auto const kp_b = randomKeyPair(KeyType::secp256k1);
auto const s_b0 = make_Manifest (
auto const s_b0 = makeManifest (
sk_b, KeyType::ed25519, kp_b.second, KeyType::secp256k1, 0);
auto const s_b1 = make_Manifest (
auto const s_b1 = makeManifest (
sk_b, KeyType::ed25519, kp_b.second, KeyType::secp256k1, 1);
auto const s_b2 = make_Manifest (
auto const s_b2 = makeManifest (
sk_b, KeyType::ed25519, kp_b.second, KeyType::secp256k1, 2,
true); // invalidSig
auto const fake = s_b1.serialized + '\0';
// applyManifest should accept new manifests with
// higher sequence numbers
BEAST_EXPECT(cache.applyManifest (clone (s_a0)) == accepted);
BEAST_EXPECT(cache.applyManifest (clone (s_a0)) == stale);
BEAST_EXPECT(cache.applyManifest (clone (s_a0)) == ManifestDisposition::accepted);
BEAST_EXPECT(cache.applyManifest (clone (s_a0)) == ManifestDisposition::stale);
BEAST_EXPECT(cache.applyManifest (clone (s_a1)) == accepted);
BEAST_EXPECT(cache.applyManifest (clone (s_a1)) == stale);
BEAST_EXPECT(cache.applyManifest (clone (s_a0)) == stale);
BEAST_EXPECT(cache.applyManifest (clone (s_a1)) == ManifestDisposition::accepted);
BEAST_EXPECT(cache.applyManifest (clone (s_a1)) == ManifestDisposition::stale);
BEAST_EXPECT(cache.applyManifest (clone (s_a0)) == ManifestDisposition::stale);
// applyManifest should accept manifests with max sequence numbers
// that revoke the master public key
BEAST_EXPECT(!cache.revoked (pk_a));
BEAST_EXPECT(s_aMax.revoked ());
BEAST_EXPECT(cache.applyManifest (clone (s_aMax)) == accepted);
BEAST_EXPECT(cache.applyManifest (clone (s_aMax)) == stale);
BEAST_EXPECT(cache.applyManifest (clone (s_a1)) == stale);
BEAST_EXPECT(cache.applyManifest (clone (s_a0)) == stale);
BEAST_EXPECT(cache.applyManifest (clone (s_aMax)) == ManifestDisposition::accepted);
BEAST_EXPECT(cache.applyManifest (clone (s_aMax)) == ManifestDisposition::stale);
BEAST_EXPECT(cache.applyManifest (clone (s_a1)) == ManifestDisposition::stale);
BEAST_EXPECT(cache.applyManifest (clone (s_a0)) == ManifestDisposition::stale);
BEAST_EXPECT(cache.revoked (pk_a));
// applyManifest should reject manifests with invalid signatures
BEAST_EXPECT(cache.applyManifest (clone (s_b0)) == accepted);
BEAST_EXPECT(cache.applyManifest (clone (s_b0)) == stale);
BEAST_EXPECT(cache.applyManifest (clone (s_b0)) == ManifestDisposition::accepted);
BEAST_EXPECT(cache.applyManifest (clone (s_b0)) == ManifestDisposition::stale);
BEAST_EXPECT(!Manifest::make_Manifest(fake));
BEAST_EXPECT(cache.applyManifest (clone (s_b2)) == invalid);
BEAST_EXPECT(!deserializeManifest(fake));
BEAST_EXPECT(cache.applyManifest (clone (s_b2)) == ManifestDisposition::invalid);
}
testLoadStore (cache);
testGetSignature ();
testGetKeys ();
testValidatorToken ();
testMakeManifest ();
testManifestDeserialization ();
testManifestDomainNames ();
testManifestVersioning ();
}
};

View File

@@ -91,8 +91,7 @@ public:
auto const tokenPublicKey =
derivePublicKey(KeyType::secp256k1, tokenSecretKey);
auto const m = Manifest::make_Manifest(
base64_decode(tokenManifest));
auto const m = deserializeManifest(base64_decode(tokenManifest));
BEAST_EXPECT(m);
NodeID const tokenNodeID = calcNodeID(m->masterKey);

View File

@@ -68,11 +68,32 @@ private:
STObject st(sfGeneric);
st[sfSequence] = seq;
st[sfPublicKey] = pk;
st[sfSigningPubKey] = spk;
sign(st, HashPrefix::manifest, *publicKeyType(spk), ssk);
sign(st, HashPrefix::manifest, *publicKeyType(pk), sk,
sfMasterSignature);
if (seq != std::numeric_limits<std::uint32_t>::max())
{
st[sfSigningPubKey] = spk;
sign(st, HashPrefix::manifest, *publicKeyType(spk), ssk);
}
sign(st, HashPrefix::manifest, *publicKeyType(pk), sk, sfMasterSignature);
Serializer s;
st.add(s);
return std::string(static_cast<char const*> (s.data()), s.size());
}
static
std::string
makeRevocationString (
PublicKey const& pk,
SecretKey const& sk)
{
STObject st(sfGeneric);
st[sfSequence] = std::numeric_limits<std::uint32_t>::max();
st[sfPublicKey] = pk;
sign(st, HashPrefix::manifest, *publicKeyType(pk), sk, sfMasterSignature);
Serializer s;
st.add(s);
@@ -162,8 +183,8 @@ private:
jtx::Env env (*this);
PublicKey emptyLocalKey;
std::vector<std::string> emptyCfgKeys;
std::vector<std::string> emptyCfgPublishers;
std::vector<std::string> const emptyCfgKeys;
std::vector<std::string> const emptyCfgPublishers;
auto const localSigningKeys = randomKeyPair(KeyType::secp256k1);
auto const localSigningPublic = localSigningKeys.first;
@@ -220,7 +241,7 @@ private:
localSigningPublic, emptyCfgKeys, emptyCfgPublishers));
BEAST_EXPECT(trustedKeys->listed (localSigningPublic));
manifests.applyManifest (*Manifest::make_Manifest(cfgManifest));
manifests.applyManifest (*deserializeManifest(cfgManifest));
BEAST_EXPECT(trustedKeys->load (
localSigningPublic, emptyCfgKeys, emptyCfgPublishers));
@@ -253,23 +274,16 @@ private:
BEAST_EXPECT(trustedKeys->listed (masterNode2));
// load should reject invalid config keys
std::vector<std::string> badKeys({"NotAPublicKey"});
BEAST_EXPECT(!trustedKeys->load (
emptyLocalKey, badKeys, emptyCfgPublishers));
badKeys[0] = format (randomNode(), "!");
BEAST_EXPECT(!trustedKeys->load (
emptyLocalKey, badKeys, emptyCfgPublishers));
badKeys[0] = format (randomNode(), "! Comment");
BEAST_EXPECT(!trustedKeys->load (
emptyLocalKey, badKeys, emptyCfgPublishers));
BEAST_EXPECT(!trustedKeys->load (emptyLocalKey,
{ "NotAPublicKey" }, emptyCfgPublishers));
BEAST_EXPECT(!trustedKeys->load (emptyLocalKey,
{ format (randomNode(), "!") }, emptyCfgPublishers));
// load terminates when encountering an invalid entry
auto const goodKey = randomNode();
badKeys.push_back (format (goodKey));
BEAST_EXPECT(!trustedKeys->load (
emptyLocalKey, badKeys, emptyCfgPublishers));
BEAST_EXPECT(!trustedKeys->load (emptyLocalKey,
{ format (randomNode(), "!"), format (goodKey) },
emptyCfgPublishers));
BEAST_EXPECT(!trustedKeys->listed (goodKey));
}
{
@@ -310,7 +324,7 @@ private:
auto trustedKeys = std::make_unique <ValidatorList> (
manifests, manifests, env.timeKeeper(), env.journal);
manifests.applyManifest (*Manifest::make_Manifest(cfgManifest));
manifests.applyManifest (*deserializeManifest(cfgManifest));
BEAST_EXPECT(trustedKeys->load (
localSigningPublic, cfgKeys, emptyCfgPublishers));
@@ -369,7 +383,7 @@ private:
auto const pubRevokedSigning = randomKeyPair(KeyType::secp256k1);
// make this manifest revoked (seq num = max)
// -- thus should not be loaded
pubManifests.applyManifest (*Manifest::make_Manifest (
pubManifests.applyManifest (*deserializeManifest (
makeManifestString (
pubRevokedPublic,
pubRevokedSecret,
@@ -530,10 +544,8 @@ private:
// do not apply list with revoked publisher key
// applied list is removed due to revoked publisher key
auto const signingKeysMax = randomKeyPair(KeyType::secp256k1);
auto maxManifest = base64_encode(makeManifestString (
publisherPublic, publisherSecret,
pubSigningKeys2.first, pubSigningKeys2.second,
std::numeric_limits<std::uint32_t>::max ()));
auto maxManifest = base64_encode(makeRevocationString (
publisherPublic, publisherSecret));
auto const sequence5 = 5;
auto const blob5 = makeList (
@@ -647,7 +659,7 @@ private:
BEAST_EXPECT(!trustedKeys->trusted (signingPublic1));
// Should trust the ephemeral signing key from the applied manifest
auto m1 = Manifest::make_Manifest (makeManifestString (
auto m1 = deserializeManifest(makeManifestString(
masterPublic, masterPrivate,
signingPublic1, signingKeys1.second, 1));
@@ -663,7 +675,7 @@ private:
// from the newest applied manifest
auto const signingKeys2 = randomKeyPair(KeyType::secp256k1);
auto const signingPublic2 = signingKeys2.first;
auto m2 = Manifest::make_Manifest (makeManifestString (
auto m2 = deserializeManifest(makeManifestString(
masterPublic, masterPrivate,
signingPublic2, signingKeys2.second, 2));
BEAST_EXPECT(
@@ -680,10 +692,8 @@ private:
auto const signingKeysMax = randomKeyPair(KeyType::secp256k1);
auto const signingPublicMax = signingKeysMax.first;
activeValidators.emplace (calcNodeID(signingPublicMax));
auto mMax = Manifest::make_Manifest (makeManifestString (
masterPublic, masterPrivate,
signingPublicMax, signingKeysMax.second,
std::numeric_limits<std::uint32_t>::max ()));
auto mMax = deserializeManifest(makeRevocationString(
masterPublic, masterPrivate));
BEAST_EXPECT(mMax->revoked ());
BEAST_EXPECT(

View File

@@ -18,6 +18,8 @@
//==============================================================================
#include <ripple/app/misc/TxQ.h>
#include <ripple/app/misc/Manifest.h>
#include <ripple/basics/StringUtilities.h>
#include <ripple/protocol/ErrorCodes.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/JsonFields.h>
@@ -744,21 +746,6 @@ class LedgerRPC_test : public beast::unit_test::suite
}
}
void testLedgerEntryGenerator()
{
testcase ("ledger_entry Request Generator");
using namespace test::jtx;
Env env {*this};
// All generator requests are deprecated.
Json::Value jvParams;
jvParams[jss::generator] = 5;
jvParams[jss::ledger_hash] = to_string (env.closed()->info().hash);
Json::Value const jrr = env.rpc (
"json", "ledger_entry", to_string (jvParams))[jss::result];
checkErrorValue (jrr, "deprecatedFeature", "");
}
void testLedgerEntryOffer()
{
testcase ("ledger_entry Request Offer");
@@ -1516,7 +1503,6 @@ public:
testLedgerEntryDepositPreauth();
testLedgerEntryDirectory();
testLedgerEntryEscrow();
testLedgerEntryGenerator();
testLedgerEntryOffer();
testLedgerEntryPayChan();
testLedgerEntryRippleState();