aboutsummaryrefslogtreecommitdiff
path: root/src/wallet
diff options
context:
space:
mode:
Diffstat (limited to 'src/wallet')
-rw-r--r--src/wallet/crypter.cpp2
-rw-r--r--src/wallet/crypter.h2
-rw-r--r--src/wallet/db.cpp6
-rw-r--r--src/wallet/db.h5
-rw-r--r--src/wallet/rpcdump.cpp2
-rw-r--r--src/wallet/rpcwallet.cpp96
-rw-r--r--src/wallet/test/wallet_tests.cpp148
-rw-r--r--src/wallet/wallet.cpp358
-rw-r--r--src/wallet/wallet.h255
-rw-r--r--src/wallet/wallet_ismine.cpp2
-rw-r--r--src/wallet/wallet_ismine.h2
-rw-r--r--src/wallet/walletdb.cpp42
-rw-r--r--src/wallet/walletdb.h8
13 files changed, 640 insertions, 288 deletions
diff --git a/src/wallet/crypter.cpp b/src/wallet/crypter.cpp
index c86ad9758e..95aa4c2593 100644
--- a/src/wallet/crypter.cpp
+++ b/src/wallet/crypter.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2009-2013 The Bitcoin Core developers
+// Copyright (c) 2009-2015 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
diff --git a/src/wallet/crypter.h b/src/wallet/crypter.h
index 70aeb76723..eb06a7866a 100644
--- a/src/wallet/crypter.h
+++ b/src/wallet/crypter.h
@@ -1,4 +1,4 @@
-// Copyright (c) 2009-2014 The Bitcoin Core developers
+// Copyright (c) 2009-2015 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp
index e5bc653c33..d18250b76f 100644
--- a/src/wallet/db.cpp
+++ b/src/wallet/db.cpp
@@ -1,5 +1,5 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2014 The Bitcoin Core developers
+// Copyright (c) 2009-2015 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -85,7 +85,7 @@ bool CDBEnv::Open(const boost::filesystem::path& pathIn)
LogPrintf("CDBEnv::Open: LogDir=%s ErrorFile=%s\n", pathLogDir.string(), pathErrorFile.string());
unsigned int nEnvFlags = 0;
- if (GetBoolArg("-privdb", true))
+ if (GetBoolArg("-privdb", DEFAULT_WALLET_PRIVDB))
nEnvFlags |= DB_PRIVATE;
dbenv->set_lg_dir(pathLogDir.string().c_str());
@@ -293,7 +293,7 @@ void CDB::Flush()
if (fReadOnly)
nMinutes = 1;
- bitdb.dbenv->txn_checkpoint(nMinutes ? GetArg("-dblogsize", 100) * 1024 : 0, nMinutes, 0);
+ bitdb.dbenv->txn_checkpoint(nMinutes ? GetArg("-dblogsize", DEFAULT_WALLET_DBLOGSIZE) * 1024 : 0, nMinutes, 0);
}
void CDB::Close()
diff --git a/src/wallet/db.h b/src/wallet/db.h
index 64071caa3a..01b8c71a04 100644
--- a/src/wallet/db.h
+++ b/src/wallet/db.h
@@ -1,5 +1,5 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2014 The Bitcoin Core developers
+// Copyright (c) 2009-2015 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -20,6 +20,9 @@
#include <db_cxx.h>
+static const unsigned int DEFAULT_WALLET_DBLOGSIZE = 100;
+static const bool DEFAULT_WALLET_PRIVDB = true;
+
extern unsigned int nWalletDBUpdated;
class CDBEnv
diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp
index c431fc4013..b025c37459 100644
--- a/src/wallet/rpcdump.cpp
+++ b/src/wallet/rpcdump.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2009-2014 The Bitcoin Core developers
+// Copyright (c) 2009-2015 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 30b854477b..e68d646096 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -1,5 +1,5 @@
// Copyright (c) 2010 Satoshi Nakamoto
-// Copyright (c) 2009-2014 The Bitcoin Core developers
+// Copyright (c) 2009-2015 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -65,6 +65,8 @@ void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry)
entry.push_back(Pair("blockhash", wtx.hashBlock.GetHex()));
entry.push_back(Pair("blockindex", wtx.nIndex));
entry.push_back(Pair("blocktime", mapBlockIndex[wtx.hashBlock]->GetBlockTime()));
+ } else {
+ entry.push_back(Pair("trusted", wtx.IsTrusted()));
}
uint256 hash = wtx.GetHash();
entry.push_back(Pair("txid", hash.GetHex()));
@@ -386,11 +388,11 @@ UniValue sendtoaddress(const UniValue& params, bool fHelp)
if (fHelp || params.size() < 2 || params.size() > 5)
throw runtime_error(
"sendtoaddress \"bitcoinaddress\" amount ( \"comment\" \"comment-to\" subtractfeefromamount )\n"
- "\nSend an amount to a given address. The amount is a real and is rounded to the nearest 0.00000001\n"
+ "\nSend an amount to a given address.\n"
+ HelpRequiringPassphrase() +
"\nArguments:\n"
"1. \"bitcoinaddress\" (string, required) The bitcoin address to send to.\n"
- "2. \"amount\" (numeric, required) The amount in " + CURRENCY_UNIT + " to send. eg 0.1\n"
+ "2. \"amount\" (numeric or string, required) The amount in " + CURRENCY_UNIT + " to send. eg 0.1\n"
"3. \"comment\" (string, optional) A comment used to store what the transaction is for. \n"
" This is not part of the transaction, just kept in your wallet.\n"
"4. \"comment-to\" (string, optional) A comment to store the name of the person or organization \n"
@@ -835,7 +837,7 @@ UniValue movecmd(const UniValue& params, bool fHelp)
debit.nTime = nNow;
debit.strOtherAccount = strTo;
debit.strComment = strComment;
- walletdb.WriteAccountingEntry(debit);
+ pwalletMain->AddAccountingEntry(debit, walletdb);
// Credit
CAccountingEntry credit;
@@ -845,7 +847,7 @@ UniValue movecmd(const UniValue& params, bool fHelp)
credit.nTime = nNow;
credit.strOtherAccount = strFrom;
credit.strComment = strComment;
- walletdb.WriteAccountingEntry(credit);
+ pwalletMain->AddAccountingEntry(credit, walletdb);
if (!walletdb.TxnCommit())
throw JSONRPCError(RPC_DATABASE_ERROR, "database error");
@@ -862,13 +864,12 @@ UniValue sendfrom(const UniValue& params, bool fHelp)
if (fHelp || params.size() < 3 || params.size() > 6)
throw runtime_error(
"sendfrom \"fromaccount\" \"tobitcoinaddress\" amount ( minconf \"comment\" \"comment-to\" )\n"
- "\nDEPRECATED (use sendtoaddress). Sent an amount from an account to a bitcoin address.\n"
- "The amount is a real and is rounded to the nearest 0.00000001."
+ "\nDEPRECATED (use sendtoaddress). Sent an amount from an account to a bitcoin address."
+ HelpRequiringPassphrase() + "\n"
"\nArguments:\n"
"1. \"fromaccount\" (string, required) The name of the account to send funds from. May be the default account using \"\".\n"
"2. \"tobitcoinaddress\" (string, required) The bitcoin address to send funds to.\n"
- "3. amount (numeric, required) The amount in " + CURRENCY_UNIT + " (transaction fee is added on top).\n"
+ "3. amount (numeric or string, required) The amount in " + CURRENCY_UNIT + " (transaction fee is added on top).\n"
"4. minconf (numeric, optional, default=1) Only use funds with at least this many confirmations.\n"
"5. \"comment\" (string, optional) A comment used to store what the transaction is for. \n"
" This is not part of the transaction, just kept in your wallet.\n"
@@ -933,7 +934,7 @@ UniValue sendmany(const UniValue& params, bool fHelp)
"1. \"fromaccount\" (string, required) DEPRECATED. The account to send the funds from. Should be \"\" for the default account\n"
"2. \"amounts\" (string, required) A json object with addresses and amounts\n"
" {\n"
- " \"address\":amount (numeric) The bitcoin address is the key, the numeric amount in " + CURRENCY_UNIT + " is the value\n"
+ " \"address\":amount (numeric or string) The bitcoin address is the key, the numeric amount (can be string) in " + CURRENCY_UNIT + " is the value\n"
" ,...\n"
" }\n"
"3. minconf (numeric, optional, default=1) Only use the balance confirmed at least this many times.\n"
@@ -1182,6 +1183,8 @@ UniValue ListReceived(const UniValue& params, bool fByAccounts)
obj.push_back(Pair("account", strAccount));
obj.push_back(Pair("amount", ValueFromAmount(nAmount)));
obj.push_back(Pair("confirmations", (nConf == std::numeric_limits<int>::max() ? 0 : nConf)));
+ if (!fByAccounts)
+ obj.push_back(Pair("label", strAccount));
UniValue transactions(UniValue::VARR);
if (it != mapTally.end())
{
@@ -1235,7 +1238,8 @@ UniValue listreceivedbyaddress(const UniValue& params, bool fHelp)
" \"address\" : \"receivingaddress\", (string) The receiving address\n"
" \"account\" : \"accountname\", (string) DEPRECATED. The account of the receiving address. The default account is \"\".\n"
" \"amount\" : x.xxx, (numeric) The total amount in " + CURRENCY_UNIT + " received by the address\n"
- " \"confirmations\" : n (numeric) The number of confirmations of the most recent transaction included\n"
+ " \"confirmations\" : n, (numeric) The number of confirmations of the most recent transaction included\n"
+ " \"label\" : \"label\" (string) A comment for the address/transaction, if any\n"
" }\n"
" ,...\n"
"]\n"
@@ -1271,7 +1275,8 @@ UniValue listreceivedbyaccount(const UniValue& params, bool fHelp)
" \"involvesWatchonly\" : true, (bool) Only returned if imported addresses were involved in transaction\n"
" \"account\" : \"accountname\", (string) The account name of the receiving account\n"
" \"amount\" : x.xxx, (numeric) The total amount received by addresses with this account\n"
- " \"confirmations\" : n (numeric) The number of confirmations of the most recent transaction included\n"
+ " \"confirmations\" : n, (numeric) The number of confirmations of the most recent transaction included\n"
+ " \"label\" : \"label\" (string) A comment for the address/transaction, if any\n"
" }\n"
" ,...\n"
"]\n"
@@ -1318,6 +1323,8 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe
MaybePushAddress(entry, s.destination);
entry.push_back(Pair("category", "send"));
entry.push_back(Pair("amount", ValueFromAmount(-s.amount)));
+ if (pwalletMain->mapAddressBook.count(s.destination))
+ entry.push_back(Pair("label", pwalletMain->mapAddressBook[s.destination].name));
entry.push_back(Pair("vout", s.vout));
entry.push_back(Pair("fee", ValueFromAmount(-nFee)));
if (fLong)
@@ -1355,6 +1362,8 @@ void ListTransactions(const CWalletTx& wtx, const string& strAccount, int nMinDe
entry.push_back(Pair("category", "receive"));
}
entry.push_back(Pair("amount", ValueFromAmount(r.amount)));
+ if (pwalletMain->mapAddressBook.count(r.destination))
+ entry.push_back(Pair("label", account));
entry.push_back(Pair("vout", r.vout));
if (fLong)
WalletTxToJSON(wtx, entry);
@@ -1409,20 +1418,24 @@ UniValue listtransactions(const UniValue& params, bool fHelp)
" \"amount\": x.xxx, (numeric) The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and for the\n"
" 'move' category for moves outbound. It is positive for the 'receive' category,\n"
" and for the 'move' category for inbound funds.\n"
- " \"vout\" : n, (numeric) the vout value\n"
+ " \"vout\": n, (numeric) the vout value\n"
" \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n"
" 'send' category of transactions.\n"
" \"confirmations\": n, (numeric) The number of confirmations for the transaction. Available for 'send' and \n"
- " 'receive' category of transactions.\n"
+ " 'receive' category of transactions. Negative confirmations indicate the\n"
+ " transation conflicts with the block chain\n"
+ " \"trusted\": xxx (bool) Whether we consider the outputs of this unconfirmed transaction safe to spend.\n"
" \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction. Available for 'send' and 'receive'\n"
" category of transactions.\n"
" \"blockindex\": n, (numeric) The block index containing the transaction. Available for 'send' and 'receive'\n"
" category of transactions.\n"
+ " \"blocktime\": xxx, (numeric) The block time in seconds since epoch (1 Jan 1970 GMT).\n"
" \"txid\": \"transactionid\", (string) The transaction id. Available for 'send' and 'receive' category of transactions.\n"
" \"time\": xxx, (numeric) The transaction time in seconds since epoch (midnight Jan 1 1970 GMT).\n"
" \"timereceived\": xxx, (numeric) The time received in seconds since epoch (midnight Jan 1 1970 GMT). Available \n"
" for 'send' and 'receive' category of transactions.\n"
" \"comment\": \"...\", (string) If a comment is associated with the transaction.\n"
+ " \"label\": \"label\" (string) A comment for the address/transaction, if any\n"
" \"otheraccount\": \"accountname\", (string) For the 'move' category of transactions, the account the funds came \n"
" from (for receiving funds, positive amounts), or went to (for sending funds,\n"
" negative amounts).\n"
@@ -1461,11 +1474,10 @@ UniValue listtransactions(const UniValue& params, bool fHelp)
UniValue ret(UniValue::VARR);
- std::list<CAccountingEntry> acentries;
- CWallet::TxItems txOrdered = pwalletMain->OrderedTxItems(acentries, strAccount);
+ const CWallet::TxItems & txOrdered = pwalletMain->wtxOrdered;
// iterate backwards until we have nCount items to return:
- for (CWallet::TxItems::reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
+ for (CWallet::TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
{
CWalletTx *const pwtx = (*it).second.first;
if (pwtx != 0)
@@ -1570,8 +1582,7 @@ UniValue listaccounts(const UniValue& params, bool fHelp)
}
}
- list<CAccountingEntry> acentries;
- CWalletDB(pwalletMain->strWalletFile).ListAccountCreditDebit("*", acentries);
+ const list<CAccountingEntry> & acentries = pwalletMain->laccentries;
BOOST_FOREACH(const CAccountingEntry& entry, acentries)
mapAccountBalances[entry.strAccount] += entry.nCreditDebit;
@@ -1613,6 +1624,7 @@ UniValue listsinceblock(const UniValue& params, bool fHelp)
" \"time\": xxx, (numeric) The transaction time in seconds since epoch (Jan 1 1970 GMT).\n"
" \"timereceived\": xxx, (numeric) The time received in seconds since epoch (Jan 1 1970 GMT). Available for 'send' and 'receive' category of transactions.\n"
" \"comment\": \"...\", (string) If a comment is associated with the transaction.\n"
+ " \"label\" : \"label\" (string) A comment for the address/transaction, if any\n"
" \"to\": \"...\", (string) If a comment to is associated with the transaction.\n"
" ],\n"
" \"lastblock\": \"lastblockhash\" (string) The hash of the last block\n"
@@ -1700,7 +1712,8 @@ UniValue gettransaction(const UniValue& params, bool fHelp)
" \"account\" : \"accountname\", (string) DEPRECATED. The account name involved in the transaction, can be \"\" for the default account.\n"
" \"address\" : \"bitcoinaddress\", (string) The bitcoin address involved in the transaction\n"
" \"category\" : \"send|receive\", (string) The category, either 'send' or 'receive'\n"
- " \"amount\" : x.xxx (numeric) The amount in " + CURRENCY_UNIT + "\n"
+ " \"amount\" : x.xxx, (numeric) The amount in " + CURRENCY_UNIT + "\n"
+ " \"label\" : \"label\", (string) A comment for the address/transaction, if any\n"
" \"vout\" : n, (numeric) the vout value\n"
" }\n"
" ,...\n"
@@ -1750,6 +1763,40 @@ UniValue gettransaction(const UniValue& params, bool fHelp)
return entry;
}
+UniValue abandontransaction(const UniValue& params, bool fHelp)
+{
+ if (!EnsureWalletIsAvailable(fHelp))
+ return NullUniValue;
+
+ if (fHelp || params.size() != 1)
+ throw runtime_error(
+ "abandontransaction \"txid\"\n"
+ "\nMark in-wallet transaction <txid> as abandoned\n"
+ "This will mark this transaction and all its in-wallet descendants as abandoned which will allow\n"
+ "for their inputs to be respent. It can be used to replace \"stuck\" or evicted transactions.\n"
+ "It only works on transactions which are not included in a block and are not currently in the mempool.\n"
+ "It has no effect on transactions which are already conflicted or abandoned.\n"
+ "\nArguments:\n"
+ "1. \"txid\" (string, required) The transaction id\n"
+ "\nResult:\n"
+ "\nExamples:\n"
+ + HelpExampleCli("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
+ + HelpExampleRpc("abandontransaction", "\"1075db55d416d3ca199f55b6084e2115b9345e16c5cf302fc80e9d5fbf5d48d\"")
+ );
+
+ LOCK2(cs_main, pwalletMain->cs_wallet);
+
+ uint256 hash;
+ hash.SetHex(params[0].get_str());
+
+ if (!pwalletMain->mapWallet.count(hash))
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid or non-wallet transaction id");
+ if (!pwalletMain->AbandonTransaction(hash))
+ throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Transaction not eligible for abandonment");
+
+ return NullUniValue;
+}
+
UniValue backupwallet(const UniValue& params, bool fHelp)
{
@@ -2164,9 +2211,9 @@ UniValue settxfee(const UniValue& params, bool fHelp)
if (fHelp || params.size() < 1 || params.size() > 1)
throw runtime_error(
"settxfee amount\n"
- "\nSet the transaction fee per kB.\n"
+ "\nSet the transaction fee per kB. Overwrites the paytxfee parameter.\n"
"\nArguments:\n"
- "1. amount (numeric, required) The transaction fee in " + CURRENCY_UNIT + "/kB rounded to the nearest 0.00000001\n"
+ "1. amount (numeric or sting, required) The transaction fee in " + CURRENCY_UNIT + "/kB\n"
"\nResult\n"
"true|false (boolean) Returns true if successful\n"
"\nExamples:\n"
@@ -2385,7 +2432,7 @@ UniValue fundrawtransaction(const UniValue& params, bool fHelp)
"\nResult:\n"
"{\n"
" \"hex\": \"value\", (string) The resulting raw transaction (hex-encoded string)\n"
- " \"fee\": n, (numeric) The fee added to the transaction\n"
+ " \"fee\": n, (numeric) Fee the resulting transaction pays\n"
" \"changepos\": n (numeric) The position of the added change output, or -1\n"
"}\n"
"\"hex\" \n"
@@ -2407,9 +2454,12 @@ UniValue fundrawtransaction(const UniValue& params, bool fHelp)
if (!DecodeHexTx(origTx, params[0].get_str()))
throw JSONRPCError(RPC_DESERIALIZATION_ERROR, "TX decode failed");
+ if (origTx.vout.size() == 0)
+ throw JSONRPCError(RPC_INVALID_PARAMETER, "TX must have at least one output");
+
bool includeWatching = false;
if (params.size() > 1)
- includeWatching = true;
+ includeWatching = params[1].get_bool();
CMutableTransaction tx(origTx);
CAmount nFee;
diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp
index a5bc52b8dc..e84d588026 100644
--- a/src/wallet/test/wallet_tests.cpp
+++ b/src/wallet/test/wallet_tests.cpp
@@ -1,4 +1,4 @@
-// Copyright (c) 2012-2014 The Bitcoin Core developers
+// Copyright (c) 2012-2015 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -117,7 +117,7 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests)
// try making 34 cents from 1,2,5,10,20 - we can't do it exactly
BOOST_CHECK( wallet.SelectCoinsMinConf(34 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_GT(nValueRet, 34 * CENT); // but should get more than 34 cents
+ BOOST_CHECK_EQUAL(nValueRet, 35 * CENT); // but 35 cents is closest
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U); // the best should be 20+10+5. it's incredibly unlikely the 1 or 2 got included (but possible)
// when we try making 7 cents, the smaller coins (1,2,5) are enough. We should see just 2+5
@@ -185,33 +185,34 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests)
BOOST_CHECK_EQUAL(nValueRet, 2 * COIN); // we should get 2 BTC in 1 coin
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
- // empty the wallet and start again, now with fractions of a cent, to test sub-cent change avoidance
+ // empty the wallet and start again, now with fractions of a cent, to test small change avoidance
+
empty_wallet();
- add_coin(0.1*CENT);
- add_coin(0.2*CENT);
- add_coin(0.3*CENT);
- add_coin(0.4*CENT);
- add_coin(0.5*CENT);
-
- // try making 1 cent from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 = 1.5 cents
- // we'll get sub-cent change whatever happens, so can expect 1.0 exactly
- BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 1 * CENT);
+ add_coin(0.1*MIN_CHANGE);
+ add_coin(0.2*MIN_CHANGE);
+ add_coin(0.3*MIN_CHANGE);
+ add_coin(0.4*MIN_CHANGE);
+ add_coin(0.5*MIN_CHANGE);
+
+ // try making 1 * MIN_CHANGE from the 1.5 * MIN_CHANGE
+ // we'll get change smaller than MIN_CHANGE whatever happens, so can expect MIN_CHANGE exactly
+ BOOST_CHECK( wallet.SelectCoinsMinConf(MIN_CHANGE, 1, 1, vCoins, setCoinsRet, nValueRet));
+ BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE);
- // but if we add a bigger coin, making it possible to avoid sub-cent change, things change:
- add_coin(1111*CENT);
+ // but if we add a bigger coin, small change is avoided
+ add_coin(1111*MIN_CHANGE);
- // try making 1 cent from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5 cents
- BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount
+ // try making 1 from 0.1 + 0.2 + 0.3 + 0.4 + 0.5 + 1111 = 1112.5
+ BOOST_CHECK( wallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, vCoins, setCoinsRet, nValueRet));
+ BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
- // if we add more sub-cent coins:
- add_coin(0.6*CENT);
- add_coin(0.7*CENT);
+ // if we add more small coins:
+ add_coin(0.6*MIN_CHANGE);
+ add_coin(0.7*MIN_CHANGE);
- // and try again to make 1.0 cents, we can still make 1.0 cents
- BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount
+ // and try again to make 1.0 * MIN_CHANGE
+ BOOST_CHECK( wallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, vCoins, setCoinsRet, nValueRet));
+ BOOST_CHECK_EQUAL(nValueRet, 1 * MIN_CHANGE); // we should get the exact amount
// run the 'mtgox' test (see http://blockexplorer.com/tx/29a3efd3ef04f9153d47a990bd7b048a4b2d213daaa5fb8ed670fb85f13bdbcf)
// they tried to consolidate 10 50k coins into one 500k coin, and ended up with 50k in change
@@ -223,45 +224,65 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests)
BOOST_CHECK_EQUAL(nValueRet, 500000 * COIN); // we should get the exact amount
BOOST_CHECK_EQUAL(setCoinsRet.size(), 10U); // in ten coins
- // if there's not enough in the smaller coins to make at least 1 cent change (0.5+0.6+0.7 < 1.0+1.0),
+ // if there's not enough in the smaller coins to make at least 1 * MIN_CHANGE change (0.5+0.6+0.7 < 1.0+1.0),
// we need to try finding an exact subset anyway
// sometimes it will fail, and so we use the next biggest coin:
empty_wallet();
- add_coin(0.5 * CENT);
- add_coin(0.6 * CENT);
- add_coin(0.7 * CENT);
- add_coin(1111 * CENT);
- BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 1111 * CENT); // we get the bigger coin
+ add_coin(0.5 * MIN_CHANGE);
+ add_coin(0.6 * MIN_CHANGE);
+ add_coin(0.7 * MIN_CHANGE);
+ add_coin(1111 * MIN_CHANGE);
+ BOOST_CHECK( wallet.SelectCoinsMinConf(1 * MIN_CHANGE, 1, 1, vCoins, setCoinsRet, nValueRet));
+ BOOST_CHECK_EQUAL(nValueRet, 1111 * MIN_CHANGE); // we get the bigger coin
BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
// but sometimes it's possible, and we use an exact subset (0.4 + 0.6 = 1.0)
empty_wallet();
- add_coin(0.4 * CENT);
- add_coin(0.6 * CENT);
- add_coin(0.8 * CENT);
- add_coin(1111 * CENT);
- BOOST_CHECK( wallet.SelectCoinsMinConf(1 * CENT, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 1 * CENT); // we should get the exact amount
+ add_coin(0.4 * MIN_CHANGE);
+ add_coin(0.6 * MIN_CHANGE);
+ add_coin(0.8 * MIN_CHANGE);
+ add_coin(1111 * MIN_CHANGE);
+ BOOST_CHECK( wallet.SelectCoinsMinConf(MIN_CHANGE, 1, 1, vCoins, setCoinsRet, nValueRet));
+ BOOST_CHECK_EQUAL(nValueRet, MIN_CHANGE); // we should get the exact amount
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U); // in two coins 0.4+0.6
- // test avoiding sub-cent change
+ // test avoiding small change
empty_wallet();
- add_coin(0.0005 * COIN);
- add_coin(0.01 * COIN);
- add_coin(1 * COIN);
+ add_coin(0.05 * MIN_CHANGE);
+ add_coin(1 * MIN_CHANGE);
+ add_coin(100 * MIN_CHANGE);
- // trying to make 1.0001 from these three coins
- BOOST_CHECK( wallet.SelectCoinsMinConf(1.0001 * COIN, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 1.0105 * COIN); // we should get all coins
+ // trying to make 100.01 from these three coins
+ BOOST_CHECK( wallet.SelectCoinsMinConf(100.01 * MIN_CHANGE, 1, 1, vCoins, setCoinsRet, nValueRet));
+ BOOST_CHECK_EQUAL(nValueRet, 101.05 * MIN_CHANGE); // we should get all coins
BOOST_CHECK_EQUAL(setCoinsRet.size(), 3U);
- // but if we try to make 0.999, we should take the bigger of the two small coins to avoid sub-cent change
- BOOST_CHECK( wallet.SelectCoinsMinConf(0.999 * COIN, 1, 1, vCoins, setCoinsRet, nValueRet));
- BOOST_CHECK_EQUAL(nValueRet, 1.01 * COIN); // we should get 1 + 0.01
+ // but if we try to make 99.9, we should take the bigger of the two small coins to avoid small change
+ BOOST_CHECK( wallet.SelectCoinsMinConf(99.9 * MIN_CHANGE, 1, 1, vCoins, setCoinsRet, nValueRet));
+ BOOST_CHECK_EQUAL(nValueRet, 101 * MIN_CHANGE);
BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
+ // test with many inputs
+ for (CAmount amt=1500; amt < COIN; amt*=10) {
+ empty_wallet();
+ // Create 676 inputs (= MAX_STANDARD_TX_SIZE / 148 bytes per input)
+ for (uint16_t j = 0; j < 676; j++)
+ add_coin(amt);
+ BOOST_CHECK(wallet.SelectCoinsMinConf(2000, 1, 1, vCoins, setCoinsRet, nValueRet));
+ if (amt - 2000 < MIN_CHANGE) {
+ // needs more than one input:
+ uint16_t returnSize = std::ceil((2000.0 + MIN_CHANGE)/amt);
+ CAmount returnValue = amt * returnSize;
+ BOOST_CHECK_EQUAL(nValueRet, returnValue);
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), returnSize);
+ } else {
+ // one input is sufficient:
+ BOOST_CHECK_EQUAL(nValueRet, amt);
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 1U);
+ }
+ }
+
// test randomness
{
empty_wallet();
@@ -307,4 +328,37 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests)
empty_wallet();
}
+BOOST_AUTO_TEST_CASE(ApproximateBestSubset)
+{
+ CoinSet setCoinsRet;
+ CAmount nValueRet;
+
+ LOCK(wallet.cs_wallet);
+
+ empty_wallet();
+
+ // Test vValue sort order
+ for (int i = 0; i < 1000; i++)
+ add_coin(1000 * COIN);
+ add_coin(3 * COIN);
+
+ BOOST_CHECK(wallet.SelectCoinsMinConf(1003 * COIN, 1, 6, vCoins, setCoinsRet, nValueRet));
+ BOOST_CHECK_EQUAL(nValueRet, 1003 * COIN);
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 2U);
+
+ empty_wallet();
+
+ // Test trimming
+ for (int i = 0; i < 100; i++)
+ add_coin(10 * COIN);
+ for (int i = 0; i < 100; i++)
+ add_coin(1000 * COIN);
+
+ BOOST_CHECK(wallet.SelectCoinsMinConf(100001 * COIN, 1, 6, vCoins, setCoinsRet, nValueRet));
+ // We need all 100 larger coins and exactly one small coin.
+ // Superfluous small coins must be trimmed from the set:
+ BOOST_CHECK_EQUAL(nValueRet, 100010 * COIN);
+ BOOST_CHECK_EQUAL(setCoinsRet.size(), 101);
+}
+
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index bd3004061b..cbc71aa16b 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1,5 +1,5 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2014 The Bitcoin Core developers
+// Copyright (c) 2009-2015 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -39,15 +39,22 @@ using namespace std;
CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE);
CAmount maxTxFee = DEFAULT_TRANSACTION_MAXFEE;
unsigned int nTxConfirmTarget = DEFAULT_TX_CONFIRM_TARGET;
-bool bSpendZeroConfChange = true;
-bool fSendFreeTransactions = false;
-bool fPayAtLeastCustomFee = true;
+bool bSpendZeroConfChange = DEFAULT_SPEND_ZEROCONF_CHANGE;
+bool fSendFreeTransactions = DEFAULT_SEND_FREE_TRANSACTIONS;
/**
* Fees smaller than this (in satoshi) are considered zero fee (for transaction creation)
* Override with -mintxfee
*/
-CFeeRate CWallet::minTxFee = CFeeRate(1000);
+CFeeRate CWallet::minTxFee = CFeeRate(DEFAULT_TRANSACTION_MINFEE);
+/**
+ * If fee estimation does not have enough data to provide estimates, use this fee instead.
+ * Has no effect if not using fee estimation
+ * Override with -fallbackfee
+ */
+CFeeRate CWallet::fallbackFee = CFeeRate(DEFAULT_FALLBACK_FEE);
+
+const uint256 CMerkleTx::ABANDON_HASH(uint256S("0000000000000000000000000000000000000000000000000000000000000001"));
/** @defgroup mapWallet
*
@@ -456,8 +463,11 @@ bool CWallet::IsSpent(const uint256& hash, unsigned int n) const
{
const uint256& wtxid = it->second;
std::map<uint256, CWalletTx>::const_iterator mit = mapWallet.find(wtxid);
- if (mit != mapWallet.end() && mit->second.GetDepthInMainChain() >= 0)
- return true; // Spent
+ if (mit != mapWallet.end()) {
+ int depth = mit->second.GetDepthInMainChain();
+ if (depth > 0 || (depth == 0 && !mit->second.isAbandoned()))
+ return true; // Spent
+ }
}
return false;
}
@@ -588,31 +598,6 @@ int64_t CWallet::IncOrderPosNext(CWalletDB *pwalletdb)
return nRet;
}
-CWallet::TxItems CWallet::OrderedTxItems(std::list<CAccountingEntry>& acentries, std::string strAccount)
-{
- AssertLockHeld(cs_wallet); // mapWallet
- CWalletDB walletdb(strWalletFile);
-
- // First: get all CWalletTx and CAccountingEntry into a sorted-by-order multimap.
- TxItems txOrdered;
-
- // Note: maintaining indices in the database of (account,time) --> txid and (account, time) --> acentry
- // would make this much faster for applications that do this a lot.
- for (map<uint256, CWalletTx>::iterator it = mapWallet.begin(); it != mapWallet.end(); ++it)
- {
- CWalletTx* wtx = &((*it).second);
- txOrdered.insert(make_pair(wtx->nOrderPos, TxPair(wtx, (CAccountingEntry*)0)));
- }
- acentries.clear();
- walletdb.ListAccountCreditDebit(strAccount, acentries);
- BOOST_FOREACH(CAccountingEntry& entry, acentries)
- {
- txOrdered.insert(make_pair(entry.nOrderPos, TxPair((CWalletTx*)0, &entry)));
- }
-
- return txOrdered;
-}
-
void CWallet::MarkDirty()
{
{
@@ -629,8 +614,18 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD
if (fFromLoadWallet)
{
mapWallet[hash] = wtxIn;
- mapWallet[hash].BindWallet(this);
+ CWalletTx& wtx = mapWallet[hash];
+ wtx.BindWallet(this);
+ wtxOrdered.insert(make_pair(wtx.nOrderPos, TxPair(&wtx, (CAccountingEntry*)0)));
AddToSpends(hash);
+ BOOST_FOREACH(const CTxIn& txin, wtx.vin) {
+ if (mapWallet.count(txin.prevout.hash)) {
+ CWalletTx& prevtx = mapWallet[txin.prevout.hash];
+ if (prevtx.nIndex == -1 && !prevtx.hashUnset()) {
+ MarkConflicted(prevtx.hashBlock, wtx.GetHash());
+ }
+ }
+ }
}
else
{
@@ -644,9 +639,10 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD
{
wtx.nTimeReceived = GetAdjustedTime();
wtx.nOrderPos = IncOrderPosNext(pwalletdb);
+ wtxOrdered.insert(make_pair(wtx.nOrderPos, TxPair(&wtx, (CAccountingEntry*)0)));
wtx.nTimeSmart = wtx.nTimeReceived;
- if (!wtxIn.hashBlock.IsNull())
+ if (!wtxIn.hashUnset())
{
if (mapBlockIndex.count(wtxIn.hashBlock))
{
@@ -655,9 +651,8 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD
{
// Tolerate times up to the last timestamp in the wallet not more than 5 minutes into the future
int64_t latestTolerated = latestNow + 300;
- std::list<CAccountingEntry> acentries;
- TxItems txOrdered = OrderedTxItems(acentries);
- for (TxItems::reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
+ const TxItems & txOrdered = wtxOrdered;
+ for (TxItems::const_reverse_iterator it = txOrdered.rbegin(); it != txOrdered.rend(); ++it)
{
CWalletTx *const pwtx = (*it).second.first;
if (pwtx == &wtx)
@@ -697,7 +692,13 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD
if (!fInsertedNew)
{
// Merge
- if (!wtxIn.hashBlock.IsNull() && wtxIn.hashBlock != wtx.hashBlock)
+ if (!wtxIn.hashUnset() && wtxIn.hashBlock != wtx.hashBlock)
+ {
+ wtx.hashBlock = wtxIn.hashBlock;
+ fUpdated = true;
+ }
+ // If no longer abandoned, update
+ if (wtxIn.hashBlock.IsNull() && wtx.isAbandoned())
{
wtx.hashBlock = wtxIn.hashBlock;
fUpdated = true;
@@ -750,6 +751,20 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl
{
{
AssertLockHeld(cs_wallet);
+
+ if (pblock) {
+ BOOST_FOREACH(const CTxIn& txin, tx.vin) {
+ std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range = mapTxSpends.equal_range(txin.prevout);
+ while (range.first != range.second) {
+ if (range.first->second != tx.GetHash()) {
+ LogPrintf("Transaction %s (in block %s) conflicts with wallet transaction %s (both spend %s:%i)\n", tx.GetHash().ToString(), pblock->GetHash().ToString(), range.first->second.ToString(), range.first->first.hash.ToString(), range.first->first.n);
+ MarkConflicted(pblock->GetHash(), range.first->second);
+ }
+ range.first++;
+ }
+ }
+ }
+
bool fExisted = mapWallet.count(tx.GetHash()) != 0;
if (fExisted && !fUpdate) return false;
if (fExisted || IsMine(tx) || IsFromMe(tx))
@@ -770,9 +785,122 @@ bool CWallet::AddToWalletIfInvolvingMe(const CTransaction& tx, const CBlock* pbl
return false;
}
+bool CWallet::AbandonTransaction(const uint256& hashTx)
+{
+ LOCK2(cs_main, cs_wallet);
+
+ // Do not flush the wallet here for performance reasons
+ CWalletDB walletdb(strWalletFile, "r+", false);
+
+ std::set<uint256> todo;
+ std::set<uint256> done;
+
+ // Can't mark abandoned if confirmed or in mempool
+ assert(mapWallet.count(hashTx));
+ CWalletTx& origtx = mapWallet[hashTx];
+ if (origtx.GetDepthInMainChain() > 0 || origtx.InMempool()) {
+ return false;
+ }
+
+ todo.insert(hashTx);
+
+ while (!todo.empty()) {
+ uint256 now = *todo.begin();
+ todo.erase(now);
+ done.insert(now);
+ assert(mapWallet.count(now));
+ CWalletTx& wtx = mapWallet[now];
+ int currentconfirm = wtx.GetDepthInMainChain();
+ // If the orig tx was not in block, none of its spends can be
+ assert(currentconfirm <= 0);
+ // if (currentconfirm < 0) {Tx and spends are already conflicted, no need to abandon}
+ if (currentconfirm == 0 && !wtx.isAbandoned()) {
+ // If the orig tx was not in block/mempool, none of its spends can be in mempool
+ assert(!wtx.InMempool());
+ wtx.nIndex = -1;
+ wtx.setAbandoned();
+ wtx.MarkDirty();
+ wtx.WriteToDisk(&walletdb);
+ NotifyTransactionChanged(this, wtx.GetHash(), CT_UPDATED);
+ // Iterate over all its outputs, and mark transactions in the wallet that spend them abandoned too
+ TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(hashTx, 0));
+ while (iter != mapTxSpends.end() && iter->first.hash == now) {
+ if (!done.count(iter->second)) {
+ todo.insert(iter->second);
+ }
+ iter++;
+ }
+ // If a transaction changes 'conflicted' state, that changes the balance
+ // available of the outputs it spends. So force those to be recomputed
+ BOOST_FOREACH(const CTxIn& txin, wtx.vin)
+ {
+ if (mapWallet.count(txin.prevout.hash))
+ mapWallet[txin.prevout.hash].MarkDirty();
+ }
+ }
+ }
+
+ return true;
+}
+
+void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx)
+{
+ LOCK2(cs_main, cs_wallet);
+
+ CBlockIndex* pindex;
+ assert(mapBlockIndex.count(hashBlock));
+ pindex = mapBlockIndex[hashBlock];
+ int conflictconfirms = 0;
+ if (chainActive.Contains(pindex)) {
+ conflictconfirms = -(chainActive.Height() - pindex->nHeight + 1);
+ }
+ assert(conflictconfirms < 0);
+
+ // Do not flush the wallet here for performance reasons
+ CWalletDB walletdb(strWalletFile, "r+", false);
+
+ std::set<uint256> todo;
+ std::set<uint256> done;
+
+ todo.insert(hashTx);
+
+ while (!todo.empty()) {
+ uint256 now = *todo.begin();
+ todo.erase(now);
+ done.insert(now);
+ assert(mapWallet.count(now));
+ CWalletTx& wtx = mapWallet[now];
+ int currentconfirm = wtx.GetDepthInMainChain();
+ if (conflictconfirms < currentconfirm) {
+ // Block is 'more conflicted' than current confirm; update.
+ // Mark transaction as conflicted with this block.
+ wtx.nIndex = -1;
+ wtx.hashBlock = hashBlock;
+ wtx.MarkDirty();
+ wtx.WriteToDisk(&walletdb);
+ // Iterate over all its outputs, and mark transactions in the wallet that spend them conflicted too
+ TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(now, 0));
+ while (iter != mapTxSpends.end() && iter->first.hash == now) {
+ if (!done.count(iter->second)) {
+ todo.insert(iter->second);
+ }
+ iter++;
+ }
+ // If a transaction changes 'conflicted' state, that changes the balance
+ // available of the outputs it spends. So force those to be recomputed
+ BOOST_FOREACH(const CTxIn& txin, wtx.vin)
+ {
+ if (mapWallet.count(txin.prevout.hash))
+ mapWallet[txin.prevout.hash].MarkDirty();
+ }
+ }
+ }
+}
+
void CWallet::SyncTransaction(const CTransaction& tx, const CBlock* pblock)
{
LOCK2(cs_main, cs_wallet);
+
if (!AddToWalletIfInvolvingMe(tx, pblock, true))
return; // Not one of ours
@@ -923,7 +1051,7 @@ int CWalletTx::GetRequestCount() const
if (IsCoinBase())
{
// Generated block
- if (!hashBlock.IsNull())
+ if (!hashUnset())
{
map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(hashBlock);
if (mi != pwallet->mapRequestCount.end())
@@ -939,7 +1067,7 @@ int CWalletTx::GetRequestCount() const
nRequests = (*mi).second;
// How about the block it's in?
- if (nRequests == 0 && !hashBlock.IsNull())
+ if (nRequests == 0 && !hashUnset())
{
map<uint256, int>::const_iterator mi = pwallet->mapRequestCount.find(hashBlock);
if (mi != pwallet->mapRequestCount.end())
@@ -988,7 +1116,8 @@ void CWalletTx::GetAmounts(list<COutputEntry>& listReceived,
// In either case, we need to get the destination address
CTxDestination address;
- if (!ExtractDestination(txout.scriptPubKey, address))
+
+ if (!ExtractDestination(txout.scriptPubKey, address) && !txout.scriptPubKey.IsUnspendable())
{
LogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, txid %s\n",
this->GetHash().ToString());
@@ -1078,7 +1207,7 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate)
ShowProgress(_("Rescanning..."), std::max(1, std::min(99, (int)((Checkpoints::GuessVerificationProgress(chainParams.Checkpoints(), pindex, false) - dProgressStart) / (dProgressTip - dProgressStart) * 100))));
CBlock block;
- ReadBlockFromDisk(block, pindex);
+ ReadBlockFromDisk(block, pindex, Params().GetConsensus());
BOOST_FOREACH(CTransaction& tx, block.vtx)
{
if (AddToWalletIfInvolvingMe(tx, &block, fUpdate))
@@ -1112,7 +1241,7 @@ void CWallet::ReacceptWalletTransactions()
int nDepth = wtx.GetDepthInMainChain();
- if (!wtx.IsCoinBase() && nDepth < 0) {
+ if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) {
mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx));
}
}
@@ -1132,7 +1261,7 @@ bool CWalletTx::RelayWalletTransaction()
assert(pwallet->GetBroadcastTransactions());
if (!IsCoinBase())
{
- if (GetDepthInMainChain() == 0) {
+ if (GetDepthInMainChain() == 0 && !isAbandoned()) {
LogPrintf("Relaying wtx %s\n", GetHash().ToString());
RelayTransaction((CTransaction)*this);
return true;
@@ -1313,6 +1442,15 @@ CAmount CWalletTx::GetChange() const
return nChangeCached;
}
+bool CWalletTx::InMempool() const
+{
+ LOCK(mempool.cs);
+ if (mempool.exists(GetHash())) {
+ return true;
+ }
+ return false;
+}
+
bool CWalletTx::IsTrusted() const
{
// Quick answer in most cases
@@ -1326,6 +1464,10 @@ bool CWalletTx::IsTrusted() const
if (!bSpendZeroConfChange || !IsFromMe(ISMINE_ALL)) // using wtx's cached debit
return false;
+ // Don't trust unconfirmed transactions from us unless they are in the mempool.
+ if (!InMempool())
+ return false;
+
// Trusted if all inputs are from us and are in the mempool:
BOOST_FOREACH(const CTxIn& txin, vin)
{
@@ -1497,9 +1639,6 @@ CAmount CWallet::GetImmatureWatchOnlyBalance() const
return nTotal;
}
-/**
- * populate vCoins with vector of available COutputs.
- */
void CWallet::AvailableCoins(vector<COutput>& vCoins, bool fOnlyConfirmed, const CCoinControl *coinControl, bool fIncludeZeroValue) const
{
vCoins.clear();
@@ -1581,6 +1720,16 @@ static void ApproximateBestSubset(vector<pair<CAmount, pair<const CWalletTx*,uns
}
}
}
+
+ //Reduces the approximate best subset by removing any inputs that are smaller than the surplus of nTotal beyond nTargetValue.
+ for (unsigned int i = 0; i < vValue.size(); i++)
+ {
+ if (vfBest[i] && (nBest - vValue[i].first) >= nTargetValue )
+ {
+ vfBest[i] = false;
+ nBest -= vValue[i].first;
+ }
+ }
}
bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, vector<COutput> vCoins,
@@ -1619,7 +1768,7 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int
nValueRet += coin.first;
return true;
}
- else if (n < nTargetValue + CENT)
+ else if (n < nTargetValue + MIN_CHANGE)
{
vValue.push_back(coin);
nTotalLower += n;
@@ -1654,14 +1803,14 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int
vector<char> vfBest;
CAmount nBest;
- ApproximateBestSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest, 1000);
- if (nBest != nTargetValue && nTotalLower >= nTargetValue + CENT)
- ApproximateBestSubset(vValue, nTotalLower, nTargetValue + CENT, vfBest, nBest, 1000);
+ ApproximateBestSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest);
+ if (nBest != nTargetValue && nTotalLower >= nTargetValue + MIN_CHANGE)
+ ApproximateBestSubset(vValue, nTotalLower, nTargetValue + MIN_CHANGE, vfBest, nBest);
// If we have a bigger coin and (either the stochastic approximation didn't find a good solution,
// or the next bigger coin is closer), return the bigger coin
if (coinLowestLarger.second.first &&
- ((nBest != nTargetValue && nBest < nTargetValue + CENT) || coinLowestLarger.first <= nBest))
+ ((nBest != nTargetValue && nBest < nTargetValue + MIN_CHANGE) || coinLowestLarger.first <= nBest))
{
setCoinsRet.insert(coinLowestLarger.second);
nValueRet += coinLowestLarger.first;
@@ -1820,15 +1969,25 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
// Discourage fee sniping.
//
- // However because of a off-by-one-error in previous versions we need to
- // neuter it by setting nLockTime to at least one less than nBestHeight.
- // Secondly currently propagation of transactions created for block heights
- // corresponding to blocks that were just mined may be iffy - transactions
- // aren't re-accepted into the mempool - we additionally neuter the code by
- // going ten blocks back. Doesn't yet do anything for sniping, but does act
- // to shake out wallet bugs like not showing nLockTime'd transactions at
- // all.
- txNew.nLockTime = std::max(0, chainActive.Height() - 10);
+ // For a large miner the value of the transactions in the best block and
+ // the mempool can exceed the cost of deliberately attempting to mine two
+ // blocks to orphan the current best block. By setting nLockTime such that
+ // only the next block can include the transaction, we discourage this
+ // practice as the height restricted and limited blocksize gives miners
+ // considering fee sniping fewer options for pulling off this attack.
+ //
+ // A simple way to think about this is from the wallet's point of view we
+ // always want the blockchain to move forward. By setting nLockTime this
+ // way we're basically making the statement that we only want this
+ // transaction to appear in the next block; we don't want to potentially
+ // encourage reorgs by allowing transactions to appear at lower heights
+ // than the next block in forks of the best chain.
+ //
+ // Of course, the subsidy is high enough, and transaction volume low
+ // enough, that fee sniping isn't a problem yet, but by implementing a fix
+ // now we ensure code won't be written that makes assumptions about
+ // nLockTime that preclude a fix later.
+ txNew.nLockTime = chainActive.Height();
// Secondly occasionally randomly pick a nLockTime even further back, so
// that transactions that are delayed after signing for whatever reason,
@@ -1844,6 +2003,7 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
LOCK2(cs_main, cs_wallet);
{
nFeeRet = 0;
+ // Start with no fee and loop until there is enough fee
while (true)
{
txNew.vin.clear();
@@ -1904,6 +2064,7 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
//a chance at a free transaction.
//But mempool inputs might still be in the mempool, so their age stays 0
int age = pcoin.first->GetDepthInMainChain();
+ assert(age >= 0);
if (age != 0)
age += 1;
dPriority += (double)nCredit * age;
@@ -2035,17 +2196,16 @@ bool CWallet::CreateTransaction(const vector<CRecipient>& vecSend, CWalletTx& wt
if (fSendFreeTransactions && nBytes <= MAX_FREE_TRANSACTION_CREATE_SIZE)
{
// Not enough fee: enough priority?
- double dPriorityNeeded = mempool.estimatePriority(nTxConfirmTarget);
- // Not enough mempool history to estimate: use hard-coded AllowFree.
- if (dPriorityNeeded <= 0 && AllowFree(dPriority))
- break;
-
- // Small enough, and priority high enough, to send for free
- if (dPriorityNeeded > 0 && dPriority >= dPriorityNeeded)
+ double dPriorityNeeded = mempool.estimateSmartPriority(nTxConfirmTarget);
+ // Require at least hard-coded AllowFree.
+ if (dPriority >= dPriorityNeeded && AllowFree(dPriority))
break;
}
CAmount nFeeNeeded = GetMinimumFee(nBytes, nTxConfirmTarget, mempool);
+ if (coinControl && nFeeNeeded > 0 && coinControl->nMinimumTotalFee > nFeeNeeded) {
+ nFeeNeeded = coinControl->nMinimumTotalFee;
+ }
// If we made it here and we aren't even able to meet the relay fee on the next pass, give up
// because we must be at the maximum allowed fee.
@@ -2120,23 +2280,37 @@ bool CWallet::CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey)
return true;
}
+bool CWallet::AddAccountingEntry(const CAccountingEntry& acentry, CWalletDB & pwalletdb)
+{
+ if (!pwalletdb.WriteAccountingEntry_Backend(acentry))
+ return false;
+
+ laccentries.push_back(acentry);
+ CAccountingEntry & entry = laccentries.back();
+ wtxOrdered.insert(make_pair(entry.nOrderPos, TxPair((CWalletTx*)0, &entry)));
+
+ return true;
+}
+
+CAmount CWallet::GetRequiredFee(unsigned int nTxBytes)
+{
+ return std::max(minTxFee.GetFee(nTxBytes), ::minRelayTxFee.GetFee(nTxBytes));
+}
+
CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool)
{
// payTxFee is user-set "I want to pay this much"
CAmount nFeeNeeded = payTxFee.GetFee(nTxBytes);
- // user selected total at least (default=true)
- if (fPayAtLeastCustomFee && nFeeNeeded > 0 && nFeeNeeded < payTxFee.GetFeePerK())
- nFeeNeeded = payTxFee.GetFeePerK();
// User didn't set: use -txconfirmtarget to estimate...
- if (nFeeNeeded == 0)
- nFeeNeeded = pool.estimateFee(nConfirmTarget).GetFee(nTxBytes);
- // ... unless we don't have enough mempool data, in which case fall
- // back to a hard-coded fee
- if (nFeeNeeded == 0)
- nFeeNeeded = minTxFee.GetFee(nTxBytes);
- // prevent user from paying a non-sense fee (like 1 satoshi): 0 < fee < minRelayFee
- if (nFeeNeeded < ::minRelayTxFee.GetFee(nTxBytes))
- nFeeNeeded = ::minRelayTxFee.GetFee(nTxBytes);
+ if (nFeeNeeded == 0) {
+ int estimateFoundTarget = nConfirmTarget;
+ nFeeNeeded = pool.estimateSmartFee(nConfirmTarget, &estimateFoundTarget).GetFee(nTxBytes);
+ // ... unless we don't have enough mempool data for estimatefee, then use fallbackFee
+ if (nFeeNeeded == 0)
+ nFeeNeeded = fallbackFee.GetFee(nTxBytes);
+ }
+ // prevent user from paying a fee below minRelayTxFee or minTxFee
+ nFeeNeeded = std::max(nFeeNeeded, GetRequiredFee(nTxBytes));
// But always obey the maximum
if (nFeeNeeded > maxTxFee)
nFeeNeeded = maxTxFee;
@@ -2270,7 +2444,7 @@ bool CWallet::NewKeyPool()
if (IsLocked())
return false;
- int64_t nKeys = max(GetArg("-keypool", 100), (int64_t)0);
+ int64_t nKeys = max(GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t)0);
for (int i = 0; i < nKeys; i++)
{
int64_t nIndex = i+1;
@@ -2297,7 +2471,7 @@ bool CWallet::TopUpKeyPool(unsigned int kpSize)
if (kpSize > 0)
nTargetSize = kpSize;
else
- nTargetSize = max(GetArg("-keypool", 100), (int64_t) 0);
+ nTargetSize = max(GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), (int64_t) 0);
while (setKeyPool.size() < (nTargetSize + 1))
{
@@ -2824,10 +2998,11 @@ int CMerkleTx::SetMerkleBranch(const CBlock& block)
return chainActive.Height() - pindex->nHeight + 1;
}
-int CMerkleTx::GetDepthInMainChainINTERNAL(const CBlockIndex* &pindexRet) const
+int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const
{
- if (hashBlock.IsNull() || nIndex == -1)
+ if (hashUnset())
return 0;
+
AssertLockHeld(cs_main);
// Find the block it claims to be in
@@ -2839,17 +3014,7 @@ int CMerkleTx::GetDepthInMainChainINTERNAL(const CBlockIndex* &pindexRet) const
return 0;
pindexRet = pindex;
- return chainActive.Height() - pindex->nHeight + 1;
-}
-
-int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const
-{
- AssertLockHeld(cs_main);
- int nResult = GetDepthInMainChainINTERNAL(pindexRet);
- if (nResult == 0 && !mempool.exists(GetHash()))
- return -1; // Not in chain, not in mempool
-
- return nResult;
+ return ((nIndex == -1) ? (-1) : 1) * (chainActive.Height() - pindex->nHeight + 1);
}
int CMerkleTx::GetBlocksToMaturity() const
@@ -2863,6 +3028,5 @@ int CMerkleTx::GetBlocksToMaturity() const
bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, bool fRejectAbsurdFee)
{
CValidationState state;
- return ::AcceptToMemoryPool(mempool, state, *this, fLimitFree, NULL, fRejectAbsurdFee);
+ return ::AcceptToMemoryPool(mempool, state, *this, fLimitFree, NULL, false, fRejectAbsurdFee);
}
-
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 34e98cfb81..2176a5ff66 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -1,5 +1,5 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2014 The Bitcoin Core developers
+// Copyright (c) 2009-2015 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -35,22 +35,32 @@ extern CAmount maxTxFee;
extern unsigned int nTxConfirmTarget;
extern bool bSpendZeroConfChange;
extern bool fSendFreeTransactions;
-extern bool fPayAtLeastCustomFee;
+static const unsigned int DEFAULT_KEYPOOL_SIZE = 100;
//! -paytxfee default
static const CAmount DEFAULT_TRANSACTION_FEE = 0;
//! -paytxfee will warn if called with a higher fee than this amount (in satoshis) per KB
static const CAmount nHighTransactionFeeWarning = 0.01 * COIN;
+//! -fallbackfee default
+static const CAmount DEFAULT_FALLBACK_FEE = 20000;
+//! -mintxfee default
+static const CAmount DEFAULT_TRANSACTION_MINFEE = 1000;
//! -maxtxfee default
static const CAmount DEFAULT_TRANSACTION_MAXFEE = 0.1 * COIN;
+//! minimum change amount
+static const CAmount MIN_CHANGE = CENT;
+//! Default for -spendzeroconfchange
+static const bool DEFAULT_SPEND_ZEROCONF_CHANGE = true;
+//! Default for -sendfreetransactions
+static const bool DEFAULT_SEND_FREE_TRANSACTIONS = false;
//! -txconfirmtarget default
static const unsigned int DEFAULT_TX_CONFIRM_TARGET = 2;
//! -maxtxfee will warn if called with a higher fee than this amount (in satoshis)
static const CAmount nHighTransactionMaxFeeWarning = 100 * nHighTransactionFeeWarning;
//! Largest (in bytes) free transaction we're willing to create
static const unsigned int MAX_FREE_TRANSACTION_CREATE_SIZE = 1000;
+static const bool DEFAULT_WALLETBROADCAST = true;
-class CAccountingEntry;
class CBlockIndex;
class CCoinControl;
class COutput;
@@ -147,10 +157,17 @@ struct COutputEntry
class CMerkleTx : public CTransaction
{
private:
- int GetDepthInMainChainINTERNAL(const CBlockIndex* &pindexRet) const;
+ /** Constant used in hashBlock to indicate tx has been abandoned */
+ static const uint256 ABANDON_HASH;
public:
uint256 hashBlock;
+
+ /* An nIndex == -1 means that hashBlock (in nonzero) refers to the earliest
+ * block in the chain we know this or any in-wallet dependency conflicts
+ * with. Older clients interpret nIndex == -1 as unconfirmed for backward
+ * compatibility.
+ */
int nIndex;
CMerkleTx()
@@ -183,18 +200,20 @@ public:
int SetMerkleBranch(const CBlock& block);
-
/**
* Return depth of transaction in blockchain:
- * -1 : not in blockchain, and not in memory pool (conflicted transaction)
+ * <0 : conflicts with a transaction this deep in the blockchain
* 0 : in memory pool, waiting to be included in a block
* >=1 : this many blocks deep in the main chain
*/
int GetDepthInMainChain(const CBlockIndex* &pindexRet) const;
int GetDepthInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet); }
- bool IsInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChainINTERNAL(pindexRet) > 0; }
+ bool IsInMainChain() const { const CBlockIndex *pindexRet; return GetDepthInMainChain(pindexRet) > 0; }
int GetBlocksToMaturity() const;
bool AcceptToMemoryPool(bool fLimitFree=true, bool fRejectAbsurdFee=true);
+ bool hashUnset() const { return (hashBlock.IsNull() || hashBlock == ABANDON_HASH); }
+ bool isAbandoned() const { return (hashBlock == ABANDON_HASH); }
+ void setAbandoned() { hashBlock = ABANDON_HASH; }
};
/**
@@ -373,6 +392,7 @@ public:
// True if only scriptSigs are different
bool IsEquivalentTo(const CWalletTx& tx) const;
+ bool InMempool() const;
bool IsTrusted() const;
bool WriteToDisk(CWalletDB *pwalletdb);
@@ -433,6 +453,86 @@ public:
}
};
+/**
+ * Internal transfers.
+ * Database key is acentry<account><counter>.
+ */
+class CAccountingEntry
+{
+public:
+ std::string strAccount;
+ CAmount nCreditDebit;
+ int64_t nTime;
+ std::string strOtherAccount;
+ std::string strComment;
+ mapValue_t mapValue;
+ int64_t nOrderPos; //! position in ordered transaction list
+ uint64_t nEntryNo;
+
+ CAccountingEntry()
+ {
+ SetNull();
+ }
+
+ void SetNull()
+ {
+ nCreditDebit = 0;
+ nTime = 0;
+ strAccount.clear();
+ strOtherAccount.clear();
+ strComment.clear();
+ nOrderPos = -1;
+ nEntryNo = 0;
+ }
+
+ ADD_SERIALIZE_METHODS;
+
+ template <typename Stream, typename Operation>
+ inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
+ if (!(nType & SER_GETHASH))
+ READWRITE(nVersion);
+ //! Note: strAccount is serialized as part of the key, not here.
+ READWRITE(nCreditDebit);
+ READWRITE(nTime);
+ READWRITE(LIMITED_STRING(strOtherAccount, 65536));
+
+ if (!ser_action.ForRead())
+ {
+ WriteOrderPos(nOrderPos, mapValue);
+
+ if (!(mapValue.empty() && _ssExtra.empty()))
+ {
+ CDataStream ss(nType, nVersion);
+ ss.insert(ss.begin(), '\0');
+ ss << mapValue;
+ ss.insert(ss.end(), _ssExtra.begin(), _ssExtra.end());
+ strComment.append(ss.str());
+ }
+ }
+
+ READWRITE(LIMITED_STRING(strComment, 65536));
+
+ size_t nSepPos = strComment.find("\0", 0, 1);
+ if (ser_action.ForRead())
+ {
+ mapValue.clear();
+ if (std::string::npos != nSepPos)
+ {
+ CDataStream ss(std::vector<char>(strComment.begin() + nSepPos + 1, strComment.end()), nType, nVersion);
+ ss >> mapValue;
+ _ssExtra = std::vector<char>(ss.begin(), ss.end());
+ }
+ ReadOrderPos(nOrderPos, mapValue);
+ }
+ if (std::string::npos != nSepPos)
+ strComment.erase(nSepPos);
+
+ mapValue.erase("n");
+ }
+
+private:
+ std::vector<char> _ssExtra;
+};
/**
@@ -442,6 +542,11 @@ public:
class CWallet : public CCryptoKeyStore, public CValidationInterface
{
private:
+ /**
+ * Select a set of coins such that nValueRet >= nTargetValue and at least
+ * all coins from coinControl are selected; Never select unconfirmed coins
+ * if they are not ours
+ */
bool SelectCoins(const CAmount& nTargetValue, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet, const CCoinControl *coinControl = NULL) const;
CWalletDB *pwalletdbEncryption;
@@ -466,6 +571,9 @@ private:
void AddToSpends(const COutPoint& outpoint, const uint256& wtxid);
void AddToSpends(const uint256& wtxid);
+ /* Mark a transaction (and its in-wallet descendants) as conflicting with a particular block. */
+ void MarkConflicted(const uint256& hashBlock, const uint256& hashTx);
+
void SyncMetaData(std::pair<TxSpends::iterator, TxSpends::iterator>);
public:
@@ -522,6 +630,11 @@ public:
}
std::map<uint256, CWalletTx> mapWallet;
+ std::list<CAccountingEntry> laccentries;
+
+ typedef std::pair<CWalletTx*, CAccountingEntry*> TxPair;
+ typedef std::multimap<int64_t, TxPair > TxItems;
+ TxItems wtxOrdered;
int64_t nOrderPosNext;
std::map<uint256, int> mapRequestCount;
@@ -539,7 +652,17 @@ public:
//! check whether we are allowed to upgrade (or already support) to the named feature
bool CanSupportFeature(enum WalletFeature wf) { AssertLockHeld(cs_wallet); return nWalletMaxVersion >= wf; }
+ /**
+ * populate vCoins with vector of available COutputs.
+ */
void AvailableCoins(std::vector<COutput>& vCoins, bool fOnlyConfirmed=true, const CCoinControl *coinControl = NULL, bool fIncludeZeroValue=false) const;
+
+ /**
+ * Shuffle and select coins until nTargetValue is reached while avoiding
+ * small change; This method is stochastic for some inputs and upon
+ * completion the coin set and corresponding actual target value is
+ * assembled
+ */
bool SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int nConfTheirs, std::vector<COutput> vCoins, std::set<std::pair<const CWalletTx*,unsigned int> >& setCoinsRet, CAmount& nValueRet) const;
bool IsSpent(const uint256& hash, unsigned int n) const;
@@ -598,16 +721,6 @@ public:
*/
int64_t IncOrderPosNext(CWalletDB *pwalletdb = NULL);
- typedef std::pair<CWalletTx*, CAccountingEntry*> TxPair;
- typedef std::multimap<int64_t, TxPair > TxItems;
-
- /**
- * Get the wallet's activity log
- * @return multimap of ordered transactions and accounting entries
- * @warning Returned pointers are *only* valid within the scope of passed acentries
- */
- TxItems OrderedTxItems(std::list<CAccountingEntry>& acentries, std::string strAccount = "");
-
void MarkDirty();
bool AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletDB* pwalletdb);
void SyncTransaction(const CTransaction& tx, const CBlock* pblock);
@@ -622,13 +735,35 @@ public:
CAmount GetWatchOnlyBalance() const;
CAmount GetUnconfirmedWatchOnlyBalance() const;
CAmount GetImmatureWatchOnlyBalance() const;
+
+ /**
+ * Insert additional inputs into the transaction by
+ * calling CreateTransaction();
+ */
bool FundTransaction(CMutableTransaction& tx, CAmount& nFeeRet, int& nChangePosRet, std::string& strFailReason, bool includeWatching);
+
+ /**
+ * Create a new transaction paying the recipients with a set of coins
+ * selected by SelectCoins(); Also create the change output, when needed
+ */
bool CreateTransaction(const std::vector<CRecipient>& vecSend, CWalletTx& wtxNew, CReserveKey& reservekey, CAmount& nFeeRet, int& nChangePosRet,
std::string& strFailReason, const CCoinControl *coinControl = NULL, bool sign = true);
bool CommitTransaction(CWalletTx& wtxNew, CReserveKey& reservekey);
+ bool AddAccountingEntry(const CAccountingEntry&, CWalletDB & pwalletdb);
+
static CFeeRate minTxFee;
+ static CFeeRate fallbackFee;
+ /**
+ * Estimate the minimum fee considering user set parameters
+ * and the required fee
+ */
static CAmount GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarget, const CTxMemPool& pool);
+ /**
+ * Return the minimum required fee taking into account the
+ * floating relay fee and user set minimum transaction fee
+ */
+ static CAmount GetRequiredFee(unsigned int nTxBytes);
bool NewKeyPool();
bool TopUpKeyPool(unsigned int kpSize = 0);
@@ -736,6 +871,9 @@ public:
bool GetBroadcastTransactions() const { return fBroadcastTransactions; }
/** Set whether this wallet broadcasts transactions. */
void SetBroadcastTransactions(bool broadcast) { fBroadcastTransactions = broadcast; }
+
+ /* Mark a transaction (and it in-wallet descendants) as abandoned so its inputs may be respent. */
+ bool AbandonTransaction(const uint256& hashTx);
};
/** A key allocated from the key pool. */
@@ -793,87 +931,4 @@ public:
}
};
-
-
-/**
- * Internal transfers.
- * Database key is acentry<account><counter>.
- */
-class CAccountingEntry
-{
-public:
- std::string strAccount;
- CAmount nCreditDebit;
- int64_t nTime;
- std::string strOtherAccount;
- std::string strComment;
- mapValue_t mapValue;
- int64_t nOrderPos; //! position in ordered transaction list
- uint64_t nEntryNo;
-
- CAccountingEntry()
- {
- SetNull();
- }
-
- void SetNull()
- {
- nCreditDebit = 0;
- nTime = 0;
- strAccount.clear();
- strOtherAccount.clear();
- strComment.clear();
- nOrderPos = -1;
- nEntryNo = 0;
- }
-
- ADD_SERIALIZE_METHODS;
-
- template <typename Stream, typename Operation>
- inline void SerializationOp(Stream& s, Operation ser_action, int nType, int nVersion) {
- if (!(nType & SER_GETHASH))
- READWRITE(nVersion);
- //! Note: strAccount is serialized as part of the key, not here.
- READWRITE(nCreditDebit);
- READWRITE(nTime);
- READWRITE(LIMITED_STRING(strOtherAccount, 65536));
-
- if (!ser_action.ForRead())
- {
- WriteOrderPos(nOrderPos, mapValue);
-
- if (!(mapValue.empty() && _ssExtra.empty()))
- {
- CDataStream ss(nType, nVersion);
- ss.insert(ss.begin(), '\0');
- ss << mapValue;
- ss.insert(ss.end(), _ssExtra.begin(), _ssExtra.end());
- strComment.append(ss.str());
- }
- }
-
- READWRITE(LIMITED_STRING(strComment, 65536));
-
- size_t nSepPos = strComment.find("\0", 0, 1);
- if (ser_action.ForRead())
- {
- mapValue.clear();
- if (std::string::npos != nSepPos)
- {
- CDataStream ss(std::vector<char>(strComment.begin() + nSepPos + 1, strComment.end()), nType, nVersion);
- ss >> mapValue;
- _ssExtra = std::vector<char>(ss.begin(), ss.end());
- }
- ReadOrderPos(nOrderPos, mapValue);
- }
- if (std::string::npos != nSepPos)
- strComment.erase(nSepPos);
-
- mapValue.erase("n");
- }
-
-private:
- std::vector<char> _ssExtra;
-};
-
#endif // BITCOIN_WALLET_WALLET_H
diff --git a/src/wallet/wallet_ismine.cpp b/src/wallet/wallet_ismine.cpp
index d27b1531e3..ebda5cc53d 100644
--- a/src/wallet/wallet_ismine.cpp
+++ b/src/wallet/wallet_ismine.cpp
@@ -1,5 +1,5 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2014 The Bitcoin Core developers
+// Copyright (c) 2009-2015 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
diff --git a/src/wallet/wallet_ismine.h b/src/wallet/wallet_ismine.h
index 9f45f76c6b..93cdf6ab8f 100644
--- a/src/wallet/wallet_ismine.h
+++ b/src/wallet/wallet_ismine.h
@@ -1,5 +1,5 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2014 The Bitcoin Core developers
+// Copyright (c) 2009-2015 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp
index 0624e442d1..5266946ca0 100644
--- a/src/wallet/walletdb.cpp
+++ b/src/wallet/walletdb.cpp
@@ -1,5 +1,5 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2014 The Bitcoin Core developers
+// Copyright (c) 2009-2015 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -15,6 +15,12 @@
#include "utiltime.h"
#include "wallet/wallet.h"
+#if defined(FORCE_BOOST_EMULATED_SCOPED_ENUMS)
+#define BOOST_NO_SCOPED_ENUMS
+#define BOOST_NO_CXX11_SCOPED_ENUMS
+#endif
+
+#include <boost/version.hpp>
#include <boost/filesystem.hpp>
#include <boost/foreach.hpp>
#include <boost/scoped_ptr.hpp>
@@ -113,19 +119,19 @@ bool CWalletDB::WriteMasterKey(unsigned int nID, const CMasterKey& kMasterKey)
bool CWalletDB::WriteCScript(const uint160& hash, const CScript& redeemScript)
{
nWalletDBUpdated++;
- return Write(std::make_pair(std::string("cscript"), hash), redeemScript, false);
+ return Write(std::make_pair(std::string("cscript"), hash), *(const CScriptBase*)(&redeemScript), false);
}
bool CWalletDB::WriteWatchOnly(const CScript &dest)
{
nWalletDBUpdated++;
- return Write(std::make_pair(std::string("watchs"), dest), '1');
+ return Write(std::make_pair(std::string("watchs"), *(const CScriptBase*)(&dest)), '1');
}
bool CWalletDB::EraseWatchOnly(const CScript &dest)
{
nWalletDBUpdated++;
- return Erase(std::make_pair(std::string("watchs"), dest));
+ return Erase(std::make_pair(std::string("watchs"), *(const CScriptBase*)(&dest)));
}
bool CWalletDB::WriteBestBlock(const CBlockLocator& locator)
@@ -191,7 +197,7 @@ bool CWalletDB::WriteAccountingEntry(const uint64_t nAccEntryNum, const CAccount
return Write(std::make_pair(std::string("acentry"), std::make_pair(acentry.strAccount, nAccEntryNum)), acentry);
}
-bool CWalletDB::WriteAccountingEntry(const CAccountingEntry& acentry)
+bool CWalletDB::WriteAccountingEntry_Backend(const CAccountingEntry& acentry)
{
return WriteAccountingEntry(++nAccountingEntryNumber, acentry);
}
@@ -421,7 +427,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
else if (strType == "watchs")
{
CScript script;
- ssKey >> script;
+ ssKey >> *(CScriptBase*)(&script);
char fYes;
ssValue >> fYes;
if (fYes == '1')
@@ -512,8 +518,13 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
}
else if (strType == "ckey")
{
- vector<unsigned char> vchPubKey;
+ CPubKey vchPubKey;
ssKey >> vchPubKey;
+ if (!vchPubKey.IsValid())
+ {
+ strErr = "Error reading wallet database: CPubKey corrupt";
+ return false;
+ }
vector<unsigned char> vchPrivKey;
ssValue >> vchPrivKey;
wss.nCKeys++;
@@ -570,7 +581,7 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue,
uint160 hash;
ssKey >> hash;
CScript script;
- ssValue >> script;
+ ssValue >> *(CScriptBase*)(&script);
if (!pwallet->LoadCScript(script))
{
strErr = "Error reading wallet database: LoadCScript failed";
@@ -704,6 +715,12 @@ DBErrors CWalletDB::LoadWallet(CWallet* pwallet)
if (wss.fAnyUnordered)
result = ReorderTransactions(pwallet);
+ pwallet->laccentries.clear();
+ ListAccountCreditDebit("*", pwallet->laccentries);
+ BOOST_FOREACH(CAccountingEntry& entry, pwallet->laccentries) {
+ pwallet->wtxOrdered.insert(make_pair(entry.nOrderPos, CWallet::TxPair((CWalletTx*)0, &entry)));
+ }
+
return result;
}
@@ -799,7 +816,7 @@ void ThreadFlushWalletDB(const string& strFile)
if (fOneThread)
return;
fOneThread = true;
- if (!GetBoolArg("-flushwallet", true))
+ if (!GetBoolArg("-flushwallet", DEFAULT_FLUSHWALLET))
return;
unsigned int nLastSeen = nWalletDBUpdated;
@@ -949,8 +966,13 @@ bool CWalletDB::Recover(CDBEnv& dbenv, const std::string& filename, bool fOnlyKe
CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION);
CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION);
string strType, strErr;
- bool fReadOK = ReadKeyValue(&dummyWallet, ssKey, ssValue,
+ bool fReadOK;
+ {
+ // Required in LoadKeyMetadata():
+ LOCK(dummyWallet.cs_wallet);
+ fReadOK = ReadKeyValue(&dummyWallet, ssKey, ssValue,
wss, strType, strErr);
+ }
if (!IsKeyType(strType))
continue;
if (!fReadOK)
diff --git a/src/wallet/walletdb.h b/src/wallet/walletdb.h
index 270f826aed..8da33dead2 100644
--- a/src/wallet/walletdb.h
+++ b/src/wallet/walletdb.h
@@ -1,5 +1,5 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
-// Copyright (c) 2009-2013 The Bitcoin Core developers
+// Copyright (c) 2009-2015 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
@@ -16,6 +16,8 @@
#include <utility>
#include <vector>
+static const bool DEFAULT_FLUSHWALLET = true;
+
class CAccount;
class CAccountingEntry;
struct CBlockLocator;
@@ -110,6 +112,9 @@ public:
bool WriteMinVersion(int nVersion);
+ /// This writes directly to the database, and will not update the CWallet's cached accounting entries!
+ /// Use wallet.AddAccountingEntry instead, to write *and* update its caches.
+ bool WriteAccountingEntry_Backend(const CAccountingEntry& acentry);
bool ReadAccount(const std::string& strAccount, CAccount& account);
bool WriteAccount(const std::string& strAccount, const CAccount& account);
@@ -118,7 +123,6 @@ public:
/// Erase destination data tuple from wallet database
bool EraseDestData(const std::string &address, const std::string &key);
- bool WriteAccountingEntry(const CAccountingEntry& acentry);
CAmount GetAccountCreditDebit(const std::string& strAccount);
void ListAccountCreditDebit(const std::string& strAccount, std::list<CAccountingEntry>& acentries);