diff --git a/src/contract/polls.cpp b/src/contract/polls.cpp index 0b09027764..f3efd2b33e 100644 --- a/src/contract/polls.cpp +++ b/src/contract/polls.cpp @@ -12,6 +12,7 @@ #include "neuralnet/contract/contract.h" #include "neuralnet/contract/message.h" #include "neuralnet/quorum.h" +#include "neuralnet/superblock.h" #include "neuralnet/tally.h" double GetTotalBalance(); diff --git a/src/main.cpp b/src/main.cpp index 71810f7732..38548cbbb3 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -15,6 +15,8 @@ #include "block.h" #include "miner.h" #include "neuralnet/beacon.h" +#include "neuralnet/claim.h" +#include "neuralnet/contract/contract.h" #include "neuralnet/project.h" #include "neuralnet/quorum.h" #include "neuralnet/researcher.h" @@ -1127,8 +1129,8 @@ bool CTransaction::CheckTransaction() const return DoS(10, error("CTransaction::CheckTransaction() : vin empty")); if (vout.empty()) return DoS(10, error("CTransaction::CheckTransaction() : vout empty")); - // Size limits - if (::GetSerializeSize(*this, SER_NETWORK, PROTOCOL_VERSION) > MAX_BLOCK_SIZE) + // Size limits - don't count coinbase superblocks--we check this at the block level: + if (::GetSerializeSize(*this, (SER_NETWORK & SER_SKIPSUPERBLOCK), PROTOCOL_VERSION) > MAX_BLOCK_SIZE) return DoS(100, error("CTransaction::CheckTransaction() : size limits failed")); // Check for negative or overflow output values @@ -2240,18 +2242,6 @@ bool CBlock::DisconnectBlock(CTxDB& txdb, CBlockIndex* pindex) return true; } - - -double BlockVersion(std::string v) -{ - if (v.length() < 10) return 0; - std::string vIn = v.substr(1,7); - boost::replace_all(vIn, ".", ""); - double ver1 = RoundFromString(vIn,0); - return ver1; -} - - std::string PubKeyToAddress(const CScript& scriptPubKey) { //Converts a script Public Key to a Gridcoin wallet address @@ -2270,10 +2260,53 @@ std::string PubKeyToAddress(const CScript& scriptPubKey) return address; } -double ClientVersionNew() +const NN::Claim& CBlock::GetClaim() const +{ + if (nVersion >= 11 || !vtx[0].vContracts.empty()) { + return *vtx[0].vContracts[0].SharePayloadAs(); + } + + // Before block version 11, the Gridcoin reward claim context is stored + // in the hashBoinc field of the first transaction. We cache the parsed + // representation here to speed up subsequent access: + // + REF(vtx[0]).vContracts.emplace_back(NN::MakeContract( + NN::ContractAction::ADD, + NN::Claim::Parse(vtx[0].hashBoinc, nVersion))); + + return *vtx[0].vContracts[0].SharePayloadAs(); +} + +NN::Claim CBlock::PullClaim() +{ + if (nVersion >= 11 || !vtx[0].vContracts.empty()) { + return vtx[0].vContracts[0].PullPayloadAs(); + } + + // Before block version 11, the Gridcoin reward claim context is stored + // in the hashBoinc field of the first transaction. + // + return NN::Claim::Parse(vtx[0].hashBoinc, nVersion); +} + +const NN::Superblock& CBlock::GetSuperblock() const +{ + return GetClaim().m_superblock; +} + +NN::Superblock CBlock::PullSuperblock() { - double cv = BlockVersion(FormatFullVersion()); - return cv; + if (nVersion >= 11 || !vtx[0].vContracts.empty()) { + auto payload = vtx[0].vContracts[0].SharePayload(); + return std::move(payload.As().m_superblock); + } + + // Before block version 11, the Gridcoin reward claim context is stored + // in the hashBoinc field of the first transaction. + // + NN::Claim claim = NN::Claim::Parse(vtx[0].hashBoinc, nVersion); + + return std::move(claim.m_superblock); } // @@ -2629,9 +2662,9 @@ bool TryLoadSuperblock( const CBlockIndex* const pindex, const NN::Claim& claim) { - // Note: PullSuperblock() invalidates the m_claim.m_superblock field + // Note: PullSuperblock() invalidates the coinbase tx claim contract // by moving it. This must be the last instance where we reference a - // superblock in a block's claim field: + // superblock in a block's claim contract: // NN::SuperblockPtr superblock = NN::SuperblockPtr::BindShared(block.PullSuperblock(), pindex); @@ -3413,52 +3446,28 @@ bool CBlock::CheckBlock(std::string sCaller, int height1, int64_t Mint, bool fCh if (vtx[i].IsCoinBase()) return DoS(100, error("CheckBlock[] : more than one coinbase")); - //Research Age - 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: + // Version 11+ blocks store the Gridcoin claim context as a contract in the + // coinbase transaction instead of the hashBoinc field. // if (nVersion >= 11) { - if (claim.m_version <= 1) { - return DoS(100, error("%s: legacy claim", __func__)); + if (vtx[0].vContracts.empty()) { + return DoS(100, error("%s: missing claim contract", __func__)); } - if (!claim.WellFormed()) { - return DoS(100, error("%s: malformed claim", __func__)); + if (vtx[0].vContracts.size() > 1) { + return DoS(100, error("%s: too many coinbase contracts", __func__)); } - if (claim.GetHash() != uint256S(vtx[0].hashBoinc)) { - return DoS(100, error("%s: claim hash mismatch", __func__)); + if (vtx[0].vContracts[0].m_type != NN::ContractType::CLAIM) { + return DoS(100, error("%s: unexpected coinbase contract", __func__)); } - } - if(nVersion<9) - { - //For higher security, plus lets catch these bad blocks before adding them to the chain to prevent reorgs: - //Orphan Flood Attack - if (height1 > nGrandfather) - { - double blockVersion = BlockVersion(claim.m_client_version); - double cvn = ClientVersionNew(); - LogPrint(BCLog::LogFlags::NOISY, "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 "); + if (!vtx[0].vContracts[0].WellFormed()) { + return DoS(100, error("%s: malformed claim contract", __func__)); } - } - if (!fLoadingIndex && claim.HasResearchReward() && height1 > nGrandfather && BlockNeedsChecked(nTime)) - { - // Full "v3" signature check is performed in ConnectBlock - if (claim.m_signature.size() < 16) - { - return DoS(20, error( - "Bad CPID or Block Signature : height %i, CPID %s, Bad Hashboinc [%s]", - height1, - claim.m_mining_id.ToString(), - vtx[0].hashBoinc)); + if (vtx[0].vContracts[0].m_version <= 1 || GetClaim().m_version <= 1) { + return DoS(100, error("%s: legacy claim", __func__)); } } diff --git a/src/main.h b/src/main.h index 8a808a2a16..1840243fb9 100644 --- a/src/main.h +++ b/src/main.h @@ -8,7 +8,6 @@ #include "arith_uint256.h" #include "util.h" #include "net.h" -#include "neuralnet/claim.h" #include "neuralnet/contract/contract.h" #include "neuralnet/cpid.h" #include "sync.h" @@ -30,6 +29,16 @@ class CInv; class CNode; class CTxMemPool; +namespace NN { +class Claim; +class Superblock; + +//! +//! \brief An optional type that either contains some claim object or does not. +//! +typedef boost::optional ClaimOption; +} + static const int LAST_POW_BLOCK = 2050; static const int CONSENSUS_LOOKBACK = 5; //Amount of blocks to go back from best block, to avoid counting forked blocks static const int BLOCK_GRANULARITY = 10; //Consensus block divisor @@ -1168,9 +1177,6 @@ class CBlock : public CBlockHeader // 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; @@ -1200,22 +1206,6 @@ class CBlock : public CBlockHeader 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(); @@ -1230,7 +1220,6 @@ class CBlock : public CBlockHeader vchBlockSig.clear(); vMerkleTree.clear(); nDoS = 0; - m_claim = NN::Claim(); } CBlockHeader GetBlockHeader() const @@ -1245,51 +1234,10 @@ class CBlock : public CBlockHeader return block; } - 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; - } - - NN::Superblock PullSuperblock() - { - if (nVersion >= 11 || m_claim.m_mining_id.Valid() || vtx.empty()) { - return std::move(m_claim.m_superblock); - } - - // Before block version 11, the Gridcoin reward claim context is - // stored in the first transaction of the block. - // - NN::Claim claim = NN::Claim::Parse(vtx[0].hashBoinc, nVersion); - - return std::move(claim.m_superblock); - } + const NN::Claim& GetClaim() const; + NN::Claim PullClaim(); + const NN::Superblock& GetSuperblock() const; + NN::Superblock PullSuperblock(); // entropy bit for stake modifier if chosen by modifier unsigned int GetStakeEntropyBit() const diff --git a/src/miner.cpp b/src/miner.cpp index 9a91b2a8e7..eeda41ec35 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -9,6 +9,8 @@ #include "kernel.h" #include "main.h" #include "neuralnet/beacon.h" +#include "neuralnet/claim.h" +#include "neuralnet/contract/contract.h" #include "neuralnet/quorum.h" #include "neuralnet/researcher.h" #include "neuralnet/tally.h" @@ -100,7 +102,7 @@ bool SignClaim( const NN::CpidOption cpid = claim.m_mining_id.TryCpid(); if (!cpid) { - return false; // Skip beacon signature for investors. + return true; // Skip beacon signature for investors. } const NN::BeaconOption beacon = NN::GetBeaconRegistry().Try(*cpid); @@ -129,6 +131,7 @@ bool SignClaim( LogPrint(BCLog::LogFlags::MINER, "%s: Signed for CPID %s and block hash %s with signature %s", + __func__, cpid->ToString(), last_block_hash.ToString(), HexStr(claim.m_signature)); @@ -892,33 +895,6 @@ unsigned int GetNumberOfStakeOutputs(int64_t &nValue, int64_t &nMinStakeSplitVal bool SignStakeBlock(CBlock &block, CKey &key, vector &StakeInputs, CWallet *pwallet) { - SignClaim(pwallet, block.m_claim, block.nTime, 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; - } - - block.vtx[0].hashBoinc = block.m_claim.ToString(block.nVersion); - - // Invalidate the claim object so that the new block is forced to parse - // it using the legacy routine when we pass it back to ProcessBlock(): - // - block.m_claim.m_mining_id = NN::MiningId(); - } 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(); - } - //Sign the coinstake transaction unsigned nIn = 0; for (auto const& pcoin : StakeInputs) @@ -964,12 +940,15 @@ void AddNeuralContractOrVote(CBlock& blocknew) return; } - blocknew.m_claim.m_quorum_hash = superblock.GetHash(); - blocknew.m_claim.m_superblock = std::move(superblock); + // TODO: fix the const cast: + NN::Claim& claim = const_cast(blocknew.GetClaim()); + + claim.m_quorum_hash = superblock.GetHash(); + claim.m_superblock = std::move(superblock); LogPrintf( "AddNeuralContractOrVote: Added our Superblock (size %" PRIszu ").", - GetSerializeSize(blocknew.m_claim.m_superblock, SER_NETWORK, 1)); + GetSerializeSize(claim.m_superblock, SER_NETWORK, 1)); return; } @@ -983,41 +962,48 @@ void AddNeuralContractOrVote(CBlock& blocknew) return; } + // TODO: fix the const cast: + NN::Claim& claim = const_cast(blocknew.GetClaim()); + // Add our Neural Vote // // CreateSuperblock() will return an empty superblock 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::Quorum::CreateSuperblock().GetHash(); + claim.m_quorum_hash = NN::Quorum::CreateSuperblock().GetHash(); - if (!blocknew.m_claim.m_quorum_hash.Valid()) { + if (!claim.m_quorum_hash.Valid()) { LogPrintf("AddNeuralContractOrVote: Local contract empty."); return; } - blocknew.m_claim.m_quorum_address = std::move(quorum_address); + claim.m_quorum_address = std::move(quorum_address); LogPrintf( "AddNeuralContractOrVote: Added our quorum vote: %s", - blocknew.m_claim.m_quorum_hash.ToString()); + claim.m_quorum_hash.ToString()); const NN::QuorumHash consensus_hash = NN::Quorum::FindPopularHash(pindexBest); - if (blocknew.m_claim.m_quorum_hash != consensus_hash) { + if (claim.m_quorum_hash != consensus_hash) { LogPrintf("AddNeuralContractOrVote: Not in consensus."); return; } // We have consensus, add our superblock contract: - blocknew.m_claim.m_superblock = NN::Quorum::CreateSuperblock(); + claim.m_superblock = NN::Quorum::CreateSuperblock(); LogPrintf( "AddNeuralContractOrVote: Added our Superblock (size %" PRIszu ").", - GetSerializeSize(blocknew.m_claim.m_superblock, SER_NETWORK, 1)); + GetSerializeSize(claim.m_superblock, SER_NETWORK, 1)); } -bool CreateGridcoinReward(CBlock &blocknew, CBlockIndex* pindexPrev, int64_t &nReward) +bool CreateGridcoinReward( + CBlock &blocknew, + CBlockIndex* pindexPrev, + int64_t &nReward, + CWallet* pwallet) { // Remove fees from coinbase: int64_t nFees = blocknew.vtx[0].vout[0].nValue; @@ -1025,7 +1011,7 @@ bool CreateGridcoinReward(CBlock &blocknew, CBlockIndex* pindexPrev, int64_t &nR const NN::ResearcherPtr researcher = NN::Researcher::Get(); - NN::Claim& claim = blocknew.m_claim; + NN::Claim claim; claim.m_mining_id = researcher->Id(); // If a researcher's beacon expired, generate the block as an investor. We @@ -1078,6 +1064,15 @@ bool CreateGridcoinReward(CBlock &blocknew, CBlockIndex* pindexPrev, int64_t &nR claim.m_magnitude_unit = NN::Tally::GetMagnitudeUnit(pindexPrev); } + if (!SignClaim(pwallet, claim, blocknew.nTime, blocknew.hashPrevBlock)) { + LogPrintf("%s: Failed to sign researcher claim. Staking as investor", __func__); + + nReward -= claim.m_research_subsidy; + claim.m_mining_id = NN::MiningId::ForInvestor(); + claim.m_research_subsidy = 0; + claim.m_magnitude = 0; + } + LogPrintf( "CreateGridcoinReward: for %s mint %s magnitude %d Research %s, Interest %s", claim.m_mining_id.ToString(), @@ -1086,6 +1081,30 @@ bool CreateGridcoinReward(CBlock &blocknew, CBlockIndex* pindexPrev, int64_t &nR FormatMoney(claim.m_research_subsidy), FormatMoney(claim.m_block_subsidy)); + // Append the claim context to the block before signing the transactions: + // + if (blocknew.nVersion <= 10) { + claim.m_version = 1; + + // 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 (claim.HasResearchReward()) { + claim.m_last_block_hash = blocknew.hashPrevBlock; + } + + blocknew.vtx[0].hashBoinc = claim.ToString(blocknew.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: + // + blocknew.vtx[0].vContracts.emplace_back(NN::MakeContract( + NN::ContractAction::ADD, + std::move(claim))); + } + blocknew.vtx[1].vout[1].nValue += nReward; return true; @@ -1282,7 +1301,6 @@ void StakeMiner(CWallet *pwallet) StakeBlock.nVersion = 11; } else { StakeBlock.nVersion = 10; - StakeBlock.m_claim.m_version = 1; } MinerStatus.Version= StakeBlock.nVersion; @@ -1325,7 +1343,7 @@ void StakeMiner(CWallet *pwallet) // * add gridcoin reward to coinstake, fill-in nReward int64_t nReward = 0; - if(!CreateGridcoinReward(StakeBlock, pindexPrev, nReward)) { + if(!CreateGridcoinReward(StakeBlock, pindexPrev, nReward, pwallet)) { continue; } diff --git a/src/neuralnet/accrual/snapshot.h b/src/neuralnet/accrual/snapshot.h index e7206f6d84..edc6ebf961 100644 --- a/src/neuralnet/accrual/snapshot.h +++ b/src/neuralnet/accrual/snapshot.h @@ -5,7 +5,7 @@ #include "neuralnet/accrual/computer.h" #include "neuralnet/beacon.h" #include "neuralnet/cpid.h" -#include "neuralnet/quorum.h" +#include "neuralnet/superblock.h" #include "serialize.h" #include "streams.h" #include "tinyformat.h" diff --git a/src/neuralnet/claim.cpp b/src/neuralnet/claim.cpp index 3a78199bdb..210bcbbe7c 100644 --- a/src/neuralnet/claim.cpp +++ b/src/neuralnet/claim.cpp @@ -207,7 +207,12 @@ bool Claim::VerifySignature( uint256 Claim::GetHash() const { - return SerializeHash(*this); + CHashWriter hasher(SER_NETWORK, PROTOCOL_VERSION); + + // Claim contracts do not use the contract action specifier: + Serialize(hasher, ContractAction::UNKNOWN); + + return hasher.GetHash(); } std::string Claim::ToString(const int block_version) const diff --git a/src/neuralnet/claim.h b/src/neuralnet/claim.h index f03d033d8d..fe0919a925 100644 --- a/src/neuralnet/claim.h +++ b/src/neuralnet/claim.h @@ -1,5 +1,6 @@ #pragma once +#include "neuralnet/contract/payload.h" #include "neuralnet/cpid.h" #include "neuralnet/superblock.h" #include "serialize.h" @@ -17,8 +18,9 @@ namespace NN { //! 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 +class Claim : public IContractPayload { +public: //! //! \brief Version number of the current format for a serialized reward //! claim block. @@ -53,8 +55,8 @@ struct Claim //! 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. + //! Version 2: Claim data serializable in binary format. Stored in a block + //! as the first contract in the coinbase transaction. //! uint32_t m_version = CURRENT_VERSION; @@ -215,6 +217,27 @@ struct Claim //! static Claim Parse(const std::string& claim, int block_version); + //! + //! \brief Get the type of contract that this payload contains data for. + //! + NN::ContractType ContractType() const + { + return NN::ContractType::CLAIM; + } + + //! + //! \brief Determine whether the object contains a well-formed payload. + //! + //! \param action The action declared for the contract that contains the + //! payload. It may determine how to validate the payload. + //! + //! \return \c true if the payload is complete. + //! + bool WellFormed(const NN::ContractAction action) const override + { + return WellFormed(); // Claims do not have contract actions. + } + //! //! \brief Determine whether the instance represents a complete claim. //! @@ -226,6 +249,38 @@ struct Claim //! bool WellFormed() const; + //! + //! \brief Get a string for the key used to construct a legacy contract. + //! + std::string LegacyKeyString() const override + { + return ""; // No legacy contract key representation exists. + } + + //! + //! \brief Get a string for the value used to construct a legacy contract. + //! + std::string LegacyValueString() const override + { + return ""; // No legacy contract value representation exists. + } + + //! + //! \brief Get the burn fee amount required to send a particular contract. + //! + //! \return Burn fee in units of 1/100000000 GRC. + //! + int64_t RequiredBurnAmount() const + { + // TODO: remove redefinition of this constant when porting amount.h + // from Bitcoin: + // + constexpr int64_t MAX_MONEY = 2000000000 * COIN; + + // Prevent users from sending this contract manually: + return MAX_MONEY; + } + //! //! \brief Determine whether the instance represents a claim that includes //! accrued research rewards. @@ -306,7 +361,17 @@ struct Claim // // For Claim::m_version >= 2. // - ADD_SERIALIZE_METHODS; + ADD_CONTRACT_PAYLOAD_SERIALIZE_METHODS; + + template + inline void SerializationOp( + Stream& s, + Operation ser_action, + const ContractAction contract_action) + { + // Claim contracts do not use the contract action specifier: + SerializationOp(s, ser_action); + } template inline void SerializationOp(Stream& s, Operation ser_action) @@ -334,9 +399,4 @@ struct Claim } } }; // Claim - -//! -//! \brief An optional type that either contains some claim object or does not. -//! -typedef boost::optional ClaimOption; } diff --git a/src/neuralnet/contract/contract.cpp b/src/neuralnet/contract/contract.cpp index 675b38b820..d53d2bc756 100644 --- a/src/neuralnet/contract/contract.cpp +++ b/src/neuralnet/contract/contract.cpp @@ -1,11 +1,13 @@ #include "appcache.h" #include "block.h" #include "main.h" +#include "neuralnet/claim.h" #include "neuralnet/contract/contract.h" #include "neuralnet/contract/handler.h" #include "neuralnet/beacon.h" #include "neuralnet/project.h" #include "neuralnet/researcher.h" +#include "neuralnet/superblock.h" #include "util.h" #include "wallet.h" @@ -592,8 +594,7 @@ bool Contract::WellFormed() const // Version 2+ contracts rely on the signatures in the transactions // instead of embedding another signature in the contract: && (m_version > 1 || m_signature.Viable()) - && (m_version > 1 || (RequiresSpecialKey() || m_public_key.Viable())) - && m_tx_timestamp > 0; + && (m_version > 1 || (RequiresSpecialKey() || m_public_key.Viable())); } bool Contract::Validate() const @@ -725,6 +726,7 @@ std::string Contract::Type::ToString() const { switch (m_value) { case ContractType::BEACON: return "beacon"; + case ContractType::CLAIM: return "claim"; case ContractType::POLL: return "poll"; case ContractType::PROJECT: return "project"; case ContractType::PROTOCOL: return "protocol"; @@ -796,6 +798,10 @@ ContractPayload Contract::Body::ConvertFromLegacy(const ContractType type) const case ContractType::BEACON: return ContractPayload::Make( BeaconPayload::Parse(legacy.m_key, legacy.m_value)); + case ContractType::CLAIM: + // Claims can only exist in a coinbase transaction and have no + // legacy representation as a contract: + assert(false && "Attempted to convert legacy claim contract."); case ContractType::POLL: return m_payload; case ContractType::PROJECT: @@ -820,6 +826,9 @@ void Contract::Body::ResetType(const ContractType type) case ContractType::BEACON: m_payload.Reset(new BeaconPayload()); break; + case ContractType::CLAIM: + m_payload.Reset(new Claim()); + break; case ContractType::POLL: m_payload.Reset(new LegacyPayload()); break; diff --git a/src/neuralnet/contract/message.cpp b/src/neuralnet/contract/message.cpp index 92b23d5df6..0d8e0e828b 100644 --- a/src/neuralnet/contract/message.cpp +++ b/src/neuralnet/contract/message.cpp @@ -182,6 +182,7 @@ std::pair NN::SendContract(Contract contract) wtx.hashBoinc = contract.ToString(); } + contract.m_tx_timestamp = wtx.nTime; wtx.vContracts.emplace_back(std::move(contract)); std::string error = SendContractTx(wtx); diff --git a/src/neuralnet/contract/payload.h b/src/neuralnet/contract/payload.h index b14bb678c5..9039e30568 100644 --- a/src/neuralnet/contract/payload.h +++ b/src/neuralnet/contract/payload.h @@ -51,12 +51,13 @@ enum class ContractType : uint8_t { UNKNOWN = 0x00, //!< An invalid, non-standard, or empty contract type. BEACON = 0x01, //!< Beacon advertisement or deletion. - POLL = 0x02, //!< Submission of a new poll. - PROJECT = 0x03, //!< Project whitelist addition or removal. - PROTOCOL = 0x04, //!< Network control message or configuration directive. - SCRAPER = 0x05, //!< Scraper node authorization grants and revocations. - VOTE = 0x06, //!< A vote cast by a wallet for a poll. - MAX_VALUE = 0x06, //!< Increment this when adding items to the enum. + CLAIM = 0x02, //!< Gridcoin block reward claim context. + POLL = 0x03, //!< Submission of a new poll. + PROJECT = 0x04, //!< Project whitelist addition or removal. + PROTOCOL = 0x05, //!< Network control message or configuration directive. + SCRAPER = 0x06, //!< Scraper node authorization grants and revocations. + VOTE = 0x07, //!< A vote cast by a wallet for a poll. + MAX_VALUE = 0x07, //!< Increment this when adding items to the enum. }; //! @@ -212,6 +213,26 @@ class ContractPayload return static_cast(*m_payload); } + //! + //! \brief Cast a wrapped contract payload as a reference to the specified + //! type. + //! + //! \tparam PayloadType Type of the wrapped IContractPayload implementation. + //! + template + PayloadType& As() + { + static_assert( + std::is_base_of::value, + "ContractPayload::As: T not derived from IContractPayload."); + + // We use static_cast here instead of dynamic_cast to avoid the lookup. + // Since only handlers for a particular contract type should access the + // the payload, the derived type is known at the casting site. + // + return static_cast(*m_payload); + } + //! //! \brief Replace the wrapped contract payload object. //! diff --git a/src/neuralnet/quorum.cpp b/src/neuralnet/quorum.cpp index b5e4c3705a..bb22a1b51b 100644 --- a/src/neuralnet/quorum.cpp +++ b/src/neuralnet/quorum.cpp @@ -1485,9 +1485,9 @@ bool Quorum::ValidateSuperblockClaim( return error("ValidateSuperblockClaim(): rejected legacy version."); } - // Superblocks are not included in the input for the claim hash (and, - // therefor, not for the block's hash), so we need to verify the hash - // to protect the integrity of the superblock data: + // Superblocks are not included in the input for the claim hash + // so we need to compare the computed hash to the claim hash to + // protect the integrity of the superblock data: // if (superblock->GetHash() != claim.m_quorum_hash) { return error("ValidateSuperblockClaim(): quorum hash mismatch."); diff --git a/src/neuralnet/quorum.h b/src/neuralnet/quorum.h index 03fc114ea6..8c36af7f90 100644 --- a/src/neuralnet/quorum.h +++ b/src/neuralnet/quorum.h @@ -1,6 +1,5 @@ #pragma once -#include #include class CBlockIndex; @@ -11,94 +10,7 @@ class Claim; class Magnitude; class QuorumHash; class Superblock; - -//! -//! \brief A smart pointer that wraps a superblock object for shared ownership -//! with context of its containing block. -//! -//! In general, this class represents a superblock published and received in a -//! block. -//! -class SuperblockPtr -{ -public: - int64_t m_height; //!< Height of the block that contains the contract. - int64_t m_timestamp; //!< Timestamp of the block that contains the contract. - - //! - //! \brief Wrap the provided superblock and store context of its containing - //! block. - //! - //! \param superblock The superblock object to wrap. - //! \param pindex Index of the block that contains the superblock. - //! - //! \return A smart pointer that wraps the provided superblock. - //! - static SuperblockPtr BindShared( - Superblock&& superblock, - const CBlockIndex* const pindex) - { - return SuperblockPtr( - std::make_shared(std::move(superblock)), - pindex); - } - - //! - //! \brief Create a representation of an empty, invalid superblock. - //! - //! \return A smart pointer to an empty superblock. - //! - static SuperblockPtr Empty() - { - return SuperblockPtr(std::make_shared(), 0, 0); - } - - const Superblock& operator*() const noexcept { return *m_superblock; } - const Superblock* operator->() const noexcept { return m_superblock.get(); } - - //! - //! \brief Get the current age of the superblock. - //! - //! \return Superblock age in seconds. - //! - int64_t Age() const - { - return GetAdjustedTime() - m_timestamp; - } - -private: - std::shared_ptr m_superblock; //!< The wrapped superblock. - - //! - //! \brief Initialize a new superblock smart pointer wrapper. - //! - //! \param superblock Smart pointer around a superblock object. - //! \param height Height of the block that contains the superblock. - //! \param timestamp Time of the block that contains the superblock. - //! - SuperblockPtr( - std::shared_ptr superblock, - const int64_t height, - const int64_t timestamp) - : m_height(height) - , m_timestamp(timestamp) - , m_superblock(std::move(superblock)) - { - } - - //! - //! \brief Initialize a new superblock smart pointer. - //! - //! \param superblock Smart pointer around a superblock object. - //! \param pindex Provides context about the containing block. - //! - SuperblockPtr( - std::shared_ptr superblock, - const CBlockIndex* const pindex) - : SuperblockPtr(std::move(superblock), pindex->nHeight, pindex->nTime) - { - } -}; +class SuperblockPtr; //! //! \brief Produces, stores, and validates superblocks. diff --git a/src/neuralnet/superblock.cpp b/src/neuralnet/superblock.cpp index a32d88c5c1..a26ade3e97 100644 --- a/src/neuralnet/superblock.cpp +++ b/src/neuralnet/superblock.cpp @@ -1,5 +1,6 @@ #include "compat/endian.h" #include "hash.h" +#include "main.h" #include "neuralnet/superblock.h" #include "sync.h" #include "util.h" @@ -980,6 +981,17 @@ void Superblock::VerifiedBeacons::Reset( } } +// ----------------------------------------------------------------------------- +// Class: SuperblockPtr +// ----------------------------------------------------------------------------- + +SuperblockPtr::SuperblockPtr( + std::shared_ptr superblock, + const CBlockIndex* const pindex) + : SuperblockPtr(std::move(superblock), pindex->nHeight, pindex->nTime) +{ +} + // ----------------------------------------------------------------------------- // Class: QuorumHash // ----------------------------------------------------------------------------- diff --git a/src/neuralnet/superblock.h b/src/neuralnet/superblock.h index 18353e7850..d79e965560 100644 --- a/src/neuralnet/superblock.h +++ b/src/neuralnet/superblock.h @@ -17,6 +17,7 @@ extern int64_t SCRAPER_CMANIFEST_RETENTION_TIME; extern std::vector GetVerifiedBeaconIDs(const ConvergedManifest& StructConvergedManifest); extern std::vector GetVerifiedBeaconIDs(const ScraperPendingBeaconMap& VerifiedBeaconMap); +class CBlockIndex; class ConvergedScraperStats; // Forward for Superblock namespace NN { @@ -1336,6 +1337,91 @@ class Superblock //! mutable QuorumHash m_hash_cache; }; // Superblock + +//! +//! \brief A smart pointer that wraps a superblock object for shared ownership +//! with context of its containing block. +//! +//! In general, this class represents a superblock published and received in a +//! block. +//! +class SuperblockPtr +{ +public: + int64_t m_height; //!< Height of the block that contains the contract. + int64_t m_timestamp; //!< Timestamp of the block that contains the contract. + + //! + //! \brief Wrap the provided superblock and store context of its containing + //! block. + //! + //! \param superblock The superblock object to wrap. + //! \param pindex Index of the block that contains the superblock. + //! + //! \return A smart pointer that wraps the provided superblock. + //! + static SuperblockPtr BindShared( + Superblock&& superblock, + const CBlockIndex* const pindex) + { + return SuperblockPtr( + std::make_shared(std::move(superblock)), + pindex); + } + + //! + //! \brief Create a representation of an empty, invalid superblock. + //! + //! \return A smart pointer to an empty superblock. + //! + static SuperblockPtr Empty() + { + return SuperblockPtr(std::make_shared(), 0, 0); + } + + const Superblock& operator*() const noexcept { return *m_superblock; } + const Superblock* operator->() const noexcept { return m_superblock.get(); } + + //! + //! \brief Get the current age of the superblock. + //! + //! \return Superblock age in seconds. + //! + int64_t Age() const + { + return GetAdjustedTime() - m_timestamp; + } + +private: + std::shared_ptr m_superblock; //!< The wrapped superblock. + + //! + //! \brief Initialize a new superblock smart pointer wrapper. + //! + //! \param superblock Smart pointer around a superblock object. + //! \param height Height of the block that contains the superblock. + //! \param timestamp Time of the block that contains the superblock. + //! + SuperblockPtr( + std::shared_ptr superblock, + const int64_t height, + const int64_t timestamp) + : m_height(height) + , m_timestamp(timestamp) + , m_superblock(std::move(superblock)) + { + } + + //! + //! \brief Initialize a new superblock smart pointer. + //! + //! \param superblock Smart pointer around a superblock object. + //! \param pindex Provides context about the containing block. + //! + SuperblockPtr( + std::shared_ptr superblock, + const CBlockIndex* const pindex); +}; // SuperblockPtr } // namespace NN namespace std { diff --git a/src/neuralnet/tally.cpp b/src/neuralnet/tally.cpp index b159e30132..b08f5adc82 100644 --- a/src/neuralnet/tally.cpp +++ b/src/neuralnet/tally.cpp @@ -3,8 +3,10 @@ #include "neuralnet/accrual/null.h" #include "neuralnet/accrual/research_age.h" #include "neuralnet/accrual/snapshot.h" +#include "neuralnet/claim.h" #include "neuralnet/cpid.h" #include "neuralnet/quorum.h" +#include "neuralnet/superblock.h" #include "neuralnet/tally.h" #include "util.h" diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 0e376b555f..c7e1bcd84b 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -86,6 +86,7 @@ #include "neuralnet/beacon.h" #include "neuralnet/quorum.h" #include "neuralnet/researcher.h" +#include "neuralnet/superblock.h" #include #include // for to_lower() diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 7db1c7bd36..b9f8d31951 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -8,6 +8,7 @@ #include "alert.h" #include "main.h" +#include "neuralnet/superblock.h" #include "ui_interface.h" #include "util.h" diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp index 77fdd3e5ba..99ee51be3e 100644 --- a/src/rpcblockchain.cpp +++ b/src/rpcblockchain.cpp @@ -11,11 +11,13 @@ #include "checkpoints.h" #include "txdb.h" #include "neuralnet/beacon.h" +#include "neuralnet/claim.h" #include "neuralnet/contract/contract.h" #include "neuralnet/contract/message.h" #include "neuralnet/project.h" #include "neuralnet/quorum.h" #include "neuralnet/researcher.h" +#include "neuralnet/superblock.h" #include "neuralnet/tally.h" #include "backup.h" #include "appcache.h" diff --git a/src/rpcmining.cpp b/src/rpcmining.cpp index 5d70ef8251..d093be5407 100644 --- a/src/rpcmining.cpp +++ b/src/rpcmining.cpp @@ -8,6 +8,7 @@ #include "miner.h" #include "neuralnet/quorum.h" #include "neuralnet/researcher.h" +#include "neuralnet/superblock.h" #include "neuralnet/tally.h" #include "rpcprotocol.h" #include "rpcserver.h" diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp index 336ea37242..2019e0d818 100755 --- a/src/rpcrawtransaction.cpp +++ b/src/rpcrawtransaction.cpp @@ -7,9 +7,11 @@ #include #include "base58.h" -#include "neuralnet/contract/contract.h" #include "neuralnet/beacon.h" +#include "neuralnet/claim.h" +#include "neuralnet/contract/contract.h" #include "neuralnet/project.h" +#include "neuralnet/superblock.h" #include "rpcserver.h" #include "rpcprotocol.h" #include "txdb.h" @@ -410,6 +412,27 @@ UniValue BeaconToJson(const NN::ContractPayload& payload) return out; } +UniValue RawClaimToJson(const NN::ContractPayload& payload) +{ + const auto& claim = payload.As(); + + 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", ValueFromAmount(claim.m_block_subsidy)); + json.pushKV("research_subsidy", ValueFromAmount(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 ProjectToJson(const NN::ContractPayload& payload) { const auto& project = payload.As(); @@ -436,6 +459,9 @@ UniValue ContractToJson(const NN::Contract& contract) case NN::ContractType::BEACON: out.pushKV("body", BeaconToJson(contract.SharePayload())); break; + case NN::ContractType::CLAIM: + out.pushKV("body", RawClaimToJson(contract.SharePayload())); + break; case NN::ContractType::PROJECT: out.pushKV("body", ProjectToJson(contract.SharePayload())); break; diff --git a/src/test/neuralnet/claim_tests.cpp b/src/test/neuralnet/claim_tests.cpp index bfb15d40fe..b0d19faf55 100644 --- a/src/test/neuralnet/claim_tests.cpp +++ b/src/test/neuralnet/claim_tests.cpp @@ -220,6 +220,17 @@ BOOST_AUTO_TEST_CASE(it_parses_a_legacy_boincblock_string_for_researcher) BOOST_CHECK(claim.m_superblock.GetHash() == superblock.GetHash()); } +BOOST_AUTO_TEST_CASE(it_behaves_like_a_contract_payload) +{ + const NN::Claim claim = GetResearcherClaim(); + + BOOST_CHECK(claim.ContractType() == NN::ContractType::CLAIM); + BOOST_CHECK(claim.WellFormed(NN::ContractAction::ADD) == true); + BOOST_CHECK(claim.LegacyKeyString().empty() == true); + BOOST_CHECK(claim.LegacyValueString().empty() == true); + BOOST_CHECK(claim.RequiredBurnAmount() > 0); +} + BOOST_AUTO_TEST_CASE(it_determines_whether_a_claim_is_well_formed) { const NN::Claim claim = GetInvestorClaim(); @@ -443,7 +454,7 @@ BOOST_AUTO_TEST_CASE(it_serializes_to_a_stream_for_investor) << claim.m_quorum_hash; CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); - stream << claim; + claim.Serialize(stream, NN::ContractAction::UNKNOWN); BOOST_CHECK_EQUAL_COLLECTIONS( stream.begin(), @@ -470,7 +481,7 @@ BOOST_AUTO_TEST_CASE(it_serializes_to_a_stream_for_investor_with_superblock) << claim.m_superblock; CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); - stream << claim; + claim.Serialize(stream, NN::ContractAction::UNKNOWN); BOOST_CHECK_EQUAL_COLLECTIONS( stream.begin(), @@ -493,7 +504,7 @@ BOOST_AUTO_TEST_CASE(it_deserializes_from_a_stream_for_investor) NN::Claim claim; - stream >> claim; + claim.Unserialize(stream, NN::ContractAction::UNKNOWN); BOOST_CHECK(claim.m_version == expected.m_version); BOOST_CHECK(claim.m_mining_id == expected.m_mining_id); @@ -529,7 +540,7 @@ BOOST_AUTO_TEST_CASE(it_deserializes_from_a_stream_for_investor_with_superblock) NN::Claim claim; - stream >> claim; + claim.Unserialize(stream, NN::ContractAction::UNKNOWN); BOOST_CHECK(claim.m_version == expected.m_version); BOOST_CHECK(claim.m_mining_id == expected.m_mining_id); @@ -564,7 +575,7 @@ BOOST_AUTO_TEST_CASE(it_serializes_to_a_stream_for_researcher) << claim.m_quorum_hash; CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); - stream << claim; + claim.Serialize(stream, NN::ContractAction::UNKNOWN); BOOST_CHECK_EQUAL_COLLECTIONS( stream.begin(), @@ -593,7 +604,7 @@ BOOST_AUTO_TEST_CASE(it_serializes_to_a_stream_for_researcher_with_superblock) << claim.m_superblock; CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); - stream << claim; + claim.Serialize(stream, NN::ContractAction::UNKNOWN); BOOST_CHECK_EQUAL_COLLECTIONS( stream.begin(), @@ -619,7 +630,7 @@ BOOST_AUTO_TEST_CASE(it_deserializes_from_a_stream_for_researcher) NN::Claim claim; - stream >> claim; + claim.Unserialize(stream, NN::ContractAction::UNKNOWN); BOOST_CHECK(claim.m_version == expected.m_version); BOOST_CHECK(claim.m_mining_id == expected.m_mining_id); @@ -658,7 +669,7 @@ BOOST_AUTO_TEST_CASE(it_deserializes_from_a_stream_for_researcher_with_superbloc NN::Claim claim; - stream >> claim; + claim.Unserialize(stream, NN::ContractAction::UNKNOWN); BOOST_CHECK(claim.m_version == expected.m_version); BOOST_CHECK(claim.m_mining_id == expected.m_mining_id); diff --git a/src/test/neuralnet/contract_tests.cpp b/src/test/neuralnet/contract_tests.cpp index 272a432713..a5c8ba9ac5 100644 --- a/src/test/neuralnet/contract_tests.cpp +++ b/src/test/neuralnet/contract_tests.cpp @@ -297,7 +297,7 @@ struct TestMessage { std::vector serialized { 0x02, 0x00, 0x00, 0x00, // Version: 32-bit int (little-endian) - 0x03, // Type: PROJECT + 0x04, // Type: PROJECT 0x01, // Action: ADD 0x01, 0x00, 0x00, 0x00, // Project contract version 0x04, // Length of the project name