aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Makefile.test.include1
-rw-r--r--src/addrman.cpp295
-rw-r--r--src/addrman.h206
-rw-r--r--src/chainparams.cpp6
-rw-r--r--src/chainparams.h6
-rw-r--r--src/init.cpp5
-rw-r--r--src/main.cpp148
-rw-r--r--src/main.h2
-rw-r--r--src/net.cpp3
-rw-r--r--src/qt/bitcoingui.cpp18
-rw-r--r--src/qt/bitcoingui.h2
-rw-r--r--src/qt/peertablemodel.cpp2
-rw-r--r--src/qt/rpcconsole.cpp1
-rw-r--r--src/qt/test/test_main.cpp2
-rw-r--r--src/qt/transactiontablemodel.cpp4
-rw-r--r--src/qt/walletview.cpp8
-rw-r--r--src/qt/walletview.h2
-rw-r--r--src/rpcserver.cpp3
-rw-r--r--src/rpcserver.h1
-rw-r--r--src/test/mempool_tests.cpp104
-rw-r--r--src/test/test_bitcoin.cpp2
-rw-r--r--src/txdb.h2
-rw-r--r--src/txmempool.cpp12
-rw-r--r--src/util.cpp7
-rw-r--r--src/validationinterface.cpp4
-rw-r--r--src/validationinterface.h6
-rw-r--r--src/wallet/rpcwallet.cpp22
-rw-r--r--src/wallet/wallet.cpp57
-rw-r--r--src/wallet/wallet.h5
29 files changed, 626 insertions, 310 deletions
diff --git a/src/Makefile.test.include b/src/Makefile.test.include
index 8dd0a28454..52ff3f224f 100644
--- a/src/Makefile.test.include
+++ b/src/Makefile.test.include
@@ -50,6 +50,7 @@ BITCOIN_TESTS =\
test/hash_tests.cpp \
test/key_tests.cpp \
test/main_tests.cpp \
+ test/mempool_tests.cpp \
test/miner_tests.cpp \
test/mruset_tests.cpp \
test/multisig_tests.cpp \
diff --git a/src/addrman.cpp b/src/addrman.cpp
index 4b7e4d51b9..5d9527f0e1 100644
--- a/src/addrman.cpp
+++ b/src/addrman.cpp
@@ -10,34 +10,27 @@
using namespace std;
-int CAddrInfo::GetTriedBucket(const std::vector<unsigned char>& nKey) const
+int CAddrInfo::GetTriedBucket(const uint256& nKey) const
{
- CDataStream ss1(SER_GETHASH, 0);
- std::vector<unsigned char> vchKey = GetKey();
- ss1 << nKey << vchKey;
- uint64_t hash1 = Hash(ss1.begin(), ss1.end()).GetCheapHash();
-
- CDataStream ss2(SER_GETHASH, 0);
- std::vector<unsigned char> vchGroupKey = GetGroup();
- ss2 << nKey << vchGroupKey << (hash1 % ADDRMAN_TRIED_BUCKETS_PER_GROUP);
- uint64_t hash2 = Hash(ss2.begin(), ss2.end()).GetCheapHash();
+ uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << GetKey()).GetHash().GetCheapHash();
+ uint64_t hash2 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup() << (hash1 % ADDRMAN_TRIED_BUCKETS_PER_GROUP)).GetHash().GetCheapHash();
return hash2 % ADDRMAN_TRIED_BUCKET_COUNT;
}
-int CAddrInfo::GetNewBucket(const std::vector<unsigned char>& nKey, const CNetAddr& src) const
+int CAddrInfo::GetNewBucket(const uint256& nKey, const CNetAddr& src) const
{
- CDataStream ss1(SER_GETHASH, 0);
- std::vector<unsigned char> vchGroupKey = GetGroup();
std::vector<unsigned char> vchSourceGroupKey = src.GetGroup();
- ss1 << nKey << vchGroupKey << vchSourceGroupKey;
- uint64_t hash1 = Hash(ss1.begin(), ss1.end()).GetCheapHash();
-
- CDataStream ss2(SER_GETHASH, 0);
- ss2 << nKey << vchSourceGroupKey << (hash1 % ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP);
- uint64_t hash2 = Hash(ss2.begin(), ss2.end()).GetCheapHash();
+ uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << GetGroup() << vchSourceGroupKey).GetHash().GetCheapHash();
+ uint64_t hash2 = (CHashWriter(SER_GETHASH, 0) << nKey << vchSourceGroupKey << (hash1 % ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP)).GetHash().GetCheapHash();
return hash2 % ADDRMAN_NEW_BUCKET_COUNT;
}
+int CAddrInfo::GetBucketPosition(const uint256 &nKey, bool fNew, int nBucket) const
+{
+ uint64_t hash1 = (CHashWriter(SER_GETHASH, 0) << nKey << (fNew ? 'N' : 'K') << nBucket << GetKey()).GetHash().GetCheapHash();
+ return hash1 % ADDRMAN_BUCKET_SIZE;
+}
+
bool CAddrInfo::IsTerrible(int64_t nNow) const
{
if (nLastTry && nLastTry >= nNow - 60) // never remove things tried in the last minute
@@ -70,8 +63,6 @@ double CAddrInfo::GetChance(int64_t nNow) const
if (nSinceLastTry < 0)
nSinceLastTry = 0;
- fChance *= 600.0 / (600.0 + nSinceLastSeen);
-
// deprioritize very recent attempts away
if (nSinceLastTry < 60 * 10)
fChance *= 0.01;
@@ -128,85 +119,44 @@ void CAddrMan::SwapRandom(unsigned int nRndPos1, unsigned int nRndPos2)
vRandom[nRndPos2] = nId1;
}
-int CAddrMan::SelectTried(int nKBucket)
+void CAddrMan::Delete(int nId)
{
- std::vector<int>& vTried = vvTried[nKBucket];
-
- // randomly shuffle the first few elements (using the entire list)
- // find the least recently tried among them
- int64_t nOldest = -1;
- int nOldestPos = -1;
- for (unsigned int i = 0; i < ADDRMAN_TRIED_ENTRIES_INSPECT_ON_EVICT && i < vTried.size(); i++) {
- int nPos = GetRandInt(vTried.size() - i) + i;
- int nTemp = vTried[nPos];
- vTried[nPos] = vTried[i];
- vTried[i] = nTemp;
- assert(nOldest == -1 || mapInfo.count(nTemp) == 1);
- if (nOldest == -1 || mapInfo[nTemp].nLastSuccess < mapInfo[nOldest].nLastSuccess) {
- nOldest = nTemp;
- nOldestPos = nPos;
- }
- }
+ assert(mapInfo.count(nId) != 0);
+ CAddrInfo& info = mapInfo[nId];
+ assert(!info.fInTried);
+ assert(info.nRefCount == 0);
- return nOldestPos;
+ SwapRandom(info.nRandomPos, vRandom.size() - 1);
+ vRandom.pop_back();
+ mapAddr.erase(info);
+ mapInfo.erase(nId);
+ nNew--;
}
-int CAddrMan::ShrinkNew(int nUBucket)
+void CAddrMan::ClearNew(int nUBucket, int nUBucketPos)
{
- assert(nUBucket >= 0 && (unsigned int)nUBucket < vvNew.size());
- std::set<int>& vNew = vvNew[nUBucket];
-
- // first look for deletable items
- for (std::set<int>::iterator it = vNew.begin(); it != vNew.end(); it++) {
- assert(mapInfo.count(*it));
- CAddrInfo& info = mapInfo[*it];
- if (info.IsTerrible()) {
- if (--info.nRefCount == 0) {
- SwapRandom(info.nRandomPos, vRandom.size() - 1);
- vRandom.pop_back();
- mapAddr.erase(info);
- mapInfo.erase(*it);
- nNew--;
- }
- vNew.erase(it);
- return 0;
- }
- }
-
- // otherwise, select four randomly, and pick the oldest of those to replace
- int n[4] = {GetRandInt(vNew.size()), GetRandInt(vNew.size()), GetRandInt(vNew.size()), GetRandInt(vNew.size())};
- int nI = 0;
- int nOldest = -1;
- for (std::set<int>::iterator it = vNew.begin(); it != vNew.end(); it++) {
- if (nI == n[0] || nI == n[1] || nI == n[2] || nI == n[3]) {
- assert(nOldest == -1 || mapInfo.count(*it) == 1);
- if (nOldest == -1 || mapInfo[*it].nTime < mapInfo[nOldest].nTime)
- nOldest = *it;
+ // if there is an entry in the specified bucket, delete it.
+ if (vvNew[nUBucket][nUBucketPos] != -1) {
+ int nIdDelete = vvNew[nUBucket][nUBucketPos];
+ CAddrInfo& infoDelete = mapInfo[nIdDelete];
+ assert(infoDelete.nRefCount > 0);
+ infoDelete.nRefCount--;
+ vvNew[nUBucket][nUBucketPos] = -1;
+ if (infoDelete.nRefCount == 0) {
+ Delete(nIdDelete);
}
- nI++;
- }
- assert(mapInfo.count(nOldest) == 1);
- CAddrInfo& info = mapInfo[nOldest];
- if (--info.nRefCount == 0) {
- SwapRandom(info.nRandomPos, vRandom.size() - 1);
- vRandom.pop_back();
- mapAddr.erase(info);
- mapInfo.erase(nOldest);
- nNew--;
}
- vNew.erase(nOldest);
-
- return 1;
}
-void CAddrMan::MakeTried(CAddrInfo& info, int nId, int nOrigin)
+void CAddrMan::MakeTried(CAddrInfo& info, int nId)
{
- assert(vvNew[nOrigin].count(nId) == 1);
-
// remove the entry from all new buckets
- for (std::vector<std::set<int> >::iterator it = vvNew.begin(); it != vvNew.end(); it++) {
- if ((*it).erase(nId))
+ for (int bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; bucket++) {
+ int pos = info.GetBucketPosition(nKey, true, bucket);
+ if (vvNew[bucket][pos] == nId) {
+ vvNew[bucket][pos] = -1;
info.nRefCount--;
+ }
}
nNew--;
@@ -214,44 +164,36 @@ void CAddrMan::MakeTried(CAddrInfo& info, int nId, int nOrigin)
// which tried bucket to move the entry to
int nKBucket = info.GetTriedBucket(nKey);
- std::vector<int>& vTried = vvTried[nKBucket];
-
- // first check whether there is place to just add it
- if (vTried.size() < ADDRMAN_TRIED_BUCKET_SIZE) {
- vTried.push_back(nId);
- nTried++;
- info.fInTried = true;
- return;
- }
-
- // otherwise, find an item to evict
- int nPos = SelectTried(nKBucket);
-
- // find which new bucket it belongs to
- assert(mapInfo.count(vTried[nPos]) == 1);
- int nUBucket = mapInfo[vTried[nPos]].GetNewBucket(nKey);
- std::set<int>& vNew = vvNew[nUBucket];
-
- // remove the to-be-replaced tried entry from the tried set
- CAddrInfo& infoOld = mapInfo[vTried[nPos]];
- infoOld.fInTried = false;
- infoOld.nRefCount = 1;
- // do not update nTried, as we are going to move something else there immediately
-
- // check whether there is place in that one,
- if (vNew.size() < ADDRMAN_NEW_BUCKET_SIZE) {
- // if so, move it back there
- vNew.insert(vTried[nPos]);
- } else {
- // otherwise, move it to the new bucket nId came from (there is certainly place there)
- vvNew[nOrigin].insert(vTried[nPos]);
+ int nKBucketPos = info.GetBucketPosition(nKey, false, nKBucket);
+
+ // first make space to add it (the existing tried entry there is moved to new, deleting whatever is there).
+ if (vvTried[nKBucket][nKBucketPos] != -1) {
+ // find an item to evict
+ int nIdEvict = vvTried[nKBucket][nKBucketPos];
+ assert(mapInfo.count(nIdEvict) == 1);
+ CAddrInfo& infoOld = mapInfo[nIdEvict];
+
+ // Remove the to-be-evicted item from the tried set.
+ infoOld.fInTried = false;
+ vvTried[nKBucket][nKBucketPos] = -1;
+ nTried--;
+
+ // find which new bucket it belongs to
+ int nUBucket = infoOld.GetNewBucket(nKey);
+ int nUBucketPos = infoOld.GetBucketPosition(nKey, true, nUBucket);
+ ClearNew(nUBucket, nUBucketPos);
+ assert(vvNew[nUBucket][nUBucketPos] == -1);
+
+ // Enter it into the new set again.
+ infoOld.nRefCount = 1;
+ vvNew[nUBucket][nUBucketPos] = nIdEvict;
+ nNew++;
}
- nNew++;
+ assert(vvTried[nKBucket][nKBucketPos] == -1);
- vTried[nPos] = nId;
- // we just overwrote an entry in vTried; no need to update nTried
+ vvTried[nKBucket][nKBucketPos] = nId;
+ nTried++;
info.fInTried = true;
- return;
}
void CAddrMan::Good_(const CService& addr, int64_t nTime)
@@ -281,12 +223,12 @@ void CAddrMan::Good_(const CService& addr, int64_t nTime)
return;
// find a bucket it is in now
- int nRnd = GetRandInt(vvNew.size());
+ int nRnd = GetRandInt(ADDRMAN_NEW_BUCKET_COUNT);
int nUBucket = -1;
- for (unsigned int n = 0; n < vvNew.size(); n++) {
- int nB = (n + nRnd) % vvNew.size();
- std::set<int>& vNew = vvNew[nB];
- if (vNew.count(nId)) {
+ for (unsigned int n = 0; n < ADDRMAN_NEW_BUCKET_COUNT; n++) {
+ int nB = (n + nRnd) % ADDRMAN_NEW_BUCKET_COUNT;
+ int nBpos = info.GetBucketPosition(nKey, true, nB);
+ if (vvNew[nB][nBpos] == nId) {
nUBucket = nB;
break;
}
@@ -300,7 +242,7 @@ void CAddrMan::Good_(const CService& addr, int64_t nTime)
LogPrint("addrman", "Moving %s to tried\n", addr.ToString());
// move nId to the tried tables
- MakeTried(info, nId, nUBucket);
+ MakeTried(info, nId);
}
bool CAddrMan::Add_(const CAddress& addr, const CNetAddr& source, int64_t nTimePenalty)
@@ -348,12 +290,25 @@ bool CAddrMan::Add_(const CAddress& addr, const CNetAddr& source, int64_t nTimeP
}
int nUBucket = pinfo->GetNewBucket(nKey, source);
- std::set<int>& vNew = vvNew[nUBucket];
- if (!vNew.count(nId)) {
- pinfo->nRefCount++;
- if (vNew.size() == ADDRMAN_NEW_BUCKET_SIZE)
- ShrinkNew(nUBucket);
- vvNew[nUBucket].insert(nId);
+ int nUBucketPos = pinfo->GetBucketPosition(nKey, true, nUBucket);
+ if (vvNew[nUBucket][nUBucketPos] != nId) {
+ bool fInsert = vvNew[nUBucket][nUBucketPos] == -1;
+ if (!fInsert) {
+ CAddrInfo& infoExisting = mapInfo[vvNew[nUBucket][nUBucketPos]];
+ if (infoExisting.IsTerrible() || (infoExisting.nRefCount > 1 && pinfo->nRefCount == 0)) {
+ // Overwrite the existing new table entry.
+ fInsert = true;
+ }
+ }
+ if (fInsert) {
+ ClearNew(nUBucket, nUBucketPos);
+ pinfo->nRefCount++;
+ vvNew[nUBucket][nUBucketPos] = nId;
+ } else {
+ if (pinfo->nRefCount == 0) {
+ Delete(nId);
+ }
+ }
}
return fNew;
}
@@ -377,24 +332,23 @@ void CAddrMan::Attempt_(const CService& addr, int64_t nTime)
info.nAttempts++;
}
-CAddress CAddrMan::Select_(int nUnkBias)
+CAddress CAddrMan::Select_()
{
if (size() == 0)
return CAddress();
- double nCorTried = sqrt(nTried) * (100.0 - nUnkBias);
- double nCorNew = sqrt(nNew) * nUnkBias;
- if ((nCorTried + nCorNew) * GetRandInt(1 << 30) / (1 << 30) < nCorTried) {
+ // Use a 50% chance for choosing between tried and new table entries.
+ if (nTried > 0 && (nNew == 0 || GetRandInt(2) == 0)) {
// use a tried node
double fChanceFactor = 1.0;
while (1) {
- int nKBucket = GetRandInt(vvTried.size());
- std::vector<int>& vTried = vvTried[nKBucket];
- if (vTried.size() == 0)
+ int nKBucket = GetRandInt(ADDRMAN_TRIED_BUCKET_COUNT);
+ int nKBucketPos = GetRandInt(ADDRMAN_BUCKET_SIZE);
+ if (vvTried[nKBucket][nKBucketPos] == -1)
continue;
- int nPos = GetRandInt(vTried.size());
- assert(mapInfo.count(vTried[nPos]) == 1);
- CAddrInfo& info = mapInfo[vTried[nPos]];
+ int nId = vvTried[nKBucket][nKBucketPos];
+ assert(mapInfo.count(nId) == 1);
+ CAddrInfo& info = mapInfo[nId];
if (GetRandInt(1 << 30) < fChanceFactor * info.GetChance() * (1 << 30))
return info;
fChanceFactor *= 1.2;
@@ -403,16 +357,13 @@ CAddress CAddrMan::Select_(int nUnkBias)
// use a new node
double fChanceFactor = 1.0;
while (1) {
- int nUBucket = GetRandInt(vvNew.size());
- std::set<int>& vNew = vvNew[nUBucket];
- if (vNew.size() == 0)
+ int nUBucket = GetRandInt(ADDRMAN_NEW_BUCKET_COUNT);
+ int nUBucketPos = GetRandInt(ADDRMAN_BUCKET_SIZE);
+ if (vvNew[nUBucket][nUBucketPos] == -1)
continue;
- int nPos = GetRandInt(vNew.size());
- std::set<int>::iterator it = vNew.begin();
- while (nPos--)
- it++;
- assert(mapInfo.count(*it) == 1);
- CAddrInfo& info = mapInfo[*it];
+ int nId = vvNew[nUBucket][nUBucketPos];
+ assert(mapInfo.count(nId) == 1);
+ CAddrInfo& info = mapInfo[nId];
if (GetRandInt(1 << 30) < fChanceFactor * info.GetChance() * (1 << 30))
return info;
fChanceFactor *= 1.2;
@@ -460,22 +411,30 @@ int CAddrMan::Check_()
if (mapNew.size() != nNew)
return -10;
- for (int n = 0; n < vvTried.size(); n++) {
- std::vector<int>& vTried = vvTried[n];
- for (std::vector<int>::iterator it = vTried.begin(); it != vTried.end(); it++) {
- if (!setTried.count(*it))
- return -11;
- setTried.erase(*it);
+ for (int n = 0; n < ADDRMAN_TRIED_BUCKET_COUNT; n++) {
+ for (int i = 0; i < ADDRMAN_BUCKET_SIZE; i++) {
+ if (vvTried[n][i] != -1) {
+ if (!setTried.count(vvTried[n][i]))
+ return -11;
+ if (mapInfo[vvTried[n][i]].GetTriedBucket(nKey) != n)
+ return -17;
+ if (mapInfo[vvTried[n][i]].GetBucketPosition(nKey, false, n) != i)
+ return -18;
+ setTried.erase(vvTried[n][i]);
+ }
}
}
- for (int n = 0; n < vvNew.size(); n++) {
- std::set<int>& vNew = vvNew[n];
- for (std::set<int>::iterator it = vNew.begin(); it != vNew.end(); it++) {
- if (!mapNew.count(*it))
- return -12;
- if (--mapNew[*it] == 0)
- mapNew.erase(*it);
+ for (int n = 0; n < ADDRMAN_NEW_BUCKET_COUNT; n++) {
+ for (int i = 0; i < ADDRMAN_BUCKET_SIZE; i++) {
+ if (vvNew[n][i] != -1) {
+ if (!mapNew.count(vvNew[n][i]))
+ return -12;
+ if (mapInfo[vvNew[n][i]].GetBucketPosition(nKey, true, n) != i)
+ return -19;
+ if (--mapNew[vvNew[n][i]] == 0)
+ mapNew.erase(vvNew[n][i]);
+ }
}
}
@@ -483,6 +442,8 @@ int CAddrMan::Check_()
return -13;
if (mapNew.size())
return -15;
+ if (nKey.IsNull())
+ return -16;
return 0;
}
diff --git a/src/addrman.h b/src/addrman.h
index d47217683c..8116d0b763 100644
--- a/src/addrman.h
+++ b/src/addrman.h
@@ -79,17 +79,20 @@ public:
}
//! Calculate in which "tried" bucket this entry belongs
- int GetTriedBucket(const std::vector<unsigned char> &nKey) const;
+ int GetTriedBucket(const uint256 &nKey) const;
//! Calculate in which "new" bucket this entry belongs, given a certain source
- int GetNewBucket(const std::vector<unsigned char> &nKey, const CNetAddr& src) const;
+ int GetNewBucket(const uint256 &nKey, const CNetAddr& src) const;
//! Calculate in which "new" bucket this entry belongs, using its default source
- int GetNewBucket(const std::vector<unsigned char> &nKey) const
+ int GetNewBucket(const uint256 &nKey) const
{
return GetNewBucket(nKey, source);
}
+ //! Calculate in which position of a bucket to store this entry.
+ int GetBucketPosition(const uint256 &nKey, bool fNew, int nBucket) const;
+
//! Determine whether the statistics about this entry are bad enough so that it can just be deleted
bool IsTerrible(int64_t nNow = GetAdjustedTime()) const;
@@ -106,15 +109,15 @@ public:
*
* To that end:
* * Addresses are organized into buckets.
- * * Address that have not yet been tried go into 256 "new" buckets.
- * * Based on the address range (/16 for IPv4) of source of the information, 32 buckets are selected at random
+ * * Address that have not yet been tried go into 1024 "new" buckets.
+ * * Based on the address range (/16 for IPv4) of source of the information, 64 buckets are selected at random
* * The actual bucket is chosen from one of these, based on the range the address itself is located.
- * * One single address can occur in up to 4 different buckets, to increase selection chances for addresses that
+ * * One single address can occur in up to 8 different buckets, to increase selection chances for addresses that
* are seen frequently. The chance for increasing this multiplicity decreases exponentially.
* * When adding a new address to a full bucket, a randomly chosen entry (with a bias favoring less recently seen
* ones) is removed from it first.
- * * Addresses of nodes that are known to be accessible go into 64 "tried" buckets.
- * * Each address range selects at random 4 of these buckets.
+ * * Addresses of nodes that are known to be accessible go into 256 "tried" buckets.
+ * * Each address range selects at random 8 of these buckets.
* * The actual bucket is chosen from one of these, based on the full address.
* * When adding a new good address to a full bucket, a randomly chosen entry (with a bias favoring less recently
* tried ones) is evicted from it, back to the "new" buckets.
@@ -125,28 +128,22 @@ public:
*/
//! total number of buckets for tried addresses
-#define ADDRMAN_TRIED_BUCKET_COUNT 64
-
-//! maximum allowed number of entries in buckets for tried addresses
-#define ADDRMAN_TRIED_BUCKET_SIZE 64
+#define ADDRMAN_TRIED_BUCKET_COUNT 256
//! total number of buckets for new addresses
-#define ADDRMAN_NEW_BUCKET_COUNT 256
+#define ADDRMAN_NEW_BUCKET_COUNT 1024
-//! maximum allowed number of entries in buckets for new addresses
-#define ADDRMAN_NEW_BUCKET_SIZE 64
+//! maximum allowed number of entries in buckets for new and tried addresses
+#define ADDRMAN_BUCKET_SIZE 64
//! over how many buckets entries with tried addresses from a single group (/16 for IPv4) are spread
-#define ADDRMAN_TRIED_BUCKETS_PER_GROUP 4
+#define ADDRMAN_TRIED_BUCKETS_PER_GROUP 8
//! over how many buckets entries with new addresses originating from a single group are spread
-#define ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP 32
+#define ADDRMAN_NEW_BUCKETS_PER_SOURCE_GROUP 64
//! in how many buckets for entries with new addresses a single address may occur
-#define ADDRMAN_NEW_BUCKETS_PER_ADDRESS 4
-
-//! how many entries in a bucket with tried addresses are inspected, when selecting one to replace
-#define ADDRMAN_TRIED_ENTRIES_INSPECT_ON_EVICT 4
+#define ADDRMAN_NEW_BUCKETS_PER_ADDRESS 8
//! how old addresses can maximally be
#define ADDRMAN_HORIZON_DAYS 30
@@ -176,7 +173,7 @@ private:
mutable CCriticalSection cs;
//! secret key to randomize bucket select with
- std::vector<unsigned char> nKey;
+ uint256 nKey;
//! last used nId
int nIdCount;
@@ -194,13 +191,13 @@ private:
int nTried;
//! list of "tried" buckets
- std::vector<std::vector<int> > vvTried;
+ int vvTried[ADDRMAN_TRIED_BUCKET_COUNT][ADDRMAN_BUCKET_SIZE];
//! number of (unique) "new" entries
int nNew;
//! list of "new" buckets
- std::vector<std::set<int> > vvNew;
+ int vvNew[ADDRMAN_NEW_BUCKET_COUNT][ADDRMAN_BUCKET_SIZE];
protected:
@@ -214,17 +211,14 @@ protected:
//! Swap two elements in vRandom.
void SwapRandom(unsigned int nRandomPos1, unsigned int nRandomPos2);
- //! Return position in given bucket to replace.
- int SelectTried(int nKBucket);
+ //! Move an entry from the "new" table(s) to the "tried" table
+ void MakeTried(CAddrInfo& info, int nId);
- //! Remove an element from a "new" bucket.
- //! This is the only place where actual deletions occur.
- //! Elements are never deleted while in the "tried" table, only possibly evicted back to the "new" table.
- int ShrinkNew(int nUBucket);
+ //! Delete an entry. It must not be in tried, and have refcount 0.
+ void Delete(int nId);
- //! Move an entry from the "new" table(s) to the "tried" table
- //! @pre vvUnkown[nOrigin].count(nId) != 0
- void MakeTried(CAddrInfo& info, int nId, int nOrigin);
+ //! Clear a position in a "new" table. This is the only place where entries are actually deleted.
+ void ClearNew(int nUBucket, int nUBucketPos);
//! Mark an entry "good", possibly moving it from "new" to "tried".
void Good_(const CService &addr, int64_t nTime);
@@ -237,7 +231,7 @@ protected:
//! Select an address to connect to.
//! nUnkBias determines how much to favor new addresses over tried ones (min=0, max=100)
- CAddress Select_(int nUnkBias);
+ CAddress Select_();
#ifdef DEBUG_ADDRMAN
//! Perform consistency check. Returns an error code or zero.
@@ -253,17 +247,21 @@ protected:
public:
/**
* serialized format:
- * * version byte (currently 0)
- * * nKey
+ * * version byte (currently 1)
+ * * 0x20 + nKey (serialized as if it were a vector, for backward compatibility)
* * nNew
* * nTried
- * * number of "new" buckets
+ * * number of "new" buckets XOR 2**30
* * all nNew addrinfos in vvNew
* * all nTried addrinfos in vvTried
* * for each bucket:
* * number of elements
* * for each element: index
*
+ * 2**30 is xorred with the number of buckets to make addrman deserializer v0 detect it
+ * as incompatible. This is necessary because it did not check the version number on
+ * deserialization.
+ *
* Notice that vvTried, mapAddr and vVector are never encoded explicitly;
* they are instead reconstructed from the other information.
*
@@ -275,48 +273,53 @@ public:
*
* We don't use ADD_SERIALIZE_METHODS since the serialization and deserialization code has
* very little in common.
- *
*/
template<typename Stream>
void Serialize(Stream &s, int nType, int nVersionDummy) const
{
LOCK(cs);
- unsigned char nVersion = 0;
+ unsigned char nVersion = 1;
s << nVersion;
+ s << ((unsigned char)32);
s << nKey;
s << nNew;
s << nTried;
- int nUBuckets = ADDRMAN_NEW_BUCKET_COUNT;
+ int nUBuckets = ADDRMAN_NEW_BUCKET_COUNT ^ (1 << 30);
s << nUBuckets;
std::map<int, int> mapUnkIds;
int nIds = 0;
for (std::map<int, CAddrInfo>::const_iterator it = mapInfo.begin(); it != mapInfo.end(); it++) {
- if (nIds == nNew) break; // this means nNew was wrong, oh ow
mapUnkIds[(*it).first] = nIds;
const CAddrInfo &info = (*it).second;
if (info.nRefCount) {
+ assert(nIds != nNew); // this means nNew was wrong, oh ow
s << info;
nIds++;
}
}
nIds = 0;
for (std::map<int, CAddrInfo>::const_iterator it = mapInfo.begin(); it != mapInfo.end(); it++) {
- if (nIds == nTried) break; // this means nTried was wrong, oh ow
const CAddrInfo &info = (*it).second;
if (info.fInTried) {
+ assert(nIds != nTried); // this means nTried was wrong, oh ow
s << info;
nIds++;
}
}
- for (std::vector<std::set<int> >::const_iterator it = vvNew.begin(); it != vvNew.end(); it++) {
- const std::set<int> &vNew = (*it);
- int nSize = vNew.size();
+ for (int bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; bucket++) {
+ int nSize = 0;
+ for (int i = 0; i < ADDRMAN_BUCKET_SIZE; i++) {
+ if (vvNew[bucket][i] != -1)
+ nSize++;
+ }
s << nSize;
- for (std::set<int>::const_iterator it2 = vNew.begin(); it2 != vNew.end(); it2++) {
- int nIndex = mapUnkIds[*it2];
- s << nIndex;
+ for (int i = 0; i < ADDRMAN_BUCKET_SIZE; i++) {
+ if (vvNew[bucket][i] != -1) {
+ int nIndex = mapUnkIds[vvNew[bucket][i]];
+ s << nIndex;
+ }
}
}
}
@@ -326,64 +329,97 @@ public:
{
LOCK(cs);
+ Clear();
+
unsigned char nVersion;
s >> nVersion;
+ unsigned char nKeySize;
+ s >> nKeySize;
+ if (nKeySize != 32) throw std::ios_base::failure("Incorrect keysize in addrman deserialization");
s >> nKey;
s >> nNew;
s >> nTried;
-
int nUBuckets = 0;
s >> nUBuckets;
- nIdCount = 0;
- mapInfo.clear();
- mapAddr.clear();
- vRandom.clear();
- vvTried = std::vector<std::vector<int> >(ADDRMAN_TRIED_BUCKET_COUNT, std::vector<int>(0));
- vvNew = std::vector<std::set<int> >(ADDRMAN_NEW_BUCKET_COUNT, std::set<int>());
+ if (nVersion != 0) {
+ nUBuckets ^= (1 << 30);
+ }
+
+ // Deserialize entries from the new table.
for (int n = 0; n < nNew; n++) {
CAddrInfo &info = mapInfo[n];
s >> info;
mapAddr[info] = n;
info.nRandomPos = vRandom.size();
vRandom.push_back(n);
- if (nUBuckets != ADDRMAN_NEW_BUCKET_COUNT) {
- vvNew[info.GetNewBucket(nKey)].insert(n);
- info.nRefCount++;
+ if (nVersion != 1 || nUBuckets != ADDRMAN_NEW_BUCKET_COUNT) {
+ // In case the new table data cannot be used (nVersion unknown, or bucket count wrong),
+ // immediately try to give them a reference based on their primary source address.
+ int nUBucket = info.GetNewBucket(nKey);
+ int nUBucketPos = info.GetBucketPosition(nKey, true, nUBucket);
+ if (vvNew[nUBucket][nUBucketPos] == -1) {
+ vvNew[nUBucket][nUBucketPos] = n;
+ info.nRefCount++;
+ }
}
}
nIdCount = nNew;
+
+ // Deserialize entries from the tried table.
int nLost = 0;
for (int n = 0; n < nTried; n++) {
CAddrInfo info;
s >> info;
- std::vector<int> &vTried = vvTried[info.GetTriedBucket(nKey)];
- if (vTried.size() < ADDRMAN_TRIED_BUCKET_SIZE) {
+ int nKBucket = info.GetTriedBucket(nKey);
+ int nKBucketPos = info.GetBucketPosition(nKey, false, nKBucket);
+ if (vvTried[nKBucket][nKBucketPos] == -1) {
info.nRandomPos = vRandom.size();
info.fInTried = true;
vRandom.push_back(nIdCount);
mapInfo[nIdCount] = info;
mapAddr[info] = nIdCount;
- vTried.push_back(nIdCount);
+ vvTried[nKBucket][nKBucketPos] = nIdCount;
nIdCount++;
} else {
nLost++;
}
}
nTried -= nLost;
- for (int b = 0; b < nUBuckets; b++) {
- std::set<int> &vNew = vvNew[b];
+
+ // Deserialize positions in the new table (if possible).
+ for (int bucket = 0; bucket < nUBuckets; bucket++) {
int nSize = 0;
s >> nSize;
for (int n = 0; n < nSize; n++) {
int nIndex = 0;
s >> nIndex;
- CAddrInfo &info = mapInfo[nIndex];
- if (nUBuckets == ADDRMAN_NEW_BUCKET_COUNT && info.nRefCount < ADDRMAN_NEW_BUCKETS_PER_ADDRESS) {
- info.nRefCount++;
- vNew.insert(nIndex);
+ if (nIndex >= 0 && nIndex < nNew) {
+ CAddrInfo &info = mapInfo[nIndex];
+ int nUBucketPos = info.GetBucketPosition(nKey, true, bucket);
+ if (nVersion == 1 && nUBuckets == ADDRMAN_NEW_BUCKET_COUNT && vvNew[bucket][nUBucketPos] == -1 && info.nRefCount < ADDRMAN_NEW_BUCKETS_PER_ADDRESS) {
+ info.nRefCount++;
+ vvNew[bucket][nUBucketPos] = nIndex;
+ }
}
}
}
+
+ // Prune new entries with refcount 0 (as a result of collisions).
+ int nLostUnk = 0;
+ for (std::map<int, CAddrInfo>::const_iterator it = mapInfo.begin(); it != mapInfo.end(); ) {
+ if (it->second.fInTried == false && it->second.nRefCount == 0) {
+ std::map<int, CAddrInfo>::const_iterator itCopy = it++;
+ Delete(itCopy->first);
+ nLostUnk++;
+ } else {
+ it++;
+ }
+ }
+ if (nLost + nLostUnk > 0) {
+ LogPrint("addrman", "addrman lost %i new and %i tried addresses due to collisions\n", nLostUnk, nLost);
+ }
+
+ Check();
}
unsigned int GetSerializeSize(int nType, int nVersion) const
@@ -391,14 +427,34 @@ public:
return (CSizeComputer(nType, nVersion) << *this).size();
}
- CAddrMan() : vRandom(0), vvTried(ADDRMAN_TRIED_BUCKET_COUNT, std::vector<int>(0)), vvNew(ADDRMAN_NEW_BUCKET_COUNT, std::set<int>())
+ void Clear()
{
- nKey.resize(32);
- GetRandBytes(&nKey[0], 32);
+ std::vector<int>().swap(vRandom);
+ nKey = GetRandHash();
+ for (size_t bucket = 0; bucket < ADDRMAN_NEW_BUCKET_COUNT; bucket++) {
+ for (size_t entry = 0; entry < ADDRMAN_BUCKET_SIZE; entry++) {
+ vvNew[bucket][entry] = -1;
+ }
+ }
+ for (size_t bucket = 0; bucket < ADDRMAN_TRIED_BUCKET_COUNT; bucket++) {
+ for (size_t entry = 0; entry < ADDRMAN_BUCKET_SIZE; entry++) {
+ vvTried[bucket][entry] = -1;
+ }
+ }
+
+ nIdCount = 0;
+ nTried = 0;
+ nNew = 0;
+ }
- nIdCount = 0;
- nTried = 0;
- nNew = 0;
+ CAddrMan()
+ {
+ Clear();
+ }
+
+ ~CAddrMan()
+ {
+ nKey.SetNull();
}
//! Return the number of (unique) addresses in all tables.
@@ -477,13 +533,13 @@ public:
* Choose an address to connect to.
* nUnkBias determines how much "new" entries are favored over "tried" ones (0-100).
*/
- CAddress Select(int nUnkBias = 50)
+ CAddress Select()
{
CAddress addrRet;
{
LOCK(cs);
Check();
- addrRet = Select_(nUnkBias);
+ addrRet = Select_();
Check();
}
return addrRet;
diff --git a/src/chainparams.cpp b/src/chainparams.cpp
index 3e20d9f8f2..8633d51b2f 100644
--- a/src/chainparams.cpp
+++ b/src/chainparams.cpp
@@ -167,7 +167,7 @@ public:
fRequireRPCPassword = true;
fMiningRequiresPeers = true;
- fDefaultCheckMemPool = false;
+ fDefaultConsistencyChecks = false;
fRequireStandard = true;
fMineBlocksOnDemand = false;
fTestnetToBeDeprecatedFieldRPC = false;
@@ -222,7 +222,7 @@ public:
fRequireRPCPassword = true;
fMiningRequiresPeers = true;
- fDefaultCheckMemPool = false;
+ fDefaultConsistencyChecks = false;
fRequireStandard = false;
fMineBlocksOnDemand = false;
fTestnetToBeDeprecatedFieldRPC = true;
@@ -263,7 +263,7 @@ public:
fRequireRPCPassword = false;
fMiningRequiresPeers = false;
- fDefaultCheckMemPool = true;
+ fDefaultConsistencyChecks = true;
fRequireStandard = false;
fMineBlocksOnDemand = true;
fTestnetToBeDeprecatedFieldRPC = false;
diff --git a/src/chainparams.h b/src/chainparams.h
index aa2ec1e301..d0613beb45 100644
--- a/src/chainparams.h
+++ b/src/chainparams.h
@@ -57,8 +57,8 @@ public:
bool RequireRPCPassword() const { return fRequireRPCPassword; }
/** Make miner wait to have peers to avoid wasting work */
bool MiningRequiresPeers() const { return fMiningRequiresPeers; }
- /** Default value for -checkmempool argument */
- bool DefaultCheckMemPool() const { return fDefaultCheckMemPool; }
+ /** Default value for -checkmempool and -checkblockindex argument */
+ bool DefaultConsistencyChecks() const { return fDefaultConsistencyChecks; }
/** Allow mining of a min-difficulty block */
bool AllowMinDifficultyBlocks() const { return consensus.fPowAllowMinDifficultyBlocks; }
/** Make standard checks */
@@ -92,7 +92,7 @@ protected:
std::vector<CAddress> vFixedSeeds;
bool fRequireRPCPassword;
bool fMiningRequiresPeers;
- bool fDefaultCheckMemPool;
+ bool fDefaultConsistencyChecks;
bool fRequireStandard;
bool fMineBlocksOnDemand;
bool fTestnetToBeDeprecatedFieldRPC;
diff --git a/src/init.cpp b/src/init.cpp
index 2a13af6690..d6957ebdbc 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -697,8 +697,9 @@ bool AppInit2(boost::thread_group& threadGroup)
if (GetBoolArg("-benchmark", false))
InitWarning(_("Warning: Unsupported argument -benchmark ignored, use -debug=bench."));
- // Checkmempool defaults to true in regtest mode
- mempool.setSanityCheck(GetBoolArg("-checkmempool", Params().DefaultCheckMemPool()));
+ // Checkmempool and checkblockindex default to true in regtest mode
+ mempool.setSanityCheck(GetBoolArg("-checkmempool", Params().DefaultConsistencyChecks()));
+ fCheckBlockIndex = GetBoolArg("-checkblockindex", Params().DefaultConsistencyChecks());
Checkpoints::fEnabled = GetBoolArg("-checkpoints", true);
// -par=0 means autodetect, but nScriptCheckThreads==0 means no concurrency
diff --git a/src/main.cpp b/src/main.cpp
index 1d78eedc1a..011016204e 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -53,6 +53,7 @@ bool fImporting = false;
bool fReindex = false;
bool fTxIndex = false;
bool fIsBareMultisigStd = true;
+bool fCheckBlockIndex = false;
unsigned int nCoinCacheSize = 5000;
/** Fees smaller than this (in satoshi) are considered zero fee (for relaying and mining) */
@@ -74,6 +75,7 @@ void EraseOrphansFor(NodeId peer);
* and going backwards.
*/
static bool IsSuperMajority(int minVersion, const CBlockIndex* pstart, unsigned int nRequired);
+static void CheckBlockIndex();
/** Constant stuff for coinbase transactions we create: */
CScript COINBASE_FLAGS;
@@ -85,7 +87,7 @@ namespace {
struct CBlockIndexWorkComparator
{
- bool operator()(CBlockIndex *pa, CBlockIndex *pb) {
+ bool operator()(CBlockIndex *pa, CBlockIndex *pb) const {
// First sort by most total work, ...
if (pa->nChainWork > pb->nChainWork) return false;
if (pa->nChainWork < pb->nChainWork) return true;
@@ -107,8 +109,8 @@ namespace {
CBlockIndex *pindexBestInvalid;
/**
- * The set of all CBlockIndex entries with BLOCK_VALID_TRANSACTIONS or better that are at least
- * as good as our current tip. Entries may be failed, though.
+ * The set of all CBlockIndex entries with BLOCK_VALID_TRANSACTIONS (for itself and all ancestors) and
+ * as good as our current tip or better. Entries may be failed, though.
*/
set<CBlockIndex*, CBlockIndexWorkComparator> setBlockIndexCandidates;
/** Number of nodes with fSyncStarted. */
@@ -2226,6 +2228,7 @@ bool ActivateBestChain(CValidationState &state, CBlock *pblock) {
uiInterface.NotifyBlockTip(hashNewTip);
}
} while(pindexMostWork != chainActive.Tip());
+ CheckBlockIndex();
// Write changes periodically to disk, after relay.
if (!FlushStateToDisk(state, FLUSH_STATE_PERIODIC)) {
@@ -2362,7 +2365,9 @@ bool ReceivedBlockTransactions(const CBlock &block, CValidationState& state, CBl
CBlockIndex *pindex = queue.front();
queue.pop_front();
pindex->nChainTx = (pindex->pprev ? pindex->pprev->nChainTx : 0) + pindex->nTx;
- setBlockIndexCandidates.insert(pindex);
+ if (chainActive.Tip() == NULL || !setBlockIndexCandidates.value_comp()(pindex, chainActive.Tip())) {
+ setBlockIndexCandidates.insert(pindex);
+ }
std::pair<std::multimap<CBlockIndex*, CBlockIndex*>::iterator, std::multimap<CBlockIndex*, CBlockIndex*>::iterator> range = mapBlocksUnlinked.equal_range(pindex);
while (range.first != range.second) {
std::multimap<CBlockIndex*, CBlockIndex*>::iterator it = range.first;
@@ -2725,6 +2730,7 @@ bool ProcessNewBlock(CValidationState &state, CNode* pfrom, CBlock* pblock, CDis
if (pindex && pfrom) {
mapBlockSource[pindex->GetBlockHash()] = pfrom->GetId();
}
+ CheckBlockIndex();
if (!ret)
return error("%s: AcceptBlock FAILED", __func__);
}
@@ -3213,6 +3219,136 @@ bool LoadExternalBlockFile(FILE* fileIn, CDiskBlockPos *dbp)
return nLoaded > 0;
}
+void static CheckBlockIndex()
+{
+ if (!fCheckBlockIndex) {
+ return;
+ }
+
+ LOCK(cs_main);
+
+ // Build forward-pointing map of the entire block tree.
+ std::multimap<CBlockIndex*,CBlockIndex*> forward;
+ for (BlockMap::iterator it = mapBlockIndex.begin(); it != mapBlockIndex.end(); it++) {
+ forward.insert(std::make_pair(it->second->pprev, it->second));
+ }
+
+ assert(forward.size() == mapBlockIndex.size());
+
+ std::pair<std::multimap<CBlockIndex*,CBlockIndex*>::iterator,std::multimap<CBlockIndex*,CBlockIndex*>::iterator> rangeGenesis = forward.equal_range(NULL);
+ CBlockIndex *pindex = rangeGenesis.first->second;
+ rangeGenesis.first++;
+ assert(rangeGenesis.first == rangeGenesis.second); // There is only one index entry with parent NULL.
+
+ // Iterate over the entire block tree, using depth-first search.
+ // Along the way, remember whether there are blocks on the path from genesis
+ // block being explored which are the first to have certain properties.
+ size_t nNodes = 0;
+ int nHeight = 0;
+ CBlockIndex* pindexFirstInvalid = NULL; // Oldest ancestor of pindex which is invalid.
+ CBlockIndex* pindexFirstMissing = NULL; // Oldest ancestor of pindex which does not have BLOCK_HAVE_DATA.
+ CBlockIndex* pindexFirstNotTreeValid = NULL; // Oldest ancestor of pindex which does not have BLOCK_VALID_TREE (regardless of being valid or not).
+ CBlockIndex* pindexFirstNotChainValid = NULL; // Oldest ancestor of pindex which does not have BLOCK_VALID_CHAIN (regardless of being valid or not).
+ CBlockIndex* pindexFirstNotScriptsValid = NULL; // Oldest ancestor of pindex which does not have BLOCK_VALID_SCRIPTS (regardless of being valid or not).
+ while (pindex != NULL) {
+ nNodes++;
+ if (pindexFirstInvalid == NULL && pindex->nStatus & BLOCK_FAILED_VALID) pindexFirstInvalid = pindex;
+ if (pindexFirstMissing == NULL && !(pindex->nStatus & BLOCK_HAVE_DATA)) pindexFirstMissing = pindex;
+ if (pindex->pprev != NULL && pindexFirstNotTreeValid == NULL && (pindex->nStatus & BLOCK_VALID_MASK) < BLOCK_VALID_TREE) pindexFirstNotTreeValid = pindex;
+ if (pindex->pprev != NULL && pindexFirstNotChainValid == NULL && (pindex->nStatus & BLOCK_VALID_MASK) < BLOCK_VALID_CHAIN) pindexFirstNotChainValid = pindex;
+ if (pindex->pprev != NULL && pindexFirstNotScriptsValid == NULL && (pindex->nStatus & BLOCK_VALID_MASK) < BLOCK_VALID_SCRIPTS) pindexFirstNotScriptsValid = pindex;
+
+ // Begin: actual consistency checks.
+ if (pindex->pprev == NULL) {
+ // Genesis block checks.
+ assert(pindex->GetBlockHash() == Params().HashGenesisBlock()); // Genesis block's hash must match.
+ assert(pindex == chainActive.Genesis()); // The current active chain's genesis block must be this block.
+ }
+ assert((pindexFirstMissing != NULL) == (pindex->nChainTx == 0)); // nChainTx == 0 is used to signal that all parent block's transaction data is available.
+ assert(pindex->nHeight == nHeight); // nHeight must be consistent.
+ assert(pindex->pprev == NULL || pindex->nChainWork >= pindex->pprev->nChainWork); // For every block except the genesis block, the chainwork must be larger than the parent's.
+ assert(nHeight < 2 || (pindex->pskip && (pindex->pskip->nHeight < nHeight))); // The pskip pointer must point back for all but the first 2 blocks.
+ assert(pindexFirstNotTreeValid == NULL); // All mapBlockIndex entries must at least be TREE valid
+ if ((pindex->nStatus & BLOCK_VALID_MASK) >= BLOCK_VALID_TREE) assert(pindexFirstNotTreeValid == NULL); // TREE valid implies all parents are TREE valid
+ if ((pindex->nStatus & BLOCK_VALID_MASK) >= BLOCK_VALID_CHAIN) assert(pindexFirstNotChainValid == NULL); // CHAIN valid implies all parents are CHAIN valid
+ if ((pindex->nStatus & BLOCK_VALID_MASK) >= BLOCK_VALID_SCRIPTS) assert(pindexFirstNotScriptsValid == NULL); // SCRIPTS valid implies all parents are SCRIPTS valid
+ if (pindexFirstInvalid == NULL) {
+ // Checks for not-invalid blocks.
+ assert((pindex->nStatus & BLOCK_FAILED_MASK) == 0); // The failed mask cannot be set for blocks without invalid parents.
+ }
+ if (!CBlockIndexWorkComparator()(pindex, chainActive.Tip()) && pindexFirstMissing == NULL) {
+ if (pindexFirstInvalid == NULL) { // If this block sorts at least as good as the current tip and is valid, it must be in setBlockIndexCandidates.
+ assert(setBlockIndexCandidates.count(pindex));
+ }
+ } else { // If this block sorts worse than the current tip, it cannot be in setBlockIndexCandidates.
+ assert(setBlockIndexCandidates.count(pindex) == 0);
+ }
+ // Check whether this block is in mapBlocksUnlinked.
+ std::pair<std::multimap<CBlockIndex*,CBlockIndex*>::iterator,std::multimap<CBlockIndex*,CBlockIndex*>::iterator> rangeUnlinked = mapBlocksUnlinked.equal_range(pindex->pprev);
+ bool foundInUnlinked = false;
+ while (rangeUnlinked.first != rangeUnlinked.second) {
+ assert(rangeUnlinked.first->first == pindex->pprev);
+ if (rangeUnlinked.first->second == pindex) {
+ foundInUnlinked = true;
+ break;
+ }
+ rangeUnlinked.first++;
+ }
+ if (pindex->pprev && pindex->nStatus & BLOCK_HAVE_DATA && pindexFirstMissing != NULL) {
+ if (pindexFirstInvalid == NULL) { // If this block has block data available, some parent doesn't, and has no invalid parents, it must be in mapBlocksUnlinked.
+ assert(foundInUnlinked);
+ }
+ } else { // If this block does not have block data available, or all parents do, it cannot be in mapBlocksUnlinked.
+ assert(!foundInUnlinked);
+ }
+ // assert(pindex->GetBlockHash() == pindex->GetBlockHeader().GetHash()); // Perhaps too slow
+ // End: actual consistency checks.
+
+ // Try descending into the first subnode.
+ std::pair<std::multimap<CBlockIndex*,CBlockIndex*>::iterator,std::multimap<CBlockIndex*,CBlockIndex*>::iterator> range = forward.equal_range(pindex);
+ if (range.first != range.second) {
+ // A subnode was found.
+ pindex = range.first->second;
+ nHeight++;
+ continue;
+ }
+ // This is a leaf node.
+ // Move upwards until we reach a node of which we have not yet visited the last child.
+ while (pindex) {
+ // We are going to either move to a parent or a sibling of pindex.
+ // If pindex was the first with a certain property, unset the corresponding variable.
+ if (pindex == pindexFirstInvalid) pindexFirstInvalid = NULL;
+ if (pindex == pindexFirstMissing) pindexFirstMissing = NULL;
+ if (pindex == pindexFirstNotTreeValid) pindexFirstNotTreeValid = NULL;
+ if (pindex == pindexFirstNotChainValid) pindexFirstNotChainValid = NULL;
+ if (pindex == pindexFirstNotScriptsValid) pindexFirstNotScriptsValid = NULL;
+ // Find our parent.
+ CBlockIndex* pindexPar = pindex->pprev;
+ // Find which child we just visited.
+ std::pair<std::multimap<CBlockIndex*,CBlockIndex*>::iterator,std::multimap<CBlockIndex*,CBlockIndex*>::iterator> rangePar = forward.equal_range(pindexPar);
+ while (rangePar.first->second != pindex) {
+ assert(rangePar.first != rangePar.second); // Our parent must have at least the node we're coming from as child.
+ rangePar.first++;
+ }
+ // Proceed to the next one.
+ rangePar.first++;
+ if (rangePar.first != rangePar.second) {
+ // Move to the sibling.
+ pindex = rangePar.first->second;
+ break;
+ } else {
+ // Move up further.
+ pindex = pindexPar;
+ nHeight--;
+ continue;
+ }
+ }
+ }
+
+ // Check that we actually traversed the entire map.
+ assert(nNodes == forward.size());
+}
+
//////////////////////////////////////////////////////////////////////////////
//
// CAlert
@@ -3971,6 +4107,8 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv,
LogPrint("net", "more getheaders (%d) to end to peer=%d (startheight:%d)\n", pindexLast->nHeight, pfrom->id, pfrom->nStartingHeight);
pfrom->PushMessage("getheaders", chainActive.GetLocator(pindexLast), uint256());
}
+
+ CheckBlockIndex();
}
else if (strCommand == "block" && !fImporting && !fReindex) // Ignore blocks received while importing
@@ -4475,7 +4613,7 @@ bool SendMessages(CNode* pto, bool fSendTrickle)
// transactions become unconfirmed and spams other nodes.
if (!fReindex && !fImporting && !IsInitialBlockDownload())
{
- GetMainSignals().Broadcast();
+ GetMainSignals().Broadcast(nTimeBestReceived);
}
//
diff --git a/src/main.h b/src/main.h
index ac73b242f1..bb6fd6f204 100644
--- a/src/main.h
+++ b/src/main.h
@@ -115,7 +115,6 @@ extern BlockMap mapBlockIndex;
extern uint64_t nLastBlockTx;
extern uint64_t nLastBlockSize;
extern const std::string strMessageMagic;
-extern int64_t nTimeBestReceived;
extern CWaitableCriticalSection csBestBlock;
extern CConditionVariable cvBlockChange;
extern bool fImporting;
@@ -123,6 +122,7 @@ extern bool fReindex;
extern int nScriptCheckThreads;
extern bool fTxIndex;
extern bool fIsBareMultisigStd;
+extern bool fCheckBlockIndex;
extern unsigned int nCoinCacheSize;
extern CFeeRate minRelayTxFee;
diff --git a/src/net.cpp b/src/net.cpp
index 11c2cd07d9..48e6367f20 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -1221,8 +1221,7 @@ void ThreadOpenConnections()
int nTries = 0;
while (true)
{
- // use an nUnkBias between 10 (no outgoing connections) and 90 (8 outgoing connections)
- CAddress addr = addrman.Select(10 + min(nOutbound,8)*10);
+ CAddress addr = addrman.Select();
// if we selected an invalid address, restart
if (!addr.IsValid() || setConnected.count(addr.GetGroup()) || IsLocal(addr))
diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp
index 58c005ae75..198dd1fdf9 100644
--- a/src/qt/bitcoingui.cpp
+++ b/src/qt/bitcoingui.cpp
@@ -859,18 +859,18 @@ void BitcoinGUI::closeEvent(QCloseEvent *event)
}
#ifdef ENABLE_WALLET
-void BitcoinGUI::incomingTransaction(const QString& date, int unit, const CAmount& amount, const QString& type, const QString& address)
+void BitcoinGUI::incomingTransaction(const QString& date, int unit, const CAmount& amount, const QString& type, const QString& address, const QString& label)
{
// On new transaction, make an info balloon
+ QString msg = tr("Date: %1\n").arg(date) +
+ tr("Amount: %1\n").arg(BitcoinUnits::formatWithUnit(unit, amount, true)) +
+ tr("Type: %1\n").arg(type);
+ if (!label.isEmpty())
+ msg += tr("Label: %1\n").arg(label);
+ else if (!address.isEmpty())
+ msg += tr("Address: %1\n").arg(address);
message((amount)<0 ? tr("Sent transaction") : tr("Incoming transaction"),
- tr("Date: %1\n"
- "Amount: %2\n"
- "Type: %3\n"
- "Address: %4\n")
- .arg(date)
- .arg(BitcoinUnits::formatWithUnit(unit, amount, true))
- .arg(type)
- .arg(address), CClientUIInterface::MSG_INFORMATION);
+ msg, CClientUIInterface::MSG_INFORMATION);
}
#endif // ENABLE_WALLET
diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h
index 5a289a9046..494541f002 100644
--- a/src/qt/bitcoingui.h
+++ b/src/qt/bitcoingui.h
@@ -165,7 +165,7 @@ public slots:
bool handlePaymentRequest(const SendCoinsRecipient& recipient);
/** Show incoming transaction notification for new transactions. */
- void incomingTransaction(const QString& date, int unit, const CAmount& amount, const QString& type, const QString& address);
+ void incomingTransaction(const QString& date, int unit, const CAmount& amount, const QString& type, const QString& address, const QString& label);
#endif // ENABLE_WALLET
private slots:
diff --git a/src/qt/peertablemodel.cpp b/src/qt/peertablemodel.cpp
index dfb7a623af..220f273d02 100644
--- a/src/qt/peertablemodel.cpp
+++ b/src/qt/peertablemodel.cpp
@@ -115,7 +115,7 @@ PeerTableModel::PeerTableModel(ClientModel *parent) :
clientModel(parent),
timer(0)
{
- columns << tr("Address/Hostname") << tr("User Agent") << tr("Ping Time");
+ columns << tr("Node/Service") << tr("User Agent") << tr("Ping Time");
priv = new PeerTablePriv();
// default to unsorted
priv->sortColumn = -1;
diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp
index ccde44fb29..29c971ec79 100644
--- a/src/qt/rpcconsole.cpp
+++ b/src/qt/rpcconsole.cpp
@@ -357,7 +357,6 @@ void RPCConsole::clear()
ui->messagesWidget->document()->setDefaultStyleSheet(
"table { }"
"td.time { color: #808080; padding-top: 3px; } "
- "td.message { font-family: monospace; font-size: 12px; } " // Todo: Remove fixed font-size
"td.cmd-request { color: #006060; } "
"td.cmd-error { color: red; } "
"b { color: #006060; } "
diff --git a/src/qt/test/test_main.cpp b/src/qt/test/test_main.cpp
index da5f074390..bb768f1325 100644
--- a/src/qt/test/test_main.cpp
+++ b/src/qt/test/test_main.cpp
@@ -6,6 +6,7 @@
#include "config/bitcoin-config.h"
#endif
+#include "util.h"
#include "uritests.h"
#ifdef ENABLE_WALLET
@@ -27,6 +28,7 @@ Q_IMPORT_PLUGIN(qkrcodecs)
// This is all you need to run all the tests
int main(int argc, char *argv[])
{
+ SetupEnvironment();
bool fInvalid = false;
// Don't remove this, it's needed to access
diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp
index dff2676b10..34464b4075 100644
--- a/src/qt/transactiontablemodel.cpp
+++ b/src/qt/transactiontablemodel.cpp
@@ -227,7 +227,7 @@ TransactionTableModel::TransactionTableModel(CWallet* wallet, WalletModel *paren
priv(new TransactionTablePriv(wallet, this)),
fProcessingQueuedTransactions(false)
{
- columns << QString() << QString() << tr("Date") << tr("Type") << tr("Address") << BitcoinUnits::getAmountColumnTitle(walletModel->getOptionsModel()->getDisplayUnit());
+ columns << QString() << QString() << tr("Date") << tr("Type") << tr("Label") << BitcoinUnits::getAmountColumnTitle(walletModel->getOptionsModel()->getDisplayUnit());
priv->refreshWallet();
connect(walletModel->getOptionsModel(), SIGNAL(displayUnitChanged(int)), this, SLOT(updateDisplayUnit()));
@@ -626,7 +626,7 @@ QVariant TransactionTableModel::headerData(int section, Qt::Orientation orientat
case Watchonly:
return tr("Whether or not a watch-only address is involved in this transaction.");
case ToAddress:
- return tr("Destination address of transaction.");
+ return tr("User-defined intent/purpose of the transaction.");
case Amount:
return tr("Amount removed from or added to balance.");
}
diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp
index 9d6a060194..956c8b8913 100644
--- a/src/qt/walletview.cpp
+++ b/src/qt/walletview.cpp
@@ -93,7 +93,7 @@ void WalletView::setBitcoinGUI(BitcoinGUI *gui)
connect(this, SIGNAL(encryptionStatusChanged(int)), gui, SLOT(setEncryptionStatus(int)));
// Pass through transaction notifications
- connect(this, SIGNAL(incomingTransaction(QString,int,CAmount,QString,QString)), gui, SLOT(incomingTransaction(QString,int,CAmount,QString,QString)));
+ connect(this, SIGNAL(incomingTransaction(QString,int,CAmount,QString,QString,QString)), gui, SLOT(incomingTransaction(QString,int,CAmount,QString,QString,QString)));
}
}
@@ -149,9 +149,11 @@ void WalletView::processNewTransaction(const QModelIndex& parent, int start, int
QString date = ttm->index(start, TransactionTableModel::Date, parent).data().toString();
qint64 amount = ttm->index(start, TransactionTableModel::Amount, parent).data(Qt::EditRole).toULongLong();
QString type = ttm->index(start, TransactionTableModel::Type, parent).data().toString();
- QString address = ttm->index(start, TransactionTableModel::ToAddress, parent).data().toString();
+ QModelIndex index = ttm->index(start, 0, parent);
+ QString address = ttm->data(index, TransactionTableModel::AddressRole).toString();
+ QString label = ttm->data(index, TransactionTableModel::LabelRole).toString();
- emit incomingTransaction(date, walletModel->getOptionsModel()->getDisplayUnit(), amount, type, address);
+ emit incomingTransaction(date, walletModel->getOptionsModel()->getDisplayUnit(), amount, type, address, label);
}
void WalletView::gotoOverviewPage()
diff --git a/src/qt/walletview.h b/src/qt/walletview.h
index f3d14c065c..1840e21e9c 100644
--- a/src/qt/walletview.h
+++ b/src/qt/walletview.h
@@ -113,7 +113,7 @@ signals:
/** Encryption status of wallet changed */
void encryptionStatusChanged(int status);
/** Notify that a new transaction appeared */
- void incomingTransaction(const QString& date, int unit, const CAmount& amount, const QString& type, const QString& address);
+ void incomingTransaction(const QString& date, int unit, const CAmount& amount, const QString& type, const QString& address, const QString& label);
};
#endif // BITCOIN_QT_WALLETVIEW_H
diff --git a/src/rpcserver.cpp b/src/rpcserver.cpp
index ba71725222..d30fa32eb4 100644
--- a/src/rpcserver.cpp
+++ b/src/rpcserver.cpp
@@ -333,6 +333,9 @@ static const CRPCCommand vRPCCommands[] =
{ "hidden", "invalidateblock", &invalidateblock, true, false },
{ "hidden", "reconsiderblock", &reconsiderblock, true, false },
{ "hidden", "setmocktime", &setmocktime, true, false },
+#ifdef ENABLE_WALLET
+ { "hidden", "resendwallettransactions", &resendwallettransactions, true, true },
+#endif
#ifdef ENABLE_WALLET
/* Wallet */
diff --git a/src/rpcserver.h b/src/rpcserver.h
index f63438ecb8..7011d41fc1 100644
--- a/src/rpcserver.h
+++ b/src/rpcserver.h
@@ -207,6 +207,7 @@ extern json_spirit::Value getwalletinfo(const json_spirit::Array& params, bool f
extern json_spirit::Value getblockchaininfo(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getnetworkinfo(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value setmocktime(const json_spirit::Array& params, bool fHelp);
+extern json_spirit::Value resendwallettransactions(const json_spirit::Array& params, bool fHelp);
extern json_spirit::Value getrawtransaction(const json_spirit::Array& params, bool fHelp); // in rcprawtransaction.cpp
extern json_spirit::Value listunspent(const json_spirit::Array& params, bool fHelp);
diff --git a/src/test/mempool_tests.cpp b/src/test/mempool_tests.cpp
new file mode 100644
index 0000000000..0996e13c48
--- /dev/null
+++ b/src/test/mempool_tests.cpp
@@ -0,0 +1,104 @@
+// Copyright (c) 2011-2014 The Bitcoin Core developers
+// Distributed under the MIT software license, see the accompanying
+// file COPYING or http://www.opensource.org/licenses/mit-license.php.
+
+#include "main.h"
+#include "txmempool.h"
+#include "util.h"
+
+#include "test/test_bitcoin.h"
+
+#include <boost/test/unit_test.hpp>
+#include <list>
+
+BOOST_FIXTURE_TEST_SUITE(mempool_tests, TestingSetup)
+
+BOOST_AUTO_TEST_CASE(MempoolRemoveTest)
+{
+ // Test CTxMemPool::remove functionality
+
+ // Parent transaction with three children,
+ // and three grand-children:
+ CMutableTransaction txParent;
+ txParent.vin.resize(1);
+ txParent.vin[0].scriptSig = CScript() << OP_11;
+ txParent.vout.resize(3);
+ for (int i = 0; i < 3; i++)
+ {
+ txParent.vout[i].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
+ txParent.vout[i].nValue = 33000LL;
+ }
+ CMutableTransaction txChild[3];
+ for (int i = 0; i < 3; i++)
+ {
+ txChild[i].vin.resize(1);
+ txChild[i].vin[0].scriptSig = CScript() << OP_11;
+ txChild[i].vin[0].prevout.hash = txParent.GetHash();
+ txChild[i].vin[0].prevout.n = i;
+ txChild[i].vout.resize(1);
+ txChild[i].vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
+ txChild[i].vout[0].nValue = 11000LL;
+ }
+ CMutableTransaction txGrandChild[3];
+ for (int i = 0; i < 3; i++)
+ {
+ txGrandChild[i].vin.resize(1);
+ txGrandChild[i].vin[0].scriptSig = CScript() << OP_11;
+ txGrandChild[i].vin[0].prevout.hash = txChild[i].GetHash();
+ txGrandChild[i].vin[0].prevout.n = 0;
+ txGrandChild[i].vout.resize(1);
+ txGrandChild[i].vout[0].scriptPubKey = CScript() << OP_11 << OP_EQUAL;
+ txGrandChild[i].vout[0].nValue = 11000LL;
+ }
+
+
+ CTxMemPool testPool(CFeeRate(0));
+ std::list<CTransaction> removed;
+
+ // Nothing in pool, remove should do nothing:
+ testPool.remove(txParent, removed, true);
+ BOOST_CHECK_EQUAL(removed.size(), 0);
+
+ // Just the parent:
+ testPool.addUnchecked(txParent.GetHash(), CTxMemPoolEntry(txParent, 0, 0, 0.0, 1));
+ testPool.remove(txParent, removed, true);
+ BOOST_CHECK_EQUAL(removed.size(), 1);
+ removed.clear();
+
+ // Parent, children, grandchildren:
+ testPool.addUnchecked(txParent.GetHash(), CTxMemPoolEntry(txParent, 0, 0, 0.0, 1));
+ for (int i = 0; i < 3; i++)
+ {
+ testPool.addUnchecked(txChild[i].GetHash(), CTxMemPoolEntry(txChild[i], 0, 0, 0.0, 1));
+ testPool.addUnchecked(txGrandChild[i].GetHash(), CTxMemPoolEntry(txGrandChild[i], 0, 0, 0.0, 1));
+ }
+ // Remove Child[0], GrandChild[0] should be removed:
+ testPool.remove(txChild[0], removed, true);
+ BOOST_CHECK_EQUAL(removed.size(), 2);
+ removed.clear();
+ // ... make sure grandchild and child are gone:
+ testPool.remove(txGrandChild[0], removed, true);
+ BOOST_CHECK_EQUAL(removed.size(), 0);
+ testPool.remove(txChild[0], removed, true);
+ BOOST_CHECK_EQUAL(removed.size(), 0);
+ // Remove parent, all children/grandchildren should go:
+ testPool.remove(txParent, removed, true);
+ BOOST_CHECK_EQUAL(removed.size(), 5);
+ BOOST_CHECK_EQUAL(testPool.size(), 0);
+ removed.clear();
+
+ // Add children and grandchildren, but NOT the parent (simulate the parent being in a block)
+ for (int i = 0; i < 3; i++)
+ {
+ testPool.addUnchecked(txChild[i].GetHash(), CTxMemPoolEntry(txChild[i], 0, 0, 0.0, 1));
+ testPool.addUnchecked(txGrandChild[i].GetHash(), CTxMemPoolEntry(txGrandChild[i], 0, 0, 0.0, 1));
+ }
+ // Now remove the parent, as might happen if a block-re-org occurs but the parent cannot be
+ // put into the mempool (maybe because it is non-standard):
+ testPool.remove(txParent, removed, true);
+ BOOST_CHECK_EQUAL(removed.size(), 6);
+ BOOST_CHECK_EQUAL(testPool.size(), 0);
+ removed.clear();
+}
+
+BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp
index 7d5207b11e..a2cb78c989 100644
--- a/src/test/test_bitcoin.cpp
+++ b/src/test/test_bitcoin.cpp
@@ -28,7 +28,9 @@ extern void noui_connect();
BasicTestingSetup::BasicTestingSetup()
{
+ SetupEnvironment();
fPrintToDebugLog = false; // don't want to write to debug.log file
+ fCheckBlockIndex = true;
SelectParams(CBaseChainParams::MAIN);
}
BasicTestingSetup::~BasicTestingSetup()
diff --git a/src/txdb.h b/src/txdb.h
index 1ce93969d8..86e1c5d831 100644
--- a/src/txdb.h
+++ b/src/txdb.h
@@ -16,7 +16,7 @@
class CBlockFileInfo;
class CBlockIndex;
-class CDiskTxPos;
+struct CDiskTxPos;
class uint256;
//! -dbcache default (MiB)
diff --git a/src/txmempool.cpp b/src/txmempool.cpp
index 0d50660327..85ea3f77b5 100644
--- a/src/txmempool.cpp
+++ b/src/txmempool.cpp
@@ -444,6 +444,18 @@ void CTxMemPool::remove(const CTransaction &origTx, std::list<CTransaction>& rem
LOCK(cs);
std::deque<uint256> txToRemove;
txToRemove.push_back(origTx.GetHash());
+ if (fRecursive && !mapTx.count(origTx.GetHash())) {
+ // If recursively removing but origTx isn't in the mempool
+ // be sure to remove any children that are in the pool. This can
+ // happen during chain re-orgs if origTx isn't re-accepted into
+ // the mempool for any reason.
+ for (unsigned int i = 0; i < origTx.vout.size(); i++) {
+ std::map<COutPoint, CInPoint>::iterator it = mapNextTx.find(COutPoint(origTx.GetHash(), i));
+ if (it == mapNextTx.end())
+ continue;
+ txToRemove.push_back(it->second.ptx->GetHash());
+ }
+ }
while (!txToRemove.empty())
{
uint256 hash = txToRemove.front();
diff --git a/src/util.cpp b/src/util.cpp
index 4192e44ae1..5fef3a40dd 100644
--- a/src/util.cpp
+++ b/src/util.cpp
@@ -723,18 +723,19 @@ void RenameThread(const char* name)
void SetupEnvironment()
{
+ std::locale loc("C");
// On most POSIX systems (e.g. Linux, but not BSD) the environment's locale
// may be invalid, in which case the "C" locale is used as fallback.
#if !defined(WIN32) && !defined(MAC_OSX) && !defined(__FreeBSD__) && !defined(__OpenBSD__)
try {
- std::locale(""); // Raises a runtime error if current locale is invalid
+ loc = std::locale(""); // Raises a runtime error if current locale is invalid
} catch (const std::runtime_error&) {
- std::locale::global(std::locale("C"));
+ setenv("LC_ALL", "C", 1);
}
#endif
// The path locale is lazy initialized and to avoid deinitialization errors
// in multithreading environments, it is set explicitly by the main thread.
- boost::filesystem::path::imbue(std::locale());
+ boost::filesystem::path::imbue(loc);
}
void SetThreadPriority(int nPriority)
diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp
index ae4cd3c592..aa9aefb0de 100644
--- a/src/validationinterface.cpp
+++ b/src/validationinterface.cpp
@@ -18,13 +18,13 @@ void RegisterValidationInterface(CValidationInterface* pwalletIn) {
g_signals.UpdatedTransaction.connect(boost::bind(&CValidationInterface::UpdatedTransaction, pwalletIn, _1));
g_signals.SetBestChain.connect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1));
g_signals.Inventory.connect(boost::bind(&CValidationInterface::Inventory, pwalletIn, _1));
- g_signals.Broadcast.connect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn));
+ g_signals.Broadcast.connect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, _1));
g_signals.BlockChecked.connect(boost::bind(&CValidationInterface::BlockChecked, pwalletIn, _1, _2));
}
void UnregisterValidationInterface(CValidationInterface* pwalletIn) {
g_signals.BlockChecked.disconnect(boost::bind(&CValidationInterface::BlockChecked, pwalletIn, _1, _2));
- g_signals.Broadcast.disconnect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn));
+ g_signals.Broadcast.disconnect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, _1));
g_signals.Inventory.disconnect(boost::bind(&CValidationInterface::Inventory, pwalletIn, _1));
g_signals.SetBestChain.disconnect(boost::bind(&CValidationInterface::SetBestChain, pwalletIn, _1));
g_signals.UpdatedTransaction.disconnect(boost::bind(&CValidationInterface::UpdatedTransaction, pwalletIn, _1));
diff --git a/src/validationinterface.h b/src/validationinterface.h
index b21b6e5782..b4c93d72ca 100644
--- a/src/validationinterface.h
+++ b/src/validationinterface.h
@@ -9,7 +9,7 @@
#include <boost/signals2/signal.hpp>
class CBlock;
-class CBlockLocator;
+struct CBlockLocator;
class CTransaction;
class CValidationInterface;
class CValidationState;
@@ -33,7 +33,7 @@ protected:
virtual void SetBestChain(const CBlockLocator &locator) {};
virtual void UpdatedTransaction(const uint256 &hash) {};
virtual void Inventory(const uint256 &hash) {};
- virtual void ResendWalletTransactions() {};
+ virtual void ResendWalletTransactions(int64_t nBestBlockTime) {};
virtual void BlockChecked(const CBlock&, const CValidationState&) {};
friend void ::RegisterValidationInterface(CValidationInterface*);
friend void ::UnregisterValidationInterface(CValidationInterface*);
@@ -52,7 +52,7 @@ struct CMainSignals {
/** Notifies listeners about an inventory item being seen on the network. */
boost::signals2::signal<void (const uint256 &)> Inventory;
/** Tells listeners to broadcast their data. */
- boost::signals2::signal<void ()> Broadcast;
+ boost::signals2::signal<void (int64_t nBestBlockTime)> Broadcast;
/** Notifies listeners of a block validation result */
boost::signals2::signal<void (const CBlock&, const CValidationState&)> BlockChecked;
};
diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp
index 9318c1b2b1..29f3eda15d 100644
--- a/src/wallet/rpcwallet.cpp
+++ b/src/wallet/rpcwallet.cpp
@@ -2096,3 +2096,25 @@ Value getwalletinfo(const Array& params, bool fHelp)
obj.push_back(Pair("unlocked_until", nWalletUnlockTime));
return obj;
}
+
+Value resendwallettransactions(const Array& params, bool fHelp)
+{
+ if (fHelp || params.size() != 0)
+ throw runtime_error(
+ "resendwallettransactions\n"
+ "Immediately re-broadcast unconfirmed wallet transactions to all peers.\n"
+ "Intended only for testing; the wallet code periodically re-broadcasts\n"
+ "automatically.\n"
+ "Returns array of transaction ids that were re-broadcast.\n"
+ );
+
+ LOCK2(cs_main, pwalletMain->cs_wallet);
+
+ std::vector<uint256> txids = pwalletMain->ResendWalletTransactionsBefore(GetTime());
+ Array result;
+ BOOST_FOREACH(const uint256& txid, txids)
+ {
+ result.push_back(txid.ToString());
+ }
+ return result;
+}
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 09bcda577e..a10123002e 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1114,15 +1114,17 @@ void CWallet::ReacceptWalletTransactions()
}
}
-void CWalletTx::RelayWalletTransaction()
+bool CWalletTx::RelayWalletTransaction()
{
if (!IsCoinBase())
{
if (GetDepthInMainChain() == 0) {
LogPrintf("Relaying wtx %s\n", GetHash().ToString());
RelayTransaction((CTransaction)*this);
+ return true;
}
}
+ return false;
}
set<uint256> CWalletTx::GetConflicts() const
@@ -1324,7 +1326,31 @@ bool CWalletTx::IsTrusted() const
return true;
}
-void CWallet::ResendWalletTransactions()
+std::vector<uint256> CWallet::ResendWalletTransactionsBefore(int64_t nTime)
+{
+ std::vector<uint256> result;
+
+ LOCK(cs_wallet);
+ // Sort them in chronological order
+ multimap<unsigned int, CWalletTx*> mapSorted;
+ BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet)
+ {
+ CWalletTx& wtx = item.second;
+ // Don't rebroadcast if newer than nTime:
+ if (wtx.nTimeReceived > nTime)
+ continue;
+ mapSorted.insert(make_pair(wtx.nTimeReceived, &wtx));
+ }
+ BOOST_FOREACH(PAIRTYPE(const unsigned int, CWalletTx*)& item, mapSorted)
+ {
+ CWalletTx& wtx = *item.second;
+ if (wtx.RelayWalletTransaction())
+ result.push_back(wtx.GetHash());
+ }
+ return result;
+}
+
+void CWallet::ResendWalletTransactions(int64_t nBestBlockTime)
{
// Do this infrequently and randomly to avoid giving away
// that these are our transactions.
@@ -1336,30 +1362,15 @@ void CWallet::ResendWalletTransactions()
return;
// Only do it if there's been a new block since last time
- if (nTimeBestReceived < nLastResend)
+ if (nBestBlockTime < nLastResend)
return;
nLastResend = GetTime();
- // Rebroadcast any of our txes that aren't in a block yet
- LogPrintf("ResendWalletTransactions()\n");
- {
- LOCK(cs_wallet);
- // Sort them in chronological order
- multimap<unsigned int, CWalletTx*> mapSorted;
- BOOST_FOREACH(PAIRTYPE(const uint256, CWalletTx)& item, mapWallet)
- {
- CWalletTx& wtx = item.second;
- // Don't rebroadcast until it's had plenty of time that
- // it should have gotten in already by now.
- if (nTimeBestReceived - (int64_t)wtx.nTimeReceived > 5 * 60)
- mapSorted.insert(make_pair(wtx.nTimeReceived, &wtx));
- }
- BOOST_FOREACH(PAIRTYPE(const unsigned int, CWalletTx*)& item, mapSorted)
- {
- CWalletTx& wtx = *item.second;
- wtx.RelayWalletTransaction();
- }
- }
+ // Rebroadcast unconfirmed txes older than 5 minutes before the last
+ // block was found:
+ std::vector<uint256> relayed = ResendWalletTransactionsBefore(nBestBlockTime-5*60);
+ if (!relayed.empty())
+ LogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__, relayed.size());
}
/** @} */ // end of mapWallet
diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h
index 4a13f02195..6ae1c87b1d 100644
--- a/src/wallet/wallet.h
+++ b/src/wallet/wallet.h
@@ -381,7 +381,7 @@ public:
int64_t GetTxTime() const;
int GetRequestCount() const;
- void RelayWalletTransaction();
+ bool RelayWalletTransaction();
std::set<uint256> GetConflicts() const;
};
@@ -614,7 +614,8 @@ public:
void EraseFromWallet(const uint256 &hash);
int ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate = false);
void ReacceptWalletTransactions();
- void ResendWalletTransactions();
+ void ResendWalletTransactions(int64_t nBestBlockTime);
+ std::vector<uint256> ResendWalletTransactionsBefore(int64_t nTime);
CAmount GetBalance() const;
CAmount GetUnconfirmedBalance() const;
CAmount GetImmatureBalance() const;