Browse Source

monitor ThreadSocketHandler and terminate and restart if hung, convert _beginthread to CreateThread wrapper, disconnect inactive connections, ping, break up long messages to speed up initial download, better priorities for initiating connections, track how many nodes have requested our blocks and transactions, status #/offline and warning message on unsent blocks, minimize on close as separate option -- linux-test5

git-svn-id: https://bitcoin.svn.sourceforge.net/svnroot/bitcoin/trunk@38 1a98c847-1fd6-4fd8-948a-caf3550aa51b
metaverse
s_nakamoto 15 years ago
parent
commit
70e79525c9
  1. 2
      build-msw.txt
  2. 1
      build-unix.txt
  3. 9
      db.cpp
  4. 1
      headers.h
  5. 5
      irc.cpp
  6. 144
      main.cpp
  7. 25
      main.h
  8. 342
      net.cpp
  9. 25
      net.h
  10. 137
      ui.cpp
  11. 2
      ui.h
  12. 16
      uibase.cpp
  13. 92
      uibase.h
  14. 121
      uiproject.fbp
  15. 48
      util.cpp
  16. 105
      util.h

2
build-msw.txt

@ -8,7 +8,7 @@ the OpenSSL Toolkit (http://www.openssl.org/). This product includes
cryptographic software written by Eric Young (eay@cryptsoft.com). cryptographic software written by Eric Young (eay@cryptsoft.com).
WINDOWS BUILD NOTES WINDOWS BUILD NOTES
Compilers Supported Compilers Supported

1
build-unix.txt

@ -13,6 +13,7 @@ UNIX BUILD NOTES
Dependencies Dependencies
------------ ------------
Install the dev files for the shared libraries:
apt-get install build-essential apt-get install build-essential
apt-get install libgtk2.0-dev apt-get install libgtk2.0-dev
apt-get install libssl-dev apt-get install libssl-dev

9
db.cpp

@ -505,6 +505,13 @@ bool CWalletDB::LoadWallet(vector<unsigned char>& vchDefaultKeyRet)
{ {
vchDefaultKeyRet.clear(); vchDefaultKeyRet.clear();
// Modify defaults
#ifndef __WXMSW__
// Reports that tray icon can disappear on gnome, leaving no way to access the program
fMinimizeToTray = false;
fMinimizeOnClose = false;
#endif
//// todo: shouldn't we catch exceptions and try to recover and continue? //// todo: shouldn't we catch exceptions and try to recover and continue?
CRITICAL_BLOCK(cs_mapKeys) CRITICAL_BLOCK(cs_mapKeys)
CRITICAL_BLOCK(cs_mapWallet) CRITICAL_BLOCK(cs_mapWallet)
@ -638,7 +645,7 @@ bool LoadWallet(bool& fFirstRunRet)
CWalletDB().WriteDefaultKey(keyUser.GetPubKey()); CWalletDB().WriteDefaultKey(keyUser.GetPubKey());
} }
_beginthread(ThreadFlushWalletDB, 0, NULL); CreateThread(ThreadFlushWalletDB, NULL);
return true; return true;
} }

1
headers.h

@ -75,7 +75,6 @@
#include <net/if.h> #include <net/if.h>
#include <ifaddrs.h> #include <ifaddrs.h>
#include <boost/filesystem.hpp> #include <boost/filesystem.hpp>
#include <boost/thread/thread.hpp>
#include <boost/algorithm/string.hpp> #include <boost/algorithm/string.hpp>
#endif #endif

5
irc.cpp

@ -54,7 +54,7 @@ static bool Send(SOCKET hSocket, const char* pszSend)
const char* pszEnd = psz + strlen(psz); const char* pszEnd = psz + strlen(psz);
while (psz < pszEnd) while (psz < pszEnd)
{ {
int ret = send(hSocket, psz, pszEnd - psz, 0); int ret = send(hSocket, psz, pszEnd - psz, MSG_NOSIGNAL);
if (ret < 0) if (ret < 0)
return false; return false;
psz += ret; psz += ret;
@ -156,7 +156,7 @@ bool Wait(int nSeconds)
void ThreadIRCSeed(void* parg) void ThreadIRCSeed(void* parg)
{ {
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); SetThreadPriority(THREAD_PRIORITY_NORMAL);
int nErrorWait = 10; int nErrorWait = 10;
int nRetryWait = 10; int nRetryWait = 10;
@ -256,6 +256,7 @@ void ThreadIRCSeed(void* parg)
CAddress addr; CAddress addr;
if (DecodeAddress(pszName, addr)) if (DecodeAddress(pszName, addr))
{ {
addr.nTime = GetAdjustedTime() - 51 * 60;
CAddrDB addrdb; CAddrDB addrdb;
if (AddAddress(addrdb, addr)) if (AddAddress(addrdb, addr))
printf("IRC got new address\n"); printf("IRC got new address\n");

144
main.cpp

@ -42,6 +42,9 @@ map<uint160, vector<unsigned char> > mapPubKeys;
CCriticalSection cs_mapKeys; CCriticalSection cs_mapKeys;
CKey keyUser; CKey keyUser;
map<uint256, int> mapRequestCount;
CCriticalSection cs_mapRequestCount;
// Settings // Settings
int fGenerateBitcoins = false; int fGenerateBitcoins = false;
int64 nTransactionFee = 0; int64 nTransactionFee = 0;
@ -274,7 +277,44 @@ int64 CWalletTx::GetTxTime() const
return nTimeReceived; return nTimeReceived;
} }
int CWalletTx::GetRequestCount() const
{
// Returns -1 if it wasn't being tracked
int nRequests = -1;
CRITICAL_BLOCK(cs_mapRequestCount)
{
if (IsCoinBase())
{
// Generated block
if (hashBlock != 0)
{
map<uint256, int>::iterator mi = mapRequestCount.find(hashBlock);
if (mi != mapRequestCount.end())
nRequests = (*mi).second;
}
}
else
{
// Did anyone request this transaction?
map<uint256, int>::iterator mi = mapRequestCount.find(GetHash());
if (mi != mapRequestCount.end())
{
nRequests = (*mi).second;
// How about the block it's in?
if (nRequests == 0 && hashBlock != 0)
{
map<uint256, int>::iterator mi = mapRequestCount.find(hashBlock);
if (mi != mapRequestCount.end())
nRequests = (*mi).second;
else
nRequests = 1; // If it's in someone else's block it must have got out
}
}
}
}
return nRequests;
}
@ -295,7 +335,7 @@ int CMerkleTx::SetMerkleBranch(const CBlock* pblock)
CTxIndex txindex; CTxIndex txindex;
if (!CTxDB("r").ReadTxIndex(GetHash(), txindex)) if (!CTxDB("r").ReadTxIndex(GetHash(), txindex))
return 0; return 0;
if (!blockTmp.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos, true)) if (!blockTmp.ReadFromDisk(txindex.pos.nFile, txindex.pos.nBlockPos))
return 0; return 0;
pblock = &blockTmp; pblock = &blockTmp;
} }
@ -1003,7 +1043,7 @@ bool Reorganize(CTxDB& txdb, CBlockIndex* pindexNew)
foreach(CBlockIndex* pindex, vDisconnect) foreach(CBlockIndex* pindex, vDisconnect)
{ {
CBlock block; CBlock block;
if (!block.ReadFromDisk(pindex->nFile, pindex->nBlockPos, true)) if (!block.ReadFromDisk(pindex->nFile, pindex->nBlockPos))
return error("Reorganize() : ReadFromDisk for disconnect failed"); return error("Reorganize() : ReadFromDisk for disconnect failed");
if (!block.DisconnectBlock(txdb, pindex)) if (!block.DisconnectBlock(txdb, pindex))
return error("Reorganize() : DisconnectBlock failed"); return error("Reorganize() : DisconnectBlock failed");
@ -1020,7 +1060,7 @@ bool Reorganize(CTxDB& txdb, CBlockIndex* pindexNew)
{ {
CBlockIndex* pindex = vConnect[i]; CBlockIndex* pindex = vConnect[i];
CBlock block; CBlock block;
if (!block.ReadFromDisk(pindex->nFile, pindex->nBlockPos, true)) if (!block.ReadFromDisk(pindex->nFile, pindex->nBlockPos))
return error("Reorganize() : ReadFromDisk for connect failed"); return error("Reorganize() : ReadFromDisk for connect failed");
if (!block.ConnectBlock(txdb, pindex)) if (!block.ConnectBlock(txdb, pindex))
{ {
@ -1380,7 +1420,7 @@ bool CheckDiskSpace(int64 nAdditionalBytes)
{ {
fShutdown = true; fShutdown = true;
ThreadSafeMessageBox("Warning: Your disk space is low ", "Bitcoin", wxOK | wxICON_EXCLAMATION); ThreadSafeMessageBox("Warning: Your disk space is low ", "Bitcoin", wxOK | wxICON_EXCLAMATION);
_beginthread(Shutdown, 0, NULL); CreateThread(Shutdown, NULL);
return false; return false;
} }
return true; return true;
@ -1547,7 +1587,7 @@ void PrintBlockTree()
// print item // print item
CBlock block; CBlock block;
block.ReadFromDisk(pindex, true); block.ReadFromDisk(pindex);
printf("%d (%u,%u) %s %s tx %d", printf("%d (%u,%u) %s %s tx %d",
pindex->nHeight, pindex->nHeight,
pindex->nFile, pindex->nFile,
@ -1623,7 +1663,8 @@ bool ProcessMessages(CNode* pfrom)
CDataStream& vRecv = pfrom->vRecv; CDataStream& vRecv = pfrom->vRecv;
if (vRecv.empty()) if (vRecv.empty())
return true; return true;
//printf("ProcessMessages(%d bytes)\n", vRecv.size()); //if (fDebug)
// printf("ProcessMessages(%d bytes)\n", vRecv.size());
// //
// Message format // Message format
@ -1666,7 +1707,8 @@ bool ProcessMessages(CNode* pfrom)
{ {
// Rewind and wait for rest of message // Rewind and wait for rest of message
///// need a mechanism to give up waiting for overlong message size error ///// need a mechanism to give up waiting for overlong message size error
//printf("message-break\n"); //if (fDebug)
// printf("message-break\n");
vRecv.insert(vRecv.begin(), BEGIN(hdr), END(hdr)); vRecv.insert(vRecv.begin(), BEGIN(hdr), END(hdr));
Sleep(100); Sleep(100);
break; break;
@ -1718,6 +1760,8 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
{ {
static map<unsigned int, vector<unsigned char> > mapReuseKey; static map<unsigned int, vector<unsigned char> > mapReuseKey;
RandAddSeedPerfmon(); RandAddSeedPerfmon();
if (fDebug)
printf("%s ", DateTimeStrFormat("%x %H:%M:%S", GetTime()).c_str());
printf("received: %s (%d bytes)\n", strCommand.c_str(), vRecv.size()); printf("received: %s (%d bytes)\n", strCommand.c_str(), vRecv.size());
if (mapArgs.count("-dropmessagestest") && GetRand(atoi(mapArgs["-dropmessagestest"])) == 0) if (mapArgs.count("-dropmessagestest") && GetRand(atoi(mapArgs["-dropmessagestest"])) == 0)
{ {
@ -1739,18 +1783,19 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
CAddress addrMe; CAddress addrMe;
CAddress addrFrom; CAddress addrFrom;
uint64 nNonce = 1; uint64 nNonce = 1;
string strSubVer;
vRecv >> pfrom->nVersion >> pfrom->nServices >> nTime >> addrMe; vRecv >> pfrom->nVersion >> pfrom->nServices >> nTime >> addrMe;
if (pfrom->nVersion >= 106 && !vRecv.empty()) if (pfrom->nVersion >= 106 && !vRecv.empty())
vRecv >> addrFrom >> nNonce; vRecv >> addrFrom >> nNonce;
if (pfrom->nVersion >= 106 && !vRecv.empty())
vRecv >> strSubVer;
if (pfrom->nVersion == 0) if (pfrom->nVersion == 0)
return false; return false;
// Disconnect if we connected to ourself // Disconnect if we connected to ourself
if (nNonce == nLocalHostNonce) if (nNonce == nLocalHostNonce && nNonce > 1)
{ {
pfrom->fDisconnect = true; pfrom->fDisconnect = true;
pfrom->vRecv.clear();
pfrom->vSend.clear();
return true; return true;
} }
@ -1776,10 +1821,6 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
pfrom->fSuccessfullyConnected = true; pfrom->fSuccessfullyConnected = true;
// Update the last seen time
if (pfrom->fNetworkNode)
AddressCurrentlyConnected(pfrom->addr);
printf("version message: version %d\n", pfrom->nVersion); printf("version message: version %d\n", pfrom->nVersion);
} }
@ -1824,10 +1865,6 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
vector<CInv> vInv; vector<CInv> vInv;
vRecv >> vInv; vRecv >> vInv;
// Update the last seen time for this node's address
if (pfrom->fNetworkNode)
AddressCurrentlyConnected(pfrom->addr);
CTxDB txdb("r"); CTxDB txdb("r");
foreach(const CInv& inv, vInv) foreach(const CInv& inv, vInv)
{ {
@ -1842,6 +1879,14 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
pfrom->AskFor(inv); pfrom->AskFor(inv);
else if (inv.type == MSG_BLOCK && mapOrphanBlocks.count(inv.hash)) else if (inv.type == MSG_BLOCK && mapOrphanBlocks.count(inv.hash))
pfrom->PushMessage("getblocks", CBlockLocator(pindexBest), GetOrphanRoot(mapOrphanBlocks[inv.hash])); pfrom->PushMessage("getblocks", CBlockLocator(pindexBest), GetOrphanRoot(mapOrphanBlocks[inv.hash]));
// Track requests for our stuff
CRITICAL_BLOCK(cs_mapRequestCount)
{
map<uint256, int>::iterator mi = mapRequestCount.find(inv.hash);
if (mi != mapRequestCount.end())
(*mi).second++;
}
} }
} }
@ -1879,6 +1924,14 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
pfrom->PushMessage(inv.GetCommand(), (*mi).second); pfrom->PushMessage(inv.GetCommand(), (*mi).second);
} }
} }
// Track requests for our stuff
CRITICAL_BLOCK(cs_mapRequestCount)
{
map<uint256, int>::iterator mi = mapRequestCount.find(inv.hash);
if (mi != mapRequestCount.end())
(*mi).second++;
}
} }
} }
@ -2086,11 +2139,23 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv)
} }
else if (strCommand == "ping")
{
}
else else
{ {
// Ignore unknown commands for extensibility // Ignore unknown commands for extensibility
} }
// Update the last seen time for this node's address
if (pfrom->fNetworkNode)
if (strCommand == "version" || strCommand == "addr" || strCommand == "inv" || strCommand == "getdata" || strCommand == "ping")
AddressCurrentlyConnected(pfrom->addr);
return true; return true;
} }
@ -2129,6 +2194,10 @@ bool SendMessages(CNode* pto)
} }
} }
// Keep-alive ping
if (pto->nLastSend && GetTime() - pto->nLastSend > 12 * 60 && pto->vSend.empty())
pto->PushMessage("ping");
// //
// Message: addr // Message: addr
@ -2139,7 +2208,14 @@ bool SendMessages(CNode* pto)
{ {
// returns true if wasn't already contained in the set // returns true if wasn't already contained in the set
if (pto->setAddrKnown.insert(addr).second) if (pto->setAddrKnown.insert(addr).second)
{
vAddrToSend.push_back(addr); vAddrToSend.push_back(addr);
if (vAddrToSend.size() >= 1000)
{
pto->PushMessage("addr", vAddrToSend);
vAddrToSend.clear();
}
}
} }
pto->vAddrToSend.clear(); pto->vAddrToSend.clear();
if (!vAddrToSend.empty()) if (!vAddrToSend.empty())
@ -2157,7 +2233,14 @@ bool SendMessages(CNode* pto)
{ {
// returns true if wasn't already contained in the set // returns true if wasn't already contained in the set
if (pto->setInventoryKnown.insert(inv).second) if (pto->setInventoryKnown.insert(inv).second)
{
vInventoryToSend.push_back(inv); vInventoryToSend.push_back(inv);
if (vInventoryToSend.size() >= 1000)
{
pto->PushMessage("inv", vInventoryToSend);
vInventoryToSend.clear();
}
}
} }
pto->vInventoryToSend.clear(); pto->vInventoryToSend.clear();
pto->setInventoryKnown2.clear(); pto->setInventoryKnown2.clear();
@ -2179,6 +2262,11 @@ bool SendMessages(CNode* pto)
{ {
printf("sending getdata: %s\n", inv.ToString().c_str()); printf("sending getdata: %s\n", inv.ToString().c_str());
vAskFor.push_back(inv); vAskFor.push_back(inv);
if (vAskFor.size() >= 1000)
{
pto->PushMessage("getdata", vAskFor);
vAskFor.clear();
}
} }
pto->mapAskFor.erase(pto->mapAskFor.begin()); pto->mapAskFor.erase(pto->mapAskFor.begin());
} }
@ -2226,8 +2314,8 @@ void GenerateBitcoins(bool fGenerate)
int nAddThreads = nProcessors - vnThreadsRunning[3]; int nAddThreads = nProcessors - vnThreadsRunning[3];
printf("Starting %d BitcoinMiner threads\n", nAddThreads); printf("Starting %d BitcoinMiner threads\n", nAddThreads);
for (int i = 0; i < nAddThreads; i++) for (int i = 0; i < nAddThreads; i++)
if (_beginthread(ThreadBitcoinMiner, 0, NULL) == -1) if (!CreateThread(ThreadBitcoinMiner, NULL))
printf("Error: _beginthread(ThreadBitcoinMiner) failed\n"); printf("Error: CreateThread(ThreadBitcoinMiner) failed\n");
} }
} }
@ -2304,7 +2392,7 @@ void BitcoinMiner()
CBigNum bnExtraNonce = 0; CBigNum bnExtraNonce = 0;
while (fGenerateBitcoins) while (fGenerateBitcoins)
{ {
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST); SetThreadPriority(THREAD_PRIORITY_LOWEST);
Sleep(50); Sleep(50);
if (fShutdown) if (fShutdown)
return; return;
@ -2440,7 +2528,7 @@ void BitcoinMiner()
printf("proof-of-work found \n hash: %s \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str()); printf("proof-of-work found \n hash: %s \ntarget: %s\n", hash.GetHex().c_str(), hashTarget.GetHex().c_str());
pblock->print(); pblock->print();
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_NORMAL); SetThreadPriority(THREAD_PRIORITY_NORMAL);
CRITICAL_BLOCK(cs_main) CRITICAL_BLOCK(cs_main)
{ {
if (pindexPrev == pindexBest) if (pindexPrev == pindexBest)
@ -2450,12 +2538,16 @@ void BitcoinMiner()
return; return;
key.MakeNewKey(); key.MakeNewKey();
// Track how many getdata requests this block gets
CRITICAL_BLOCK(cs_mapRequestCount)
mapRequestCount[pblock->GetHash()] = 0;
// Process this block the same as if we had received it from another node // Process this block the same as if we had received it from another node
if (!ProcessBlock(NULL, pblock.release())) if (!ProcessBlock(NULL, pblock.release()))
printf("ERROR in BitcoinMiner, ProcessBlock, block not accepted\n"); printf("ERROR in BitcoinMiner, ProcessBlock, block not accepted\n");
} }
} }
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_LOWEST); SetThreadPriority(THREAD_PRIORITY_LOWEST);
Sleep(500); Sleep(500);
break; break;
@ -2534,7 +2626,7 @@ bool SelectCoins(int64 nTargetValue, set<CWalletTx*>& setCoinsRet)
setCoinsRet.clear(); setCoinsRet.clear();
// List of values less than target // List of values less than target
int64 nLowestLarger = _I64_MAX; int64 nLowestLarger = INT64_MAX;
CWalletTx* pcoinLowestLarger = NULL; CWalletTx* pcoinLowestLarger = NULL;
vector<pair<int64, CWalletTx*> > vValue; vector<pair<int64, CWalletTx*> > vValue;
int64 nTotalLower = 0; int64 nTotalLower = 0;
@ -2777,6 +2869,10 @@ bool SendMoney(CScript scriptPubKey, int64 nValue, CWalletTx& wtxNew)
return error("SendMoney() : Error finalizing transaction"); return error("SendMoney() : Error finalizing transaction");
} }
// Track how many getdata requests our transaction gets
CRITICAL_BLOCK(cs_mapRequestCount)
mapRequestCount[wtxNew.GetHash()] = 0;
printf("SendMoney: %s\n", wtxNew.GetHash().ToString().substr(0,6).c_str()); printf("SendMoney: %s\n", wtxNew.GetHash().ToString().substr(0,6).c_str());
// Broadcast // Broadcast

25
main.h

@ -34,6 +34,8 @@ extern int nBestHeight;
extern uint256 hashBestChain; extern uint256 hashBestChain;
extern CBlockIndex* pindexBest; extern CBlockIndex* pindexBest;
extern unsigned int nTransactionsUpdated; extern unsigned int nTransactionsUpdated;
extern map<uint256, int> mapRequestCount;
extern CCriticalSection cs_mapRequestCount;
// Settings // Settings
extern int fGenerateBitcoins; extern int fGenerateBitcoins;
@ -647,6 +649,15 @@ public:
nGetCreditCached = 0; nGetCreditCached = 0;
} }
IMPLEMENT_SERIALIZE
(
nSerSize += SerReadWrite(s, *(CTransaction*)this, nType, nVersion, ser_action);
nVersion = this->nVersion;
READWRITE(hashBlock);
READWRITE(vMerkleBranch);
READWRITE(nIndex);
)
int64 GetCredit(bool fUseCache=false) const int64 GetCredit(bool fUseCache=false) const
{ {
// Must wait until coinbase is safely deep enough in the chain before valuing it // Must wait until coinbase is safely deep enough in the chain before valuing it
@ -661,15 +672,6 @@ public:
return nGetCreditCached; return nGetCreditCached;
} }
IMPLEMENT_SERIALIZE
(
nSerSize += SerReadWrite(s, *(CTransaction*)this, nType, nVersion, ser_action);
nVersion = this->nVersion;
READWRITE(hashBlock);
READWRITE(vMerkleBranch);
READWRITE(nIndex);
)
int SetMerkleBranch(const CBlock* pblock=NULL); int SetMerkleBranch(const CBlock* pblock=NULL);
int GetDepthInMainChain() const; int GetDepthInMainChain() const;
@ -749,6 +751,7 @@ public:
int64 GetTxTime() const; int64 GetTxTime() const;
int GetRequestCount() const;
void AddSupportingTransactions(CTxDB& txdb); void AddSupportingTransactions(CTxDB& txdb);
@ -978,7 +981,7 @@ public:
return true; return true;
} }
bool ReadFromDisk(unsigned int nFile, unsigned int nBlockPos, bool fReadTransactions) bool ReadFromDisk(unsigned int nFile, unsigned int nBlockPos, bool fReadTransactions=true)
{ {
SetNull(); SetNull();
@ -1027,7 +1030,7 @@ public:
int64 GetBlockValue(int64 nFees) const; int64 GetBlockValue(int64 nFees) const;
bool DisconnectBlock(CTxDB& txdb, CBlockIndex* pindex); bool DisconnectBlock(CTxDB& txdb, CBlockIndex* pindex);
bool ConnectBlock(CTxDB& txdb, CBlockIndex* pindex); bool ConnectBlock(CTxDB& txdb, CBlockIndex* pindex);
bool ReadFromDisk(const CBlockIndex* blockindex, bool fReadTransactions); bool ReadFromDisk(const CBlockIndex* blockindex, bool fReadTransactions=true);
bool AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos); bool AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos);
bool CheckBlock() const; bool CheckBlock() const;
bool AcceptBlock(); bool AcceptBlock();

342
net.cpp

@ -13,7 +13,6 @@ bool OpenNetworkConnection(const CAddress& addrConnect);
// //
// Global state variables // Global state variables
// //
@ -25,6 +24,7 @@ uint64 nLocalHostNonce = 0;
bool fShutdown = false; bool fShutdown = false;
array<int, 10> vnThreadsRunning; array<int, 10> vnThreadsRunning;
SOCKET hListenSocket = INVALID_SOCKET; SOCKET hListenSocket = INVALID_SOCKET;
int64 nThreadSocketHandlerHeartbeat = INT64_MAX;
vector<CNode*> vNodes; vector<CNode*> vNodes;
CCriticalSection cs_vNodes; CCriticalSection cs_vNodes;
@ -65,7 +65,7 @@ bool ConnectSocket(const CAddress& addrConnect, SOCKET& hSocketRet)
if (fProxy) if (fProxy)
{ {
printf("Proxy connecting %s\n", addrConnect.ToStringLog().c_str()); printf("proxy connecting %s\n", addrConnect.ToStringLog().c_str());
char pszSocks4IP[] = "\4\1\0\0\0\0\0\0user"; char pszSocks4IP[] = "\4\1\0\0\0\0\0\0user";
memcpy(pszSocks4IP + 2, &addrConnect.port, 2); memcpy(pszSocks4IP + 2, &addrConnect.port, 2);
memcpy(pszSocks4IP + 4, &addrConnect.ip, 4); memcpy(pszSocks4IP + 4, &addrConnect.ip, 4);
@ -87,9 +87,11 @@ bool ConnectSocket(const CAddress& addrConnect, SOCKET& hSocketRet)
if (pchRet[1] != 0x5a) if (pchRet[1] != 0x5a)
{ {
closesocket(hSocket); closesocket(hSocket);
return error("Proxy returned error %d", pchRet[1]); if (pchRet[1] != 0x5b)
printf("ERROR: Proxy returned error %d\n", pchRet[1]);
return false;
} }
printf("Proxy connection established %s\n", addrConnect.ToStringLog().c_str()); printf("proxy connected %s\n", addrConnect.ToStringLog().c_str());
} }
hSocketRet = hSocket; hSocketRet = hSocket;
@ -219,6 +221,7 @@ bool AddAddress(CAddrDB& addrdb, CAddress addr, bool fCurrentlyOnline)
if (it == mapAddresses.end()) if (it == mapAddresses.end())
{ {
// New address // New address
printf("AddAddress(%s)\n", addr.ToStringLog().c_str());
mapAddresses.insert(make_pair(addr.GetKey(), addr)); mapAddresses.insert(make_pair(addr.GetKey(), addr));
addrdb.WriteAddress(addr); addrdb.WriteAddress(addr);
return true; return true;
@ -256,7 +259,7 @@ void AddressCurrentlyConnected(const CAddress& addr)
if (it != mapAddresses.end()) if (it != mapAddresses.end())
{ {
CAddress& addrFound = (*it).second; CAddress& addrFound = (*it).second;
int64 nUpdateInterval = 60 * 60; int64 nUpdateInterval = 20 * 60;
if (addrFound.nTime < GetAdjustedTime() - nUpdateInterval) if (addrFound.nTime < GetAdjustedTime() - nUpdateInterval)
{ {
// Periodically update most recently seen time // Periodically update most recently seen time
@ -417,7 +420,13 @@ CNode* ConnectNode(CAddress addrConnect, int64 nTimeout)
} }
/// debug print /// debug print
printf("trying connection %s\n", addrConnect.ToStringLog().c_str()); printf("trying connection %s lastseen=%.1fhrs lasttry=%.1fhrs\n",
addrConnect.ToStringLog().c_str(),
(double)(addrConnect.nTime - GetAdjustedTime())/3600.0,
(double)(addrConnect.nLastTry - GetAdjustedTime())/3600.0);
CRITICAL_BLOCK(cs_mapAddresses)
mapAddresses[addrConnect.GetKey()].nLastTry = GetAdjustedTime();
// Connect // Connect
SOCKET hSocket; SOCKET hSocket;
@ -428,7 +437,7 @@ CNode* ConnectNode(CAddress addrConnect, int64 nTimeout)
// Set to nonblocking // Set to nonblocking
#ifdef __WXMSW__ #ifdef __WXMSW__
u_long nOne = 1; u_long nOne = 1;
if (ioctlsocket(hSocket, FIONBIO, &nOne) == SOCKET_ERROR) if (ioctlsocket(hSocket, FIONBIO, &nOne) == SOCKET_ERROR)
printf("ConnectSocket() : ioctlsocket nonblocking setting failed, error %d\n", WSAGetLastError()); printf("ConnectSocket() : ioctlsocket nonblocking setting failed, error %d\n", WSAGetLastError());
#else #else
@ -445,29 +454,23 @@ CNode* ConnectNode(CAddress addrConnect, int64 nTimeout)
CRITICAL_BLOCK(cs_vNodes) CRITICAL_BLOCK(cs_vNodes)
vNodes.push_back(pnode); vNodes.push_back(pnode);
CRITICAL_BLOCK(cs_mapAddresses) pnode->nTimeConnected = GetTime();
mapAddresses[addrConnect.GetKey()].nLastFailed = 0;
return pnode; return pnode;
} }
else else
{ {
CRITICAL_BLOCK(cs_mapAddresses)
mapAddresses[addrConnect.GetKey()].nLastFailed = GetAdjustedTime();
return NULL; return NULL;
} }
} }
void CNode::DoDisconnect() void CNode::DoDisconnect()
{ {
if (fDebug)
printf("%s ", DateTimeStrFormat("%x %H:%M:%S", GetTime()).c_str());
printf("disconnecting node %s\n", addr.ToStringLog().c_str()); printf("disconnecting node %s\n", addr.ToStringLog().c_str());
closesocket(hSocket); closesocket(hSocket);
// If outbound and never got version message, mark address as failed
if (!fInbound && !fSuccessfullyConnected)
CRITICAL_BLOCK(cs_mapAddresses)
mapAddresses[addr.GetKey()].nLastFailed = GetAdjustedTime();
// All of a nodes broadcasts and subscriptions are automatically torn down // All of a nodes broadcasts and subscriptions are automatically torn down
// when it goes down, so a node has to stay up to keep its broadcast going. // when it goes down, so a node has to stay up to keep its broadcast going.
@ -508,7 +511,7 @@ void ThreadSocketHandler(void* parg)
PrintException(&e, "ThreadSocketHandler()"); PrintException(&e, "ThreadSocketHandler()");
} catch (...) { } catch (...) {
vnThreadsRunning[0]--; vnThreadsRunning[0]--;
PrintException(NULL, "ThreadSocketHandler()"); throw; // support pthread_cancel()
} }
printf("ThreadSocketHandler exiting\n"); printf("ThreadSocketHandler exiting\n");
@ -531,15 +534,18 @@ void ThreadSocketHandler2(void* parg)
vector<CNode*> vNodesCopy = vNodes; vector<CNode*> vNodesCopy = vNodes;
foreach(CNode* pnode, vNodesCopy) foreach(CNode* pnode, vNodesCopy)
{ {
if (pnode->ReadyToDisconnect() && pnode->vRecv.empty() && pnode->vSend.empty()) if (pnode->fDisconnect ||
(pnode->GetRefCount() <= 0 && pnode->vRecv.empty() && pnode->vSend.empty()))
{ {
// remove from vNodes // remove from vNodes
vNodes.erase(remove(vNodes.begin(), vNodes.end(), pnode), vNodes.end()); vNodes.erase(remove(vNodes.begin(), vNodes.end(), pnode), vNodes.end());
// close socket
pnode->DoDisconnect(); pnode->DoDisconnect();
// hold in disconnected pool until all refs are released // hold in disconnected pool until all refs are released
pnode->nReleaseTime = max(pnode->nReleaseTime, GetTime() + 5 * 60); pnode->nReleaseTime = max(pnode->nReleaseTime, GetTime() + 5 * 60);
if (pnode->fNetworkNode) if (pnode->fNetworkNode || pnode->fInbound)
pnode->Release(); pnode->Release();
vNodesDisconnected.push_back(pnode); vNodesDisconnected.push_back(pnode);
} }
@ -582,8 +588,10 @@ void ThreadSocketHandler2(void* parg)
fd_set fdsetRecv; fd_set fdsetRecv;
fd_set fdsetSend; fd_set fdsetSend;
fd_set fdsetError;
FD_ZERO(&fdsetRecv); FD_ZERO(&fdsetRecv);
FD_ZERO(&fdsetSend); FD_ZERO(&fdsetSend);
FD_ZERO(&fdsetError);
SOCKET hSocketMax = 0; SOCKET hSocketMax = 0;
FD_SET(hListenSocket, &fdsetRecv); FD_SET(hListenSocket, &fdsetRecv);
hSocketMax = max(hSocketMax, hListenSocket); hSocketMax = max(hSocketMax, hListenSocket);
@ -592,6 +600,7 @@ void ThreadSocketHandler2(void* parg)
foreach(CNode* pnode, vNodes) foreach(CNode* pnode, vNodes)
{ {
FD_SET(pnode->hSocket, &fdsetRecv); FD_SET(pnode->hSocket, &fdsetRecv);
FD_SET(pnode->hSocket, &fdsetError);
hSocketMax = max(hSocketMax, pnode->hSocket); hSocketMax = max(hSocketMax, pnode->hSocket);
TRY_CRITICAL_BLOCK(pnode->cs_vSend) TRY_CRITICAL_BLOCK(pnode->cs_vSend)
if (!pnode->vSend.empty()) if (!pnode->vSend.empty())
@ -600,30 +609,21 @@ void ThreadSocketHandler2(void* parg)
} }
vnThreadsRunning[0]--; vnThreadsRunning[0]--;
int nSelect = select(hSocketMax + 1, &fdsetRecv, &fdsetSend, NULL, &timeout); int nSelect = select(hSocketMax + 1, &fdsetRecv, &fdsetSend, &fdsetError, &timeout);
vnThreadsRunning[0]++; vnThreadsRunning[0]++;
if (fShutdown) if (fShutdown)
return; return;
if (nSelect == SOCKET_ERROR) if (nSelect == SOCKET_ERROR)
{ {
int nErr = WSAGetLastError(); int nErr = WSAGetLastError();
printf("select failed: %d\n", nErr); printf("socket select error %d\n", nErr);
for (int i = 0; i <= hSocketMax; i++) for (int i = 0; i <= hSocketMax; i++)
{
FD_SET(i, &fdsetRecv); FD_SET(i, &fdsetRecv);
FD_SET(i, &fdsetSend); FD_ZERO(&fdsetSend);
} FD_ZERO(&fdsetError);
Sleep(timeout.tv_usec/1000); Sleep(timeout.tv_usec/1000);
} }
//// debug print
//foreach(CNode* pnode, vNodes)
//{
// printf("vRecv = %-5d ", pnode->vRecv.size());
// printf("vSend = %-5d ", pnode->vSend.size());
//}
//printf("\n");
// //
// Accept new connections // Accept new connections
@ -641,7 +641,7 @@ void ThreadSocketHandler2(void* parg)
if (hSocket == INVALID_SOCKET) if (hSocket == INVALID_SOCKET)
{ {
if (WSAGetLastError() != WSAEWOULDBLOCK) if (WSAGetLastError() != WSAEWOULDBLOCK)
printf("ERROR ThreadSocketHandler accept failed: %d\n", WSAGetLastError()); printf("socket error accept failed: %d\n", WSAGetLastError());
} }
else else
{ {
@ -669,7 +669,7 @@ void ThreadSocketHandler2(void* parg)
// //
// Receive // Receive
// //
if (FD_ISSET(hSocket, &fdsetRecv)) if (FD_ISSET(hSocket, &fdsetRecv) || FD_ISSET(hSocket, &fdsetError))
{ {
TRY_CRITICAL_BLOCK(pnode->cs_vRecv) TRY_CRITICAL_BLOCK(pnode->cs_vRecv)
{ {
@ -677,25 +677,29 @@ void ThreadSocketHandler2(void* parg)
unsigned int nPos = vRecv.size(); unsigned int nPos = vRecv.size();
// typical socket buffer is 8K-64K // typical socket buffer is 8K-64K
const unsigned int nBufSize = 0x10000; char pchBuf[0x10000];
vRecv.resize(nPos + nBufSize); int nBytes = recv(hSocket, pchBuf, sizeof(pchBuf), MSG_DONTWAIT);
int nBytes = recv(hSocket, &vRecv[nPos], nBufSize, 0); if (nBytes > 0)
vRecv.resize(nPos + max(nBytes, 0)); {
if (nBytes == 0) vRecv.resize(nPos + nBytes);
memcpy(&vRecv[nPos], pchBuf, nBytes);
pnode->nLastRecv = GetTime();
}
else if (nBytes == 0)
{ {
// socket closed gracefully // socket closed gracefully
if (!pnode->fDisconnect) if (!pnode->fDisconnect)
printf("recv: socket closed\n"); printf("socket closed\n");
pnode->fDisconnect = true; pnode->fDisconnect = true;
} }
else if (nBytes < 0) else if (nBytes < 0)
{ {
// socket error // error
int nErr = WSAGetLastError(); int nErr = WSAGetLastError();
if (nErr != WSAEWOULDBLOCK && nErr != WSAEMSGSIZE && nErr != WSAEINTR && nErr != WSAEINPROGRESS) if (nErr != WSAEWOULDBLOCK && nErr != WSAEMSGSIZE && nErr != WSAEINTR && nErr != WSAEINPROGRESS)
{ {
if (!pnode->fDisconnect) if (!pnode->fDisconnect)
printf("recv failed: %d\n", nErr); printf("socket recv error %d\n", nErr);
pnode->fDisconnect = true; pnode->fDisconnect = true;
} }
} }
@ -712,28 +716,63 @@ void ThreadSocketHandler2(void* parg)
CDataStream& vSend = pnode->vSend; CDataStream& vSend = pnode->vSend;
if (!vSend.empty()) if (!vSend.empty())
{ {
int nBytes = send(hSocket, &vSend[0], vSend.size(), MSG_NOSIGNAL); int nBytes = send(hSocket, &vSend[0], vSend.size(), MSG_NOSIGNAL | MSG_DONTWAIT);
if (nBytes > 0) if (nBytes > 0)
{ {
vSend.erase(vSend.begin(), vSend.begin() + nBytes); vSend.erase(vSend.begin(), vSend.begin() + nBytes);
pnode->nLastSend = GetTime();
} }
else if (nBytes == 0) else if (nBytes < 0)
{
if (pnode->ReadyToDisconnect())
pnode->vSend.clear();
}
else
{ {
printf("send error %d\n", nBytes); // error
if (pnode->ReadyToDisconnect()) int nErr = WSAGetLastError();
pnode->vSend.clear(); if (nErr != WSAEWOULDBLOCK && nErr != WSAEMSGSIZE && nErr != WSAEINTR && nErr != WSAEINPROGRESS)
{
printf("socket send error %d\n", nErr);
pnode->fDisconnect = true;
}
} }
} }
} }
} }
//
// Inactivity checking
//
if (pnode->vSend.empty())
pnode->nLastSendEmpty = GetTime();
if (GetTime() - pnode->nTimeConnected > 60)
{
if (pnode->nLastRecv == 0 || pnode->nLastSend == 0)
{
printf("socket no message in first 60 seconds, %d %d\n", pnode->nLastRecv != 0, pnode->nLastSend != 0);
pnode->fDisconnect = true;
}
else if (GetTime() - pnode->nLastSend > 10 * 60 && GetTime() - pnode->nLastSendEmpty > 10 * 60)
{
printf("socket not sending\n");
pnode->fDisconnect = true;
}
else if (GetTime() - pnode->nLastRecv > (pnode->nVersion >= 107 ? 15*60 : 90*60))
{
printf("socket inactivity timeout\n");
pnode->fDisconnect = true;
}
}
}
//// debug heartbeat
static int64 nHeartbeat1;
if (GetTime() - nHeartbeat1 >= 5 * 60)
{
printf("%s sendrecv\n", DateTimeStrFormat("%x %H:%M:%S", GetTime()).c_str());
nHeartbeat1 = GetTime();
fDebug = true;
} }
nThreadSocketHandlerHeartbeat = GetTime();
Sleep(10); Sleep(10);
} }
} }
@ -772,15 +811,20 @@ void ThreadOpenConnections2(void* parg)
{ {
printf("ThreadOpenConnections started\n"); printf("ThreadOpenConnections started\n");
// Connect to one specified address // Connect to specific addresses
while (mapArgs.count("-connect")) while (mapArgs.count("-connect"))
{ {
OpenNetworkConnection(CAddress(mapArgs["-connect"])); foreach(string strAddr, mapMultiArgs["-connect"])
for (int i = 0; i < 10; i++)
{ {
Sleep(1000); CAddress addr(strAddr, NODE_NETWORK);
if (fShutdown) if (addr.IsValid())
return; OpenNetworkConnection(addr);
for (int i = 0; i < 10; i++)
{
Sleep(1000);
if (fShutdown)
return;
}
} }
} }
@ -821,12 +865,7 @@ void ThreadOpenConnections2(void* parg)
// Choose an address to connect to based on most recently seen // Choose an address to connect to based on most recently seen
// //
CAddress addrConnect; CAddress addrConnect;
int64 nBestTime = 0; int64 nBest = INT64_MIN;
int64 nDelay = ((60 * 60) << vNodes.size());
if (vNodes.size() >= 3)
nDelay *= 4;
if (nGotIRCAddresses > 0)
nDelay *= 100;
// Do this here so we don't have to critsect vNodes inside mapAddresses critsect // Do this here so we don't have to critsect vNodes inside mapAddresses critsect
set<unsigned int> setConnected; set<unsigned int> setConnected;
@ -841,24 +880,51 @@ void ThreadOpenConnections2(void* parg)
const CAddress& addr = item.second; const CAddress& addr = item.second;
if (!addr.IsIPv4() || !addr.IsValid() || setConnected.count(addr.ip)) if (!addr.IsIPv4() || !addr.IsValid() || setConnected.count(addr.ip))
continue; continue;
int64 nSinceLastSeen = GetAdjustedTime() - addr.nTime;
int64 nSinceLastTry = GetAdjustedTime() - addr.nLastTry;
// Randomize the order in a deterministic way, putting the standard port first // Randomize the order in a deterministic way, putting the standard port first
int64 nRandomizer = (uint64)(addr.nLastFailed * 9567851 + addr.ip * 7789) % (1 * 60 * 60); int64 nRandomizer = (uint64)(addr.nLastTry * 9567851 + addr.ip * 7789) % (30 * 60);
if (addr.port != DEFAULT_PORT) if (addr.port != DEFAULT_PORT)
nRandomizer += 1 * 60 * 60; nRandomizer += 30 * 60;
// Last seen Base retry frequency
// <1 hour 10 min
// 1 hour 1 hour
// 4 hours 2 hours
// 24 hours 5 hours
// 48 hours 7 hours
// 7 days 13 hours
// 30 days 27 hours
// 90 days 46 hours
// 365 days 93 hours
int64 nDelay = 3600.0 * sqrt(fabs(nSinceLastSeen) / 3600.0) + nRandomizer;
// Fast reconnect for one hour after last seen
if (nSinceLastSeen < 60 * 60)
nDelay = 10 * 60;
// Limit retry frequency // Limit retry frequency
if (GetAdjustedTime() < addr.nLastFailed + nDelay + nRandomizer) if (nSinceLastTry < nDelay)
continue; continue;
// Try again only after all addresses had a first attempt // If we have IRC, we'll be notified when they first come online,
int64 nTime = addr.nTime - nRandomizer; // and again every 24 hours by the refresh broadcast.
if (addr.nLastFailed > addr.nTime) if (nGotIRCAddresses > 0 && vNodes.size() >= 2 && nSinceLastSeen > 24 * 60 * 60)
nTime -= 365 * 24 * 60 * 60; continue;
if (nTime > nBestTime) // Only try the old stuff if we don't have enough connections
if (vNodes.size() >= 2 && nSinceLastSeen > 7 * 24 * 60 * 60)
continue;
if (vNodes.size() >= 4 && nSinceLastSeen > 24 * 60 * 60)
continue;
// If multiple addresses are ready, prioritize by time since
// last seen and time since last tried.
int64 nScore = min(nSinceLastTry, (int64)24 * 60 * 60) - nSinceLastSeen - nRandomizer;
if (nScore > nBest)
{ {
nBestTime = nTime; nBest = nScore;
addrConnect = addr; addrConnect = addr;
} }
} }
@ -941,7 +1007,7 @@ void ThreadMessageHandler(void* parg)
void ThreadMessageHandler2(void* parg) void ThreadMessageHandler2(void* parg)
{ {
printf("ThreadMessageHandler started\n"); printf("ThreadMessageHandler started\n");
SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_BELOW_NORMAL); SetThreadPriority(THREAD_PRIORITY_BELOW_NORMAL);
loop loop
{ {
// Poll the connected nodes for messages // Poll the connected nodes for messages
@ -1063,39 +1129,31 @@ bool BindListenPort(string& strError)
return true; return true;
} }
bool StartNode(string& strError) void StartNode(void* parg)
{ {
strError = "";
if (pnodeLocalHost == NULL) if (pnodeLocalHost == NULL)
pnodeLocalHost = new CNode(INVALID_SOCKET, CAddress("127.0.0.1", nLocalServices)); pnodeLocalHost = new CNode(INVALID_SOCKET, CAddress("127.0.0.1", nLocalServices));
#ifdef __WXMSW__ #ifdef __WXMSW__
// Get local host ip // Get local host ip
char pszHostName[255]; char pszHostName[1000] = "";
if (gethostname(pszHostName, sizeof(pszHostName)) == SOCKET_ERROR) if (gethostname(pszHostName, sizeof(pszHostName)) != SOCKET_ERROR)
{ {
strError = strprintf("Error: Unable to get IP address of this computer (gethostname returned error %d)", WSAGetLastError()); struct hostent* phostent = gethostbyname(pszHostName);
printf("%s\n", strError.c_str()); if (phostent)
return false;
}
struct hostent* phostent = gethostbyname(pszHostName);
if (!phostent)
{
strError = strprintf("Error: Unable to get IP address of this computer (gethostbyname returned error %d)", WSAGetLastError());
printf("%s\n", strError.c_str());
return false;
}
// Take the first IP that isn't loopback 127.x.x.x
for (int i = 0; phostent->h_addr_list[i] != NULL; i++)
printf("host ip %d: %s\n", i, CAddress(*(unsigned int*)phostent->h_addr_list[i]).ToStringIP().c_str());
for (int i = 0; phostent->h_addr_list[i] != NULL; i++)
{
CAddress addr(*(unsigned int*)phostent->h_addr_list[i], DEFAULT_PORT, nLocalServices);
if (addr.IsValid() && addr.GetByte(3) != 127)
{ {
addrLocalHost = addr; // Take the first IP that isn't loopback 127.x.x.x
break; for (int i = 0; phostent->h_addr_list[i] != NULL; i++)
printf("host ip %d: %s\n", i, CAddress(*(unsigned int*)phostent->h_addr_list[i]).ToStringIP().c_str());
for (int i = 0; phostent->h_addr_list[i] != NULL; i++)
{
CAddress addr(*(unsigned int*)phostent->h_addr_list[i], DEFAULT_PORT, nLocalServices);
if (addr.IsValid() && addr.GetByte(3) != 127)
{
addrLocalHost = addr;
break;
}
}
} }
} }
#else #else
@ -1145,45 +1203,85 @@ bool StartNode(string& strError)
} }
else else
{ {
if (addrIncoming.ip) if (addrIncoming.IsValid())
addrLocalHost.ip = addrIncoming.ip; addrLocalHost.ip = addrIncoming.ip;
if (GetMyExternalIP(addrLocalHost.ip)) if (GetMyExternalIP(addrLocalHost.ip))
{ {
addrIncoming = addrLocalHost; addrIncoming = addrLocalHost;
CWalletDB().WriteSetting("addrIncoming", addrIncoming); CWalletDB().WriteSetting("addrIncoming", addrIncoming);
printf("addrLocalHost = %s\n", addrLocalHost.ToString().c_str());
} }
} }
// Get addresses from IRC and advertise ours
if (_beginthread(ThreadIRCSeed, 0, NULL) == -1)
printf("Error: _beginthread(ThreadIRCSeed) failed\n");
// //
// Start threads // Start threads
// //
if (_beginthread(ThreadSocketHandler, 0, NULL) == -1)
{
strError = "Error: _beginthread(ThreadSocketHandler) failed";
printf("%s\n", strError.c_str());
return false;
}
if (_beginthread(ThreadOpenConnections, 0, NULL) == -1) // Get addresses from IRC and advertise ours
{ if (!CreateThread(ThreadIRCSeed, NULL))
strError = "Error: _beginthread(ThreadOpenConnections) failed"; printf("Error: CreateThread(ThreadIRCSeed) failed\n");
printf("%s\n", strError.c_str());
return false; // Send and receive from sockets, accept connections
} pthread_t hThreadSocketHandler = CreateThread(ThreadSocketHandler, NULL, true);
// Initiate outbound connections
if (!CreateThread(ThreadOpenConnections, NULL))
printf("Error: CreateThread(ThreadOpenConnections) failed\n");
// Process messages
if (!CreateThread(ThreadMessageHandler, NULL))
printf("Error: CreateThread(ThreadMessageHandler) failed\n");
if (_beginthread(ThreadMessageHandler, 0, NULL) == -1) // Generate coins in the background
GenerateBitcoins(fGenerateBitcoins);
//
// Thread monitoring
//
loop
{ {
strError = "Error: _beginthread(ThreadMessageHandler) failed"; Sleep(15000);
printf("%s\n", strError.c_str()); if (GetTime() - nThreadSocketHandlerHeartbeat > 4 * 60)
return false; {
// First see if closing sockets will free it
printf("*** ThreadSocketHandler is stopped ***\n");
CRITICAL_BLOCK(cs_vNodes)
{
foreach(CNode* pnode, vNodes)
{
bool fGot = false;
TRY_CRITICAL_BLOCK(pnode->cs_vRecv)
TRY_CRITICAL_BLOCK(pnode->cs_vSend)
fGot = true;
if (!fGot)
{
printf("*** closing socket\n");
closesocket(pnode->hSocket);
pnode->fDisconnect = true;
}
}
}
Sleep(10000);
if (GetTime() - nThreadSocketHandlerHeartbeat < 60)
continue;
// Hopefully it never comes to this.
// We know it'll always be hung in the recv or send call.
// cs_vRecv or cs_vSend may be left permanently unreleased,
// but we always only use TRY_CRITICAL_SECTION on them.
printf("*** Restarting ThreadSocketHandler ***\n");
TerminateThread(hThreadSocketHandler, 0);
#ifdef __WXMSW__
CloseHandle(hThreadSocketHandler);
#endif
vnThreadsRunning[0] = 0;
// Restart
hThreadSocketHandler = CreateThread(ThreadSocketHandler, NULL, true);
nThreadSocketHandlerHeartbeat = GetTime();
}
} }
return true;
} }
bool StopNode() bool StopNode()

25
net.h

@ -29,7 +29,7 @@ CNode* ConnectNode(CAddress addrConnect, int64 nTimeout=0);
void AbandonRequests(void (*fn)(void*, CDataStream&), void* param1); void AbandonRequests(void (*fn)(void*, CDataStream&), void* param1);
bool AnySubscribed(unsigned int nChannel); bool AnySubscribed(unsigned int nChannel);
bool BindListenPort(string& strError=REF(string())); bool BindListenPort(string& strError=REF(string()));
bool StartNode(string& strError=REF(string())); void StartNode(void* parg);
bool StopNode(); bool StopNode();
@ -39,7 +39,6 @@ bool StopNode();
// //
// Message header // Message header
// (4) message start // (4) message start
@ -139,7 +138,7 @@ public:
unsigned int nTime; unsigned int nTime;
// memory only // memory only
unsigned int nLastFailed; unsigned int nLastTry;
CAddress() CAddress()
{ {
@ -183,7 +182,7 @@ public:
ip = INADDR_NONE; ip = INADDR_NONE;
port = DEFAULT_PORT; port = DEFAULT_PORT;
nTime = GetAdjustedTime(); nTime = GetAdjustedTime();
nLastFailed = 0; nLastTry = 0;
} }
bool SetAddress(const char* pszIn) bool SetAddress(const char* pszIn)
@ -458,6 +457,7 @@ extern uint64 nLocalHostNonce;
extern bool fShutdown; extern bool fShutdown;
extern array<int, 10> vnThreadsRunning; extern array<int, 10> vnThreadsRunning;
extern SOCKET hListenSocket; extern SOCKET hListenSocket;
extern int64 nThreadSocketHandlerHeartbeat;
extern vector<CNode*> vNodes; extern vector<CNode*> vNodes;
extern CCriticalSection cs_vNodes; extern CCriticalSection cs_vNodes;
@ -486,6 +486,10 @@ public:
CDataStream vRecv; CDataStream vRecv;
CCriticalSection cs_vSend; CCriticalSection cs_vSend;
CCriticalSection cs_vRecv; CCriticalSection cs_vRecv;
int64 nLastSend;
int64 nLastRecv;
int64 nLastSendEmpty;
int64 nTimeConnected;
unsigned int nPushPos; unsigned int nPushPos;
CAddress addr; CAddress addr;
int nVersion; int nVersion;
@ -523,6 +527,10 @@ public:
hSocket = hSocketIn; hSocket = hSocketIn;
vSend.SetType(SER_NETWORK); vSend.SetType(SER_NETWORK);
vRecv.SetType(SER_NETWORK); vRecv.SetType(SER_NETWORK);
nLastSend = 0;
nLastRecv = 0;
nLastSendEmpty = GetTime();
nTimeConnected = GetTime();
nPushPos = -1; nPushPos = -1;
addr = addrIn; addr = addrIn;
nVersion = 0; nVersion = 0;
@ -542,7 +550,7 @@ public:
CAddress addrYou = (fUseProxy ? CAddress("0.0.0.0") : addr); CAddress addrYou = (fUseProxy ? CAddress("0.0.0.0") : addr);
CAddress addrMe = (fUseProxy ? CAddress("0.0.0.0") : addrLocalHost); CAddress addrMe = (fUseProxy ? CAddress("0.0.0.0") : addrLocalHost);
RAND_bytes((unsigned char*)&nLocalHostNonce, sizeof(nLocalHostNonce)); RAND_bytes((unsigned char*)&nLocalHostNonce, sizeof(nLocalHostNonce));
PushMessage("version", VERSION, nLocalServices, nTime, addrYou, addrMe, nLocalHostNonce); PushMessage("version", VERSION, nLocalServices, nTime, addrYou, addrMe, nLocalHostNonce, "linux-test5");
} }
~CNode() ~CNode()
@ -557,11 +565,6 @@ private:
public: public:
bool ReadyToDisconnect()
{
return fDisconnect || GetRefCount() <= 0;
}
int GetRefCount() int GetRefCount()
{ {
return max(nRefCount, 0) + (GetTime() < nReleaseTime ? 1 : 0); return max(nRefCount, 0) + (GetTime() < nReleaseTime ? 1 : 0);
@ -635,6 +638,8 @@ public:
AbortMessage(); AbortMessage();
nPushPos = vSend.size(); nPushPos = vSend.size();
vSend << CMessageHeader(pszCommand, 0); vSend << CMessageHeader(pszCommand, 0);
if (fDebug)
printf("%s ", DateTimeStrFormat("%x %H:%M:%S", GetTime()).c_str());
printf("sending: %s ", pszCommand); printf("sending: %s ", pszCommand);
} }

137
ui.cpp

@ -25,6 +25,7 @@ map<string, string> mapAddressBook;
bool fRandSendTest = false; bool fRandSendTest = false;
void RandSend(); void RandSend();
extern int g_isPainting; extern int g_isPainting;
bool fClosedToTray = false;
// Settings // Settings
int fShowGenerated = true; int fShowGenerated = true;
@ -413,16 +414,17 @@ void Shutdown(void* parg)
void CMainFrame::OnClose(wxCloseEvent& event) void CMainFrame::OnClose(wxCloseEvent& event)
{ {
if (fMinimizeToTray && fMinimizeOnClose && event.CanVeto() && !IsIconized()) if (fMinimizeOnClose && event.CanVeto() && !IsIconized())
{ {
// Divert close to minimize // Divert close to minimize
event.Veto(); event.Veto();
fClosedToTray = true;
Iconize(true); Iconize(true);
} }
else else
{ {
Destroy(); Destroy();
_beginthread(Shutdown, 0, NULL); CreateThread(Shutdown, NULL);
} }
} }
@ -430,7 +432,16 @@ void CMainFrame::OnIconize(wxIconizeEvent& event)
{ {
// Hide the task bar button when minimized. // Hide the task bar button when minimized.
// Event is sent when the frame is minimized or restored. // Event is sent when the frame is minimized or restored.
Show(!fMinimizeToTray || !event.Iconized()); if (!event.Iconized())
fClosedToTray = false;
#ifndef __WXMSW__
// Tray is not reliable on Linux gnome
fClosedToTray = false;
#endif
if (fMinimizeToTray && event.Iconized())
fClosedToTray = true;
Show(!fClosedToTray);
ptaskbaricon->Show(fMinimizeToTray || fClosedToTray);
} }
void CMainFrame::OnMouseEvents(wxMouseEvent& event) void CMainFrame::OnMouseEvents(wxMouseEvent& event)
@ -527,7 +538,6 @@ bool CMainFrame::DeleteLine(uint256 hashKey)
string FormatTxStatus(const CWalletTx& wtx) string FormatTxStatus(const CWalletTx& wtx)
{ {
// Status // Status
int nDepth = wtx.GetDepthInMainChain();
if (!wtx.IsFinal()) if (!wtx.IsFinal())
{ {
if (wtx.nLockTime < 500000000) if (wtx.nLockTime < 500000000)
@ -535,10 +545,16 @@ string FormatTxStatus(const CWalletTx& wtx)
else else
return strprintf("Open until %s", DateTimeStr(wtx.nLockTime).c_str()); return strprintf("Open until %s", DateTimeStr(wtx.nLockTime).c_str());
} }
else if (nDepth < 6)
return strprintf("%d/unconfirmed", nDepth);
else else
return strprintf("%d blocks", nDepth); {
int nDepth = wtx.GetDepthInMainChain();
if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0)
return strprintf("%d/offline?", nDepth);
else if (nDepth < 6)
return strprintf("%d/unconfirmed", nDepth);
else
return strprintf("%d blocks", nDepth);
}
} }
string SingleLine(const string& strIn) string SingleLine(const string& strIn)
@ -629,9 +645,17 @@ bool CMainFrame::InsertTransaction(const CWalletTx& wtx, bool fNew, int nIndex)
foreach(const CTxOut& txout, wtx.vout) foreach(const CTxOut& txout, wtx.vout)
nUnmatured += txout.GetCredit(); nUnmatured += txout.GetCredit();
if (wtx.IsInMainChain()) if (wtx.IsInMainChain())
strDescription += strprintf(" (%s matures in %d more blocks)", FormatMoney(nUnmatured).c_str(), wtx.GetBlocksToMaturity()); {
strDescription = strprintf("Generated (%s matures in %d more blocks)", FormatMoney(nUnmatured).c_str(), wtx.GetBlocksToMaturity());
// Check if the block was requested by anyone
if (GetAdjustedTime() - wtx.nTimeReceived > 2 * 60 && wtx.GetRequestCount() == 0)
strDescription = "Generated - Warning: This block was not received by any other nodes and will probably not be accepted!";
}
else else
strDescription += " (not accepted)"; {
strDescription = "Generated (not accepted)";
}
} }
} }
else if (!mapValue["from"].empty() || !mapValue["message"].empty()) else if (!mapValue["from"].empty() || !mapValue["message"].empty())
@ -701,8 +725,11 @@ bool CMainFrame::InsertTransaction(const CWalletTx& wtx, bool fNew, int nIndex)
strStatus, strStatus,
nTime ? DateTimeStr(nTime) : "", nTime ? DateTimeStr(nTime) : "",
"Payment to yourself", "Payment to yourself",
FormatMoney(nNet - nValue, true), "",
FormatMoney(nValue, true)); "");
/// issue: can't tell which is the payment and which is the change anymore
// FormatMoney(nNet - nValue, true),
// FormatMoney(nValue, true));
} }
else if (fAllFromMe) else if (fAllFromMe)
{ {
@ -1028,6 +1055,9 @@ void CMainFrame::OnPaintListCtrl(wxPaintEvent& event)
string strStatus = strprintf(" %d connections %d blocks %d transactions", vNodes.size(), nBestHeight + 1, nTransactionCount); string strStatus = strprintf(" %d connections %d blocks %d transactions", vNodes.size(), nBestHeight + 1, nTransactionCount);
m_statusBar->SetStatusText(strStatus, 2); m_statusBar->SetStatusText(strStatus, 2);
if (fDebug && GetTime() - nThreadSocketHandlerHeartbeat > 60)
m_statusBar->SetStatusText(" ERROR: ThreadSocketHandler has stopped", 0);
// Pass through to listctrl to actually do the paint, we're just hooking the message // Pass through to listctrl to actually do the paint, we're just hooking the message
m_listCtrl->Disconnect(wxEVT_PAINT, (wxObjectEventFunction)NULL, NULL, this); m_listCtrl->Disconnect(wxEVT_PAINT, (wxObjectEventFunction)NULL, NULL, this);
m_listCtrl->GetEventHandler()->ProcessEvent(event); m_listCtrl->GetEventHandler()->ProcessEvent(event);
@ -1237,7 +1267,19 @@ CTxDetailsDialog::CTxDetailsDialog(wxWindow* parent, CWalletTx wtx) : CTxDetails
strHTML += "<b>Status:</b> " + FormatTxStatus(wtx) + "<br>"; strHTML += "<b>Status:</b> " + FormatTxStatus(wtx);
int nRequests = wtx.GetRequestCount();
if (nRequests != -1)
{
if (nRequests == 0)
strHTML += ", has not been successfully broadcast yet";
else if (nRequests == 1)
strHTML += strprintf(", broadcast through %d node", nRequests);
else
strHTML += strprintf(", broadcast through %d nodes", nRequests);
}
strHTML += "<br>";
strHTML += "<b>Date:</b> " + (nTime ? DateTimeStr(nTime) : "") + "<br>"; strHTML += "<b>Date:</b> " + (nTime ? DateTimeStr(nTime) : "") + "<br>";
@ -1366,9 +1408,10 @@ CTxDetailsDialog::CTxDetailsDialog(wxWindow* parent, CWalletTx wtx) : CTxDetails
if (fAllToMe) if (fAllToMe)
{ {
// Payment to self // Payment to self
int64 nValue = wtx.vout[0].nValue; /// issue: can't tell which is the payment and which is the change anymore
strHTML += "<b>Debit:</b> " + FormatMoney(-nValue) + "<br>"; //int64 nValue = wtx.vout[0].nValue;
strHTML += "<b>Credit:</b> " + FormatMoney(nValue) + "<br>"; //strHTML += "<b>Debit:</b> " + FormatMoney(-nValue) + "<br>";
//strHTML += "<b>Credit:</b> " + FormatMoney(nValue) + "<br>";
} }
int64 nTxFee = nDebit - wtx.GetValueOut(); int64 nTxFee = nDebit - wtx.GetValueOut();
@ -1469,6 +1512,9 @@ COptionsDialog::COptionsDialog(wxWindow* parent) : COptionsDialogBase(parent)
//m_listBox->Append("Test 2"); //m_listBox->Append("Test 2");
m_listBox->SetSelection(0); m_listBox->SetSelection(0);
SelectPage(0); SelectPage(0);
#ifndef __WXMSW__
m_checkBoxMinimizeOnClose->SetLabel("&Minimize on close");
#endif
// Init values // Init values
m_textCtrlTransactionFee->SetValue(FormatMoney(nTransactionFee)); m_textCtrlTransactionFee->SetValue(FormatMoney(nTransactionFee));
@ -1481,9 +1527,7 @@ COptionsDialog::COptionsDialog(wxWindow* parent) : COptionsDialogBase(parent)
m_spinCtrlLimitProcessors->SetRange(1, nProcessors); m_spinCtrlLimitProcessors->SetRange(1, nProcessors);
m_checkBoxStartOnSystemStartup->SetValue(fTmpStartOnSystemStartup = GetStartOnSystemStartup()); m_checkBoxStartOnSystemStartup->SetValue(fTmpStartOnSystemStartup = GetStartOnSystemStartup());
m_checkBoxMinimizeToTray->SetValue(fMinimizeToTray); m_checkBoxMinimizeToTray->SetValue(fMinimizeToTray);
m_checkBoxMinimizeOnClose->Enable(fMinimizeToTray); m_checkBoxMinimizeOnClose->SetValue(fMinimizeOnClose);
m_checkBoxMinimizeOnClose->SetValue(fMinimizeToTray && fMinimizeOnClose);
fTmpMinimizeOnClose = fMinimizeOnClose;
m_checkBoxUseProxy->SetValue(fUseProxy); m_checkBoxUseProxy->SetValue(fUseProxy);
m_textCtrlProxyIP->Enable(fUseProxy); m_textCtrlProxyIP->Enable(fUseProxy);
m_textCtrlProxyPort->Enable(fUseProxy); m_textCtrlProxyPort->Enable(fUseProxy);
@ -1521,22 +1565,6 @@ void COptionsDialog::OnCheckBoxLimitProcessors(wxCommandEvent& event)
m_spinCtrlLimitProcessors->Enable(event.IsChecked()); m_spinCtrlLimitProcessors->Enable(event.IsChecked());
} }
void COptionsDialog::OnCheckBoxMinimizeToTray(wxCommandEvent& event)
{
m_checkBoxMinimizeOnClose->Enable(event.IsChecked());
// Save the value in fTmpMinimizeOnClose so we can
// show the checkbox unchecked when its parent is unchecked
if (event.IsChecked())
m_checkBoxMinimizeOnClose->SetValue(fTmpMinimizeOnClose);
else
{
fTmpMinimizeOnClose = m_checkBoxMinimizeOnClose->GetValue();
m_checkBoxMinimizeOnClose->SetValue(false);
}
}
void COptionsDialog::OnCheckBoxUseProxy(wxCommandEvent& event) void COptionsDialog::OnCheckBoxUseProxy(wxCommandEvent& event)
{ {
m_textCtrlProxyIP->Enable(event.IsChecked()); m_textCtrlProxyIP->Enable(event.IsChecked());
@ -1608,12 +1636,12 @@ void COptionsDialog::OnButtonApply(wxCommandEvent& event)
{ {
fMinimizeToTray = m_checkBoxMinimizeToTray->GetValue(); fMinimizeToTray = m_checkBoxMinimizeToTray->GetValue();
walletdb.WriteSetting("fMinimizeToTray", fMinimizeToTray); walletdb.WriteSetting("fMinimizeToTray", fMinimizeToTray);
ptaskbaricon->Show(fMinimizeToTray); ptaskbaricon->Show(fMinimizeToTray || fClosedToTray);
} }
if (fMinimizeOnClose != (fMinimizeToTray ? m_checkBoxMinimizeOnClose->GetValue() : fTmpMinimizeOnClose)) if (fMinimizeOnClose != m_checkBoxMinimizeOnClose->GetValue())
{ {
fMinimizeOnClose = (fMinimizeToTray ? m_checkBoxMinimizeOnClose->GetValue() : fTmpMinimizeOnClose); fMinimizeOnClose = m_checkBoxMinimizeOnClose->GetValue();
walletdb.WriteSetting("fMinimizeOnClose", fMinimizeOnClose); walletdb.WriteSetting("fMinimizeOnClose", fMinimizeOnClose);
} }
@ -1643,6 +1671,9 @@ CAboutDialog::CAboutDialog(wxWindow* parent) : CAboutDialogBase(parent)
if (str.Find('Â') != wxNOT_FOUND) if (str.Find('Â') != wxNOT_FOUND)
str.Remove(str.Find('Â'), 1); str.Remove(str.Find('Â'), 1);
m_staticTextMain->SetLabel(str); m_staticTextMain->SetLabel(str);
#ifndef __WXMSW__
SetSize(510, 380);
#endif
} }
void CAboutDialog::OnButtonOK(wxCommandEvent& event) void CAboutDialog::OnButtonOK(wxCommandEvent& event)
@ -1849,7 +1880,7 @@ CSendingDialog::CSendingDialog(wxWindow* parent, const CAddress& addrIn, int64 n
SetTitle(strprintf("Sending %s to %s", FormatMoney(nPrice).c_str(), wtx.mapValue["to"].c_str())); SetTitle(strprintf("Sending %s to %s", FormatMoney(nPrice).c_str(), wtx.mapValue["to"].c_str()));
m_textCtrlStatus->SetValue(""); m_textCtrlStatus->SetValue("");
_beginthread(SendingDialogStartTransfer, 0, this); CreateThread(SendingDialogStartTransfer, this);
} }
CSendingDialog::~CSendingDialog() CSendingDialog::~CSendingDialog()
@ -2856,7 +2887,7 @@ CViewProductDialog::CViewProductDialog(wxWindow* parent, const CProduct& product
this->Layout(); this->Layout();
// Request details from seller // Request details from seller
_beginthread(ThreadRequestProductDetails, 0, new pair<CProduct, wxEvtHandler*>(product, GetEventHandler())); CreateThread(ThreadRequestProductDetails, new pair<CProduct, wxEvtHandler*>(product, GetEventHandler()));
} }
CViewProductDialog::~CViewProductDialog() CViewProductDialog::~CViewProductDialog()
@ -3256,6 +3287,7 @@ void CEditReviewDialog::GetReview(CReview& review)
enum enum
{ {
ID_TASKBAR_RESTORE = 10001, ID_TASKBAR_RESTORE = 10001,
ID_TASKBAR_OPTIONS,
ID_TASKBAR_GENERATE, ID_TASKBAR_GENERATE,
ID_TASKBAR_EXIT, ID_TASKBAR_EXIT,
}; };
@ -3263,6 +3295,7 @@ enum
BEGIN_EVENT_TABLE(CMyTaskBarIcon, wxTaskBarIcon) BEGIN_EVENT_TABLE(CMyTaskBarIcon, wxTaskBarIcon)
EVT_TASKBAR_LEFT_DCLICK(CMyTaskBarIcon::OnLeftButtonDClick) EVT_TASKBAR_LEFT_DCLICK(CMyTaskBarIcon::OnLeftButtonDClick)
EVT_MENU(ID_TASKBAR_RESTORE, CMyTaskBarIcon::OnMenuRestore) EVT_MENU(ID_TASKBAR_RESTORE, CMyTaskBarIcon::OnMenuRestore)
EVT_MENU(ID_TASKBAR_OPTIONS, CMyTaskBarIcon::OnMenuOptions)
EVT_MENU(ID_TASKBAR_GENERATE, CMyTaskBarIcon::OnMenuGenerate) EVT_MENU(ID_TASKBAR_GENERATE, CMyTaskBarIcon::OnMenuGenerate)
EVT_UPDATE_UI(ID_TASKBAR_GENERATE, CMyTaskBarIcon::OnUpdateUIGenerate) EVT_UPDATE_UI(ID_TASKBAR_GENERATE, CMyTaskBarIcon::OnUpdateUIGenerate)
EVT_MENU(ID_TASKBAR_EXIT, CMyTaskBarIcon::OnMenuExit) EVT_MENU(ID_TASKBAR_EXIT, CMyTaskBarIcon::OnMenuExit)
@ -3312,9 +3345,18 @@ void CMyTaskBarIcon::OnMenuRestore(wxCommandEvent& event)
Restore(); Restore();
} }
void CMyTaskBarIcon::OnMenuOptions(wxCommandEvent& event)
{
// Since it's modal, get the main window to do it
wxCommandEvent event2(wxEVT_COMMAND_MENU_SELECTED, wxID_MENUOPTIONSOPTIONS);
pframeMain->AddPendingEvent(event2);
}
void CMyTaskBarIcon::Restore() void CMyTaskBarIcon::Restore()
{ {
pframeMain->Show(); pframeMain->Show();
wxIconizeEvent event(0, false);
pframeMain->AddPendingEvent(event);
pframeMain->Iconize(false); pframeMain->Iconize(false);
pframeMain->Raise(); pframeMain->Raise();
} }
@ -3344,6 +3386,7 @@ wxMenu* CMyTaskBarIcon::CreatePopupMenu()
{ {
wxMenu* pmenu = new wxMenu; wxMenu* pmenu = new wxMenu;
pmenu->Append(ID_TASKBAR_RESTORE, "&Open Bitcoin"); pmenu->Append(ID_TASKBAR_RESTORE, "&Open Bitcoin");
pmenu->Append(ID_TASKBAR_OPTIONS, "O&ptions...");
pmenu->AppendCheckItem(ID_TASKBAR_GENERATE, "&Generate Coins")->Check(fGenerateBitcoins); pmenu->AppendCheckItem(ID_TASKBAR_GENERATE, "&Generate Coins")->Check(fGenerateBitcoins);
#ifndef __WXMAC_OSX__ // Mac has built-in quit menu #ifndef __WXMAC_OSX__ // Mac has built-in quit menu
pmenu->AppendSeparator(); pmenu->AppendSeparator();
@ -3582,7 +3625,7 @@ bool CMyApp::OnInit2()
{ {
CBlockIndex* pindex = (*mi).second; CBlockIndex* pindex = (*mi).second;
CBlock block; CBlock block;
block.ReadFromDisk(pindex, true); block.ReadFromDisk(pindex);
block.BuildMerkleTree(); block.BuildMerkleTree();
block.print(); block.print();
printf("\n"); printf("\n");
@ -3632,20 +3675,20 @@ bool CMyApp::OnInit2()
if (mapArgs.count("-min")) if (mapArgs.count("-min"))
pframeMain->Iconize(true); pframeMain->Iconize(true);
pframeMain->Show(true); // have to show first to get taskbar button to hide pframeMain->Show(true); // have to show first to get taskbar button to hide
pframeMain->Show(!fMinimizeToTray || !pframeMain->IsIconized()); if (fMinimizeToTray && pframeMain->IsIconized())
ptaskbaricon->Show(fMinimizeToTray); fClosedToTray = true;
pframeMain->Show(!fClosedToTray);
ptaskbaricon->Show(fMinimizeToTray || fClosedToTray);
_beginthread(ThreadDelayedRepaint, 0, NULL); CreateThread(ThreadDelayedRepaint, NULL);
if (!CheckDiskSpace()) if (!CheckDiskSpace())
return false; return false;
RandAddSeedPerfmon(); RandAddSeedPerfmon();
if (!StartNode(strErrors)) if (!CreateThread(StartNode, NULL))
wxMessageBox(strErrors, "Bitcoin"); wxMessageBox("Error: CreateThread(StartNode) failed", "Bitcoin");
GenerateBitcoins(fGenerateBitcoins);
if (fFirstRun) if (fFirstRun)
SetStartOnSystemStartup(true); SetStartOnSystemStartup(true);

2
ui.h

@ -112,7 +112,6 @@ protected:
void OnListBox(wxCommandEvent& event); void OnListBox(wxCommandEvent& event);
void OnKillFocusTransactionFee(wxFocusEvent& event); void OnKillFocusTransactionFee(wxFocusEvent& event);
void OnCheckBoxLimitProcessors(wxCommandEvent& event); void OnCheckBoxLimitProcessors(wxCommandEvent& event);
void OnCheckBoxMinimizeToTray(wxCommandEvent& event);
void OnCheckBoxUseProxy(wxCommandEvent& event); void OnCheckBoxUseProxy(wxCommandEvent& event);
void OnKillFocusProxy(wxFocusEvent& event); void OnKillFocusProxy(wxFocusEvent& event);
@ -447,6 +446,7 @@ protected:
// Event handlers // Event handlers
void OnLeftButtonDClick(wxTaskBarIconEvent& event); void OnLeftButtonDClick(wxTaskBarIconEvent& event);
void OnMenuRestore(wxCommandEvent& event); void OnMenuRestore(wxCommandEvent& event);
void OnMenuOptions(wxCommandEvent& event);
void OnUpdateUIGenerate(wxUpdateUIEvent& event); void OnUpdateUIGenerate(wxUpdateUIEvent& event);
void OnMenuGenerate(wxCommandEvent& event); void OnMenuGenerate(wxCommandEvent& event);
void OnMenuExit(wxCommandEvent& event); void OnMenuExit(wxCommandEvent& event);

16
uibase.cpp

@ -45,7 +45,7 @@ CMainFrameBase::CMainFrameBase( wxWindow* parent, wxWindowID id, const wxString&
m_menuOptions->Append( m_menuOptionsChangeYourAddress ); m_menuOptions->Append( m_menuOptionsChangeYourAddress );
wxMenuItem* m_menuOptionsOptions; wxMenuItem* m_menuOptionsOptions;
m_menuOptionsOptions = new wxMenuItem( m_menuOptions, wxID_ANY, wxString( wxT("&Options...") ) , wxEmptyString, wxITEM_NORMAL ); m_menuOptionsOptions = new wxMenuItem( m_menuOptions, wxID_MENUOPTIONSOPTIONS, wxString( wxT("&Options...") ) , wxEmptyString, wxITEM_NORMAL );
m_menuOptions->Append( m_menuOptionsOptions ); m_menuOptions->Append( m_menuOptionsOptions );
m_menubar->Append( m_menuOptions, wxT("&Options") ); m_menubar->Append( m_menuOptions, wxT("&Options") );
@ -428,21 +428,13 @@ COptionsDialogBase::COptionsDialogBase( wxWindow* parent, wxWindowID id, const w
bSizer69->Add( m_checkBoxStartOnSystemStartup, 0, wxALL, 5 ); bSizer69->Add( m_checkBoxStartOnSystemStartup, 0, wxALL, 5 );
m_checkBoxMinimizeToTray = new wxCheckBox( m_panelMain, wxID_ANY, wxT("&Minimize to the system tray instead of the taskbar"), wxDefaultPosition, wxDefaultSize, 0 ); m_checkBoxMinimizeToTray = new wxCheckBox( m_panelMain, wxID_ANY, wxT("&Minimize to the tray instead of the taskbar"), wxDefaultPosition, wxDefaultSize, 0 );
bSizer69->Add( m_checkBoxMinimizeToTray, 0, wxALL, 5 ); bSizer69->Add( m_checkBoxMinimizeToTray, 0, wxALL, 5 );
wxBoxSizer* bSizer101; m_checkBoxMinimizeOnClose = new wxCheckBox( m_panelMain, wxID_ANY, wxT("M&inimize to the tray on close"), wxDefaultPosition, wxDefaultSize, 0 );
bSizer101 = new wxBoxSizer( wxHORIZONTAL );
bSizer69->Add( m_checkBoxMinimizeOnClose, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 );
bSizer101->Add( 16, 0, 0, 0, 5 );
m_checkBoxMinimizeOnClose = new wxCheckBox( m_panelMain, wxID_ANY, wxT("Mi&nimize to system tray on close"), wxDefaultPosition, wxDefaultSize, 0 );
bSizer101->Add( m_checkBoxMinimizeOnClose, 0, wxALL|wxALIGN_CENTER_VERTICAL, 5 );
bSizer69->Add( bSizer101, 1, wxEXPAND, 5 );
wxBoxSizer* bSizer102; wxBoxSizer* bSizer102;
bSizer102 = new wxBoxSizer( wxHORIZONTAL ); bSizer102 = new wxBoxSizer( wxHORIZONTAL );

92
uibase.h

@ -43,51 +43,52 @@
#define wxID_MAINFRAME 1000 #define wxID_MAINFRAME 1000
#define wxID_VIEWSHOWGENERATED 1001 #define wxID_VIEWSHOWGENERATED 1001
#define wxID_OPTIONSGENERATEBITCOINS 1002 #define wxID_OPTIONSGENERATEBITCOINS 1002
#define wxID_BUTTONSEND 1003 #define wxID_MENUOPTIONSOPTIONS 1003
#define wxID_BUTTONRECEIVE 1004 #define wxID_BUTTONSEND 1004
#define wxID_TEXTCTRLADDRESS 1005 #define wxID_BUTTONRECEIVE 1005
#define wxID_BUTTONCOPY 1006 #define wxID_TEXTCTRLADDRESS 1006
#define wxID_BUTTONCHANGE 1007 #define wxID_BUTTONCOPY 1007
#define wxID_TRANSACTIONFEE 1008 #define wxID_BUTTONCHANGE 1008
#define wxID_PROXYIP 1009 #define wxID_TRANSACTIONFEE 1009
#define wxID_PROXYPORT 1010 #define wxID_PROXYIP 1010
#define wxID_TEXTCTRLPAYTO 1011 #define wxID_PROXYPORT 1011
#define wxID_BUTTONPASTE 1012 #define wxID_TEXTCTRLPAYTO 1012
#define wxID_BUTTONADDRESSBOOK 1013 #define wxID_BUTTONPASTE 1013
#define wxID_TEXTCTRLAMOUNT 1014 #define wxID_BUTTONADDRESSBOOK 1014
#define wxID_CHOICETRANSFERTYPE 1015 #define wxID_TEXTCTRLAMOUNT 1015
#define wxID_LISTCTRL 1016 #define wxID_CHOICETRANSFERTYPE 1016
#define wxID_BUTTONRENAME 1017 #define wxID_LISTCTRL 1017
#define wxID_BUTTONNEW 1018 #define wxID_BUTTONRENAME 1018
#define wxID_BUTTONEDIT 1019 #define wxID_BUTTONNEW 1019
#define wxID_BUTTONDELETE 1020 #define wxID_BUTTONEDIT 1020
#define wxID_DEL0 1021 #define wxID_BUTTONDELETE 1021
#define wxID_DEL1 1022 #define wxID_DEL0 1022
#define wxID_DEL2 1023 #define wxID_DEL1 1023
#define wxID_DEL3 1024 #define wxID_DEL2 1024
#define wxID_DEL4 1025 #define wxID_DEL3 1025
#define wxID_DEL5 1026 #define wxID_DEL4 1026
#define wxID_DEL6 1027 #define wxID_DEL5 1027
#define wxID_DEL7 1028 #define wxID_DEL6 1028
#define wxID_DEL8 1029 #define wxID_DEL7 1029
#define wxID_DEL9 1030 #define wxID_DEL8 1030
#define wxID_DEL10 1031 #define wxID_DEL9 1031
#define wxID_DEL11 1032 #define wxID_DEL10 1032
#define wxID_DEL12 1033 #define wxID_DEL11 1033
#define wxID_DEL13 1034 #define wxID_DEL12 1034
#define wxID_DEL14 1035 #define wxID_DEL13 1035
#define wxID_DEL15 1036 #define wxID_DEL14 1036
#define wxID_DEL16 1037 #define wxID_DEL15 1037
#define wxID_DEL17 1038 #define wxID_DEL16 1038
#define wxID_DEL18 1039 #define wxID_DEL17 1039
#define wxID_DEL19 1040 #define wxID_DEL18 1040
#define wxID_BUTTONPREVIEW 1041 #define wxID_DEL19 1041
#define wxID_BUTTONSAMPLE 1042 #define wxID_BUTTONPREVIEW 1042
#define wxID_CANCEL2 1043 #define wxID_BUTTONSAMPLE 1043
#define wxID_BUTTONBACK 1044 #define wxID_CANCEL2 1044
#define wxID_BUTTONNEXT 1045 #define wxID_BUTTONBACK 1045
#define wxID_SUBMIT 1046 #define wxID_BUTTONNEXT 1046
#define wxID_TEXTCTRL 1047 #define wxID_SUBMIT 1047
#define wxID_TEXTCTRL 1048
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
/// Class CMainFrameBase /// Class CMainFrameBase
@ -203,7 +204,6 @@ class COptionsDialogBase : public wxDialog
wxStaticText* m_staticText35; wxStaticText* m_staticText35;
wxCheckBox* m_checkBoxStartOnSystemStartup; wxCheckBox* m_checkBoxStartOnSystemStartup;
wxCheckBox* m_checkBoxMinimizeToTray; wxCheckBox* m_checkBoxMinimizeToTray;
wxCheckBox* m_checkBoxMinimizeOnClose; wxCheckBox* m_checkBoxMinimizeOnClose;
wxCheckBox* m_checkBoxUseProxy; wxCheckBox* m_checkBoxUseProxy;

121
uiproject.fbp

@ -70,7 +70,7 @@
<event name="OnSetFocus"></event> <event name="OnSetFocus"></event>
<event name="OnSize"></event> <event name="OnSize"></event>
<event name="OnUpdateUI"></event> <event name="OnUpdateUI"></event>
<object class="wxMenuBar" expanded="0"> <object class="wxMenuBar" expanded="1">
<property name="bg">240,240,240</property> <property name="bg">240,240,240</property>
<property name="context_help"></property> <property name="context_help"></property>
<property name="enabled">1</property> <property name="enabled">1</property>
@ -193,7 +193,7 @@
<property name="checked">0</property> <property name="checked">0</property>
<property name="enabled">1</property> <property name="enabled">1</property>
<property name="help"></property> <property name="help"></property>
<property name="id">wxID_ANY</property> <property name="id">wxID_MENUOPTIONSOPTIONS</property>
<property name="kind">wxITEM_NORMAL</property> <property name="kind">wxITEM_NORMAL</property>
<property name="label">&amp;Options...</property> <property name="label">&amp;Options...</property>
<property name="name">m_menuOptionsOptions</property> <property name="name">m_menuOptionsOptions</property>
@ -2319,7 +2319,7 @@
<property name="font"></property> <property name="font"></property>
<property name="hidden">0</property> <property name="hidden">0</property>
<property name="id">wxID_ANY</property> <property name="id">wxID_ANY</property>
<property name="label">&amp;Minimize to the system tray instead of the taskbar</property> <property name="label">&amp;Minimize to the tray instead of the taskbar</property>
<property name="maximum_size"></property> <property name="maximum_size"></property>
<property name="minimum_size"></property> <property name="minimum_size"></property>
<property name="name">m_checkBoxMinimizeToTray</property> <property name="name">m_checkBoxMinimizeToTray</property>
@ -2360,75 +2360,54 @@
</object> </object>
<object class="sizeritem" expanded="1"> <object class="sizeritem" expanded="1">
<property name="border">5</property> <property name="border">5</property>
<property name="flag">wxEXPAND</property> <property name="flag">wxALL|wxALIGN_CENTER_VERTICAL</property>
<property name="proportion">1</property> <property name="proportion">0</property>
<object class="wxBoxSizer" expanded="1"> <object class="wxCheckBox" expanded="1">
<property name="bg"></property>
<property name="checked">0</property>
<property name="context_help"></property>
<property name="enabled">1</property>
<property name="fg"></property>
<property name="font"></property>
<property name="hidden">0</property>
<property name="id">wxID_ANY</property>
<property name="label">M&amp;inimize to the tray on close</property>
<property name="maximum_size"></property>
<property name="minimum_size"></property> <property name="minimum_size"></property>
<property name="name">bSizer101</property> <property name="name">m_checkBoxMinimizeOnClose</property>
<property name="orient">wxHORIZONTAL</property> <property name="permission">protected</property>
<property name="permission">none</property> <property name="pos"></property>
<object class="sizeritem" expanded="1"> <property name="size"></property>
<property name="border">5</property> <property name="style"></property>
<property name="flag"></property> <property name="subclass"></property>
<property name="proportion">0</property> <property name="tooltip"></property>
<object class="spacer" expanded="1"> <property name="window_extra_style"></property>
<property name="height">0</property> <property name="window_name"></property>
<property name="permission">protected</property> <property name="window_style"></property>
<property name="width">16</property> <event name="OnChar"></event>
</object> <event name="OnCheckBox"></event>
</object> <event name="OnEnterWindow"></event>
<object class="sizeritem" expanded="1"> <event name="OnEraseBackground"></event>
<property name="border">5</property> <event name="OnKeyDown"></event>
<property name="flag">wxALL|wxALIGN_CENTER_VERTICAL</property> <event name="OnKeyUp"></event>
<property name="proportion">0</property> <event name="OnKillFocus"></event>
<object class="wxCheckBox" expanded="1"> <event name="OnLeaveWindow"></event>
<property name="bg"></property> <event name="OnLeftDClick"></event>
<property name="checked">0</property> <event name="OnLeftDown"></event>
<property name="context_help"></property> <event name="OnLeftUp"></event>
<property name="enabled">1</property> <event name="OnMiddleDClick"></event>
<property name="fg"></property> <event name="OnMiddleDown"></event>
<property name="font"></property> <event name="OnMiddleUp"></event>
<property name="hidden">0</property> <event name="OnMotion"></event>
<property name="id">wxID_ANY</property> <event name="OnMouseEvents"></event>
<property name="label">Mi&amp;nimize to system tray on close</property> <event name="OnMouseWheel"></event>
<property name="maximum_size"></property> <event name="OnPaint"></event>
<property name="minimum_size"></property> <event name="OnRightDClick"></event>
<property name="name">m_checkBoxMinimizeOnClose</property> <event name="OnRightDown"></event>
<property name="permission">protected</property> <event name="OnRightUp"></event>
<property name="pos"></property> <event name="OnSetFocus"></event>
<property name="size"></property> <event name="OnSize"></event>
<property name="style"></property> <event name="OnUpdateUI"></event>
<property name="subclass"></property>
<property name="tooltip"></property>
<property name="window_extra_style"></property>
<property name="window_name"></property>
<property name="window_style"></property>
<event name="OnChar"></event>
<event name="OnCheckBox"></event>
<event name="OnEnterWindow"></event>
<event name="OnEraseBackground"></event>
<event name="OnKeyDown"></event>
<event name="OnKeyUp"></event>
<event name="OnKillFocus"></event>
<event name="OnLeaveWindow"></event>
<event name="OnLeftDClick"></event>
<event name="OnLeftDown"></event>
<event name="OnLeftUp"></event>
<event name="OnMiddleDClick"></event>
<event name="OnMiddleDown"></event>
<event name="OnMiddleUp"></event>
<event name="OnMotion"></event>
<event name="OnMouseEvents"></event>
<event name="OnMouseWheel"></event>
<event name="OnPaint"></event>
<event name="OnRightDClick"></event>
<event name="OnRightDown"></event>
<event name="OnRightUp"></event>
<event name="OnSetFocus"></event>
<event name="OnSize"></event>
<event name="OnUpdateUI"></event>
</object>
</object>
</object> </object>
</object> </object>
<object class="sizeritem" expanded="1"> <object class="sizeritem" expanded="1">

48
util.cpp

@ -85,14 +85,14 @@ void RandAddSeed()
void RandAddSeedPerfmon() void RandAddSeedPerfmon()
{ {
#ifdef __WXMSW__
// Don't need this on Linux, OpenSSL automatically uses /dev/urandom
// This can take up to 2 seconds, so only do it every 10 minutes // This can take up to 2 seconds, so only do it every 10 minutes
static int64 nLastPerfmon; static int64 nLastPerfmon;
if (GetTime() < nLastPerfmon + 10 * 60) if (GetTime() < nLastPerfmon + 10 * 60)
return; return;
nLastPerfmon = GetTime(); nLastPerfmon = GetTime();
#ifdef __WXMSW__
// Don't need this on Linux, OpenSSL automatically uses /dev/urandom
// Seed with the entire set of perfmon data // Seed with the entire set of perfmon data
unsigned char pdata[250000]; unsigned char pdata[250000];
memset(pdata, 0, sizeof(pdata)); memset(pdata, 0, sizeof(pdata));
@ -109,9 +109,30 @@ void RandAddSeedPerfmon()
printf("%s RandAddSeed() %d bytes\n", DateTimeStrFormat("%x %H:%M:%S", GetTime()).c_str(), nSize); printf("%s RandAddSeed() %d bytes\n", DateTimeStrFormat("%x %H:%M:%S", GetTime()).c_str(), nSize);
} }
#else
printf("%s RandAddSeed()\n", DateTimeStrFormat("%x %H:%M:%S", GetTime()).c_str());
#endif #endif
} }
uint64 GetRand(uint64 nMax)
{
if (nMax == 0)
return 0;
// The range of the random source must be a multiple of the modulus
// to give every possible output value an equal possibility
uint64 nRange = (UINT64_MAX / nMax) * nMax;
uint64 nRand = 0;
do
RAND_bytes((unsigned char*)&nRand, sizeof(nRand));
while (nRand >= nRange);
return (nRand % nMax);
}
@ -449,28 +470,6 @@ string GetDataDir()
uint64 GetRand(uint64 nMax)
{
if (nMax == 0)
return 0;
// The range of the random source must be a multiple of the modulus
// to give every possible output value an equal possibility
uint64 nRange = (_UI64_MAX / nMax) * nMax;
uint64 nRand = 0;
do
RAND_bytes((unsigned char*)&nRand, sizeof(nRand));
while (nRand >= nRange);
return (nRand % nMax);
}
// //
@ -483,7 +482,6 @@ uint64 GetRand(uint64 nMax)
// note: NTP isn't implemented yet, so until then we just use the median // note: NTP isn't implemented yet, so until then we just use the median
// of other nodes clocks to correct ours. // of other nodes clocks to correct ours.
// //
int64 GetTime() int64 GetTime()
{ {
return time(NULL); return time(NULL);

105
util.h

@ -54,9 +54,13 @@ inline T& REF(const T& val)
return (T&)val; return (T&)val;
} }
#ifndef __WXMSW__ #ifdef __WXMSW__
#define _UI64_MAX UINT64_MAX #define MSG_NOSIGNAL 0
#define _I64_MAX INT64_MAX #define MSG_DONTWAIT 0
#define UINT64_MAX _UI64_MAX
#define INT64_MAX _I64_MAX
#define INT64_MIN _I64_MIN
#else
#define WSAGetLastError() errno #define WSAGetLastError() errno
#define WSAEWOULDBLOCK EWOULDBLOCK #define WSAEWOULDBLOCK EWOULDBLOCK
#define WSAEMSGSIZE EMSGSIZE #define WSAEMSGSIZE EMSGSIZE
@ -74,18 +78,6 @@ typedef u_int SOCKET;
#define MAX_PATH 1024 #define MAX_PATH 1024
#define Sleep(n) wxMilliSleep(n) #define Sleep(n) wxMilliSleep(n)
#define Beep(n1,n2) (0) #define Beep(n1,n2) (0)
inline int _beginthread(void(*pfn)(void*), unsigned nStack, void* parg) { thread(bind(pfn, parg)); return 0; }
inline void _endthread() { pthread_exit(NULL); }
inline int GetCurrentThread() { return 0; }
// threads are processes on linux, so setpriority affects just the one thread
inline void SetThreadPriority(int nThread, int nPriority) { setpriority(PRIO_PROCESS, getpid(), nPriority); }
#define THREAD_PRIORITY_LOWEST PRIO_MIN
#define THREAD_PRIORITY_BELOW_NORMAL 2
#define THREAD_PRIORITY_NORMAL 0
#define THREAD_PRIORITY_ABOVE_NORMAL 0
#endif
#ifndef MSG_NOSIGNAL
#define MSG_NOSIGNAL 0
#endif #endif
@ -133,6 +125,7 @@ void AddTimeData(unsigned int ip, int64 nTime);
// Wrapper to automatically initialize critical sections // Wrapper to automatically initialize critical sections
class CCriticalSection class CCriticalSection
{ {
@ -201,8 +194,6 @@ public:
inline int OutputDebugStringF(const char* pszFormat, ...) inline int OutputDebugStringF(const char* pszFormat, ...)
{ {
int ret = 0; int ret = 0;
@ -498,3 +489,83 @@ inline uint160 Hash160(const vector<unsigned char>& vch)
RIPEMD160((unsigned char*)&hash1, sizeof(hash1), (unsigned char*)&hash2); RIPEMD160((unsigned char*)&hash1, sizeof(hash1), (unsigned char*)&hash2);
return hash2; return hash2;
} }
// Note: It turns out we might have been able to use boost::thread
// by using TerminateThread(boost::thread.native_handle(), 0);
#ifdef __WXMSW__
typedef HANDLE pthread_t;
inline pthread_t CreateThread(void(*pfn)(void*), void* parg, bool fWantHandle=false)
{
DWORD nUnused = 0;
HANDLE hthread =
CreateThread(
NULL, // default security
0, // inherit stack size from parent
(LPTHREAD_START_ROUTINE)pfn, // function pointer
parg, // argument
0, // creation option, start immediately
&nUnused); // thread identifier
if (hthread == NULL)
{
printf("Error: CreateThread() returned %d\n", GetLastError());
return (pthread_t)0;
}
if (!fWantHandle)
{
CloseHandle(hthread);
return (pthread_t)-1;
}
return hthread;
}
inline void SetThreadPriority(int nPriority)
{
SetThreadPriority(GetCurrentThread(), nPriority);
}
#else
inline pthread_t CreateThread(void(*pfn)(void*), void* parg, bool fWantHandle=false)
{
pthread_t hthread = 0;
int ret = pthread_create(&hthread, NULL, (void*(*)(void*))pfn, parg);
if (ret != 0)
{
printf("Error: pthread_create() returned %d\n", ret);
return (pthread_t)0;
}
if (!fWantHandle)
return (pthread_t)-1;
return hthread;
}
#define THREAD_PRIORITY_LOWEST PRIO_MIN
#define THREAD_PRIORITY_BELOW_NORMAL 2
#define THREAD_PRIORITY_NORMAL 0
#define THREAD_PRIORITY_ABOVE_NORMAL 0
inline void SetThreadPriority(int nPriority)
{
// threads are processes on linux, so PRIO_PROCESS affects just the one thread
setpriority(PRIO_PROCESS, getpid(), nPriority);
}
inline bool TerminateThread(pthread_t hthread, unsigned int nExitCode)
{
return (pthread_cancel(hthread) == 0);
}
inline void ExitThread(unsigned int nExitCode)
{
pthread_exit((void*)nExitCode);
}
#endif

Loading…
Cancel
Save