Merge pull request #2945 from codablock/pr_serialize_llmqtype

Implement support for explicit enum serialization and use it for Consensus::LLMQType
This commit is contained in:
Alexander Block 2019-05-29 10:54:29 +02:00 committed by GitHub
commit 7c3fd254f4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 91 additions and 54 deletions

View File

@ -183,4 +183,9 @@ struct Params {
};
} // namespace Consensus
// This must be outside of all namespaces. We must also duplicate the forward declaration of is_serializable_enum to
// avoid inclusion of serialize.h here.
template<typename T> struct is_serializable_enum;
template<> struct is_serializable_enum<Consensus::LLMQType> : std::true_type {};
#endif // BITCOIN_CONSENSUS_PARAMS_H

View File

@ -32,7 +32,7 @@ CQuorumManager* quorumManager;
static uint256 MakeQuorumKey(const CQuorum& q)
{
CHashWriter hw(SER_NETWORK, 0);
hw << (uint8_t)q.params.type;
hw << q.params.type;
hw << q.qc.quorumHash;
for (const auto& dmn : q.members) {
hw << dmn->proTxHash;

View File

@ -168,10 +168,10 @@ bool CQuorumBlockProcessor::ProcessBlock(const CBlock& block, const CBlockIndex*
// We store a mapping from minedHeight->quorumHeight in the DB
// minedHeight is inversed so that entries are traversable in reversed order
static std::tuple<std::string, uint8_t, uint32_t> BuildInversedHeightKey(Consensus::LLMQType llmqType, int nMinedHeight)
static std::tuple<std::string, Consensus::LLMQType, uint32_t> BuildInversedHeightKey(Consensus::LLMQType llmqType, int nMinedHeight)
{
// nMinedHeight must be converted to big endian to make it comparable when serialized
return std::make_tuple(DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT, (uint8_t)llmqType, htobe32(std::numeric_limits<uint32_t>::max() - nMinedHeight));
return std::make_tuple(DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT, llmqType, htobe32(std::numeric_limits<uint32_t>::max() - nMinedHeight));
}
bool CQuorumBlockProcessor::ProcessCommitment(int nHeight, const uint256& blockHash, const CFinalCommitment& qc, CValidationState& state)
@ -211,7 +211,7 @@ bool CQuorumBlockProcessor::ProcessCommitment(int nHeight, const uint256& blockH
// Store commitment in DB
auto quorumIndex = mapBlockIndex.at(qc.quorumHash);
evoDb.Write(std::make_pair(DB_MINED_COMMITMENT, std::make_pair((uint8_t)params.type, quorumHash)), std::make_pair(qc, blockHash));
evoDb.Write(std::make_pair(DB_MINED_COMMITMENT, std::make_pair(params.type, quorumHash)), std::make_pair(qc, blockHash));
evoDb.Write(BuildInversedHeightKey(params.type, nHeight), quorumIndex->nHeight);
{
@ -285,7 +285,7 @@ void CQuorumBlockProcessor::UpgradeDB()
continue;
}
auto quorumIndex = mapBlockIndex.at(qc.quorumHash);
evoDb.GetRawDB().Write(std::make_pair(DB_MINED_COMMITMENT, std::make_pair((uint8_t)qc.llmqType, qc.quorumHash)), std::make_pair(qc, pindex->GetBlockHash()));
evoDb.GetRawDB().Write(std::make_pair(DB_MINED_COMMITMENT, std::make_pair(qc.llmqType, qc.quorumHash)), std::make_pair(qc, pindex->GetBlockHash()));
evoDb.GetRawDB().Write(BuildInversedHeightKey((Consensus::LLMQType)qc.llmqType, pindex->nHeight), quorumIndex->nHeight);
}
@ -381,7 +381,7 @@ bool CQuorumBlockProcessor::HasMinedCommitment(Consensus::LLMQType llmqType, con
}
}
auto key = std::make_pair(DB_MINED_COMMITMENT, std::make_pair((uint8_t)llmqType, quorumHash));
auto key = std::make_pair(DB_MINED_COMMITMENT, std::make_pair(llmqType, quorumHash));
bool ret = evoDb.Exists(key);
LOCK(minableCommitmentsCs);
@ -391,7 +391,7 @@ bool CQuorumBlockProcessor::HasMinedCommitment(Consensus::LLMQType llmqType, con
bool CQuorumBlockProcessor::GetMinedCommitment(Consensus::LLMQType llmqType, const uint256& quorumHash, CFinalCommitment& retQc, uint256& retMinedBlockHash)
{
auto key = std::make_pair(DB_MINED_COMMITMENT, std::make_pair((uint8_t)llmqType, quorumHash));
auto key = std::make_pair(DB_MINED_COMMITMENT, std::make_pair(llmqType, quorumHash));
std::pair<CFinalCommitment, uint256> p;
if (!evoDb.Read(key, p)) {
return false;
@ -419,7 +419,7 @@ std::vector<const CBlockIndex*> CQuorumBlockProcessor::GetMinedCommitmentsUntilB
if (!dbIt->GetKey(curKey) || curKey >= lastKey) {
break;
}
if (std::get<0>(curKey) != DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT || std::get<1>(curKey) != (uint8_t)llmqType) {
if (std::get<0>(curKey) != DB_MINED_COMMITMENT_BY_INVERSED_HEIGHT || std::get<1>(curKey) != llmqType) {
break;
}

View File

@ -81,7 +81,7 @@ bool CFinalCommitment::Verify(const std::vector<CDeterministicMNCPtr>& members,
// sigs are only checked when the block is processed
if (checkSigs) {
uint256 commitmentHash = CLLMQUtils::BuildCommitmentHash((uint8_t)params.type, quorumHash, validMembers, quorumPublicKey, quorumVvecHash);
uint256 commitmentHash = CLLMQUtils::BuildCommitmentHash(params.type, quorumHash, validMembers, quorumPublicKey, quorumVvecHash);
std::vector<CBLSPublicKey> memberPubKeys;
for (size_t i = 0; i < members.size(); i++) {

View File

@ -24,7 +24,7 @@ public:
public:
uint16_t nVersion{CURRENT_VERSION};
uint8_t llmqType{Consensus::LLMQ_NONE};
Consensus::LLMQType llmqType{Consensus::LLMQ_NONE};
uint256 quorumHash;
std::vector<bool> signers;
std::vector<bool> validMembers;

View File

@ -151,7 +151,7 @@ void CDKGDebugManager::InitLocalSessionStatus(Consensus::LLMQType llmqType, cons
auto it = localStatus.sessions.find(llmqType);
if (it == localStatus.sessions.end()) {
it = localStatus.sessions.emplace((uint8_t)llmqType, CDKGDebugSessionStatus()).first;
it = localStatus.sessions.emplace(llmqType, CDKGDebugSessionStatus()).first;
}
auto& params = Params().GetConsensus().llmqs.at(llmqType);

View File

@ -47,7 +47,7 @@ public:
class CDKGDebugSessionStatus
{
public:
uint8_t llmqType{Consensus::LLMQ_NONE};
Consensus::LLMQType llmqType{Consensus::LLMQ_NONE};
uint256 quorumHash;
uint32_t quorumHeight{0};
uint8_t phase{0};
@ -79,7 +79,7 @@ class CDKGDebugStatus
public:
int64_t nTime{0};
std::map<uint8_t, CDKGDebugSessionStatus> sessions;
std::map<Consensus::LLMQType, CDKGDebugSessionStatus> sessions;
public:
UniValue ToJson(int detailLevel) const;

View File

@ -169,7 +169,7 @@ void CDKGSession::SendContributions(CDKGPendingMessages& pendingMessages)
}
CDKGContribution qc;
qc.llmqType = (uint8_t)params.type;
qc.llmqType = params.type;
qc.quorumHash = quorumHash;
qc.proTxHash = myProTxHash;
qc.vvec = vvecContribution;
@ -448,7 +448,7 @@ void CDKGSession::SendComplaint(CDKGPendingMessages& pendingMessages)
assert(AreWeMember());
CDKGComplaint qc(params);
qc.llmqType = (uint8_t)params.type;
qc.llmqType = params.type;
qc.quorumHash = quorumHash;
qc.proTxHash = myProTxHash;
@ -642,7 +642,7 @@ void CDKGSession::SendJustification(CDKGPendingMessages& pendingMessages, const
logger.Batch("sending justification for %d members", forMembers.size());
CDKGJustification qj;
qj.llmqType = (uint8_t)params.type;
qj.llmqType = params.type;
qj.quorumHash = quorumHash;
qj.proTxHash = myProTxHash;
qj.contributions.reserve(forMembers.size());
@ -898,7 +898,7 @@ void CDKGSession::SendCommitment(CDKGPendingMessages& pendingMessages)
logger.Batch("sending commitment");
CDKGPrematureCommitment qc(params);
qc.llmqType = (uint8_t)params.type;
qc.llmqType = params.type;
qc.quorumHash = quorumHash;
qc.proTxHash = myProTxHash;

View File

@ -36,7 +36,7 @@ public:
class CDKGContribution
{
public:
uint8_t llmqType;
Consensus::LLMQType llmqType;
uint256 quorumHash;
uint256 proTxHash;
BLSVerificationVectorPtr vvec;
@ -88,7 +88,7 @@ public:
class CDKGComplaint
{
public:
uint8_t llmqType;
Consensus::LLMQType llmqType;
uint256 quorumHash;
uint256 proTxHash;
std::vector<bool> badMembers;
@ -123,7 +123,7 @@ public:
class CDKGJustification
{
public:
uint8_t llmqType;
Consensus::LLMQType llmqType;
uint256 quorumHash;
uint256 proTxHash;
std::vector<std::pair<uint32_t, CBLSSecretKey>> contributions;
@ -157,7 +157,7 @@ public:
class CDKGPrematureCommitment
{
public:
uint8_t llmqType;
Consensus::LLMQType llmqType;
uint256 quorumHash;
uint256 proTxHash;
std::vector<bool> validMembers;

View File

@ -200,12 +200,12 @@ bool CDKGSessionManager::GetPrematureCommitment(const uint256& hash, CDKGPrematu
void CDKGSessionManager::WriteVerifiedVvecContribution(Consensus::LLMQType llmqType, const uint256& quorumHash, const uint256& proTxHash, const BLSVerificationVectorPtr& vvec)
{
llmqDb.Write(std::make_tuple(DB_VVEC, (uint8_t)llmqType, quorumHash, proTxHash), *vvec);
llmqDb.Write(std::make_tuple(DB_VVEC, llmqType, quorumHash, proTxHash), *vvec);
}
void CDKGSessionManager::WriteVerifiedSkContribution(Consensus::LLMQType llmqType, const uint256& quorumHash, const uint256& proTxHash, const CBLSSecretKey& skContribution)
{
llmqDb.Write(std::make_tuple(DB_SKCONTRIB, (uint8_t)llmqType, quorumHash, proTxHash), skContribution);
llmqDb.Write(std::make_tuple(DB_SKCONTRIB, llmqType, quorumHash, proTxHash), skContribution);
}
bool CDKGSessionManager::GetVerifiedContributions(Consensus::LLMQType llmqType, const uint256& quorumHash, const std::vector<bool>& validMembers, std::vector<uint16_t>& memberIndexesRet, std::vector<BLSVerificationVectorPtr>& vvecsRet, BLSSecretKeyVector& skContributionsRet)
@ -248,10 +248,10 @@ bool CDKGSessionManager::GetVerifiedContribution(Consensus::LLMQType llmqType, c
BLSVerificationVector vvec;
BLSVerificationVectorPtr vvecPtr;
CBLSSecretKey skContribution;
if (llmqDb.Read(std::make_tuple(DB_VVEC, (uint8_t)llmqType, quorumHash, proTxHash), vvec)) {
if (llmqDb.Read(std::make_tuple(DB_VVEC, llmqType, quorumHash, proTxHash), vvec)) {
vvecPtr = std::make_shared<BLSVerificationVector>(std::move(vvec));
}
llmqDb.Read(std::make_tuple(DB_SKCONTRIB, (uint8_t)llmqType, quorumHash, proTxHash), skContribution);
llmqDb.Read(std::make_tuple(DB_SKCONTRIB, llmqType, quorumHash, proTxHash), skContribution);
it = contributionsCache.emplace(cacheKey, ContributionsCacheEntry{GetTimeMillis(), vvecPtr, skContribution}).first;

View File

@ -59,7 +59,7 @@ void CRecoveredSigsDb::ConvertInvalidTimeKeys()
std::unique_ptr<CDBIterator> pcursor(db.NewIterator());
auto start = std::make_tuple(std::string("rs_t"), (uint32_t)0, (uint8_t)0, uint256());
auto start = std::make_tuple(std::string("rs_t"), (uint32_t)0, (Consensus::LLMQType)0, uint256());
pcursor->Seek(start);
CDBBatch batch(db);
@ -96,7 +96,7 @@ void CRecoveredSigsDb::AddVoteTimeKeys()
std::unique_ptr<CDBIterator> pcursor(db.NewIterator());
auto start = std::make_tuple(std::string("rs_v"), (uint8_t)0, uint256());
auto start = std::make_tuple(std::string("rs_v"), (Consensus::LLMQType)0, uint256());
pcursor->Seek(start);
CDBBatch batch(db);
@ -108,7 +108,7 @@ void CRecoveredSigsDb::AddVoteTimeKeys()
break;
}
uint8_t llmqType = std::get<1>(k);
Consensus::LLMQType llmqType = std::get<1>(k);
const uint256& id = std::get<2>(k);
auto k2 = std::make_tuple(std::string("rs_vt"), (uint32_t)htobe32(curTime), llmqType, id);
@ -127,7 +127,7 @@ void CRecoveredSigsDb::AddVoteTimeKeys()
bool CRecoveredSigsDb::HasRecoveredSig(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash)
{
auto k = std::make_tuple(std::string("rs_r"), (uint8_t)llmqType, id, msgHash);
auto k = std::make_tuple(std::string("rs_r"), llmqType, id, msgHash);
return db.Exists(k);
}
@ -143,7 +143,7 @@ bool CRecoveredSigsDb::HasRecoveredSigForId(Consensus::LLMQType llmqType, const
}
auto k = std::make_tuple(std::string("rs_r"), (uint8_t)llmqType, id);
auto k = std::make_tuple(std::string("rs_r"), llmqType, id);
ret = db.Exists(k);
LOCK(cs);
@ -189,7 +189,7 @@ bool CRecoveredSigsDb::HasRecoveredSigForHash(const uint256& hash)
bool CRecoveredSigsDb::ReadRecoveredSig(Consensus::LLMQType llmqType, const uint256& id, CRecoveredSig& ret)
{
auto k = std::make_tuple(std::string("rs_r"), (uint8_t)llmqType, id);
auto k = std::make_tuple(std::string("rs_r"), llmqType, id);
CDataStream ds(SER_DISK, CLIENT_VERSION);
if (!db.ReadDataStream(k, ds)) {
@ -207,12 +207,12 @@ bool CRecoveredSigsDb::ReadRecoveredSig(Consensus::LLMQType llmqType, const uint
bool CRecoveredSigsDb::GetRecoveredSigByHash(const uint256& hash, CRecoveredSig& ret)
{
auto k1 = std::make_tuple(std::string("rs_h"), hash);
std::pair<uint8_t, uint256> k2;
std::pair<Consensus::LLMQType, uint256> k2;
if (!db.Read(k1, k2)) {
return false;
}
return ReadRecoveredSig((Consensus::LLMQType)k2.first, k2.second, ret);
return ReadRecoveredSig(k2.first, k2.second, ret);
}
bool CRecoveredSigsDb::GetRecoveredSigById(Consensus::LLMQType llmqType, const uint256& id, CRecoveredSig& ret)
@ -260,7 +260,7 @@ void CRecoveredSigsDb::CleanupOldRecoveredSigs(int64_t maxAge)
{
std::unique_ptr<CDBIterator> pcursor(db.NewIterator());
auto start = std::make_tuple(std::string("rs_t"), (uint32_t)0, (uint8_t)0, uint256());
auto start = std::make_tuple(std::string("rs_t"), (uint32_t)0, (Consensus::LLMQType)0, uint256());
uint32_t endTime = (uint32_t)(GetAdjustedTime() - maxAge);
pcursor->Seek(start);
@ -277,7 +277,7 @@ void CRecoveredSigsDb::CleanupOldRecoveredSigs(int64_t maxAge)
break;
}
toDelete.emplace_back((Consensus::LLMQType)std::get<2>(k), std::get<3>(k));
toDelete.emplace_back(std::get<2>(k), std::get<3>(k));
toDelete2.emplace_back(k);
pcursor->Next();
@ -330,20 +330,20 @@ void CRecoveredSigsDb::CleanupOldRecoveredSigs(int64_t maxAge)
bool CRecoveredSigsDb::HasVotedOnId(Consensus::LLMQType llmqType, const uint256& id)
{
auto k = std::make_tuple(std::string("rs_v"), (uint8_t)llmqType, id);
auto k = std::make_tuple(std::string("rs_v"), llmqType, id);
return db.Exists(k);
}
bool CRecoveredSigsDb::GetVoteForId(Consensus::LLMQType llmqType, const uint256& id, uint256& msgHashRet)
{
auto k = std::make_tuple(std::string("rs_v"), (uint8_t)llmqType, id);
auto k = std::make_tuple(std::string("rs_v"), llmqType, id);
return db.Read(k, msgHashRet);
}
void CRecoveredSigsDb::WriteVoteForId(Consensus::LLMQType llmqType, const uint256& id, const uint256& msgHash)
{
auto k1 = std::make_tuple(std::string("rs_v"), (uint8_t)llmqType, id);
auto k2 = std::make_tuple(std::string("rs_vt"), (uint32_t)htobe32(GetAdjustedTime()), (uint8_t)llmqType, id);
auto k1 = std::make_tuple(std::string("rs_v"), llmqType, id);
auto k2 = std::make_tuple(std::string("rs_vt"), (uint32_t)htobe32(GetAdjustedTime()), llmqType, id);
CDBBatch batch(db);
batch.Write(k1, msgHash);
@ -356,7 +356,7 @@ void CRecoveredSigsDb::CleanupOldVotes(int64_t maxAge)
{
std::unique_ptr<CDBIterator> pcursor(db.NewIterator());
auto start = std::make_tuple(std::string("rs_vt"), (uint32_t)0, (uint8_t)0, uint256());
auto start = std::make_tuple(std::string("rs_vt"), (uint32_t)0, (Consensus::LLMQType)0, uint256());
uint32_t endTime = (uint32_t)(GetAdjustedTime() - maxAge);
pcursor->Seek(start);
@ -372,7 +372,7 @@ void CRecoveredSigsDb::CleanupOldVotes(int64_t maxAge)
break;
}
uint8_t llmqType = std::get<2>(k);
Consensus::LLMQType llmqType = std::get<2>(k);
const uint256& id = std::get<3>(k);
batch.Erase(k);
@ -839,7 +839,7 @@ CQuorumCPtr CSigningManager::SelectQuorumForSigning(Consensus::LLMQType llmqType
scores.reserve(quorums.size());
for (size_t i = 0; i < quorums.size(); i++) {
CHashWriter h(SER_NETWORK, 0);
h << (uint8_t)llmqType;
h << llmqType;
h << quorums[i]->qc.quorumHash;
h << selectionHash;
scores.emplace_back(h.GetHash(), i);

View File

@ -21,7 +21,7 @@ namespace llmq
class CRecoveredSig
{
public:
uint8_t llmqType;
Consensus::LLMQType llmqType;
uint256 quorumHash;
uint256 id;
uint256 msgHash;

View File

@ -998,7 +998,7 @@ bool CSigSharesManager::SendMessages()
CSigSesAnn sigSesAnn;
sigSesAnn.sessionId = session->sendSessionId;
sigSesAnn.llmqType = (uint8_t)session->llmqType;
sigSesAnn.llmqType = session->llmqType;
sigSesAnn.quorumHash = session->quorumHash;
sigSesAnn.id = session->id;
sigSesAnn.msgHash = session->msgHash;

View File

@ -34,7 +34,7 @@ typedef std::pair<uint256, uint16_t> SigShareKey;
class CSigShare
{
public:
uint8_t llmqType;
Consensus::LLMQType llmqType;
uint256 quorumHash;
uint16_t quorumMember;
uint256 id;
@ -63,7 +63,7 @@ class CSigSesAnn
{
public:
uint32_t sessionId{(uint32_t)-1};
uint8_t llmqType;
Consensus::LLMQType llmqType;
uint256 quorumHash;
uint256 id;
uint256 msgHash;

View File

@ -16,11 +16,11 @@ std::vector<CDeterministicMNCPtr> CLLMQUtils::GetAllQuorumMembers(Consensus::LLM
{
auto& params = Params().GetConsensus().llmqs.at(llmqType);
auto allMns = deterministicMNManager->GetListForBlock(blockHash);
auto modifier = ::SerializeHash(std::make_pair((uint8_t)llmqType, blockHash));
auto modifier = ::SerializeHash(std::make_pair(llmqType, blockHash));
return allMns.CalculateQuorum(params.size, modifier);
}
uint256 CLLMQUtils::BuildCommitmentHash(uint8_t llmqType, const uint256& blockHash, const std::vector<bool>& validMembers, const CBLSPublicKey& pubKey, const uint256& vvecHash)
uint256 CLLMQUtils::BuildCommitmentHash(Consensus::LLMQType llmqType, const uint256& blockHash, const std::vector<bool>& validMembers, const CBLSPublicKey& pubKey, const uint256& vvecHash)
{
CHashWriter hw(SER_NETWORK, 0);
hw << llmqType;
@ -34,7 +34,7 @@ uint256 CLLMQUtils::BuildCommitmentHash(uint8_t llmqType, const uint256& blockHa
uint256 CLLMQUtils::BuildSignHash(Consensus::LLMQType llmqType, const uint256& quorumHash, const uint256& id, const uint256& msgHash)
{
CHashWriter h(SER_GETHASH, 0);
h << (uint8_t)llmqType;
h << llmqType;
h << quorumHash;
h << id;
h << msgHash;
@ -89,7 +89,7 @@ std::set<size_t> CLLMQUtils::CalcDeterministicWatchConnections(Consensus::LLMQTy
std::set<size_t> result;
uint256 rnd = qwatchConnectionSeed;
for (size_t i = 0; i < connectionCount; i++) {
rnd = ::SerializeHash(std::make_pair(rnd, std::make_pair((uint8_t)llmqType, blockHash)));
rnd = ::SerializeHash(std::make_pair(rnd, std::make_pair(llmqType, blockHash)));
result.emplace(rnd.GetUint64(0) % memberCount);
}
return result;

View File

@ -21,7 +21,7 @@ public:
// includes members which failed DKG
static std::vector<CDeterministicMNCPtr> GetAllQuorumMembers(Consensus::LLMQType llmqType, const uint256& blockHash);
static uint256 BuildCommitmentHash(uint8_t llmqType, const uint256& blockHash, const std::vector<bool>& validMembers, const CBLSPublicKey& pubKey, const uint256& vvecHash);
static uint256 BuildCommitmentHash(Consensus::LLMQType llmqType, const uint256& blockHash, const std::vector<bool>& validMembers, const CBLSPublicKey& pubKey, const uint256& vvecHash);
static uint256 BuildSignHash(Consensus::LLMQType llmqType, const uint256& quorumHash, const uint256& id, const uint256& msgHash);
// works for sig shares and recovered sigs

View File

@ -720,20 +720,52 @@ template<typename Stream, typename T> void Unserialize(Stream& os, std::unique_p
/**
* If none of the specialized versions above matched, default to calling member function.
* If none of the specialized versions above matched and T is a class, default to calling member function.
*/
template<typename Stream, typename T>
template<typename Stream, typename T, typename std::enable_if<std::is_class<T>::value>::type* = nullptr>
inline void Serialize(Stream& os, const T& a)
{
a.Serialize(os);
}
template<typename Stream, typename T>
template<typename Stream, typename T, typename std::enable_if<std::is_class<T>::value>::type* = nullptr>
inline void Unserialize(Stream& is, T& a)
{
a.Unserialize(is);
}
/**
* If none of the specialized versions above matched and T is an enum, default to calling
* Serialize/Unserialze with the underlying type. This is only allowed when a specialized struct of is_serializable_enum<Enum>
* is found which derives from std::true_type. This is to ensure that enums are not serialized with the wrong type by
* accident.
*/
template<typename T> struct is_serializable_enum;
template<typename T> struct is_serializable_enum : std::false_type {};
template<typename Stream, typename T, typename std::enable_if<std::is_enum<T>::value>::type* = nullptr>
inline void Serialize(Stream& s, T a )
{
// If you ever get into this situation, it usaully means you forgot to declare is_serializable_enum for the desired enum type
static_assert(is_serializable_enum<T>::value);
typedef typename std::underlying_type<T>::type T2;
T2 b = (T2)a;
Serialize(s, b);
}
template<typename Stream, typename T, typename std::enable_if<std::is_enum<T>::value>::type* = nullptr>
inline void Unserialize(Stream& s, T& a )
{
// If you ever get into this situation, it usaully means you forgot to declare is_serializable_enum for the desired enum type
static_assert(is_serializable_enum<T>::value);
typedef typename std::underlying_type<T>::type T2;
T2 b;
Unserialize(s, b);
a = (T)b;
}