mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Harden validations:
This commit introduces the "HardenedValidations" amendment which, if enabled, allows validators to include additional information in their validations that can increase the robustness of consensus. Specifically, the commit introduces a new optional field that can be set in validation messages can be used to attest to the hash of the latest ledger that a validator considers to be fully validated. Additionally, the commit leverages the previously introduced "cookie" field to improve the robustness of the network by making it possible for servers to automatically detect accidental misconfiguration which results in two or more validators using the same validation key.
This commit is contained in:
@@ -111,7 +111,7 @@ class FeatureCollections
|
||||
"RequireFullyCanonicalSig",
|
||||
"fix1781", // XRPEndpointSteps should be included in the circular
|
||||
// payment check
|
||||
};
|
||||
"HardenedValidations"};
|
||||
|
||||
std::vector<uint256> features;
|
||||
boost::container::flat_map<uint256, std::size_t> featureToIndex;
|
||||
@@ -366,6 +366,7 @@ extern uint256 const featureDeletableAccounts;
|
||||
extern uint256 const fixQualityUpperBound;
|
||||
extern uint256 const featureRequireFullyCanonicalSig;
|
||||
extern uint256 const fix1781;
|
||||
extern uint256 const featureHardenedValidations;
|
||||
|
||||
// The following amendments have been active for at least two years.
|
||||
// Their pre-amendment code has been removed.
|
||||
|
||||
@@ -432,6 +432,7 @@ extern SF_U256 const sfDigest;
|
||||
extern SF_U256 const sfPayChannel;
|
||||
extern SF_U256 const sfConsensusHash;
|
||||
extern SF_U256 const sfCheckID;
|
||||
extern SF_U256 const sfValidatedHash;
|
||||
|
||||
// currency amount (common)
|
||||
extern SF_Amount const sfAmount;
|
||||
|
||||
@@ -50,7 +50,13 @@ public:
|
||||
: sField_(fieldName), style_(style)
|
||||
{
|
||||
if (!sField_.get().isUseful())
|
||||
Throw<std::runtime_error>("SField in SOElement must be useful.");
|
||||
{
|
||||
auto nm = std::to_string(fieldName.getCode());
|
||||
if (fieldName.hasName())
|
||||
nm += ": '" + fieldName.getName() + "'";
|
||||
Throw<std::runtime_error>(
|
||||
"SField (" + nm + ") in SOElement must be useful.");
|
||||
}
|
||||
}
|
||||
|
||||
SField const&
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#include <ripple/protocol/PublicKey.h>
|
||||
#include <ripple/protocol/STObject.h>
|
||||
#include <ripple/protocol/SecretKey.h>
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
#include <memory>
|
||||
@@ -32,8 +33,12 @@
|
||||
namespace ripple {
|
||||
|
||||
// Validation flags
|
||||
const std::uint32_t vfFullyCanonicalSig =
|
||||
0x80000000; // signature is fully canonical
|
||||
|
||||
// This is a full (as opposed to a partial) validation
|
||||
constexpr std::uint32_t vfFullValidation = 0x00000001;
|
||||
|
||||
// The signature is fully canonical
|
||||
constexpr std::uint32_t vfFullyCanonicalSig = 0x80000000;
|
||||
|
||||
class STValidation final : public STObject, public CountedObject<STValidation>
|
||||
{
|
||||
@@ -44,11 +49,6 @@ public:
|
||||
return "STValidation";
|
||||
}
|
||||
|
||||
using pointer = std::shared_ptr<STValidation>;
|
||||
using ref = const std::shared_ptr<STValidation>&;
|
||||
|
||||
enum { kFullFlag = 0x1 };
|
||||
|
||||
/** Construct a STValidation from a peer.
|
||||
|
||||
Construct a STValidation from serialized data previously shared by a
|
||||
@@ -70,71 +70,63 @@ public:
|
||||
SerialIter& sit,
|
||||
LookupNodeID&& lookupNodeID,
|
||||
bool checkSignature)
|
||||
: STObject(getFormat(), sit, sfValidation)
|
||||
: STObject(validationFormat(), sit, sfValidation)
|
||||
{
|
||||
auto const spk = getFieldVL(sfSigningPubKey);
|
||||
|
||||
if (publicKeyType(makeSlice(spk)) != KeyType::secp256k1)
|
||||
{
|
||||
JLOG(debugLog().error()) << "Invalid public key in validation"
|
||||
JLOG(debugLog().error()) << "Invalid public key in validation: "
|
||||
<< getJson(JsonOptions::none);
|
||||
Throw<std::runtime_error>("Invalid public key in validation");
|
||||
}
|
||||
|
||||
if (checkSignature && !isValid())
|
||||
{
|
||||
JLOG(debugLog().error()) << "Invalid signature in validation"
|
||||
JLOG(debugLog().error()) << "Invalid signature in validation: "
|
||||
<< getJson(JsonOptions::none);
|
||||
Throw<std::runtime_error>("Invalid signature in validation");
|
||||
}
|
||||
|
||||
mNodeID = lookupNodeID(PublicKey(makeSlice(spk)));
|
||||
assert(mNodeID.isNonZero());
|
||||
nodeID_ = lookupNodeID(PublicKey(makeSlice(spk)));
|
||||
assert(nodeID_.isNonZero());
|
||||
}
|
||||
|
||||
/** Fees to set when issuing a new validation.
|
||||
/** Construct, sign and trust a new STValidation issued by this node.
|
||||
|
||||
Optional fees to include when issuing a new validation
|
||||
*/
|
||||
struct FeeSettings
|
||||
{
|
||||
boost::optional<std::uint32_t> loadFee;
|
||||
boost::optional<XRPAmount> baseFee;
|
||||
boost::optional<XRPAmount> reserveBase;
|
||||
boost::optional<XRPAmount> reserveIncrement;
|
||||
};
|
||||
|
||||
/** Construct, sign and trust a new STValidation
|
||||
|
||||
Constructs, signs and trusts new STValidation issued by this node.
|
||||
|
||||
@param ledgerHash The hash of the validated ledger
|
||||
@param ledgerSeq The sequence number (index) of the ledger
|
||||
@param consensusHash The hash of the consensus transaction set
|
||||
@param signTime When the validation is signed
|
||||
@param publicKey The current signing public key
|
||||
@param secretKey The current signing secret key
|
||||
@param nodeID ID corresponding to node's public master key
|
||||
@param isFull Whether the validation is full or partial
|
||||
@param fee FeeSettings to include in the validation
|
||||
@param amendments If not empty, the amendments to include in this
|
||||
validation
|
||||
|
||||
@note The fee and amendment settings are only set if not boost::none.
|
||||
Typically, the amendments and fees are set for validations of flag
|
||||
ledgers only.
|
||||
@param f callback function to "fill" the validation with necessary data
|
||||
*/
|
||||
template <typename F>
|
||||
STValidation(
|
||||
uint256 const& ledgerHash,
|
||||
std::uint32_t ledgerSeq,
|
||||
uint256 const& consensusHash,
|
||||
NetClock::time_point signTime,
|
||||
PublicKey const& publicKey,
|
||||
SecretKey const& secretKey,
|
||||
PublicKey const& pk,
|
||||
SecretKey const& sk,
|
||||
NodeID const& nodeID,
|
||||
bool isFull,
|
||||
FeeSettings const& fees,
|
||||
std::vector<uint256> const& amendments);
|
||||
F&& f)
|
||||
: STObject(validationFormat(), sfValidation)
|
||||
, nodeID_(nodeID)
|
||||
, seenTime_(signTime)
|
||||
{
|
||||
// First, set our own public key:
|
||||
if (publicKeyType(pk) != KeyType::secp256k1)
|
||||
LogicError(
|
||||
"We can only use secp256k1 keys for signing validations");
|
||||
|
||||
setFieldVL(sfSigningPubKey, pk.slice());
|
||||
setFieldU32(sfSigningTime, signTime.time_since_epoch().count());
|
||||
|
||||
// Perform additional initialization
|
||||
f(*this);
|
||||
|
||||
// Finally, sign the validation and mark it as trusted:
|
||||
setFlag(vfFullyCanonicalSig);
|
||||
setFieldVL(sfSignature, signDigest(pk, sk, getSigningHash()));
|
||||
setTrusted();
|
||||
}
|
||||
|
||||
STBase*
|
||||
copy(std::size_t n, void* buf) const override
|
||||
@@ -168,7 +160,7 @@ public:
|
||||
NodeID
|
||||
getNodeID() const
|
||||
{
|
||||
return mNodeID;
|
||||
return nodeID_;
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -201,7 +193,7 @@ public:
|
||||
void
|
||||
setSeen(NetClock::time_point s)
|
||||
{
|
||||
mSeen = s;
|
||||
seenTime_ = s;
|
||||
}
|
||||
|
||||
Blob
|
||||
@@ -212,11 +204,11 @@ public:
|
||||
|
||||
private:
|
||||
static SOTemplate const&
|
||||
getFormat();
|
||||
validationFormat();
|
||||
|
||||
NodeID mNodeID;
|
||||
NodeID nodeID_;
|
||||
bool mTrusted = false;
|
||||
NetClock::time_point mSeen = {};
|
||||
NetClock::time_point seenTime_ = {};
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -130,7 +130,7 @@ detail::supportedAmendments()
|
||||
"fixQualityUpperBound",
|
||||
"RequireFullyCanonicalSig",
|
||||
"fix1781",
|
||||
};
|
||||
"HardenedValidations"};
|
||||
return supported;
|
||||
}
|
||||
|
||||
@@ -187,6 +187,8 @@ uint256 const fixQualityUpperBound =
|
||||
uint256 const featureRequireFullyCanonicalSig =
|
||||
*getRegisteredFeature("RequireFullyCanonicalSig");
|
||||
uint256 const fix1781 = *getRegisteredFeature("fix1781");
|
||||
uint256 const featureHardenedValidations =
|
||||
*getRegisteredFeature("HardenedValidations");
|
||||
|
||||
// The following amendments have been active for at least two years.
|
||||
// Their pre-amendment code has been removed.
|
||||
|
||||
@@ -168,6 +168,7 @@ SF_U256 const sfDigest(access, STI_HASH256, 21, "Digest");
|
||||
SF_U256 const sfPayChannel(access, STI_HASH256, 22, "Channel");
|
||||
SF_U256 const sfConsensusHash(access, STI_HASH256, 23, "ConsensusHash");
|
||||
SF_U256 const sfCheckID(access, STI_HASH256, 24, "CheckID");
|
||||
SF_U256 const sfValidatedHash(access, STI_HASH256, 25, "ValidatedHash");
|
||||
|
||||
// currency amount (common)
|
||||
SF_Amount const sfAmount(access, STI_AMOUNT, 1, "Amount");
|
||||
|
||||
@@ -25,68 +25,32 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
STValidation::STValidation(
|
||||
uint256 const& ledgerHash,
|
||||
std::uint32_t ledgerSeq,
|
||||
uint256 const& consensusHash,
|
||||
NetClock::time_point signTime,
|
||||
PublicKey const& publicKey,
|
||||
SecretKey const& secretKey,
|
||||
NodeID const& nodeID,
|
||||
bool isFull,
|
||||
FeeSettings const& fees,
|
||||
std::vector<uint256> const& amendments)
|
||||
: STObject(getFormat(), sfValidation), mNodeID(nodeID), mSeen(signTime)
|
||||
SOTemplate const&
|
||||
STValidation::validationFormat()
|
||||
{
|
||||
// This is our own public key and it should always be valid.
|
||||
if (!publicKeyType(publicKey))
|
||||
LogicError("Invalid validation public key");
|
||||
assert(mNodeID.isNonZero());
|
||||
setFieldH256(sfLedgerHash, ledgerHash);
|
||||
setFieldH256(sfConsensusHash, consensusHash);
|
||||
setFieldU32(sfSigningTime, signTime.time_since_epoch().count());
|
||||
// We can't have this be a magic static at namespace scope because
|
||||
// it relies on the SField's below being initialized, and we can't
|
||||
// guarantee the initialization order.
|
||||
static SOTemplate const format{
|
||||
{sfFlags, soeREQUIRED},
|
||||
{sfLedgerHash, soeREQUIRED},
|
||||
{sfLedgerSequence, soeOPTIONAL},
|
||||
{sfCloseTime, soeOPTIONAL},
|
||||
{sfLoadFee, soeOPTIONAL},
|
||||
{sfAmendments, soeOPTIONAL},
|
||||
{sfBaseFee, soeOPTIONAL},
|
||||
{sfReserveBase, soeOPTIONAL},
|
||||
{sfReserveIncrement, soeOPTIONAL},
|
||||
{sfSigningTime, soeREQUIRED},
|
||||
{sfSigningPubKey, soeREQUIRED},
|
||||
{sfSignature, soeOPTIONAL},
|
||||
{sfConsensusHash, soeOPTIONAL},
|
||||
{sfCookie, soeDEFAULT},
|
||||
{sfValidatedHash, soeOPTIONAL},
|
||||
};
|
||||
|
||||
setFieldVL(sfSigningPubKey, publicKey.slice());
|
||||
if (isFull)
|
||||
setFlag(kFullFlag);
|
||||
|
||||
setFieldU32(sfLedgerSequence, ledgerSeq);
|
||||
|
||||
if (fees.loadFee)
|
||||
setFieldU32(sfLoadFee, *fees.loadFee);
|
||||
|
||||
// IF any of the values are out of the valid range, don't send a value.
|
||||
// They should not be an issue, though, because the voting
|
||||
// process (FeeVoteImpl) ignores any out of range values.
|
||||
if (fees.baseFee)
|
||||
{
|
||||
if (auto const v = fees.baseFee->dropsAs<std::uint64_t>())
|
||||
setFieldU64(sfBaseFee, *v);
|
||||
}
|
||||
|
||||
if (fees.reserveBase)
|
||||
{
|
||||
if (auto const v = fees.reserveBase->dropsAs<std::uint32_t>())
|
||||
setFieldU32(sfReserveBase, *v);
|
||||
}
|
||||
|
||||
if (fees.reserveIncrement)
|
||||
{
|
||||
if (auto const v = fees.reserveIncrement->dropsAs<std::uint32_t>())
|
||||
setFieldU32(sfReserveIncrement, *v);
|
||||
}
|
||||
|
||||
if (!amendments.empty())
|
||||
setFieldV256(sfAmendments, STVector256(sfAmendments, amendments));
|
||||
|
||||
setFlag(vfFullyCanonicalSig);
|
||||
|
||||
auto const signingHash = getSigningHash();
|
||||
setFieldVL(
|
||||
sfSignature, signDigest(getSignerPublic(), secretKey, signingHash));
|
||||
|
||||
setTrusted();
|
||||
}
|
||||
return format;
|
||||
};
|
||||
|
||||
uint256
|
||||
STValidation::getSigningHash() const
|
||||
@@ -115,7 +79,7 @@ STValidation::getSignTime() const
|
||||
NetClock::time_point
|
||||
STValidation::getSeenTime() const
|
||||
{
|
||||
return mSeen;
|
||||
return seenTime_;
|
||||
}
|
||||
|
||||
bool
|
||||
@@ -148,7 +112,7 @@ STValidation::getSignerPublic() const
|
||||
bool
|
||||
STValidation::isFull() const
|
||||
{
|
||||
return (getFlags() & kFullFlag) != 0;
|
||||
return (getFlags() & vfFullValidation) != 0;
|
||||
}
|
||||
|
||||
Blob
|
||||
@@ -165,32 +129,4 @@ STValidation::getSerialized() const
|
||||
return s.peekData();
|
||||
}
|
||||
|
||||
SOTemplate const&
|
||||
STValidation::getFormat()
|
||||
{
|
||||
struct FormatHolder
|
||||
{
|
||||
SOTemplate format{
|
||||
{sfFlags, soeREQUIRED},
|
||||
{sfLedgerHash, soeREQUIRED},
|
||||
{sfLedgerSequence, soeOPTIONAL},
|
||||
{sfCloseTime, soeOPTIONAL},
|
||||
{sfLoadFee, soeOPTIONAL},
|
||||
{sfAmendments, soeOPTIONAL},
|
||||
{sfBaseFee, soeOPTIONAL},
|
||||
{sfReserveBase, soeOPTIONAL},
|
||||
{sfReserveIncrement, soeOPTIONAL},
|
||||
{sfSigningTime, soeREQUIRED},
|
||||
{sfSigningPubKey, soeREQUIRED},
|
||||
{sfSignature, soeOPTIONAL},
|
||||
{sfConsensusHash, soeOPTIONAL},
|
||||
{sfCookie, soeOPTIONAL},
|
||||
};
|
||||
};
|
||||
|
||||
static const FormatHolder holder;
|
||||
|
||||
return holder.format;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
Reference in New Issue
Block a user