diff options
40 files changed, 1375 insertions, 184 deletions
diff --git a/bitcoin-qt.pro b/bitcoin-qt.pro index f586c53372..c1f0c452ad 100644 --- a/bitcoin-qt.pro +++ b/bitcoin-qt.pro @@ -1,6 +1,6 @@ TEMPLATE = app TARGET = -VERSION = 0.6.1 +VERSION = 0.6.99 INCLUDEPATH += src src/json src/qt DEFINES += QT_GUI BOOST_THREAD_USE_LIB CONFIG += no_include_pwd @@ -158,7 +158,8 @@ HEADERS += src/qt/bitcoingui.h \ src/qt/notificator.h \ src/qt/qtipcserver.h \ src/allocators.h \ - src/ui_interface.h + src/ui_interface.h \ + src/qt/rpcconsole.h SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \ src/qt/transactiontablemodel.cpp \ @@ -212,7 +213,8 @@ SOURCES += src/qt/bitcoin.cpp src/qt/bitcoingui.cpp \ src/qt/askpassphrasedialog.cpp \ src/protocol.cpp \ src/qt/notificator.cpp \ - src/qt/qtipcserver.cpp + src/qt/qtipcserver.cpp \ + src/qt/rpcconsole.cpp RESOURCES += \ src/qt/bitcoin.qrc @@ -226,7 +228,8 @@ FORMS += \ src/qt/forms/transactiondescdialog.ui \ src/qt/forms/overviewpage.ui \ src/qt/forms/sendcoinsentry.ui \ - src/qt/forms/askpassphrasedialog.ui + src/qt/forms/askpassphrasedialog.ui \ + src/qt/forms/rpcconsole.ui contains(USE_QRCODE, 1) { HEADERS += src/qt/qrcodedialog.h diff --git a/contrib/debian/changelog b/contrib/debian/changelog index db5e2682c6..cdd4a47e91 100644 --- a/contrib/debian/changelog +++ b/contrib/debian/changelog @@ -1,13 +1,43 @@ -bitcoin (0.5.1-natty1) natty; urgency=low +bitcoin (0.6.1-natty0) natty; urgency=low + + * New upstream release. + + -- Matt Corallo <matt@bluematt.me> Sun, 6 May 2012 20:09:00 -0500 + +bitcoin (0.6.0-natty0) natty; urgency=low + + * New upstream release. + * Add GNOME/KDE support for bitcoin-qt's bitcoin: URI support. + Thanks to luke-jr for the KDE .protocol file. + + -- Matt Corallo <matt@bluematt.me> Sat, 31 Mar 2012 15:35:00 -0500 + +bitcoin (0.5.3-natty1) natty; urgency=low + + * Mark for upload to PPA. + + -- Matt Corallo <matt@bluematt.me> Wed, 14 Mar 2012 23:06:00 -0400 + +bitcoin (0.5.3-natty0) natty; urgency=low + + * New upstream release. + + -- Luke Dashjr <luke+bitcoin+deb@dashjr.org> Tue, 10 Jan 2012 15:57:00 -0500 + +bitcoin (0.5.2-natty1) natty; urgency=low * Remove mentions on anonymity in package descriptions and manpage. These should never have been there, bitcoin isnt anonymous without a ton of work that virtually no users will ever be willing and capable of doing - * Add GNOME/KDE support for bitcoin-qt's bitcoin: URI support. - Thanks to luke-jr for the KDE .protocol file. - -- Matt Corallo <matt@bluematt.me> Fri, 23 Dec 2011 20:25:00 -0500 + -- Matt Corallo <matt@bluematt.me> Sat, 7 Jan 2012 13:37:00 -0500 + +bitcoin (0.5.2-natty0) natty; urgency=low + + * New upstream release. + + -- Luke Dashjr <luke+bitcoin+deb@dashjr.org> Fri, 16 Dec 2011 17:57:00 -0500 bitcoin (0.5.1-natty0) natty; urgency=low diff --git a/doc/README b/doc/README index aa89960959..4eda8bcd76 100644 --- a/doc/README +++ b/doc/README @@ -1,4 +1,4 @@ -Bitcoin 0.6.1rc1 BETA +Bitcoin 0.6.99 BETA Copyright (c) 2009-2012 Bitcoin Developers Distributed under the MIT/X11 software license, see the accompanying diff --git a/doc/README_windows.txt b/doc/README_windows.txt index 6b48b1d1e5..eec252931d 100644 --- a/doc/README_windows.txt +++ b/doc/README_windows.txt @@ -1,4 +1,4 @@ -Bitcoin 0.6.1rc1 BETA
+Bitcoin 0.6.99 BETA
Copyright (c) 2009-2012 Bitcoin Developers
Distributed under the MIT/X11 software license, see the accompanying
diff --git a/share/setup.nsi b/share/setup.nsi index fa33bd9786..fbbad3769e 100644 --- a/share/setup.nsi +++ b/share/setup.nsi @@ -5,7 +5,7 @@ SetCompressor /SOLID lzma # General Symbol Definitions
!define REGKEY "SOFTWARE\$(^Name)"
-!define VERSION 0.6.1
+!define VERSION 0.6.99
!define COMPANY "Bitcoin project"
!define URL http://www.bitcoin.org/
@@ -45,13 +45,13 @@ Var StartMenuGroup !insertmacro MUI_LANGUAGE English
# Installer attributes
-OutFile bitcoin-0.6.1rc1-win32-setup.exe
+OutFile bitcoin-0.6.99-win32-setup.exe
InstallDir $PROGRAMFILES\Bitcoin
CRCCheck on
XPStyle on
BrandingText " "
ShowInstDetails show
-VIProductVersion 0.6.1.1
+VIProductVersion 0.6.99.0
VIAddVersionKey ProductName Bitcoin
VIAddVersionKey ProductVersion "${VERSION}"
VIAddVersionKey CompanyName "${COMPANY}"
diff --git a/src/addrman.cpp b/src/addrman.cpp index 345261e229..10d005aae9 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -107,9 +107,15 @@ void CAddrMan::SwapRandom(int nRndPos1, int nRndPos2) if (nRndPos1 == nRndPos2) return; + assert(nRndPos1 >= 0 && nRndPos2 >= 0); + assert(nRndPos1 < vRandom.size() && nRndPos2 < vRandom.size()); + int nId1 = vRandom[nRndPos1]; int nId2 = vRandom[nRndPos2]; + assert(mapInfo.count(nId1) == 1); + assert(mapInfo.count(nId2) == 1); + mapInfo[nId1].nRandomPos = nRndPos2; mapInfo[nId2].nRandomPos = nRndPos1; @@ -124,26 +130,32 @@ int CAddrMan::SelectTried(int nKBucket) // random shuffle the first few elements (using the entire list) // find the least recently tried among them int64 nOldest = -1; + int nOldestPos = -1; for (unsigned int i = 0; i < ADDRMAN_TRIED_ENTRIES_INSPECT_ON_EVICT && i < vTried.size(); i++) { int nPos = GetRandInt(vTried.size() - i) + i; int nTemp = vTried[nPos]; vTried[nPos] = vTried[i]; vTried[i] = nTemp; - if (nOldest == -1 || mapInfo[nTemp].nLastSuccess < mapInfo[nOldest].nLastSuccess) + assert(nOldest == -1 || mapInfo.count(nTemp) == 1); + if (nOldest == -1 || mapInfo[nTemp].nLastSuccess < mapInfo[nOldest].nLastSuccess) { nOldest = nTemp; + nOldestPos = nPos; + } } - return nOldest; + return nOldestPos; } int CAddrMan::ShrinkNew(int nUBucket) { + assert(nUBucket >= 0 && nUBucket < vvNew.size()); std::set<int> &vNew = vvNew[nUBucket]; // first look for deletable items for (std::set<int>::iterator it = vNew.begin(); it != vNew.end(); it++) { + assert(mapInfo.count(*it)); CAddrInfo &info = mapInfo[*it]; if (info.IsTerrible()) { @@ -168,11 +180,13 @@ int CAddrMan::ShrinkNew(int nUBucket) { if (nI == n[0] || nI == n[1] || nI == n[2] || nI == n[3]) { + assert(nOldest == -1 || mapInfo.count(*it) == 1); if (nOldest == -1 || mapInfo[*it].nTime < mapInfo[nOldest].nTime) nOldest = *it; } nI++; } + assert(mapInfo.count(nOldest) == 1); CAddrInfo &info = mapInfo[nOldest]; if (--info.nRefCount == 0) { @@ -189,6 +203,8 @@ int CAddrMan::ShrinkNew(int nUBucket) void CAddrMan::MakeTried(CAddrInfo& info, int nId, int nOrigin) { + assert(vvNew[nOrigin].count(nId) == 1); + // remove the entry from all new buckets for (std::vector<std::set<int> >::iterator it = vvNew.begin(); it != vvNew.end(); it++) { @@ -197,6 +213,8 @@ void CAddrMan::MakeTried(CAddrInfo& info, int nId, int nOrigin) } nNew--; + assert(info.nRefCount == 0); + // what tried bucket to move the entry to int nKBucket = info.GetTriedBucket(nKey); std::vector<int> &vTried = vvTried[nKBucket]; @@ -214,6 +232,7 @@ void CAddrMan::MakeTried(CAddrInfo& info, int nId, int nOrigin) int nPos = SelectTried(nKBucket); // find which new bucket it belongs to + assert(mapInfo.count(vTried[nPos]) == 1); int nUBucket = mapInfo[vTried[nPos]].GetNewBucket(nKey); std::set<int> &vNew = vvNew[nUBucket]; @@ -385,6 +404,7 @@ CAddress CAddrMan::Select_(int nUnkBias) std::vector<int> &vTried = vvTried[nKBucket]; if (vTried.size() == 0) continue; int nPos = GetRandInt(vTried.size()); + assert(mapInfo.count(vTried[nPos]) == 1); CAddrInfo &info = mapInfo[vTried[nPos]]; if (GetRandInt(1<<30) < fChanceFactor*info.GetChance()*(1<<30)) return info; @@ -402,6 +422,7 @@ CAddress CAddrMan::Select_(int nUnkBias) std::set<int>::iterator it = vNew.begin(); while (nPos--) it++; + assert(mapInfo.count(*it) == 1); CAddrInfo &info = mapInfo[*it]; if (GetRandInt(1<<30) < fChanceFactor*info.GetChance()*(1<<30)) return info; @@ -481,6 +502,7 @@ void CAddrMan::GetAddr_(std::vector<CAddress> &vAddr) { int nRndPos = GetRandInt(vRandom.size() - n) + n; SwapRandom(n, nRndPos); + assert(mapInfo.count(vRandom[n]) == 1); vAddr.push_back(mapInfo[vRandom[n]]); } } diff --git a/src/addrman.h b/src/addrman.h index 7652df66ae..3768614cfe 100644 --- a/src/addrman.h +++ b/src/addrman.h @@ -62,7 +62,7 @@ public: nRandomPos = -1; } - CAddrInfo(const CAddress &addrIn, const CNetAddr &addrSource) : CAddress(addrIn) + CAddrInfo(const CAddress &addrIn, const CNetAddr &addrSource) : CAddress(addrIn), source(addrSource) { Init(); } diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index 15bcf1da3d..a189b2b2b0 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -44,6 +44,8 @@ static CCriticalSection cs_nWalletUnlockTime; extern Value dumpprivkey(const Array& params, bool fHelp); extern Value importprivkey(const Array& params, bool fHelp); +const Object emptyobj; + Object JSONRPCError(int code, const string& message) { Object error; @@ -111,6 +113,33 @@ HexBits(unsigned int nBits) return HexStr(BEGIN(uBits.cBits), END(uBits.cBits)); } +enum DecomposeMode { + DM_NONE = 0, + DM_HASH, + DM_HEX, + DM_ASM, + DM_OBJ, +}; + +enum DecomposeMode +FindDecompose(const Object& decompositions, const char* pcType, const char* pcDefault) +{ + Value val = find_value(decompositions, pcType); + std::string strDecompose = (val.type() == null_type) ? pcDefault : val.get_str(); + + if (strDecompose == "no") + return DM_NONE; + if (strDecompose == "hash") + return DM_HASH; + if (strDecompose == "hex") + return DM_HEX; + if (strDecompose == "asm") + return DM_ASM; + if (strDecompose == "obj") + return DM_OBJ; + throw JSONRPCError(-18, "Invalid decomposition"); +} + void WalletTxToJSON(const CWalletTx& wtx, Object& entry) { int confirms = wtx.GetDepthInMainChain(); @@ -126,6 +155,141 @@ void WalletTxToJSON(const CWalletTx& wtx, Object& entry) entry.push_back(Pair(item.first, item.second)); } +void +ScriptSigToJSON(const CTxIn& txin, Object& out) +{ + out.push_back(Pair("asm", txin.scriptSig.ToString())); + out.push_back(Pair("hex", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); + + CTransaction txprev; + uint256 hashTxprevBlock; + if (!GetTransaction(txin.prevout.hash, txprev, hashTxprevBlock)) + return; + + txnouttype type; + vector<CBitcoinAddress> addresses; + int nRequired; + + if (!ExtractAddresses(txprev.vout[txin.prevout.n].scriptPubKey, type, + addresses, nRequired)) + { + out.push_back(Pair("type", GetTxnOutputType(TX_NONSTANDARD))); + return; + } + + out.push_back(Pair("type", GetTxnOutputType(type))); + if (type == TX_MULTISIG) + { + // TODO: Need to handle this specially since not all input addresses are required... + return; + } + + Array a; + BOOST_FOREACH(const CBitcoinAddress& addr, addresses) + a.push_back(addr.ToString()); + out.push_back(Pair("addresses", a)); +} + +void +ScriptPubKeyToJSON(const CScript& scriptPubKey, Object& out) +{ + txnouttype type; + vector<CBitcoinAddress> addresses; + int nRequired; + + out.push_back(Pair("asm", scriptPubKey.ToString())); + out.push_back(Pair("hex", HexStr(scriptPubKey.begin(), scriptPubKey.end()))); + + if (!ExtractAddresses(scriptPubKey, type, addresses, nRequired)) + { + out.push_back(Pair("type", GetTxnOutputType(TX_NONSTANDARD))); + return; + } + + out.push_back(Pair("reqSigs", nRequired)); + out.push_back(Pair("type", GetTxnOutputType(type))); + + Array a; + BOOST_FOREACH(const CBitcoinAddress& addr, addresses) + a.push_back(addr.ToString()); + out.push_back(Pair("addresses", a)); +} + +void TxToJSON(const CTransaction &tx, Object& entry, const Object& decompositions) +{ + entry.push_back(Pair("version", tx.nVersion)); + entry.push_back(Pair("locktime", (boost::int64_t)tx.nLockTime)); + entry.push_back(Pair("size", (boost::int64_t)::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION))); + + enum DecomposeMode decomposeScript = FindDecompose(decompositions, "script", "asm"); + + Array vin; + BOOST_FOREACH(const CTxIn& txin, tx.vin) + { + Object in; + if (tx.IsCoinBase()) + in.push_back(Pair("coinbase", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); + else + { + Object prevout; + prevout.push_back(Pair("hash", txin.prevout.hash.GetHex())); + prevout.push_back(Pair("n", (boost::int64_t)txin.prevout.n)); + in.push_back(Pair("prevout", prevout)); + switch (decomposeScript) { + case DM_NONE: + break; + case DM_HEX: + in.push_back(Pair("scriptSig", HexStr(txin.scriptSig.begin(), txin.scriptSig.end()))); + break; + case DM_ASM: + in.push_back(Pair("scriptSig", txin.scriptSig.ToString())); + break; + case DM_OBJ: + { + Object o; + ScriptSigToJSON(txin, o); + in.push_back(Pair("scriptSig", o)); + break; + } + default: + throw JSONRPCError(-18, "Invalid script decomposition"); + } + } + in.push_back(Pair("sequence", (boost::int64_t)txin.nSequence)); + vin.push_back(in); + } + entry.push_back(Pair("vin", vin)); + Array vout; + BOOST_FOREACH(const CTxOut& txout, tx.vout) + { + Object out; + out.push_back(Pair("value", ValueFromAmount(txout.nValue))); + switch (decomposeScript) { + case DM_NONE: + break; + case DM_HEX: + out.push_back(Pair("scriptPubKey", HexStr(txout.scriptPubKey.begin(), txout.scriptPubKey.end()))); + break; + case DM_ASM: + out.push_back(Pair("scriptPubKey", txout.scriptPubKey.ToString())); + break; + case DM_OBJ: + { + Object o; + ScriptPubKeyToJSON(txout.scriptPubKey, o); + out.push_back(Pair("scriptPubKey", o)); + break; + } + default: + throw JSONRPCError(-18, "Invalid script decomposition"); + } + vout.push_back(out); + } + entry.push_back(Pair("vout", vout)); +} + +void AnyTxToJSON(const uint256 hash, const CTransaction* ptx, Object& entry, const Object& decompositions); + string AccountFromValue(const Value& value) { string strAccount = value.get_str(); @@ -134,10 +298,13 @@ string AccountFromValue(const Value& value) return strAccount; } -Object blockToJSON(const CBlock& block, const CBlockIndex* blockindex) +Object blockToJSON(const CBlock& block, const CBlockIndex* blockindex, const Object& decompositions) { Object result; result.push_back(Pair("hash", block.GetHash().GetHex())); + CMerkleTx txGen(block.vtx[0]); + txGen.SetMerkleBranch(&block); + result.push_back(Pair("confirmations", (int)txGen.GetDepthInMainChain())); result.push_back(Pair("size", (int)::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION))); result.push_back(Pair("height", blockindex->nHeight)); result.push_back(Pair("version", block.nVersion)); @@ -146,10 +313,38 @@ Object blockToJSON(const CBlock& block, const CBlockIndex* blockindex) result.push_back(Pair("nonce", (boost::uint64_t)block.nNonce)); result.push_back(Pair("bits", HexBits(block.nBits))); result.push_back(Pair("difficulty", GetDifficulty(blockindex))); - Array txhashes; - BOOST_FOREACH (const CTransaction&tx, block.vtx) - txhashes.push_back(tx.GetHash().GetHex()); - result.push_back(Pair("tx", txhashes)); + + enum DecomposeMode decomposeTxn = FindDecompose(decompositions, "tx", "hash"); + if (decomposeTxn) + { + Array txs; + switch (decomposeTxn) { + case DM_OBJ: + BOOST_FOREACH (const CTransaction&tx, block.vtx) + { + Object entry; + AnyTxToJSON(tx.GetHash(), &tx, entry, decompositions); + txs.push_back(entry); + } + break; + case DM_HEX: + BOOST_FOREACH (const CTransaction&tx, block.vtx) + { + CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); + ssTx << tx; + + txs.push_back(HexStr(ssTx.begin(), ssTx.end())); + } + break; + case DM_HASH: + BOOST_FOREACH (const CTransaction&tx, block.vtx) + txs.push_back(tx.GetHash().GetHex()); + break; + default: + throw JSONRPCError(-18, "Invalid transaction decomposition"); + } + result.push_back(Pair("tx", txs)); + } if (blockindex->pprev) result.push_back(Pair("previousblockhash", blockindex->pprev->GetBlockHash().GetHex())); @@ -160,6 +355,7 @@ Object blockToJSON(const CBlock& block, const CBlockIndex* blockindex) + /// /// Note: This interface may still be subject to change. /// @@ -999,10 +1195,12 @@ Value addmultisigaddress(const Array& params, bool fHelp) strAccount = AccountFromValue(params[2]); // Gather public keys - if ((nRequired < 1) || ((int)keys.size() < nRequired)) + if (nRequired < 1) + throw runtime_error("a multisignature address must require at least one key to redeem"); + if ((int)keys.size() < nRequired) throw runtime_error( - strprintf("wrong number of keys" - "(got %d, need at least %d)", keys.size(), nRequired)); + strprintf("not enough keys supplied " + "(got %d keys, but need at least %d to redeem)", keys.size(), nRequired)); std::vector<CKey> pubkeys; pubkeys.resize(keys.size()); for (unsigned int i = 0; i < keys.size(); i++) @@ -1462,11 +1660,69 @@ Value listsinceblock(const Array& params, bool fHelp) return ret; } +void +AnyTxToJSON(const uint256 hash, const CTransaction* ptx, Object& entry, const Object& decompositions) +{ + if (pwalletMain->mapWallet.count(hash)) + { + const CWalletTx& wtx = pwalletMain->mapWallet[hash]; + + TxToJSON(wtx, entry, decompositions); + + int64 nCredit = wtx.GetCredit(); + int64 nDebit = wtx.GetDebit(); + int64 nNet = nCredit - nDebit; + int64 nFee = (wtx.IsFromMe() ? wtx.GetValueOut() - nDebit : 0); + + entry.push_back(Pair("amount", ValueFromAmount(nNet - nFee))); + if (wtx.IsFromMe()) + entry.push_back(Pair("fee", ValueFromAmount(nFee))); + + WalletTxToJSON(wtx, entry); + + Array details; + ListTransactions(pwalletMain->mapWallet[hash], "*", 0, false, details); + entry.push_back(Pair("details", details)); + } + else + { + CTransaction tx; + uint256 hashBlock = 0; + if ((!ptx) && GetTransaction(hash, tx, hashBlock)) + ptx = &tx; + if (ptx) + { + entry.push_back(Pair("txid", hash.GetHex())); + TxToJSON(*ptx, entry, decompositions); + if (hashBlock == 0) + entry.push_back(Pair("confirmations", 0)); + else + { + entry.push_back(Pair("blockhash", hashBlock.GetHex())); + map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hashBlock); + if (mi != mapBlockIndex.end() && (*mi).second) + { + CBlockIndex* pindex = (*mi).second; + if (pindex->IsInMainChain()) + { + entry.push_back(Pair("confirmations", 1 + nBestHeight - pindex->nHeight)); + entry.push_back(Pair("time", (boost::int64_t)pindex->nTime)); + } + else + entry.push_back(Pair("confirmations", 0)); + } + } + } + else + throw JSONRPCError(-5, "No information available about transaction"); + } +} + Value gettransaction(const Array& params, bool fHelp) { - if (fHelp || params.size() != 1) + if (fHelp || params.size() < 1 || params.size() > 2) throw runtime_error( - "gettransaction <txid>\n" + "gettransaction <txid> [decompositions]\n" "Get detailed information about <txid>"); uint256 hash; @@ -1474,24 +1730,8 @@ Value gettransaction(const Array& params, bool fHelp) Object entry; - if (!pwalletMain->mapWallet.count(hash)) - throw JSONRPCError(-5, "Invalid or non-wallet transaction id"); - const CWalletTx& wtx = pwalletMain->mapWallet[hash]; - - int64 nCredit = wtx.GetCredit(); - int64 nDebit = wtx.GetDebit(); - int64 nNet = nCredit - nDebit; - int64 nFee = (wtx.IsFromMe() ? wtx.GetValueOut() - nDebit : 0); - - entry.push_back(Pair("amount", ValueFromAmount(nNet - nFee))); - if (wtx.IsFromMe()) - entry.push_back(Pair("fee", ValueFromAmount(nFee))); - - WalletTxToJSON(pwalletMain->mapWallet[hash], entry); - - Array details; - ListTransactions(pwalletMain->mapWallet[hash], "*", 0, false, details); - entry.push_back(Pair("details", details)); + AnyTxToJSON(hash, NULL, entry, + (params.size() > 1) ? params[1].get_obj() : emptyobj); return entry; } @@ -1973,9 +2213,9 @@ Value getblockhash(const Array& params, bool fHelp) Value getblock(const Array& params, bool fHelp) { - if (fHelp || params.size() != 1) + if (fHelp || params.size() < 1 || params.size() > 2) throw runtime_error( - "getblock <hash>\n" + "getblock <hash> [decompositions]\n" "Returns details of a block with given block-hash."); std::string strHash = params[0].get_str(); @@ -1988,7 +2228,8 @@ Value getblock(const Array& params, bool fHelp) CBlockIndex* pblockindex = mapBlockIndex[hash]; block.ReadFromDisk(pblockindex, true); - return blockToJSON(block, pblockindex); + return blockToJSON(block, pblockindex, + (params.size() > 1) ? params[1].get_obj() : emptyobj); } @@ -2505,34 +2746,11 @@ void ThreadRPCServer2(void* parg) else throw JSONRPCError(-32600, "Params must be an array"); - // Find method - const CRPCCommand *pcmd = tableRPC[strMethod]; - if (!pcmd) - throw JSONRPCError(-32601, "Method not found"); - - // Observe safe mode - string strWarning = GetWarnings("rpc"); - if (strWarning != "" && !GetBoolArg("-disablesafemode") && - !pcmd->okSafeMode) - throw JSONRPCError(-2, string("Safe mode: ") + strWarning); + Value result = tableRPC.execute(strMethod, params); - try - { - // Execute - Value result; - { - LOCK2(cs_main, pwalletMain->cs_wallet); - result = pcmd->actor(params, false); - } - - // Send reply - string strReply = JSONRPCReply(result, Value::null, id); - stream << HTTPReply(200, strReply) << std::flush; - } - catch (std::exception& e) - { - ErrorReply(stream, JSONRPCError(-1, e.what()), id); - } + // Send reply + string strReply = JSONRPCReply(result, Value::null, id); + stream << HTTPReply(200, strReply) << std::flush; } catch (Object& objError) { @@ -2545,7 +2763,34 @@ void ThreadRPCServer2(void* parg) } } +json_spirit::Value CRPCTable::execute(const std::string &strMethod, const json_spirit::Array ¶ms) const +{ + // Find method + const CRPCCommand *pcmd = tableRPC[strMethod]; + if (!pcmd) + throw JSONRPCError(-32601, "Method not found"); + // Observe safe mode + string strWarning = GetWarnings("rpc"); + if (strWarning != "" && !GetBoolArg("-disablesafemode") && + !pcmd->okSafeMode) + throw JSONRPCError(-2, string("Safe mode: ") + strWarning); + + try + { + // Execute + Value result; + { + LOCK2(cs_main, pwalletMain->cs_wallet); + result = pcmd->actor(params, false); + } + return result; + } + catch (std::exception& e) + { + throw JSONRPCError(-1, e.what()); + } +} Object CallRPC(const string& strMethod, const Array& params) @@ -2619,6 +2864,62 @@ void ConvertTo(Value& value) } } +// Convert strings to command-specific RPC representation +Array RPCConvertValues(const std::string &strMethod, const std::vector<std::string> &strParams) +{ + Array params; + BOOST_FOREACH(const std::string ¶m, strParams) + params.push_back(param); + + int n = params.size(); + + // + // Special case non-string parameter types + // + if (strMethod == "setgenerate" && n > 0) ConvertTo<bool>(params[0]); + if (strMethod == "setgenerate" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "sendtoaddress" && n > 1) ConvertTo<double>(params[1]); + if (strMethod == "settxfee" && n > 0) ConvertTo<double>(params[0]); + if (strMethod == "getreceivedbyaddress" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "getreceivedbyaccount" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "listreceivedbyaddress" && n > 0) ConvertTo<boost::int64_t>(params[0]); + if (strMethod == "listreceivedbyaddress" && n > 1) ConvertTo<bool>(params[1]); + if (strMethod == "listreceivedbyaccount" && n > 0) ConvertTo<boost::int64_t>(params[0]); + if (strMethod == "listreceivedbyaccount" && n > 1) ConvertTo<bool>(params[1]); + if (strMethod == "getbalance" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "getblock" && n > 1) ConvertTo<Object>(params[1]); + if (strMethod == "getblockhash" && n > 0) ConvertTo<boost::int64_t>(params[0]); + if (strMethod == "gettransaction" && n > 1) ConvertTo<Object>(params[1]); + if (strMethod == "move" && n > 2) ConvertTo<double>(params[2]); + if (strMethod == "move" && n > 3) ConvertTo<boost::int64_t>(params[3]); + if (strMethod == "sendfrom" && n > 2) ConvertTo<double>(params[2]); + if (strMethod == "sendfrom" && n > 3) ConvertTo<boost::int64_t>(params[3]); + if (strMethod == "listtransactions" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "listtransactions" && n > 2) ConvertTo<boost::int64_t>(params[2]); + if (strMethod == "listaccounts" && n > 0) ConvertTo<boost::int64_t>(params[0]); + if (strMethod == "walletpassphrase" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "listsinceblock" && n > 1) ConvertTo<boost::int64_t>(params[1]); + if (strMethod == "sendmany" && n > 1) + { + string s = params[1].get_str(); + Value v; + if (!read_string(s, v) || v.type() != obj_type) + throw runtime_error("type mismatch"); + params[1] = v.get_obj(); + } + if (strMethod == "sendmany" && n > 2) ConvertTo<boost::int64_t>(params[2]); + if (strMethod == "addmultisigaddress" && n > 0) ConvertTo<boost::int64_t>(params[0]); + if (strMethod == "addmultisigaddress" && n > 1) + { + string s = params[1].get_str(); + Value v; + if (!read_string(s, v) || v.type() != array_type) + throw runtime_error("type mismatch "+s); + params[1] = v.get_array(); + } + return params; +} + int CommandLineRPC(int argc, char *argv[]) { string strPrint; @@ -2638,53 +2939,8 @@ int CommandLineRPC(int argc, char *argv[]) string strMethod = argv[1]; // Parameters default to strings - Array params; - for (int i = 2; i < argc; i++) - params.push_back(argv[i]); - int n = params.size(); - - // - // Special case non-string parameter types - // - if (strMethod == "setgenerate" && n > 0) ConvertTo<bool>(params[0]); - if (strMethod == "setgenerate" && n > 1) ConvertTo<boost::int64_t>(params[1]); - if (strMethod == "sendtoaddress" && n > 1) ConvertTo<double>(params[1]); - if (strMethod == "settxfee" && n > 0) ConvertTo<double>(params[0]); - if (strMethod == "getreceivedbyaddress" && n > 1) ConvertTo<boost::int64_t>(params[1]); - if (strMethod == "getreceivedbyaccount" && n > 1) ConvertTo<boost::int64_t>(params[1]); - if (strMethod == "listreceivedbyaddress" && n > 0) ConvertTo<boost::int64_t>(params[0]); - if (strMethod == "listreceivedbyaddress" && n > 1) ConvertTo<bool>(params[1]); - if (strMethod == "listreceivedbyaccount" && n > 0) ConvertTo<boost::int64_t>(params[0]); - if (strMethod == "listreceivedbyaccount" && n > 1) ConvertTo<bool>(params[1]); - if (strMethod == "getbalance" && n > 1) ConvertTo<boost::int64_t>(params[1]); - if (strMethod == "getblockhash" && n > 0) ConvertTo<boost::int64_t>(params[0]); - if (strMethod == "move" && n > 2) ConvertTo<double>(params[2]); - if (strMethod == "move" && n > 3) ConvertTo<boost::int64_t>(params[3]); - if (strMethod == "sendfrom" && n > 2) ConvertTo<double>(params[2]); - if (strMethod == "sendfrom" && n > 3) ConvertTo<boost::int64_t>(params[3]); - if (strMethod == "listtransactions" && n > 1) ConvertTo<boost::int64_t>(params[1]); - if (strMethod == "listtransactions" && n > 2) ConvertTo<boost::int64_t>(params[2]); - if (strMethod == "listaccounts" && n > 0) ConvertTo<boost::int64_t>(params[0]); - if (strMethod == "walletpassphrase" && n > 1) ConvertTo<boost::int64_t>(params[1]); - if (strMethod == "listsinceblock" && n > 1) ConvertTo<boost::int64_t>(params[1]); - if (strMethod == "sendmany" && n > 1) - { - string s = params[1].get_str(); - Value v; - if (!read_string(s, v) || v.type() != obj_type) - throw runtime_error("type mismatch"); - params[1] = v.get_obj(); - } - if (strMethod == "sendmany" && n > 2) ConvertTo<boost::int64_t>(params[2]); - if (strMethod == "addmultisigaddress" && n > 0) ConvertTo<boost::int64_t>(params[0]); - if (strMethod == "addmultisigaddress" && n > 1) - { - string s = params[1].get_str(); - Value v; - if (!read_string(s, v) || v.type() != array_type) - throw runtime_error("type mismatch "+s); - params[1] = v.get_array(); - } + std::vector<std::string> strParams(&argv[2], &argv[argc]); + Array params = RPCConvertValues(strMethod, strParams); // Execute Object reply = CallRPC(strMethod, params); diff --git a/src/bitcoinrpc.h b/src/bitcoinrpc.h index 6b7293ed19..dd18a504f3 100644 --- a/src/bitcoinrpc.h +++ b/src/bitcoinrpc.h @@ -16,6 +16,9 @@ void ThreadRPCServer(void* parg); int CommandLineRPC(int argc, char *argv[]); +/** Convert parameter values for RPC call from strings to command-specific JSON objects. */ +json_spirit::Array RPCConvertValues(const std::string &strMethod, const std::vector<std::string> &strParams); + typedef json_spirit::Value(*rpcfn_type)(const json_spirit::Array& params, bool fHelp); class CRPCCommand @@ -26,6 +29,9 @@ public: bool okSafeMode; }; +/** + * Bitcoin RPC command dispatcher. + */ class CRPCTable { private: @@ -34,6 +40,15 @@ public: CRPCTable(); const CRPCCommand* operator[](std::string name) const; std::string help(std::string name) const; + + /** + * Execute a method. + * @param method Method to execute + * @param params Array of arguments (JSON objects) + * @returns Result of the call. + * @throws an exception (json_spirit::Value) when an error happens. + */ + json_spirit::Value execute(const std::string &method, const json_spirit::Array ¶ms) const; }; extern const CRPCTable tableRPC; diff --git a/src/db.cpp b/src/db.cpp index 12647e568a..5bd0528202 100644 --- a/src/db.cpp +++ b/src/db.cpp @@ -613,7 +613,7 @@ bool CTxDB::LoadBlockIndex() map<pair<unsigned int, unsigned int>, CBlockIndex*> mapBlockPos; for (CBlockIndex* pindex = pindexBest; pindex && pindex->pprev; pindex = pindex->pprev) { - if (pindex->nHeight < nBestHeight-nCheckDepth) + if (fRequestShutdown || pindex->nHeight < nBestHeight-nCheckDepth) break; CBlock block; if (!block.ReadFromDisk(pindex)) @@ -715,7 +715,7 @@ bool CTxDB::LoadBlockIndex() } } } - if (pindexFork) + if (pindexFork && !fRequestShutdown) { // Reorg back to the fork printf("LoadBlockIndex() : *** moving best chain pointer back to block %d\n", pindexFork->nHeight); diff --git a/src/init.cpp b/src/init.cpp index 4696428972..60927f20b3 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -231,7 +231,8 @@ bool AppInit2(int argc, char* argv[]) " -keypool=<n> \t " + _("Set key pool size to <n> (default: 100)") + "\n" + " -rescan \t " + _("Rescan the block chain for missing wallet transactions") + "\n" + " -checkblocks=<n> \t\t " + _("How many blocks to check at startup (default: 2500, 0 = all)") + "\n" + - " -checklevel=<n> \t\t " + _("How thorough the block verification is (0-6, default: 1)") + "\n"; + " -checklevel=<n> \t\t " + _("How thorough the block verification is (0-6, default: 1)") + "\n" + + " -loadblock=<file>\t " + _("Imports blocks from external blk000?.dat file") + "\n"; strUsage += string() + _("\nSSL options: (see the Bitcoin Wiki for SSL setup instructions)") + "\n" + @@ -372,6 +373,16 @@ bool AppInit2(int argc, char* argv[]) } printf(" block index %15"PRI64d"ms\n", GetTimeMillis() - nStart); + if (mapArgs.count("-loadblock")) + { + BOOST_FOREACH(string strFile, mapMultiArgs["-loadblock"]) + { + FILE *file = fopen(strFile.c_str(), "rb"); + if (file) + LoadExternalBlockFile(file); + } + } + InitMessage(_("Loading wallet...")); printf("Loading wallet...\n"); nStart = GetTimeMillis(); diff --git a/src/init.h b/src/init.h index e3971c85e3..0a2f0d8932 100644 --- a/src/init.h +++ b/src/init.h @@ -13,7 +13,4 @@ void Shutdown(void* parg); bool AppInit(int argc, char* argv[]); bool AppInit2(int argc, char* argv[]); -bool GetStartOnSystemStartup(); -bool SetStartOnSystemStartup(bool fAutoStart); - #endif diff --git a/src/main.cpp b/src/main.cpp index b5d8f8f270..b337993cc2 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -740,7 +740,31 @@ int CTxIndex::GetDepthInMainChain() const return 1 + nBestHeight - pindex->nHeight; } - +// Return transaction in tx, and if it was found inside a block, its hash is placed in hashBlock +bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock) +{ + { + LOCK(cs_main); + { + LOCK(mempool.cs); + if (mempool.exists(hash)) + { + tx = mempool.lookup(hash); + return true; + } + } + CTxDB txdb("r"); + CTxIndex txindex; + if (tx.ReadFromDisk(txdb, COutPoint(hash, 0), txindex)) + { + CBlock block; + if (block.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, false)) + hashBlock = block.GetHash(); + return true; + } + } + return false; +} @@ -1836,7 +1860,7 @@ bool CheckDiskSpace(uint64 nAdditionalBytes) FILE* OpenBlockFile(unsigned int nFile, unsigned int nBlockPos, const char* pszMode) { - if (nFile == -1) + if ((nFile < 1) || (nFile == (unsigned int) -1)) return NULL; FILE* file = fopen((GetDataDir() / strprintf("blk%04d.dat", nFile)).string().c_str(), pszMode); if (!file) @@ -2028,6 +2052,62 @@ void PrintBlockTree() } } +bool LoadExternalBlockFile(FILE* fileIn) +{ + int nLoaded = 0; + { + LOCK(cs_main); + try { + CAutoFile blkdat(fileIn, SER_DISK, CLIENT_VERSION); + unsigned int nPos = 0; + while (nPos != -1 && blkdat.good() && !fRequestShutdown) + { + unsigned char pchData[65536]; + do { + fseek(blkdat, nPos, SEEK_SET); + int nRead = fread(pchData, 1, sizeof(pchData), blkdat); + if (nRead <= 8) + { + nPos = -1; + break; + } + void* nFind = memchr(pchData, pchMessageStart[0], nRead+1-sizeof(pchMessageStart)); + if (nFind) + { + if (memcmp(nFind, pchMessageStart, sizeof(pchMessageStart))==0) + { + nPos += ((unsigned char*)nFind - pchData) + sizeof(pchMessageStart); + break; + } + nPos += ((unsigned char*)nFind - pchData) + 1; + } + else + nPos += sizeof(pchData) - sizeof(pchMessageStart) + 1; + } while(!fRequestShutdown); + if (nPos == -1) + break; + fseek(blkdat, nPos, SEEK_SET); + unsigned int nSize; + blkdat >> nSize; + if (nSize > 0 && nSize <= MAX_BLOCK_SIZE) + { + CBlock block; + blkdat >> block; + if (ProcessBlock(NULL,&block)) + { + nLoaded++; + nPos += 4 + nSize; + } + } + } + } + catch (std::exception &e) + { + } + } + printf("Loaded %i blocks from external file\n", nLoaded); + return nLoaded > 0; +} @@ -2398,6 +2478,12 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) return error("message inv size() = %d", vInv.size()); } + // find last block in inv vector + unsigned int nLastBlock = (unsigned int)(-1); + for (unsigned int nInv = 0; nInv < vInv.size(); nInv++) { + if (vInv[vInv.size() - 1 - nInv].type == MSG_BLOCK) + nLastBlock = vInv.size() - 1 - nInv; + } CTxDB txdb("r"); for (unsigned int nInv = 0; nInv < vInv.size(); nInv++) { @@ -2414,9 +2500,15 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) // Always request the last block in an inv bundle (even if we already have it), as it is the // trigger for the other side to send further invs. If we are stuck on a (very long) side chain, // this is necessary to connect earlier received orphan blocks to the chain again. - if (!fAlreadyHave || (inv.type == MSG_BLOCK && nInv==vInv.size()-1)) + if (fAlreadyHave && nInv == nLastBlock) { + // bypass mapAskFor, and send request directly; it must go through. + std::vector<CInv> vGetData(1,inv); + pfrom->PushMessage("getdata", vGetData); + } + + if (!fAlreadyHave) pfrom->AskFor(inv); - if (inv.type == MSG_BLOCK && mapOrphanBlocks.count(inv.hash)) + else if (inv.type == MSG_BLOCK && mapOrphanBlocks.count(inv.hash)) pfrom->PushGetBlocks(pindexBest, GetOrphanRoot(mapOrphanBlocks[inv.hash])); // Track requests for our stuff @@ -3195,7 +3287,7 @@ CBlock* CreateNewBlock(CReserveKey& reservekey) dPriority += (double)nValueIn * nConf; if (fDebug && GetBoolArg("-printpriority")) - printf("priority nValueIn=%-12I64d nConf=%-5d dPriority=%-20.1f\n", nValueIn, nConf, dPriority); + printf("priority nValueIn=%-12"PRI64d" nConf=%-5d dPriority=%-20.1f\n", nValueIn, nConf, dPriority); } // Priority is sum(valuein * age) / txsize diff --git a/src/main.h b/src/main.h index 262e77e806..965100d6d4 100644 --- a/src/main.h +++ b/src/main.h @@ -69,6 +69,7 @@ extern int64 nHPSTimerStart; extern int64 nTimeBestReceived; extern CCriticalSection cs_setpwalletRegistered; extern std::set<CWallet*> setpwalletRegistered; +extern unsigned char pchMessageStart[4]; // Settings extern int64 nTransactionFee; @@ -91,6 +92,7 @@ bool LoadBlockIndex(bool fAllowNew=true); void PrintBlockTree(); bool ProcessMessages(CNode* pfrom); bool SendMessages(CNode* pto, bool fSendTrickle); +bool LoadExternalBlockFile(FILE* fileIn); void GenerateBitcoins(bool fGenerate, CWallet* pwallet); CBlock* CreateNewBlock(CReserveKey& reservekey); void IncrementExtraNonce(CBlock* pblock, CBlockIndex* pindexPrev, unsigned int& nExtraNonce); @@ -101,7 +103,7 @@ unsigned int ComputeMinWork(unsigned int nBase, int64 nTime); int GetNumBlocksOfPeers(); bool IsInitialBlockDownload(); std::string GetWarnings(std::string strFor); - +bool GetTransaction(const uint256 &hash, CTransaction &tx, uint256 &hashBlock); @@ -136,8 +138,8 @@ public: } IMPLEMENT_SERIALIZE( READWRITE(FLATDATA(*this)); ) - void SetNull() { nFile = -1; nBlockPos = 0; nTxPos = 0; } - bool IsNull() const { return (nFile == -1); } + void SetNull() { nFile = (unsigned int) -1; nBlockPos = 0; nTxPos = 0; } + bool IsNull() const { return (nFile == (unsigned int) -1); } friend bool operator==(const CDiskTxPos& a, const CDiskTxPos& b) { @@ -176,8 +178,8 @@ public: CInPoint() { SetNull(); } CInPoint(CTransaction* ptxIn, unsigned int nIn) { ptx = ptxIn; n = nIn; } - void SetNull() { ptx = NULL; n = -1; } - bool IsNull() const { return (ptx == NULL && n == -1); } + void SetNull() { ptx = NULL; n = (unsigned int) -1; } + bool IsNull() const { return (ptx == NULL && n == (unsigned int) -1); } }; @@ -192,8 +194,8 @@ public: COutPoint() { SetNull(); } COutPoint(uint256 hashIn, unsigned int nIn) { hash = hashIn; n = nIn; } IMPLEMENT_SERIALIZE( READWRITE(FLATDATA(*this)); ) - void SetNull() { hash = 0; n = -1; } - bool IsNull() const { return (hash == 0 && n == -1); } + void SetNull() { hash = 0; n = (unsigned int) -1; } + bool IsNull() const { return (hash == 0 && n == (unsigned int) -1); } friend bool operator<(const COutPoint& a, const COutPoint& b) { diff --git a/src/net.cpp b/src/net.cpp index c626e49b1b..7efe304fb6 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -716,7 +716,7 @@ void ThreadSocketHandler2(void* parg) if (nSelect == SOCKET_ERROR) { int nErr = WSAGetLastError(); - if (hSocketMax > -1) + if (hSocketMax > (SOCKET) -1) { printf("socket select error %d\n", nErr); for (unsigned int i = 0; i <= hSocketMax; i++) diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 7c262e14cd..4a77bf9b70 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -187,30 +187,31 @@ int main(int argc, char *argv[]) // ... then GUI settings: OptionsModel optionsModel; - // Get desired locale ("en_US") from command line or system locale + // Get desired locale (e.g. "de_DE") from command line or use system locale QString lang_territory = QString::fromStdString(GetArg("-lang", QLocale::system().name().toStdString())); - // Load language files for configured locale: - // - First load the translator for the base language, without territory - // - Then load the more specific locale translator QString lang = lang_territory; + // Convert to "de" only by truncating "_DE" + lang.truncate(lang_territory.lastIndexOf('_')); - lang.truncate(lang_territory.lastIndexOf('_')); // "en" QTranslator qtTranslatorBase, qtTranslator, translatorBase, translator; + // Load language files for configured locale: + // - First load the translator for the base language, without territory + // - Then load the more specific locale translator - qtTranslatorBase.load(QLibraryInfo::location(QLibraryInfo::TranslationsPath) + "/qt_" + lang); - if (!qtTranslatorBase.isEmpty()) + // Load e.g. qt_de.qm + if (qtTranslatorBase.load("qt_" + lang, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) app.installTranslator(&qtTranslatorBase); - qtTranslator.load(QLibraryInfo::location(QLibraryInfo::TranslationsPath) + "/qt_" + lang_territory); - if (!qtTranslator.isEmpty()) + // Load e.g. qt_de_DE.qm + if (qtTranslator.load("qt_" + lang_territory, QLibraryInfo::location(QLibraryInfo::TranslationsPath))) app.installTranslator(&qtTranslator); - translatorBase.load(":/translations/"+lang); - if (!translatorBase.isEmpty()) + // Load e.g. bitcoin_de.qm (shortcut "de" needs to be defined in bitcoin.qrc) + if (translatorBase.load(lang, ":/translations/")) app.installTranslator(&translatorBase); - translator.load(":/translations/"+lang_territory); - if (!translator.isEmpty()) + // Load e.g. bitcoin_de_DE.qm (shortcut "de_DE" needs to be defined in bitcoin.qrc) + if (translator.load(lang_territory, ":/translations/")) app.installTranslator(&translator); QSplashScreen splash(QPixmap(":/images/splash"), 0); @@ -281,6 +282,7 @@ int main(int argc, char *argv[]) #endif app.exec(); + window.hide(); window.setClientModel(0); window.setWalletModel(0); guiref = 0; diff --git a/src/qt/bitcoin.qrc b/src/qt/bitcoin.qrc index e631a65155..e696170312 100644 --- a/src/qt/bitcoin.qrc +++ b/src/qt/bitcoin.qrc @@ -38,11 +38,11 @@ <file alias="lock_open">res/icons/lock_open.png</file> <file alias="key">res/icons/key.png</file> <file alias="filesave">res/icons/filesave.png</file> + <file alias="qrcode">res/icons/qrcode.png</file> </qresource> <qresource prefix="/images"> <file alias="about">res/images/about.png</file> <file alias="splash">res/images/splash2.jpg</file> - <file alias="qrcode">res/images/qrcode.png</file> </qresource> <qresource prefix="/movies"> <file alias="update_spinner">res/movies/update_spinner.mng</file> diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index bcf90917ed..007f185d06 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -24,6 +24,7 @@ #include "askpassphrasedialog.h" #include "notificator.h" #include "guiutil.h" +#include "rpcconsole.h" #ifdef Q_WS_MAC #include "macdockiconhandler.h" @@ -64,7 +65,8 @@ BitcoinGUI::BitcoinGUI(QWidget *parent): changePassphraseAction(0), aboutQtAction(0), trayIcon(0), - notificator(0) + notificator(0), + rpcConsole(0) { resize(850, 550); setWindowTitle(tr("Bitcoin Wallet")); @@ -158,6 +160,9 @@ BitcoinGUI::BitcoinGUI(QWidget *parent): // Doubleclicking on a transaction on the transaction history page shows details connect(transactionView, SIGNAL(doubleClicked(QModelIndex)), transactionView, SLOT(showDetails())); + rpcConsole = new RPCConsole(this); + connect(openRPCConsoleAction, SIGNAL(triggered()), rpcConsole, SLOT(show())); + gotoOverviewPage(); } @@ -248,6 +253,8 @@ void BitcoinGUI::createActions() backupWalletAction->setToolTip(tr("Backup wallet to another location")); changePassphraseAction = new QAction(QIcon(":/icons/key"), tr("&Change Passphrase"), this); changePassphraseAction->setToolTip(tr("Change the passphrase used for wallet encryption")); + openRPCConsoleAction = new QAction(tr("&Debug window"), this); + openRPCConsoleAction->setToolTip(tr("Open debugging and diagnostic console")); connect(quitAction, SIGNAL(triggered()), qApp, SLOT(quit())); connect(optionsAction, SIGNAL(triggered()), this, SLOT(optionsClicked())); @@ -286,6 +293,8 @@ void BitcoinGUI::createMenuBar() settings->addAction(optionsAction); QMenu *help = appMenuBar->addMenu(tr("&Help")); + help->addAction(openRPCConsoleAction); + help->addSeparator(); help->addAction(aboutAction); help->addAction(aboutQtAction); } @@ -338,6 +347,8 @@ void BitcoinGUI::setClientModel(ClientModel *clientModel) // Report errors from network/worker thread connect(clientModel, SIGNAL(error(QString,QString, bool)), this, SLOT(error(QString,QString,bool))); + + rpcConsole->setClientModel(clientModel); } } diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index 2cce8d3459..eb4f883496 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -13,6 +13,7 @@ class AddressBookPage; class SendCoinsDialog; class MessagePage; class Notificator; +class RPCConsole; QT_BEGIN_NAMESPACE class QLabel; @@ -87,10 +88,12 @@ private: QAction *backupWalletAction; QAction *changePassphraseAction; QAction *aboutQtAction; + QAction *openRPCConsoleAction; QSystemTrayIcon *trayIcon; Notificator *notificator; TransactionView *transactionView; + RPCConsole *rpcConsole; QMovie *syncIconMovie; diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index cb602ce327..d7172fd9cd 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -93,3 +93,8 @@ QString ClientModel::formatBuildDate() const { return QString::fromStdString(CLIENT_DATE); } + +QString ClientModel::clientName() const +{ + return QString::fromStdString(CLIENT_NAME); +} diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index 8e7431a2f3..74e0c0688f 100644 --- a/src/qt/clientmodel.h +++ b/src/qt/clientmodel.h @@ -38,6 +38,7 @@ public: QString formatFullVersion() const; QString formatBuildDate() const; + QString clientName() const; private: OptionsModel *optionsModel; diff --git a/src/qt/forms/aboutdialog.ui b/src/qt/forms/aboutdialog.ui index 6e342e5e8a..21fc7b2019 100644 --- a/src/qt/forms/aboutdialog.ui +++ b/src/qt/forms/aboutdialog.ui @@ -22,9 +22,6 @@ <verstretch>0</verstretch> </sizepolicy> </property> - <property name="text"> - <string/> - </property> <property name="pixmap"> <pixmap resource="../bitcoin.qrc">:/images/about</pixmap> </property> @@ -49,6 +46,9 @@ <layout class="QHBoxLayout" name="horizontalLayout"> <item> <widget class="QLabel" name="label"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> <property name="text"> <string><b>Bitcoin</b> version</string> </property> @@ -59,6 +59,9 @@ </item> <item> <widget class="QLabel" name="versionLabel"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> <property name="text"> <string notr="true">0.3.666-beta</string> </property> @@ -87,6 +90,9 @@ </item> <item> <widget class="QLabel" name="label_2"> + <property name="cursor"> + <cursorShape>IBeamCursor</cursorShape> + </property> <property name="text"> <string>Copyright © 2009-2012 Bitcoin Developers diff --git a/src/qt/forms/addressbookpage.ui b/src/qt/forms/addressbookpage.ui index b31a9ce997..3ccebd40d9 100644 --- a/src/qt/forms/addressbookpage.ui +++ b/src/qt/forms/addressbookpage.ui @@ -86,7 +86,7 @@ </property> <property name="icon"> <iconset resource="../bitcoin.qrc"> - <normaloff>:/images/qrcode</normaloff>:/images/qrcode</iconset> + <normaloff>:/icons/qrcode</normaloff>:/icons/qrcode</iconset> </property> </widget> </item> diff --git a/src/qt/forms/messagepage.ui b/src/qt/forms/messagepage.ui index ae1e062fca..512e47ad6d 100644 --- a/src/qt/forms/messagepage.ui +++ b/src/qt/forms/messagepage.ui @@ -101,9 +101,6 @@ <italic>true</italic> </font> </property> - <property name="text"> - <string>Click "Sign Message" to get signature</string> - </property> <property name="readOnly"> <bool>true</bool> </property> @@ -131,7 +128,7 @@ <string>Copy the current signature to the system clipboard</string> </property> <property name="text"> - <string>&Copy to Clipboard</string> + <string>&Copy Signature</string> </property> <property name="icon"> <iconset resource="../bitcoin.qrc"> @@ -140,6 +137,20 @@ </widget> </item> <item> + <widget class="QPushButton" name="clearButton"> + <property name="toolTip"> + <string>Reset all sign message fields</string> + </property> + <property name="text"> + <string>Clear &All</string> + </property> + <property name="icon"> + <iconset resource="../bitcoin.qrc"> + <normaloff>:/icons/remove</normaloff>:/icons/remove</iconset> + </property> + </widget> + </item> + <item> <spacer name="horizontalSpacer"> <property name="orientation"> <enum>Qt::Horizontal</enum> diff --git a/src/qt/forms/overviewpage.ui b/src/qt/forms/overviewpage.ui index cc67fae533..3cf7dd0ed3 100644 --- a/src/qt/forms/overviewpage.ui +++ b/src/qt/forms/overviewpage.ui @@ -78,12 +78,14 @@ </item> <item row="1" column="0"> <widget class="QLabel" name="label_5"> + <property name="font"> + <font> + <pointsize>11</pointsize> + <bold>true</bold> + </font> + </property> <property name="text"> - <string><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> -<html><head><meta name="qrichtext" content="1" /><style type="text/css"> -p, li { white-space: pre-wrap; } -</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;"> -<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-weight:600;">Wallet</span></p></body></html></string> + <string>Wallet</string> </property> </widget> </item> diff --git a/src/qt/forms/qrcodedialog.ui b/src/qt/forms/qrcodedialog.ui index 714b1d6cd8..ef21841c26 100644 --- a/src/qt/forms/qrcodedialog.ui +++ b/src/qt/forms/qrcodedialog.ui @@ -6,12 +6,12 @@ <rect> <x>0</x> <y>0</y> - <width>320</width> - <height>404</height> + <width>334</width> + <height>423</height> </rect> </property> <property name="windowTitle"> - <string>Dialog</string> + <string>QR-Code Dialog</string> </property> <layout class="QVBoxLayout" name="verticalLayout_3"> <item> diff --git a/src/qt/forms/rpcconsole.ui b/src/qt/forms/rpcconsole.ui new file mode 100644 index 0000000000..bde607c527 --- /dev/null +++ b/src/qt/forms/rpcconsole.ui @@ -0,0 +1,323 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>RPCConsole</class> + <widget class="QDialog" name="RPCConsole"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>706</width> + <height>382</height> + </rect> + </property> + <property name="windowTitle"> + <string>Bitcoin debug window</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2"> + <item> + <widget class="QTabWidget" name="tabWidget"> + <property name="currentIndex"> + <number>0</number> + </property> + <widget class="QWidget" name="tab"> + <attribute name="title"> + <string>Information</string> + </attribute> + <layout class="QGridLayout" name="gridLayout" columnstretch="0,1"> + <property name="horizontalSpacing"> + <number>12</number> + </property> + <item row="1" column="0"> + <widget class="QLabel" name="label_5"> + <property name="text"> + <string>Client name</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QLabel" name="clientName"> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_6"> + <property name="text"> + <string>Client version</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QLabel" name="clientVersion"> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="0" column="0"> + <widget class="QLabel" name="label_9"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Version</string> + </property> + </widget> + </item> + <item row="4" column="0"> + <widget class="QLabel" name="label_11"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Network</string> + </property> + </widget> + </item> + <item row="5" column="0"> + <widget class="QLabel" name="label_7"> + <property name="text"> + <string>Number of connections</string> + </property> + </widget> + </item> + <item row="5" column="1"> + <widget class="QLabel" name="numberOfConnections"> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="6" column="0"> + <widget class="QLabel" name="label_8"> + <property name="text"> + <string>On testnet</string> + </property> + </widget> + </item> + <item row="6" column="1"> + <widget class="QCheckBox" name="isTestNet"> + <property name="enabled"> + <bool>false</bool> + </property> + <property name="text"> + <string/> + </property> + </widget> + </item> + <item row="7" column="0"> + <widget class="QLabel" name="label_10"> + <property name="font"> + <font> + <weight>75</weight> + <bold>true</bold> + </font> + </property> + <property name="text"> + <string>Block chain</string> + </property> + </widget> + </item> + <item row="8" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Current number of blocks</string> + </property> + </widget> + </item> + <item row="8" column="1"> + <widget class="QLabel" name="numberOfBlocks"> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="9" column="0"> + <widget class="QLabel" name="label_4"> + <property name="text"> + <string>Estimated total blocks</string> + </property> + </widget> + </item> + <item row="9" column="1"> + <widget class="QLabel" name="totalBlocks"> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="10" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Last block time</string> + </property> + </widget> + </item> + <item row="10" column="1"> + <widget class="QLabel" name="lastBlockTime"> + <property name="text"> + <string>N/A</string> + </property> + <property name="textFormat"> + <enum>Qt::PlainText</enum> + </property> + <property name="textInteractionFlags"> + <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set> + </property> + </widget> + </item> + <item row="11" column="0"> + <spacer name="verticalSpacer"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>40</height> + </size> + </property> + </spacer> + </item> + <item row="3" column="0"> + <widget class="QLabel" name="label_12"> + <property name="text"> + <string>Build date</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QLabel" name="buildDate"> + <property name="text"> + <string>N/A</string> + </property> + </widget> + </item> + </layout> + </widget> + <widget class="QWidget" name="tab_2"> + <attribute name="title"> + <string>Console</string> + </attribute> + <layout class="QVBoxLayout" name="verticalLayout_3"> + <property name="spacing"> + <number>3</number> + </property> + <item> + <widget class="QTableWidget" name="messagesWidget"> + <property name="minimumSize"> + <size> + <width>0</width> + <height>100</height> + </size> + </property> + <property name="tabKeyNavigation"> + <bool>false</bool> + </property> + <property name="selectionBehavior"> + <enum>QAbstractItemView::SelectRows</enum> + </property> + <property name="columnCount"> + <number>2</number> + </property> + <attribute name="horizontalHeaderVisible"> + <bool>false</bool> + </attribute> + <attribute name="verticalHeaderVisible"> + <bool>false</bool> + </attribute> + <column/> + <column/> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout"> + <property name="spacing"> + <number>3</number> + </property> + <item> + <widget class="QLabel" name="label"> + <property name="text"> + <string>></string> + </property> + </widget> + </item> + <item> + <widget class="QLineEdit" name="lineEdit"/> + </item> + <item> + <widget class="QPushButton" name="clearButton"> + <property name="maximumSize"> + <size> + <width>24</width> + <height>24</height> + </size> + </property> + <property name="toolTip"> + <string>Clear console</string> + </property> + <property name="text"> + <string/> + </property> + <property name="icon"> + <iconset resource="../bitcoin.qrc"> + <normaloff>:/icons/remove</normaloff>:/icons/remove</iconset> + </property> + <property name="shortcut"> + <string notr="true">Ctrl+L</string> + </property> + <property name="autoDefault"> + <bool>false</bool> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </widget> + </widget> + </item> + </layout> + </widget> + <resources> + <include location="../bitcoin.qrc"/> + </resources> + <connections/> +</ui> diff --git a/src/qt/messagepage.cpp b/src/qt/messagepage.cpp index 18bb64fe6c..57ad8292dc 100644 --- a/src/qt/messagepage.cpp +++ b/src/qt/messagepage.cpp @@ -24,6 +24,11 @@ MessagePage::MessagePage(QWidget *parent) : ui(new Ui::MessagePage) { ui->setupUi(this); + +#if (QT_VERSION >= 0x040700) + /* Do not move this to the XML file, Qt before 4.7 will choke on it */ + ui->signature->setPlaceholderText(tr("Click \"Sign Message\" to get signature")); +#endif GUIUtil::setupAddressWidget(ui->signFrom, this); } @@ -105,3 +110,10 @@ void MessagePage::on_signMessage_clicked() ui->signature->setText(QString::fromStdString(EncodeBase64(&vchSig[0], vchSig.size()))); ui->signature->setFont(GUIUtil::bitcoinAddressFont()); } + +void MessagePage::on_clearButton_clicked() +{ + ui->signFrom->clear(); + ui->message->clear(); + ui->signature->clear(); +} diff --git a/src/qt/messagepage.h b/src/qt/messagepage.h index 55e6228124..b5a38166da 100644 --- a/src/qt/messagepage.h +++ b/src/qt/messagepage.h @@ -33,6 +33,7 @@ private slots: void on_signMessage_clicked(); void on_copyToClipboard_clicked(); + void on_clearButton_clicked(); }; #endif // MESSAGEPAGE_H diff --git a/src/qt/optionsdialog.cpp b/src/qt/optionsdialog.cpp index 59c44ac5f9..416880d462 100644 --- a/src/qt/optionsdialog.cpp +++ b/src/qt/optionsdialog.cpp @@ -222,7 +222,6 @@ MainOptionsPage::MainOptionsPage(QWidget *parent): QLabel *fee_label = new QLabel(tr("Pay transaction &fee")); fee_hbox->addWidget(fee_label); fee_edit = new BitcoinAmountField(); - fee_edit->setToolTip(tr("Optional transaction fee per kB that helps make sure your transactions are processed quickly. Most transactions are 1 kB. Fee 0.01 recommended.")); fee_label->setBuddy(fee_edit); fee_hbox->addWidget(fee_edit); diff --git a/src/qt/res/icons/qrcode.png b/src/qt/res/icons/qrcode.png Binary files differnew file mode 100644 index 0000000000..a8d97174b3 --- /dev/null +++ b/src/qt/res/icons/qrcode.png diff --git a/src/qt/res/images/qrcode.png b/src/qt/res/images/qrcode.png Binary files differdeleted file mode 100644 index c89a49bbce..0000000000 --- a/src/qt/res/images/qrcode.png +++ /dev/null diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp new file mode 100644 index 0000000000..d59f5c6a38 --- /dev/null +++ b/src/qt/rpcconsole.cpp @@ -0,0 +1,316 @@ +#include "rpcconsole.h" +#include "ui_rpcconsole.h" + +#include "clientmodel.h" +#include "bitcoinrpc.h" +#include "guiutil.h" + +#include <QTime> +#include <QTimer> +#include <QThread> +#include <QTextEdit> +#include <QKeyEvent> + +#include <boost/tokenizer.hpp> + +// TODO: make it possible to filter out categories (esp debug messages when implemented) +// TODO: receive errors and debug messages through ClientModel + +const int CONSOLE_SCROLLBACK = 50; +const int CONSOLE_HISTORY = 50; + +/* Object for executing console RPC commands in a separate thread. +*/ +class RPCExecutor: public QObject +{ + Q_OBJECT +public slots: + void start(); + void request(const QString &command); +signals: + void reply(int category, const QString &command); +}; + +#include "rpcconsole.moc" + +void RPCExecutor::start() +{ + // Nothing to do +} + +void RPCExecutor::request(const QString &command) +{ + // Parse shell-like command line into separate arguments + boost::escaped_list_separator<char> els('\\',' ','\"'); + std::string strCommand = command.toStdString(); + boost::tokenizer<boost::escaped_list_separator<char> > tok(strCommand, els); + + std::string strMethod; + std::vector<std::string> strParams; + int n = 0; + for(boost::tokenizer<boost::escaped_list_separator<char> >::iterator beg=tok.begin(); beg!=tok.end();++beg,++n) + { + if(n == 0) // First parameter is the command + strMethod = *beg; + else + strParams.push_back(*beg); + } + + try { + std::string strPrint; + json_spirit::Value result = tableRPC.execute(strMethod, RPCConvertValues(strMethod, strParams)); + + // Format result reply + if (result.type() == json_spirit::null_type) + strPrint = ""; + else if (result.type() == json_spirit::str_type) + strPrint = result.get_str(); + else + strPrint = write_string(result, true); + + emit reply(RPCConsole::CMD_REPLY, QString::fromStdString(strPrint)); + } + catch (json_spirit::Object& objError) + { + emit reply(RPCConsole::CMD_ERROR, QString::fromStdString(write_string(json_spirit::Value(objError), false))); + } + catch (std::exception& e) + { + emit reply(RPCConsole::CMD_ERROR, QString("Error: ") + QString::fromStdString(e.what())); + } +} + +RPCConsole::RPCConsole(QWidget *parent) : + QDialog(parent), + ui(new Ui::RPCConsole), + firstLayout(true), + historyPtr(0) +{ + ui->setupUi(this); + ui->messagesWidget->horizontalHeader()->setResizeMode(1, QHeaderView::Stretch); + ui->messagesWidget->setContextMenuPolicy(Qt::ActionsContextMenu); + + // Install event filter for up and down arrow + ui->lineEdit->installEventFilter(this); + + // Add "Copy message" to context menu explicitly + QAction *copyMessageAction = new QAction(tr("&Copy"), this); + copyMessageAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_C)); + copyMessageAction->setShortcutContext(Qt::WidgetShortcut); + connect(copyMessageAction, SIGNAL(triggered()), this, SLOT(copyMessage())); + ui->messagesWidget->addAction(copyMessageAction); + + connect(ui->clearButton, SIGNAL(clicked()), this, SLOT(clear())); + + startExecutor(); + + clear(); +} + +RPCConsole::~RPCConsole() +{ + emit stopExecutor(); + delete ui; +} + +bool RPCConsole::event(QEvent *event) +{ + int returnValue = QWidget::event(event); + + if (event->type() == QEvent::LayoutRequest && firstLayout) + { + // Work around QTableWidget issue: + // Call resizeRowsToContents on first Layout request with widget visible, + // to make sure multiline messages that were added before the console was shown + // have the right height. + if(ui->messagesWidget->isVisible()) + { + firstLayout = false; + ui->messagesWidget->resizeRowsToContents(); + } + return true; + } + + return returnValue; +} + +bool RPCConsole::eventFilter(QObject* obj, QEvent *event) +{ + if(obj == ui->lineEdit) + { + if(event->type() == QEvent::KeyPress) + { + QKeyEvent *key = static_cast<QKeyEvent*>(event); + switch(key->key()) + { + case Qt::Key_Up: browseHistory(-1); return true; + case Qt::Key_Down: browseHistory(1); return true; + } + } + } + return QDialog::eventFilter(obj, event); +} + +void RPCConsole::setClientModel(ClientModel *model) +{ + this->clientModel = model; + if(model) + { + // Subscribe to information, replies, messages, errors + connect(model, SIGNAL(numConnectionsChanged(int)), this, SLOT(setNumConnections(int))); + connect(model, SIGNAL(numBlocksChanged(int)), this, SLOT(setNumBlocks(int))); + + // Provide initial values + ui->clientVersion->setText(model->formatFullVersion()); + ui->clientName->setText(model->clientName()); + ui->buildDate->setText(model->formatBuildDate()); + + setNumConnections(model->getNumConnections()); + ui->isTestNet->setChecked(model->isTestNet()); + + setNumBlocks(model->getNumBlocks()); + } +} + +static QColor categoryColor(int category) +{ + switch(category) + { + case RPCConsole::MC_ERROR: return QColor(255,0,0); break; + case RPCConsole::MC_DEBUG: return QColor(192,192,192); break; + case RPCConsole::CMD_REQUEST: return QColor(128,128,128); break; + case RPCConsole::CMD_REPLY: return QColor(128,255,128); break; + case RPCConsole::CMD_ERROR: return QColor(255,128,128); break; + default: return QColor(0,0,0); + } +} + +void RPCConsole::clear() +{ + ui->messagesWidget->clear(); + ui->messagesWidget->setRowCount(0); + ui->lineEdit->clear(); + ui->lineEdit->setFocus(); + + message(CMD_REPLY, tr("Welcome to the bitcoin RPC console.")+"\n"+ + tr("Use up and down arrows to navigate history, and Ctrl-L to clear screen.")+"\n"+ + tr("Type \"help\" for an overview of available commands.")); +} + +void RPCConsole::message(int category, const QString &message) +{ + // Add row to messages widget + int row = ui->messagesWidget->rowCount(); + ui->messagesWidget->setRowCount(row+1); + + QTime time = QTime::currentTime(); + QTableWidgetItem *newTime = new QTableWidgetItem(time.toString()); + newTime->setData(Qt::DecorationRole, categoryColor(category)); + newTime->setForeground(QColor(128,128,128)); + newTime->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled); // make non-editable + + int numLines = message.count("\n") + 1; + // As Qt doesn't like very tall cells (they break scrolling) keep only short messages in + // the cell text, longer messages trigger a display widget with scroll bar + if(numLines < 5) + { + QTableWidgetItem *newItem = new QTableWidgetItem(message); + newItem->setFlags(Qt::ItemIsSelectable|Qt::ItemIsEnabled); // make non-editable + if(category == CMD_ERROR) // Coloring error messages in red + newItem->setForeground(QColor(255,16,16)); + ui->messagesWidget->setItem(row, 1, newItem); + } else { + QTextEdit *newWidget = new QTextEdit; + newWidget->setText(message); + newWidget->setMaximumHeight(100); + newWidget->setReadOnly(true); + ui->messagesWidget->setCellWidget(row, 1, newWidget); + } + + ui->messagesWidget->setItem(row, 0, newTime); + ui->messagesWidget->resizeRowToContents(row); + // Preserve only limited scrollback buffer + while(ui->messagesWidget->rowCount() > CONSOLE_SCROLLBACK) + ui->messagesWidget->removeRow(0); + // Scroll to bottom after table is updated + QTimer::singleShot(0, ui->messagesWidget, SLOT(scrollToBottom())); +} + +void RPCConsole::setNumConnections(int count) +{ + ui->numberOfConnections->setText(QString::number(count)); +} + +void RPCConsole::setNumBlocks(int count) +{ + ui->numberOfBlocks->setText(QString::number(count)); + if(clientModel) + { + ui->totalBlocks->setText(QString::number(clientModel->getNumBlocksOfPeers())); + ui->lastBlockTime->setText(clientModel->getLastBlockDate().toString()); + } +} + +void RPCConsole::on_lineEdit_returnPressed() +{ + QString cmd = ui->lineEdit->text(); + ui->lineEdit->clear(); + + if(!cmd.isEmpty()) + { + message(CMD_REQUEST, cmd); + emit cmdRequest(cmd); + // Truncate history from current position + history.erase(history.begin() + historyPtr, history.end()); + // Append command to history + history.append(cmd); + // Enforce maximum history size + while(history.size() > CONSOLE_HISTORY) + history.removeFirst(); + // Set pointer to end of history + historyPtr = history.size(); + } +} + +void RPCConsole::browseHistory(int offset) +{ + historyPtr += offset; + if(historyPtr < 0) + historyPtr = 0; + if(historyPtr > history.size()) + historyPtr = history.size(); + QString cmd; + if(historyPtr < history.size()) + cmd = history.at(historyPtr); + ui->lineEdit->setText(cmd); +} + +void RPCConsole::startExecutor() +{ + QThread* thread = new QThread; + RPCExecutor *executor = new RPCExecutor(); + executor->moveToThread(thread); + + // Notify executor when thread started (in executor thread) + connect(thread, SIGNAL(started()), executor, SLOT(start())); + // Replies from executor object must go to this object + connect(executor, SIGNAL(reply(int,QString)), this, SLOT(message(int,QString))); + // Requests from this object must go to executor + connect(this, SIGNAL(cmdRequest(QString)), executor, SLOT(request(QString))); + // On stopExecutor signal + // - queue executor for deletion (in execution thread) + // - quit the Qt event loop in the execution thread + connect(this, SIGNAL(stopExecutor()), executor, SLOT(deleteLater())); + connect(this, SIGNAL(stopExecutor()), thread, SLOT(quit())); + // Queue the thread for deletion (in this thread) when it is finished + connect(thread, SIGNAL(finished()), thread, SLOT(deleteLater())); + + // Default implementation of QThread::run() simply spins up an event loop in the thread, + // which is what we want. + thread->start(); +} + +void RPCConsole::copyMessage() +{ + GUIUtil::copyEntryData(ui->messagesWidget, 1, Qt::EditRole); +} diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h new file mode 100644 index 0000000000..a0a73bedd3 --- /dev/null +++ b/src/qt/rpcconsole.h @@ -0,0 +1,64 @@ +#ifndef RPCCONSOLE_H +#define RPCCONSOLE_H + +#include <QDialog> + +namespace Ui { + class RPCConsole; +} +class ClientModel; + +/** Local bitcoin RPC console. */ +class RPCConsole: public QDialog +{ + Q_OBJECT + +public: + explicit RPCConsole(QWidget *parent = 0); + ~RPCConsole(); + + void setClientModel(ClientModel *model); + + enum MessageClass { + MC_ERROR, + MC_DEBUG, + CMD_REQUEST, + CMD_REPLY, + CMD_ERROR + }; + +protected: + virtual bool event(QEvent *event); + virtual bool eventFilter(QObject* obj, QEvent *event); + +private slots: + void on_lineEdit_returnPressed(); + +public slots: + void clear(); + void message(int category, const QString &message); + /** Set number of connections shown in the UI */ + void setNumConnections(int count); + /** Set number of blocks shown in the UI */ + void setNumBlocks(int count); + /** Go forward or back in history */ + void browseHistory(int offset); + /** Copy currently selected message to clipboard */ + void copyMessage(); + +signals: + // For RPC command executor + void stopExecutor(); + void cmdRequest(const QString &command); + +private: + Ui::RPCConsole *ui; + ClientModel *clientModel; + bool firstLayout; + QStringList history; + int historyPtr; + + void startExecutor(); +}; + +#endif // RPCCONSOLE_H diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index c8242d8352..5960597c77 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -20,10 +20,10 @@ SendCoinsEntry::SendCoinsEntry(QWidget *parent) : #ifdef Q_WS_MAC ui->payToLayout->setSpacing(4); #endif - #if QT_VERSION >= 0x040700 - ui->payTo->setPlaceholderText(tr("Enter a Bitcoin address (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L)")); + /* Do not move this to the XML file, Qt before 4.7 will choke on it */ ui->addAsLabel->setPlaceholderText(tr("Enter a label for this address to add it to your address book")); + ui->payTo->setPlaceholderText(tr("Enter a Bitcoin address (e.g. 1NS17iag9jJgTHD1VXjvLCEnZuQ3rJDE9L)")); #endif setFocusPolicy(Qt::TabFocus); setFocusProxy(ui->payTo); diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index a915274da3..b9ccb06c09 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -150,14 +150,21 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(const QList<SendCoinsRecipie hex = QString::fromStdString(wtx.GetHash().GetHex()); } - // Add addresses that we've sent to to the address book + // Add addresses / update labels that we've sent to to the address book foreach(const SendCoinsRecipient &rcp, recipients) { std::string strAddress = rcp.address.toStdString(); + std::string strLabel = rcp.label.toStdString(); { LOCK(wallet->cs_wallet); - if (!wallet->mapAddressBook.count(strAddress)) - wallet->SetAddressBookName(strAddress, rcp.label.toStdString()); + + std::map<CBitcoinAddress, std::string>::iterator mi = wallet->mapAddressBook.find(strAddress); + + // Check if we have a new address or an updated label + if (mi == wallet->mapAddressBook.end() || mi->second != strLabel) + { + wallet->SetAddressBookName(strAddress, strLabel); + } } } diff --git a/src/script.cpp b/src/script.cpp index 65e9b7c9a2..0b103a80bc 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -940,7 +940,7 @@ bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& script, co // ([sig ...] num_of_signatures [pubkey ...] num_of_pubkeys -- bool) int i = 1; - if (stack.size() < i) + if ((int)stack.size() < i) return false; int nKeysCount = CastToBigNum(stacktop(-i)).getint(); @@ -951,7 +951,7 @@ bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& script, co return false; int ikey = ++i; i += nKeysCount; - if (stack.size() < i) + if ((int)stack.size() < i) return false; int nSigsCount = CastToBigNum(stacktop(-i)).getint(); @@ -959,7 +959,7 @@ bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& script, co return false; int isig = ++i; i += nSigsCount; - if (stack.size() < i) + if ((int)stack.size() < i) return false; // Subset of script starting at the most recent codeseparator diff --git a/src/util.cpp b/src/util.cpp index 3569f22ecd..3d301d21e3 100644 --- a/src/util.cpp +++ b/src/util.cpp @@ -43,7 +43,7 @@ namespace boost { #ifdef _WIN32_IE #undef _WIN32_IE #endif -#define _WIN32_IE 0x0400 +#define _WIN32_IE 0x0501 #define WIN32_LEAN_AND_MEAN 1 #ifndef NOMINMAX #define NOMINMAX diff --git a/src/version.h b/src/version.h index e8d5b5c421..423e885f93 100644 --- a/src/version.h +++ b/src/version.h @@ -12,8 +12,8 @@ static const int CLIENT_VERSION_MAJOR = 0; static const int CLIENT_VERSION_MINOR = 6; -static const int CLIENT_VERSION_REVISION = 1; -static const int CLIENT_VERSION_BUILD = 1; +static const int CLIENT_VERSION_REVISION = 99; +static const int CLIENT_VERSION_BUILD = 0; static const int CLIENT_VERSION = 1000000 * CLIENT_VERSION_MAJOR diff --git a/src/walletdb.cpp b/src/walletdb.cpp index 709ecac184..e5d57288e8 100644 --- a/src/walletdb.cpp +++ b/src/walletdb.cpp @@ -189,7 +189,7 @@ int CWalletDB::LoadWallet(CWallet* pwallet) //// debug print //printf("LoadWallet %s\n", wtx.GetHash().ToString().c_str()); - //printf(" %12I64d %s %s %s\n", + //printf(" %12"PRI64d" %s %s %s\n", // wtx.vout[0].nValue, // DateTimeStrFormat("%x %H:%M:%S", wtx.GetBlockTime()).c_str(), // wtx.hashBlock.ToString().substr(0,20).c_str(), |