Tune relaying of untrusted proposals & validations:

In deciding whether to relay a proposal or validation, a server would
consider whether it was issued by a validator on that server's UNL.

While both trusted proposals and validations were always relayed,
the code prioritized relaying of untrusted proposals over untrusted
validations. While not technically incorrect, validations are
generally more "valuable" because they are required during the
consensus process, whereas proposals are not, strictly, required.

The commit introduces two new configuration options, allowing server
operators to fine-tune the relaying behavior:

The `[relay_proposals]` option controls the relaying behavior for
proposals received by this server. It has two settings: "trusted"
and "all" and the default is "trusted".

The `[relay_validations]` options controls the relaying behavior for
validations received by this server. It has two settings: "trusted"
and "all" and the default is "all".

This change does not require an amendment as it does not affect
transaction processing.
This commit is contained in:
Nik Bougalis
2020-05-05 23:40:55 -07:00
committed by manojsdoshi
parent ca664b17d3
commit 268e28a278
19 changed files with 405 additions and 214 deletions

View File

@@ -593,6 +593,23 @@
#
#
#
#
# [relay_proposals]
#
# Controls the relaying behavior for proposals received by this server that
# are issued by validators that are not on the server's UNL.
#
# Legal values are: "trusted" and "all". The default is "trusted".
#
#
# [relay_validations]
#
# Controls the relaying behavior for validations received by this server that
# are issued by validators that are not on the server's UNL.
#
# Legal values are: "trusted" and "all". The default is "all".
#
#
# [node_size]
#
# Tunes the servers based on the expected load and available memory. Legal

View File

@@ -217,7 +217,7 @@ RCLConsensus::Adaptor::propose(RCLCxPeerPos::Proposal const& proposal)
app_.getHashRouter().addSuppression(suppression);
app_.overlay().send(prop);
app_.overlay().broadcast(prop);
}
void
@@ -828,12 +828,17 @@ RCLConsensus::Adaptor::validate(
// suppress it if we receive it
app_.getHashRouter().addSuppression(
sha512Half(makeSlice(v->getSerialized())));
handleNewValidation(app_, v, "local");
// Broadcast to all our peers:
Blob validation = v->getSerialized();
protocol::TMValidation val;
val.set_validation(&validation[0], validation.size());
// Send signed validation to all of our directly connected peers
app_.overlay().send(val);
app_.overlay().broadcast(val);
// Publish to all our subscribers:
app_.getOPs().pubValidation(v);
}
void

View File

