From 3752234161394043e0986b7dea68621d6e0daf49 Mon Sep 17 00:00:00 2001 From: Nik Bougalis Date: Mon, 29 Mar 2021 00:24:53 -0700 Subject: [PATCH] Report additional fields in validation stream: The HardenedValidations amendment introduces additional fields in validations: - `sfValidatedHash`, if present, is the hash the of last ledger that the validator considers to be fully validated. - `sfCookie`, if present, is a 64-bit cookie (the default implementation selects it randomly at startup but other implementations are possible), which can be used to improve the detection and classification of duplicate validations. - `sfServerVersion`, if present, reports the version of the software that the validator is running. By surfacing this information, server operators gain additional insight about variety of software on the network. If merged, this commit fixes #3797 by adding the fields to the `validations` stream as shown below: - `sfValidateHash` as `validated_hash`: a 256-bit hex string; - `sfCookie` as `cookie`: a 64-bit integer as a string; and - `sfServerVersion` as `server_version`: a 64-bit integer as a string. --- src/ripple/app/misc/NetworkOPs.cpp | 9 ++++ src/ripple/protocol/jss.h | 3 ++ src/test/rpc/Subscribe_test.cpp | 76 +++++++++++++++++++++++------- 3 files changed, 72 insertions(+), 16 deletions(-) diff --git a/src/ripple/app/misc/NetworkOPs.cpp b/src/ripple/app/misc/NetworkOPs.cpp index 73719c4a96..cae1b6163f 100644 --- a/src/ripple/app/misc/NetworkOPs.cpp +++ b/src/ripple/app/misc/NetworkOPs.cpp @@ -2009,6 +2009,15 @@ NetworkOPsImp::pubValidation(std::shared_ptr const& val) jvObj[jss::signing_time] = *(*val)[~sfSigningTime]; jvObj[jss::data] = strHex(val->getSerializer().slice()); + if (auto version = (*val)[~sfServerVersion]) + jvObj[jss::server_version] = std::to_string(*version); + + if (auto cookie = (*val)[~sfCookie]) + jvObj[jss::cookie] = std::to_string(*cookie); + + if (auto hash = (*val)[~sfValidatedHash]) + jvObj[jss::validated_hash] = strHex(*hash); + auto const masterKey = app_.validatorManifests().getMasterKey(signerPublic); diff --git a/src/ripple/protocol/jss.h b/src/ripple/protocol/jss.h index b9b782a6a2..1b0c4793aa 100644 --- a/src/ripple/protocol/jss.h +++ b/src/ripple/protocol/jss.h @@ -178,6 +178,7 @@ JSS(complete_shards); // out: OverlayImpl, PeerImp JSS(consensus); // out: NetworkOPs, LedgerConsensus JSS(converge_time); // out: NetworkOPs JSS(converge_time_s); // out: NetworkOPs +JSS(cookie); // out: NetworkOPs JSS(count); // in: AccountTx*, ValidatorList JSS(counters); // in/out: retrieve counters JSS(currentShard); // out: NodeToShardStatus @@ -498,6 +499,7 @@ JSS(server_domain); // out: NetworkOPs JSS(server_state); // out: NetworkOPs JSS(server_state_duration_us); // out: NetworkOPs JSS(server_status); // out: NetworkOPs +JSS(server_version); // out: NetworkOPs JSS(settle_delay); // out: AccountChannels JSS(severity); // in: LogLevel JSS(shards); // in/out: GetCounts, DownloadShard @@ -587,6 +589,7 @@ JSS(validated); // out: NetworkOPs, RPCHelpers, AccountTx* JSS(validator_list_expires); // out: NetworkOps, ValidatorList JSS(validator_list); // out: NetworkOps, ValidatorList JSS(validators); +JSS(validated_hash); // out: NetworkOPs JSS(validated_ledger); // out: NetworkOPs JSS(validated_ledger_index); // out: SubmitTransaction JSS(validated_ledgers); // out: NetworkOPs diff --git a/src/test/rpc/Subscribe_test.cpp b/src/test/rpc/Subscribe_test.cpp index 3e1916caf4..65a85ef25e 100644 --- a/src/test/rpc/Subscribe_test.cpp +++ b/src/test/rpc/Subscribe_test.cpp @@ -365,24 +365,68 @@ public: } { - // Accept a ledger - env.close(); + // Lambda to check ledger validations from the stream. + auto validValidationFields = [&env, &valPublicKey]( + Json::Value const& jv) { + if (jv[jss::type] != "validationReceived") + return false; - // Check stream update - using namespace std::chrono_literals; + if (jv[jss::validation_public_key].asString() != valPublicKey) + return false; - BEAST_EXPECT(wsc->findMsg(5s, [&](auto const& jv) { - return jv[jss::type] == "validationReceived" && - jv[jss::validation_public_key].asString() == valPublicKey && - jv[jss::ledger_hash] == - to_string(env.closed()->info().hash) && - jv[jss::ledger_index] == - std::to_string(env.closed()->info().seq) && - jv[jss::flags] == - (vfFullyCanonicalSig | vfFullValidation) && - jv[jss::full] == true && !jv.isMember(jss::load_fee) && - jv[jss::signature] && jv[jss::signing_time]; - })); + if (jv[jss::ledger_hash] != + to_string(env.closed()->info().hash)) + return false; + + if (jv[jss::ledger_index] != + std::to_string(env.closed()->info().seq)) + return false; + + if (jv[jss::flags] != (vfFullyCanonicalSig | vfFullValidation)) + return false; + + if (jv[jss::full] != true) + return false; + + if (jv.isMember(jss::load_fee)) + return false; + + if (!jv.isMember(jss::signature)) + return false; + + if (!jv.isMember(jss::signing_time)) + return false; + + if (!jv.isMember(jss::cookie)) + return false; + + if (!jv.isMember(jss::validated_hash)) + return false; + + // Certain fields are only added on a flag ledger. + bool const isFlagLedger = + (env.closed()->info().seq + 1) % 256 == 0; + + if (jv.isMember(jss::server_version) != isFlagLedger) + return false; + + if (jv.isMember(jss::reserve_base) != isFlagLedger) + return false; + + if (jv.isMember(jss::reserve_inc) != isFlagLedger) + return false; + + return true; + }; + + // Check stream update. Look at enough stream entries so we see + // at least one flag ledger. + while (env.closed()->info().seq < 300) + { + env.close(); + using namespace std::chrono_literals; + BEAST_EXPECT(wsc->findMsg(5s, validValidationFields)); + } } // RPC unsubscribe