Primitive, context-free consensus changes for CPourTx
* PourTxs cannot appear in coinbase transactions. * Transactions can only contain empty vin/vouts if they contain a PourTx. * PourTx public values must be well-formed (not negative or too large). * Transactions cannot have the same serial twice throughout all PourTxs.
This commit is contained in:
53
src/main.cpp
53
src/main.cpp
@@ -847,12 +847,16 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& in
|
|||||||
bool CheckTransaction(const CTransaction& tx, CValidationState &state)
|
bool CheckTransaction(const CTransaction& tx, CValidationState &state)
|
||||||
{
|
{
|
||||||
// Basic checks that don't depend on any context
|
// Basic checks that don't depend on any context
|
||||||
if (tx.vin.empty())
|
|
||||||
|
// Transactions can contain empty `vin` and `vout` so long as
|
||||||
|
// `vpour` is non-empty.
|
||||||
|
if (tx.vin.empty() && tx.vpour.empty())
|
||||||
return state.DoS(10, error("CheckTransaction(): vin empty"),
|
return state.DoS(10, error("CheckTransaction(): vin empty"),
|
||||||
REJECT_INVALID, "bad-txns-vin-empty");
|
REJECT_INVALID, "bad-txns-vin-empty");
|
||||||
if (tx.vout.empty())
|
if (tx.vout.empty() && tx.vpour.empty())
|
||||||
return state.DoS(10, error("CheckTransaction(): vout empty"),
|
return state.DoS(10, error("CheckTransaction(): vout empty"),
|
||||||
REJECT_INVALID, "bad-txns-vout-empty");
|
REJECT_INVALID, "bad-txns-vout-empty");
|
||||||
|
|
||||||
// Size limits
|
// Size limits
|
||||||
if (::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION) > MAX_BLOCK_SIZE)
|
if (::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION) > MAX_BLOCK_SIZE)
|
||||||
return state.DoS(100, error("CheckTransaction(): size limits failed"),
|
return state.DoS(100, error("CheckTransaction(): size limits failed"),
|
||||||
@@ -874,6 +878,32 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state)
|
|||||||
REJECT_INVALID, "bad-txns-txouttotal-toolarge");
|
REJECT_INVALID, "bad-txns-txouttotal-toolarge");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Ensure that pour values are well-formed
|
||||||
|
BOOST_FOREACH(const CPourTx& pour, tx.vpour)
|
||||||
|
{
|
||||||
|
if (pour.vpub_old < 0)
|
||||||
|
return state.DoS(100, error("CheckTransaction(): pour.vpub_old negative"),
|
||||||
|
REJECT_INVALID, "bad-txns-vpub_old-negative");
|
||||||
|
|
||||||
|
if (pour.vpub_new < 0)
|
||||||
|
return state.DoS(100, error("CheckTransaction(): pour.vpub_new negative"),
|
||||||
|
REJECT_INVALID, "bad-txns-vpub_new-negative");
|
||||||
|
|
||||||
|
if (pour.vpub_old > MAX_MONEY)
|
||||||
|
return state.DoS(100, error("CheckTransaction(): pour.vpub_old too high"),
|
||||||
|
REJECT_INVALID, "bad-txns-vpub_old-toolarge");
|
||||||
|
|
||||||
|
if (pour.vpub_new > MAX_MONEY)
|
||||||
|
return state.DoS(100, error("CheckTransaction(): pour.vpub_new too high"),
|
||||||
|
REJECT_INVALID, "bad-txns-vpub_new-toolarge");
|
||||||
|
|
||||||
|
nValueOut += pour.vpub_new;
|
||||||
|
if (!MoneyRange(nValueOut))
|
||||||
|
return state.DoS(100, error("CheckTransaction(): txout total out of range"),
|
||||||
|
REJECT_INVALID, "bad-txns-txouttotal-toolarge");
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
// Check for duplicate inputs
|
// Check for duplicate inputs
|
||||||
set<COutPoint> vInOutPoints;
|
set<COutPoint> vInOutPoints;
|
||||||
BOOST_FOREACH(const CTxIn& txin, tx.vin)
|
BOOST_FOREACH(const CTxIn& txin, tx.vin)
|
||||||
@@ -884,8 +914,27 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state)
|
|||||||
vInOutPoints.insert(txin.prevout);
|
vInOutPoints.insert(txin.prevout);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check for duplicate pour serials in this transaction
|
||||||
|
set<uint256> vPourSerials;
|
||||||
|
BOOST_FOREACH(const CPourTx& pour, tx.vpour)
|
||||||
|
{
|
||||||
|
BOOST_FOREACH(const uint256& serial, pour.serials)
|
||||||
|
{
|
||||||
|
if (vPourSerials.count(serial))
|
||||||
|
return state.DoS(100, error("CheckTransaction(): duplicate serials"),
|
||||||
|
REJECT_INVALID, "bad-pours-serials-duplicate");
|
||||||
|
|
||||||
|
vPourSerials.insert(serial);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (tx.IsCoinBase())
|
if (tx.IsCoinBase())
|
||||||
{
|
{
|
||||||
|
// There should be no pours in a coinbase transaction
|
||||||
|
if (tx.vpour.size() > 0)
|
||||||
|
return state.DoS(100, error("CheckTransaction(): coinbase has pours"),
|
||||||
|
REJECT_INVALID, "bad-cb-has-pours");
|
||||||
|
|
||||||
if (tx.vin[0].scriptSig.size() < 2 || tx.vin[0].scriptSig.size() > 100)
|
if (tx.vin[0].scriptSig.size() < 2 || tx.vin[0].scriptSig.size() > 100)
|
||||||
return state.DoS(100, error("CheckTransaction(): coinbase script size"),
|
return state.DoS(100, error("CheckTransaction(): coinbase script size"),
|
||||||
REJECT_INVALID, "bad-cb-length");
|
REJECT_INVALID, "bad-cb-length");
|
||||||
|
|||||||
@@ -285,6 +285,117 @@ SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet)
|
|||||||
return dummyTransactions;
|
return dummyTransactions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BOOST_AUTO_TEST_CASE(test_simple_pour_invalidity)
|
||||||
|
{
|
||||||
|
CMutableTransaction tx;
|
||||||
|
tx.nVersion = 2;
|
||||||
|
{
|
||||||
|
// Ensure that empty vin/vout remain invalid without
|
||||||
|
// pours.
|
||||||
|
CMutableTransaction newTx(tx);
|
||||||
|
CValidationState state;
|
||||||
|
// No pours, vin and vout, means it should be invalid.
|
||||||
|
BOOST_CHECK(!CheckTransaction(newTx, state));
|
||||||
|
BOOST_CHECK(state.GetRejectReason() == "bad-txns-vin-empty");
|
||||||
|
|
||||||
|
newTx.vin.push_back(CTxIn(uint256S("0000000000000000000000000000000000000000000000000000000000000001"), 0));
|
||||||
|
|
||||||
|
BOOST_CHECK(!CheckTransaction(newTx, state));
|
||||||
|
BOOST_CHECK(state.GetRejectReason() == "bad-txns-vout-empty");
|
||||||
|
|
||||||
|
newTx.vpour.push_back(CPourTx());
|
||||||
|
CPourTx *pourtx = &newTx.vpour[0];
|
||||||
|
|
||||||
|
pourtx->serials[0] = GetRandHash();
|
||||||
|
pourtx->serials[1] = GetRandHash();
|
||||||
|
|
||||||
|
BOOST_CHECK_MESSAGE(CheckTransaction(newTx, state), state.GetRejectReason());
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Ensure that values within the pour are well-formed.
|
||||||
|
CMutableTransaction newTx(tx);
|
||||||
|
CValidationState state;
|
||||||
|
|
||||||
|
newTx.vpour.push_back(CPourTx());
|
||||||
|
|
||||||
|
CPourTx *pourtx = &newTx.vpour[0];
|
||||||
|
pourtx->vpub_old = -1;
|
||||||
|
|
||||||
|
BOOST_CHECK(!CheckTransaction(newTx, state));
|
||||||
|
BOOST_CHECK(state.GetRejectReason() == "bad-txns-vpub_old-negative");
|
||||||
|
|
||||||
|
pourtx->vpub_old = MAX_MONEY + 1;
|
||||||
|
|
||||||
|
BOOST_CHECK(!CheckTransaction(newTx, state));
|
||||||
|
BOOST_CHECK(state.GetRejectReason() == "bad-txns-vpub_old-toolarge");
|
||||||
|
|
||||||
|
pourtx->vpub_old = 0;
|
||||||
|
pourtx->vpub_new = -1;
|
||||||
|
|
||||||
|
BOOST_CHECK(!CheckTransaction(newTx, state));
|
||||||
|
BOOST_CHECK(state.GetRejectReason() == "bad-txns-vpub_new-negative");
|
||||||
|
|
||||||
|
pourtx->vpub_new = MAX_MONEY + 1;
|
||||||
|
|
||||||
|
BOOST_CHECK(!CheckTransaction(newTx, state));
|
||||||
|
BOOST_CHECK(state.GetRejectReason() == "bad-txns-vpub_new-toolarge");
|
||||||
|
|
||||||
|
pourtx->vpub_new = (MAX_MONEY / 2) + 10;
|
||||||
|
|
||||||
|
newTx.vpour.push_back(CPourTx());
|
||||||
|
|
||||||
|
CPourTx *pourtx2 = &newTx.vpour[1];
|
||||||
|
pourtx2->vpub_new = (MAX_MONEY / 2) + 10;
|
||||||
|
|
||||||
|
BOOST_CHECK(!CheckTransaction(newTx, state));
|
||||||
|
BOOST_CHECK(state.GetRejectReason() == "bad-txns-txouttotal-toolarge");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Ensure that serials are never duplicated within a transaction.
|
||||||
|
CMutableTransaction newTx(tx);
|
||||||
|
CValidationState state;
|
||||||
|
|
||||||
|
newTx.vpour.push_back(CPourTx());
|
||||||
|
CPourTx *pourtx = &newTx.vpour[0];
|
||||||
|
|
||||||
|
pourtx->serials[0] = GetRandHash();
|
||||||
|
pourtx->serials[1] = pourtx->serials[0];
|
||||||
|
|
||||||
|
BOOST_CHECK(!CheckTransaction(newTx, state));
|
||||||
|
BOOST_CHECK(state.GetRejectReason() == "bad-pours-serials-duplicate");
|
||||||
|
|
||||||
|
pourtx->serials[1] = GetRandHash();
|
||||||
|
|
||||||
|
newTx.vpour.push_back(CPourTx());
|
||||||
|
CPourTx *pourtx2 = &newTx.vpour[1];
|
||||||
|
|
||||||
|
pourtx2->serials[0] = GetRandHash();
|
||||||
|
pourtx2->serials[1] = pourtx->serials[0];
|
||||||
|
|
||||||
|
BOOST_CHECK(!CheckTransaction(newTx, state));
|
||||||
|
BOOST_CHECK(state.GetRejectReason() == "bad-pours-serials-duplicate");
|
||||||
|
}
|
||||||
|
{
|
||||||
|
// Ensure that coinbase transactions do not have pours.
|
||||||
|
CMutableTransaction newTx(tx);
|
||||||
|
CValidationState state;
|
||||||
|
|
||||||
|
newTx.vpour.push_back(CPourTx());
|
||||||
|
CPourTx *pourtx = &newTx.vpour[0];
|
||||||
|
pourtx->serials[0] = GetRandHash();
|
||||||
|
pourtx->serials[1] = GetRandHash();
|
||||||
|
|
||||||
|
newTx.vin.push_back(CTxIn(uint256(), -1));
|
||||||
|
|
||||||
|
{
|
||||||
|
CTransaction finalNewTx(newTx);
|
||||||
|
BOOST_CHECK(finalNewTx.IsCoinBase());
|
||||||
|
}
|
||||||
|
BOOST_CHECK(!CheckTransaction(newTx, state));
|
||||||
|
BOOST_CHECK(state.GetRejectReason() == "bad-cb-has-pours");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
BOOST_AUTO_TEST_CASE(test_Get)
|
BOOST_AUTO_TEST_CASE(test_Get)
|
||||||
{
|
{
|
||||||
CBasicKeyStore keystore;
|
CBasicKeyStore keystore;
|
||||||
|
|||||||
Reference in New Issue
Block a user