@@ -148,7 +148,7 @@ RCLValidationsAdaptor::acquire(LedgerHash const& hash)
return RCLValidatedLedger(std::move(ledger), j_);
}
bool
void
handleNewValidation(
Application& app,
std::shared_ptr<STValidation> const& val,
@@ -166,7 +166,6 @@ handleNewValidation(
if (!masterKey)
masterKey = app.validators().getListedKey(signingKey);
bool shouldRelay = false;
RCLValidations& validations = app.getValidations();
beast::Journal const j = validations.adaptor().journal();
@@ -186,13 +185,17 @@ handleNewValidation(
if (masterKey)
{
ValStatus const outcome = validations.add(calcNodeID(*masterKey), val);
auto const seq = val->getFieldU32(sfLedgerSequence);
if (j.debug())
dmp(j.debug(), to_string(outcome));
// One might think that we would not wish to relay validations that
// fail these checks. Somewhat counterintuitively, we actually want
// to do it for validations that we receive but deem suspicious, so
// that our peers will also observe them and realize they're bad.
if (outcome == ValStatus::conflicting && j.warn())
{
auto const seq = val->getFieldU32(sfLedgerSequence);
dmp(j.warn(),
"conflicting validations issued for " + to_string(seq) +
" (likely from a Byzantine validator)");
@@ -200,25 +203,13 @@ handleNewValidation(
if (outcome == ValStatus::multiple && j.warn())
{
auto const seq = val->getFieldU32(sfLedgerSequence);
dmp(j.warn(),
"multiple validations issued for " + to_string(seq) +
" (multiple validators operating with the same key?)");
}
if (outcome == ValStatus::badSeq && j.warn())
{
auto const seq = val->getFieldU32(sfLedgerSequence);
dmp(j.debug(),
"already validated sequence at or past " + std::to_string(seq));
}
if (val->isTrusted() && outcome == ValStatus::current)
{
app.getLedgerMaster().checkAccept(
hash, val->getFieldU32(sfLedgerSequence));
shouldRelay = true;
}
app.getLedgerMaster().checkAccept(hash, seq);
}
else
{
@@ -226,16 +217,6 @@ handleNewValidation(
<< toBase58(TokenType::NodePublic, signingKey)
<< " not added UNlisted";
}
// This currently never forwards untrusted validations, though we may
// reconsider in the future. From @JoelKatz:
// The idea was that we would have a certain number of validation slots with
// priority going to validators we trusted. Remaining slots might be
// allocated to validators that were listed by publishers we trusted but
// that we didn't choose to trust. The shorter term plan was just to forward
// untrusted validations if peers wanted them or if we had the
// ability/bandwidth to. None of that was implemented.
return shouldRelay;
}
} // namespace ripple

View File

@@ -243,10 +243,8 @@ using RCLValidations = Validations<RCLValidationsAdaptor>;
@param app Application object containing validations and ledgerMaster
@param val The validation to add
@param source Name associated with validation used in logging
@return Whether the validation should be relayed
*/
bool
void
handleNewValidation(
Application& app,
std::shared_ptr<STValidation> const& val,

View File

@@ -356,10 +356,8 @@ public:
Json::Value& jvResult) override;
// Ledger proposal/close functions.
void
processTrustedProposal(
RCLCxPeerPos proposal,
std::shared_ptr<protocol::TMProposeSet> set) override;
bool
processTrustedProposal(RCLCxPeerPos proposal) override;
bool
recvValidation(
@@ -1780,17 +1778,10 @@ NetworkOPsImp::getConsensusLCL()
return mConsensus.prevLedgerID();
}
void
NetworkOPsImp::processTrustedProposal(
RCLCxPeerPos peerPos,
std::shared_ptr<protocol::TMProposeSet> set)
bool
NetworkOPsImp::processTrustedProposal(RCLCxPeerPos peerPos)
{
if (mConsensus.peerProposal(app_.timeKeeper().closeTime(), peerPos))
{
app_.overlay().relay(*set, peerPos.suppressionID());
}
else
JLOG(m_journal.info()) << "Not relaying trusted proposal";
return mConsensus.peerProposal(app_.timeKeeper().closeTime(), peerPos);
}
void
@@ -2481,8 +2472,14 @@ NetworkOPsImp::recvValidation(
{
JLOG(m_journal.debug())
<< "recvValidation " << val->getLedgerHash() << " from " << source;
handleNewValidation(app_, val, source);
pubValidation(val);
return handleNewValidation(app_, val, source);
// We will always relay trusted validations; if configured, we will
// also relay all untrusted validations.
return app_.config().RELAY_UNTRUSTED_VALIDATIONS || val->isTrusted();
}
Json::Value

View File

@@ -169,10 +169,8 @@ public:
//--------------------------------------------------------------------------
// ledger proposal/close functions
virtual void
processTrustedProposal(
RCLCxPeerPos peerPos,
std::shared_ptr<protocol::TMProposeSet> set) = 0;
virtual bool
processTrustedProposal(RCLCxPeerPos peerPos) = 0;
virtual bool
recvValidation(

View File

@@ -162,6 +162,33 @@ rand_int()
}
/** @} */
/** Return a random byte */
/** @{ */
template <class Byte, class Engine>
std::enable_if_t<
(std::is_same<Byte, unsigned char>::value ||
std::is_same<Byte, std::uint8_t>::value) &&
detail::is_engine<Engine>::value,
Byte>
rand_byte(Engine& engine)
{
return static_cast<Byte>(rand_int<Engine, std::uint32_t>(
engine,
std::numeric_limits<Byte>::min(),
std::numeric_limits<Byte>::max()));
}
template <class Byte = std::uint8_t>
std::enable_if_t<
(std::is_same<Byte, unsigned char>::value ||
std::is_same<Byte, std::uint8_t>::value),
Byte>
rand_byte()
{
return rand_byte<Byte>(default_prng());
}
/** @} */
/** Return a random boolean value */
/** @{ */
template <class Engine>

View File

@@ -136,6 +136,8 @@ public:
std::size_t NETWORK_QUORUM = 1;
// Peer networking parameters
bool RELAY_UNTRUSTED_VALIDATIONS = true;
bool RELAY_UNTRUSTED_PROPOSALS = false;
// True to ask peers not to relay current IP.
bool PEER_PRIVATE = false;

View File

@@ -69,6 +69,8 @@ struct ConfigSection
#define SECTION_PATH_SEARCH_MAX "path_search_max"
#define SECTION_PEER_PRIVATE "peer_private"
#define SECTION_PEERS_MAX "peers_max"
#define SECTION_RELAY_PROPOSALS "relay_proposals"
#define SECTION_RELAY_VALIDATIONS "relay_validations"
#define SECTION_RPC_STARTUP "rpc_startup"
#define SECTION_SIGNING_SUPPORT "signing_support"
#define SECTION_SNTP "sntp_servers"

View File

@@ -397,6 +397,30 @@ Config::loadFromString(std::string const& fileContents)
if (getSingleSection(secConfig, SECTION_SSL_VERIFY, strTemp, j_))
SSL_VERIFY = beast::lexicalCastThrow<bool>(strTemp);
if (getSingleSection(secConfig, SECTION_RELAY_VALIDATIONS, strTemp, j_))
{
if (boost::iequals(strTemp, "all"))
RELAY_UNTRUSTED_VALIDATIONS = true;
else if (boost::iequals(strTemp, "trusted"))
RELAY_UNTRUSTED_VALIDATIONS = false;
else
Throw<std::runtime_error>(
"Invalid value specified in [" SECTION_RELAY_VALIDATIONS
"] section");
}
if (getSingleSection(secConfig, SECTION_RELAY_PROPOSALS, strTemp, j_))
{
if (boost::iequals(strTemp, "all"))
RELAY_UNTRUSTED_PROPOSALS = true;
else if (boost::iequals(strTemp, "trusted"))
RELAY_UNTRUSTED_PROPOSALS = false;
else
Throw<std::runtime_error>(
"Invalid value specified in [" SECTION_RELAY_PROPOSALS
"] section");
}
if (exists(SECTION_VALIDATION_SEED) && exists(SECTION_VALIDATOR_TOKEN))
Throw<std::runtime_error>("Cannot have both [" SECTION_VALIDATION_SEED
"] "

View File

@@ -141,11 +141,11 @@ public:
/** Broadcast a proposal. */
virtual void
send(protocol::TMProposeSet& m) = 0;
broadcast(protocol::TMProposeSet& m) = 0;
/** Broadcast a validation. */
virtual void
send(protocol::TMValidation& m) = 0;
broadcast(protocol::TMValidation& m) = 0;
/** Relay a proposal. */
virtual void

View File

@@ -1134,26 +1134,11 @@ OverlayImpl::findPeerByPublicKey(PublicKey const& pubKey)
}
void
OverlayImpl::send(protocol::TMProposeSet& m)
OverlayImpl::broadcast(protocol::TMProposeSet& m)
{
auto const sm = std::make_shared<Message>(m, protocol::mtPROPOSE_LEDGER);
for_each([&](std::shared_ptr<PeerImp>&& p) { p->send(sm); });
}
void
OverlayImpl::send(protocol::TMValidation& m)
{
auto const sm = std::make_shared<Message>(m, protocol::mtVALIDATION);
for_each([&](std::shared_ptr<PeerImp>&& p) { p->send(sm); });
SerialIter sit(m.validation().data(), m.validation().size());
auto val = std::make_shared<STValidation>(
std::ref(sit),
[this](PublicKey const& pk) {
return calcNodeID(app_.validatorManifests().getMasterKey(pk));
},
false);
app_.getOPs().pubValidation(val);
}
void
OverlayImpl::relay(protocol::TMProposeSet& m, uint256 const& uid)
@@ -1169,6 +1154,13 @@ OverlayImpl::relay(protocol::TMProposeSet& m, uint256 const& uid)
}
}
void
OverlayImpl::broadcast(protocol::TMValidation& m)
{
auto const sm = std::make_shared<Message>(m, protocol::mtVALIDATION);
for_each([sm](std::shared_ptr<PeerImp>&& p) { p->send(sm); });
}
void
OverlayImpl::relay(protocol::TMValidation& m, uint256 const& uid)
{

View File

@@ -201,10 +201,10 @@ public:
findPeerByPublicKey(PublicKey const& pubKey) override;
void
send(protocol::TMProposeSet& m) override;
broadcast(protocol::TMProposeSet& m) override;
void
send(protocol::TMValidation& m) override;
broadcast(protocol::TMValidation& m) override;
void
relay(protocol::TMProposeSet& m, uint256 const& uid) override;

View File

@@ -2453,25 +2453,15 @@ PeerImp::checkPropose(
return;
}
bool relay;
if (isTrusted)
{
app_.getOPs().processTrustedProposal(peerPos, packet);
}
relay = app_.getOPs().processTrustedProposal(peerPos);
else
{
if (cluster() ||
(app_.getOPs().getConsensusLCL() ==
peerPos.proposal().prevLedger()))
{
// relay untrusted proposal
JLOG(p_journal_.trace()) << "relaying UNTRUSTED proposal";
overlay_.relay(*packet, peerPos.suppressionID());
}
else
{
JLOG(p_journal_.debug()) << "Not relaying UNTRUSTED proposal";
}
}
relay = app_.config().RELAY_UNTRUSTED_PROPOSALS || cluster();
if (relay)
app_.overlay().relay(*packet, peerPos.suppressionID());
}
void

View File

@@ -122,15 +122,19 @@ public:
// Perform additional initialization
f(*this);
// This is a logic error because we should never assemble a
// validation without a ledger sequence number.
if (!isFieldPresent(sfLedgerSequence))
LogicError("Missing ledger sequence in validation");
// Finally, sign the validation and mark it as trusted:
setFlag(vfFullyCanonicalSig);
setFieldVL(sfSignature, signDigest(pk, sk, getSigningHash()));
setTrusted();
// Check to ensure that all required fields are present.
for (auto const& e : validationFormat())
{
if (e.style() == soeREQUIRED && !isFieldPresent(e.sField()))
LogicError(
"Required field '" + e.sField().getName() +
"' missing from validation.");
}
}
STBase*

View File

@@ -43,7 +43,7 @@ STValidation::validationFormat()
{sfReserveIncrement, soeOPTIONAL},
{sfSigningTime, soeREQUIRED},
{sfSigningPubKey, soeREQUIRED},
{sfSignature, soeOPTIONAL},
{sfSignature, soeREQUIRED},
{sfConsensusHash, soeOPTIONAL},
{sfCookie, soeDEFAULT},
{sfValidatedHash, soeOPTIONAL},

View File

@@ -400,6 +400,7 @@ public:
if (!field.empty())
v.setFieldV256(
sfAmendments, STVector256(sfAmendments, field));
v.setFieldU32(sfLedgerSequence, 6180339);
});
validations.emplace_back(v);

