diff --git a/src/main.cpp b/src/main.cpp index aace382d8..d0d6a99eb 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -384,7 +384,7 @@ bool CTransaction::IsStandard() const BOOST_FOREACH(const CTxOut& txout, vout) { if (!::IsStandard(txout.scriptPubKey)) return false; - if (txout.nValue == 0) + if (txout.IsDust()) return false; } return true; diff --git a/src/main.h b/src/main.h index 24b2cb2aa..8c1e6d265 100644 --- a/src/main.h +++ b/src/main.h @@ -439,6 +439,24 @@ public: return !(a == b); } + size_t size() const + { + return sizeof(nValue)+scriptPubKey.size(); + } + + bool IsDust() const + { + // "Dust" is defined in terms of MIN_RELAY_TX_FEE, which + // has units satoshis-per-kilobyte. + // If you'd pay more than 1/3 in fees + // to spend something, then we consider it dust. + // A typical txout is 32 bytes big, and will + // need a CTxIn of at least 148 bytes to spend, + // so dust is a txout less than 54 uBTC + // (5400 satoshis) + return ((nValue*1000)/(3*(size()+148)) < MIN_RELAY_TX_FEE); + } + std::string ToString() const { if (scriptPubKey.size() < 6) diff --git a/src/test/script_P2SH_tests.cpp b/src/test/script_P2SH_tests.cpp index 3444726ca..ee0d25a2f 100644 --- a/src/test/script_P2SH_tests.cpp +++ b/src/test/script_P2SH_tests.cpp @@ -78,7 +78,9 @@ BOOST_AUTO_TEST_CASE(sign) for (int i = 0; i < 4; i++) { txFrom.vout[i].scriptPubKey = evalScripts[i]; + txFrom.vout[i].nValue = COIN; txFrom.vout[i+4].scriptPubKey = standardScripts[i]; + txFrom.vout[i+4].nValue = COIN; } BOOST_CHECK(txFrom.IsStandard()); @@ -169,6 +171,7 @@ BOOST_AUTO_TEST_CASE(set) for (int i = 0; i < 4; i++) { txFrom.vout[i].scriptPubKey = outer[i]; + txFrom.vout[i].nValue = CENT; } BOOST_CHECK(txFrom.IsStandard()); @@ -179,7 +182,7 @@ BOOST_AUTO_TEST_CASE(set) txTo[i].vout.resize(1); txTo[i].vin[0].prevout.n = i; txTo[i].vin[0].prevout.hash = txFrom.GetHash(); - txTo[i].vout[0].nValue = 1; + txTo[i].vout[0].nValue = 1*CENT; txTo[i].vout[0].scriptPubKey = inner[i]; BOOST_CHECK_MESSAGE(IsMine(keystore, txFrom.vout[i].scriptPubKey), strprintf("IsMine %d", i)); } diff --git a/src/test/transaction_tests.cpp b/src/test/transaction_tests.cpp index f44d46fdb..ddff2acd4 100644 --- a/src/test/transaction_tests.cpp +++ b/src/test/transaction_tests.cpp @@ -242,24 +242,34 @@ BOOST_AUTO_TEST_CASE(test_Get) BOOST_CHECK(!t1.AreInputsStandard(coins)); } -BOOST_AUTO_TEST_CASE(test_GetThrow) +BOOST_AUTO_TEST_CASE(test_IsStandard) { CBasicKeyStore keystore; CCoinsView coinsDummy; CCoinsViewCache coins(coinsDummy); std::vector dummyTransactions = SetupDummyInputs(keystore, coins); - CTransaction t1; - t1.vin.resize(3); - t1.vin[0].prevout.hash = dummyTransactions[0].GetHash(); - t1.vin[0].prevout.n = 0; - t1.vin[1].prevout.hash = dummyTransactions[1].GetHash();; - t1.vin[1].prevout.n = 0; - t1.vin[2].prevout.hash = dummyTransactions[1].GetHash();; - t1.vin[2].prevout.n = 1; - t1.vout.resize(2); - t1.vout[0].nValue = 90*CENT; - t1.vout[0].scriptPubKey << OP_1; + CTransaction t; + t.vin.resize(1); + t.vin[0].prevout.hash = dummyTransactions[0].GetHash(); + t.vin[0].prevout.n = 1; + t.vin[0].scriptSig << std::vector(65, 0); + t.vout.resize(1); + t.vout[0].nValue = 90*CENT; + CKey key; + key.MakeNewKey(true); + t.vout[0].scriptPubKey.SetDestination(key.GetPubKey().GetID()); + + BOOST_CHECK(t.IsStandard()); + + t.vout[0].nValue = 5011; // dust + BOOST_CHECK(!t.IsStandard()); + + t.vout[0].nValue = 6011; // not dust + BOOST_CHECK(t.IsStandard()); + + t.vout[0].scriptPubKey = CScript() << OP_1; + BOOST_CHECK(!t.IsStandard()); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/wallet_tests.cpp b/src/test/wallet_tests.cpp index a4cbfaee4..a14f6b2b7 100644 --- a/src/test/wallet_tests.cpp +++ b/src/test/wallet_tests.cpp @@ -21,13 +21,12 @@ static vector vCoins; static void add_coin(int64 nValue, int nAge = 6*24, bool fIsFromMe = false, int nInput=0) { - static int i; - CTransaction* tx = new CTransaction; - tx->nLockTime = i++; // so all transactions get different hashes - tx->vout.resize(nInput+1); - tx->vout[nInput].nValue = nValue; - CWalletTx* wtx = new CWalletTx(&wallet, *tx); - delete tx; + static int nextLockTime = 0; + CTransaction tx; + tx.nLockTime = nextLockTime++; // so all transactions get different hashes + tx.vout.resize(nInput+1); + tx.vout[nInput].nValue = nValue; + CWalletTx* wtx = new CWalletTx(&wallet, tx); if (fIsFromMe) { // IsFromMe() returns (GetDebit() > 0), and GetDebit() is 0 if vin.empty(), @@ -55,8 +54,8 @@ static bool equal_sets(CoinSet a, CoinSet b) BOOST_AUTO_TEST_CASE(coin_selection_tests) { - static CoinSet setCoinsRet, setCoinsRet2; - static int64 nValueRet; + CoinSet setCoinsRet, setCoinsRet2; + int64 nValueRet; // test multiple times to allow for differences in the shuffle order for (int i = 0; i < RUN_TESTS; i++) diff --git a/src/wallet.cpp b/src/wallet.cpp index c7eb4f74e..1554b1576 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -1162,7 +1162,12 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CW double dPriority = 0; // vouts to the payees BOOST_FOREACH (const PAIRTYPE(CScript, int64)& s, vecSend) - wtxNew.vout.push_back(CTxOut(s.second, s.first)); + { + CTxOut txout(s.second, s.first); + if (txout.IsDust()) + return false; + wtxNew.vout.push_back(txout); + } // Choose coins to use set > setCoins; @@ -1208,9 +1213,21 @@ bool CWallet::CreateTransaction(const vector >& vecSend, CW CScript scriptChange; scriptChange.SetDestination(vchPubKey.GetID()); - // Insert change txn at random position: - vector::iterator position = wtxNew.vout.begin()+GetRandInt(wtxNew.vout.size()+1); - wtxNew.vout.insert(position, CTxOut(nChange, scriptChange)); + CTxOut newTxOut(nChange, scriptChange); + + // Never create dust outputs; if we would, just + // add the dust to the fee. + if (newTxOut.IsDust()) + { + nFeeRet += nChange; + reservekey.ReturnKey(); + } + else + { + // Insert change txn at random position: + vector::iterator position = wtxNew.vout.begin()+GetRandInt(wtxNew.vout.size()+1); + wtxNew.vout.insert(position, newTxOut); + } } else reservekey.ReturnKey();