diff options
Diffstat (limited to 'src/wallet')
-rw-r--r-- | src/wallet/db.cpp | 2 | ||||
-rw-r--r-- | src/wallet/rpcwallet.cpp | 169 | ||||
-rw-r--r-- | src/wallet/rpcwallet.h | 10 | ||||
-rw-r--r-- | src/wallet/test/wallet_tests.cpp | 25 | ||||
-rw-r--r-- | src/wallet/wallet.cpp | 131 | ||||
-rw-r--r-- | src/wallet/wallet.h | 179 | ||||
-rw-r--r-- | src/wallet/wallet_ismine.h | 2 | ||||
-rw-r--r-- | src/wallet/walletdb.cpp | 8 |
8 files changed, 383 insertions, 143 deletions
diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp index d18250b76f..50b0f40a6a 100644 --- a/src/wallet/db.cpp +++ b/src/wallet/db.cpp @@ -205,7 +205,7 @@ bool CDBEnv::Salvage(const std::string& strFile, bool fAggressive, std::vector<C std::string keyHex, valueHex; while (!strDump.eof() && keyHex != "DATA=END") { getline(strDump, keyHex); - if (keyHex != "DATA_END") { + if (keyHex != "DATA=END") { getline(strDump, valueHex); vResult.push_back(make_pair(ParseHex(keyHex), ParseHex(valueHex))); } diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 374f2fd401..8b56a0a3d0 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -11,6 +11,7 @@ #include "main.h" #include "net.h" #include "netbase.h" +#include "policy/rbf.h" #include "rpcserver.h" #include "timedata.h" #include "util.h" @@ -76,6 +77,23 @@ void WalletTxToJSON(const CWalletTx& wtx, UniValue& entry) entry.push_back(Pair("walletconflicts", conflicts)); entry.push_back(Pair("time", wtx.GetTxTime())); entry.push_back(Pair("timereceived", (int64_t)wtx.nTimeReceived)); + + // Add opt-in RBF status + std::string rbfStatus = "no"; + if (confirms <= 0) { + LOCK(mempool.cs); + if (!mempool.exists(hash)) { + if (SignalsOptInRBF(wtx)) { + rbfStatus = "yes"; + } else { + rbfStatus = "unknown"; + } + } else if (IsRBFOptIn(*mempool.mapTx.find(hash), mempool)) { + rbfStatus = "yes"; + } + } + entry.push_back(Pair("bip125-replaceable", rbfStatus)); + BOOST_FOREACH(const PAIRTYPE(string,string)& item, wtx.mapValue) entry.push_back(Pair(item.first, item.second)); } @@ -137,26 +155,25 @@ CBitcoinAddress GetAccountAddress(string strAccount, bool bForceNew=false) CAccount account; walletdb.ReadAccount(strAccount, account); - bool bKeyUsed = false; - - // Check if the current key has been used - if (account.vchPubKey.IsValid()) - { - CScript scriptPubKey = GetScriptForDestination(account.vchPubKey.GetID()); - for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); - it != pwalletMain->mapWallet.end() && account.vchPubKey.IsValid(); - ++it) - { - const CWalletTx& wtx = (*it).second; - BOOST_FOREACH(const CTxOut& txout, wtx.vout) - if (txout.scriptPubKey == scriptPubKey) - bKeyUsed = true; + if (!bForceNew) { + if (!account.vchPubKey.IsValid()) + bForceNew = true; + else { + // Check if the current key has been used + CScript scriptPubKey = GetScriptForDestination(account.vchPubKey.GetID()); + for (map<uint256, CWalletTx>::iterator it = pwalletMain->mapWallet.begin(); + it != pwalletMain->mapWallet.end() && account.vchPubKey.IsValid(); + ++it) + BOOST_FOREACH(const CTxOut& txout, (*it).second.vout) + if (txout.scriptPubKey == scriptPubKey) { + bForceNew = true; + break; + } } } // Generate a new key - if (!account.vchPubKey.IsValid() || bForceNew || bKeyUsed) - { + if (bForceNew) { if (!pwalletMain->GetKeyFromPool(account.vchPubKey)) throw JSONRPCError(RPC_WALLET_KEYPOOL_RAN_OUT, "Error: Keypool ran out, please call keypoolrefill first"); @@ -388,11 +405,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" @@ -864,13 +881,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" @@ -935,7 +951,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" @@ -1424,7 +1440,7 @@ UniValue listtransactions(const UniValue& params, bool fHelp) " 'send' category of transactions.\n" " \"confirmations\": n, (numeric) The number of confirmations for the transaction. Available for 'send' and \n" " 'receive' category of transactions. Negative confirmations indicate the\n" - " transation conflicts with the block chain\n" + " transaction 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" @@ -1440,6 +1456,8 @@ UniValue listtransactions(const UniValue& params, bool fHelp) " \"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" + " \"bip125-replaceable\": \"yes|no|unknown\" (string) Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n" + " may be unknown for unconfirmed transactions not in the mempool\n" " }\n" "]\n" @@ -1708,6 +1726,8 @@ UniValue gettransaction(const UniValue& params, bool fHelp) " \"txid\" : \"transactionid\", (string) The transaction id.\n" " \"time\" : ttt, (numeric) The transaction time in seconds since epoch (1 Jan 1970 GMT)\n" " \"timereceived\" : ttt, (numeric) The time received in seconds since epoch (1 Jan 1970 GMT)\n" + " \"bip125-replaceable\": \"yes|no|unknown\" (string) Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n" + " may be unknown for unconfirmed transactions not in the mempool\n" " \"details\" : [\n" " {\n" " \"account\" : \"accountname\", (string) DEPRECATED. The account name involved in the transaction, can be \"\" for the default account.\n" @@ -1764,6 +1784,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) { @@ -2180,7 +2234,7 @@ UniValue settxfee(const UniValue& params, bool fHelp) "settxfee amount\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" @@ -2442,3 +2496,70 @@ UniValue fundrawtransaction(const UniValue& params, bool fHelp) return result; } + +extern UniValue dumpprivkey(const UniValue& params, bool fHelp); // in rpcdump.cpp +extern UniValue importprivkey(const UniValue& params, bool fHelp); +extern UniValue importaddress(const UniValue& params, bool fHelp); +extern UniValue importpubkey(const UniValue& params, bool fHelp); +extern UniValue dumpwallet(const UniValue& params, bool fHelp); +extern UniValue importwallet(const UniValue& params, bool fHelp); + +const CRPCCommand vWalletRPCCommands[] = +{ // category name actor (function) okSafeMode + // --------------------- ------------------------ ----------------------- ---------- + { "rawtransactions", "fundrawtransaction", &fundrawtransaction, false }, + { "hidden", "resendwallettransactions", &resendwallettransactions, true }, + { "wallet", "abandontransaction", &abandontransaction, false }, + { "wallet", "addmultisigaddress", &addmultisigaddress, true }, + { "wallet", "backupwallet", &backupwallet, true }, + { "wallet", "dumpprivkey", &dumpprivkey, true }, + { "wallet", "dumpwallet", &dumpwallet, true }, + { "wallet", "encryptwallet", &encryptwallet, true }, + { "wallet", "getaccountaddress", &getaccountaddress, true }, + { "wallet", "getaccount", &getaccount, true }, + { "wallet", "getaddressesbyaccount", &getaddressesbyaccount, true }, + { "wallet", "getbalance", &getbalance, false }, + { "wallet", "getnewaddress", &getnewaddress, true }, + { "wallet", "getrawchangeaddress", &getrawchangeaddress, true }, + { "wallet", "getreceivedbyaccount", &getreceivedbyaccount, false }, + { "wallet", "getreceivedbyaddress", &getreceivedbyaddress, false }, + { "wallet", "gettransaction", &gettransaction, false }, + { "wallet", "getunconfirmedbalance", &getunconfirmedbalance, false }, + { "wallet", "getwalletinfo", &getwalletinfo, false }, + { "wallet", "importprivkey", &importprivkey, true }, + { "wallet", "importwallet", &importwallet, true }, + { "wallet", "importaddress", &importaddress, true }, + { "wallet", "importpubkey", &importpubkey, true }, + { "wallet", "keypoolrefill", &keypoolrefill, true }, + { "wallet", "listaccounts", &listaccounts, false }, + { "wallet", "listaddressgroupings", &listaddressgroupings, false }, + { "wallet", "listlockunspent", &listlockunspent, false }, + { "wallet", "listreceivedbyaccount", &listreceivedbyaccount, false }, + { "wallet", "listreceivedbyaddress", &listreceivedbyaddress, false }, + { "wallet", "listsinceblock", &listsinceblock, false }, + { "wallet", "listtransactions", &listtransactions, false }, + { "wallet", "listunspent", &listunspent, false }, + { "wallet", "lockunspent", &lockunspent, true }, + { "wallet", "move", &movecmd, false }, + { "wallet", "sendfrom", &sendfrom, false }, + { "wallet", "sendmany", &sendmany, false }, + { "wallet", "sendtoaddress", &sendtoaddress, false }, + { "wallet", "setaccount", &setaccount, true }, + { "wallet", "settxfee", &settxfee, true }, + { "wallet", "signmessage", &signmessage, true }, + { "wallet", "walletlock", &walletlock, true }, + { "wallet", "walletpassphrasechange", &walletpassphrasechange, true }, + { "wallet", "walletpassphrase", &walletpassphrase, true }, +}; + +void walletRegisterRPCCommands() +{ + unsigned int vcidx; + for (vcidx = 0; vcidx < ARRAYLEN(vWalletRPCCommands); vcidx++) + { + const CRPCCommand *pcmd; + + pcmd = &vWalletRPCCommands[vcidx]; + tableRPC.appendCommand(pcmd->name, pcmd); + } +} diff --git a/src/wallet/rpcwallet.h b/src/wallet/rpcwallet.h new file mode 100644 index 0000000000..42e8021afa --- /dev/null +++ b/src/wallet/rpcwallet.h @@ -0,0 +1,10 @@ +// Copyright (c) 2016 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_WALLET_RPCWALLET_H +#define BITCOIN_WALLET_RPCWALLET_H + +void walletRegisterRPCCommands(); + +#endif //BITCOIN_WALLET_RPCWALLET_H diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index f03cb4896c..e84d588026 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -328,7 +328,7 @@ BOOST_AUTO_TEST_CASE(coin_selection_tests) empty_wallet(); } -BOOST_AUTO_TEST_CASE(pruning_in_ApproximateBestSet) +BOOST_AUTO_TEST_CASE(ApproximateBestSubset) { CoinSet setCoinsRet; CAmount nValueRet; @@ -336,15 +336,28 @@ BOOST_AUTO_TEST_CASE(pruning_in_ApproximateBestSet) 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 * CENT); + add_coin(10 * COIN); for (int i = 0; i < 100; i++) - add_coin(1000 * CENT); + add_coin(1000 * COIN); - BOOST_CHECK(wallet.SelectCoinsMinConf(100001 * CENT, 1, 6, vCoins, setCoinsRet, nValueRet)); + 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 pruned: - BOOST_CHECK_EQUAL(nValueRet, 100010 * CENT); + // Superfluous small coins must be trimmed from the set: + BOOST_CHECK_EQUAL(nValueRet, 100010 * COIN); BOOST_CHECK_EQUAL(setCoinsRet.size(), 101); } diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 444bd88f8b..5b8bd55498 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -47,6 +47,14 @@ bool fSendFreeTransactions = DEFAULT_SEND_FREE_TRANSACTIONS; * Override with -mintxfee */ 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 * @@ -455,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; } @@ -610,7 +621,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD BOOST_FOREACH(const CTxIn& txin, wtx.vin) { if (mapWallet.count(txin.prevout.hash)) { CWalletTx& prevtx = mapWallet[txin.prevout.hash]; - if (prevtx.nIndex == -1 && !prevtx.hashBlock.IsNull()) { + if (prevtx.nIndex == -1 && !prevtx.hashUnset()) { MarkConflicted(prevtx.hashBlock, wtx.GetHash()); } } @@ -631,7 +642,7 @@ bool CWallet::AddToWallet(const CWalletTx& wtxIn, bool fFromLoadWallet, CWalletD 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)) { @@ -681,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; @@ -768,6 +785,64 @@ 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); @@ -784,14 +859,14 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) // Do not flush the wallet here for performance reasons CWalletDB walletdb(strWalletFile, "r+", false); - std::deque<uint256> todo; + std::set<uint256> todo; std::set<uint256> done; - todo.push_back(hashTx); + todo.insert(hashTx); while (!todo.empty()) { - uint256 now = todo.front(); - todo.pop_front(); + uint256 now = *todo.begin(); + todo.erase(now); done.insert(now); assert(mapWallet.count(now)); CWalletTx& wtx = mapWallet[now]; @@ -807,10 +882,17 @@ void CWallet::MarkConflicted(const uint256& hashBlock, const uint256& hashTx) TxSpends::const_iterator iter = mapTxSpends.lower_bound(COutPoint(now, 0)); while (iter != mapTxSpends.end() && iter->first.hash == now) { if (!done.count(iter->second)) { - todo.push_back(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(); + } } } } @@ -969,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()) @@ -985,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()) @@ -1159,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)); } } @@ -1179,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; @@ -1717,7 +1799,8 @@ bool CWallet::SelectCoinsMinConf(const CAmount& nTargetValue, int nConfMine, int } // Solve subset sum by stochastic approximation - sort(vValue.rbegin(), vValue.rend(), CompareValueOnly()); + std::sort(vValue.begin(), vValue.end(), CompareValueOnly()); + std::reverse(vValue.begin(), vValue.end()); vector<char> vfBest; CAmount nBest; @@ -2223,14 +2306,12 @@ CAmount CWallet::GetMinimumFee(unsigned int nTxBytes, unsigned int nConfirmTarge if (nFeeNeeded == 0) { int estimateFoundTarget = nConfirmTarget; nFeeNeeded = pool.estimateSmartFee(nConfirmTarget, &estimateFoundTarget).GetFee(nTxBytes); - // ... unless we don't have enough mempool data for our desired target - // so we make sure we're paying at least minTxFee - if (nFeeNeeded == 0 || (unsigned int)estimateFoundTarget > nConfirmTarget) - nFeeNeeded = std::max(nFeeNeeded, GetRequiredFee(nTxBytes)); - } - // prevent user from paying a non-sense fee (like 1 satoshi): 0 < fee < minRelayFee - if (nFeeNeeded < ::minRelayTxFee.GetFee(nTxBytes)) - nFeeNeeded = ::minRelayTxFee.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; @@ -2920,8 +3001,9 @@ int CMerkleTx::SetMerkleBranch(const CBlock& block) int CMerkleTx::GetDepthInMainChain(const CBlockIndex* &pindexRet) const { - if (hashBlock.IsNull()) + if (hashUnset()) return 0; + AssertLockHeld(cs_main); // Find the block it claims to be in @@ -2949,4 +3031,3 @@ bool CMerkleTx::AcceptToMemoryPool(bool fLimitFree, bool fRejectAbsurdFee) CValidationState state; return ::AcceptToMemoryPool(mempool, state, *this, fLimitFree, NULL, false, fRejectAbsurdFee); } - diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index 53a2b96690..ffc7dcbd2c 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -15,6 +15,7 @@ #include "wallet/crypter.h" #include "wallet/wallet_ismine.h" #include "wallet/walletdb.h" +#include "wallet/rpcwallet.h" #include <algorithm> #include <map> @@ -41,6 +42,8 @@ static const unsigned int DEFAULT_KEYPOOL_SIZE = 100; 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 @@ -59,7 +62,6 @@ static const CAmount nHighTransactionMaxFeeWarning = 100 * nHighTransactionFeeWa static const unsigned int MAX_FREE_TRANSACTION_CREATE_SIZE = 1000; static const bool DEFAULT_WALLETBROADCAST = true; -class CAccountingEntry; class CBlockIndex; class CCoinControl; class COutput; @@ -155,6 +157,10 @@ struct COutputEntry /** A transaction with a merkle branch linking it to the block chain. */ class CMerkleTx : public CTransaction { +private: + /** Constant used in hashBlock to indicate tx has been abandoned */ + static const uint256 ABANDON_HASH; + public: uint256 hashBlock; @@ -206,6 +212,9 @@ public: 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; } }; /** @@ -445,6 +454,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; +}; /** @@ -486,7 +575,6 @@ private: /* 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: @@ -666,6 +754,7 @@ public: bool AddAccountingEntry(const CAccountingEntry&, CWalletDB & pwalletdb); static CFeeRate minTxFee; + static CFeeRate fallbackFee; /** * Estimate the minimum fee considering user set parameters * and the required fee @@ -783,6 +872,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. */ @@ -840,87 +932,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.h b/src/wallet/wallet_ismine.h index 93cdf6ab8f..51afd1b140 100644 --- a/src/wallet/wallet_ismine.h +++ b/src/wallet/wallet_ismine.h @@ -17,7 +17,7 @@ class CScript; enum isminetype { ISMINE_NO = 0, - //! Indicates that we dont know how to create a scriptSig that would solve this if we were given the appropriate private keys + //! Indicates that we don't know how to create a scriptSig that would solve this if we were given the appropriate private keys ISMINE_WATCH_UNSOLVABLE = 1, //! Indicates that we know how to create a scriptSig that would solve this if we were given the appropriate private keys ISMINE_WATCH_SOLVABLE = 2, diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index 88dc3102da..67511976df 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -15,6 +15,7 @@ #include "utiltime.h" #include "wallet/wallet.h" +#include <boost/version.hpp> #include <boost/filesystem.hpp> #include <boost/foreach.hpp> #include <boost/scoped_ptr.hpp> @@ -960,8 +961,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) |