View File

@@ -41,7 +41,7 @@ class RCLValidations_test : public beast::unit_test::suite
keys.first,
keys.second,
calcNodeID(keys.first),
[&](STValidation& v) {});
[&](STValidation& v) { v.setFieldU32(sfLedgerSequence, 123456); });
BEAST_EXPECT(v->isTrusted());
v->setUntrusted();
@@ -312,89 +312,6 @@ class RCLValidations_test : public beast::unit_test::suite
BEAST_EXPECT(trie.branchSupport(ledg_259) == 4);
}
void
testLedgerSequence()
{
testcase("Validations with and without the LedgerSequence field");
auto const nodeID =
from_hex_text<NodeID>("38ECC15DBD999DE4CE70A6DC69A4166AB18031A7");
try
{
std::string const withLedgerSequence =
"228000000126034B9FFF2926460DC55185937F7F41DD7977F21B9DF95FCB61"
"9E5132ABB0D7ADEA0F7CE8A9347871A34250179D85BDE824F57FFE0AC8F89B"
"55FCB89277272A1D83D08ADEC98096A88EF723137321029D19FB0940E5C0D8"
"5873FA711999944A687D129DA5C33E928C2751FC1B31EB3276463044022022"
"6229CF66A678EE021F62CA229BA006B41939845004D3FAF8347C6FFBB7C613"
"02200BE9CD3629FD67C6C672BD433A2769FCDB36B1ECA2292919C58A86224E"
"2BF5970313C13F00C1FC4A53E60AB02C864641002B3172F38677E29C26C540"
"6685179B37E1EDAC157D2D480E006395B76F948E3E07A45A05FE10230D88A7"
"993C71F97AE4B1F2D11F4AFA8FA1BC8827AD4C0F682C03A8B671DCDF6B5C4D"
"E36D44243A684103EF8825BA44241B3BD880770BFA4DA21C71805768318553"
"68CBEC6A3154FDE4A7676E3012E8230864E95A58C60FD61430D7E1B4D33531"
"95F2981DC12B0C7C0950FFAC30CD365592B8EE40489BA01AE2F7555CAC9C98"
"3145871DC82A42A31CF5BAE7D986E83A7D2ECE3AD5FA87AB2195AE015C9504"
"69ABF0B72EAACED318F74886AE9089308AF3B8B10B7192C4E613E1D2E4D9BA"
"64B2EE2D5232402AE82A6A7220D953";
if (auto ret = strUnHex(withLedgerSequence); ret)
{
SerialIter sit(makeSlice(*ret));
auto val = std::make_shared<STValidation>(
std::ref(sit),
[nodeID](PublicKey const& pk) { return nodeID; },
false);
BEAST_EXPECT(val);
BEAST_EXPECT(calcNodeID(val->getSignerPublic()) == nodeID);
BEAST_EXPECT(val->isFieldPresent(sfLedgerSequence));
}
}
catch (std::exception const& ex)
{
fail(std::string("Unexpected exception thrown: ") + ex.what());
}
try
{
std::string const withoutLedgerSequence =
"22800000012926460DC55185937F7F41DD7977F21B9DF95FCB619E5132ABB0"
"D7ADEA0F7CE8A9347871A34250179D85BDE824F57FFE0AC8F89B55FCB89277"
"272A1D83D08ADEC98096A88EF723137321029D19FB0940E5C0D85873FA7119"
"99944A687D129DA5C33E928C2751FC1B31EB3276473045022100BE2EA49CF2"
"FFB7FE7A03F6860B8C35FEA04A064C7023FE28EC97E5A32E85DEC4022003B8"
"5D1D497F504B34F089D5BDB91BD888690C3D3A242A0FEF1DD52875FBA02E03"
"13C13F00C1FC4A53E60AB02C864641002B3172F38677E29C26C5406685179B"
"37E1EDAC157D2D480E006395B76F948E3E07A45A05FE10230D88A7993C71F9"
"7AE4B1F2D11F4AFA8FA1BC8827AD4C0F682C03A8B671DCDF6B5C4DE36D4424"
"3A684103EF8825BA44241B3BD880770BFA4DA21C7180576831855368CBEC6A"
"3154FDE4A7676E3012E8230864E95A58C60FD61430D7E1B4D3353195F2981D"
"C12B0C7C0950FFAC30CD365592B8EE40489BA01AE2F7555CAC9C983145871D"
"C82A42A31CF5BAE7D986E83A7D2ECE3AD5FA87AB2195AE015C950469ABF0B7"
"2EAACED318F74886AE9089308AF3B8B10B7192C4E613E1D2E4D9BA64B2EE2D"
"5232402AE82A6A7220D953";
if (auto ret = strUnHex(withoutLedgerSequence); ret)
{
SerialIter sit(makeSlice(*ret));
auto val = std::make_shared<STValidation>(
std::ref(sit),
[nodeID](PublicKey const& pk) { return nodeID; },
false);
fail("Expected exception not thrown from validation");
}
}
catch (std::exception const& ex)
{
pass();
}
}
public:
void
run() override
@@ -402,7 +319,6 @@ public:
testChangeTrusted();
testRCLValidatedLedger();
testLedgerTrieRCLValidatedLedger();
testLedgerSequence();
}
};

