From 69761d8269d645069f5458d20f3af33a8a6dcc8a Mon Sep 17 00:00:00 2001 From: Sean Bowe Date: Tue, 29 Dec 2015 20:20:30 -0700 Subject: [PATCH] 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. --- src/main.cpp | 53 +++++++++++++++- src/test/transaction_tests.cpp | 111 +++++++++++++++++++++++++++++++++ 2 files changed, 162 insertions(+), 2 deletions(-) diff --git a/src/main.cpp b/src/main.cpp index fe072ec6e..703848580 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -847,12 +847,16 @@ unsigned int GetP2SHSigOpCount(const CTransaction& tx, const CCoinsViewCache& in bool CheckTransaction(const CTransaction& tx, CValidationState &state) { // 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"), 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"), REJECT_INVALID, "bad-txns-vout-empty"); + // Size limits if (::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION) > MAX_BLOCK_SIZE) 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"); } + // 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 set vInOutPoints; BOOST_FOREACH(const CTxIn& txin, tx.vin) @@ -884,8 +914,27 @@ bool CheckTransaction(const CTransaction& tx, CValidationState &state) vInOutPoints.insert(txin.prevout); } + // Check for duplicate pour serials in this transaction + set 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()) { + // 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) return state.DoS(100, error("CheckTransaction(): coinbase script size"), REJECT_INVALID, "bad-cb-length"); diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index 28152c64a..34a9372a0 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -285,6 +285,117 @@ SetupDummyInputs(CBasicKeyStore& keystoreRet, CCoinsViewCache& coinsRet) 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) { CBasicKeyStore keystore;