Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor coinbase validation functions #2295

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
317 changes: 161 additions & 156 deletions src/validation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2044,223 +2044,228 @@ static unsigned int GetBlockScriptFlags(const CBlockIndex* pindex, const Consens
return flags;
}

Res ApplyGeneralCoinbaseTx(CCustomCSView & mnview, CTransaction const & tx, int height, CAmount nFees, const Consensus::Params& consensus)
Res ApplyGeneralCoinbaseTx(CCustomCSView &mnview, const CTransaction &tx, const int height, const CAmount nFees, const Consensus::Params& consensus)
{
// TODO(legacy-cleanup): Clean up the rest of the method with proper structures
// and a more comprehensible flow

TAmounts const cbValues = tx.GetValuesOut();
CAmount blockReward = GetBlockSubsidy(height, consensus);
if (cbValues.size() != 1 || cbValues.begin()->first != DCT_ID{0})
return Res::ErrDbg("bad-cb-wrong-tokens", "coinbase should pay only Defi coins");

auto finalCheckAndReturn = [&](const CAmount totalBlockReward) {
if (cbValues.at(DCT_ID{0}) > totalBlockReward + nFees)
return Res::ErrDbg("bad-cb-amount", "coinbase pays too much (actual=%d vs limit=%d)", cbValues.at(DCT_ID{0}), blockReward + nFees);
return Res::Ok();
};

if (height >= consensus.AMKHeight)
{
auto logAccountChange = [](const CTransaction& tx, const CAmount subsidy, const std::string account) {
LogPrint(BCLog::ACCOUNTCHANGE, "AccountChange: hash=%s fund=%s change=%s\n",
tx.GetHash().ToString(),
account,
(CBalances{{{{0}, subsidy}}}.ToString()));
};

auto isUnusedEmissionFundEnabled = [](const ATTRIBUTES& attrs) {
CDataStructureV0 k{AttributeTypes::Param, ParamIDs::Feature, DFIPKeys::EmissionUnusedFund};
return attrs.GetValue(k, false);
};

auto isGovernanceEnabled = [](const ATTRIBUTES& attrs) {
CDataStructureV0 k{AttributeTypes::Param, ParamIDs::Feature, DFIPKeys::GovernanceEnabled};
return attrs.GetValue(k, false);
};

auto tryVerifyUtxoRewards = [](const CTransaction &tx, const CAmount blockReward, const int height, const Consensus::Params &consensus) {
CAmount foundationReward{0};
if (height >= consensus.GrandCentralHeight)
{
if (height >= consensus.GrandCentralHeight) {
// no foundation utxo reward check anymore
}
else if (height >= consensus.EunosHeight)
{
} else if (height >= consensus.EunosHeight) {
foundationReward = CalculateCoinbaseReward(blockReward, consensus.dist.community);
}
else if (!consensus.foundationShareScript.empty() && consensus.foundationShareDFIP1)
{
} else if (!consensus.foundationShareScript.empty() && consensus.foundationShareDFIP1) {
foundationReward = blockReward * consensus.foundationShareDFIP1 / COIN;
}

if (foundationReward)
{
if (foundationReward) {
bool foundationsRewardfound = false;
for (auto& txout : tx.vout)
{
if (txout.scriptPubKey == consensus.foundationShareScript)
{
if (txout.nValue < foundationReward)
{
return Res::ErrDbg("bad-cb-foundation-reward", "coinbase doesn't pay proper foundation reward! (actual=%d vs expected=%d", txout.nValue, foundationReward);
for (const auto &txout : tx.vout) {
if (txout.scriptPubKey == consensus.foundationShareScript) {
if (txout.nValue < foundationReward) {
return Res::ErrDbg("bad-cb-foundation-reward",
"coinbase doesn't pay proper foundation reward! (actual=%d vs expected=%d", txout.nValue, foundationReward);
}

foundationsRewardfound = true;
break;
}
}

if (!foundationsRewardfound)
{
if (!foundationsRewardfound) {
return Res::ErrDbg("bad-cb-foundation-reward", "coinbase doesn't pay foundation reward!");
}
}
return Res::Ok();
};

// count and subtract for non-UTXO community rewards
auto handleLegacyTokenRewards = [&finalCheckAndReturn, &logAccountChange](const CTransaction& tx, CAmount blockReward, CCustomCSView& view, const Consensus::Params& consensus) {
CAmount nonUtxoTotal = 0;
if (height >= consensus.EunosHeight)
{
CAmount subsidy;
for (const auto& kv : consensus.blockTokenRewards)
{
if (kv.first == CommunityAccountType::CommunityDevFunds) {
if (height < consensus.GrandCentralHeight) {
continue;
}
}

subsidy = CalculateCoinbaseReward(blockReward, kv.second);
for (const auto& [accountType, accountVal] : consensus.blockTokenRewardsLegacy) {
CAmount subsidy = blockReward * accountVal / COIN;
Res res = view.AddCommunityBalance(accountType, subsidy);
if (!res.ok) {
return Res::ErrDbg("bad-cb-community-rewards", "can't take non-UTXO community share from coinbase");
} else {
logAccountChange(tx, subsidy, GetCommunityAccountName(accountType));
}
nonUtxoTotal += subsidy;
}
blockReward -= nonUtxoTotal;
return finalCheckAndReturn(blockReward);
};

Res res = Res::Ok();
auto handleCurrentTokenRewards = [&finalCheckAndReturn, &logAccountChange, &isGovernanceEnabled, &isUnusedEmissionFundEnabled](const CTransaction& tx, CAmount blockReward, CCustomCSView& view, const Consensus::Params& consensus, int height) {
CAmount nonUtxoTotal = 0;
CAmount subsidy;

// Loan below FC and Options are unused and all go to Unallocated (burnt) pot.
if ((height < consensus.FortCanningHeight && kv.first == CommunityAccountType::Loan) ||
(height < consensus.GrandCentralHeight && kv.first == CommunityAccountType::Options))
{
res = mnview.AddCommunityBalance(CommunityAccountType::Unallocated, subsidy);
if (res)
LogPrint(BCLog::ACCOUNTCHANGE, "AccountChange: hash=%s fund=%s change=%s\n", tx.GetHash().ToString(), GetCommunityAccountName(CommunityAccountType::Unallocated), (CBalances{{{{0}, subsidy}}}.ToString()));
for (const auto& [accountType, accountVal] : consensus.blockTokenRewards) {
if (accountType == CommunityAccountType::CommunityDevFunds) {
if (height < consensus.GrandCentralHeight) {
continue;
}
else
{
if (height >= consensus.GrandCentralHeight)
{
const auto attributes = mnview.GetAttributes();
assert(attributes);

if (kv.first == CommunityAccountType::CommunityDevFunds) {
CDataStructureV0 enabledKey{AttributeTypes::Param, ParamIDs::Feature, DFIPKeys::GovernanceEnabled};

if (!attributes->GetValue(enabledKey, false))
{
res = mnview.AddBalance(consensus.foundationShareScript, {DCT_ID{0}, subsidy});
LogPrint(BCLog::ACCOUNTCHANGE, "AccountChange: hash=%s fund=%s change=%s\n",
tx.GetHash().ToString(), ScriptToString(consensus.foundationShareScript),
(CBalances{{{{0}, subsidy}}}.ToString()));
nonUtxoTotal += subsidy;
}

continue;
}
} else if (kv.first == CommunityAccountType::Unallocated || kv.first == CommunityAccountType::Options) {
CDataStructureV0 enabledKey{AttributeTypes::Param, ParamIDs::Feature, DFIPKeys::EmissionUnusedFund};

if (attributes->GetValue(enabledKey, false)) {
res = mnview.AddBalance(consensus.unusedEmission, {DCT_ID{0}, subsidy});
if (res) {
LogPrint(BCLog::ACCOUNTCHANGE, "AccountChange: hash=%s fund=%s change=%s\n",
tx.GetHash().ToString(), ScriptToString(consensus.unusedEmission),
(CBalances{{{{0}, subsidy}}}.ToString()));
}
} else {
// Previous behaviour was for Options and Unallocated to go to Unallocated
res = mnview.AddCommunityBalance(CommunityAccountType::Unallocated, subsidy);
if (res)
LogPrint(BCLog::ACCOUNTCHANGE, "AccountChange: hash=%s fund=%s change=%s\n", tx.GetHash().ToString(), GetCommunityAccountName(CommunityAccountType::Unallocated), (CBalances{{{{0}, subsidy}}}.ToString()));
}
subsidy = CalculateCoinbaseReward(blockReward, accountVal);
Res res = Res::Ok();

// Loan below FC and Options are unused and all go to Unallocated (burnt) pot.
if ((height < consensus.FortCanningHeight && accountType == CommunityAccountType::Loan) ||
(height < consensus.GrandCentralHeight && accountType == CommunityAccountType::Options)) {
res = view.AddCommunityBalance(CommunityAccountType::Unallocated, subsidy);
if (res) {
logAccountChange(tx, subsidy, GetCommunityAccountName(CommunityAccountType::Unallocated));
}
} else {
if (height >= consensus.GrandCentralHeight) {
const auto attributes = view.GetAttributes();
assert(attributes);
if (accountType == CommunityAccountType::CommunityDevFunds) {
if (!isGovernanceEnabled(*attributes)) {
res = view.AddBalance(consensus.foundationShareScript, {DCT_ID{0}, subsidy});
// TODO: Result check missed; check full sync and add checks
logAccountChange(tx, subsidy, ScriptToString(consensus.foundationShareScript));
nonUtxoTotal += subsidy;

continue;
}
} else if (accountType == CommunityAccountType::Unallocated || accountType == CommunityAccountType::Options) {
if (isUnusedEmissionFundEnabled(*attributes)) {
res = view.AddBalance(consensus.unusedEmission, {DCT_ID{0}, subsidy});
if (res) { logAccountChange(tx, subsidy, ScriptToString(consensus.unusedEmission)); }
} else {
// Previous behaviour was for Options and Unallocated to go to Unallocated
res = view.AddCommunityBalance(CommunityAccountType::Unallocated, subsidy);
if (res) { logAccountChange(tx, subsidy, GetCommunityAccountName(CommunityAccountType::Unallocated)); }
}
nonUtxoTotal += subsidy;
continue;
}

res = mnview.AddCommunityBalance(kv.first, subsidy);
if (res)
LogPrint(BCLog::ACCOUNTCHANGE, "AccountChange: hash=%s fund=%s change=%s\n", tx.GetHash().ToString(), GetCommunityAccountName(kv.first), (CBalances{{{{0}, subsidy}}}.ToString()));
}

if (!res.ok)
{
return Res::ErrDbg("bad-cb-community-rewards", "Cannot take non-UTXO community share from coinbase");
res = view.AddCommunityBalance(accountType, subsidy);
if (res) {
logAccountChange(tx, subsidy, GetCommunityAccountName(accountType));
}

nonUtxoTotal += subsidy;
}
}
else
{
for (const auto& kv : consensus.blockTokenRewardsLegacy) {
CAmount subsidy = blockReward * kv.second / COIN;
Res res = mnview.AddCommunityBalance(kv.first, subsidy);
if (!res.ok) {
return Res::ErrDbg("bad-cb-community-rewards", "can't take non-UTXO community share from coinbase");
} else {
LogPrint(BCLog::ACCOUNTCHANGE, "AccountChange: hash=%s fund=%s change=%s\n", tx.GetHash().ToString(), GetCommunityAccountName(kv.first), (CBalances{{{{0}, subsidy}}}.ToString()));
}
nonUtxoTotal += subsidy;

if (!res.ok) {
return Res::ErrDbg("bad-cb-community-rewards", "Cannot take non-UTXO community share from coinbase");
}

nonUtxoTotal += subsidy;
}

blockReward -= nonUtxoTotal;
return finalCheckAndReturn(blockReward);
};

// Actual logic starts here

if (height < consensus.AMKHeight) {
return finalCheckAndReturn(blockReward);
}

// pre-AMK logic, compatible after prev blockReward mod:
if (cbValues.at(DCT_ID{0}) > blockReward + nFees)
return Res::ErrDbg("bad-cb-amount", "coinbase pays too much (actual=%d vs limit=%d)", cbValues.at(DCT_ID{0}), blockReward + nFees);
if (auto r = tryVerifyUtxoRewards(tx, blockReward, height, consensus); !r) {
return r;
}

if (height < consensus.EunosHeight) {
return handleLegacyTokenRewards(tx, blockReward, mnview, consensus);
}

return Res::Ok();
return handleCurrentTokenRewards(tx, blockReward, mnview, consensus, height);
}


void ReverseGeneralCoinbaseTx(CCustomCSView & mnview, int height, const Consensus::Params& consensus)
{
CAmount blockReward = GetBlockSubsidy(height, Params().GetConsensus());
CAmount blockReward = GetBlockSubsidy(height, consensus);

if (height >= Params().GetConsensus().AMKHeight)
{
if (height >= Params().GetConsensus().EunosHeight)
{
for (const auto& kv : Params().GetConsensus().blockTokenRewards)
{
if (kv.first == CommunityAccountType::CommunityDevFunds) {
if (height < Params().GetConsensus().GrandCentralHeight) {
continue;
}
}
auto isUnusedEmissionFundEnabled = [](const ATTRIBUTES& attrs) {
CDataStructureV0 k{AttributeTypes::Param, ParamIDs::Feature, DFIPKeys::EmissionUnusedFund};
return attrs.GetValue(k, false);
};

CAmount subsidy = CalculateCoinbaseReward(blockReward, kv.second);
auto isGovernanceEnabled = [](const ATTRIBUTES& attrs) {
CDataStructureV0 k{AttributeTypes::Param, ParamIDs::Feature, DFIPKeys::GovernanceEnabled};
return attrs.GetValue(k, false);
};

// Remove Loan and Options balances from Unallocated
if ((height < Params().GetConsensus().FortCanningHeight && kv.first == CommunityAccountType::Loan) ||
(height < consensus.GrandCentralHeight && kv.first == CommunityAccountType::Options))
{
mnview.SubCommunityBalance(CommunityAccountType::Unallocated, subsidy);
}
else
{
if (height >= consensus.GrandCentralHeight)
{
const auto attributes = mnview.GetAttributes();
assert(attributes);
if (height < consensus.AMKHeight) {
return;
}

if (kv.first == CommunityAccountType::CommunityDevFunds) {
CDataStructureV0 enabledKey{AttributeTypes::Param, ParamIDs::Feature, DFIPKeys::GovernanceEnabled};
// TODO(legacy-cleanup): Use proper structures

if (!attributes->GetValue(enabledKey, false))
{
mnview.SubBalance(consensus.foundationShareScript, {DCT_ID{0}, subsidy});
if (height < consensus.EunosHeight) {
for (const auto& [accountType, accountVal] : consensus.blockTokenRewardsLegacy) {
CAmount subsidy = blockReward * accountVal / COIN;
mnview.SubCommunityBalance(accountType, subsidy);
}
return;
}

continue;
}
} else if (kv.first == CommunityAccountType::Unallocated || kv.first == CommunityAccountType::Options) {
CDataStructureV0 enabledKey{AttributeTypes::Param, ParamIDs::Feature, DFIPKeys::EmissionUnusedFund};
for (const auto& [accountType, accountVal] : consensus.blockTokenRewards) {
if (accountType == CommunityAccountType::CommunityDevFunds) {
if (height < consensus.GrandCentralHeight) {
continue;
}
}

if (attributes->GetValue(enabledKey, false)) {
mnview.SubBalance(consensus.unusedEmission, {DCT_ID{0}, subsidy});
} else {
mnview.SubCommunityBalance(CommunityAccountType::Unallocated, subsidy);
}
CAmount subsidy = CalculateCoinbaseReward(blockReward, accountVal);

continue;
}
}
// Remove Loan and Options balances from Unallocated
if ((height < consensus.FortCanningHeight && accountType == CommunityAccountType::Loan) ||
(height < consensus.GrandCentralHeight && accountType == CommunityAccountType::Options)) {
mnview.SubCommunityBalance(CommunityAccountType::Unallocated, subsidy);
} else {
if (height >= consensus.GrandCentralHeight) {
const auto attributes = mnview.GetAttributes();
assert(attributes);

mnview.SubCommunityBalance(kv.first, subsidy);
if (accountType == CommunityAccountType::CommunityDevFunds) {
if (!isGovernanceEnabled(*attributes)) {
mnview.SubBalance(consensus.foundationShareScript, {DCT_ID{0}, subsidy});
continue;
}
} else if (accountType == CommunityAccountType::Unallocated || accountType == CommunityAccountType::Options) {
if (isUnusedEmissionFundEnabled(*attributes)) {
mnview.SubBalance(consensus.unusedEmission, {DCT_ID{0}, subsidy});
} else {
mnview.SubCommunityBalance(CommunityAccountType::Unallocated, subsidy);
}
continue;
}
}
}
else
{
for (const auto& kv : Params().GetConsensus().blockTokenRewardsLegacy)
{
CAmount subsidy = blockReward * kv.second / COIN;
mnview.SubCommunityBalance(kv.first, subsidy);
}
mnview.SubCommunityBalance(accountType, subsidy);
}
}
}
Expand Down
Loading
Loading