View File

@@ -18,7 +18,9 @@
//==============================================================================
#include <ripple/basics/Log.h>
#include <ripple/basics/random.h>
#include <ripple/beast/unit_test.h>
#include <ripple/beast/xor_shift_engine.h>
#include <ripple/json/json_reader.h>
#include <ripple/json/to_string.h>
#include <ripple/protocol/SecretKey.h>
@@ -33,79 +35,314 @@ namespace ripple {
class STValidation_test : public beast::unit_test::suite
{
// No public key:
static constexpr std::uint8_t payload1[] = {
0x22, 0x80, 0x00, 0x00, 0x01, 0x26, 0x03, 0x4B, 0xEA, 0x97, 0x29, 0x26,
0x47, 0x31, 0x1A, 0x3A, 0x4E, 0x69, 0x6B, 0x2B, 0x54, 0x69, 0x66, 0x66,
0x51, 0x53, 0x1F, 0x1A, 0x4E, 0xBB, 0x43, 0x19, 0x69, 0x16, 0xF8, 0x3E,
0xEA, 0x5C, 0x77, 0x94, 0x08, 0x19, 0x0B, 0x4B, 0x40, 0x8C, 0xDE, 0xB8,
0x79, 0x39, 0xF3, 0x9D, 0x66, 0x7B, 0x12, 0xCA, 0x97, 0x50, 0x17, 0x21,
0x0B, 0xAB, 0xBC, 0x8C, 0xB7, 0xFB, 0x45, 0x49, 0xED, 0x1E, 0x07, 0xB4,
0xFB, 0xC5, 0xF2, 0xFB, 0x67, 0x2D, 0x18, 0xA6, 0x43, 0x35, 0x28, 0xEB,
0xD9, 0x06, 0x3E, 0xB3, 0x8B, 0xC2, 0xE0, 0x76, 0x47, 0x30, 0x45, 0x02,
0x21, 0x00, 0xAF, 0x1D, 0x17, 0xA2, 0x12, 0x7B, 0xA4, 0x6B, 0x40, 0xBD,
0x58, 0x76, 0x39, 0x3F, 0xF4, 0x49, 0x6B, 0x25, 0xA1, 0xAD, 0xB7, 0x36,
0xFB, 0x64, 0x4C, 0x05, 0x21, 0x0C, 0x43, 0x02, 0xE5, 0xEE, 0x02, 0x20,
0x26, 0x01, 0x7C, 0x5F, 0x69, 0xDA, 0xD1, 0xC3, 0x28, 0xED, 0x80, 0x05,
0x36, 0x86, 0x8B, 0x1B, 0x22, 0xE4, 0x8E, 0x09, 0x11, 0x52, 0x28, 0x5A,
0x48, 0x8F, 0x98, 0x7A, 0x5A, 0x10, 0x74, 0xCC};
// Short public key:
static constexpr std::uint8_t payload2[] = {
0x22, 0x80, 0x00, 0x00, 0x01, 0x26, 0x03, 0x4B, 0xEA, 0x97, 0x29, 0x26,
0x47, 0x31, 0x1A, 0x51, 0x53, 0x1F, 0x1A, 0x4E, 0xBB, 0x43, 0x19, 0x69,
0x16, 0xF8, 0x3E, 0xEA, 0x5C, 0x77, 0x94, 0x08, 0x19, 0x0B, 0x4B, 0x40,
0x8C, 0xDE, 0xB8, 0x79, 0x39, 0xF3, 0x9D, 0x66, 0x7B, 0x12, 0xCA, 0x97,
0x50, 0x17, 0x21, 0x0B, 0xAB, 0xBC, 0x8C, 0xB7, 0xFB, 0x45, 0x49, 0xED,
0x1E, 0x07, 0xB4, 0xFB, 0xC5, 0xF2, 0xFB, 0x67, 0x2D, 0x18, 0xA6, 0x43,
0x35, 0x28, 0xEB, 0xD9, 0x06, 0x3E, 0xB3, 0x8B, 0xC2, 0xE0, 0x73, 0x20,
0x02, 0x9D, 0x19, 0xFB, 0x09, 0x40, 0xE5, 0xC0, 0xD8, 0x58, 0x73, 0xFA,
0x71, 0x19, 0x99, 0x94, 0x4A, 0x68, 0x7D, 0x12, 0x9D, 0xA5, 0xC3, 0x3E,
0x92, 0x8C, 0x27, 0x51, 0xFC, 0x1B, 0x31, 0xEB, 0x76, 0x46, 0x30, 0x44,
0x02, 0x20, 0x34, 0x89, 0xA3, 0xBF, 0xA9, 0x97, 0x13, 0xBC, 0x87, 0x61,
0xC5, 0x2B, 0x7F, 0xAA, 0xE9, 0x31, 0x4C, 0xCD, 0x6F, 0x57, 0x68, 0x70,
0xC8, 0xDC, 0x58, 0x76, 0x91, 0x2F, 0x70, 0x2F, 0xD0, 0x78, 0x02, 0x20,
0x7E, 0x57, 0x9D, 0xCA, 0x11, 0xF1, 0x3B, 0xA0, 0x39, 0x38, 0x37, 0x40,
0xC5, 0xC8, 0xFE, 0xC1, 0xFC, 0xE9, 0xE7, 0x84, 0x6C, 0x2D, 0x47, 0x6E,
0xD7, 0xFF, 0x83, 0x9D, 0xEF, 0x7D, 0xF7, 0x6A};
// Long public key:
static constexpr std::uint8_t payload3[] = {
0x22, 0x80, 0x00, 0x00, 0x01, 0x26, 0x03, 0x4B, 0xEA, 0x97, 0x29, 0x26,
0x47, 0x31, 0x1A, 0x51, 0x53, 0x1F, 0x1A, 0x4E, 0xBB, 0x43, 0x19, 0x69,
0x16, 0xF8, 0x3E, 0xEA, 0x5C, 0x77, 0x94, 0x08, 0x19, 0x0B, 0x4B, 0x40,
0x8C, 0xDE, 0xB8, 0x79, 0x39, 0xF3, 0x9D, 0x66, 0x7B, 0x12, 0xCA, 0x97,
0x50, 0x17, 0x21, 0x0B, 0xAB, 0xBC, 0x8C, 0xB7, 0xFB, 0x45, 0x49, 0xED,
0x1E, 0x07, 0xB4, 0xFB, 0xC5, 0xF2, 0xFB, 0x67, 0x2D, 0x18, 0xA6, 0x43,
0x35, 0x28, 0xEB, 0xD9, 0x06, 0x3E, 0xB3, 0x8B, 0xC2, 0xE0, 0x73, 0x22,
0x02, 0x9D, 0x19, 0xFB, 0x09, 0x40, 0xE5, 0xC0, 0xD8, 0x58, 0x73, 0xFA,
0x71, 0x19, 0x99, 0x94, 0x4A, 0x68, 0x7D, 0x12, 0x9D, 0xA5, 0xC3, 0x3E,
0x92, 0x8C, 0x27, 0x51, 0xFC, 0x1B, 0x31, 0xEB, 0x32, 0x78, 0x76, 0x46,
0x30, 0x44, 0x02, 0x20, 0x3C, 0xAB, 0xEE, 0x36, 0xD8, 0xF3, 0x74, 0x5F,
0x50, 0x28, 0x66, 0x17, 0x57, 0x26, 0x6A, 0xBD, 0x9A, 0x19, 0x08, 0xAA,
0x65, 0x94, 0x0B, 0xDF, 0x24, 0x20, 0x44, 0x99, 0x05, 0x8C, 0xB7, 0x3D,
0x02, 0x20, 0x79, 0x66, 0xE6, 0xCC, 0xA2, 0x5E, 0x15, 0xFE, 0x18, 0x4B,
0xB2, 0xA8, 0x01, 0x3A, 0xD6, 0x63, 0x54, 0x08, 0x1B, 0xDA, 0xD0, 0x04,
0xEF, 0x4C, 0x73, 0xB3, 0xFF, 0xFE, 0xA9, 0x8E, 0x92, 0xE8};
// Ed25519 public key:
static constexpr std::uint8_t payload4[] = {
0x22, 0x80, 0x00, 0x00, 0x01, 0x26, 0x03, 0x4B, 0xEA, 0x97, 0x29, 0x26,
0x47, 0x31, 0x1A, 0x51, 0x53, 0x1F, 0x1A, 0x4E, 0xBB, 0x43, 0x19, 0x69,
0x16, 0xF8, 0x3E, 0xEA, 0x5C, 0x77, 0x94, 0x08, 0x19, 0x0B, 0x4B, 0x40,
0x8C, 0xDE, 0xB8, 0x79, 0x39, 0xF3, 0x9D, 0x66, 0x7B, 0x12, 0xCA, 0x97,
0x50, 0x17, 0x21, 0x0B, 0xAB, 0xBC, 0x8C, 0xB7, 0xFB, 0x45, 0x49, 0xED,
0x1E, 0x07, 0xB4, 0xFB, 0xC5, 0xF2, 0xFB, 0x67, 0x2D, 0x18, 0xA6, 0x43,
0x35, 0x28, 0xEB, 0xD9, 0x06, 0x3E, 0xB3, 0x8B, 0xC2, 0xE0, 0x73, 0x21,
0xED, 0x04, 0x8B, 0x9A, 0x31, 0x5E, 0xC7, 0x33, 0xC0, 0x15, 0x3B, 0x67,
0x04, 0x73, 0x7A, 0x91, 0x3D, 0xEF, 0x57, 0x1D, 0xAD, 0xEC, 0x57, 0xE5,
0x91, 0x5D, 0x55, 0xD9, 0x32, 0x9D, 0x45, 0x12, 0x85, 0x76, 0x40, 0x52,
0x07, 0xF9, 0x0D, 0x18, 0x2B, 0xB7, 0xAF, 0x5D, 0x43, 0xF8, 0xF9, 0xC5,
0xAD, 0xF9, 0xBA, 0x33, 0x23, 0xC0, 0x2F, 0x95, 0xFF, 0x36, 0x94, 0xD8,
0x99, 0x99, 0xE0, 0x66, 0xF8, 0xB6, 0x27, 0x22, 0xFD, 0x29, 0x39, 0x30,
0x39, 0xAB, 0x93, 0xDB, 0x9D, 0x2C, 0xE5, 0xF0, 0x4C, 0xB7, 0x30, 0xFD,
0xC7, 0xD3, 0x21, 0xC9, 0x4E, 0x0D, 0x8A, 0x1B, 0xB2, 0x89, 0x97, 0x10,
0x7E, 0x84, 0x09};
// No ledger sequence:
static constexpr std::uint8_t payload5[] = {
0x22, 0x80, 0x00, 0x00, 0x01, 0x29, 0x26, 0x47, 0x31, 0x1A, 0x51, 0x53,
0x1F, 0x1A, 0x4E, 0xBB, 0x43, 0x19, 0x69, 0x16, 0xF8, 0x3E, 0xEA, 0x5C,
0x77, 0x94, 0x08, 0x19, 0x0B, 0x4B, 0x40, 0x8C, 0xDE, 0xB8, 0x79, 0x39,
0xF3, 0x9D, 0x66, 0x7B, 0x12, 0xCA, 0x97, 0x50, 0x17, 0x21, 0x0B, 0xAB,
0xBC, 0x8C, 0xB7, 0xFB, 0x45, 0x49, 0xED, 0x1E, 0x07, 0xB4, 0xFB, 0xC5,
0xF2, 0xFB, 0x67, 0x2D, 0x18, 0xA6, 0x43, 0x35, 0x28, 0xEB, 0xD9, 0x06,
0x3E, 0xB3, 0x8B, 0xC2, 0xE0, 0x73, 0x21, 0x02, 0x9D, 0x19, 0xFB, 0x09,
0x40, 0xE5, 0xC0, 0xD8, 0x58, 0x73, 0xFA, 0x71, 0x19, 0x99, 0x94, 0x4A,
0x68, 0x7D, 0x12, 0x9D, 0xA5, 0xC3, 0x3E, 0x92, 0x8C, 0x27, 0x51, 0xFC,
0x1B, 0x31, 0xEB, 0x32, 0x76, 0x47, 0x30, 0x45, 0x02, 0x21, 0x00, 0x83,
0xB3, 0x1B, 0xE9, 0x03, 0x8F, 0x4A, 0x92, 0x8B, 0x9B, 0x51, 0xEF, 0x79,
0xED, 0xA1, 0x4A, 0x58, 0x9B, 0x20, 0xCF, 0x89, 0xC4, 0x75, 0x99, 0x5F,
0x6D, 0x79, 0x51, 0x79, 0x07, 0xF9, 0x93, 0x02, 0x20, 0x39, 0xA6, 0x0C,
0x77, 0x68, 0x84, 0x50, 0xDB, 0xDA, 0x64, 0x32, 0x74, 0xEC, 0x63, 0x48,
0x48, 0x96, 0xB5, 0x94, 0x57, 0x55, 0x8D, 0x7D, 0xD8, 0x25, 0x78, 0xD1,
0xEA, 0x5F, 0xD9, 0xC7, 0xAA};
// No sign time:
static constexpr std::uint8_t payload6[] = {
0x22, 0x80, 0x00, 0x00, 0x01, 0x26, 0x03, 0x4B, 0xEA, 0x97, 0x51, 0x53,
0x1F, 0x1A, 0x4E, 0xBB, 0x43, 0x19, 0x69, 0x16, 0xF8, 0x3E, 0xEA, 0x5C,
0x77, 0x94, 0x08, 0x19, 0x0B, 0x4B, 0x40, 0x8C, 0xDE, 0xB8, 0x79, 0x39,
0xF3, 0x9D, 0x66, 0x7B, 0x12, 0xCA, 0x97, 0x50, 0x17, 0x21, 0x0B, 0xAB,
0xBC, 0x8C, 0xB7, 0xFB, 0x45, 0x49, 0xED, 0x1E, 0x07, 0xB4, 0xFB, 0xC5,
0xF2, 0xFB, 0x67, 0x2D, 0x18, 0xA6, 0x43, 0x35, 0x28, 0xEB, 0xD9, 0x06,
0x3E, 0xB3, 0x8B, 0xC2, 0xE0, 0x73, 0x21, 0x02, 0x9D, 0x19, 0xFB, 0x09,
0x40, 0xE5, 0xC0, 0xD8, 0x58, 0x73, 0xFA, 0x71, 0x19, 0x99, 0x94, 0x4A,
0x68, 0x7D, 0x12, 0x9D, 0xA5, 0xC3, 0x3E, 0x92, 0x8C, 0x27, 0x51, 0xFC,
0x1B, 0x31, 0xEB, 0x32, 0x76, 0x47, 0x30, 0x45, 0x02, 0x21, 0x00, 0xDD,
0xB0, 0x59, 0x9A, 0x02, 0x3E, 0xF2, 0x44, 0xCE, 0x1D, 0xA8, 0x99, 0x06,
0xF3, 0x8A, 0x4B, 0xEB, 0x95, 0x42, 0x63, 0x6A, 0x6C, 0x04, 0x30, 0x7F,
0x62, 0x78, 0x3A, 0x89, 0xB0, 0x3F, 0x22, 0x02, 0x20, 0x4E, 0x6A, 0x55,
0x63, 0x8A, 0x19, 0xED, 0xFE, 0x70, 0x34, 0xD1, 0x30, 0xED, 0x7C, 0xAF,
0xB2, 0x78, 0xBB, 0x15, 0x6C, 0x42, 0x3E, 0x19, 0x5D, 0xEA, 0xC5, 0x5E,
0x23, 0xE2, 0x14, 0x80, 0x54};
// No signature field:
static constexpr std::uint8_t payload7[] = {
0x22, 0x80, 0x00, 0x00, 0x01, 0x26, 0x03, 0x4B, 0xEA, 0x97, 0x29, 0x26,
0x47, 0x31, 0x1A, 0x51, 0x53, 0x1F, 0x1A, 0x4E, 0xBB, 0x43, 0x19, 0x69,
0x16, 0xF8, 0x3E, 0xEA, 0x5C, 0x77, 0x94, 0x08, 0x19, 0x0B, 0x4B, 0x40,
0x8C, 0xDE, 0xB8, 0x79, 0x39, 0xF3, 0x9D, 0x66, 0x7B, 0x12, 0xCA, 0x97,
0x50, 0x17, 0x21, 0x0B, 0xAB, 0xBC, 0x8C, 0xB7, 0xFB, 0x45, 0x49, 0xED,
0x1E, 0x07, 0xB4, 0xFB, 0xC5, 0xF2, 0xFB, 0x67, 0x2D, 0x18, 0xA6, 0x43,
0x35, 0x28, 0xEB, 0xD9, 0x06, 0x3E, 0xB3, 0x8B, 0xC2, 0xE0, 0x73, 0x21,
0x02, 0x9D, 0x19, 0xFB, 0x09, 0x40, 0xE5, 0xC0, 0xD8, 0x58, 0x73, 0xFA,
0x71, 0x19, 0x99, 0x94, 0x4A, 0x68, 0x7D, 0x12, 0x9D, 0xA5, 0xC3, 0x3E,
0x92, 0x8C, 0x27, 0x51, 0xFC, 0x1B, 0x31, 0xEB, 0x32};
// Good:
static constexpr std::uint8_t payload8[] = {
0x22, 0x80, 0x00, 0x00, 0x01, 0x26, 0x03, 0x4B, 0xEA, 0x97, 0x29, 0x26,
0x47, 0x31, 0x1A, 0x51, 0x53, 0x1F, 0x1A, 0x4E, 0xBB, 0x43, 0x19, 0x69,
0x16, 0xF8, 0x3E, 0xEA, 0x5C, 0x77, 0x94, 0x08, 0x19, 0x0B, 0x4B, 0x40,
0x8C, 0xDE, 0xB8, 0x79, 0x39, 0xF3, 0x9D, 0x66, 0x7B, 0x12, 0xCA, 0x97,
0x50, 0x17, 0x21, 0x0B, 0xAB, 0xBC, 0x8C, 0xB7, 0xFB, 0x45, 0x49, 0xED,
0x1E, 0x07, 0xB4, 0xFB, 0xC5, 0xF2, 0xFB, 0x67, 0x2D, 0x18, 0xA6, 0x43,
0x35, 0x28, 0xEB, 0xD9, 0x06, 0x3E, 0xB3, 0x8B, 0xC2, 0xE0, 0x73, 0x21,
0x02, 0x9D, 0x19, 0xFB, 0x09, 0x40, 0xE5, 0xC0, 0xD8, 0x58, 0x73, 0xFA,
0x71, 0x19, 0x99, 0x94, 0x4A, 0x68, 0x7D, 0x12, 0x9D, 0xA5, 0xC3, 0x3E,
0x92, 0x8C, 0x27, 0x51, 0xFC, 0x1B, 0x31, 0xEB, 0x32, 0x76, 0x47, 0x30,
0x45, 0x02, 0x21, 0x00, 0xDD, 0x29, 0xDC, 0xAC, 0x82, 0x5E, 0xF9, 0xE2,
0x2D, 0x26, 0x03, 0x95, 0xC2, 0x11, 0x3A, 0x2A, 0x83, 0xEE, 0xA0, 0x2B,
0x9F, 0x2A, 0x51, 0xBD, 0x6B, 0xF7, 0x83, 0xCE, 0x4A, 0x7C, 0x52, 0x29,
0x02, 0x20, 0x52, 0x45, 0xB9, 0x07, 0x57, 0xEF, 0xB2, 0x6C, 0x69, 0xC5,
0x47, 0xCA, 0xE2, 0x76, 0x00, 0xFC, 0x35, 0x46, 0x5D, 0x19, 0x64, 0xCE,
0xCA, 0x88, 0xA1, 0x2A, 0x20, 0xCF, 0x3C, 0xF9, 0xCE, 0xCF};
public:
void
testDeserialization()
{
testcase("Deserialization");
constexpr std::uint8_t payload1[] = {
// specifies an Ed25519 public key.
0x22, 0x00, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00,
0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x2a, 0x73, 0x21, 0xed, 0x78, 0x00, 0xe6, 0x73,
0x00, 0x72, 0x00, 0x3c, 0x00, 0x00, 0x00, 0x88, 0x00, 0xe6,
0x73, 0x38, 0x00, 0x00, 0x8a, 0x00, 0x88, 0x4e, 0x31, 0x30,
0x5f, 0x5f, 0x63, 0x78, 0x78, 0x61, 0x62, 0x69};
try
{
SerialIter sit{payload8};
constexpr std::uint8_t payload2[] = {
0x22, 0x00, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00,
0x51, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x2a, 0x73, 0x21, 0xed, 0xff, 0x03, 0x1c, 0xbe,
0x65, 0x22, 0x61, 0x9c, 0x5e, 0x13, 0x12, 0x00, 0x3b, 0x43,
0x00, 0x00, 0x00, 0xf7, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f, 0x3f,
0x3f, 0x3f, 0x13, 0x13, 0x13, 0x3a, 0x27, 0xff};
auto val = std::make_shared<STValidation>(
sit, [](PublicKey const& pk) { return calcNodeID(pk); }, true);
constexpr std::uint8_t payload3[] = {
// Has no public key at all
0x22, 0x00, 0x00, 0x00, 0x00, 0x29, 0x00, 0x00, 0x00, 0x00, 0x51,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x2a};
BEAST_EXPECT(val);
BEAST_EXPECT(val->isFieldPresent(sfLedgerSequence));
BEAST_EXPECT(val->isFieldPresent(sfSigningPubKey));
BEAST_EXPECT(val->isFieldPresent(sfSigningTime));
BEAST_EXPECT(val->isFieldPresent(sfFlags));
BEAST_EXPECT(val->isFieldPresent(sfLedgerHash));
BEAST_EXPECT(val->isFieldPresent(sfSignature));
}
catch (std::exception const& ex)
{
fail(std::string("Unexpected exception thrown: ") + ex.what());
}
testcase("Deserialization: Public Key Tests");
try
{
SerialIter sit{payload1};
auto stx = std::make_shared<ripple::STValidation>(
auto val = std::make_shared<ripple::STValidation>(
sit, [](PublicKey const& pk) { return calcNodeID(pk); }, false);
fail("An exception should have been thrown");
}
catch (std::exception const& e)
catch (std::exception const& ex)
{
BEAST_EXPECT(
strcmp(e.what(), "Invalid public key in validation") == 0);
strcmp(
ex.what(),
"Field 'SigningPubKey' is required but missing.") == 0);
}
try
{
SerialIter sit{payload2};
auto stx = std::make_shared<ripple::STValidation>(
auto val = std::make_shared<ripple::STValidation>(
sit, [](PublicKey const& pk) { return calcNodeID(pk); }, false);
fail("An exception should have been thrown");
}
catch (std::exception const& e)
catch (std::exception const& ex)
{
BEAST_EXPECT(
strcmp(e.what(), "Invalid public key in validation") == 0);
strcmp(ex.what(), "Invalid public key in validation") == 0);
}
try
{
SerialIter sit{payload3};
auto stx = std::make_shared<ripple::STValidation>(
auto val = std::make_shared<ripple::STValidation>(
sit, [](PublicKey const& pk) { return calcNodeID(pk); }, false);
fail("An exception should have been thrown");
}
catch (std::exception const& e)
catch (std::exception const& ex)
{
BEAST_EXPECT(
strcmp(ex.what(), "Invalid public key in validation") == 0);
}
try
{
SerialIter sit{payload4};
auto val = std::make_shared<ripple::STValidation>(
sit, [](PublicKey const& pk) { return calcNodeID(pk); }, false);
fail("An exception should have been thrown");
}
catch (std::exception const& ex)
{
BEAST_EXPECT(
strcmp(ex.what(), "Invalid public key in validation") == 0);
}
testcase("Deserialization: Missing Fields");
try
{
SerialIter sit{payload5};
auto val = std::make_shared<STValidation>(
sit, [](PublicKey const& pk) { return calcNodeID(pk); }, false);
fail("Expected exception not thrown from validation");
}
catch (std::exception const& ex)
{
BEAST_EXPECT(
strcmp(
e.what(),
"Field 'SigningPubKey' is required but missing.") == 0);
ex.what(),
"Field 'LedgerSequence' is required but missing.") == 0);
}
try
{
SerialIter sit{payload6};
auto val = std::make_shared<STValidation>(
sit, [](PublicKey const& pk) { return calcNodeID(pk); }, false);
fail("Expected exception not thrown from validation");
}
catch (std::exception const& ex)
{
BEAST_EXPECT(
strcmp(
ex.what(),
"Field 'SigningTime' is required but missing.") == 0);
}
try
{
SerialIter sit{payload7};
auto val = std::make_shared<STValidation>(
sit, [](PublicKey const& pk) { return calcNodeID(pk); }, false);
fail("Expected exception not thrown from validation");
}
catch (std::exception const& ex)
{
BEAST_EXPECT(
strcmp(
ex.what(), "Field 'Signature' is required but missing.") ==
0);
}
testcase("Deserialization: Corrupted Data / Fuzzing");
// Mutate a known-good validation and expect it to fail:
std::vector<std::uint8_t> v;
for (auto c : payload8)
v.push_back(c);
beast::xor_shift_engine g(148979842);
for (std::size_t i = 0; i != v.size(); ++i)
{
auto v2 = v;
while (v2[i] == v[i])
v2[i] = rand_byte<std::uint8_t>(g);
try
{
SerialIter sit{makeSlice(v2)};
auto val = std::make_shared<STValidation>(
sit,
[](PublicKey const& pk) { return calcNodeID(pk); },
true);
fail(
"Mutated validation signature checked out: offset=" +
std::to_string(i));
}
catch (std::exception const&)
{
pass();
}
}
}