Skip to content

Commit bfd0b1f

Browse files
authored
Merge pull request #2505 from jamescowens/mrc
rpc, contract: Adjust ValidateMRC, CreateMRC, and createmrcrequest to correct provided fee handling
2 parents 7b27847 + 5d8f712 commit bfd0b1f

File tree

7 files changed

+96
-103
lines changed

7 files changed

+96
-103
lines changed

src/gridcoin/mrc.cpp

+23-53
Original file line numberDiff line numberDiff line change
@@ -209,54 +209,8 @@ uint256 MRC::GetHash() const
209209

210210
bool GRC::MRCContractHandler::Validate(const Contract& contract, const CTransaction& tx) const
211211
{
212-
// The MRC transaction should only have one contract on it, and the contract type should be MRC (which we already
213-
// know to arrive at this virtual method implementation).
214-
if (tx.GetContracts().size() != 1) return false;
215-
216-
// Check that the burn in the contract is equal or greater than the required burn.
217-
CAmount burn_amount = 0;
218-
219-
for (const auto& output : tx.vout) {
220-
if (output.scriptPubKey == (CScript() << OP_RETURN)) {
221-
burn_amount += output.nValue;
222-
}
223-
}
224-
225-
GRC::MRC mrc = contract.CopyPayloadAs<GRC::MRC>();
226-
227-
LogPrintf("INFO: %s: mrc m_client_version = %s, m_fee = %s, m_last_block_hash = %s, m_magnitude = %u, "
228-
"m_magnitude_unit = %f, m_mining_id = %s, m_organization = %s, m_research_subsidy = %s, "
229-
"m_version = %s",
230-
__func__,
231-
mrc.m_client_version,
232-
FormatMoney(mrc.m_fee),
233-
mrc.m_last_block_hash.GetHex(),
234-
mrc.m_magnitude,
235-
mrc.m_magnitude_unit,
236-
mrc.m_mining_id.ToString(),
237-
mrc.m_organization,
238-
FormatMoney(mrc.m_research_subsidy),
239-
mrc.m_version);
240-
241-
if (burn_amount < mrc.RequiredBurnAmount()) {
242-
return error("%s: Burn amount of %s in mrc contract is less than the required %s.",
243-
__func__,
244-
FormatMoney(mrc.m_fee),
245-
FormatMoney(mrc.RequiredBurnAmount())
246-
);
247-
}
248-
249-
// We cannot fully validate incoming mrc transactions to the mempool. During testing under stress, a node can send an
250-
// mrc transaction right before the receipt of a block staked by another node, which then results in the just submitted
251-
// transaction being invalid on nodes that receive it after the staked block, but valid on the sending node (because
252-
// the order is reversed). To avoid this we will relieve the strict requirement here and do a partial validation which
253-
// checks the burn fee and the MRC contract signature to ensure the MRC contract is validly signed by an active beacon
254-
// holder. The block level validations handle the full MRC validation. This completes the concept of a "bad" or "stale"
255-
// mrc transaction being accepted into the memory pool. They will also be bound into the block, but they will be
256-
// "absorbed," meaning they will not result in a coinstake payout to the mrc requester. The deterrent to prevent someone
257-
// from flooding the mempool with invalid mrc transactions that pass this level of checking is the loss of the burn fees
258-
// AND the trouble to create the valid beacon context and signature for each MRC request transaction.
259-
return ValidateMRC(tx, mrc);
212+
// Fully validate the incoming MRC txn.
213+
return ValidateMRC(contract, tx);
260214
}
261215

