diff --git a/cfg/rippled-example.cfg b/cfg/rippled-example.cfg index 3c856b40f..c4917d650 100644 --- a/cfg/rippled-example.cfg +++ b/cfg/rippled-example.cfg @@ -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 diff --git a/src/ripple/app/consensus/RCLConsensus.cpp b/src/ripple/app/consensus/RCLConsensus.cpp index 90e9fd729..f05497a53 100644 --- a/src/ripple/app/consensus/RCLConsensus.cpp +++ b/src/ripple/app/consensus/RCLConsensus.cpp @@ -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 diff --git a/src/ripple/app/consensus/RCLValidations.cpp b/src/ripple/app/consensus/RCLValidations.cpp index 6311677c2..0546a86d6 100644 --- a/src/ripple/app/consensus/RCLValidations.cpp +++ b/src/ripple/app/consensus/RCLValidations.cpp @@ -148,7 +148,7 @@ RCLValidationsAdaptor::acquire(LedgerHash const& hash) return RCLValidatedLedger(std::move(ledger), j_); } -bool +void handleNewValidation( Application& app, std::shared_ptr 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 diff --git a/src/ripple/app/consensus/RCLValidations.h b/src/ripple/app/consensus/RCLValidations.h index 3f0245d67..9324e0c83 100644 --- a/src/ripple/app/consensus/RCLValidations.h +++ b/src/ripple/app/consensus/RCLValidations.h @@ -243,10 +243,8 @@ using RCLValidations = Validations; @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 const& val, diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index c4b7ad31c..28afc7dfb 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -356,10 +356,8 @@ public: Json::Value& jvResult) override; // Ledger proposal/close functions. - void - processTrustedProposal( - RCLCxPeerPos proposal, - std::shared_ptr 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 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 diff --git a/src/ripple/app/misc/NetworkOPs.h b/src/ripple/app/misc/NetworkOPs.h index fa0499526..34da5092b 100644 --- a/src/ripple/app/misc/NetworkOPs.h +++ b/src/ripple/app/misc/NetworkOPs.h @@ -169,10 +169,8 @@ public: //-------------------------------------------------------------------------- // ledger proposal/close functions - virtual void - processTrustedProposal( - RCLCxPeerPos peerPos, - std::shared_ptr set) = 0; + virtual bool + processTrustedProposal(RCLCxPeerPos peerPos) = 0; virtual bool recvValidation( diff --git a/src/ripple/basics/random.h b/src/ripple/basics/random.h index e149503a0..7ea8972ea 100644 --- a/src/ripple/basics/random.h +++ b/src/ripple/basics/random.h @@ -162,6 +162,33 @@ rand_int() } /** @} */ +/** Return a random byte */ +/** @{ */ +template +std::enable_if_t< + (std::is_same::value || + std::is_same::value) && + detail::is_engine::value, + Byte> +rand_byte(Engine& engine) +{ + return static_cast(rand_int( + engine, + std::numeric_limits::min(), + std::numeric_limits::max())); +} + +template +std::enable_if_t< + (std::is_same::value || + std::is_same::value), + Byte> +rand_byte() +{ + return rand_byte(default_prng()); +} +/** @} */ + /** Return a random boolean value */ /** @{ */ template diff --git a/src/ripple/core/Config.h b/src/ripple/core/Config.h index 0b7525efa..7943906fd 100644 --- a/src/ripple/core/Config.h +++ b/src/ripple/core/Config.h @@ -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; diff --git a/src/ripple/core/ConfigSections.h b/src/ripple/core/ConfigSections.h index cec6619f3..c9b61c2cb 100644 --- a/src/ripple/core/ConfigSections.h +++ b/src/ripple/core/ConfigSections.h @@ -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" diff --git a/src/ripple/core/impl/Config.cpp b/src/ripple/core/impl/Config.cpp index 3fc805b21..f12ba7dbc 100644 --- a/src/ripple/core/impl/Config.cpp +++ b/src/ripple/core/impl/Config.cpp @@ -397,6 +397,30 @@ Config::loadFromString(std::string const& fileContents) if (getSingleSection(secConfig, SECTION_SSL_VERIFY, strTemp, j_)) SSL_VERIFY = beast::lexicalCastThrow(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( + "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( + "Invalid value specified in [" SECTION_RELAY_PROPOSALS + "] section"); + } + if (exists(SECTION_VALIDATION_SEED) && exists(SECTION_VALIDATOR_TOKEN)) Throw("Cannot have both [" SECTION_VALIDATION_SEED "] " diff --git a/src/ripple/overlay/Overlay.h b/src/ripple/overlay/Overlay.h index 3c9bd1f4a..d5f0c4bae 100644 --- a/src/ripple/overlay/Overlay.h +++ b/src/ripple/overlay/Overlay.h @@ -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 diff --git a/src/ripple/overlay/impl/OverlayImpl.cpp b/src/ripple/overlay/impl/OverlayImpl.cpp index 883b0a456..953138206 100644 --- a/src/ripple/overlay/impl/OverlayImpl.cpp +++ b/src/ripple/overlay/impl/OverlayImpl.cpp @@ -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(m, protocol::mtPROPOSE_LEDGER); for_each([&](std::shared_ptr&& p) { p->send(sm); }); } -void -OverlayImpl::send(protocol::TMValidation& m) -{ - auto const sm = std::make_shared(m, protocol::mtVALIDATION); - for_each([&](std::shared_ptr&& p) { p->send(sm); }); - - SerialIter sit(m.validation().data(), m.validation().size()); - auto val = std::make_shared( - 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(m, protocol::mtVALIDATION); + for_each([sm](std::shared_ptr&& p) { p->send(sm); }); +} + void OverlayImpl::relay(protocol::TMValidation& m, uint256 const& uid) { diff --git a/src/ripple/overlay/impl/OverlayImpl.h b/src/ripple/overlay/impl/OverlayImpl.h index 02b87679e..b36004d51 100644 --- a/src/ripple/overlay/impl/OverlayImpl.h +++ b/src/ripple/overlay/impl/OverlayImpl.h @@ -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; diff --git a/src/ripple/overlay/impl/PeerImp.cpp b/src/ripple/overlay/impl/PeerImp.cpp index 4518d6155..c08c023cd 100644 --- a/src/ripple/overlay/impl/PeerImp.cpp +++ b/src/ripple/overlay/impl/PeerImp.cpp @@ -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 diff --git a/src/ripple/protocol/STValidation.h b/src/ripple/protocol/STValidation.h index 996b56887..be1537ad0 100644 --- a/src/ripple/protocol/STValidation.h +++ b/src/ripple/protocol/STValidation.h @@ -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* diff --git a/src/ripple/protocol/impl/STValidation.cpp b/src/ripple/protocol/impl/STValidation.cpp index 779a031a7..9f13c2be9 100644 --- a/src/ripple/protocol/impl/STValidation.cpp +++ b/src/ripple/protocol/impl/STValidation.cpp @@ -43,7 +43,7 @@ STValidation::validationFormat() {sfReserveIncrement, soeOPTIONAL}, {sfSigningTime, soeREQUIRED}, {sfSigningPubKey, soeREQUIRED}, - {sfSignature, soeOPTIONAL}, + {sfSignature, soeREQUIRED}, {sfConsensusHash, soeOPTIONAL}, {sfCookie, soeDEFAULT}, {sfValidatedHash, soeOPTIONAL}, diff --git a/src/test/app/AmendmentTable_test.cpp b/src/test/app/AmendmentTable_test.cpp index f0e9f401a..01e08244c 100644 --- a/src/test/app/AmendmentTable_test.cpp +++ b/src/test/app/AmendmentTable_test.cpp @@ -400,6 +400,7 @@ public: if (!field.empty()) v.setFieldV256( sfAmendments, STVector256(sfAmendments, field)); + v.setFieldU32(sfLedgerSequence, 6180339); }); validations.emplace_back(v); diff --git a/src/test/app/RCLValidations_test.cpp b/src/test/app/RCLValidations_test.cpp index 6b4f16d42..eca66a26a 100644 --- a/src/test/app/RCLValidations_test.cpp +++ b/src/test/app/RCLValidations_test.cpp @@ -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("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( - 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( - 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(); } }; diff --git a/src/test/protocol/STValidation_test.cpp b/src/test/protocol/STValidation_test.cpp index 716936cc3..3435db22c 100644 --- a/src/test/protocol/STValidation_test.cpp +++ b/src/test/protocol/STValidation_test.cpp @@ -18,7 +18,9 @@ //============================================================================== #include +#include #include +#include #include #include #include @@ -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( + 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( + auto val = std::make_shared( 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( + auto val = std::make_shared( 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( + auto val = std::make_shared( 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( + 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( + 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( + 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( + 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 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(g); + + try + { + SerialIter sit{makeSlice(v2)}; + + auto val = std::make_shared( + sit, + [](PublicKey const& pk) { return calcNodeID(pk); }, + true); + + fail( + "Mutated validation signature checked out: offset=" + + std::to_string(i)); + } + catch (std::exception const&) + { + pass(); + } } }