diff --git a/src/Makefile.am b/src/Makefile.am index 67d6a103cf..687beb68ab 100755 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -91,6 +91,7 @@ GRIDCOIN_CORE_H = \ main.h \ miner.h \ mruset.h \ + neuralnet/claim.h \ neuralnet/cpid.h \ neuralnet/neuralnet.h \ neuralnet/neuralnet_native.h \ @@ -158,10 +159,11 @@ GRIDCOIN_CORE_CPP = addrdb.cpp \ keystore.cpp \ main.cpp \ miner.cpp \ + neuralnet/claim.cpp \ + neuralnet/cpid.cpp \ neuralnet/neuralnet.cpp \ neuralnet/neuralnet_native.cpp \ neuralnet/neuralnet_stub.cpp \ - neuralnet/cpid.cpp \ neuralnet/project.cpp \ neuralnet/researcher.cpp \ neuralnet/superblock.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 8a88f9878d..19c7d5c9d9 100755 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -40,6 +40,7 @@ GRIDCOIN_TESTS =\ test/mruset_tests.cpp \ test/multisig_tests.cpp \ test/netbase_tests.cpp \ + test/neuralnet/claim_tests.cpp \ test/neuralnet/cpid_tests.cpp \ test/neuralnet/project_tests.cpp \ test/neuralnet/researcher_tests.cpp \ diff --git a/src/beacon.cpp b/src/beacon.cpp index 9c0f9708ea..d7067b50aa 100644 --- a/src/beacon.cpp +++ b/src/beacon.cpp @@ -108,6 +108,29 @@ std::string GetBeaconPublicKey(const std::string& cpid, bool bAdvertisingBeacon) return sBeaconPublicKey; } +std::set GetAlternativeBeaconKeys(const std::string& cpid) +{ + int64_t iMaxSeconds = 60 * 24 * 30 * 6 * 60; + std::set result; + + for(const auto& item : ReadCacheSection(Section::BEACONALT)) + { + const std::string& key = item.first; + const std::string& value = item.second.value; + if(!std::equal(cpid.begin(), cpid.end(), key.begin())) + continue; + + const int64_t iAge = pindexBest != NULL + ? pindexBest->nTime - item.second.timestamp + : 0; + if (iAge > iMaxSeconds) + continue; + + result.emplace(value); + } + return result; +} + int64_t BeaconTimeStamp(const std::string& cpid, bool bZeroOutAfterPOR) { if (!IsResearcher(cpid)) { diff --git a/src/beacon.h b/src/beacon.h index bf9375e355..44296e0665 100644 --- a/src/beacon.h +++ b/src/beacon.h @@ -8,6 +8,7 @@ #include "util.h" #include #include +#include // This is modelled after AppCacheEntry/Section but named separately. struct BeaconEntry @@ -69,6 +70,7 @@ int64_t BeaconAgeAdvertiseThreshold(); void GetBeaconElements(const std::string& sBeacon, std::string& out_cpid, std::string& out_address, std::string& out_publickey); std::string GetBeaconPublicKey(const std::string& cpid, bool bAdvertisingBeacon); +std::set GetAlternativeBeaconKeys(const std::string& cpid); int64_t BeaconTimeStamp(const std::string& cpid, bool bZeroOutAfterPOR); bool HasActiveBeacon(const std::string& cpid); diff --git a/src/contract/polls.cpp b/src/contract/polls.cpp index 6831a70b45..5406fb6c43 100644 --- a/src/contract/polls.cpp +++ b/src/contract/polls.cpp @@ -93,16 +93,17 @@ std::pair CreateVoteContract(std::string sTitle, std:: std::string acceptable_answers = PollAnswers(sTitle); return std::make_pair("Error", "Sorry, Answer " + sAnswer + " is not one of the acceptable answers, allowable answers are: " + acceptable_answers + ". If you are voting multiple choice, please use a semicolon delimited vote string such as : 'dog;cat'."); } - std::string sParam = SerializeBoincBlock(GlobalCPUMiningCPID, pindexBest->nVersion); + + const std::string primary_cpid = NN::GetPrimaryCpid(); + std::string GRCAddress = DefaultWalletAddress(); - StructCPID& structMag = GetInitializedStructCPID2(GlobalCPUMiningCPID.cpid, mvMagnitudes); + StructCPID& structMag = GetInitializedStructCPID2(primary_cpid, mvMagnitudes); double dmag = structMag.Magnitude; double poll_duration = PollDuration(sTitle) * 86400; // Prevent Double Voting - std::string cpid1 = GlobalCPUMiningCPID.cpid; std::string GRCAddress1 = DefaultWalletAddress(); - GetEarliestStakeTime(GRCAddress1, cpid1); + GetEarliestStakeTime(GRCAddress1, primary_cpid); double cpid_age = GetAdjustedTime() - ReadCache(Section::GLOBAL, "nCPIDTime").timestamp; double stake_age = GetAdjustedTime() - ReadCache(Section::GLOBAL, "nGRCTime").timestamp; @@ -123,12 +124,12 @@ std::pair CreateVoteContract(std::string sTitle, std:: else { std::string voter = "" - + GlobalCPUMiningCPID.cpid + "" + GRCAddress + "" + + primary_cpid + "" + GRCAddress + "" + hashRand.GetHex() + "" + RoundToString(nBalance,2) + "" + RoundToString(dmag,0) + ""; // Add the provable balance and the provable magnitude - this goes into effect July 1 2017 voter += GetProvableVotingWeightXML(); - std::string pk = sTitle + ";" + GRCAddress + ";" + GlobalCPUMiningCPID.cpid; + std::string pk = sTitle + ";" + GRCAddress + ";" + primary_cpid; std::string contract = "" + sTitle + "" + sAnswer + "" + voter; std::string result = SendContract("vote",pk,contract); std::string narr = "Your CPID weight is " + RoundToString(dmag,0) + " and your Balance weight is " + RoundToString(nBalance,0) + "."; diff --git a/src/fwd.h b/src/fwd.h index 7316305a65..c9194fb3bd 100644 --- a/src/fwd.h +++ b/src/fwd.h @@ -10,7 +10,6 @@ class CTransaction; class CWallet; // Gridcoin -struct MiningCPID; struct StructCPID; class ThreadHandler; diff --git a/src/global_objects_noui.hpp b/src/global_objects_noui.hpp index 56e5567c78..7b24471625 100755 --- a/src/global_objects_noui.hpp +++ b/src/global_objects_noui.hpp @@ -40,39 +40,12 @@ struct StructCPID std::set rewardBlocks; }; -struct MiningCPID -{ - bool initialized; - double Magnitude; - double LastPaymentTime; - double ResearchSubsidy; - double ResearchAge; - double ResearchMagnitudeUnit; - double ResearchAverageMagnitude; - double InterestSubsidy; - - std::string cpid; - std::string clientversion; - std::string GRCAddress; - std::string lastblockhash; - std::string Organization; - std::string NeuralHash; - std::string superblock; - std::string LastPORBlockHash; - std::string CurrentNeuralHash; - std::string BoincPublicKey; - std::string BoincSignature; -}; - //Network Averages extern std::map mvNetwork; extern std::map mvNetworkCopy; extern std::map mvMagnitudes; extern std::map mvMagnitudesCopy; -//Global CPU Mining CPID: -extern MiningCPID GlobalCPUMiningCPID; - // Timers extern std::map mvTimers; // Contains event timers that reset after max ms duration iterator is exceeded diff --git a/src/kernel.cpp b/src/kernel.cpp index 5333e61b8c..95cfde4769 100644 --- a/src/kernel.cpp +++ b/src/kernel.cpp @@ -12,7 +12,6 @@ using namespace std; StructCPID GetStructCPID(); -extern double GetLastPaymentTimeByCPID(std::string cpid); namespace { //! @@ -260,39 +259,6 @@ bool ComputeNextStakeModifier(const CBlockIndex* pindexPrev, uint64_t& nStakeMod return true; } -double GetLastPaymentTimeByCPID(std::string cpid) -{ - double lpt = 0; - if (mvMagnitudes.size() > 0) - { - StructCPID UntrustedHost = GetStructCPID(); - UntrustedHost = mvMagnitudes[cpid]; //Contains Consensus Magnitude - if (UntrustedHost.initialized) - { - double mag_accuracy = UntrustedHost.Accuracy; - if (mag_accuracy > 0) - { - lpt = UntrustedHost.LastPaymentTime; - } - } - else - { - if (IsResearcher(cpid)) - { - lpt = 0; - } - } - } - else - { - if (IsResearcher(cpid)) - { - lpt=0; - } - } - return lpt; -} - // Get stake modifier checksum unsigned int GetStakeModifierChecksum(const CBlockIndex* pindex) { @@ -392,10 +358,11 @@ bool CalculateLegacyV3HashProof( // after the coins mature! CBigNum CalculateStakeHashV8( - const CBlock &CoinBlock, const CTransaction &CoinTx, - unsigned CoinTxN, unsigned nTimeTx, - uint64_t StakeModifier, - const MiningCPID &BoincData) + const CBlock &CoinBlock, + const CTransaction &CoinTx, + unsigned CoinTxN, + unsigned nTimeTx, + uint64_t StakeModifier) { CDataStream ss(SER_GETHASH, 0); ss << StakeModifier; @@ -407,9 +374,7 @@ CBigNum CalculateStakeHashV8( return hashProofOfStake; } -int64_t CalculateStakeWeightV8( - const CTransaction &CoinTx, unsigned CoinTxN, - const MiningCPID &BoincData) +int64_t CalculateStakeWeightV8(const CTransaction &CoinTx, unsigned CoinTxN) { int64_t nValueIn = CoinTx.vout[CoinTxN].nValue; nValueIn /= 1250000; @@ -489,16 +454,13 @@ bool CheckProofOfStakeV8( if (blockPrev.nTime + nStakeMinAge > tx.nTime) // Min age requirement return error("CheckProofOfStakeV8: min age violation"); - MiningCPID boincblock = DeserializeBoincBlock(Block.vtx[0].hashBoinc,Block.nVersion); - - // uint64_t StakeModifier = 0; if(!FindStakeModifierRev(StakeModifier,pindexPrev)) return error("CheckProofOfStakeV8: unable to find stake modifier"); //Stake refactoring TomasBrod - int64_t Weight= CalculateStakeWeightV8(txPrev,txin.prevout.n,boincblock); - CBigNum bnHashProof= CalculateStakeHashV8(blockPrev,txPrev,txin.prevout.n,tx.nTime,StakeModifier,boincblock); + int64_t Weight = CalculateStakeWeightV8(txPrev, txin.prevout.n); + CBigNum bnHashProof = CalculateStakeHashV8(blockPrev, txPrev, txin.prevout.n, tx.nTime, StakeModifier); // Base target CBigNum bnTarget; diff --git a/src/kernel.h b/src/kernel.h index 8c6ceb0c4c..abd3e797f4 100644 --- a/src/kernel.h +++ b/src/kernel.h @@ -71,12 +71,12 @@ bool FindStakeModifierRev(uint64_t& StakeModifier,CBlockIndex* pindexPrev); // Kernel for V8 CBigNum CalculateStakeHashV8( - const CBlock &CoinBlock, const CTransaction &CoinTx, - unsigned CoinTxN, unsigned nTimeTx, - uint64_t StakeModifier, - const MiningCPID &BoincData); -int64_t CalculateStakeWeightV8( - const CTransaction &CoinTx, unsigned CoinTxN, - const MiningCPID &BoincData); + const CBlock &CoinBlock, + const CTransaction &CoinTx, + unsigned CoinTxN, + unsigned nTimeTx, + uint64_t StakeModifier); + +int64_t CalculateStakeWeightV8(const CTransaction &CoinTx, unsigned CoinTxN); #endif // PPCOIN_KERNEL_H diff --git a/src/key.h b/src/key.h index c4a991f146..f789d44b00 100644 --- a/src/key.h +++ b/src/key.h @@ -68,7 +68,7 @@ class CPubKey { public: CPubKey() { } - CPubKey(const std::vector &vchPubKeyIn) : vchPubKey(vchPubKeyIn) { } + CPubKey(std::vector vchPubKeyIn) : vchPubKey(std::move(vchPubKeyIn)) { } friend bool operator==(const CPubKey &a, const CPubKey &b) { return a.vchPubKey == b.vchPubKey; } friend bool operator!=(const CPubKey &a, const CPubKey &b) { return a.vchPubKey != b.vchPubKey; } friend bool operator<(const CPubKey &a, const CPubKey &b) { return a.vchPubKey < b.vchPubKey; } @@ -81,6 +81,15 @@ class CPubKey { READWRITE(vchPubKey); } + static CPubKey Parse(const std::string& input) + { + if (input.empty()) { + return CPubKey(); + } + + return CPubKey(ParseHex(input)); + } + CKeyID GetID() const { return CKeyID(Hash160(vchPubKey)); } @@ -100,6 +109,11 @@ class CPubKey { std::vector Raw() const { return vchPubKey; } + + std::string ToString() const + { + return HexStr(vchPubKey); + } }; diff --git a/src/main.cpp b/src/main.cpp index dbff57f6fd..d86710c61d 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -49,7 +49,7 @@ extern void ResetTimerMain(std::string timer_name); bool TallyResearchAverages(CBlockIndex* index); bool TallyResearchAverages_retired(CBlockIndex* index); bool TallyResearchAverages_v9(CBlockIndex* index); -extern void IncrementCurrentNeuralNetworkSupermajority(std::string NeuralHash, std::string GRCAddress, double distance); +extern void IncrementCurrentNeuralNetworkSupermajority(const NN::QuorumHash& quorum_hash, std::string GRCAddress, double distance); extern double ExtractMagnitudeFromExplainMagnitude(); extern void GridcoinServices(); extern double SnapToGrid(double d); @@ -63,7 +63,7 @@ extern void IncrementVersionCount(const std::string& Version); double GetSuperblockAvgMag(std::string data,double& out_beacon_count,double& out_participant_count,double& out_avg,bool bIgnoreBeacons, int nHeight); extern bool LoadAdminMessages(bool bFullTableScan,std::string& out_errors); extern std::string GetCurrentNeuralNetworkSupermajorityHash(double& out_popularity); -extern double CalculatedMagnitude2(std::string cpid, int64_t locktime,bool bUseLederstrumpf); +extern double CalculatedMagnitude2(std::string cpid, int64_t locktime); bool AsyncNeuralRequest(std::string command_name,std::string cpid,int NodeLimit); extern bool FullSyncWithDPORNodes(); @@ -71,7 +71,7 @@ extern bool FullSyncWithDPORNodes(); extern bool GetEarliestStakeTime(std::string grcaddress, std::string cpid); extern double GetTotalBalance(); extern std::string PubKeyToAddress(const CScript& scriptPubKey); -extern void IncrementNeuralNetworkSupermajority(const std::string& NeuralHash, const std::string& GRCAddress, double distance, const CBlockIndex* pblockindex); +extern void IncrementNeuralNetworkSupermajority(const NN::QuorumHash& NeuralHash, const std::string& GRCAddress, double distance, const CBlockIndex* pblockindex); extern CBlockIndex* GetHistoricalMagnitude(std::string cpid); @@ -105,7 +105,6 @@ int64_t nLastGRCtallied = 0; int64_t nLastCleaned = 0; extern double CoinToDouble(double surrogate); -extern MiningCPID GetMiningCPID(); extern StructCPID GetStructCPID(); ///////////////////////MINOR VERSION//////////////////////////////// @@ -115,7 +114,7 @@ std::string msMasterMessagePrivateKey = "308201130201010420fbd45ffb02ff05a3322c0 std::string msMasterMessagePublicKey = "044b2938fbc38071f24bede21e838a0758a52a0085f2e034e7f971df445436a252467f692ec9c5ba7e5eaa898ab99cbd9949496f7e3cafbf56304b1cc2e5bdf06e"; int64_t GetMaximumBoincSubsidy(int64_t nTime); -extern double CalculatedMagnitude(int64_t locktime,bool bUseLederstrumpf); +extern double CalculatedMagnitude(int64_t locktime); extern int64_t GetCoinYearReward(int64_t nTime); BlockMap mapBlockIndex; @@ -186,7 +185,6 @@ extern double LederstrumpfMagnitude2(double Magnitude, int64_t locktime); extern void GetGlobalStatus(); bool PollIsActive(const std::string& poll_contract); -extern bool IsCPIDValidv2(MiningCPID& mc, int height); extern bool LessVerbose(int iMax1000); /////////////////////////////// @@ -224,7 +222,6 @@ bool fEnforceCanonical = true; bool fUseFastIndex = false; // Gridcoin status ************* -MiningCPID GlobalCPUMiningCPID = GetMiningCPID(); int nBoincUtilization = 0; std::string sRegVer; @@ -570,7 +567,7 @@ void GetGlobalStatus() try { - double boincmagnitude = CalculatedMagnitude(GetAdjustedTime(),false); + double boincmagnitude = CalculatedMagnitude(GetAdjustedTime()); uint64_t nWeight = 0; pwalletMain->GetStakeWeight(nWeight); nBoincUtilization = boincmagnitude; //Legacy Support for the about screen @@ -597,7 +594,7 @@ void GetGlobalStatus() GlobalStatusStruct.magnitude = RoundToString(boincmagnitude,2); GlobalStatusStruct.ETTS = RoundToString(dETTS,3); GlobalStatusStruct.ERRperday = RoundToString(boincmagnitude * GRCMagnitudeUnit(GetAdjustedTime()),2); - GlobalStatusStruct.cpid = GlobalCPUMiningCPID.cpid; + GlobalStatusStruct.cpid = NN::GetPrimaryCpid(); try { GlobalStatusStruct.poll = GetCurrentOverviewTabPoll(); @@ -1657,20 +1654,18 @@ static CBigNum GetProofOfStakeLimit(int nHeight) } -double CalculatedMagnitude(int64_t locktime,bool bUseLederstrumpf) +double CalculatedMagnitude(int64_t locktime) { // Get neural network magnitude: - std::string cpid = ""; - if (GlobalCPUMiningCPID.initialized && !GlobalCPUMiningCPID.cpid.empty()) cpid = GlobalCPUMiningCPID.cpid; - StructCPID& stDPOR = GetInitializedStructCPID2(cpid,mvDPOR); - return bUseLederstrumpf ? LederstrumpfMagnitude2(stDPOR.Magnitude,locktime) : stDPOR.Magnitude; + StructCPID& stDPOR = GetInitializedStructCPID2(NN::GetPrimaryCpid(), mvDPOR); + return stDPOR.Magnitude; } -double CalculatedMagnitude2(std::string cpid, int64_t locktime,bool bUseLederstrumpf) +double CalculatedMagnitude2(std::string cpid, int64_t locktime) { // Get neural network magnitude: StructCPID& stDPOR = GetInitializedStructCPID2(cpid,mvDPOR); - return bUseLederstrumpf ? LederstrumpfMagnitude2(stDPOR.Magnitude,locktime) : stDPOR.Magnitude; + return stDPOR.Magnitude; } //Survey Results: Start inflation rate: 9%, end=1%, 30 day steps, 9 steps, mag multiplier start: 2, mag end .3, 9 steps @@ -1796,7 +1791,7 @@ int64_t GetConstantBlockReward(const CBlockIndex* index) } int64_t GetProofOfStakeReward(uint64_t nCoinAge, int64_t nFees, std::string cpid, - bool VerifyingBlock, int VerificationPhase, int64_t nTime, CBlockIndex* pindexLast, std::string operation, + bool VerifyingBlock, int VerificationPhase, int64_t nTime, CBlockIndex* pindexLast, double& OUT_POR, double& OUT_INTEREST, double& dAccrualAge, double& dMagnitudeUnit, double& AvgMagnitude) { @@ -1834,7 +1829,7 @@ int64_t GetProofOfStakeReward(uint64_t nCoinAge, int64_t nFees, std::string cpid else { // Research Age Subsidy - PROD - int64_t nBoinc = ComputeResearchAccrual(nTime, cpid, operation, pindexLast, VerifyingBlock, VerificationPhase, dAccrualAge, dMagnitudeUnit, AvgMagnitude); + int64_t nBoinc = ComputeResearchAccrual(nTime, cpid, pindexLast, VerifyingBlock, VerificationPhase, dAccrualAge, dMagnitudeUnit, AvgMagnitude); int64_t nInterest = 0; // TestNet: For any subsidy < 30 day duration, ensure 100% that we have a start magnitude and an end magnitude, otherwise make subsidy 0 : PASS @@ -1969,10 +1964,12 @@ bool CheckProofOfResearch( !IsResearchAgeEnabled(pindexPrev->nHeight)) return true; - MiningCPID bb = DeserializeBoincBlock(block.vtx[0].hashBoinc, block.nVersion); - if(!IsResearcher(bb.cpid)) + const NN::Claim& claim = block.GetClaim(); + if(!claim.HasResearchReward()) return true; + std::string cpid = claim.m_mining_id.ToString(); + //For higher security, plus lets catch these bad blocks before adding them to the chain to prevent reorgs: double OUT_POR = 0; double OUT_INTEREST = 0; @@ -1988,40 +1985,43 @@ bool CheckProofOfResearch( return true; // 6-4-2017 - Verify researchers stored block magnitude - double dNeuralNetworkMagnitude = CalculatedMagnitude2(bb.cpid, block.nTime, false); - if( bb.Magnitude > 0 - && (fTestNet || (!fTestNet && pindexPrev->nHeight > 947000)) - && bb.Magnitude > (dNeuralNetworkMagnitude*1.25) ) + double dNeuralNetworkMagnitude = CalculatedMagnitude2(cpid, block.nTime); + + if (claim.m_magnitude > (dNeuralNetworkMagnitude*1.25) + && (fTestNet || (!fTestNet && pindexPrev->nHeight > 947000))) { - return error("CheckProofOfResearch: Researchers block magnitude > neural network magnitude: Block Magnitude %f, Neural Network Magnitude %f, CPID %s ", - bb.Magnitude, dNeuralNetworkMagnitude, bb.cpid.c_str()); + return error( + "CheckProofOfResearch: Researchers block magnitude > neural network magnitude: Block Magnitude %f, Neural Network Magnitude %f, CPID %s ", + claim.m_magnitude, + dNeuralNetworkMagnitude, + cpid); } - int64_t nCalculatedResearch = GetProofOfStakeReward(nCoinAge, nFees, bb.cpid, true, 1, block.nTime, - pindexBest, "checkblock_researcher", OUT_POR, OUT_INTEREST, dAccrualAge, dMagnitudeUnit, dAvgMagnitude); + int64_t nCalculatedResearch = GetProofOfStakeReward(nCoinAge, nFees, cpid, true, 1, block.nTime, + pindexBest, OUT_POR, OUT_INTEREST, dAccrualAge, dMagnitudeUnit, dAvgMagnitude); if(!IsV9Enabled_Tally(pindexPrev->nHeight)) { - if (bb.ResearchSubsidy > ((OUT_POR*1.25)+1)) + if (claim.m_research_subsidy > ((OUT_POR*1.25)+1)) { if (fDebug) LogPrintf("CheckProofOfResearch: Researchers Reward Pays too much : Retallying : " "claimedand %f vs calculated StakeReward %f for CPID %s", - bb.ResearchSubsidy, OUT_POR, bb.cpid); + claim.m_research_subsidy, OUT_POR, cpid); TallyResearchAverages(pindexBest); - GetLifetimeCPID(bb.cpid); - nCalculatedResearch = GetProofOfStakeReward(nCoinAge, nFees, bb.cpid, true, 2, block.nTime, - pindexBest, "checkblock_researcher_doublecheck", OUT_POR, OUT_INTEREST, dAccrualAge, dMagnitudeUnit, dAvgMagnitude); + GetLifetimeCPID(cpid); + nCalculatedResearch = GetProofOfStakeReward(nCoinAge, nFees, cpid, true, 2, block.nTime, + pindexBest, OUT_POR, OUT_INTEREST, dAccrualAge, dMagnitudeUnit, dAvgMagnitude); } } (void)nCalculatedResearch; - if (bb.ResearchSubsidy > ((OUT_POR*1.25)+1)) + if (claim.m_research_subsidy > ((OUT_POR*1.25)+1)) { - if(fDebug) LogPrintf("CheckProofOfResearch: pHistorical was %s", GetHistoricalMagnitude(bb.cpid)->GetBlockHash().GetHex()); + if(fDebug) LogPrintf("CheckProofOfResearch: pHistorical was %s", GetHistoricalMagnitude(cpid)->GetBlockHash().GetHex()); return block.DoS(10,error("CheckProofOfResearch: Researchers Reward Pays too much : " "claimed %f vs calculated %f for CPID %s", - bb.ResearchSubsidy, OUT_POR, bb.cpid.c_str() )); + claim.m_research_subsidy, OUT_POR, cpid)); } return true; @@ -2661,7 +2661,9 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck, boo mapQueuedChanges[hashTx] = CTxIndex(posThisTx, tx.vout.size()); } - MiningCPID bb = DeserializeBoincBlock(vtx[0].hashBoinc,nVersion); + const NN::Claim& claim = GetClaim(); + const std::string cpid = claim.m_mining_id.ToString(); + uint64_t nCoinAge = 0; double dStakeReward = CoinToDouble(nStakeReward); double dStakeRewardWithoutFees = CoinToDouble(nStakeReward - nFees); @@ -2698,17 +2700,17 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck, boo //9-3-2015 double dMaxResearchAgeReward = CoinToDouble(GetMaximumBoincSubsidy(nTime) * COIN * 255); - if (bb.ResearchSubsidy > dMaxResearchAgeReward && IsResearchAgeEnabled(pindex->nHeight)) + if (claim.m_research_subsidy > dMaxResearchAgeReward && IsResearchAgeEnabled(pindex->nHeight)) return DoS(1, error("ConnectBlock[ResearchAge] : Coinstake pays above maximum (actual= %f, vs calculated=%f )", dStakeRewardWithoutFees, dMaxResearchAgeReward)); - if (!IsResearcher(bb.cpid) && dStakeReward > 1) + if (!claim.HasResearchReward() && dStakeReward > 1) { double OUT_POR = 0; double OUT_INTEREST_OWED = 0; double unused; int64_t calculatedResearchReward = GetProofOfStakeReward( - nCoinAge, nFees, bb.cpid, true, 1, nTime, - pindex, "connectblock_investor", + nCoinAge, nFees, cpid, true, 1, nTime, + pindex, OUT_POR, OUT_INTEREST_OWED, unused, unused, unused); if(!is_claim_valid(nStakeReward, 0, OUT_INTEREST_OWED, nFees)) @@ -2731,11 +2733,11 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck, boo // Gridcoin: Store verified magnitude and CPID in block index (7-11-2015) if(IsResearchAgeEnabled(pindex->nHeight)) { - pindex->SetCPID(bb.cpid); - pindex->nMagnitude = bb.Magnitude; - pindex->nResearchSubsidy = bb.ResearchSubsidy; - pindex->nInterestSubsidy = bb.InterestSubsidy; - pindex->nIsSuperBlock = (bb.superblock.length() > 20) ? 1 : 0; + pindex->SetCPID(cpid); + pindex->nMagnitude = claim.m_magnitude; + pindex->nResearchSubsidy = claim.m_research_subsidy; + pindex->nInterestSubsidy = claim.m_block_subsidy; + pindex->nIsSuperBlock = claim.ContainsSuperblock() ? 1 : 0; // Must scan transactions after CoinStake to know if this is a contract. int iPos = 0; pindex->nIsContract = 0; @@ -2761,25 +2763,19 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck, boo double dAvgMagnitude = 0; // ResearchAge 1: - GetProofOfStakeReward(nCoinAge, nFees, bb.cpid, true, 1, nTime, - pindex, "connectblock_researcher", OUT_POR, OUT_INTEREST, dAccrualAge, dMagnitudeUnit, dAvgMagnitude); - if (IsResearcher(bb.cpid)) + GetProofOfStakeReward(nCoinAge, nFees, cpid, true, 1, nTime, + pindex, OUT_POR, OUT_INTEREST, dAccrualAge, dMagnitudeUnit, dAvgMagnitude); + if (claim.HasResearchReward()) { //ResearchAge: Since the best block may increment before the RA is connected but After the RA is computed, the ResearchSubsidy can sometimes be slightly smaller than we calculate here due to the RA timespan increasing. So we will allow for time shift before rejecting the block. - double dDrift = IsResearchAgeEnabled(pindex->nHeight) ? bb.ResearchSubsidy*.15 : 1; + double dDrift = IsResearchAgeEnabled(pindex->nHeight) ? claim.m_research_subsidy * .15 : 1; if (IsResearchAgeEnabled(pindex->nHeight) && dDrift < 10) dDrift = 10; - if ((bb.ResearchSubsidy + bb.InterestSubsidy + dDrift) < dStakeRewardWithoutFees) + if ((claim.TotalSubsidy() + dDrift) < dStakeRewardWithoutFees) { return DoS(20, error("ConnectBlock[] : Researchers Interest %f + Research %f + TimeDrift %f = %f exceeded by StakeRewardWithoutFees %f, with mint %f, Out_Interest %f, OUT_POR %f, Fees %f, for CPID %s", - bb.InterestSubsidy, bb.ResearchSubsidy, dDrift, bb.ResearchSubsidy + bb.InterestSubsidy + dDrift, - dStakeRewardWithoutFees, mint, OUT_INTEREST, OUT_POR, CoinToDouble(nFees), bb.cpid.c_str())); - } - - if (bb.lastblockhash != pindex->pprev->GetBlockHash().GetHex()) - { - std::string sNarr = "ConnectBlock[ResearchAge] : Historical DPOR Replay attack : lastblockhash != actual last block hash."; - LogPrintf("****** %s ***** ",sNarr); + claim.m_block_subsidy, claim.m_research_subsidy, dDrift, claim.TotalSubsidy() + dDrift, + dStakeRewardWithoutFees, mint, OUT_INTEREST, OUT_POR, CoinToDouble(nFees), cpid)); } if (IsResearchAgeEnabled(pindex->nHeight) @@ -2788,77 +2784,68 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck, boo // 6-4-2017 - Verify researchers stored block magnitude // 2018 02 04 - Moved here for better effect. - double dNeuralNetworkMagnitude = CalculatedMagnitude2(bb.cpid, nTime, false); - if( bb.Magnitude > 0 - && (fTestNet || (!fTestNet && (pindex->nHeight-1) > 947000)) - && bb.Magnitude > (dNeuralNetworkMagnitude*1.25) ) + double dNeuralNetworkMagnitude = CalculatedMagnitude2(cpid, nTime); + if (claim.m_magnitude > (dNeuralNetworkMagnitude * 1.25) + && (fTestNet || (!fTestNet && (pindex->nHeight-1) > 947000))) { return DoS(20, error( "ConnectBlock[ResearchAge]: Researchers block magnitude > neural network magnitude: Block Magnitude %f, Neural Network Magnitude %f, CPID %s ", - bb.Magnitude, dNeuralNetworkMagnitude, bb.cpid.c_str())); + claim.m_magnitude, dNeuralNetworkMagnitude, cpid)); } // 2018 02 04 - Brod - Move cpid check here for better effect /* Only signature check is sufficient here, but kiss and call the function. The height is of previous block. */ - if( !IsCPIDValidv2(bb,pindex->nHeight-1) ) + if (pindex->nHeight > nGrandfather && !NN::VerifyClaim(claim, pindex->pprev->GetBlockHash())) { if( GetBadBlocks().count(pindex->GetBlockHash())==0 ) return DoS(20, error( "ConnectBlock[ResearchAge]: Bad CPID or Block Signature : CPID %s, LBH %s, Bad Hashboinc [%s]", - bb.cpid.c_str(), - bb.lastblockhash.c_str(), vtx[0].hashBoinc.c_str())); + cpid, + pindex->pprev->GetBlockHash().ToString(), + vtx[0].hashBoinc.c_str())); else LogPrintf("WARNING: ignoring invalid hashBoinc signature on block %s", pindex->GetBlockHash().ToString()); } - // Mitigate DPOR Relay attack - // bb.LastBlockhash should be equal to previous index lastblockhash, in order to check block signature correctly and prevent re-use of lastblockhash - if (bb.lastblockhash != pindex->pprev->GetBlockHash().GetHex()) - { - std::string sNarr = "ConnectBlock[ResearchAge] : DPOR Replay attack : lastblockhash != actual last block hash."; - LogPrintf("****** %s ***** ", sNarr); - if (fTestNet || (pindex->nHeight > 975000)) return DoS(20, error(" %s ",sNarr.c_str())); - } - if(!is_claim_valid(nStakeReward, OUT_POR, OUT_INTEREST, nFees)) { GetLifetimeCPID(pindex->GetCPID()); // Rescan... - GetProofOfStakeReward(nCoinAge, nFees, bb.cpid, true, 2, nTime, - pindex, "connectblock_researcher_doublecheck", OUT_POR, OUT_INTEREST, dAccrualAge, dMagnitudeUnit, dAvgMagnitude); + GetProofOfStakeReward(nCoinAge, nFees, cpid, true, 2, nTime, + pindex, OUT_POR, OUT_INTEREST, dAccrualAge, dMagnitudeUnit, dAvgMagnitude); if(!is_claim_valid(nStakeReward, OUT_POR, OUT_INTEREST, nFees)) { if(GetBadBlocks().count(pindex->GetBlockHash()) == 0) return DoS(10,error( "ConnectBlock[ResearchAge] : Researchers Reward Pays too much : Interest %f and Research %f and StakeReward %f, OUT_POR %f, with Out_Interest %f for CPID %s ", - bb.InterestSubsidy, bb.ResearchSubsidy, dStakeReward, OUT_POR, OUT_INTEREST,bb.cpid.c_str())); + claim.m_block_subsidy, claim.m_research_subsidy, dStakeReward, OUT_POR, OUT_INTEREST, cpid)); else LogPrintf("WARNING ConnectBlock[ResearchAge] : Researchers Reward Pays too much : bad block ignored: Interest %f and Research %f and StakeReward %f, OUT_POR %f, with Out_Interest %f for CPID %s ", - bb.InterestSubsidy, bb.ResearchSubsidy, dStakeReward, OUT_POR, OUT_INTEREST,bb.cpid.c_str()); + claim.m_block_subsidy, claim.m_research_subsidy, dStakeReward, OUT_POR, OUT_INTEREST, cpid); } } } } //Approve first coinstake in DPOR block - if (IsResearcher(bb.cpid) && IsLockTimeWithinMinutes(GetBlockTime(), 15, GetAdjustedTime()) && !IsResearchAgeEnabled(pindex->nHeight)) + if (claim.HasResearchReward() && IsLockTimeWithinMinutes(GetBlockTime(), 15, GetAdjustedTime()) && !IsResearchAgeEnabled(pindex->nHeight)) { - if (bb.ResearchSubsidy > (GetOwedAmount(bb.cpid)+1)) + if (claim.m_research_subsidy > (GetOwedAmount(cpid) + 1)) { - StructCPID& strUntrustedHost = GetInitializedStructCPID2(bb.cpid,mvMagnitudes); - if (bb.ResearchSubsidy > strUntrustedHost.totalowed) + StructCPID& strUntrustedHost = GetInitializedStructCPID2(cpid, mvMagnitudes); + if (claim.m_research_subsidy > strUntrustedHost.totalowed) { - double deficit = strUntrustedHost.totalowed - bb.ResearchSubsidy; + double deficit = strUntrustedHost.totalowed - claim.m_research_subsidy; if ( (deficit < -500 && strUntrustedHost.Accuracy > 10) || (deficit < -150 && strUntrustedHost.Accuracy > 5) || deficit < -50) { LogPrintf("ConnectBlock[] : Researchers Reward results in deficit of %f for CPID %s with trust level of %f - (Submitted Research Subsidy %f vs calculated=%f) Hash: %s", - deficit, bb.cpid, (double)strUntrustedHost.Accuracy, bb.ResearchSubsidy, + deficit, cpid, (double)strUntrustedHost.Accuracy, claim.m_research_subsidy, OUT_POR, vtx[0].hashBoinc.c_str()); } else { return error("ConnectBlock[] : Researchers Reward for CPID %s pays too much - (Submitted Research Subsidy %f vs calculated=%f) Hash: %s", - bb.cpid.c_str(), bb.ResearchSubsidy, + cpid, claim.m_research_subsidy, OUT_POR, vtx[0].hashBoinc.c_str()); } } @@ -2870,7 +2857,7 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck, boo //DPOR - 6/12/2015 - Reject superblocks not hashing to the supermajority: - if (bb.superblock.length() > 20) + if (claim.ContainsSuperblock()) { if(nVersion >= 9) { @@ -2885,7 +2872,7 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck, boo if ((pindex->nHeight > nGrandfather && !fReorganizing) || nVersion >= 9 ) { // 12-20-2015 : Add support for Binary Superblocks - std::string superblock = UnpackBinarySuperblock(bb.superblock); + std::string superblock = UnpackBinarySuperblock(claim.m_superblock.PackLegacy()); std::string neural_hash = GetQuorumHash(superblock); std::string legacy_neural_hash = RetrieveMd5(superblock); double popularity = 0; @@ -2899,20 +2886,20 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck, boo try { CBitcoinAddress address; - bool validaddressinblock = address.SetString(bb.GRCAddress); + bool validaddressinblock = address.SetString(claim.m_quorum_address); validaddressinblock &= address.IsValid(); if (!validaddressinblock) { return error("ConnectBlock[] : Superblock staked with invalid GRC address in block"); } - if (!IsNeuralNodeParticipant(bb.GRCAddress, nTime)) + if (!IsNeuralNodeParticipant(claim.m_quorum_address, nTime)) { return error("ConnectBlock[] : Superblock staked by ineligible neural node participant"); } } catch (...) { - return error("ConnectBlock[] : Superblock stake check caused unknown exception with GRC address %s", bb.GRCAddress.c_str()); + return error("ConnectBlock[] : Superblock stake check caused unknown exception with GRC address %s", claim.m_quorum_address); } } if (!VerifySuperblock(superblock, pindex)) @@ -2946,9 +2933,9 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex, bool fJustCheck, boo // I would suggest to NOT bother with superblock at all here. It will be loaded in tally. if ((OutOfSyncByAge() || fColdBoot || fReorganizing) && IsResearchAgeEnabled(pindex->nHeight) && pindex->nHeight > nGrandfather) { - if (bb.superblock.length() > 20) + if (claim.ContainsSuperblock()) { - std::string superblock = UnpackBinarySuperblock(bb.superblock); + std::string superblock = UnpackBinarySuperblock(claim.m_superblock.PackLegacy()); if (VerifySuperblock(superblock, pindex)) { LoadSuperblock(superblock,pindex->nTime,pindex->nHeight); @@ -3118,13 +3105,13 @@ bool DisconnectBlocksBatch(CTxDB& txdb, list& vResurrect, unsigned /* fix up after disconnecting, prepare for new blocks */ if(cnt_dis>0) { - - //Block was disconnected - User is Re-eligibile for staking - StructCPID& sMag = GetInitializedStructCPID2(GlobalCPUMiningCPID.cpid,mvMagnitudes); + // Block was disconnected - User is Re-eligibile for staking + const std::string primary_cpid = NN::GetPrimaryCpid(); + StructCPID& sMag = GetInitializedStructCPID2(primary_cpid, mvMagnitudes); if (sMag.initialized) { sMag.LastPaymentTime = 0; - mvMagnitudes[GlobalCPUMiningCPID.cpid]=sMag; + mvMagnitudes[primary_cpid] = sMag; } // Resurrect memory transactions that were in the disconnected branch @@ -3569,8 +3556,13 @@ bool CBlock::CheckBlock(std::string sCaller, int height1, int64_t Mint, bool fCh // that can be verified before saving an orphan block. // Size limits - if (vtx.empty() || vtx.size() > MAX_BLOCK_SIZE || ::GetSerializeSize(*this, SER_NETWORK, PROTOCOL_VERSION) > MAX_BLOCK_SIZE) + if (vtx.empty() + || vtx.size() > MAX_BLOCK_SIZE + || ::GetSerializeSize(*this, (SER_NETWORK & SER_SKIPSUPERBLOCK), PROTOCOL_VERSION) > MAX_BLOCK_SIZE + || ::GetSerializeSize(GetSuperblock(), SER_NETWORK, PROTOCOL_VERSION) > NN::Superblock::MAX_SIZE) + { return DoS(100, error("CheckBlock[] : size limits failed")); + } // Check proof of work matches claimed amount if (fCheckPOW && IsProofOfWork() && !CheckProofOfWork(GetPoWHash(), nBits)) @@ -3589,43 +3581,49 @@ bool CBlock::CheckBlock(std::string sCaller, int height1, int64_t Mint, bool fCh for (unsigned int i = 1; i < vtx.size(); i++) if (vtx[i].IsCoinBase()) return DoS(100, error("CheckBlock[] : more than one coinbase")); + //Research Age - MiningCPID bb = DeserializeBoincBlock(vtx[0].hashBoinc,nVersion); + const NN::Claim& claim = GetClaim(); + + // Version 11+ blocks store the claim context in the block itself instead + // of the hashBoinc field of the first transaction. The hash of the claim + // is placed in the coinbase transaction instead to verify its integrity: + // + if (nVersion >= 11) { + if (!claim.WellFormed()) { + return DoS(100, error("CheckBlock[] : malformed claim")); + } + + if (claim.GetHash() != uint256(vtx[0].hashBoinc)) { + return DoS(100, error("CheckBlock[] : claim hash mismatch")); + } + } + if(nVersion<9) { //For higher security, plus lets catch these bad blocks before adding them to the chain to prevent reorgs: - if (IsResearcher(bb.cpid) && IsProofOfStake() && height1 > nGrandfather && IsResearchAgeEnabled(height1) && BlockNeedsChecked(nTime) && !fLoadingIndex) - { - double blockVersion = BlockVersion(bb.clientversion); - double cvn = ClientVersionNew(); - if (fDebug10) LogPrintf("BV %f, CV %f ",blockVersion,cvn); - // Enforce Beacon Age - if (blockVersion < 3588 && height1 > 860500 && !fTestNet) - return error("CheckBlock[]: Old client spamming new blocks after mandatory upgrade "); - } - //Orphan Flood Attack if (height1 > nGrandfather) { - double bv = BlockVersion(bb.clientversion); + double blockVersion = BlockVersion(claim.m_client_version); double cvn = ClientVersionNew(); - if (fDebug10) LogPrintf("BV %f, CV %f ",bv,cvn); + if (fDebug10) LogPrintf("BV %f, CV %f ",blockVersion,cvn); // Enforce Beacon Age - if (bv < 3588 && height1 > 860500 && !fTestNet) + if (blockVersion < 3588 && height1 > 860500 && !fTestNet) return error("CheckBlock[]: Old client spamming new blocks after mandatory upgrade "); } } - if (!fLoadingIndex && IsResearcher(bb.cpid) && height1 > nGrandfather && BlockNeedsChecked(nTime)) + if (!fLoadingIndex && claim.HasResearchReward() && height1 > nGrandfather && BlockNeedsChecked(nTime)) { // Full "v3" signature check is performed in ConnectBlock - if (bb.lastblockhash.size() != 64 || bb.BoincSignature.size() < 16 - || bb.BoincSignature.find(' ') != std::string::npos) + if (claim.m_signature.size() < 16) { return DoS(20, error( - "Bad CPID or Block Signature : height %i, CPID %s, LBH %s, Bad Hashboinc [%s]", - height1, bb.cpid.c_str(), - bb.lastblockhash.c_str(), vtx[0].hashBoinc.c_str())); + "Bad CPID or Block Signature : height %i, CPID %s, Bad Hashboinc [%s]", + height1, + claim.m_mining_id.ToString(), + vtx[0].hashBoinc)); } } @@ -3690,7 +3688,6 @@ bool CBlock::CheckBlock(std::string sCaller, int height1, int64_t Mint, bool fCh if (fCheckMerkleRoot && hashMerkleRoot != BuildMerkleTree()) return DoS(100, error("CheckBlock[] : hashMerkleRoot mismatch")); - //if (fDebug3) LogPrintf(".EOCB."); return true; } @@ -3719,12 +3716,15 @@ bool CBlock::AcceptBlock(bool generated_by_me) || (IsV8Enabled(nHeight) && nVersion < 8) || (IsV9Enabled(nHeight) && nVersion < 9) || (IsV10Enabled(nHeight) && nVersion < 10) + || (IsV11Enabled(nHeight) && nVersion < 11) ) return DoS(20, error("AcceptBlock() : reject too old nVersion = %d", nVersion)); else if( (!IsProtocolV2(nHeight) && nVersion >= 7) ||(!IsV8Enabled(nHeight) && nVersion >= 8) ||(!IsV9Enabled(nHeight) && nVersion >= 9) - ||(!IsV10Enabled(nHeight) && nVersion >= 10)) + ||(!IsV10Enabled(nHeight) && nVersion >= 10) + ||(!IsV11Enabled(nHeight) && nVersion >= 11) + ) return DoS(100, error("AcceptBlock() : reject too new nVersion = %d", nVersion)); if (IsProofOfWork() && nHeight > LAST_POW_BLOCK) @@ -4078,23 +4078,21 @@ void GridcoinServices() } /* Do this only for users with valid CPID */ - if (TimerMain("send_beacon",180) && IsResearcher(GlobalCPUMiningCPID.cpid)) - { - std::string tBeaconPublicKey = GetBeaconPublicKey(GlobalCPUMiningCPID.cpid,true); - - /* If there is no public key, beacon needs advertising */ - if (tBeaconPublicKey.empty()) - { - std::string sOutPubKey = ""; - std::string sOutPrivKey = ""; - std::string sError = ""; - std::string sMessage = ""; - bool fResult = AdvertiseBeacon(sOutPrivKey,sOutPubKey,sError,sMessage); - if (!fResult) - { - LogPrintf("BEACON ERROR! Unable to send beacon %s, %s",sError, sMessage); - LOCK(MinerStatus.lock); - msMiningErrors6 = _("Unable To Send Beacon! Unlock Wallet!"); + if (TimerMain("send_beacon", 180)) { + if (const NN::CpidOption cpid = NN::Researcher::Get()->Id().TryCpid()) { + // If there is no public key, beacon needs advertising + if (GetBeaconPublicKey(cpid->ToString(), true).empty()) { + std::string sOutPubKey = ""; + std::string sOutPrivKey = ""; + std::string sError = ""; + std::string sMessage = ""; + bool fResult = AdvertiseBeacon(sOutPrivKey,sOutPubKey,sError,sMessage); + if (!fResult) + { + LogPrintf("BEACON ERROR! Unable to send beacon %s, %s",sError, sMessage); + LOCK(MinerStatus.lock); + msMiningErrors6 = _("Unable To Send Beacon! Unlock Wallet!"); + } } } } @@ -4562,61 +4560,6 @@ std::string RetrieveMd5(std::string s1) } } -std::set GetAlternativeBeaconKeys(const std::string& cpid) -{ - int64_t iMaxSeconds = 60 * 24 * 30 * 6 * 60; - std::set result; - - for(const auto& item : ReadCacheSection(Section::BEACONALT)) - { - const std::string& key = item.first; - const std::string& value = item.second.value; - if(!std::equal(cpid.begin(), cpid.end(), key.begin())) - continue; - - const int64_t iAge = pindexBest != NULL - ? pindexBest->nTime - item.second.timestamp - : 0; - if (iAge > iMaxSeconds) - continue; - - result.emplace(value); - } - return result; -} - -bool IsCPIDValidv2(MiningCPID& mc, int height) -{ - //09-25-2016: Transition to CPID Keypairs. - if (height < nGrandfather) return true; - if (mc.cpid.empty()) return error("IsCPIDValidv2(): cpid empty"); - if (!IsResearcher(mc.cpid)) return true; /* is investor? */ - - const std::string sBPK_n = GetBeaconPublicKey(mc.cpid, false); - bool kmval = sBPK_n == mc.BoincPublicKey; - bool result = CheckMessageSignature("R","cpid", mc.cpid + mc.lastblockhash, mc.BoincSignature, sBPK_n); - - if (!result) - { - for (const std::string& key_alt : GetAlternativeBeaconKeys(mc.cpid)) - { - const bool scval_alt = CheckMessageSignature("R","cpid", mc.cpid + mc.lastblockhash, mc.BoincSignature, key_alt); - kmval = key_alt == mc.BoincPublicKey; - - if (scval_alt) { - LogPrintf("WARNING: IsCPIDValidv2: good signature with alternative key"); - result = true; - } - } - } - - if (!kmval) { - LogPrintf("WARNING: IsCPIDValidv2: block key mismatch"); - } - - return result; -} - double GetOwedAmount(std::string cpid) { if (mvMagnitudes.size() > 1) @@ -4777,8 +4720,7 @@ bool GetEarliestStakeTime(std::string grcaddress, std::string cpid) block.ReadFromDisk(pblockindex); std::string hashboinc = ""; if (block.vtx.size() > 0) hashboinc = block.vtx[0].hashBoinc; - MiningCPID bb = DeserializeBoincBlock(hashboinc,block.nVersion); - myCPID = bb.cpid; + myCPID = block.GetClaim().m_mining_id.ToString(); } else { @@ -4955,27 +4897,22 @@ bool ComputeNeuralNetworkSupermajorityHashes() continue; block.ReadFromDisk(pblockindex); + const NN::Claim& claim = block.GetClaim(); - std::string hashboinc; - if (block.vtx.size() > 0) hashboinc = block.vtx[0].hashBoinc; - if (!hashboinc.empty()) + //If block is pending: 7-25-2015 + if (claim.ContainsSuperblock()) { - MiningCPID bb = DeserializeBoincBlock(hashboinc,block.nVersion); - //If block is pending: 7-25-2015 - if (bb.superblock.length() > 20) + std::string superblock = UnpackBinarySuperblock(claim.m_superblock.PackLegacy()); + if (VerifySuperblock(superblock, pblockindex)) { - std::string superblock = UnpackBinarySuperblock(bb.superblock); - if (VerifySuperblock(superblock, pblockindex)) - { - WriteCache(Section::NEURALSECURITY, "pending",ToString(pblockindex->nHeight),GetAdjustedTime()); - } + WriteCache(Section::NEURALSECURITY, "pending",ToString(pblockindex->nHeight),GetAdjustedTime()); } - - IncrementVersionCount(bb.clientversion); - //Increment Neural Network Hashes Supermajority (over the last N blocks) - IncrementNeuralNetworkSupermajority(bb.NeuralHash,bb.GRCAddress,(nMaxDepth-pblockindex->nHeight)+10,pblockindex); - IncrementCurrentNeuralNetworkSupermajority(bb.CurrentNeuralHash,bb.GRCAddress,(nMaxDepth-pblockindex->nHeight)+10); } + + IncrementVersionCount(claim.m_client_version); + //Increment Neural Network Hashes Supermajority (over the last N blocks) + IncrementNeuralNetworkSupermajority(claim.m_quorum_hash, claim.m_quorum_address, (nMaxDepth-pblockindex->nHeight)+10, pblockindex); + IncrementCurrentNeuralNetworkSupermajority(claim.m_quorum_hash, claim.m_quorum_address, (nMaxDepth-pblockindex->nHeight)+10); } if (fDebug3) LogPrintf(".11."); @@ -5063,10 +5000,11 @@ bool TallyResearchAverages_retired(CBlockIndex* index) iRow++; if (IsSuperBlock(pblockindex) && !superblockloaded) { - MiningCPID bb = GetBoincBlockByIndex(pblockindex); - if (bb.superblock.length() > 20) + const NN::ClaimOption claim = GetClaimByIndex(pblockindex); + + if (claim && claim->ContainsSuperblock()) { - std::string superblock = UnpackBinarySuperblock(bb.superblock); + std::string superblock = UnpackBinarySuperblock(claim->m_superblock.PackLegacy()); if (VerifySuperblock(superblock, pblockindex)) { LoadSuperblock(superblock,pblockindex->nTime,pblockindex->nHeight); @@ -5171,11 +5109,13 @@ bool TallyResearchAverages_v9(CBlockIndex* index) if(!IsSuperBlock(sbIndex)) continue; - MiningCPID bb = GetBoincBlockByIndex(sbIndex); - if(bb.superblock.length() <= 20) + const NN::ClaimOption claim = GetClaimByIndex(sbIndex); + + if(!claim || !claim->ContainsSuperblock()) continue; - const std::string& superblock = UnpackBinarySuperblock(bb.superblock); + const std::string superblock = UnpackBinarySuperblock(claim->m_superblock.PackLegacy()); + if(!VerifySuperblock(superblock, sbIndex)) continue; @@ -6538,186 +6478,6 @@ std::string GetLastPORBlockHash(std::string cpid) return stCPID.BlockHash; } -std::string SerializeBoincBlock(MiningCPID mcpid, int BlockVersion) -{ - std::string delim = "<|>"; - std::string version = FormatFullVersion(); - int subsidy_places= BlockVersion<8 ? 2 : 8; - if (!IsResearchAgeEnabled(pindexBest->nHeight)) - { - mcpid.Organization = GetArg("-org", "windows"); - } - - mcpid.LastPORBlockHash = GetLastPORBlockHash(mcpid.cpid); - - if (mcpid.lastblockhash.empty()) mcpid.lastblockhash = "0"; - if (mcpid.LastPORBlockHash.empty()) mcpid.LastPORBlockHash="0"; - - if (IsResearcher(mcpid.cpid) && mcpid.lastblockhash != "0") - { - mcpid.BoincPublicKey = GetBeaconPublicKey(mcpid.cpid, false); - } - - // Note: Commented-out items recorded to document removed fields: - // - std::string bb = mcpid.cpid - + delim // + mcpid.projectname // Obsolete - + delim // + mcpid.aesskein // Obsolete - + delim // + RoundToString(mcpid.rac,0) // Obsolete - + delim // + RoundToString(mcpid.pobdifficulty,5) // Obsolete - + delim // + RoundToString((double)mcpid.diffbytes,0) // Obsolete - + delim // + mcpid.enccpid // Obsolete - + delim // + mcpid.encaes // Obsolete - + delim // + RoundToString(mcpid.nonce,0) // Obsolete - + delim // + RoundToString(mcpid.NetworkRAC,0) // Obsolete - + delim + version - + delim + RoundToString(mcpid.ResearchSubsidy,subsidy_places) - + delim + RoundToString(mcpid.LastPaymentTime,0) - + delim // + RoundToString(mcpid.RSAWeight,0) // Obsolete - + delim // + mcpid.cpidv2 // Obsolete - + delim + RoundToString(mcpid.Magnitude,0) - + delim + mcpid.GRCAddress - + delim + mcpid.lastblockhash - + delim + RoundToString(mcpid.InterestSubsidy,subsidy_places) - + delim + mcpid.Organization - + delim // + mcpid.OrganizationKey // Obsolete - + delim + mcpid.NeuralHash - + delim + mcpid.superblock - + delim // + RoundToString(mcpid.ResearchSubsidy2,2) // Obsolete - + delim + RoundToString(mcpid.ResearchAge,6) - + delim + RoundToString(mcpid.ResearchMagnitudeUnit,6) - + delim + RoundToString(mcpid.ResearchAverageMagnitude,2) - + delim + mcpid.LastPORBlockHash - + delim + mcpid.CurrentNeuralHash - + delim + mcpid.BoincPublicKey - + delim + mcpid.BoincSignature; - return bb; -} - - - -MiningCPID DeserializeBoincBlock(std::string block, int BlockVersion) -{ - MiningCPID surrogate = GetMiningCPID(); - int subsidy_places= BlockVersion<8 ? 2 : 8; - try - { - - std::vector s = split(block,"<|>"); - if (s.size() > 7) - { - // Note: Commented-out items recorded to document removed fields: - // - surrogate.cpid = s[0]; - //surrogate.projectname = s[1]; // Obsolete - //boost::to_lower(surrogate.projectname); // Obsolete - //surrogate.aesskein = s[2]; // Obsolete - //surrogate.rac = RoundFromString(s[3],0); // Obsolete - //surrogate.pobdifficulty = RoundFromString(s[4],6); // Obsolete - //surrogate.diffbytes = (unsigned int)RoundFromString(s[5],0); // Obsolete - //surrogate.enccpid = s[6]; // Obsolete - //surrogate.encboincpublickey = s[6]; // Obsolete - //surrogate.encaes = s[7]; // Obsolete - //surrogate.nonce = RoundFromString(s[8],0); // Obsolete - //if (s.size() > 9) - //{ - // surrogate.NetworkRAC = RoundFromString(s[9],0); // Obsolete - //} - if (s.size() > 10) - { - surrogate.clientversion = s[10]; - } - if (s.size() > 11) - { - surrogate.ResearchSubsidy = RoundFromString(s[11],2); - } - if (s.size() > 12) - { - surrogate.LastPaymentTime = RoundFromString(s[12],0); - } - //if (s.size() > 13) - //{ - // surrogate.RSAWeight = RoundFromString(s[13],0); // Obsolete - //} - //if (s.size() > 14) - //{ - // surrogate.cpidv2 = s[14]; // Obsolete - //} - if (s.size() > 15) - { - surrogate.Magnitude = RoundFromString(s[15],0); - } - if (s.size() > 16) - { - surrogate.GRCAddress = s[16]; - } - if (s.size() > 17) - { - surrogate.lastblockhash = s[17]; - } - if (s.size() > 18) - { - surrogate.InterestSubsidy = RoundFromString(s[18],subsidy_places); - } - if (s.size() > 19) - { - surrogate.Organization = s[19]; - } - //if (s.size() > 20) - //{ - // surrogate.OrganizationKey = s[20]; // Obsolete - //} - if (s.size() > 21) - { - surrogate.NeuralHash = s[21]; - } - if (s.size() > 22) - { - surrogate.superblock = s[22]; - } - //if (s.size() > 23) - //{ - // // Obsolete - // surrogate.ResearchSubsidy2 = RoundFromString(s[23],subsidy_places); - //} - if (s.size() > 24) - { - surrogate.ResearchAge = RoundFromString(s[24],6); - } - if (s.size() > 25) - { - surrogate.ResearchMagnitudeUnit = RoundFromString(s[25],6); - } - if (s.size() > 26) - { - surrogate.ResearchAverageMagnitude = RoundFromString(s[26],2); - } - if (s.size() > 27) - { - surrogate.LastPORBlockHash = s[27]; - } - if (s.size() > 28) - { - surrogate.CurrentNeuralHash = s[28]; - } - if (s.size() > 29) - { - surrogate.BoincPublicKey = s[29]; - } - if (s.size() > 30) - { - surrogate.BoincSignature = s[30]; - } - - } - } - catch (...) - { - LogPrintf("Deserialize ended with an error (06182014) "); - } - return surrogate; -} - StructCPID GetStructCPID() { StructCPID c; @@ -6745,21 +6505,6 @@ StructCPID GetStructCPID() } -MiningCPID GetMiningCPID() -{ - MiningCPID mc; - mc.initialized = false; - mc.lastblockhash = "0"; - mc.Magnitude = 0; - mc.LastPaymentTime=0; - mc.ResearchSubsidy = 0; - mc.InterestSubsidy = 0; - mc.ResearchAge = 0; - mc.ResearchMagnitudeUnit = 0; - mc.ResearchAverageMagnitude = 0; - return mc; -} - bool SendMessages(CNode* pto, bool fSendTrickle) { // Treat lock failures as send successes in case the caller disconnects @@ -6961,10 +6706,15 @@ bool SendMessages(CNode* pto, bool fSendTrickle) return true; } -void IncrementCurrentNeuralNetworkSupermajority(std::string NeuralHash, std::string GRCAddress, double distance) +void IncrementCurrentNeuralNetworkSupermajority( + const NN::QuorumHash& quorum_hash, + std::string GRCAddress, + double distance) { - if (NeuralHash.length() < 5) - return; + if (!quorum_hash.Valid()) + return; + + std::string NeuralHash = quorum_hash.ToString(); // 6-13-2015 ONLY Count Each Neural Hash Once per GRC address / CPID (1 VOTE PER RESEARCHER) const std::string& Security = ReadCache(Section::CURRENTNEURALSECURITY, GRCAddress).value; @@ -6981,11 +6731,17 @@ void IncrementCurrentNeuralNetworkSupermajority(std::string NeuralHash, std::str mvCurrentNeuralNetworkHash[NeuralHash] += votes; } -void IncrementNeuralNetworkSupermajority(const std::string& NeuralHash, const std::string& GRCAddress, double distance, const CBlockIndex* pblockindex) +void IncrementNeuralNetworkSupermajority( + const NN::QuorumHash& quorum_hash, + const std::string& GRCAddress, + double distance, + const CBlockIndex* pblockindex) { - if (NeuralHash.length() < 5) + if (!quorum_hash.Valid()) return; - + + const std::string NeuralHash = quorum_hash.ToString(); + if (pblockindex->nVersion >= 8) { try @@ -7065,9 +6821,9 @@ std::string GetCurrentNeuralNetworkSupermajorityHash(double& out_popularity) std::map sorted_hashes( mvCurrentNeuralNetworkHash.begin(), mvCurrentNeuralNetworkHash.end()); - + double highest_popularity = -1; - std::string neural_hash; + std::string neural_hash; for(auto& entry : sorted_hashes) { auto& hash = entry.first; @@ -7083,7 +6839,7 @@ std::string GetCurrentNeuralNetworkSupermajorityHash(double& out_popularity) neural_hash = hash; } } - + out_popularity = highest_popularity; return neural_hash; } @@ -7226,13 +6982,13 @@ double GRCMagnitudeUnit(int64_t locktime) } -int64_t ComputeResearchAccrual(int64_t nTime, std::string cpid, std::string operation, CBlockIndex* pindexLast, bool bVerifyingBlock, int iVerificationPhase, double& dAccrualAge, double& dMagnitudeUnit, double& AvgMagnitude) +int64_t ComputeResearchAccrual(int64_t nTime, std::string cpid, CBlockIndex* pindexLast, bool bVerifyingBlock, int iVerificationPhase, double& dAccrualAge, double& dMagnitudeUnit, double& AvgMagnitude) { // If not a researcher save cpu cycles and return 0 if (!IsResearcher(cpid)) return 0; - double dCurrentMagnitude = CalculatedMagnitude2(cpid, nTime, false); + double dCurrentMagnitude = CalculatedMagnitude2(cpid, nTime); if(pindexLast->nVersion>=9) { // Bugfix for newbie rewards always being around 1 GRC @@ -7429,24 +7185,17 @@ bool LoadAdminMessages(bool bFullTableScan, std::string& out_errors) return true; } - - - -MiningCPID GetBoincBlockByIndex(CBlockIndex* pblockindex) +NN::ClaimOption GetClaimByIndex(const CBlockIndex* const pblockindex) { CBlock block; - MiningCPID bb; - bb.initialized=false; - if (!pblockindex || !pblockindex->IsInMainChain()) return bb; - if (block.ReadFromDisk(pblockindex)) - { - std::string hashboinc = ""; - if (block.vtx.size() > 0) hashboinc = block.vtx[0].hashBoinc; - bb = DeserializeBoincBlock(hashboinc,block.nVersion); - bb.initialized=true; - return bb; - } - return bb; + + if (!pblockindex || !pblockindex->IsInMainChain() + || !block.ReadFromDisk(pblockindex)) + { + return boost::none; + } + + return block.PullClaim(); } std::string CPIDHash(double dMagIn, std::string sCPID) diff --git a/src/main.h b/src/main.h index 81116f6eeb..91edefcb6b 100644 --- a/src/main.h +++ b/src/main.h @@ -7,6 +7,7 @@ #include "util.h" #include "net.h" +#include "neuralnet/claim.h" #include "sync.h" #include "script.h" #include "scrypt.h" @@ -113,7 +114,9 @@ inline bool IsV10Enabled(int nHeight) inline bool IsV11Enabled(int nHeight) { // Returns false before planned intro of bv11. - return false; + return fTestNet + ? false + : false; } inline int GetSuperblockAgeSpacing(int nHeight) @@ -147,7 +150,6 @@ extern std::map mvDPORCopy; extern std::map mvResearchAge; -extern std::map mvBlockIndex; struct BlockHasher { @@ -266,13 +268,11 @@ bool CheckProofOfResearch( unsigned int GetNextTargetRequired(const CBlockIndex* pindexLast); int64_t GetConstantBlockReward(const CBlockIndex* index); -int64_t ComputeResearchAccrual(int64_t nTime, std::string cpid, std::string operation, CBlockIndex* pindexLast, bool bVerifyingBlock, int VerificationPhase, double& dAccrualAge, double& dMagnitudeUnit, double& AvgMagnitude); +int64_t ComputeResearchAccrual(int64_t nTime, std::string cpid, CBlockIndex* pindexLast, bool bVerifyingBlock, int VerificationPhase, double& dAccrualAge, double& dMagnitudeUnit, double& AvgMagnitude); int64_t GetProofOfStakeReward(uint64_t nCoinAge, int64_t nFees, std::string cpid, - bool VerifyingBlock, int VerificationPhase, int64_t nTime, CBlockIndex* pindexLast, std::string operation, + bool VerifyingBlock, int VerificationPhase, int64_t nTime, CBlockIndex* pindexLast, double& OUT_POR, double& OUT_INTEREST, double& dAccrualAge, double& dMagnitudeUnit, double& AvgMagnitude); -MiningCPID DeserializeBoincBlock(std::string block, int BlockVersion); -std::string SerializeBoincBlock(MiningCPID mcpid, int BlockVersion); bool OutOfSyncByAge(); bool NeedASuperblock(); std::string GetQuorumHash(const std::string& data); @@ -290,7 +290,7 @@ double GetAverageDifficulty(unsigned int nPoSInterval = 40); double GetEstimatedTimetoStake(double dDiff = 0.0, double dConfidence = 0.8); void AddRARewardBlock(const CBlockIndex* pIndex); -MiningCPID GetBoincBlockByIndex(CBlockIndex* pblockindex); +NN::ClaimOption GetClaimByIndex(const CBlockIndex* const pblockindex); int GetNumBlocksOfPeers(); bool IsInitialBlockDownload(); @@ -1047,8 +1047,8 @@ class CBlock // ppcoin: block signature - signed by one of the coin base txout[N]'s owner std::vector vchBlockSig; - - + // Gridcoin Research Reward Context + NN::Claim m_claim; // memory only mutable std::vector vMerkleTree; @@ -1057,10 +1057,6 @@ class CBlock mutable int nDoS; bool DoS(int nDoSIn, bool fIn) const { nDoS += nDoSIn; return fIn; } - //Gridcoin - 7/27/2014 - ///////////////////////////////////////// - - CBlock() { SetNull(); @@ -1078,9 +1074,26 @@ class CBlock READWRITE(nBits); READWRITE(nNonce); + // ConnectBlock depends on vtx following header to generate CDiskTxPos if (!(s.GetType() & (SER_GETHASH|SER_BLOCKHEADERONLY))) { READWRITE(vtx); READWRITE(vchBlockSig); + + // Before block version 11, the Gridcoin reward claim context is + // stored in the first transaction of the block. Versions 11 and + // above place a claim in the block to facilitate the submission + // of superblocks with a greater quantity of participant data. + // + // Because version 11+ blocks store a claim directly in a member + // field, the claim must be included as input to a block hash to + // protect its integrity. Previous versions hashed a claim along + // with the transactions. Block versions 11 and above must store + // the hash of the claim within the hashBoinc field of the first + // transaction and validation shall check that the hash matches. + // + if (nVersion >= 11) { + READWRITE(m_claim); + } } else if (ser_action.ForRead()) { const_cast(this)->vtx.clear(); const_cast(this)->vchBlockSig.clear(); @@ -1099,6 +1112,7 @@ class CBlock vchBlockSig.clear(); vMerkleTree.clear(); nDoS = 0; + m_claim = NN::Claim(); } bool IsNull() const @@ -1119,6 +1133,38 @@ class CBlock return scrypt_blockhash(CVOIDBEGIN(nVersion)); } + const NN::Claim& GetClaim() const + { + if (nVersion >= 11 || m_claim.m_mining_id.Valid() || vtx.empty()) { + return m_claim; + } + + // Before block version 11, the Gridcoin reward claim context is + // stored in the first transaction of the block. We'll store the + // parsed representation here to speed up subsequent access: + // + REF(m_claim) = NN::Claim::Parse(vtx[0].hashBoinc, nVersion); + + return m_claim; + } + + NN::Claim PullClaim() + { + if (nVersion >= 11 || m_claim.m_mining_id.Valid() || vtx.empty()) { + return std::move(m_claim); + } + + // Before block version 11, the Gridcoin reward claim context is + // stored in the first transaction of the block. + // + return NN::Claim::Parse(vtx[0].hashBoinc, nVersion); + } + + const NN::Superblock& GetSuperblock() const + { + return GetClaim().m_superblock; + } + int64_t GetBlockTime() const { return (int64_t)nTime; diff --git a/src/miner.cpp b/src/miner.cpp index e54ab96ac8..c967f0f4b6 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -10,6 +10,7 @@ #include "main.h" #include "appcache.h" #include "neuralnet/neuralnet.h" +#include "neuralnet/researcher.h" #include "contract/contract.h" #include "util.h" @@ -26,19 +27,11 @@ using namespace std; // unsigned int nMinerSleep; -void ThreadCleanWalletPassphrase(void* parg); double CoinToDouble(double surrogate); - -void ThreadTopUpKeyPool(void* parg); - -double CalculatedMagnitude(int64_t locktime, bool bUseLederstrumpf); -double GetLastPaymentTimeByCPID(std::string cpid); +double CalculatedMagnitude2(std::string cpid, int64_t locktime); bool HasActiveBeacon(const std::string& cpid); -std::string SerializeBoincBlock(MiningCPID mcpid); bool LessVerbose(int iMax1000); -namespace NN { std::string GetPrimaryCpid(); } - namespace { // Some explaining would be appreciated class COrphan @@ -87,6 +80,52 @@ bool ReturnMinerError(CMinerStatus& status, const std::string& message) return false; } + +//! +//! \brief Sign the research reward claim context in the provided block. +//! +//! \param claim An initialized claim to sign for a newly-minted block. +//! \param last_block_hash Hash of the previous block to sign into the claim. +//! +//! \return \c true if the miner holds active beacon keys used to successfully +//! sign the claim in the block. +//! +bool SignClaim(NN::Claim& claim, const uint256& last_block_hash) +{ + const NN::CpidOption cpid = claim.m_mining_id.TryCpid(); + + if (!cpid) { + return false; // Skip beacon signature for investors. + } + + const std::string cpid_str = cpid->ToString(); + + if (!HasActiveBeacon(cpid_str)) { + return false; + } + + CKey beacon_key; + + if (!GetStoredBeaconPrivateKey(cpid_str, beacon_key)) { + return error("SignStakeBlock: Failed to sign claim -> No beacon key."); + } + + if (!claim.Sign(beacon_key, last_block_hash)) { + return error( + "SignStakeBlock: Failed to sign claim -> " + "Unable to sign message, check beacon private key."); + } + + if (fDebug2) { + LogPrintf( + "Signing claim for cpid %s and blockhash %s with sig %s", + cpid_str, + last_block_hash.ToString(), + HexStr(claim.m_signature)); + } + + return true; +} } // anonymous namespace CMinerStatus::CMinerStatus(void) @@ -485,8 +524,8 @@ bool CreateCoinStake( CBlock &blocknew, CKey &key, uint64_t StakeModifier = 0; if(!FindStakeModifierRev(StakeModifier,pindexPrev)) continue; - CoinWeight = CalculateStakeWeightV8(CoinTx,CoinTxN,GlobalCPUMiningCPID); - StakeKernelHash= CalculateStakeHashV8(CoinBlock,CoinTx,CoinTxN,txnew.nTime,StakeModifier,GlobalCPUMiningCPID); + CoinWeight = CalculateStakeWeightV8(CoinTx,CoinTxN); + StakeKernelHash= CalculateStakeHashV8(CoinBlock,CoinTx,CoinTxN,txnew.nTime,StakeModifier); CBigNum StakeTarget; StakeTarget.SetCompact(blocknew.nBits); @@ -819,24 +858,29 @@ unsigned int GetNumberOfStakeOutputs(int64_t &nValue, int64_t &nMinStakeSplitVal return(nStakeOutputs); } - - -bool SignStakeBlock(CBlock &block, CKey &key, vector &StakeInputs, CWallet *pwallet, MiningCPID& BoincData) +bool SignStakeBlock(CBlock &block, CKey &key, vector &StakeInputs, CWallet *pwallet) { - // Append beacon signature to coinbase - if (HasActiveBeacon(GlobalCPUMiningCPID.cpid)) - { - std::string sBoincSignature; - std::string sError; - bool bResult = SignBlockWithCPID(GlobalCPUMiningCPID.cpid, GlobalCPUMiningCPID.lastblockhash, sBoincSignature, sError); - if (!bResult) - { - return error("SignStakeBlock: Failed to sign boinchash -> %s", sError); + SignClaim(block.m_claim, block.hashPrevBlock); + + // Append the claim context to the block before signing the transactions: + // + if (block.nVersion <= 10) { + // Nodes that do not yet support block version 11 will parse the claim + // from the coinbase hashBoinc field. Set the previous block hash that + // old nodes check for research reward claims if necessary: + // + if (block.m_claim.HasResearchReward()) { + block.m_claim.m_last_block_hash = block.hashPrevBlock; } - BoincData.BoincSignature = sBoincSignature; - if(fDebug2) LogPrintf("Signing BoincBlock for cpid %s and blockhash %s with sig %s", GlobalCPUMiningCPID.cpid, GlobalCPUMiningCPID.lastblockhash, BoincData.BoincSignature); + + block.vtx[0].hashBoinc = block.m_claim.ToString(block.nVersion); + } else { + // After the mandatory switch to block version 11, the claim context is + // serialized directly in the block, but we need to add the hash of the + // claim to a transaction to protect the integrity of the data within: + // + block.vtx[0].hashBoinc = block.m_claim.GetHash().ToString(); } - block.vtx[0].hashBoinc = SerializeBoincBlock(BoincData,block.nVersion); //Sign the coinstake transaction unsigned nIn = 0; @@ -858,135 +902,148 @@ bool SignStakeBlock(CBlock &block, CKey &key, vector &StakeInp return true; } -void AddNeuralContractOrVote(const CBlock &blocknew, MiningCPID &bb) +void AddNeuralContractOrVote(CBlock& blocknew) { - if(OutOfSyncByAge()) - { - LogPrintf("AddNeuralContractOrVote: Out Of Sync"); + if (OutOfSyncByAge()) { + LogPrintf("AddNeuralContractOrVote: Out of sync."); return; } - if(!IsNeuralNodeParticipant(bb.GRCAddress, blocknew.nTime)) - { - LogPrintf("AddNeuralContractOrVote: Not Participating"); + std::string quorum_address = DefaultWalletAddress(); + + if (!IsNeuralNodeParticipant(quorum_address, blocknew.nTime)) { + LogPrintf("AddNeuralContractOrVote: Not participating."); return; } - if(blocknew.nVersion >= 9) - { - // break away from block timing - if (fDebug) LogPrintf("AddNeuralContractOrVote: Updating Neural Supermajority (v9 M) height %d",nBestHeight); - ComputeNeuralNetworkSupermajorityHashes(); + if (fDebug) { + LogPrintf("AddNeuralContractOrVote: Updating neural supermajority..."); } - if(!NeedASuperblock()) - { - LogPrintf("AddNeuralContractOrVote: not Needed"); + ComputeNeuralNetworkSupermajorityHashes(); + + if (!NeedASuperblock()) { + LogPrintf("AddNeuralContractOrVote: Not needed."); return; } int pending_height = RoundFromString(ReadCache(Section::NEURALSECURITY, "pending").value, 0); - if (pending_height>=(pindexBest->nHeight-200)) - { - LogPrintf("AddNeuralContractOrVote: already Pending"); + if (pending_height >= (pindexBest->nHeight - 200)) { + LogPrintf("AddNeuralContractOrVote: Already pending."); return; } double popularity = 0; std::string consensus_hash = GetNeuralNetworkSupermajorityHash(popularity); - /* Retrive the neural Contract */ - const std::string& sb_contract = NN::GetInstance()->GetNeuralContract(); - const std::string& sb_hash = GetQuorumHash(sb_contract); - - if(!sb_contract.empty()) - { + // Add our Neural Vote + // + // GetSuperblockHash() returns an invalid QuorumHash when the node has not + // yet received enough scraper data to resolve a convergence locally so it + // cannot vote for a superblock. + // + blocknew.m_claim.m_quorum_hash = NN::GetInstance()->GetSuperblockHash(); - /* To save network bandwidth, start posting the neural hashes in the - CurrentNeuralHash field, so that out of sync neural network nodes can - request neural data from those that are already synced and agree with the - supermajority over the last 24 hrs - Note: CurrentNeuralHash is not actually used for sb validity - */ - bb.CurrentNeuralHash = sb_hash; + if (!blocknew.m_claim.m_quorum_hash.Valid()) { + LogPrintf("AddNeuralContractOrVote: Local contract empty."); + return; + } - /* Add our Neural Vote */ - bb.NeuralHash = sb_hash; - LogPrintf("AddNeuralContractOrVote: Added our Neural Vote %s",sb_hash); + blocknew.m_claim.m_quorum_address = std::move(quorum_address); - if (consensus_hash!=sb_hash) - { - LogPrintf("AddNeuralContractOrVote: not in Consensus"); - return; - } + LogPrintf( + "AddNeuralContractOrVote: Added our quorum vote: %s", + blocknew.m_claim.m_quorum_hash.ToString()); - /* We have consensus, Add our neural contract */ - bb.superblock = PackBinarySuperblock(sb_contract); - LogPrintf("AddNeuralContractOrVote: Added our Superblock (size %" PRIszu ")",bb.superblock.length()); + if (blocknew.m_claim.m_quorum_hash != consensus_hash) { + LogPrintf("AddNeuralContractOrVote: Not in consensus."); + return; } - else - { - LogPrintf("AddNeuralContractOrVote: Local Contract Empty"); - /* Do NOT add a Neural Vote alone, because this hash is not Trusted! */ - } + // We have consensus, add our superblock contract: + blocknew.m_claim.m_superblock = NN::GetInstance()->GetSuperblockContract(); - return; + LogPrintf( + "AddNeuralContractOrVote: Added our Superblock (size %" PRIszu ").", + GetSerializeSize(blocknew.m_claim.m_superblock, SER_NETWORK, 1)); } -bool CreateGridcoinReward(CBlock &blocknew, MiningCPID& miningcpid, uint64_t &nCoinAge, CBlockIndex* pindexPrev, int64_t &nReward) +bool CreateGridcoinReward(CBlock &blocknew, uint64_t &nCoinAge, CBlockIndex* pindexPrev, int64_t &nReward) { - //remove fees from coinbase + // Remove fees from coinbase: int64_t nFees = blocknew.vtx[0].vout[0].nValue; blocknew.vtx[0].vout[0].SetEmpty(); - double OUT_POR = 0; - double out_interest = 0; - double dAccrualAge = 0; - double dAccrualMagnitudeUnit = 0; - double dAccrualMagnitude = 0; - - // ************************************************* CREATE PROOF OF RESEARCH REWARD ****************************** R HALFORD *************** - // ResearchAge 2 - // Note: Since research Age must be exact, we need to transmit the Block nTime here so it matches AcceptBlock - + NN::Claim& claim = blocknew.m_claim; + claim.m_mining_id = NN::Researcher::Get()->Id(); + + // If a researcher's beacon expired, generate the block as an investor. We + // cannot sign a research claim without the beacon key, so this avoids the + // issue that prevents a researcher from staking blocks if the beacon does + // not exist (it expired or it has yet to be advertised). + // + CKey dummy_key; + if (claim.m_mining_id.Which() == NN::MiningId::Kind::CPID + && !GetStoredBeaconPrivateKey(claim.m_mining_id.ToString(), dummy_key)) + { + LogPrintf( + "CreateGridcoinReward: CPID eligible but no active beacon key " + "found. Staking as investor."); - nReward = GetProofOfStakeReward( - nCoinAge, nFees, GlobalCPUMiningCPID.cpid, false, 0, - pindexPrev->nTime, pindexPrev,"createcoinstake", - OUT_POR,out_interest,dAccrualAge,dAccrualMagnitudeUnit,dAccrualMagnitude); + claim.m_mining_id = NN::MiningId::ForInvestor(); + } + double out_unused; // Drop currently unused values + + // Note: Since research age must be exact, we need to transmit the block + // nTime here so it matches AcceptBlock(): + nReward = GetProofOfStakeReward( + nCoinAge, + nFees, + claim.m_mining_id.ToString(), + false, // Is verifying block? (no) + 0, // Verification phase (N/A) + pindexPrev->nTime, + pindexPrev, + claim.m_research_subsidy, + claim.m_block_subsidy, + out_unused, + claim.m_magnitude_unit, + out_unused); + + // If no pending research subsidy value exists, build an investor claim. + // This avoids polluting the block index with non-research reward blocks + // that contain CPIDs which increases the effort needed to load research + // age context at start-up: + // + if (claim.m_mining_id.Which() == NN::MiningId::Kind::CPID + && claim.m_research_subsidy <= 0) + { + LogPrintf( + "CreateGridcoinReward: No positive research reward pending at " + "time of stake. Staking as investor."); - miningcpid = GlobalCPUMiningCPID; - uint256 pbh = 0; - pbh=pindexPrev->GetBlockHash(); + claim.m_mining_id = NN::MiningId::ForInvestor(); + } - miningcpid.lastblockhash = pbh.GetHex(); - miningcpid.LastPaymentTime = GetLastPaymentTimeByCPID(miningcpid.cpid); - miningcpid.Magnitude = CalculatedMagnitude(blocknew.nTime, false); - miningcpid.ResearchSubsidy = OUT_POR; - miningcpid.ResearchAge = dAccrualAge; - miningcpid.ResearchMagnitudeUnit = dAccrualMagnitudeUnit; - miningcpid.ResearchAverageMagnitude = dAccrualMagnitude; - miningcpid.InterestSubsidy = out_interest; - miningcpid.BoincSignature = ""; - miningcpid.CurrentNeuralHash = ""; - miningcpid.NeuralHash = ""; - miningcpid.superblock = ""; - miningcpid.GRCAddress = DefaultWalletAddress(); + claim.m_client_version = FormatFullVersion().substr(0, NN::Claim::MAX_VERSION_SIZE); + claim.m_organization = GetArgument("org", "").substr(0, NN::Claim::MAX_ORGANIZATION_SIZE); - GlobalCPUMiningCPID.lastblockhash = miningcpid.lastblockhash; + if (const NN::CpidOption cpid = claim.m_mining_id.TryCpid()) { + claim.m_magnitude = CalculatedMagnitude2(cpid->ToString(), blocknew.nTime); + } LogPrintf( - "CreateGridcoinReward: for %s mint %f Research %f, Interest %f ", - miningcpid.cpid.c_str(), + "CreateGridcoinReward: for %s mint %f magnitude %d Research %f, Interest %f ", + claim.m_mining_id.ToString(), CoinToDouble(nReward), - miningcpid.ResearchSubsidy, - miningcpid.InterestSubsidy); + claim.m_magnitude, + claim.m_research_subsidy, + claim.m_block_subsidy); - //fill in reward and boinc blocknew.vtx[1].vout[1].nValue += nReward; + return true; } @@ -1177,19 +1234,18 @@ void StakeMiner(CWallet *pwallet) CBlockIndex* pindexPrev = pindexBest; CBlock StakeBlock; - MiningCPID BoincData; + { LOCK(MinerStatus.lock); //clear miner messages MinerStatus.ReasonNotStaking=""; //New versions - StakeBlock.nVersion = 7; - if(IsV8Enabled(pindexPrev->nHeight+1)) - StakeBlock.nVersion = 8; - if(IsV9Enabled(pindexPrev->nHeight+1)) - StakeBlock.nVersion = 9; - if(IsV10Enabled(pindexPrev->nHeight + 1)) + if (IsV11Enabled(pindexPrev->nHeight + 1)) { + StakeBlock.nVersion = 11; + } else { StakeBlock.nVersion = 10; + StakeBlock.m_claim.m_version = 1; + } MinerStatus.Version= StakeBlock.nVersion; } @@ -1227,19 +1283,23 @@ void StakeMiner(CWallet *pwallet) // * add gridcoin reward to coinstake, fill-in nReward int64_t nReward = 0; - if( !CreateGridcoinReward(StakeBlock, BoincData, StakeCoinAge, pindexPrev, nReward) ) + if(!CreateGridcoinReward(StakeBlock, StakeCoinAge, pindexPrev, nReward)) { continue; + } + LogPrintf("StakeMiner: added gridcoin reward to coinstake"); // * If argument is supplied desiring stake output splitting or side staking, then call SplitCoinStakeOutput. if (fEnableStakeSplit || fEnableSideStaking) SplitCoinStakeOutput(StakeBlock, nReward, fEnableStakeSplit, fEnableSideStaking, vSideStakeAlloc, nMinStakeSplitValue, dEfficiency); - AddNeuralContractOrVote(StakeBlock, BoincData); + AddNeuralContractOrVote(StakeBlock); // * sign boinchash, coinstake, wholeblock - if( !SignStakeBlock(StakeBlock,BlockKey,StakeInputs,pwallet,BoincData) ) + if (!SignStakeBlock(StakeBlock, BlockKey, StakeInputs, pwallet)) { continue; + } + LogPrintf("StakeMiner: signed boinchash, coinstake, wholeblock"); { LOCK(MinerStatus.lock); diff --git a/src/neuralnet/claim.cpp b/src/neuralnet/claim.cpp new file mode 100644 index 0000000000..b298d2d990 --- /dev/null +++ b/src/neuralnet/claim.cpp @@ -0,0 +1,271 @@ +#include "beacon.h" +#include "key.h" +#include "neuralnet/claim.h" +#include "util.h" + +using namespace NN; + +namespace { +//! +//! \brief Convert a block hash to a hex-encoded string for legacy serialized +//! claims. +//! +//! \param block_hash SHA256 hash of the block to format a string for. +//! +//! \return Hex-encoded bytes in the hash, or "0" for a blank hash to conserve +//! space. +//! +std::string BlockHashToString(const uint256& block_hash) +{ + if (block_hash == 0) { + return "0"; + } + + return block_hash.ToString(); +} + +//! +//! \brief Get the hash of a subset of the data in the claim object used as +//! input to sign or verify a research reward claim. +//! +//! \param claim Claim to generate a hash for. +//! \param last_block_hash Hash of the block that preceeds the block that +//! contains the claim. +//! +//! \return Hash of the CPID and last block hash contained in the claim. +//! +uint256 GetClaimHash(const Claim& claim, const uint256& last_block_hash) +{ + const CpidOption cpid = claim.m_mining_id.TryCpid(); + + if (!cpid) { + return uint256(0); + } + + if (claim.m_version > 1) { + return Hash( + cpid->Raw().begin(), + cpid->Raw().end(), + last_block_hash.begin(), + last_block_hash.end()); + } + + const std::string cpid_hex = cpid->ToString(); + const std::string hash_hex = BlockHashToString(last_block_hash); + + return Hash(cpid_hex.begin(), cpid_hex.end(), hash_hex.begin(), hash_hex.end()); +} +} // anonymous namespace + +// ----------------------------------------------------------------------------- +// Functions +// ----------------------------------------------------------------------------- + +bool NN::VerifyClaim(const Claim& claim, const uint256& last_block_hash) +{ + if (!claim.m_mining_id.Valid()) { + return error("VerifyClaim(): Invalid mining ID."); + } + + const CpidOption cpid = claim.m_mining_id.TryCpid(); + + if (!cpid) { + // Investor claims are not signed by a beacon key. + return true; + } + + const std::string cpid_str = cpid->ToString(); + const std::string beacon_key = GetBeaconPublicKey(cpid_str, false); + + if (claim.VerifySignature(ParseHex(beacon_key), last_block_hash)) { + return true; + } + + for (const auto& beacon_alt_key : GetAlternativeBeaconKeys(cpid_str)) { + if (claim.VerifySignature(ParseHex(beacon_alt_key), last_block_hash)) { + LogPrintf("WARNING: VerifyClaim(): Good signature with alternative key."); + return true; + } + } + + LogPrintf("WARNING: VerifyClaim(): Block key mismatch."); + + return false; +} + +// ----------------------------------------------------------------------------- +// Class: Claim +// ----------------------------------------------------------------------------- + +Claim::Claim() : Claim(CURRENT_VERSION) +{ +} + +Claim::Claim(uint32_t version) + : m_version(version) + , m_block_subsidy(0) + , m_last_block_hash(0) + , m_research_subsidy(0) + , m_magnitude(0) + , m_magnitude_unit(0) +{ +} + +Claim Claim::Parse(const std::string& claim, int block_version) +{ + const int subsidy_places = block_version < 8 ? 2 : 8; + std::vector s = split(claim, "<|>"); + + // Legacy string claims (BoincBlocks) always parse to version 1: + // + Claim c(1); + + // Note: Commented-out items recorded to document removed fields: + // + switch (std::min(s.size() - 1, 30)) { + case 30: c.m_signature = DecodeBase64(s[30].c_str()); + case 29: //c.m_public_key = CPubKey::Parse(s[29]); + case 28: c.m_quorum_hash = QuorumHash::Parse(s[28]); + case 27: //c.m_last_por_block_hash = uint256(s[27]); + case 26: //c.m_average_magnitude = RoundFromString(s[26], 2); + case 25: c.m_magnitude_unit = RoundFromString(s[25], MAG_UNIT_PLACES); + case 24: //c.m_research_age = RoundFromString(s[24], 6); + case 23: //c.ResearchSubsidy2 = RoundFromString(s[23], subsidy_places); + case 22: c.m_superblock = Superblock::UnpackLegacy(s[22]); + case 21: if (!c.m_quorum_hash.Valid()) + c.m_quorum_hash = QuorumHash::Parse(s[21]); + case 20: //c.OrganizationKey = s[20]; + case 19: c.m_organization = std::move(s[19]); + case 18: c.m_block_subsidy = RoundFromString(s[18], subsidy_places); + case 17: //c.m_last_block_hash = uint256(s[17]); + case 16: c.m_quorum_address = s[16]; + case 15: c.m_magnitude = RoundFromString(s[15], 0); + case 14: //c.cpidv2 = s[14]; + case 13: //c.m_rsa_weight = RoundFromString(s[13], 0); + case 12: //c.m_last_payment_time = RoundFromString(s[12], 0); + case 11: c.m_research_subsidy = RoundFromString(s[11], 2); + case 10: c.m_client_version = std::move(s[10]); + case 9: //c.NetworkRAC = RoundFromString(s[9], 0); + case 8: + c.m_mining_id = MiningId::Parse(s[0]); + //c.projectname = s[1]; + //boost::to_lower(c.projectname); + //c.aesskein = s[2]; + //c.rac = RoundFromString(s[3],0); + //c.pobdifficulty = RoundFromString(s[4],6); + //c.diffbytes = (unsigned int)RoundFromString(s[5],0); + //c.enccpid = s[6]; + //c.encboincpublickey = s[6]; + //c.encaes = s[7]; + //c.nonce = RoundFromString(s[8],0); + break; + } + + return c; +} + +bool Claim::WellFormed() const +{ + return m_version > 0 && m_version <= Claim::CURRENT_VERSION + && (m_version == 1 + || (m_mining_id.Valid() + && !m_client_version.empty() + && m_client_version.size() <= MAX_VERSION_SIZE + && m_organization.size() <= MAX_ORGANIZATION_SIZE + && m_block_subsidy > 0 + && (m_mining_id.Which() == MiningId::Kind::INVESTOR + || (m_research_subsidy > 0 && m_signature.size() > 0)) + && (!m_quorum_hash.Valid() || m_quorum_address.size() > 0) + ) + ); +} + +bool Claim::HasResearchReward() const +{ + return m_mining_id.Which() == MiningId::Kind::CPID; +} + +bool Claim::ContainsSuperblock() const +{ + return m_superblock.WellFormed(); +} + +double Claim::TotalSubsidy() const +{ + return m_block_subsidy + m_research_subsidy; +} + +bool Claim::Sign(CKey& private_key, const uint256& last_block_hash) +{ + const CpidOption cpid = m_mining_id.TryCpid(); + + if (!cpid) { + return false; + } + + if (!private_key.Sign(GetClaimHash(*this, last_block_hash), m_signature)) { + m_signature.clear(); + return false; + } + + return true; +} + +bool Claim::VerifySignature( + const CPubKey& public_key, + const uint256& last_block_hash) const +{ + CKey key; + + if (!key.SetPubKey(public_key)) { + return false; + } + + return key.Verify(GetClaimHash(*this, last_block_hash), m_signature); +} + +uint256 Claim::GetHash() const +{ + return SerializeHash(*this); +} + +std::string Claim::ToString(const int block_version) const +{ + constexpr char delim[] = "<|>"; + + const int subsidy_places = block_version < 8 ? 2 : 8; + + // Note: Commented-out items recorded to document removed fields: + // + return m_mining_id.ToString() + + delim // + mcpid.projectname + + delim // + mcpid.aesskein + + delim // + RoundToString(mcpid.rac, 0) + + delim // + RoundToString(mcpid.pobdifficulty, 5) + + delim // + RoundToString((double)mcpid.diffbytes, 0) + + delim // + mcpid.enccpid + + delim // + mcpid.encaes + + delim // + RoundToString(mcpid.nonce, 0) + + delim // + RoundToString(mcpid.NetworkRAC, 0) + + delim + m_client_version + + delim + RoundToString(m_research_subsidy, subsidy_places) + + delim // + std::to_string(m_last_payment_time) + + delim // + std::to_string(m_rsa_weight) + + delim // + mcpid.cpidv2 + + delim + std::to_string(m_magnitude) + + delim + m_quorum_address + + delim + BlockHashToString(m_last_block_hash) + + delim + RoundToString(m_block_subsidy, subsidy_places) + + delim + m_organization + + delim // + mcpid.OrganizationKey + + delim + m_quorum_hash.ToString() + + delim + (m_superblock.WellFormed() ? m_superblock.PackLegacy() : "") + + delim // + RoundToString(mcpid.ResearchSubsidy2,2) + + delim // + RoundToString(m_research_age, 6) + + delim + RoundToString(m_magnitude_unit, MAG_UNIT_PLACES) + + delim // + RoundToString(m_average_magnitude, 2) + + delim // + BlockHashToString(m_last_por_block_hash) + + delim // + mcpid.CurrentNeuralHash + + delim // + m_public_key.ToString() + + delim + EncodeBase64(m_signature.data(), m_signature.size()); +} diff --git a/src/neuralnet/claim.h b/src/neuralnet/claim.h new file mode 100644 index 0000000000..d657b79adf --- /dev/null +++ b/src/neuralnet/claim.h @@ -0,0 +1,445 @@ +#pragma once + +#include "neuralnet/cpid.h" +#include "neuralnet/superblock.h" +#include "serialize.h" +#include "uint256.h" + +#include + +class CPubKey; + +namespace NN { +//! +//! \brief Converts non-negative \c double values to unsigned integers and +//! serializes or deserializes them using variable-width integer encoding. +//! +//! The Claim class contains \c double type floating-point values to match +//! the representation expected by legacy code. To improve the portability +//! of these values, we serialize these values as unsigned integers scaled +//! to store the appropriate accuracy. +//! +//! \tparam scale Accuracy of the floating-point number to store. +//! +template +class ClaimDoubleCompressor +{ +public: + //! + //! \brief Wrap the supplied double reference for serialization operations. + //! + //! \param value The double value to serialize as a variable-width integer. + //! + explicit ClaimDoubleCompressor(double& value) : m_value(value) + { + } + + //! + //! \brief Wrap the supplied double reference for serialization operations. + //! + //! \param value The double value to serialize as a variable-width integer. + //! + explicit ClaimDoubleCompressor(const double& value) : m_value(REF(value)) + { + } + + //! + //! \brief Serialize the object to the provided stream. + //! + //! \param stream The output stream. + //! + template + void Serialize(Stream& stream) const + { + VARINT(GetUnsigned()).Serialize(stream); + } + + //! + //! \brief Deserialize the object from the provided stream. + //! + //! \param stream The input stream. + //! + template + void Unserialize(Stream& stream) + { + uint64_t packed; + + VARINT(packed).Unserialize(stream); + + m_value = static_cast(packed) / std::pow(10, scale); + } + +private: + double& m_value; //!< The target value to serialize or deserialize. + + //! + //! \brief Calculate the integer value of the wrapped floating-point number. + //! + //! \return Unsigned integer representation scaled to the specified number + //! of places (half-even rounding). + //! + uint64_t GetUnsigned() const + { + // Claim objects should never contain negative floating-point values + // at serialization time. + // + assert(m_value >= 0); + + // Round using the same mode as RoundToString() (half-even; banker's + // rounding) to match the behavior: + // + return std::nearbyint(m_value * std::pow(10, scale)); + } +}; // ClaimDoubleCompressor + +//! +//! \brief An alias for use within \c SerializationOp blocks. +//! +template +using VarDouble = ClaimDoubleCompressor; + +//! +//! \brief Contains the reward claim context embedded in each generated block. +//! +//! Gridcoin blocks require some auxiliary information about claimed rewards to +//! facilitate and secure the reward protocol. Nodes embed the data represented +//! by a \c Claim instance in generated blocks to provide this context. +//! +struct Claim +{ + //! + //! \brief Version number of the current format for a serialized reward + //! claim block. + //! + //! CONSENSUS: Increment this value when introducing a breaking change and + //! ensure that the serialization/deserialization routines also handle all + //! of the previous versions. + //! + static constexpr uint32_t CURRENT_VERSION = 2; + + //! + //! \brief The maximum length of a serialized client version in a claim. + //! + static constexpr size_t MAX_VERSION_SIZE = 30; + + //! + //! \brief The maximum length of a serialized organization value in a claim. + //! + static constexpr size_t MAX_ORGANIZATION_SIZE = 50; + + //! + //! \brief Number of places after the decimal point of serialized magnitude + //! unit values. + //! + static constexpr size_t MAG_UNIT_PLACES = 6; + + //! + //! \brief Version number of the serialized reward claim format. + //! + //! Defaults to the most recent version for a new claim instance. + //! + //! Version 1: Parsed from legacy "BoincBlock"-formatted string data stored + //! in the \c hashBoinc field of a coinbase transaction. + //! + //! Version 2: Claim data serializable in binary format. Stored in a block's + //! \c m_claim field to enable submission of larger superblocks. + //! + uint32_t m_version = CURRENT_VERSION; + + //! + //! \brief Indicates whether the claim is for a researcher or investor. + //! + //! If the claim declares research rewards, this field shall contain the + //! external CPID of the researcher. For this case, it must match a CPID + //! advertised in a verified beacon. + //! + MiningId m_mining_id; // MiningCPID::cpid + + //! + //! \brief The version string of the wallet software running on the node + //! that submits the claim. + //! + //! Informational. This provides a rough metric of the distribution of + //! the software versions installed on staking nodes. No protocol rule + //! exists that depends on this value. + //! + //! Max length: 30 bytes. Blocks that contain a claim structure with a + //! version field longer than 30 characters are rejected. + //! + std::string m_client_version; // MiningCPID::clientversion + + //! + //! \brief A user-customizable field that may contain any arbitrary data. + //! + //! Informational. This field is intended to demonstrate an affiliation of + //! a staked block with the staking party. For example, testnet guidelines + //! recommend that participants set the organization value to recognizable + //! identities to facilitate communication when needed. + //! + //! Max length: 50 bytes. Blocks that contain a claim structure with an + //! organization field longer than 50 characters are rejected. + //! + std::string m_organization; // MiningCPID::Organization + + //! + //! \brief The GRC value minted for generating the new block. + //! + //! Below the switch to constant block rewards, this field contains the + //! amount of accrued interest claimed by the staking node. It contains + //! the constant block reward rate after the switch. + //! + //! Claims do not strictly need to contain the block subsidy or research + //! subsidy values. Nodes will calculate these values anyway to validate + //! incoming reward claims and can index those calculated values without + //! this field. It can be considered informational. + //! + double m_block_subsidy; // MiningCPID::InterestSubsidy + + //! + //! \brief Hash of the block below the block containing this claim. + //! + //! Nodes check that this hash matches the hash of block that preceeds the + //! block that contains the claim. This hash is signed along with the CPID + //! to prevent replay of the research reward subsidy. + //! + //! The significance of the last block is embedded into the claim signature + //! for researchers so we can consider this field informational. + //! + //! TODO: Remove this field after the switch to block version 11. + //! + uint256 m_last_block_hash; // MiningCPID::lastblockhash + + //! + //! \brief The GRC value of the research rewards claimed by the node. + //! + //! Contains a value of zero for investor claims. + //! + //! Claims do not strictly need to contain the block subsidy or research + //! subsidy values. Nodes will calculate these values anyway to validate + //! incoming reward claims and can index those calculated values without + //! this field. It can be considered informational. + //! + double m_research_subsidy; // MiningCPID::ResearchSubsidy + + //! + //! \brief The researcher magnitude value from the superblock at the time + //! of the claim. + //! + //! Informational. The magnitude value may better enable off-chain services + //! like explorers to more easily produce historical logs of the researcher + //! magnitudes over time. The protocol only checks that this magnitude does + //! not exceed the magnitude in a superblock for the same CPID. + //! + //! Previous protocol versions used the magnitude in reward calculations. + //! + uint16_t m_magnitude; // MiningCPID::Magnitude + + //! + //! \brief The magnitude ratio of the network at the time of the claim. + //! + //! Informational. + //! + double m_magnitude_unit; // MiningCPID::ResearchMagnitudeUnit + + //! + //! \brief Produced by signing the CPID and last block hash with a beacon + //! public key. + //! + //! Nodes verify this signature with the CPID's stored beacon key to prevent + //! unauthorized claim or replay of the research reward subsidy. + //! + std::vector m_signature; // MiningCPID::BoincSignature + + //! + //! \brief Hash of the superblock to vote for. + //! + //! When a superblock is due for a day, nodes will vote for a particular + //! superblock contract by submitting in this field the contract hash of + //! the pending superblock that the node caches locally. + //! + QuorumHash m_quorum_hash; // MiningCPID::NeuralHash + + //! + //! \brief The default wallet address of the node submitting the claim. + //! + //! This address matches the address advertised in the beacon for the CPID + //! owned by the node. It will determine whether a node may participate in + //! the superblock quorum for a particular day. + //! + std::string m_quorum_address; // MiningCPID::GRCAddress + + //! + //! \brief The complete superblock data when the node submitting the claim + //! also publishes the daily superblock in the generated block. + //! + //! Must be accompanied by a valid superblock hash in the \c m_quorum_hash + //! field. + //! + Superblock m_superblock; // MiningCPID::superblock + + //! + //! \brief Initialize an empty, invalid reward claim object. + //! + Claim(); + + //! + //! \brief Initialize an empty, invalid reward claim object of the specified + //! version. + //! + //! \param version Version number of the serialized reward claim format. + //! + Claim(uint32_t version); + + //! + //! \brief Parse a claim object from a legacy, delimited string (hashBoinc). + //! + //! \param claim Legacy "BoincBlock"-formatted claim string data. + //! \param block_version Format version of the block that contains the claim + //! to parse. + //! + //! \return A new claim instance that contains the parsed reward context. + //! + static Claim Parse(const std::string& claim, int block_version); + + //! + //! \brief Determine whether the instance represents a complete claim. + //! + //! The result of this method call does NOT guarantee that the claim is + //! valid. The return value of \c true only indicates that the instance + //! received each of the pieces of data needed for a well-formed claim. + //! + //! \return \c true if the claim contains each of the required elements. + //! + bool WellFormed() const; + + //! + //! \brief Determine whether the instance represents a claim that includes + //! accrued research rewards. + //! + //! \return \c true if the claim contains a valid CPID. + //! + bool HasResearchReward() const; + + //! + //! \brief Determine whether the claim instance includes a superblock. + //! + //! \return \c true if the claim contains a valid superblock object. + //! + bool ContainsSuperblock() const; + + //! + //! \brief Get the sum of the claimed block and research rewards. + //! + //! \return The sum of the block subsidy and research subsidy declared in + //! the claim. + //! + double TotalSubsidy() const; + + //! + //! \brief Sign an instance that claims research rewards. + //! + //! \param private_key The private key of the beacon to sign the claim + //! with. + //! \param last_block_hash Hash of the block that preceeds the block that + //! contains the claim. + //! + //! \return \c false if the claim does not contain a valid CPID or if the + //! signing fails. + //! + bool Sign(CKey& private_key, const uint256& last_block_hash); + + //! + //! \brief Validate the authenticity of a research reward claim by verifying + //! the digital signature. + //! + //! \param public_key The public key of the beacon that signed the + //! claim. + //! \param last_block_hash Hash of the block that preceeds the block that + //! contains the claim. + //! + //! \return \c true if the signature check passes using the supplied key. + //! + bool VerifySignature( + const CPubKey& public_key, + const uint256& last_block_hash) const; + + //! + //! \brief Compute a hash of the claim data. + //! + //! \return Hash of the data in the claim. + //! + uint256 GetHash() const; + + //! + //! \brief Get the legacy string representation of the claim. + //! + //! CONSENSUS: Although this method produces a legacy string compatible + //! with older protocols, it does not guarantee that the string matches + //! exactly to legacy input string versions imported by Claim::Parse(). + //! Use this method to produce a new claim context for generated legacy + //! blocks or for informational output. Do not reproduce existing claim + //! data with this routine if it will be retransmitted to other nodes. + //! + //! \param block_version Determines the number of subsidy places. + //! + //! \return A "BoincBlock"-formatted string that contains the claim data. + //! + std::string ToString(const int block_version) const; + + // + // Serialize and deserialize the claim in binary format instead of parsing + // and formatting the legacy delimited string representation. + // + // For Claim::m_version >= 2. + // + ADD_SERIALIZE_METHODS; + + template + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(m_version); + READWRITE(m_mining_id); + READWRITE(m_client_version); + READWRITE(m_organization); + + READWRITE(VarDouble(m_block_subsidy)); + + // Serialize research-related fields only for researcher claims: + // + if (m_mining_id.Which() == MiningId::Kind::CPID) { + READWRITE(VarDouble(m_research_subsidy)); + READWRITE(m_magnitude); + READWRITE(VarDouble(m_magnitude_unit)); + + READWRITE(m_signature); + } + + READWRITE(m_quorum_hash); + + if (!(s.GetType() & SER_GETHASH) && m_quorum_hash.Valid()) { + READWRITE(m_quorum_address); + + if (!(s.GetType() & SER_SKIPSUPERBLOCK)) { + READWRITE(m_superblock); + } + } + } +}; // Claim + +//! +//! \brief An optional type that either contains some claim object or does not. +//! +typedef boost::optional ClaimOption; + +//! +//! \brief Check the authenticity of a research reward claim by verifying the +//! signature against a matching beacon public key. +//! +//! \brief claim Contains the claim data to verify. +//! \param last_block_hash Hash of the block that preceeds the block that +//! contains the claim. +//! +//! \return \c true if the signature check passes. +//! +bool VerifyClaim(const Claim& claim, const uint256& last_block_hash); +} diff --git a/src/neuralnet/researcher.cpp b/src/neuralnet/researcher.cpp index b78ecb5891..be274c20d2 100644 --- a/src/neuralnet/researcher.cpp +++ b/src/neuralnet/researcher.cpp @@ -17,7 +17,6 @@ using namespace NN; std::string ExtractXML(const std::string& XMLdata, const std::string& key, const std::string& key_end); // Used to build the legacy global mining context after reloading projects: -MiningCPID GetMiningCPID(); extern std::string msMiningErrors; namespace { @@ -284,28 +283,14 @@ void DetectSplitCpid(const MiningProjectMap& projects) } //! -//! \brief Set up the legacy global mining context variables after reloading -//! researcher context. +//! \brief Set the global BOINC researcher context. //! -//! \param researcher Contains the context to export. +//! \param context Contains the CPID and local projects loaded from BOINC. //! -void SetLegacyResearcherContext(const Researcher& researcher) +void StoreResearcher(Researcher context) { - MiningCPID mc = GetMiningCPID(); - - mc.initialized = true; - mc.cpid = researcher.Id().ToString(); - mc.Magnitude = 0; - mc.clientversion = ""; - mc.LastPaymentTime = 0; - mc.lastblockhash = "0"; - // Reuse for debugging - mc.Organization = GetArg("-org", ""); - - GlobalCPUMiningCPID = std::move(mc); - // TODO: this belongs in presentation layer code: - switch (researcher.Status()) { + switch (context.Status()) { case ResearcherStatus::ACTIVE: msMiningErrors = _("Eligible for Research Rewards"); break; @@ -316,16 +301,6 @@ void SetLegacyResearcherContext(const Researcher& researcher) msMiningErrors = _("Staking Only - Investor Mode"); break; } -} - -//! -//! \brief Set the global BOINC researcher context. -//! -//! \param context Contains the CPID and local projects loaded from BOINC. -//! -void StoreResearcher(Researcher context) -{ - SetLegacyResearcherContext(context); std::atomic_store( &researcher, diff --git a/src/neuralnet/superblock.cpp b/src/neuralnet/superblock.cpp index aaa0462aa2..67e34d4abc 100644 --- a/src/neuralnet/superblock.cpp +++ b/src/neuralnet/superblock.cpp @@ -1506,11 +1506,21 @@ QuorumHash QuorumHash::Hash(const Superblock& superblock) QuorumHash QuorumHash::Parse(const std::string& hex) { - if (hex.size() != sizeof(uint256) * 2 && hex.size() != sizeof(Md5Sum) * 2) { - return QuorumHash(); + if (hex.size() == sizeof(uint256) * 2) { + return QuorumHash(ParseHex(hex)); } - return QuorumHash(ParseHex(hex)); + if (hex.size() == sizeof(Md5Sum) * 2) { + // This is the hash of an empty legacy superblock contract. A bug in + // previous versions caused nodes to vote for empty superblocks when + // staking a block. We can ignore any quorum hashes with this value: + // + if (hex != "d41d8cd98f00b204e9800998ecf8427e") { + return QuorumHash(ParseHex(hex)); + } + } + + return QuorumHash(); } bool QuorumHash::operator==(const QuorumHash& other) const diff --git a/src/neuralnet/superblock.h b/src/neuralnet/superblock.h index 3b05a9d3d9..be4174b305 100644 --- a/src/neuralnet/superblock.h +++ b/src/neuralnet/superblock.h @@ -236,6 +236,15 @@ class Superblock //! static constexpr uint32_t CURRENT_VERSION = 2; + //! + //! \brief The maximum allowed size of a serialized superblock in bytes. + //! + //! The bulk of the superblock data is comprised of pairs of CPIDs to + //! magnitude values. A value of 4 MB provides space for roughly 200k + //! CPIDs that accumulated a non-zero magnitude. + //! + static constexpr size_t MAX_SIZE = 4 * 1000 * 1000; + //! //! \brief Contains the CPID statistics aggregated for all projects. //! diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp index 4aa3b97d48..4dc6ccf69d 100644 --- a/src/rpcblockchain.cpp +++ b/src/rpcblockchain.cpp @@ -45,7 +45,6 @@ extern UniValue MagnitudeReport(std::string cpid); bool StrLessThanReferenceHash(std::string rh); extern std::string ExtractValue(std::string data, std::string delimiter, int pos); extern UniValue SuperblockReport(int lookback = 14, bool displaycontract = false, std::string cpid = ""); -MiningCPID GetBoincBlockByIndex(CBlockIndex* pblockindex); extern double GetSuperblockMagnitudeByCPID(std::string data, std::string cpid); std::string GetQuorumHash(const std::string& data); double GetOutstandingAmountOwed(StructCPID &mag, std::string cpid, int64_t locktime, double& total_owed, double block_magnitude); @@ -82,7 +81,6 @@ double GetTotalBalance(); double CoinToDouble(double surrogate); extern void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& entry); double LederstrumpfMagnitude2(double mag,int64_t locktime); -bool IsCPIDValidv2(MiningCPID& mc, int height); BlockFinder RPCBlockFinder; @@ -143,19 +141,75 @@ double GetBlockDifficulty(unsigned int nBits) return dDiff; } +namespace { +UniValue ClaimToJson(const NN::Claim& claim) +{ + UniValue json(UniValue::VOBJ); + + json.pushKV("version", (int)claim.m_version); + json.pushKV("mining_id", claim.m_mining_id.ToString()); + json.pushKV("client_version", claim.m_client_version); + json.pushKV("organization", claim.m_organization); + + json.pushKV("block_subsidy", claim.m_block_subsidy); + + json.pushKV("research_subsidy", claim.m_research_subsidy); + json.pushKV("magnitude", claim.m_magnitude); + json.pushKV("magnitude_unit", claim.m_magnitude_unit); + + json.pushKV("signature", EncodeBase64(claim.m_signature.data(), claim.m_signature.size())); + + json.pushKV("quorum_hash", claim.m_quorum_hash.ToString()); + json.pushKV("quorum_address", claim.m_quorum_address); + + return json; +} + +UniValue SuperblockToJson(const NN::Superblock& superblock) +{ + UniValue magnitudes(UniValue::VOBJ); + + for (const auto& cpid_pair : superblock.m_cpids) { + magnitudes.pushKV(cpid_pair.first.ToString(), cpid_pair.second); + } + + UniValue projects(UniValue::VOBJ); + + for (const auto& project_pair : superblock.m_projects) { + UniValue project(UniValue::VOBJ); + + project.pushKV("average_rac", project_pair.second.m_average_rac); + project.pushKV("rac", project_pair.second.m_rac); + project.pushKV("total_credit", project_pair.second.m_total_credit); + + projects.pushKV(project_pair.first, project); + } + + UniValue json(UniValue::VOBJ); + + json.pushKV("version", (int)superblock.m_version); + json.pushKV("magnitudes", std::move(magnitudes)); + json.pushKV("projects", std::move(projects)); + + return json; +} +} // anonymous namespace + UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool fPrintTransactionDetail) { UniValue result(UniValue::VOBJ); + result.pushKV("hash", block.GetHash().GetHex()); + CMerkleTx txGen(block.vtx[0]); txGen.SetMerkleBranch(&block); + result.pushKV("confirmations", txGen.GetDepthInMainChain()); result.pushKV("size", (int)::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION)); result.pushKV("height", blockindex->nHeight); result.pushKV("version", block.nVersion); result.pushKV("merkleroot", block.hashMerkleRoot.GetHex()); - double mint = CoinToDouble(blockindex->nMint); - result.pushKV("mint", mint); + result.pushKV("mint", CoinToDouble(blockindex->nMint)); result.pushKV("MoneySupply", blockindex->nMoneySupply); result.pushKV("time", block.GetBlockTime()); result.pushKV("nonce", (uint64_t)block.nNonce); @@ -163,22 +217,30 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool fP result.pushKV("difficulty", GetDifficulty(blockindex)); result.pushKV("blocktrust", leftTrim(blockindex->GetBlockTrust().GetHex(), '0')); result.pushKV("chaintrust", leftTrim(blockindex->nChainTrust.GetHex(), '0')); + if (blockindex->pprev) result.pushKV("previousblockhash", blockindex->pprev->GetBlockHash().GetHex()); if (blockindex->pnext) result.pushKV("nextblockhash", blockindex->pnext->GetBlockHash().GetHex()); - MiningCPID bb = DeserializeBoincBlock(block.vtx[0].hashBoinc,block.nVersion); - bool IsPoR = false; - IsPoR = (bb.Magnitude > 0 && IsResearcher(bb.cpid) && blockindex->IsProofOfStake()); + + const NN::Claim& claim = block.GetClaim(); + std::string PoRNarr = ""; - if (IsPoR) PoRNarr = "proof-of-research"; - result.pushKV("flags", - strprintf("%s%s", blockindex->IsProofOfStake()? "proof-of-stake" : "proof-of-work", blockindex->GeneratedStakeModifier()? " stake-modifier": "") + " " + PoRNarr); + if (blockindex->IsProofOfStake() && claim.HasResearchReward()) { + PoRNarr = "proof-of-research"; + } + + result.pushKV("flags", strprintf("%s%s", + blockindex->IsProofOfStake() ? "proof-of-stake" : "proof-of-work", + blockindex->GeneratedStakeModifier()? " stake-modifier": "") + " " + PoRNarr); + result.pushKV("proofhash", blockindex->hashProof.GetHex()); result.pushKV("entropybit", (int)blockindex->GetStakeEntropyBit()); result.pushKV("modifier", strprintf("%016" PRIx64, blockindex->nStakeModifier)); result.pushKV("modifierchecksum", strprintf("%08x", blockindex->nStakeModifierChecksum)); + UniValue txinfo(UniValue::VARR); + for (auto const& tx : block.vtx) { if (fPrintTransactionDetail) @@ -195,51 +257,21 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool fP } result.pushKV("tx", txinfo); + if (block.IsProofOfStake()) result.pushKV("signature", HexStr(block.vchBlockSig.begin(), block.vchBlockSig.end())); - result.pushKV("CPID", bb.cpid); - result.pushKV("Magnitude", bb.Magnitude); - if (fDebug3) result.pushKV("BoincHash",block.vtx[0].hashBoinc); - result.pushKV("LastPaymentTime",TimestampToHRDate(bb.LastPaymentTime)); - - result.pushKV("ResearchSubsidy",bb.ResearchSubsidy); - result.pushKV("ResearchAge",bb.ResearchAge); - result.pushKV("ResearchMagnitudeUnit",bb.ResearchMagnitudeUnit); - result.pushKV("ResearchAverageMagnitude",bb.ResearchAverageMagnitude); - result.pushKV("LastPORBlockHash",bb.LastPORBlockHash); - result.pushKV("Interest",bb.InterestSubsidy); - result.pushKV("GRCAddress",bb.GRCAddress); - if (!bb.BoincPublicKey.empty()) - { - result.pushKV("BoincPublicKey",bb.BoincPublicKey); - result.pushKV("BoincSignature",bb.BoincSignature); - bool fValidSig = VerifyCPIDSignature(bb.cpid, bb.lastblockhash, bb.BoincSignature); - result.pushKV("SignatureValid",fValidSig); - } - result.pushKV("ClientVersion",bb.clientversion); + result.pushKV("claim", ClaimToJson(block.GetClaim())); - bool IsCPIDValid2 = IsCPIDValidv2(bb,blockindex->nHeight); - result.pushKV("CPIDValid",IsCPIDValid2); + if (fDebug3) result.pushKV("BoincHash",block.vtx[0].hashBoinc); - result.pushKV("NeuralHash",bb.NeuralHash); - if (bb.superblock.length() > 20) - { - //12-20-2015 Support for Binary Superblocks - std::string superblock=UnpackBinarySuperblock(bb.superblock); - std::string neural_hash = GetQuorumHash(superblock); - result.pushKV("SuperblockHash", neural_hash); - result.pushKV("SuperblockUnpackedLength", (int)superblock.length()); - result.pushKV("SuperblockLength", (int)bb.superblock.length()); - bool bIsBinary = Contains(bb.superblock,""); - result.pushKV("IsBinary",bIsBinary); - if(fPrintTransactionDetail) - { - result.pushKV("SuperblockContents", superblock); - } + if (fPrintTransactionDetail && blockindex->nIsSuperBlock == 1) { + result.pushKV("superblock", SuperblockToJson(block.GetSuperblock())); } + result.pushKV("IsSuperBlock", (int)blockindex->nIsSuperBlock); result.pushKV("IsContract", (int)blockindex->nIsContract); + return result; } @@ -643,14 +675,16 @@ bool AdvertiseBeacon(std::string &sOutPrivKey, std::string &sOutPubKey, std::str sOutPrivKey = "BUG! deprecated field used"; LOCK(cs_main); { - if (!IsResearcher(GlobalCPUMiningCPID.cpid)) + const std::string primary_cpid = NN::GetPrimaryCpid(); + + if (!IsResearcher(primary_cpid)) { sError = "INVESTORS_CANNOT_SEND_BEACONS"; return false; } //If beacon is already in the chain, exit early - if (!GetBeaconPublicKey(GlobalCPUMiningCPID.cpid,true).empty()) + if (!GetBeaconPublicKey(primary_cpid, true).empty()) { // Ensure they can re-send the beacon if > 5 months old : GetBeaconPublicKey returns an empty string when > 5 months: OK. // Note that we allow the client to re-advertise the beacon in 5 months, so that they have a seamless and uninterrupted keypair in use (prevents a hacker from hijacking a keypair that is in use) @@ -676,7 +710,7 @@ bool AdvertiseBeacon(std::string &sOutPrivKey, std::string &sOutPubKey, std::str } CKey keyBeacon; - if(!GenerateBeaconKeys(GlobalCPUMiningCPID.cpid, keyBeacon)) + if(!GenerateBeaconKeys(primary_cpid, keyBeacon)) { sError = "GEN_KEY_FAIL"; return false; @@ -688,11 +722,11 @@ bool AdvertiseBeacon(std::string &sOutPrivKey, std::string &sOutPubKey, std::str std::string GRCAddress = DefaultWalletAddress(); // Public Signing Key is stored in Beacon std::string contract = "UNUSED;" + hashRand.GetHex() + ";" + GRCAddress + ";" + sOutPubKey; - LogPrintf("Creating beacon for cpid %s, %s",GlobalCPUMiningCPID.cpid, contract); + LogPrintf("Creating beacon for cpid %s, %s",primary_cpid, contract); std::string sBase = EncodeBase64(contract); std::string sAction = "add"; std::string sType = "beacon"; - std::string sName = GlobalCPUMiningCPID.cpid; + std::string sName = primary_cpid; try { // Backup config with old keys like a normal backup @@ -941,7 +975,8 @@ UniValue advertisebeacon(const UniValue& params, bool fHelp) * nothing to import. This saves a migrating users from copy-pasting * the key string to importprivkey command. */ - bool importResult= ImportBeaconKeysFromConfig(GlobalCPUMiningCPID.cpid, pwalletMain); + const std::string primary_cpid = NN::GetPrimaryCpid(); + bool importResult= ImportBeaconKeysFromConfig(primary_cpid, pwalletMain); res.pushKV("ConfigKeyImported", importResult); std::string sOutPubKey = ""; @@ -951,7 +986,7 @@ UniValue advertisebeacon(const UniValue& params, bool fHelp) bool fResult = AdvertiseBeacon(sOutPrivKey,sOutPubKey,sError,sMessage); res.pushKV("Result", fResult ? "SUCCESS" : "FAIL"); - res.pushKV("CPID",GlobalCPUMiningCPID.cpid.c_str()); + res.pushKV("CPID",primary_cpid.c_str()); res.pushKV("Message",sMessage.c_str()); if (!sError.empty()) @@ -1130,7 +1165,8 @@ UniValue explainmagnitude(const UniValue& params, bool fHelp) // First try local node before bothering network... - std::string sNeuralResponse = NN::GetInstance()->ExplainMagnitude(GlobalCPUMiningCPID.cpid); + const std::string primary_cpid = NN::GetPrimaryCpid(); + std::string sNeuralResponse = NN::GetInstance()->ExplainMagnitude(primary_cpid); if (sNeuralResponse.length() < 25) { @@ -1143,7 +1179,7 @@ UniValue explainmagnitude(const UniValue& params, bool fHelp) msNeuralResponse = ""; - AsyncNeuralRequest("explainmag", GlobalCPUMiningCPID.cpid, 10); + AsyncNeuralRequest("explainmag", primary_cpid, 10); } } @@ -1334,8 +1370,8 @@ UniValue staketime(const UniValue& params, bool fHelp) LOCK2(cs_main, pwalletMain->cs_wallet); - std::string cpid = GlobalCPUMiningCPID.cpid; - std::string GRCAddress = DefaultWalletAddress(); + const std::string cpid = NN::GetPrimaryCpid(); + const std::string GRCAddress = DefaultWalletAddress(); GetEarliestStakeTime(GRCAddress, cpid); res.pushKV("GRCTime", ReadCache(Section::GLOBAL, "nGRCTime").timestamp); @@ -2315,28 +2351,25 @@ UniValue SuperblockReport(int lookback, bool displaycontract, std::string cpid) if (!pblockindex->IsInMainChain()) continue; if (IsSuperBlock(pblockindex)) { - MiningCPID bb = GetBoincBlockByIndex(pblockindex); - if (bb.superblock.length() > 20) + const NN::ClaimOption claim = GetClaimByIndex(pblockindex); + + if (claim && claim->ContainsSuperblock()) { - double out_beacon_count = 0; - double out_participant_count = 0; - double out_avg = 0; - // Binary Support 12-20-2015 - std::string superblock = UnpackBinarySuperblock(bb.superblock); - GetSuperblockAvgMag(superblock,out_beacon_count,out_participant_count,out_avg,true,pblockindex->nHeight); + const NN::Superblock& superblock = claim->m_superblock; UniValue c(UniValue::VOBJ); c.pushKV("Block #" + ToString(pblockindex->nHeight),pblockindex->GetBlockHash().GetHex()); c.pushKV("Date",TimestampToHRDate(pblockindex->nTime)); - c.pushKV("Average Mag",out_avg); - c.pushKV("Wallet Version",bb.clientversion); - double mag = GetSuperblockMagnitudeByCPID(superblock, cpid); - if (!cpid.empty()) + c.pushKV("Average Mag", superblock.m_cpids.AverageMagnitude()); + c.pushKV("Wallet Version", claim->m_client_version); + + if (const NN::CpidOption cpid_parsed = NN::MiningId::Parse(cpid).TryCpid()) { - c.pushKV("Magnitude",mag); + c.pushKV("Magnitude", superblock.m_cpids.MagnitudeOf(*cpid_parsed)); } + if (displaycontract) - c.pushKV("Contract Contents: ", superblock); + c.pushKV("Contract Contents", SuperblockToJson(superblock)); results.push_back(c); } @@ -2386,7 +2419,7 @@ UniValue MagnitudeReport(std::string cpid) int64_t nTime = GetAdjustedTime(); double dMagnitudeUnit = GRCMagnitudeUnit(nTime); double dAccrualAge, AvgMagnitude; - int64_t nBoinc = ComputeResearchAccrual(nTime, structMag.cpid, "", pindexBest, false, 69, dAccrualAge, dMagnitudeUnit, AvgMagnitude); + int64_t nBoinc = ComputeResearchAccrual(nTime, structMag.cpid, pindexBest, false, 69, dAccrualAge, dMagnitudeUnit, AvgMagnitude); entry.pushKV("Owed", nBoinc / (double)COIN); } entry.pushKV("Daily Paid",structMag.payments/14); diff --git a/src/rpcdataacq.cpp b/src/rpcdataacq.cpp index 1e9fb6592f..c262108f98 100644 --- a/src/rpcdataacq.cpp +++ b/src/rpcdataacq.cpp @@ -11,6 +11,8 @@ #include "txdb.h" #include "beacon.h" #include "appcache.h" +#include "neuralnet/claim.h" +#include "neuralnet/superblock.h" #include "util.h" #include @@ -137,19 +139,19 @@ UniValue rpc_getblockstats(const UniValue& params, bool fHelp) transactioncount+=txcountinblock; emptyblockscount+=(txcountinblock==0); c_blockversion[block.nVersion]++; - MiningCPID bb = DeserializeBoincBlock(block.vtx[0].hashBoinc, block.nVersion); - c_cpid[bb.cpid]++; - c_org[bb.Organization]++; - c_version[bb.clientversion]++; - researchtotal+=bb.ResearchSubsidy; - interesttotal+=bb.InterestSubsidy; - researchcount+=(bb.ResearchSubsidy>0.001); + const NN::Claim claim = block.GetClaim(); + c_cpid[claim.m_mining_id.ToString()]++; + c_org[claim.m_organization]++; + c_version[claim.m_client_version]++; + researchtotal += claim.m_research_subsidy; + interesttotal += claim.m_block_subsidy; + researchcount += (claim.m_research_subsidy > 0.001); minttotal+=cur->nMint; unsigned sizeblock = GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION); size_min_blk=std::min(size_min_blk,sizeblock); size_max_blk=std::max(size_max_blk,sizeblock); size_sum_blk+=sizeblock; - super_count += (bb.superblock.length()>20); + super_count += (claim.ContainsSuperblock()); } { @@ -324,20 +326,24 @@ UniValue rpc_getsupervotes(const UniValue& params, bool fHelp) if(!block.ReadFromDisk(pStart->nFile,pStart->nBlockPos,true)) throw runtime_error("failed to read block"); //assert(block.vtx.size() > 0); - MiningCPID bb = DeserializeBoincBlock(block.vtx[0].hashBoinc, block.nVersion); + const NN::Claim claim = block.GetClaim(); + const NN::Superblock& sb = claim.m_superblock; + info.pushKV("block_hash",pStart->GetBlockHash().GetHex()); info.pushKV("height",pStart->nHeight); - info.pushKV("neuralhash", bb.NeuralHash ); - std::string superblock = UnpackBinarySuperblock(bb.superblock); - std::string neural_hash = GetQuorumHash(superblock); - info.pushKV("contract_size", (int64_t)superblock.size() ); - info.pushKV("packed_size", (int64_t)bb.superblock.size() ); - info.pushKV("contract_hash", neural_hash ); + info.pushKV("neuralhash", claim.m_quorum_hash.ToString()); + + if (sb.m_version == 1) { + info.pushKV("packed_size", (int64_t)sb.PackLegacy().size()); + } else { + info.pushKV("packed_size", (int64_t)GetSerializeSize(sb, 1, 1)); + } + + info.pushKV("contract_hash", NN::QuorumHash::Hash(sb).ToString()); result1.pushKV("info", info ); } UniValue votes(UniValue::VOBJ); - std::map tally; long blockcount=0; long maxblocks= 200; @@ -360,9 +366,9 @@ UniValue rpc_getsupervotes(const UniValue& params, bool fHelp) if(!block.ReadFromDisk(cur->nFile,cur->nBlockPos,true)) throw runtime_error("failed to read block"); //assert(block.vtx.size() > 0); - MiningCPID bb = DeserializeBoincBlock(block.vtx[0].hashBoinc, block.nVersion); + const NN::Claim& claim = block.GetClaim(); - if(bb.NeuralHash.empty()) + if(!claim.m_quorum_hash.Valid()) continue; uint64_t stakeout = 0; @@ -379,30 +385,27 @@ UniValue rpc_getsupervotes(const UniValue& params, bool fHelp) if (distance < 40) multiplier = 400; double weight = (1.0/distance)*multiplier; - /* Tally votes */ - tally[bb.NeuralHash] += weight; - if(mode==0) { std::string line - = bb.NeuralHash + = claim.m_quorum_hash.ToString() + "|"+RoundToString(weight/10.0,5) - + "|"+bb.Organization - + "|"+bb.clientversion + + "|"+claim.m_organization + + "|"+claim.m_client_version + "|"+RoundToString(diff,3) + "|"+RoundToString(delta,0) - + "|"+bb.cpid + + "|"+claim.m_mining_id.ToString() ; votes.pushKV(ToString(cur->nHeight), line ); } else { UniValue result2(UniValue::VOBJ); - result2.pushKV("neuralhash", bb.NeuralHash ); + result2.pushKV("neuralhash", claim.m_quorum_hash.ToString()); result2.pushKV("weight", weight ); result2.pushKV("cpid", cur->GetCPID() ); - result2.pushKV("organization", bb.Organization ); - result2.pushKV("cversion", bb.clientversion ); + result2.pushKV("organization", claim.m_organization); + result2.pushKV("cversion", claim.m_client_version); if(mode>=2) { result2.pushKV("difficulty", diff ); @@ -518,19 +521,18 @@ UniValue rpc_exportstats(const UniValue& params, bool fHelp) min_size=std::min(min_size,i_size); max_size=std::max(max_size,i_size); - const MiningCPID bb = DeserializeBoincBlock(block.vtx[0].hashBoinc, block.nVersion); - cnt_neuralvote += (bb.NeuralHash.size()>0); - if( bb.CurrentNeuralHash.size()>0 - && bb.CurrentNeuralHash != "d41d8cd98f00b204e9800998ecf8427e" - && bb.CurrentNeuralHash != "TOTAL_VOTES" ) + const NN::Claim& claim = block.GetClaim(); + cnt_neuralvote += (claim.m_quorum_hash.Valid()); + if (claim.m_quorum_hash.Valid() + && claim.m_quorum_hash != "d41d8cd98f00b204e9800998ecf8427e") { cnt_neuralcurr += 1; } - const double i_research = bb.ResearchSubsidy; + const double i_research = claim.m_research_subsidy; sum_research= sum_research + i_research; max_research=std::max(max_research,i_research); - const double i_interest = bb.InterestSubsidy; + const double i_interest = claim.m_block_subsidy; sum_interest= sum_interest + i_interest; max_interest=std::max(max_interest,i_interest); @@ -698,28 +700,28 @@ UniValue rpc_getrecentblocks(const UniValue& params, bool fHelp) if(!block.ReadFromDisk(cur->nFile,cur->nBlockPos,true)) throw runtime_error("failed to read block"); //assert(block.vtx.size() > 0); - MiningCPID bb = DeserializeBoincBlock(block.vtx[0].hashBoinc, block.nVersion); + const NN::Claim& claim = block.GetClaim(); if(detail<100) { if(detail>=20) { - line+="<|>"+bb.Organization - + "<|>"+bb.clientversion + line+="<|>"+claim.m_organization + + "<|>"+claim.m_client_version + "<|>"+ToString(block.vtx.size()-2); } if(detail==21) { - line+="<|>"+bb.cpid - + "<|>"+(bb.NeuralHash.empty()? "--" : bb.NeuralHash); + line+="<|>"+claim.m_mining_id.ToString() + + "<|>"+(claim.m_quorum_hash.Valid() ? claim.m_quorum_hash.ToString() : "--"); } } else { - result2.pushKV("organization", bb.Organization ); - result2.pushKV("cversion", bb.clientversion ); - result2.pushKV("neuralhash", bb.NeuralHash ); - result2.pushKV("superblocksize", bb.NeuralHash ); + result2.pushKV("organization", claim.m_organization); + result2.pushKV("cversion", claim.m_client_version); + result2.pushKV("neuralhash", claim.m_quorum_hash.ToString()); + result2.pushKV("superblocksize", claim.m_quorum_hash.ToString()); result2.pushKV("vtxsz", (int64_t)block.vtx.size() ); } } diff --git a/src/rpcmining.cpp b/src/rpcmining.cpp index 11cc865b6e..23173c91a0 100644 --- a/src/rpcmining.cpp +++ b/src/rpcmining.cpp @@ -127,7 +127,7 @@ UniValue getmininginfo(const UniValue& params, bool fHelp) { double dMagnitudeUnit = GRCMagnitudeUnit(nTime); double dAccrualAge,AvgMagnitude; - int64_t nBoinc = ComputeResearchAccrual(nTime, primary_cpid, "getmininginfo", pindexBest, false, 69, dAccrualAge, dMagnitudeUnit, AvgMagnitude); + int64_t nBoinc = ComputeResearchAccrual(nTime, primary_cpid, pindexBest, false, 69, dAccrualAge, dMagnitudeUnit, AvgMagnitude); obj.pushKV("Magnitude Unit",dMagnitudeUnit); obj.pushKV("BoincRewardPending",nBoinc/(double)COIN); } diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp index b870164fa0..722f0bc5a5 100755 --- a/src/rpcrawtransaction.cpp +++ b/src/rpcrawtransaction.cpp @@ -55,35 +55,31 @@ std::vector> GetTxStakeBoincHashInfo(const C } } - //Deserialize - MiningCPID bb = DeserializeBoincBlock(block.vtx[0].hashBoinc,block.nVersion); + const NN::Claim& claim = block.GetClaim(); res.push_back(std::make_pair(_("Height"), ToString(pindex->nHeight))); res.push_back(std::make_pair(_("Block Version"), ToString(block.nVersion))); res.push_back(std::make_pair(_("Difficulty"), RoundToString(GetBlockDifficulty(block.nBits),8))); - res.push_back(std::make_pair(_("CPID"), bb.cpid)); - res.push_back(std::make_pair(_("Interest"), RoundToString(bb.InterestSubsidy,8))); + res.push_back(std::make_pair(_("CPID"), claim.m_mining_id.ToString())); + res.push_back(std::make_pair(_("Interest"), RoundToString(claim.m_block_subsidy, 8))); - if (bb.ResearchAverageMagnitude > 0) + if (claim.m_magnitude > 0) { - res.push_back(std::make_pair(_("Boinc Reward"), RoundToString(bb.ResearchSubsidy,8))); - res.push_back(std::make_pair(_("Magnitude"), RoundToString(bb.Magnitude,8))); - res.push_back(std::make_pair(_("Average Magnitude"), RoundToString(bb.ResearchAverageMagnitude, 8))); - res.push_back(std::make_pair(_("Research Age"), RoundToString(bb.ResearchAge, 8))); + res.push_back(std::make_pair(_("Boinc Reward"), RoundToString(claim.m_research_subsidy, 8))); + res.push_back(std::make_pair(_("Magnitude"), RoundToString(claim.m_magnitude, 8))); } - res.push_back(std::make_pair(_("Is Superblock"), (bb.superblock.length() >= 20 ? "Yes" : "No"))); + res.push_back(std::make_pair(_("Is Superblock"), (claim.ContainsSuperblock() ? "Yes" : "No"))); if(fDebug) { - if (bb.superblock.length() >= 20) - res.push_back(std::make_pair(_("Neural Contract Binary Size"), ToString(bb.superblock.length()))); - - res.push_back(std::make_pair(_("Neural Hash"), bb.NeuralHash)); - res.push_back(std::make_pair(_("Current Neural Hash"), bb.CurrentNeuralHash)); - res.push_back(std::make_pair(_("Client Version"), bb.clientversion)); - res.push_back(std::make_pair(_("Organization"), bb.Organization)); - res.push_back(std::make_pair(_("Boinc Public Key"), bb.BoincPublicKey)); + if (claim.ContainsSuperblock()) + res.push_back(std::make_pair(_("Neural Contract Binary Size"), ToString(GetSerializeSize(claim.m_superblock, 1, 1)))); + + res.push_back(std::make_pair(_("Neural Hash"), claim.m_quorum_hash.ToString())); + res.push_back(std::make_pair(_("Current Neural Hash"), claim.m_quorum_hash.ToString())); + res.push_back(std::make_pair(_("Client Version"), claim.m_client_version)); + res.push_back(std::make_pair(_("Organization"), claim.m_organization)); } return res; @@ -256,39 +252,10 @@ std::vector> GetTxNormalBoincHashInfo(const if (pblockindex && pblockindex->nIsSuperBlock) { block.ReadFromDisk(pblockindex); + const NN::Superblock& superblock = block.GetSuperblock(); - std::string sHashBoinc = block.vtx[0].hashBoinc; - - MiningCPID vbb = DeserializeBoincBlock(sHashBoinc, block.nVersion); - - std::string sUnpackedSuperblock = UnpackBinarySuperblock(vbb.superblock); - std::string sMagnitudeContract = ExtractXML(sUnpackedSuperblock, "", ""); - - // Since Superblockavg function gives avg for mags yes but total cpids we cannot use this function - // We need the rows_above_zero for Total Network Magnitude calculation with Money Supply Factor. - std::vector vMagnitudeContract = split(sMagnitudeContract, ";"); - int nRowsWithMag = 0; - double dTotalMag = 0; - - for (auto const& sMag : vMagnitudeContract) - { - const std::vector& vFields = split(sMag, ","); - - if (vFields.size() < 2) - continue; - - const std::string& sCPID = vFields[0]; - double dMAG = std::stoi(vFields[1].c_str()); - - if (sCPID.length() > 10) - { - nRowsWithMag++; - dTotalMag += dMAG; - } - } - - double dOutAverage = dTotalMag / ((double)nRowsWithMag + .01); - double dTotalNetworkMagnitude = (double)nRowsWithMag * dOutAverage; + double dOutAverage = superblock.m_cpids.AverageMagnitude(); + double dTotalNetworkMagnitude = (double)superblock.m_cpids.size() * dOutAverage; double dMoneySupply = DoubleFromAmount(pblockindex->nMoneySupply); double dMoneySupplyFactor = (dMoneySupply/dTotalNetworkMagnitude + .01); diff --git a/src/scraper/scraper.cpp b/src/scraper/scraper.cpp index 865743d930..fb25d94d01 100755 --- a/src/scraper/scraper.cpp +++ b/src/scraper/scraper.cpp @@ -23,6 +23,8 @@ #include #include +namespace NN { std::string GetPrimaryCpid(); } + // These are initialized empty. GetDataDir() cannot be called here. It is too early. fs::path pathDataDir = {}; fs::path pathScraper = {}; @@ -3249,10 +3251,9 @@ std::string ExplainMagnitude(std::string sCPID) // "Signature" // The magic version number of 430 from .NET is there for compatibility with the old NN protocol. - // TODO: Should we take a lock on cs_main to read GlobalCPUMiningCPID? out.append("NN Host Version: 430, "); out.append("NeuralHash: " + ConvergedScraperStatsCache.sContractHash + ", "); - out.append("SignatureCPID: " + GlobalCPUMiningCPID.cpid + ", "); + out.append("SignatureCPID: " + NN::GetPrimaryCpid() + ", "); out.append("Time: " + DateTimeStrFormat("%x %H:%M:%S", GetAdjustedTime()) + ""); //Totals diff --git a/src/serialize.h b/src/serialize.h index 57a37ee2a7..973103c1ac 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -179,6 +179,7 @@ enum // modifiers SER_SKIPSIG = (1 << 16), SER_BLOCKHEADERONLY = (1 << 17), + SER_SKIPSUPERBLOCK = (1 << 18), }; //! Convert the reference base type to X, without changing constness or reference type. diff --git a/src/test/neuralnet/claim_tests.cpp b/src/test/neuralnet/claim_tests.cpp new file mode 100644 index 0000000000..165d8353b4 --- /dev/null +++ b/src/test/neuralnet/claim_tests.cpp @@ -0,0 +1,715 @@ +#include "neuralnet/claim.h" + +#include "key.h" +#include "streams.h" + +#include +#include +#include + +namespace { +//! +//! \brief Get a complete, valid claim for an investor. +//! +//! \return Investor fields initialized except the superblock-related fields. +//! +NN::Claim GetInvestorClaim() +{ + NN::Claim claim; + + claim.m_mining_id = NN::MiningId::ForInvestor(); + claim.m_client_version = "v4.0.4.6-unk"; + claim.m_organization = "Example Org"; + claim.m_block_subsidy = 10.0; + + return claim; +} + +//! +//! \brief Get a complete, valid claim for a researcher. +//! +//! \return Researcher fields initialized except the superblock-related fields. +//! +NN::Claim GetResearcherClaim() +{ + NN::Claim claim; + + claim.m_mining_id = NN::Cpid::Parse("00010203040506070809101112131415"); + claim.m_client_version = "v4.0.4.6-unk"; + claim.m_organization = "Example Org"; + claim.m_block_subsidy = 10.0; + claim.m_research_subsidy = 123.456; + claim.m_magnitude = 123; + claim.m_magnitude_unit = 0.123456; + claim.m_signature = { + 0x7b, 0x85, 0xc8, 0x3c, 0x92, 0xd9, 0x74, 0x8e, + 0xa3, 0xd2, 0x26, 0x16, 0x6f, 0x9a, 0x00, 0x6c, + 0x6f, 0x0a, 0x97, 0x97, 0xa9, 0x3a, 0x52, 0xd0, + 0xb9, 0x4f, 0xbb, 0x29, 0x61, 0xbe, 0xd5, 0xcc, + }; + + return claim; +} + +//! +//! \brief Get a basic superblock to use for testing. +//! +//! \return A superblock with one CPID/magnitude pair and one project. +//! +NN::Superblock GetTestSuperblock(uint32_t version = NN::Superblock::CURRENT_VERSION) +{ + NN::Superblock superblock; + + superblock.m_version = version; + superblock.m_cpids.Add(NN::Cpid(), 123); + superblock.m_projects.Add("project", NN::Superblock::ProjectStats()); + + return superblock; +} + +//! +//! \brief Create a valid private key for tests. +//! +//! \return This is actually the shared message private key. +//! +static CKey GetTestPrivateKey() +{ + std::vector private_key = ParseHex( + "308201130201010420fbd45ffb02ff05a3322c0d77e1e7aea264866c24e81e5ab6" + "a8e150666b4dc6d8a081a53081a2020101302c06072a8648ce3d0101022100ffff" + "fffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f300604" + "010004010704410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959" + "f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47" + "d08ffb10d4b8022100fffffffffffffffffffffffffffffffebaaedce6af48a03b" + "bfd25e8cd0364141020101a144034200044b2938fbc38071f24bede21e838a0758" + "a52a0085f2e034e7f971df445436a252467f692ec9c5ba7e5eaa898ab99cbd9949" + "496f7e3cafbf56304b1cc2e5bdf06e"); + + CKey key; + key.SetPrivKey(CPrivKey(private_key.begin(), private_key.end())); + + return key; +} +} // anonymous namespace + +// ----------------------------------------------------------------------------- +// Claim +// ----------------------------------------------------------------------------- + +BOOST_AUTO_TEST_SUITE(Claim) + +BOOST_AUTO_TEST_CASE(it_initializes_to_an_empty_claim) +{ + const NN::Claim claim; + + BOOST_CHECK(claim.m_version == NN::Claim::CURRENT_VERSION); + BOOST_CHECK(claim.m_mining_id.Valid() == false); + BOOST_CHECK(claim.m_client_version.empty() == true); + BOOST_CHECK(claim.m_organization.empty() == true); + + BOOST_CHECK(claim.m_block_subsidy == 0.0); + + BOOST_CHECK(claim.m_magnitude == 0); + BOOST_CHECK(claim.m_research_subsidy == 0.0); + BOOST_CHECK(claim.m_magnitude_unit == 0.0); + + BOOST_CHECK(claim.m_signature.empty() == true); + + BOOST_CHECK(claim.m_quorum_hash.Valid() == false); + BOOST_CHECK(claim.m_quorum_address.empty() == true); + BOOST_CHECK(claim.m_superblock.m_cpids.empty() == true); +} + +BOOST_AUTO_TEST_CASE(it_initializes_to_the_specified_version) +{ + const NN::Claim claim(1); + + BOOST_CHECK(claim.m_version == 1); + BOOST_CHECK(claim.m_mining_id.Valid() == false); + BOOST_CHECK(claim.m_client_version.empty() == true); + BOOST_CHECK(claim.m_organization.empty() == true); + + BOOST_CHECK(claim.m_block_subsidy == 0.0); + + BOOST_CHECK(claim.m_magnitude == 0); + BOOST_CHECK(claim.m_research_subsidy == 0.0); + BOOST_CHECK(claim.m_magnitude_unit == 0.0); + + BOOST_CHECK(claim.m_signature.empty() == true); + + BOOST_CHECK(claim.m_quorum_hash.Valid() == false); + BOOST_CHECK(claim.m_quorum_address.empty() == true); + BOOST_CHECK(claim.m_superblock.m_cpids.empty() == true); +} + +BOOST_AUTO_TEST_CASE(it_parses_a_legacy_boincblock_string_for_researcher) +{ + const NN::Cpid cpid = NN::Cpid::Parse("00010203040506070809101112131415"); + const std::string quorum_address = "mk8PmpcTGLCZky8YqFHEEwXs5px3hGfQBG"; + + const NN::Superblock superblock = GetTestSuperblock(1); + + // Legacy claims only contain MD5 quorum hashes: + const NN::QuorumHash quorum_hash(NN::QuorumHash::Md5Sum { + 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, + 0x08, 0x09, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, + }); + + const std::vector signature { + 0x7b, 0x85, 0xc8, 0x3c, 0x92, 0xd9, 0x74, 0x8e, + 0xa3, 0xd2, 0x26, 0x16, 0x6f, 0x9a, 0x00, 0x6c, + 0x6f, 0x0a, 0x97, 0x97, 0xa9, 0x3a, 0x52, 0xd0, + 0xb9, 0x4f, 0xbb, 0x29, 0x61, 0xbe, 0xd5, 0xcc, + }; + + const std::string sig64 = EncodeBase64(signature.data(), signature.size()); + + const NN::Claim claim = NN::Claim::Parse( + cpid.ToString() + // Mining ID + "<|>" // Project name (obsolete) + "<|>" // AES Skein (obsolete) + "<|>" // Recent average credit (obsolete) + "<|>" // Proof-of-BOINC difficulty (obsolete) + "<|>" // Difficulty bytes (obsolete) + "<|>" // Encrypted CPID (obsolete) + "<|>" // For encrypted CPIDs? (obsolete) + "<|>" // Nonce (obsolete) + "<|>" // Network RAC (obsolete) + "<|>v4.0.4.6-unk" // Client version + "<|>47.24638888" // Research subsidy + "<|>" // Last payment time (obsolete) + "<|>" // RSA weight (obsolete) + "<|>" // CPID "v2" (obsolete) + "<|>123" // Magnitude + "<|>" + quorum_address + // Quorum address + "<|>" // Last block hash (obsolete) + "<|>10.00000000" // Block subsidy + "<|>Example Org" // Organization + "<|>" // Organization key (obsolete) + "<|>" + quorum_hash.ToString() + // Neural hash + "<|>" + superblock.PackLegacy() +// Superblock + "<|>" // Research subsidy 2 (obsolete) + "<|>" // Research age (obsolete) + "<|>0.123456" // Magnitude unit + "<|>" // Average magnitude (obsolete) + "<|>" // Last PoR block hash (obsolete) + "<|>" + quorum_hash.ToString() + // Current Neural hash (obsolete) + "<|>" // Public key (obsolete) + "<|>" + sig64, // Beacon signature + 8 // block version + ); + + // Legacy string claims (BoincBlocks) always parse to version 1: + BOOST_CHECK(claim.m_version == 1); + BOOST_CHECK(claim.WellFormed() == true); + + BOOST_CHECK(claim.m_mining_id == cpid); + BOOST_CHECK(claim.m_client_version == "v4.0.4.6-unk"); + BOOST_CHECK(claim.m_organization == "Example Org"); + + BOOST_CHECK(claim.m_block_subsidy == 10.0); + + BOOST_CHECK(claim.m_magnitude == 123); + BOOST_CHECK(claim.m_research_subsidy == 47.25); + BOOST_CHECK(claim.m_magnitude_unit == 0.123456); + + BOOST_CHECK(claim.m_signature == signature); + + BOOST_CHECK(claim.m_quorum_hash == quorum_hash); + BOOST_CHECK(claim.m_quorum_address == quorum_address); + BOOST_CHECK(claim.m_superblock.GetHash() == superblock.GetHash()); +} + +BOOST_AUTO_TEST_CASE(it_determines_whether_a_claim_is_well_formed) +{ + const NN::Claim claim = GetInvestorClaim(); + + BOOST_CHECK(claim.WellFormed() == true); +} + +BOOST_AUTO_TEST_CASE(it_validates_client_version_string_length) +{ + NN::Claim claim = GetInvestorClaim(); + + // 31 characters (max valid: 30) + claim.m_client_version.resize(31, 'x'); + + BOOST_CHECK(claim.WellFormed() == false); +} + +BOOST_AUTO_TEST_CASE(it_validates_organization_string_length) +{ + NN::Claim claim = GetInvestorClaim(); + + // 51 characters (max valid: 50) + claim.m_organization.resize(51, 'x'); + + BOOST_CHECK(claim.WellFormed() == false); +} + +BOOST_AUTO_TEST_CASE(it_determines_whether_it_is_a_research_reward_claim) +{ + NN::Claim claim = GetResearcherClaim(); + + BOOST_CHECK(claim.HasResearchReward() == true); + + claim = GetInvestorClaim(); + + BOOST_CHECK(claim.HasResearchReward() == false); +} + +BOOST_AUTO_TEST_CASE(it_determines_whether_it_contains_a_superblock) +{ + NN::Claim claim = GetInvestorClaim(); + + BOOST_CHECK(claim.ContainsSuperblock() == false); + + claim.m_superblock = GetTestSuperblock(); + + BOOST_CHECK(claim.ContainsSuperblock() == true); +} + +BOOST_AUTO_TEST_CASE(it_sums_the_block_and_research_reward_subsidies) +{ + NN::Claim claim = GetInvestorClaim(); + + BOOST_CHECK(claim.TotalSubsidy() == 10.0); + + claim = GetResearcherClaim(); + + // Legacy double format of subsidy fields can cause floating-point errors: + BOOST_CHECK(std::round(claim.TotalSubsidy() * 1000.0) / 1000.0 == 133.456); +} + +BOOST_AUTO_TEST_CASE(it_signs_itself_with_the_supplied_beacon_private_key) +{ + NN::Claim claim = GetResearcherClaim(); + + const uint256 last_block_hash(0); + CKey private_key = GetTestPrivateKey(); + + BOOST_CHECK(claim.Sign(private_key, last_block_hash) == true); + + const uint256 hashed = Hash( + claim.m_mining_id.TryCpid().get().Raw().begin(), + claim.m_mining_id.TryCpid().get().Raw().end(), + last_block_hash.begin(), + last_block_hash.end()); + + private_key = GetTestPrivateKey(); + + BOOST_CHECK(private_key.Verify(hashed, claim.m_signature)); +} + +BOOST_AUTO_TEST_CASE(it_refuses_to_sign_itself_with_an_invalid_private_key) +{ + NN::Claim claim = GetResearcherClaim(); + + CKey private_key; + uint256 last_block_hash(0); + + BOOST_CHECK(claim.Sign(private_key, last_block_hash) == false); + BOOST_CHECK(claim.m_signature.empty() == true); +} + +BOOST_AUTO_TEST_CASE(it_refuses_to_sign_an_investor_claim) +{ + NN::Claim claim = GetInvestorClaim(); + + const uint256 last_block_hash(0); + CKey private_key = GetTestPrivateKey(); + + BOOST_CHECK(claim.Sign(private_key, last_block_hash) == false); + BOOST_CHECK(claim.m_signature.empty() == true); +} + +BOOST_AUTO_TEST_CASE(it_verifies_a_signature_for_a_research_reward_claim) +{ + NN::Claim claim = GetResearcherClaim(); + + const uint256 last_block_hash(0); + CKey private_key = GetTestPrivateKey(); + + const uint256 hashed = Hash( + claim.m_mining_id.TryCpid().get().Raw().begin(), + claim.m_mining_id.TryCpid().get().Raw().end(), + last_block_hash.begin(), + last_block_hash.end()); + + private_key.Sign(hashed, claim.m_signature); + + BOOST_CHECK(claim.VerifySignature(private_key.GetPubKey(), last_block_hash)); +} + +BOOST_AUTO_TEST_CASE(it_generates_a_hash_for_an_investor_claim) +{ + NN::Claim claim = GetInvestorClaim(); + + CHashWriter hasher(SER_GETHASH, claim.m_version); + + hasher << claim.m_version + << claim.m_mining_id + << claim.m_client_version + << claim.m_organization + << NN::VarDouble(claim.m_block_subsidy) + << claim.m_quorum_hash; + + BOOST_CHECK(claim.GetHash() == hasher.GetHash()); +} + +BOOST_AUTO_TEST_CASE(it_generates_a_hash_for_a_research_reward_claim) +{ + NN::Claim claim = GetResearcherClaim(); + + CHashWriter hasher(SER_GETHASH, claim.m_version); + + hasher << claim.m_version + << claim.m_mining_id + << claim.m_client_version + << claim.m_organization + << NN::VarDouble(claim.m_block_subsidy) + << NN::VarDouble(claim.m_research_subsidy) + << claim.m_magnitude + << NN::VarDouble(claim.m_magnitude_unit) + << claim.m_signature + << claim.m_quorum_hash; + + BOOST_CHECK(claim.GetHash() == hasher.GetHash()); +} + +BOOST_AUTO_TEST_CASE(it_represents_itself_as_a_legacy_boincblock_string) +{ + const NN::Cpid cpid = NN::Cpid::Parse("00010203040506070809101112131415"); + const std::string quorum_address = "mk8PmpcTGLCZky8YqFHEEwXs5px3hGfQBG"; + + const NN::Superblock superblock = GetTestSuperblock(1); + + // Legacy claims only contain MD5 quorum hashes: + const NN::QuorumHash quorum_hash(NN::QuorumHash::Md5Sum { + 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, 0x09, 0x08, + 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00 + }); + + const std::vector signature { + 0x7b, 0x85, 0xc8, 0x3c, 0x92, 0xd9, 0x74, 0x8e, + 0xa3, 0xd2, 0x26, 0x16, 0x6f, 0x9a, 0x00, 0x6c, + 0x6f, 0x0a, 0x97, 0x97, 0xa9, 0x3a, 0x52, 0xd0, + 0xb9, 0x4f, 0xbb, 0x29, 0x61, 0xbe, 0xd5, 0xcc, + }; + + const std::string sig64 = EncodeBase64(signature.data(), signature.size()); + + NN::Claim claim; + + claim.m_mining_id = cpid; + claim.m_client_version = "v4.0.4.6-unk"; + claim.m_organization = "Example Org"; + claim.m_block_subsidy = 10.0; + claim.m_research_subsidy = 47.24638888; + claim.m_magnitude = 123; + claim.m_magnitude_unit = 0.123456; + claim.m_signature = signature; + claim.m_quorum_hash = quorum_hash; + claim.m_quorum_address = quorum_address; + claim.m_superblock = superblock; + + BOOST_CHECK(claim.ToString(8) == + cpid.ToString() + // Mining ID + "<|>" // Project name (obsolete) + "<|>" // AES Skein (obsolete) + "<|>" // Recent average credit (obsolete) + "<|>" // Proof-of-BOINC difficulty (obsolete) + "<|>" // Difficulty bytes (obsolete) + "<|>" // Encrypted CPID (obsolete) + "<|>" // For encrypted CPIDs? (obsolete) + "<|>" // Nonce (obsolete) + "<|>" // Network RAC (obsolete) + "<|>v4.0.4.6-unk" // Client version + "<|>47.24638888" // Research subsidy + "<|>" // Last payment time (obsolete) + "<|>" // RSA weight (obsolete) + "<|>" // CPID "v2" (obsolete) + "<|>123" // Magnitude + "<|>" + quorum_address + // Quorum address + "<|>0" // Last block hash (obsolete) + "<|>10.00000000" // Block subsidy + "<|>Example Org" // Organization + "<|>" // Organization key (obsolete) + "<|>" + quorum_hash.ToString() + // Neural hash + "<|>" + superblock.PackLegacy() +// Superblock + "<|>" // Research subsidy 2 (obsolete) + "<|>" // Research age (obsolete) + "<|>0.123456" // Magnitude unit + "<|>" // Average magnitude (obsolete) + "<|>" // Last PoR block hash (obsolete) + "<|>" // Current Neural hash (obsolete) + "<|>" // Public key (obsolete) + "<|>" + sig64 // Beacon signature + ); +} + +BOOST_AUTO_TEST_CASE(it_serializes_to_a_stream_for_investor) +{ + NN::Claim claim = GetInvestorClaim(); + + CDataStream expected(SER_NETWORK, PROTOCOL_VERSION); + + expected << claim.m_version + << claim.m_mining_id + << claim.m_client_version + << claim.m_organization + << NN::VarDouble(claim.m_block_subsidy) + << claim.m_quorum_hash; + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << claim; + + BOOST_CHECK_EQUAL_COLLECTIONS( + stream.begin(), + stream.end(), + expected.begin(), + expected.end()); +} + +BOOST_AUTO_TEST_CASE(it_serializes_to_a_stream_for_investor_with_superblock) +{ + NN::Claim claim = GetInvestorClaim(); + + claim.m_superblock = GetTestSuperblock(); + claim.m_quorum_hash = claim.m_superblock.GetHash(); + claim.m_quorum_address = "mk8PmpcTGLCZky8YqFHEEwXs5px3hGfQBG"; + + CDataStream expected(SER_NETWORK, PROTOCOL_VERSION); + + expected << claim.m_version + << claim.m_mining_id + << claim.m_client_version + << claim.m_organization + << NN::VarDouble(claim.m_block_subsidy) + << claim.m_quorum_hash + << claim.m_quorum_address + << claim.m_superblock; + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << claim; + + BOOST_CHECK_EQUAL_COLLECTIONS( + stream.begin(), + stream.end(), + expected.begin(), + expected.end()); +} + +BOOST_AUTO_TEST_CASE(it_deserializes_from_a_stream_for_investor) +{ + NN::Claim expected = GetInvestorClaim(); + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + + stream << expected.m_version + << expected.m_mining_id + << expected.m_client_version + << expected.m_organization + << NN::VarDouble(expected.m_block_subsidy) + << expected.m_quorum_hash; + + NN::Claim claim; + + stream >> claim; + + BOOST_CHECK(claim.m_version == expected.m_version); + BOOST_CHECK(claim.m_mining_id == expected.m_mining_id); + BOOST_CHECK(claim.m_client_version == expected.m_client_version); + BOOST_CHECK(claim.m_organization == expected.m_organization); + BOOST_CHECK(claim.m_block_subsidy == expected.m_block_subsidy); + BOOST_CHECK(claim.m_quorum_hash == expected.m_quorum_hash); + + BOOST_CHECK(claim.m_research_subsidy == 0.0); + BOOST_CHECK(claim.m_magnitude == 0.0); + BOOST_CHECK(claim.m_magnitude_unit == 0.0); + BOOST_CHECK(claim.m_signature.empty() == true); + BOOST_CHECK(claim.m_quorum_address.empty() == true); + BOOST_CHECK(claim.m_superblock.WellFormed() == false); +} + +BOOST_AUTO_TEST_CASE(it_deserializes_from_a_stream_for_investor_with_superblock) +{ + NN::Claim expected = GetInvestorClaim(); + + expected.m_superblock = GetTestSuperblock(); + expected.m_quorum_hash = expected.m_superblock.GetHash(); + expected.m_quorum_address = "mk8PmpcTGLCZky8YqFHEEwXs5px3hGfQBG"; + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + + stream << expected.m_version + << expected.m_mining_id + << expected.m_client_version + << expected.m_organization + << NN::VarDouble(expected.m_block_subsidy) + << expected.m_quorum_hash + << expected.m_quorum_address + << expected.m_superblock; + + NN::Claim claim; + + stream >> claim; + + BOOST_CHECK(claim.m_version == expected.m_version); + BOOST_CHECK(claim.m_mining_id == expected.m_mining_id); + BOOST_CHECK(claim.m_client_version == expected.m_client_version); + BOOST_CHECK(claim.m_organization == expected.m_organization); + BOOST_CHECK(claim.m_block_subsidy == expected.m_block_subsidy); + + BOOST_CHECK(claim.m_quorum_hash == expected.m_quorum_hash); + BOOST_CHECK(claim.m_quorum_address == expected.m_quorum_address); + BOOST_CHECK(claim.m_superblock.WellFormed() == true); + BOOST_CHECK(claim.m_superblock.GetHash() == expected.m_superblock.GetHash()); + + BOOST_CHECK(claim.m_research_subsidy == 0.0); + BOOST_CHECK(claim.m_magnitude == 0.0); + BOOST_CHECK(claim.m_magnitude_unit == 0.0); + BOOST_CHECK(claim.m_signature.empty() == true); +} + +BOOST_AUTO_TEST_CASE(it_serializes_to_a_stream_for_researcher) +{ + NN::Claim claim = GetResearcherClaim(); + + CDataStream expected(SER_NETWORK, claim.m_version); + + expected << claim.m_version + << claim.m_mining_id + << claim.m_client_version + << claim.m_organization + << NN::VarDouble(claim.m_block_subsidy) + << NN::VarDouble(claim.m_research_subsidy) + << claim.m_magnitude + << NN::VarDouble(claim.m_magnitude_unit) + << claim.m_signature + << claim.m_quorum_hash; + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << claim; + + BOOST_CHECK_EQUAL_COLLECTIONS( + stream.begin(), + stream.end(), + expected.begin(), + expected.end()); +} + +BOOST_AUTO_TEST_CASE(it_serializes_to_a_stream_for_researcher_with_superblock) +{ + NN::Claim claim = GetResearcherClaim(); + + claim.m_superblock = GetTestSuperblock(); + claim.m_quorum_hash = claim.m_superblock.GetHash(); + claim.m_quorum_address = "mk8PmpcTGLCZky8YqFHEEwXs5px3hGfQBG"; + + CDataStream expected(SER_NETWORK, claim.m_version); + + expected << claim.m_version + << claim.m_mining_id + << claim.m_client_version + << claim.m_organization + << NN::VarDouble(claim.m_block_subsidy) + << NN::VarDouble(claim.m_research_subsidy) + << claim.m_magnitude + << NN::VarDouble(claim.m_magnitude_unit) + << claim.m_signature + << claim.m_quorum_hash + << claim.m_quorum_address + << claim.m_superblock; + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << claim; + + BOOST_CHECK_EQUAL_COLLECTIONS( + stream.begin(), + stream.end(), + expected.begin(), + expected.end()); +} + +BOOST_AUTO_TEST_CASE(it_deserializes_from_a_stream_for_researcher) +{ + NN::Claim expected = GetResearcherClaim(); + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + + stream << expected.m_version + << expected.m_mining_id + << expected.m_client_version + << expected.m_organization + << NN::VarDouble(expected.m_block_subsidy) + << NN::VarDouble(expected.m_research_subsidy) + << expected.m_magnitude + << NN::VarDouble(expected.m_magnitude_unit) + << expected.m_signature + << expected.m_quorum_hash; + + NN::Claim claim; + + stream >> claim; + + BOOST_CHECK(claim.m_version == expected.m_version); + BOOST_CHECK(claim.m_mining_id == expected.m_mining_id); + BOOST_CHECK(claim.m_client_version == expected.m_client_version); + BOOST_CHECK(claim.m_organization == expected.m_organization); + BOOST_CHECK(claim.m_block_subsidy == expected.m_block_subsidy); + + BOOST_CHECK(claim.m_research_subsidy == expected.m_research_subsidy); + BOOST_CHECK(claim.m_magnitude == expected.m_magnitude); + BOOST_CHECK(claim.m_magnitude_unit == expected.m_magnitude_unit); + BOOST_CHECK(claim.m_signature == expected.m_signature); + + BOOST_CHECK(claim.m_quorum_hash == expected.m_quorum_hash); + BOOST_CHECK(claim.m_quorum_address.empty() == true); + BOOST_CHECK(claim.m_superblock.WellFormed() == false); +} + +BOOST_AUTO_TEST_CASE(it_deserializes_from_a_stream_for_researcher_with_superblock) +{ + NN::Claim expected = GetResearcherClaim(); + + expected.m_superblock = GetTestSuperblock(); + expected.m_quorum_hash = expected.m_superblock.GetHash(); + expected.m_quorum_address = "mk8PmpcTGLCZky8YqFHEEwXs5px3hGfQBG"; + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + + stream << expected.m_version + << expected.m_mining_id + << expected.m_client_version + << expected.m_organization + << NN::VarDouble(expected.m_block_subsidy) + << NN::VarDouble(expected.m_research_subsidy) + << expected.m_magnitude + << NN::VarDouble(expected.m_magnitude_unit) + << expected.m_signature + << expected.m_quorum_hash + << expected.m_quorum_address + << expected.m_superblock; + + NN::Claim claim; + + stream >> claim; + + BOOST_CHECK(claim.m_version == expected.m_version); + BOOST_CHECK(claim.m_mining_id == expected.m_mining_id); + BOOST_CHECK(claim.m_client_version == expected.m_client_version); + BOOST_CHECK(claim.m_organization == expected.m_organization); + BOOST_CHECK(claim.m_block_subsidy == expected.m_block_subsidy); + + BOOST_CHECK(claim.m_research_subsidy == expected.m_research_subsidy); + BOOST_CHECK(claim.m_magnitude == expected.m_magnitude); + BOOST_CHECK(claim.m_magnitude_unit == expected.m_magnitude_unit); + BOOST_CHECK(claim.m_signature == expected.m_signature); + + BOOST_CHECK(claim.m_quorum_hash == expected.m_quorum_hash); + BOOST_CHECK(claim.m_quorum_address == expected.m_quorum_address); + BOOST_CHECK(claim.m_superblock.WellFormed() == true); + BOOST_CHECK(claim.m_superblock.GetHash() == expected.m_superblock.GetHash()); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/neuralnet/superblock_tests.cpp b/src/test/neuralnet/superblock_tests.cpp index 196d36e6e2..f546cd4c78 100644 --- a/src/test/neuralnet/superblock_tests.cpp +++ b/src/test/neuralnet/superblock_tests.cpp @@ -1958,6 +1958,17 @@ BOOST_AUTO_TEST_CASE(it_parses_an_invalid_quorum_hash_to_an_invalid_variant) BOOST_CHECK(hash.Valid() == false); } +BOOST_AUTO_TEST_CASE(it_parses_an_empty_superblock_hash_to_an_invalid_variant) +{ + // This is the hash of an empty legacy superblock contract. A bug in + // previous versions caused nodes to vote for empty superblocks when + // staking a block. It should parse to an invalid quorum hash value: + // + NN::QuorumHash hash = NN::QuorumHash::Parse("d41d8cd98f00b204e9800998ecf8427e"); + + BOOST_CHECK(hash.Valid() == false); +} + BOOST_AUTO_TEST_CASE(it_hashes_cpid_magnitudes_from_a_legacy_superblock) { // Version 1 superblocks hash with the legacy MD5-based algorithm: diff --git a/src/txdb-leveldb.cpp b/src/txdb-leveldb.cpp index 26a4974723..b737ac25b4 100644 --- a/src/txdb-leveldb.cpp +++ b/src/txdb-leveldb.cpp @@ -24,7 +24,43 @@ using namespace std; using namespace boost; leveldb::DB *txdb; // global pointer for LevelDB object instance -void AddCPIDBlockHash(const std::string& cpid, const uint256& blockhash); + +namespace { +//! +//! \brief Set the correct CPID from the block claim when the block index +//! contains a zero CPID. +//! +//! There were reports of 0000 cpid in index where INVESTOR should have been. +//! +//! \param pindex Index of the block to repair. +//! +void RepairZeroCpidIndex(CBlockIndex* const pindex) +{ + const NN::ClaimOption claim = GetClaimByIndex(pindex); + + if (!claim) { + return; + } + + const std::string mining_id = claim->m_mining_id.ToString(); + + if (mining_id != pindex->GetCPID()) + { + if(fDebug) + LogPrintf("WARNING: BlockIndex CPID %s did not match %s in block {%s %d}", + pindex->GetCPID(), mining_id, + pindex->GetBlockHash().GetHex(), pindex->nHeight ); + + /* Repair the cpid field */ + pindex->SetCPID(mining_id); + +#if 0 + if(!WriteBlockIndex(CDiskBlockIndex(pindex))) + error("LoadBlockIndex: writing CDiskBlockIndex failed"); +#endif + } +} +} // anonymous namespace static leveldb::Options GetOptions() { leveldb::Options options; @@ -626,23 +662,7 @@ bool CTxDB::LoadBlockIndex() if( pindex->IsUserCPID() && pindex->cpid == uint128() ) { - /* There were reports of 0000 cpid in index where INVESTOR should have been. Check */ - auto bb = GetBoincBlockByIndex(pindex); - if( bb.cpid != pindex->GetCPID() ) - { - if(fDebug) - LogPrintf("WARNING: BlockIndex CPID %s did not match %s in block {%s %d}", - pindex->GetCPID(), bb.cpid, - pindex->GetBlockHash().GetHex(), pindex->nHeight ); - - /* Repair the cpid field */ - pindex->SetCPID(bb.cpid); - -#if 0 - if(!WriteBlockIndex(CDiskBlockIndex(pindex))) - error("LoadBlockIndex: writing CDiskBlockIndex failed"); -#endif - } + RepairZeroCpidIndex(pindex); } AddRARewardBlock(pindex); diff --git a/src/util.h b/src/util.h index ee9c38ac9c..88ae543d77 100644 --- a/src/util.h +++ b/src/util.h @@ -46,6 +46,7 @@ #include static const int64_t COIN = 100000000; +static const int64_t COIN_PLACES = 8; static const int64_t CENT = 1000000; #define BEGIN(a) ((char*)&(a)) diff --git a/src/wallet.cpp b/src/wallet.cpp index fe0895d791..630baad406 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -27,12 +27,13 @@ using namespace std; -MiningCPID DeserializeBoincBlock(std::string block); - int64_t GetMaximumBoincSubsidy(int64_t nTime); + bool fConfChange; unsigned int nDerivationMethodIndex; +namespace NN { std::string GetPrimaryCpid(); } + ////////////////////////////////////////////////////////////////////////////// // // mapWallet @@ -167,7 +168,7 @@ bool CWallet::Unlock(const SecureString& strWalletPassphrase) return false; if (CCryptoKeyStore::Unlock(vMasterKey)) { - ImportBeaconKeysFromConfig(GlobalCPUMiningCPID.cpid, this); + ImportBeaconKeysFromConfig(NN::GetPrimaryCpid(), this); return true; } }