262216
namespace {
@@ -359,13 +313,29 @@ bool GRC::CreateMRC(CBlockIndex* pindex,
359313
mrc.m_organization = gArgs.GetArg("-org", "").substr(0, GRC::Claim::MAX_ORGANIZATION_SIZE);
360314

361315
mrc.m_last_block_hash = pindex->GetBlockHash();
362-
mrc.m_fee = mrc.ComputeMRCFee();
363-
fee = mrc.m_fee;
364316

365-
if (!TrySignMRC(pwallet, pindex, mrc)) {
366-
error("%s: Failed to sign mrc.", __func__);
317+
CAmount computed_mrc_fee = mrc.ComputeMRCFee();
367318

368-
return false;
319+
// If no input fee provided (i.e. zero), then use computed_mrc_fee and also set parameter to this value. If an input
320+
// fee is provided, it must be bound by the computed_mrc_fee and the m_research_subsidy (inclusive).
321+
if (!fee) {
322+
fee = mrc.m_fee = computed_mrc_fee;
323+
} else if (fee >= computed_mrc_fee && fee <= mrc.m_research_subsidy) {
324+
mrc.m_fee = fee;
325+
} else {
326+
// Set the output fee equal to computed (for help with error handling)
327+
CAmount provided_fee = fee;
328+
fee = computed_mrc_fee;
329+
return error("%s: Invalid fee specified for mrc. The specified fee of %s is not bounded by the "
330+
"minimum calculated fee of %s and the computed research reward of %s.",
331+
__func__,
332+
FormatMoney(provided_fee),
333+
FormatMoney(computed_mrc_fee),
334+
FormatMoney(mrc.m_research_subsidy));
335+
}
336+
337+
if (!TrySignMRC(pwallet, pindex, mrc)) {
338+
return error("%s: Failed to sign mrc.", __func__);
369339
}
370340

371341
LogPrintf(

src/gridcoin/mrc.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -337,7 +337,7 @@ class MRCContractHandler : public IContractHandler
337337
//! \param pindexPrev: The index for the last block (head of the chain) when the MRC was created.
338338
//! \param mrc: The MRC contract object itself (out parameter)
339339
//! \param nReward: The research reward (out parameter)
340-
//! \param fee: The MRC fees to be taken out of the research reward (out parameter)
340+
//! \param fee: The MRC fees to be taken out of the research reward (in/out parameter)
341341
//! \param pwallet: The wallet object
342342
//! \return
343343
//!

src/main.cpp

+58-29
Original file line numberDiff line numberDiff line change
@@ -4635,40 +4635,69 @@ GRC::MintSummary CBlock::GetMint() const EXCLUSIVE_LOCKS_REQUIRED(cs_main)
46354635
}
46364636

46374637
//!
4638-
//! \brief Used in GRC::MRCContractHandler::Validate
4639-
//! \param tx The MRC request transaction
4640-
//! \param mrc The MRC contract
4638+
//! \brief Used in GRC::MRCContractHandler::Validate (essentially AcceptToMemoryPool)
4639+
//! \param contract The contract that contains the MRC
4640+
//! \param tx The transaction that contains the contract
46414641
//! \return true if successfully validated
46424642
//!
4643-
bool ValidateMRC(const CTransaction& tx, const GRC::MRC& mrc)
4643+
bool ValidateMRC(const GRC::Contract& contract, const CTransaction& tx)
46444644
{
4645-
const int64_t& mrc_time = tx.nTime;
4645+
// The MRC transaction should only have one contract on it, and the contract type should be MRC (which we already
4646+
// know to arrive at this virtual method implementation).
4647+
if (tx.GetContracts().size() != 1) {
4648+
return error("%s: Validation failed: The transaction, hash %s, that contains the MRC has more than one contract.",
4649+
__func__,
4650+
tx.GetHash().GetHex());
4651+
}
46464652

4647-
const GRC::CpidOption cpid = mrc.m_mining_id.TryCpid();
4653+
// Check that the burn in the contract is equal or greater than the required burn.
4654+
CAmount burn_amount = 0;
46484655

4649-
// No Cpid, the MRC must be invalid.
4650-
if (!cpid) return error("%s: Validation failed: MRC has no CPID.");
4651-
4652-
// Check to ensure the beacon was active at the transaction time of the MRC and the MRC signature. This also
4653-
// checks the signature using the block hash in the mrc itself. This is done for the ContractHandler::Validate whereas
4654-
// the stronger form is done in the ConnectBlock using the overloaded version below.
4655-
// If this fails, no point in going further.
4656-
if (const GRC::BeaconOption beacon = GRC::GetBeaconRegistry().TryActive(*cpid, mrc_time)) {
4657-
if (!mrc.VerifySignature(
4658-
beacon->m_public_key,
4659-
mrc.m_last_block_hash)) {
4660-
return error("%s: Validation failed: MRC signature validation failed for MRC for CPID %s.",
4661-
__func__,
4662-
cpid->ToString()
4663-
);
4656+
for (const auto& output : tx.vout) {
4657+
if (output.scriptPubKey == (CScript() << OP_RETURN)) {
4658+
burn_amount += output.nValue;
46644659
}
4665-
} else {
4666-
return error("%s: Validation failed: The beacon for the cpid %s referred to in the MRC is not active.",
4660+
}
4661+
4662+
GRC::MRC mrc = contract.CopyPayloadAs<GRC::MRC>();
4663+
4664+
LogPrintf("INFO: %s: mrc m_client_version = %s, m_fee = %s, m_last_block_hash = %s, m_magnitude = %u, "
4665+
"m_magnitude_unit = %f, m_mining_id = %s, m_organization = %s, m_research_subsidy = %s, "
4666+
"m_version = %s",
4667+
__func__,
4668+
mrc.m_client_version,
4669+
FormatMoney(mrc.m_fee),
4670+
mrc.m_last_block_hash.GetHex(),
4671+
mrc.m_magnitude,
4672+
mrc.m_magnitude_unit,
4673+
mrc.m_mining_id.ToString(),
4674+
mrc.m_organization,
4675+
FormatMoney(mrc.m_research_subsidy),
4676+
mrc.m_version);
4677+
4678+
if (burn_amount < mrc.RequiredBurnAmount()) {
4679+
return error("%s: Burn amount of %s in mrc contract is less than the required %s.",
46674680
__func__,
4668-
cpid->ToString()
4681+
FormatMoney(mrc.m_fee),
4682+
FormatMoney(mrc.RequiredBurnAmount())
46694683
);
46704684
}
46714685

4686+
// If the mrc last block hash is not pindexBest's block hash return false, because MRC requests can only be valid
4687+
// in the mempool if they refer to the head of the current chain.
4688+
if (pindexBest->GetBlockHash() != mrc.m_last_block_hash) {
4689+
return error("%s: Validation failed: MRC m_last_block_hash %s cannot be found in the chain.",
4690+
__func__,
4691+
mrc.m_last_block_hash.GetHex());
4692+
}
4693+
4694+
const GRC::CpidOption cpid = mrc.m_mining_id.TryCpid();
4695+
4696+
// No Cpid, the MRC must be invalid.
4697+
if (!cpid) return error("%s: Validation failed: MRC has no CPID.");
4698+
4699+
int64_t mrc_time = pindexBest->nTime;
4700+
46724701
// We are not going to even accept MRC transactions to the memory pool that have a payment interval less than
46734702
// MRCZeroPaymentInterval / 2. This is to prevent a rogue actor from trying to fill slots in a DoS to rightful
46744703
// MRC recipients.
@@ -4677,11 +4706,7 @@ bool ValidateMRC(const CTransaction& tx, const GRC::MRC& mrc)
46774706
const GRC::ResearchAccount& account = GRC::Tally::GetAccount(*cpid);
46784707
const int64_t last_reward_time = account.LastRewardTime();
46794708

4680-
// Here we are using the nTime of the transaction here instead of the block time of the last block.
4681-
// Note that tx.nTime is > last_block.nTime, so this is a little looser than
4682-
// last_block.nTime - last_reward_time < reject_payment_interval, but that is ok, because we are using
4683-
// half of the MRCZeroPaymentInterval.
4684-
const int64_t payment_interval = tx.nTime - last_reward_time;
4709+
const int64_t payment_interval = mrc_time - last_reward_time;
46854710

46864711
if (payment_interval < reject_payment_interval) {
46874712
return error("%s: Validation failed: MRC payment interval by tx time, %" PRId64 " sec, is less than 1/2 of the MRC "
@@ -4691,6 +4716,10 @@ bool ValidateMRC(const CTransaction& tx, const GRC::MRC& mrc)
46914716
reject_payment_interval);
46924717
}
46934718

4719+
// Note that the below overload of ValidateMRC repeats the check for a valid Cpid. It is low overhead and worth not
4720+
// repeating a bunch of what would be common code to eliminate.
4721+
ValidateMRC(pindexBest, mrc);
4722+
46944723
// If we get here, return true.
46954724
return true;
46964725
}

src/main.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -131,7 +131,7 @@ unsigned int GetCoinstakeOutputLimit(const int& block_version);
131131
Fraction FoundationSideStakeAllocation();
132132
CBitcoinAddress FoundationSideStakeAddress();
133133
unsigned int GetMRCOutputLimit(const int& block_version, bool include_foundation_sidestake);
134-
bool ValidateMRC(const CTransaction& tx, const GRC::MRC& mrc);
134+
bool ValidateMRC(const GRC::Contract &contract, const CTransaction& tx);
135135
bool ValidateMRC(const CBlockIndex* mrc_last_pindex, const GRC::MRC& mrc);
136136

137137
int GetNumBlocksOfPeers();

src/miner.cpp

+3-3
Original file line numberDiff line numberDiff line change
@@ -1048,11 +1048,11 @@ void SplitCoinStakeOutput(CBlock &blocknew, int64_t &nReward, bool &fEnableStake
10481048
// [MRC 1], ..., [MRC p].
10491049
//
10501050
// Currently according to the output limit rules encoded in CreateMRC and here:
1051-
// For block version 10:
1051+
// For block version 10-11:
10521052
// one empty, m <= 6, m + n <= 7, and p = 0.
10531053
//
1054-
// For block version 11 and above:
1055-
// one empty, m <= 6, m + n <= 7, and p <= 5.
1054+
// For block version 12:
1055+
// one empty, m <= 6, m + n <= 10, and p <= 10.
10561056

10571057
// The total generated GRC is the total of the reward splits - the fees (the original GridcoinReward which is the
10581058
// research reward + CBR), plus the total of the MRC outputs 2 to p (these outputs already have the fees subtracted)

src/rpc/blockchain.cpp

+8-14
Original file line numberDiff line numberDiff line change
@@ -2522,22 +2522,16 @@ UniValue createmrcrequest(const UniValue& params, const bool fHelp) {
25222522
throw runtime_error("MRC requests require version 12 blocks to be active.");
25232523
}
25242524

2525-
if (!GRC::CreateMRC(pindex, mrc, reward, fee, pwalletMain)) {
2526-
throw runtime_error("MRC request creation failed.");
2527-
}
2528-
2525+
// If the fee is provided, set the fee for the CreateMRC to this value to override the calculated fee.
25292526
if (provided_fee != 0) {
2530-
if (!force) {
2531-
if (provided_fee < fee) {
2532-
throw runtime_error("Provided fee lower than required.");
2533-
}
2534-
2535-
if (provided_fee > mrc.m_research_subsidy) {
2536-
throw runtime_error("Provided fee higher than subsidy.");
2537-
}
2538-
}
2527+
fee = provided_fee;
2528+
}
25392529

2540-
mrc.m_fee = provided_fee;
2530+
// If the fee is not overridden by the provided fee above (i.e. zero), it will be filled in
2531+
// at the calculated mrc value by CreateMRC. CreateMRC also rechecks the bounds
2532+
// of the provided fee.
2533+
if (!GRC::CreateMRC(pindex, mrc, reward, fee, pwalletMain)) {
2534+
throw runtime_error("MRC request creation failed. Please check the log for details.");
25412535
}
25422536

25432537
if (!dry_run && !force && reward == fee) {

src/test/gridcoin/mrc_tests.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ BOOST_AUTO_TEST_CASE(createmrc_creates_valid_mrcs)
193193
{
194194
account.m_accrual = 72;
195195
GRC::MRC mrc;
196-
CAmount reward, fee;
196+
CAmount reward, fee{0};
197197
GRC::CreateMRC(pindex->pprev, mrc, reward, fee, wallet);
198198

199199
BOOST_CHECK_EQUAL(reward, 72);
@@ -207,7 +207,7 @@ BOOST_AUTO_TEST_CASE(it_accepts_valid_fees)
207207
{
208208
account.m_accrual = 72;
209209
GRC::MRC mrc;
210-
CAmount reward, fee;
210+
CAmount reward, fee{0};
211211
GRC::CreateMRC(pindex->pprev, mrc, reward, fee, wallet);
212212

213213
mrc.m_fee = 14;

0 commit comments

Comments
 (0)