Add valueBalance to value balances, and enforce its consensus rules
This commit is contained in:
parent
f0daf3915f
commit
97b46f00cc
|
@ -546,7 +546,7 @@ CAmount CCoinsViewCache::GetValueIn(const CTransaction& tx) const
|
||||||
for (unsigned int i = 0; i < tx.vin.size(); i++)
|
for (unsigned int i = 0; i < tx.vin.size(); i++)
|
||||||
nResult += GetOutputFor(tx.vin[i]).nValue;
|
nResult += GetOutputFor(tx.vin[i]).nValue;
|
||||||
|
|
||||||
nResult += tx.GetJoinSplitValueIn();
|
nResult += tx.GetShieldedValueIn();
|
||||||
|
|
||||||
return nResult;
|
return nResult;
|
||||||
}
|
}
|
||||||
|
|
|
@ -526,7 +526,7 @@ public:
|
||||||
* so may not be able to calculate this.
|
* so may not be able to calculate this.
|
||||||
*
|
*
|
||||||
* @param[in] tx transaction for which we are checking input total
|
* @param[in] tx transaction for which we are checking input total
|
||||||
* @return Sum of value of all inputs (scriptSigs)
|
* @return Sum of value of all inputs (scriptSigs), (positive valueBalance or zero) and JoinSplit vpub_new
|
||||||
*/
|
*/
|
||||||
CAmount GetValueIn(const CTransaction& tx) const;
|
CAmount GetValueIn(const CTransaction& tx) const;
|
||||||
|
|
||||||
|
|
|
@ -288,6 +288,54 @@ TEST(checktransaction_tests, bad_txns_txouttotal_toolarge_outputs) {
|
||||||
CheckTransactionWithoutProofVerification(tx, state);
|
CheckTransactionWithoutProofVerification(tx, state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(checktransaction_tests, ValueBalanceNonZero) {
|
||||||
|
CMutableTransaction mtx = GetValidTransaction();
|
||||||
|
mtx.valueBalance = 10;
|
||||||
|
|
||||||
|
CTransaction tx(mtx);
|
||||||
|
|
||||||
|
MockCValidationState state;
|
||||||
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-valuebalance-nonzero", false)).Times(1);
|
||||||
|
CheckTransactionWithoutProofVerification(tx, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(checktransaction_tests, PositiveValueBalanceTooLarge) {
|
||||||
|
CMutableTransaction mtx = GetValidTransaction();
|
||||||
|
mtx.vShieldedSpend.resize(1);
|
||||||
|
mtx.valueBalance = MAX_MONEY + 1;
|
||||||
|
|
||||||
|
CTransaction tx(mtx);
|
||||||
|
|
||||||
|
MockCValidationState state;
|
||||||
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-valuebalance-toolarge", false)).Times(1);
|
||||||
|
CheckTransactionWithoutProofVerification(tx, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(checktransaction_tests, NegativeValueBalanceTooLarge) {
|
||||||
|
CMutableTransaction mtx = GetValidTransaction();
|
||||||
|
mtx.vShieldedSpend.resize(1);
|
||||||
|
mtx.valueBalance = -(MAX_MONEY + 1);
|
||||||
|
|
||||||
|
CTransaction tx(mtx);
|
||||||
|
|
||||||
|
MockCValidationState state;
|
||||||
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-valuebalance-toolarge", false)).Times(1);
|
||||||
|
CheckTransactionWithoutProofVerification(tx, state);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(checktransaction_tests, ValueBalanceOverflowsTotal) {
|
||||||
|
CMutableTransaction mtx = GetValidTransaction();
|
||||||
|
mtx.vShieldedSpend.resize(1);
|
||||||
|
mtx.vout[0].nValue = 1;
|
||||||
|
mtx.valueBalance = -MAX_MONEY;
|
||||||
|
|
||||||
|
CTransaction tx(mtx);
|
||||||
|
|
||||||
|
MockCValidationState state;
|
||||||
|
EXPECT_CALL(state, DoS(100, false, REJECT_INVALID, "bad-txns-txouttotal-toolarge", false)).Times(1);
|
||||||
|
CheckTransactionWithoutProofVerification(tx, state);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(checktransaction_tests, bad_txns_txouttotal_toolarge_joinsplit) {
|
TEST(checktransaction_tests, bad_txns_txouttotal_toolarge_joinsplit) {
|
||||||
CMutableTransaction mtx = GetValidTransaction();
|
CMutableTransaction mtx = GetValidTransaction();
|
||||||
mtx.vout[0].nValue = 1;
|
mtx.vout[0].nValue = 1;
|
||||||
|
|
26
src/main.cpp
26
src/main.cpp
|
@ -1083,6 +1083,28 @@ bool CheckTransactionWithoutProofVerification(const CTransaction& tx, CValidatio
|
||||||
REJECT_INVALID, "bad-txns-txouttotal-toolarge");
|
REJECT_INVALID, "bad-txns-txouttotal-toolarge");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for non-zero valueBalance when there are no Sapling inputs or outputs
|
||||||
|
if (tx.vShieldedSpend.empty() && tx.vShieldedOutput.empty() && tx.valueBalance != 0) {
|
||||||
|
return state.DoS(100, error("CheckTransaction(): tx.valueBalance has no sources or sinks"),
|
||||||
|
REJECT_INVALID, "bad-txns-valuebalance-nonzero");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check for overflow valueBalance
|
||||||
|
if (tx.valueBalance > MAX_MONEY || tx.valueBalance < -MAX_MONEY) {
|
||||||
|
return state.DoS(100, error("CheckTransaction(): abs(tx.valueBalance) too large"),
|
||||||
|
REJECT_INVALID, "bad-txns-valuebalance-toolarge");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tx.valueBalance <= 0) {
|
||||||
|
// NB: negative valueBalance "takes" money from the transparent value pool just as outputs do
|
||||||
|
nValueOut += -tx.valueBalance;
|
||||||
|
|
||||||
|
if (!MoneyRange(nValueOut)) {
|
||||||
|
return state.DoS(100, error("CheckTransaction(): txout total out of range"),
|
||||||
|
REJECT_INVALID, "bad-txns-txouttotal-toolarge");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Ensure that joinsplit values are well-formed
|
// Ensure that joinsplit values are well-formed
|
||||||
BOOST_FOREACH(const JSDescription& joinsplit, tx.vjoinsplit)
|
BOOST_FOREACH(const JSDescription& joinsplit, tx.vjoinsplit)
|
||||||
{
|
{
|
||||||
|
@ -1890,9 +1912,9 @@ bool CheckTxInputs(const CTransaction& tx, CValidationState& state, const CCoins
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
nValueIn += tx.GetJoinSplitValueIn();
|
nValueIn += tx.GetShieldedValueIn();
|
||||||
if (!MoneyRange(nValueIn))
|
if (!MoneyRange(nValueIn))
|
||||||
return state.DoS(100, error("CheckInputs(): vpub_old values out of range"),
|
return state.DoS(100, error("CheckInputs(): shielded input to transparent value pool out of range"),
|
||||||
REJECT_INVALID, "bad-txns-inputvalues-outofrange");
|
REJECT_INVALID, "bad-txns-inputvalues-outofrange");
|
||||||
|
|
||||||
if (nValueIn < tx.GetValueOut())
|
if (nValueIn < tx.GetValueOut())
|
||||||
|
|
|
@ -216,7 +216,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn)
|
||||||
|
|
||||||
dPriority += (double)nValueIn * nConf;
|
dPriority += (double)nValueIn * nConf;
|
||||||
}
|
}
|
||||||
nTotalIn += tx.GetJoinSplitValueIn();
|
nTotalIn += tx.GetShieldedValueIn();
|
||||||
|
|
||||||
if (fMissingInputs) continue;
|
if (fMissingInputs) continue;
|
||||||
|
|
||||||
|
|
|
@ -256,9 +256,18 @@ CAmount CTransaction::GetValueOut() const
|
||||||
throw std::runtime_error("CTransaction::GetValueOut(): value out of range");
|
throw std::runtime_error("CTransaction::GetValueOut(): value out of range");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (valueBalance <= 0) {
|
||||||
|
// NB: negative valueBalance "takes" money from the transparent value pool just as outputs do
|
||||||
|
nValueOut += -valueBalance;
|
||||||
|
|
||||||
|
if (!MoneyRange(-valueBalance) || !MoneyRange(nValueOut)) {
|
||||||
|
throw std::runtime_error("CTransaction::GetValueOut(): value out of range");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (std::vector<JSDescription>::const_iterator it(vjoinsplit.begin()); it != vjoinsplit.end(); ++it)
|
for (std::vector<JSDescription>::const_iterator it(vjoinsplit.begin()); it != vjoinsplit.end(); ++it)
|
||||||
{
|
{
|
||||||
// NB: vpub_old "takes" money from the value pool just as outputs do
|
// NB: vpub_old "takes" money from the transparent value pool just as outputs do
|
||||||
nValueOut += it->vpub_old;
|
nValueOut += it->vpub_old;
|
||||||
|
|
||||||
if (!MoneyRange(it->vpub_old) || !MoneyRange(nValueOut))
|
if (!MoneyRange(it->vpub_old) || !MoneyRange(nValueOut))
|
||||||
|
@ -267,16 +276,26 @@ CAmount CTransaction::GetValueOut() const
|
||||||
return nValueOut;
|
return nValueOut;
|
||||||
}
|
}
|
||||||
|
|
||||||
CAmount CTransaction::GetJoinSplitValueIn() const
|
CAmount CTransaction::GetShieldedValueIn() const
|
||||||
{
|
{
|
||||||
CAmount nValue = 0;
|
CAmount nValue = 0;
|
||||||
|
|
||||||
|
if (valueBalance >= 0) {
|
||||||
|
// NB: positive valueBalance "gives" money to the transparent value pool just as inputs do
|
||||||
|
nValue += valueBalance;
|
||||||
|
|
||||||
|
if (!MoneyRange(valueBalance) || !MoneyRange(nValue)) {
|
||||||
|
throw std::runtime_error("CTransaction::GetShieldedValueIn(): value out of range");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for (std::vector<JSDescription>::const_iterator it(vjoinsplit.begin()); it != vjoinsplit.end(); ++it)
|
for (std::vector<JSDescription>::const_iterator it(vjoinsplit.begin()); it != vjoinsplit.end(); ++it)
|
||||||
{
|
{
|
||||||
// NB: vpub_new "gives" money to the value pool just as inputs do
|
// NB: vpub_new "gives" money to the transparent value pool just as inputs do
|
||||||
nValue += it->vpub_new;
|
nValue += it->vpub_new;
|
||||||
|
|
||||||
if (!MoneyRange(it->vpub_new) || !MoneyRange(nValue))
|
if (!MoneyRange(it->vpub_new) || !MoneyRange(nValue))
|
||||||
throw std::runtime_error("CTransaction::GetJoinSplitValueIn(): value out of range");
|
throw std::runtime_error("CTransaction::GetShieldedValueIn(): value out of range");
|
||||||
}
|
}
|
||||||
|
|
||||||
return nValue;
|
return nValue;
|
||||||
|
|
|
@ -651,13 +651,13 @@ public:
|
||||||
return header;
|
return header;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return sum of txouts.
|
// Return sum of txouts, (negative valueBalance or zero) and JoinSplit vpub_old.
|
||||||
CAmount GetValueOut() const;
|
CAmount GetValueOut() const;
|
||||||
// GetValueIn() is a method on CCoinsViewCache, because
|
// GetValueIn() is a method on CCoinsViewCache, because
|
||||||
// inputs must be known to compute value in.
|
// inputs must be known to compute value in.
|
||||||
|
|
||||||
// Return sum of JoinSplit vpub_new
|
// Return sum of (positive valueBalance or zero) and JoinSplit vpub_new
|
||||||
CAmount GetJoinSplitValueIn() const;
|
CAmount GetShieldedValueIn() const;
|
||||||
|
|
||||||
// Compute priority, given priority of inputs and (optionally) tx size
|
// Compute priority, given priority of inputs and (optionally) tx size
|
||||||
double ComputePriority(double dPriorityInputs, unsigned int nTxSize=0) const;
|
double ComputePriority(double dPriorityInputs, unsigned int nTxSize=0) const;
|
||||||
|
|
|
@ -1592,9 +1592,7 @@ void CWalletTx::GetAmounts(list<COutputEntry>& listReceived,
|
||||||
if (isFromMyTaddr) {
|
if (isFromMyTaddr) {
|
||||||
CAmount nValueOut = GetValueOut(); // transparent outputs plus all vpub_old
|
CAmount nValueOut = GetValueOut(); // transparent outputs plus all vpub_old
|
||||||
CAmount nValueIn = 0;
|
CAmount nValueIn = 0;
|
||||||
for (const JSDescription & js : vjoinsplit) {
|
nValueIn += GetShieldedValueIn();
|
||||||
nValueIn += js.vpub_new;
|
|
||||||
}
|
|
||||||
nFee = nDebit - nValueOut + nValueIn;
|
nFee = nDebit - nValueOut + nValueIn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue