diff --git a/build-unix.txt b/build-unix.txt index ceb61ad5..f863b6a9 100644 --- a/build-unix.txt +++ b/build-unix.txt @@ -13,18 +13,18 @@ UNIX BUILD NOTES Dependencies ------------ -Install the dev files for the shared libraries: apt-get install build-essential apt-get install libgtk2.0-dev apt-get install libssl-dev +apt-get install libdb4.7-dev +apt-get install libdb4.7++-dev +apt-get install libboost-dev Libraries you need to obtain separately and build: default path download wxWidgets \wxwidgets http://www.wxwidgets.org/downloads/ -Berkeley DB \db http://www.oracle.com/technology/software/products/berkeley-db/index.html -Boost \boost http://www.boost.org/users/download/ -Their licenses: +Licenses: wxWidgets LGPL 2.1 with very liberal exceptions Berkeley DB New BSD license with additional requirement that linked software must be free open source Boost MIT-like license @@ -59,15 +59,9 @@ make install ldconfig -Berkeley DB ------------ -cd /usr/local/db-4.7.25.NC/build_unix -../dist/configure --enable-cxx -make - - Boost ----- +If you download and build Boost yourself cd /usr/local/boost_1_40_0 su ./bootstrap.sh diff --git a/db.cpp b/db.cpp index a9c42880..947aed29 100644 --- a/db.cpp +++ b/db.cpp @@ -20,6 +20,7 @@ static CCriticalSection cs_db; static bool fDbEnvInit = false; DbEnv dbenv(0); static map mapFileUseCount; +static map mapDb; class CDBInit { @@ -39,21 +40,17 @@ public: instance_of_cdbinit; -CDB::CDB(const char* pszFile, const char* pszMode, bool fTxn) : pdb(NULL) +CDB::CDB(const char* pszFile, const char* pszMode) : pdb(NULL) { int ret; if (pszFile == NULL) return; + fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w')); bool fCreate = strchr(pszMode, 'c'); - bool fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w')); unsigned int nFlags = DB_THREAD; if (fCreate) nFlags |= DB_CREATE; - else if (fReadOnly) - nFlags |= DB_RDONLY; - if (!fReadOnly || fTxn) - nFlags |= DB_AUTO_COMMIT; CRITICAL_BLOCK(cs_db) { @@ -72,7 +69,7 @@ CDB::CDB(const char* pszFile, const char* pszMode, bool fTxn) : pdb(NULL) dbenv.set_lk_max_locks(10000); dbenv.set_lk_max_objects(10000); dbenv.set_errfile(fopen(strErrorFile.c_str(), "a")); /// debug - ///dbenv.log_set_config(DB_LOG_AUTO_REMOVE, 1); /// causes corruption + dbenv.set_flags(DB_AUTO_COMMIT, 1); ret = dbenv.open(strDataDir.c_str(), DB_CREATE | DB_INIT_LOCK | @@ -90,31 +87,39 @@ CDB::CDB(const char* pszFile, const char* pszMode, bool fTxn) : pdb(NULL) strFile = pszFile; ++mapFileUseCount[strFile]; - } - - pdb = new Db(&dbenv, 0); + pdb = mapDb[strFile]; + if (pdb == NULL) + { + pdb = new Db(&dbenv, 0); - ret = pdb->open(NULL, // Txn pointer - pszFile, // Filename - "main", // Logical db name - DB_BTREE, // Database type - nFlags, // Flags - 0); + ret = pdb->open(NULL, // Txn pointer + pszFile, // Filename + "main", // Logical db name + DB_BTREE, // Database type + nFlags, // Flags + 0); - if (ret > 0) - { - delete pdb; - pdb = NULL; - CRITICAL_BLOCK(cs_db) - --mapFileUseCount[strFile]; - strFile = ""; - throw runtime_error(strprintf("CDB() : can't open database file %s, error %d\n", pszFile, ret)); - } + if (ret > 0) + { + delete pdb; + pdb = NULL; + CRITICAL_BLOCK(cs_db) + --mapFileUseCount[strFile]; + strFile = ""; + throw runtime_error(strprintf("CDB() : can't open database file %s, error %d\n", pszFile, ret)); + } - if (fCreate && !Exists(string("version"))) - WriteVersion(VERSION); + if (fCreate && !Exists(string("version"))) + { + bool fTmp = fReadOnly; + fReadOnly = false; + WriteVersion(VERSION); + fReadOnly = fTmp; + } - RandAddSeed(); + mapDb[strFile] = pdb; + } + } } void CDB::Close() @@ -124,8 +129,6 @@ void CDB::Close() if (!vTxn.empty()) vTxn.front()->abort(); vTxn.clear(); - pdb->close(0); - delete pdb; pdb = NULL; dbenv.txn_checkpoint(0, 0, 0); @@ -135,6 +138,21 @@ void CDB::Close() RandAddSeed(); } +void CloseDb(const string& strFile) +{ + CRITICAL_BLOCK(cs_db) + { + if (mapDb[strFile] != NULL) + { + // Close the database handle + Db* pdb = mapDb[strFile]; + pdb->close(0); + delete pdb; + mapDb[strFile] = NULL; + } + } +} + void DBFlush(bool fShutdown) { // Flush log data to the actual data file @@ -144,14 +162,18 @@ void DBFlush(bool fShutdown) return; CRITICAL_BLOCK(cs_db) { - dbenv.txn_checkpoint(0, 0, 0); map::iterator mi = mapFileUseCount.begin(); while (mi != mapFileUseCount.end()) { string strFile = (*mi).first; int nRefCount = (*mi).second; + printf("%s refcount=%d\n", strFile.c_str(), nRefCount); if (nRefCount == 0) { + // Move log data to the dat file + CloseDb(strFile); + dbenv.txn_checkpoint(0, 0, 0); + printf("%s flush\n", strFile.c_str()); dbenv.lsn_reset(strFile.c_str(), 0); mapFileUseCount.erase(mi++); } @@ -238,7 +260,10 @@ bool CTxDB::ReadOwnerTxes(uint160 hash160, int nMinHeight, vector& if (ret == DB_NOTFOUND) break; else if (ret != 0) + { + pcursor->close(); return false; + } // Unserialize string strType; @@ -255,9 +280,14 @@ bool CTxDB::ReadOwnerTxes(uint160 hash160, int nMinHeight, vector& { vtx.resize(vtx.size()+1); if (!vtx.back().ReadFromDisk(pos)) + { + pcursor->close(); return false; + } } } + + pcursor->close(); return true; } @@ -379,6 +409,7 @@ bool CTxDB::LoadBlockIndex() break; } } + pcursor->close(); if (!ReadHashBestChain(hashBestChain)) { @@ -391,7 +422,7 @@ bool CTxDB::LoadBlockIndex() return error("CTxDB::LoadBlockIndex() : blockindex for hashBestChain not found"); pindexBest = mapBlockIndex[hashBestChain]; nBestHeight = pindexBest->nHeight; - printf("LoadBlockIndex(): hashBestChain=%s height=%d\n", hashBestChain.ToString().substr(0,14).c_str(), nBestHeight); + printf("LoadBlockIndex(): hashBestChain=%s height=%d\n", hashBestChain.ToString().substr(0,16).c_str(), nBestHeight); return true; } @@ -456,6 +487,7 @@ bool CAddrDB::LoadAddresses() mapAddresses.insert(make_pair(addr.GetKey(), addr)); } } + pcursor->close(); printf("Loaded %d addresses\n", mapAddresses.size()); @@ -558,7 +590,7 @@ bool CWalletDB::LoadWallet(vector& vchDefaultKeyRet) //printf(" %12I64d %s %s %s\n", // wtx.vout[0].nValue, // DateTimeStrFormat("%x %H:%M:%S", wtx.nTime).c_str(), - // wtx.hashBlock.ToString().substr(0,14).c_str(), + // wtx.hashBlock.ToString().substr(0,16).c_str(), // wtx.mapValue["message"].c_str()); } else if (strType == "key") @@ -596,6 +628,7 @@ bool CWalletDB::LoadWallet(vector& vchDefaultKeyRet) } } + pcursor->close(); } printf("fShowGenerated = %d\n", fShowGenerated); @@ -655,6 +688,8 @@ void ThreadFlushWalletDB(void* parg) if (fOneThread) return; fOneThread = true; + if (mapArgs.count("-noflushwallet")) + return; unsigned int nLastSeen = nWalletDBUpdated; unsigned int nLastFlushed = nWalletDBUpdated; @@ -669,24 +704,37 @@ void ThreadFlushWalletDB(void* parg) nLastWalletUpdate = GetTime(); } - if (nLastFlushed != nWalletDBUpdated && nLastWalletUpdate < GetTime() - 1) + if (nLastFlushed != nWalletDBUpdated && GetTime() - nLastWalletUpdate >= 2) { TRY_CRITICAL_BLOCK(cs_db) { - string strFile = "wallet.dat"; - map::iterator mi = mapFileUseCount.find(strFile); - if (mi != mapFileUseCount.end()) + // Don't do this if any databases are in use + int nRefCount = 0; + map::iterator mi = mapFileUseCount.begin(); + while (mi != mapFileUseCount.end()) { - int nRefCount = (*mi).second; - if (nRefCount == 0 && !fShutdown) + nRefCount += (*mi).second; + mi++; + } + + if (nRefCount == 0 && !fShutdown) + { + string strFile = "wallet.dat"; + map::iterator mi = mapFileUseCount.find(strFile); + if (mi != mapFileUseCount.end()) { - // Flush wallet.dat so it's self contained + printf("%s ", DateTimeStrFormat("%x %H:%M:%S", GetTime()).c_str()); + printf("Flushing wallet.dat\n"); nLastFlushed = nWalletDBUpdated; int64 nStart = GetTimeMillis(); + + // Flush wallet.dat so it's self contained + CloseDb(strFile); dbenv.txn_checkpoint(0, 0, 0); dbenv.lsn_reset(strFile.c_str(), 0); - printf("Flushed wallet.dat %"PRI64d"ms\n", GetTimeMillis() - nStart); + mapFileUseCount.erase(mi++); + printf("Flushed wallet.dat %"PRI64d"ms\n", GetTimeMillis() - nStart); } } } diff --git a/db.h b/db.h index d11b397e..d32d2d5d 100644 --- a/db.h +++ b/db.h @@ -32,8 +32,9 @@ protected: Db* pdb; string strFile; vector vTxn; + bool fReadOnly; - explicit CDB(const char* pszFile, const char* pszMode="r+", bool fTxn=false); + explicit CDB(const char* pszFile, const char* pszMode="r+"); ~CDB() { Close(); } public: void Close(); @@ -77,6 +78,8 @@ protected: { if (!pdb) return false; + if (fReadOnly) + assert(("Write called on database in read-only mode", false)); // Key CDataStream ssKey(SER_DISK); @@ -104,6 +107,8 @@ protected: { if (!pdb) return false; + if (fReadOnly) + assert(("Erase called on database in read-only mode", false)); // Key CDataStream ssKey(SER_DISK); @@ -254,7 +259,7 @@ public: class CTxDB : public CDB { public: - CTxDB(const char* pszMode="r+", bool fTxn=false) : CDB(!fClient ? "blkindex.dat" : NULL, pszMode, fTxn) { } + CTxDB(const char* pszMode="r+") : CDB(!fClient ? "blkindex.dat" : NULL, pszMode) { } private: CTxDB(const CTxDB&); void operator=(const CTxDB&); @@ -283,7 +288,7 @@ public: class CReviewDB : public CDB { public: - CReviewDB(const char* pszMode="r+", bool fTxn=false) : CDB("reviews.dat", pszMode, fTxn) { } + CReviewDB(const char* pszMode="r+") : CDB("reviews.dat", pszMode) { } private: CReviewDB(const CReviewDB&); void operator=(const CReviewDB&); @@ -309,7 +314,7 @@ public: class CMarketDB : public CDB { public: - CMarketDB(const char* pszMode="r+", bool fTxn=false) : CDB("market.dat", pszMode, fTxn) { } + CMarketDB(const char* pszMode="r+") : CDB("market.dat", pszMode) { } private: CMarketDB(const CMarketDB&); void operator=(const CMarketDB&); @@ -322,7 +327,7 @@ private: class CAddrDB : public CDB { public: - CAddrDB(const char* pszMode="r+", bool fTxn=false) : CDB("addr.dat", pszMode, fTxn) { } + CAddrDB(const char* pszMode="r+") : CDB("addr.dat", pszMode) { } private: CAddrDB(const CAddrDB&); void operator=(const CAddrDB&); @@ -341,7 +346,7 @@ bool LoadAddresses(); class CWalletDB : public CDB { public: - CWalletDB(const char* pszMode="r+", bool fTxn=false) : CDB("wallet.dat", pszMode, fTxn) { } + CWalletDB(const char* pszMode="r+") : CDB("wallet.dat", pszMode) { } private: CWalletDB(const CWalletDB&); void operator=(const CWalletDB&); diff --git a/irc.cpp b/irc.cpp index 8432c6d1..8ac38380 100644 --- a/irc.cpp +++ b/irc.cpp @@ -159,15 +159,12 @@ void ThreadIRCSeed(void* parg) SetThreadPriority(THREAD_PRIORITY_NORMAL); int nErrorWait = 10; int nRetryWait = 10; - - // IRC server blocks TOR users - if (fUseProxy && addrProxy.port == htons(9050)) - return; + bool fTOR = (fUseProxy && addrProxy.port == htons(9050)); while (!fShutdown) { CAddress addrConnect("216.155.130.130:6667"); - if (!(fUseProxy && addrProxy.port == htons(9050))) + if (!fTOR) { struct hostent* phostent = gethostbyname("chat.freenode.net"); if (phostent && phostent->h_addr_list && phostent->h_addr_list[0]) @@ -188,6 +185,7 @@ void ThreadIRCSeed(void* parg) if (!RecvUntil(hSocket, "Found your hostname", "using your IP address instead", "Couldn't look up your hostname")) { closesocket(hSocket); + hSocket = INVALID_SOCKET; nErrorWait = nErrorWait * 11 / 10; if (Wait(nErrorWait += 60)) continue; @@ -208,6 +206,7 @@ void ThreadIRCSeed(void* parg) if (!RecvUntil(hSocket, " 004 ")) { closesocket(hSocket); + hSocket = INVALID_SOCKET; nErrorWait = nErrorWait * 11 / 10; if (Wait(nErrorWait += 60)) continue; @@ -269,6 +268,11 @@ void ThreadIRCSeed(void* parg) } } closesocket(hSocket); + hSocket = INVALID_SOCKET; + + // IRC usually blocks TOR, so only try once + if (fTOR) + return; if (GetTime() - nStart > 20 * 60) { diff --git a/main.cpp b/main.cpp index 13a9f9b7..89b42f76 100644 --- a/main.cpp +++ b/main.cpp @@ -760,7 +760,7 @@ unsigned int GetNextWorkRequired(const CBlockIndex* pindexLast) bnNew = bnProofOfWorkLimit; /// debug print - printf("\n\n\nGetNextWorkRequired RETARGET *****\n"); + printf("GetNextWorkRequired RETARGET\n"); printf("nTargetTimespan = %d nActualTimespan = %d\n", nTargetTimespan, nActualTimespan); printf("Before: %08x %s\n", pindexLast->nBits, CBigNum().SetCompact(pindexLast->nBits).getuint256().ToString().c_str()); printf("After: %08x %s\n", bnNew.GetCompact(), bnNew.getuint256().ToString().c_str()); @@ -1013,7 +1013,7 @@ bool CBlock::ConnectBlock(CTxDB& txdb, CBlockIndex* pindex) bool Reorganize(CTxDB& txdb, CBlockIndex* pindexNew) { - printf("*** REORGANIZE ***\n"); + printf("REORGANIZE\n"); // Find the fork CBlockIndex* pfork = pindexBest; @@ -1114,7 +1114,7 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos) // Check for duplicate uint256 hash = GetHash(); if (mapBlockIndex.count(hash)) - return error("AddToBlockIndex() : %s already exists", hash.ToString().substr(0,14).c_str()); + return error("AddToBlockIndex() : %s already exists", hash.ToString().substr(0,16).c_str()); // Construct new block index object CBlockIndex* pindexNew = new CBlockIndex(nFile, nBlockPos, *this); @@ -1174,7 +1174,7 @@ bool CBlock::AddToBlockIndex(unsigned int nFile, unsigned int nBlockPos) pindexBest = pindexNew; nBestHeight = pindexBest->nHeight; nTransactionsUpdated++; - printf("AddToBlockIndex: new best=%s height=%d\n", hashBestChain.ToString().substr(0,14).c_str(), nBestHeight); + printf("AddToBlockIndex: new best=%s height=%d\n", hashBestChain.ToString().substr(0,16).c_str(), nBestHeight); } txdb.TxnCommit(); @@ -1294,9 +1294,9 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) // Check for duplicate uint256 hash = pblock->GetHash(); if (mapBlockIndex.count(hash)) - return error("ProcessBlock() : already have block %d %s", mapBlockIndex[hash]->nHeight, hash.ToString().substr(0,14).c_str()); + return error("ProcessBlock() : already have block %d %s", mapBlockIndex[hash]->nHeight, hash.ToString().substr(0,16).c_str()); if (mapOrphanBlocks.count(hash)) - return error("ProcessBlock() : already have block (orphan) %s", hash.ToString().substr(0,14).c_str()); + return error("ProcessBlock() : already have block (orphan) %s", hash.ToString().substr(0,16).c_str()); // Preliminary checks if (!pblock->CheckBlock()) @@ -1308,7 +1308,7 @@ bool ProcessBlock(CNode* pfrom, CBlock* pblock) // If don't already have its previous block, shunt it off to holding area until we get it if (!mapBlockIndex.count(pblock->hashPrevBlock)) { - printf("ProcessBlock: ORPHAN BLOCK, prev=%s\n", pblock->hashPrevBlock.ToString().substr(0,14).c_str()); + printf("ProcessBlock: ORPHAN BLOCK, prev=%s\n", pblock->hashPrevBlock.ToString().substr(0,16).c_str()); mapOrphanBlocks.insert(make_pair(hash, pblock)); mapOrphanBlocksByPrev.insert(make_pair(pblock->hashPrevBlock, pblock)); @@ -1503,11 +1503,11 @@ bool LoadBlockIndex(bool fAllowNew) // vMerkleTree: 4a5e1e // Genesis block - char* pszTimestamp = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"; + const char* pszTimestamp = "The Times 03/Jan/2009 Chancellor on brink of second bailout for banks"; CTransaction txNew; txNew.vin.resize(1); txNew.vout.resize(1); - txNew.vin[0].scriptSig = CScript() << 486604799 << CBigNum(4) << vector((unsigned char*)pszTimestamp, (unsigned char*)pszTimestamp + strlen(pszTimestamp)); + txNew.vin[0].scriptSig = CScript() << 486604799 << CBigNum(4) << vector((const unsigned char*)pszTimestamp, (const unsigned char*)pszTimestamp + strlen(pszTimestamp)); txNew.vout[0].nValue = 50 * COIN; txNew.vout[0].scriptPubKey = CScript() << CBigNum("0x5F1DF16B2B704C8A578D0BBAF74D385CDE12C11EE50455F3C438EF4C3FBCF649B6DE611FEAE06279A60939E028A8D65C10B73071A6F16719274855FEB0FD8A6704") << OP_CHECKSIG; CBlock block; @@ -1519,7 +1519,7 @@ bool LoadBlockIndex(bool fAllowNew) block.nBits = 0x1d00ffff; block.nNonce = 2083236893; - //// debug print, delete this later + //// debug print printf("%s\n", block.GetHash().ToString().c_str()); printf("%s\n", block.hashMerkleRoot.ToString().c_str()); printf("%s\n", hashGenesisBlock.ToString().c_str()); @@ -1592,7 +1592,7 @@ void PrintBlockTree() pindex->nHeight, pindex->nFile, pindex->nBlockPos, - block.GetHash().ToString().substr(0,14).c_str(), + block.GetHash().ToString().substr(0,16).c_str(), DateTimeStrFormat("%x %H:%M:%S", block.nTime).c_str(), block.vtx.size()); @@ -1912,6 +1912,18 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) CBlock block; block.ReadFromDisk((*mi).second, !pfrom->fClient); pfrom->PushMessage("block", block); + + // Trigger them to send a getblocks request for the next batch of inventory + if (inv.hash == pfrom->hashContinue) + { + // Bypass PushInventory, this must send even if redundant, + // and we want it right after the last block so they don't + // wait for other stuff first. + vector vInv; + vInv.push_back(CInv(MSG_BLOCK, hashBestChain)); + pfrom->PushMessage("inv", vInv); + pfrom->hashContinue = 0; + } } } else if (inv.IsKnownType()) @@ -1948,25 +1960,23 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) // Send the rest of the chain if (pindex) pindex = pindex->pnext; - printf("getblocks %d to %s\n", (pindex ? pindex->nHeight : -1), hashStop.ToString().substr(0,14).c_str()); + printf("getblocks %d to %s\n", (pindex ? pindex->nHeight : -1), hashStop.ToString().substr(0,16).c_str()); + int nLimit = 500; for (; pindex; pindex = pindex->pnext) { if (pindex->GetBlockHash() == hashStop) { - printf(" getblocks stopping at %d %s\n", pindex->nHeight, pindex->GetBlockHash().ToString().substr(0,14).c_str()); + printf(" getblocks stopping at %d %s\n", pindex->nHeight, pindex->GetBlockHash().ToString().substr(0,16).c_str()); break; } - - // Bypass setInventoryKnown in case an inventory message got lost - CRITICAL_BLOCK(pfrom->cs_inventory) + pfrom->PushInventory(CInv(MSG_BLOCK, pindex->GetBlockHash())); + if (--nLimit <= 0) { - CInv inv(MSG_BLOCK, pindex->GetBlockHash()); - // returns true if wasn't already contained in the set - if (pfrom->setInventoryKnown2.insert(inv).second) - { - pfrom->setInventoryKnown.erase(inv); - pfrom->vInventoryToSend.push_back(inv); - } + // When this block is requested, we'll send an inv that'll make them + // getblocks the next batch of inventory. + printf(" getblocks stopping at limit %d %s\n", pindex->nHeight, pindex->GetBlockHash().ToString().substr(0,16).c_str()); + pfrom->hashContinue = pindex->GetBlockHash(); + break; } } } @@ -2049,7 +2059,13 @@ bool ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) vRecv >> *pblock; //// debug print - printf("received block:\n"); pblock->print(); + if (false) + { + printf("received block:\n"); + pblock->print(); + } + else + printf("received block %s\n", pblock->GetHash().ToString().substr(0,16).c_str()); CInv inv(MSG_BLOCK, pblock->GetHash()); pfrom->AddInventoryKnown(inv); @@ -2175,9 +2191,13 @@ bool SendMessages(CNode* pto) if (pto->nVersion == 0) return true; + // Keep-alive ping + if (pto->nLastSend && GetTime() - pto->nLastSend > 12 * 60 && pto->vSend.empty()) + pto->PushMessage("ping"); + // Address refresh broadcast static int64 nLastRebroadcast; - if (nLastRebroadcast < GetTime() - 24 * 60 * 60) // every 24 hours + if (GetTime() - nLastRebroadcast > 24 * 60 * 60) // every 24 hours { nLastRebroadcast = GetTime(); CRITICAL_BLOCK(cs_vNodes) @@ -2194,9 +2214,16 @@ bool SendMessages(CNode* pto) } } - // Keep-alive ping - if (pto->nLastSend && GetTime() - pto->nLastSend > 12 * 60 && pto->vSend.empty()) - pto->PushMessage("ping"); + // Clear inventory known periodically in case an inv message was missed, + // although usually they would just get it from another node. + static int64 nLastInventoryKnownClear; + if (GetTime() - nLastInventoryKnownClear > 2 * 60 * 60) // every 2 hours + { + nLastInventoryKnownClear = GetTime(); + CRITICAL_BLOCK(cs_vNodes) + foreach(CNode* pnode, vNodes) + pnode->setInventoryKnown.clear(); + } // @@ -2243,7 +2270,6 @@ bool SendMessages(CNode* pto) } } pto->vInventoryToSend.clear(); - pto->setInventoryKnown2.clear(); } if (!vInventoryToSend.empty()) pto->PushMessage("inv", vInventoryToSend); @@ -2817,8 +2843,7 @@ bool CommitTransactionSpent(const CWalletTx& wtxNew, const CKey& key) // This is only to keep the database open to defeat the auto-flush for the // duration of this scope. This is the only place where this optimization - // maybe makes sense; please don't do it anywhere else. Keeping databases - // open longer than necessary can create deadlocks. + // maybe makes sense; please don't do it anywhere else. CWalletDB walletdb("r"); // Add the change's private key to wallet diff --git a/main.h b/main.h index 8bb1e19e..79f14c68 100644 --- a/main.h +++ b/main.h @@ -1009,9 +1009,9 @@ public: void print() const { printf("CBlock(hash=%s, ver=%d, hashPrevBlock=%s, hashMerkleRoot=%s, nTime=%u, nBits=%08x, nNonce=%u, vtx=%d)\n", - GetHash().ToString().substr(0,14).c_str(), + GetHash().ToString().substr(0,16).c_str(), nVersion, - hashPrevBlock.ToString().substr(0,14).c_str(), + hashPrevBlock.ToString().substr(0,16).c_str(), hashMerkleRoot.ToString().substr(0,6).c_str(), nTime, nBits, nNonce, vtx.size()); @@ -1159,7 +1159,7 @@ public: return strprintf("CBlockIndex(nprev=%08x, pnext=%08x, nFile=%d, nBlockPos=%-6d nHeight=%d, merkle=%s, hashBlock=%s)", pprev, pnext, nFile, nBlockPos, nHeight, hashMerkleRoot.ToString().substr(0,6).c_str(), - GetBlockHash().ToString().substr(0,14).c_str()); + GetBlockHash().ToString().substr(0,16).c_str()); } void print() const @@ -1229,8 +1229,8 @@ public: str += CBlockIndex::ToString(); str += strprintf("\n hashBlock=%s, hashPrev=%s, hashNext=%s)", GetBlockHash().ToString().c_str(), - hashPrev.ToString().substr(0,14).c_str(), - hashNext.ToString().substr(0,14).c_str()); + hashPrev.ToString().substr(0,16).c_str(), + hashNext.ToString().substr(0,16).c_str()); return str; } diff --git a/makefile.unix b/makefile.unix index 24aa8bfe..c0d0ee1d 100644 --- a/makefile.unix +++ b/makefile.unix @@ -17,24 +17,21 @@ endif INCLUDEPATHS= \ -I"/usr/include" \ - -I"/usr/local/boost_1_40_0" \ - -I"/usr/local/db-4.7.25.NC/build_unix" \ -I"/usr/local/include/wx-2.8" \ -I"/usr/local/lib/wx/include/gtk2-ansi-debug-static-2.8" LIBPATHS= \ -L"/usr/lib" \ -L"/usr/local/lib" \ - -L"/usr/local/db-4.7.25.NC/build_unix" LIBS= \ - -Wl,-Bstatic -l boost_thread -l boost_system -l boost_filesystem -Wl,-Bdynamic \ + -Wl,-Bstatic -l boost_system -l boost_filesystem -Wl,-Bdynamic \ -Wl,-Bstatic -l db_cxx -l wx_gtk2$(D)-2.8 -Wl,-Bdynamic \ -l crypto \ -l gtk-x11-2.0 -l gthread-2.0 -l SM WXDEFS=-D__WXGTK__ -DNOPCH -CFLAGS=-O0 -w -Wno-invalid-offsetof -Wformat $(DEBUGFLAGS) $(WXDEFS) $(INCLUDEPATHS) +CFLAGS=-O0 -Wno-invalid-offsetof -Wformat $(DEBUGFLAGS) $(WXDEFS) $(INCLUDEPATHS) HEADERS=headers.h util.h main.h serialize.h uint256.h key.h bignum.h script.h db.h base58.h diff --git a/net.cpp b/net.cpp index 71295d5d..9f4060b9 100644 --- a/net.cpp +++ b/net.cpp @@ -148,8 +148,8 @@ bool GetMyExternalIP2(const CAddress& addrConnect, const char* pszGet, const cha bool GetMyExternalIP(unsigned int& ipRet) { CAddress addrConnect; - char* pszGet; - char* pszKeyword; + const char* pszGet; + const char* pszKeyword; if (fUseProxy) return false; @@ -463,14 +463,21 @@ CNode* ConnectNode(CAddress addrConnect, int64 nTimeout) } } -void CNode::DoDisconnect() +void CNode::CloseSocketDisconnect() { - if (fDebug) - printf("%s ", DateTimeStrFormat("%x %H:%M:%S", GetTime()).c_str()); - printf("disconnecting node %s\n", addr.ToStringLog().c_str()); - - closesocket(hSocket); + fDisconnect = true; + if (hSocket != INVALID_SOCKET) + { + if (fDebug) + printf("%s ", DateTimeStrFormat("%x %H:%M:%S", GetTime()).c_str()); + printf("disconnecting node %s\n", addr.ToStringLog().c_str()); + closesocket(hSocket); + hSocket = INVALID_SOCKET; + } +} +void CNode::Cleanup() +{ // 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. @@ -540,11 +547,12 @@ void ThreadSocketHandler2(void* parg) // remove from vNodes vNodes.erase(remove(vNodes.begin(), vNodes.end(), pnode), vNodes.end()); - // close socket - pnode->DoDisconnect(); + // close socket and cleanup + pnode->CloseSocketDisconnect(); + pnode->Cleanup(); // hold in disconnected pool until all refs are released - pnode->nReleaseTime = max(pnode->nReleaseTime, GetTime() + 5 * 60); + pnode->nReleaseTime = max(pnode->nReleaseTime, GetTime() + 15 * 60); if (pnode->fNetworkNode || pnode->fInbound) pnode->Release(); vNodesDisconnected.push_back(pnode); @@ -599,6 +607,8 @@ void ThreadSocketHandler2(void* parg) { foreach(CNode* pnode, vNodes) { + if (pnode->hSocket == INVALID_SOCKET || pnode->hSocket < 0) + continue; FD_SET(pnode->hSocket, &fdsetRecv); FD_SET(pnode->hSocket, &fdsetError); hSocketMax = max(hSocketMax, pnode->hSocket); @@ -659,17 +669,22 @@ void ThreadSocketHandler2(void* parg) // vector vNodesCopy; CRITICAL_BLOCK(cs_vNodes) + { vNodesCopy = vNodes; + foreach(CNode* pnode, vNodesCopy) + pnode->AddRef(); + } foreach(CNode* pnode, vNodesCopy) { if (fShutdown) return; - SOCKET hSocket = pnode->hSocket; // // Receive // - if (FD_ISSET(hSocket, &fdsetRecv) || FD_ISSET(hSocket, &fdsetError)) + if (pnode->hSocket == INVALID_SOCKET) + continue; + if (FD_ISSET(pnode->hSocket, &fdsetRecv) || FD_ISSET(pnode->hSocket, &fdsetError)) { TRY_CRITICAL_BLOCK(pnode->cs_vRecv) { @@ -678,7 +693,7 @@ void ThreadSocketHandler2(void* parg) // typical socket buffer is 8K-64K char pchBuf[0x10000]; - int nBytes = recv(hSocket, pchBuf, sizeof(pchBuf), MSG_DONTWAIT); + int nBytes = recv(pnode->hSocket, pchBuf, sizeof(pchBuf), MSG_DONTWAIT); if (nBytes > 0) { vRecv.resize(nPos + nBytes); @@ -690,7 +705,7 @@ void ThreadSocketHandler2(void* parg) // socket closed gracefully if (!pnode->fDisconnect) printf("socket closed\n"); - pnode->fDisconnect = true; + pnode->CloseSocketDisconnect(); } else if (nBytes < 0) { @@ -700,7 +715,7 @@ void ThreadSocketHandler2(void* parg) { if (!pnode->fDisconnect) printf("socket recv error %d\n", nErr); - pnode->fDisconnect = true; + pnode->CloseSocketDisconnect(); } } } @@ -709,14 +724,16 @@ void ThreadSocketHandler2(void* parg) // // Send // - if (FD_ISSET(hSocket, &fdsetSend)) + if (pnode->hSocket == INVALID_SOCKET) + continue; + if (FD_ISSET(pnode->hSocket, &fdsetSend)) { TRY_CRITICAL_BLOCK(pnode->cs_vSend) { CDataStream& vSend = pnode->vSend; if (!vSend.empty()) { - int nBytes = send(hSocket, &vSend[0], vSend.size(), MSG_NOSIGNAL | MSG_DONTWAIT); + int nBytes = send(pnode->hSocket, &vSend[0], vSend.size(), MSG_NOSIGNAL | MSG_DONTWAIT); if (nBytes > 0) { vSend.erase(vSend.begin(), vSend.begin() + nBytes); @@ -729,7 +746,7 @@ void ThreadSocketHandler2(void* parg) if (nErr != WSAEWOULDBLOCK && nErr != WSAEMSGSIZE && nErr != WSAEINTR && nErr != WSAEINPROGRESS) { printf("socket send error %d\n", nErr); - pnode->fDisconnect = true; + pnode->CloseSocketDisconnect(); } } } @@ -760,18 +777,12 @@ void ThreadSocketHandler2(void* parg) } } } - - - //// debug heartbeat - static int64 nHeartbeat1; - if (GetTime() - nHeartbeat1 >= 5 * 60) + CRITICAL_BLOCK(cs_vNodes) { - printf("%s sendrecv\n", DateTimeStrFormat("%x %H:%M:%S", GetTime()).c_str()); - nHeartbeat1 = GetTime(); - fDebug = true; + foreach(CNode* pnode, vNodesCopy) + pnode->Release(); } - nThreadSocketHandlerHeartbeat = GetTime(); Sleep(10); } @@ -812,18 +823,21 @@ void ThreadOpenConnections2(void* parg) printf("ThreadOpenConnections started\n"); // Connect to specific addresses - while (mapArgs.count("-connect")) + if (mapArgs.count("-connect")) { - foreach(string strAddr, mapMultiArgs["-connect"]) + for (int64 nLoop = 0;; nLoop++) { - CAddress addr(strAddr, NODE_NETWORK); - if (addr.IsValid()) - OpenNetworkConnection(addr); - for (int i = 0; i < 10; i++) + foreach(string strAddr, mapMultiArgs["-connect"]) { - Sleep(1000); - if (fShutdown) - return; + CAddress addr(strAddr, NODE_NETWORK); + if (addr.IsValid()) + OpenNetworkConnection(addr); + for (int i = 0; i < 10 && i < nLoop; i++) + { + Sleep(500); + if (fShutdown) + return; + } } } } @@ -837,7 +851,7 @@ void ThreadOpenConnections2(void* parg) if (addr.IsValid()) { OpenNetworkConnection(addr); - Sleep(1000); + Sleep(500); if (fShutdown) return; } @@ -898,7 +912,7 @@ void ThreadOpenConnections2(void* parg) // 30 days 27 hours // 90 days 46 hours // 365 days 93 hours - int64 nDelay = 3600.0 * sqrt(fabs(nSinceLastSeen) / 3600.0) + nRandomizer; + int64 nDelay = (int64)(3600.0 * sqrt(fabs(nSinceLastSeen) / 3600.0) + nRandomizer); // Fast reconnect for one hour after last seen if (nSinceLastSeen < 60 * 60) @@ -1013,11 +1027,13 @@ void ThreadMessageHandler2(void* parg) // Poll the connected nodes for messages vector vNodesCopy; CRITICAL_BLOCK(cs_vNodes) + { vNodesCopy = vNodes; + foreach(CNode* pnode, vNodesCopy) + pnode->AddRef(); + } foreach(CNode* pnode, vNodesCopy) { - pnode->AddRef(); - // Receive messages TRY_CRITICAL_BLOCK(pnode->cs_vRecv) ProcessMessages(pnode); @@ -1029,8 +1045,11 @@ void ThreadMessageHandler2(void* parg) SendMessages(pnode); if (fShutdown) return; - - pnode->Release(); + } + CRITICAL_BLOCK(cs_vNodes) + { + foreach(CNode* pnode, vNodesCopy) + pnode->Release(); } // Wait and allow messages to bunch up @@ -1257,8 +1276,7 @@ void StartNode(void* parg) if (!fGot) { printf("*** closing socket\n"); - closesocket(pnode->hSocket); - pnode->fDisconnect = true; + pnode->CloseSocketDisconnect(); } } } @@ -1292,7 +1310,7 @@ bool StopNode() int64 nStart = GetTime(); while (vnThreadsRunning[0] > 0 || vnThreadsRunning[2] > 0 || vnThreadsRunning[3] > 0) { - if (GetTime() - nStart > 15) + if (GetTime() - nStart > 20) break; Sleep(20); } diff --git a/net.h b/net.h index 65bbb960..7d71be49 100644 --- a/net.h +++ b/net.h @@ -414,7 +414,7 @@ public: string ToString() const { - return strprintf("%s %s", GetCommand(), hash.ToString().substr(0,14).c_str()); + return strprintf("%s %s", GetCommand(), hash.ToString().substr(0,16).c_str()); } void print() const @@ -504,6 +504,7 @@ public: int64 nReleaseTime; map mapRequests; CCriticalSection cs_mapRequests; + uint256 hashContinue; // flood vector vAddrToSend; @@ -512,7 +513,6 @@ public: // inventory based relay set setInventoryKnown; - set setInventoryKnown2; vector vInventoryToSend; CCriticalSection cs_inventory; multimap mapAskFor; @@ -541,6 +541,7 @@ public: fDisconnect = false; nRefCount = 0; nReleaseTime = 0; + hashContinue = 0; fGetAddr = false; vfSubscribe.assign(256, false); @@ -550,13 +551,16 @@ public: CAddress addrYou = (fUseProxy ? CAddress("0.0.0.0") : addr); CAddress addrMe = (fUseProxy ? CAddress("0.0.0.0") : addrLocalHost); RAND_bytes((unsigned char*)&nLocalHostNonce, sizeof(nLocalHostNonce)); - PushMessage("version", VERSION, nLocalServices, nTime, addrYou, addrMe, nLocalHostNonce, string("test5")); + PushMessage("version", VERSION, nLocalServices, nTime, addrYou, addrMe, nLocalHostNonce, string(pszSubVer)); } ~CNode() { if (hSocket != INVALID_SOCKET) + { closesocket(hSocket); + hSocket = INVALID_SOCKET; + } } private: @@ -570,12 +574,13 @@ public: return max(nRefCount, 0) + (GetTime() < nReleaseTime ? 1 : 0); } - void AddRef(int64 nTimeout=0) + CNode* AddRef(int64 nTimeout=0) { if (nTimeout != 0) nReleaseTime = max(nReleaseTime, GetTime() + nTimeout); else nRefCount++; + return this; } void Release() @@ -899,7 +904,8 @@ public: bool IsSubscribed(unsigned int nChannel); void Subscribe(unsigned int nChannel, unsigned int nHops=0); void CancelSubscribe(unsigned int nChannel); - void DoDisconnect(); + void CloseSocketDisconnect(); + void Cleanup(); }; diff --git a/serialize.h b/serialize.h index 9b20e2a0..aae821b6 100644 --- a/serialize.h +++ b/serialize.h @@ -20,6 +20,7 @@ class CDataStream; class CAutoFile; static const int VERSION = 106; +static const char* pszSubVer = " linux-test8"; diff --git a/ui.cpp b/ui.cpp index 9d7556cb..aaa26adf 100644 --- a/ui.cpp +++ b/ui.cpp @@ -1664,7 +1664,7 @@ void COptionsDialog::OnButtonApply(wxCommandEvent& event) CAboutDialog::CAboutDialog(wxWindow* parent) : CAboutDialogBase(parent) { - m_staticTextVersion->SetLabel(strprintf("version 0.%d.%d Beta", VERSION/100, VERSION%100)); + m_staticTextVersion->SetLabel(strprintf("version 0.%d.%d beta", VERSION/100, VERSION%100)); // Workaround until upgrade to wxWidgets supporting UTF-8 wxString str = m_staticTextMain->GetLabel(); @@ -2030,7 +2030,7 @@ void CSendingDialog::StartTransfer() // We may have connected already for product details if (!Status("Connecting...")) return; - CNode* pnode = ConnectNode(addr, 5 * 60); + CNode* pnode = ConnectNode(addr, 15 * 60); if (!pnode) { Error("Unable to connect"); @@ -2075,14 +2075,6 @@ void CSendingDialog::OnReply2(CDataStream& vRecv) return; } - // Should already be connected - CNode* pnode = ConnectNode(addr, 5 * 60); - if (!pnode) - { - Error("Lost connection"); - return; - } - // Pause to give the user a chance to cancel while (wxDateTime::UNow() < start + wxTimeSpan(0, 0, 0, 2 * 1000)) { @@ -2112,6 +2104,14 @@ void CSendingDialog::OnReply2(CDataStream& vRecv) return; } + // Make sure we're still connected + CNode* pnode = ConnectNode(addr, 2 * 60 * 60); + if (!pnode) + { + Error("Lost connection, transaction cancelled"); + return; + } + // Last chance to cancel Sleep(50); if (!Status()) @@ -3495,12 +3495,14 @@ bool CMyApp::OnInit2() if (mapArgs.count("-debug")) fDebug = true; + if (strstr(pszSubVer, "test")) + fDebug = true; if (mapArgs.count("-printtodebugger")) fPrintToDebugger = true; printf("\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n"); - printf("Bitcoin version %d, OS version %s\n", VERSION, wxGetOsDescription().mb_str()); + printf("Bitcoin version %d%s, OS version %s\n", VERSION, pszSubVer, wxGetOsDescription().mb_str()); if (mapArgs.count("-loadblockindextest")) { @@ -3843,9 +3845,8 @@ void SetStartOnSystemStartup(bool fAutoStart) CoInitialize(NULL); // Get a pointer to the IShellLink interface. - HRESULT hres = NULL; IShellLink* psl = NULL; - hres = CoCreateInstance(CLSID_ShellLink, NULL, + HRESULT hres = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, IID_IShellLink, reinterpret_cast(&psl)); diff --git a/util.cpp b/util.cpp index 7a947730..305db5ce 100644 --- a/util.cpp +++ b/util.cpp @@ -56,9 +56,11 @@ public: // Close sockets foreach(CNode* pnode, vNodes) - closesocket(pnode->hSocket); - if (closesocket(hListenSocket) == SOCKET_ERROR) - printf("closesocket(hListenSocket) failed with error %d\n", WSAGetLastError()); + if (pnode->hSocket != INVALID_SOCKET) + closesocket(pnode->hSocket); + if (hListenSocket != INVALID_SOCKET) + if (closesocket(hListenSocket) == SOCKET_ERROR) + printf("closesocket(hListenSocket) failed with error %d\n", WSAGetLastError()); #ifdef __WXMSW__ // Shutdown Windows Sockets @@ -348,7 +350,7 @@ void ParseParameters(int argc, char* argv[]) { char psz[10000]; strlcpy(psz, argv[i], sizeof(psz)); - char* pszValue = ""; + char* pszValue = (char*)""; if (strchr(psz, '=')) { pszValue = strchr(psz, '='); diff --git a/util.h b/util.h index ddac4494..9366e66e 100644 --- a/util.h +++ b/util.h @@ -57,9 +57,11 @@ inline T& REF(const T& val) #ifdef __WXMSW__ #define MSG_NOSIGNAL 0 #define MSG_DONTWAIT 0 +#ifndef UINT64_MAX #define UINT64_MAX _UI64_MAX #define INT64_MAX _I64_MAX #define INT64_MIN _I64_MIN +#endif #else #define WSAGetLastError() errno #define WSAEWOULDBLOCK EWOULDBLOCK @@ -67,7 +69,7 @@ inline T& REF(const T& val) #define WSAEINTR EINTR #define WSAEINPROGRESS EINPROGRESS #define WSAEADDRINUSE EADDRINUSE -#define closesocket(s) close(s) +#define WSAENOTSOCK EBADF #define INVALID_SOCKET (SOCKET)(~0) #define SOCKET_ERROR -1 typedef u_int SOCKET; @@ -80,6 +82,23 @@ typedef u_int SOCKET; #define Beep(n1,n2) (0) #endif +inline int myclosesocket(SOCKET& hSocket) +{ + if (hSocket == INVALID_SOCKET) + return WSAENOTSOCK; +#ifdef __WXMSW__ + int ret = closesocket(hSocket); +#else + int ret = close(hSocket); +#endif + hSocket = INVALID_SOCKET; + return ret; +} +#define closesocket(s) myclosesocket(s) + + + + @@ -149,7 +168,7 @@ public: bool TryEnter() { return mutex.TryLock() == wxMUTEX_NO_ERROR; } #endif public: - char* pszFile; + const char* pszFile; int nLine; }; diff --git a/xpm/addressbook16.xpm b/xpm/addressbook16.xpm index 471f700c..e00944ef 100644 --- a/xpm/addressbook16.xpm +++ b/xpm/addressbook16.xpm @@ -1,5 +1,5 @@ /* XPM */ -static char * addressbook16_xpm[] = { +static const char * addressbook16_xpm[] = { /* columns rows colors chars-per-pixel */ "16 16 256 2", " c #FFFFFF", diff --git a/xpm/addressbook20.xpm b/xpm/addressbook20.xpm index 48df12d8..7ebd73fb 100644 --- a/xpm/addressbook20.xpm +++ b/xpm/addressbook20.xpm @@ -1,5 +1,5 @@ /* XPM */ -static char * addressbook20_xpm[] = { +static const char * addressbook20_xpm[] = { /* columns rows colors chars-per-pixel */ "20 20 256 2", " c #FFFFFF", diff --git a/xpm/bitcoin16.xpm b/xpm/bitcoin16.xpm index 8bec142c..a1397522 100644 --- a/xpm/bitcoin16.xpm +++ b/xpm/bitcoin16.xpm @@ -1,5 +1,5 @@ /* XPM */ -static char * bitcoin16_xpm[] = { +static const char * bitcoin16_xpm[] = { /* columns rows colors chars-per-pixel */ "16 16 181 2", " c #775605", diff --git a/xpm/bitcoin20.xpm b/xpm/bitcoin20.xpm index 2dd61a59..93b34ba7 100644 --- a/xpm/bitcoin20.xpm +++ b/xpm/bitcoin20.xpm @@ -1,5 +1,5 @@ /* XPM */ -static char * bitcoin20_xpm[] = { +static const char * bitcoin20_xpm[] = { /* columns rows colors chars-per-pixel */ "20 20 200 2", " c #7B5500", diff --git a/xpm/bitcoin32.xpm b/xpm/bitcoin32.xpm index 25da102f..0ac49f61 100644 --- a/xpm/bitcoin32.xpm +++ b/xpm/bitcoin32.xpm @@ -1,5 +1,5 @@ /* XPM */ -static char * bitcoin32_xpm[] = { +static const char * bitcoin32_xpm[] = { /* columns rows colors chars-per-pixel */ "32 32 185 2", " c #715103", diff --git a/xpm/bitcoin48.xpm b/xpm/bitcoin48.xpm index 788e855d..bc388bdc 100644 --- a/xpm/bitcoin48.xpm +++ b/xpm/bitcoin48.xpm @@ -1,5 +1,5 @@ /* XPM */ -static char * bitcoin48_xpm[] = { +static const char * bitcoin48_xpm[] = { /* columns rows colors chars-per-pixel */ "48 48 224 2", " c #715103", diff --git a/xpm/check.xpm b/xpm/check.xpm index 8f0b9d28..e62b6569 100644 --- a/xpm/check.xpm +++ b/xpm/check.xpm @@ -1,5 +1,5 @@ /* XPM */ -static char * check_xpm[] = { +static const char * check_xpm[] = { /* columns rows colors chars-per-pixel */ "32 32 3 1", " c #008000", diff --git a/xpm/send16.xpm b/xpm/send16.xpm index 1eeceb4e..7da44d9c 100644 --- a/xpm/send16.xpm +++ b/xpm/send16.xpm @@ -1,5 +1,5 @@ /* XPM */ -static char * send16_xpm[] = { +static const char * send16_xpm[] = { /* columns rows colors chars-per-pixel */ "16 16 256 2", " c #ADF7AD", diff --git a/xpm/send16noshadow.xpm b/xpm/send16noshadow.xpm index d1b482ef..f6cef45e 100644 --- a/xpm/send16noshadow.xpm +++ b/xpm/send16noshadow.xpm @@ -1,5 +1,5 @@ /* XPM */ -static char * send16noshadow_xpm[] = { +static const char * send16noshadow_xpm[] = { /* columns rows colors chars-per-pixel */ "16 16 256 2", " c #ADF7AD", diff --git a/xpm/send20.xpm b/xpm/send20.xpm index 597ea146..68e7b137 100644 --- a/xpm/send20.xpm +++ b/xpm/send20.xpm @@ -1,5 +1,5 @@ /* XPM */ -static char * send20_xpm[] = { +static const char * send20_xpm[] = { /* columns rows colors chars-per-pixel */ "20 20 256 2", " c #CEFFCE",