diff options
Diffstat (limited to 'src')
72 files changed, 2020 insertions, 901 deletions
diff --git a/src/bitcoinrpc.cpp b/src/bitcoinrpc.cpp index 071d10fd94..29935d8f6c 100644 --- a/src/bitcoinrpc.cpp +++ b/src/bitcoinrpc.cpp @@ -229,8 +229,10 @@ static const CRPCCommand vRPCCommands[] = { "getbestblockhash", &getbestblockhash, true, false, false }, { "getconnectioncount", &getconnectioncount, true, false, false }, { "getpeerinfo", &getpeerinfo, true, false, false }, + { "ping", &ping, true, false, false }, { "addnode", &addnode, true, true, false }, { "getaddednodeinfo", &getaddednodeinfo, true, true, false }, + { "getnettotals", &getnettotals, true, true, false }, { "getdifficulty", &getdifficulty, true, false, false }, { "getnetworkhashps", &getnetworkhashps, true, false, false }, { "getgenerate", &getgenerate, true, false, false }, @@ -880,7 +882,8 @@ void StopRPCThreads() deadlineTimers.clear(); rpc_io_service->stop(); - rpc_worker_group->join_all(); + if (rpc_worker_group != NULL) + rpc_worker_group->join_all(); delete rpc_worker_group; rpc_worker_group = NULL; delete rpc_ssl_context; rpc_ssl_context = NULL; delete rpc_io_service; rpc_io_service = NULL; diff --git a/src/bitcoinrpc.h b/src/bitcoinrpc.h index 62bc7b7238..275369ddd2 100644 --- a/src/bitcoinrpc.h +++ b/src/bitcoinrpc.h @@ -153,8 +153,10 @@ extern void EnsureWalletIsUnlocked(); extern json_spirit::Value getconnectioncount(const json_spirit::Array& params, bool fHelp); // in rpcnet.cpp extern json_spirit::Value getpeerinfo(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value ping(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value addnode(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value getaddednodeinfo(const json_spirit::Array& params, bool fHelp); +extern json_spirit::Value getnettotals(const json_spirit::Array& params, bool fHelp); extern json_spirit::Value dumpprivkey(const json_spirit::Array& params, bool fHelp); // in rpcdump.cpp extern json_spirit::Value importprivkey(const json_spirit::Array& params, bool fHelp); diff --git a/src/chainparams.h b/src/chainparams.h index ce3c14306d..3f99b7eb06 100644 --- a/src/chainparams.h +++ b/src/chainparams.h @@ -68,7 +68,7 @@ public: virtual const vector<CAddress>& FixedSeeds() const = 0; int RPCPort() const { return nRPCPort; } protected: - CChainParams() {}; + CChainParams() {} uint256 hashGenesisBlock; MessageStartChars pchMessageStart; diff --git a/src/core.h b/src/core.h index ce21acd59e..9ee8b2edce 100644 --- a/src/core.h +++ b/src/core.h @@ -661,4 +661,38 @@ public: void print() const; }; + +/** Describes a place in the block chain to another node such that if the + * other node doesn't have the same branch, it can find a recent common trunk. + * The further back it is, the further before the fork it may be. + */ +struct CBlockLocator +{ + std::vector<uint256> vHave; + + CBlockLocator() {} + + CBlockLocator(const std::vector<uint256>& vHaveIn) + { + vHave = vHaveIn; + } + + IMPLEMENT_SERIALIZE + ( + if (!(nType & SER_GETHASH)) + READWRITE(nVersion); + READWRITE(vHave); + ) + + void SetNull() + { + vHave.clear(); + } + + bool IsNull() + { + return vHave.empty(); + } +}; + #endif @@ -16,7 +16,7 @@ #include <db_cxx.h> class CAddrMan; -class CBlockLocator; +struct CBlockLocator; class CDiskBlockIndex; class CMasterKey; class COutPoint; diff --git a/src/init.cpp b/src/init.cpp index 6795de4f8a..2ac610b333 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -116,7 +116,7 @@ void Shutdown() { LOCK(cs_main); if (pwalletMain) - pwalletMain->SetBestChain(CBlockLocator(pindexBest)); + pwalletMain->SetBestChain(chainActive.GetLocator()); if (pblocktree) pblocktree->Flush(); if (pcoinsTip) @@ -185,7 +185,7 @@ std::string HelpMessage() strUsage += " -timeout=<n> " + _("Specify connection timeout in milliseconds (default: 5000)") + "\n"; strUsage += " -proxy=<ip:port> " + _("Connect through socks proxy") + "\n"; strUsage += " -socks=<n> " + _("Select the version of socks proxy to use (4-5, default: 5)") + "\n"; - strUsage += " -tor=<ip:port> " + _("Use proxy to reach tor hidden services (default: same as -proxy)") + "\n"; + strUsage += " -onion=<ip:port> " + _("Use proxy to reach tor hidden services (default: same as -proxy)") + "\n"; strUsage += " -dns " + _("Allow DNS lookups for -addnode, -seednode and -connect") + "\n"; strUsage += " -port=<port> " + _("Listen for connections on <port> (default: 8333 or testnet: 18333)") + "\n"; strUsage += " -maxconnections=<n> " + _("Maintain at most <n> connections to peers (default: 125)") + "\n"; @@ -642,15 +642,20 @@ bool AppInit2(boost::thread_group& threadGroup) fProxy = true; } - // -tor can override normal proxy, -notor disables tor entirely - if (!(mapArgs.count("-tor") && mapArgs["-tor"] == "0") && (fProxy || mapArgs.count("-tor"))) { + // -onion can override normal proxy, -noonion disables tor entirely + // -tor here is a temporary backwards compatibility measure + if (mapArgs.count("-tor")) + printf("Notice: option -tor has been replaced with -onion and will be removed in a later version.\n"); + if (!(mapArgs.count("-onion") && mapArgs["-onion"] == "0") && + !(mapArgs.count("-tor") && mapArgs["-tor"] == "0") && + (fProxy || mapArgs.count("-onion") || mapArgs.count("-tor"))) { CService addrOnion; - if (!mapArgs.count("-tor")) + if (!mapArgs.count("-onion") && !mapArgs.count("-tor")) addrOnion = addrProxy; else - addrOnion = CService(mapArgs["-tor"], 9050); + addrOnion = mapArgs.count("-onion")?CService(mapArgs["-onion"], 9050):CService(mapArgs["-tor"], 9050); if (!addrOnion.IsValid()) - return InitError(strprintf(_("Invalid -tor address: '%s'"), mapArgs["-tor"].c_str())); + return InitError(strprintf(_("Invalid -onion address: '%s'"), mapArgs.count("-onion")?mapArgs["-onion"].c_str():mapArgs["-tor"].c_str())); SetProxy(NET_TOR, addrOnion, 5); SetReachable(NET_TOR); } @@ -766,7 +771,7 @@ bool AppInit2(boost::thread_group& threadGroup) // If the loaded chain has a wrong genesis, bail out immediately // (we're likely using a testnet datadir, or the other way around). - if (!mapBlockIndex.empty() && pindexGenesisBlock == NULL) + if (!mapBlockIndex.empty() && chainActive.Genesis() == NULL) return InitError(_("Incorrect or no genesis block found. Wrong datadir for network?")); // Initialize the block index (no-op if non-empty database was already loaded) @@ -912,7 +917,7 @@ bool AppInit2(boost::thread_group& threadGroup) strErrors << _("Cannot write default address") << "\n"; } - pwalletMain->SetBestChain(CBlockLocator(pindexBest)); + pwalletMain->SetBestChain(chainActive.GetLocator()); } LogPrintf("%s", strErrors.str().c_str()); @@ -920,26 +925,26 @@ bool AppInit2(boost::thread_group& threadGroup) RegisterWallet(pwalletMain); - CBlockIndex *pindexRescan = pindexBest; + CBlockIndex *pindexRescan = chainActive.Tip(); if (GetBoolArg("-rescan", false)) - pindexRescan = pindexGenesisBlock; + pindexRescan = chainActive.Genesis(); else { CWalletDB walletdb(strWalletFile); CBlockLocator locator; if (walletdb.ReadBestBlock(locator)) - pindexRescan = locator.GetBlockIndex(); + pindexRescan = chainActive.FindFork(locator); else - pindexRescan = pindexGenesisBlock; + pindexRescan = chainActive.Genesis(); } - if (pindexBest && pindexBest != pindexRescan) + if (chainActive.Tip() && chainActive.Tip() != pindexRescan) { uiInterface.InitMessage(_("Rescanning...")); - LogPrintf("Rescanning last %i blocks (from block %i)...\n", pindexBest->nHeight - pindexRescan->nHeight, pindexRescan->nHeight); + LogPrintf("Rescanning last %i blocks (from block %i)...\n", chainActive.Height() - pindexRescan->nHeight, pindexRescan->nHeight); nStart = GetTimeMillis(); pwalletMain->ScanForWalletTransactions(pindexRescan, true); LogPrintf(" rescan %15"PRI64d"ms\n", GetTimeMillis() - nStart); - pwalletMain->SetBestChain(CBlockLocator(pindexBest)); + pwalletMain->SetBestChain(chainActive.GetLocator()); nWalletDBUpdated++; } @@ -985,7 +990,7 @@ bool AppInit2(boost::thread_group& threadGroup) //// debug print LogPrintf("mapBlockIndex.size() = %"PRIszu"\n", mapBlockIndex.size()); - LogPrintf("nBestHeight = %d\n", nBestHeight); + LogPrintf("nBestHeight = %d\n", chainActive.Height()); LogPrintf("setKeyPool.size() = %"PRIszu"\n", pwalletMain ? pwalletMain->setKeyPool.size() : 0); LogPrintf("mapWallet.size() = %"PRIszu"\n", pwalletMain ? pwalletMain->mapWallet.size() : 0); LogPrintf("mapAddressBook.size() = %"PRIszu"\n", pwalletMain ? pwalletMain->mapAddressBook.size() : 0); diff --git a/src/key.cpp b/src/key.cpp index 57e1cac58a..996539dca5 100644 --- a/src/key.cpp +++ b/src/key.cpp @@ -166,9 +166,12 @@ public: assert(nSize == nSize2); } - bool SetPrivKey(const CPrivKey &privkey) { + bool SetPrivKey(const CPrivKey &privkey, bool fSkipCheck=false) { const unsigned char* pbegin = &privkey[0]; if (d2i_ECPrivateKey(&pkey, &pbegin, privkey.size())) { + if(fSkipCheck) + return true; + // d2i_ECPrivateKey returns true if parsing succeeds. // This doesn't necessarily mean the key is valid. if (EC_KEY_check_key(pkey)) @@ -411,6 +414,24 @@ bool CKey::SignCompact(const uint256 &hash, std::vector<unsigned char>& vchSig) return true; } +bool CKey::Load(CPrivKey &privkey, CPubKey &vchPubKey, bool fSkipCheck=false) { + CECKey key; + if (!key.SetPrivKey(privkey, fSkipCheck)) + return false; + + key.GetSecretBytes(vch); + fCompressed = vchPubKey.IsCompressed(); + fValid = true; + + if (fSkipCheck) + return true; + + if (GetPubKey() != vchPubKey) + return false; + + return true; +} + bool CPubKey::Verify(const uint256 &hash, const std::vector<unsigned char>& vchSig) const { if (!IsValid()) return false; @@ -261,6 +261,9 @@ public: // Derive BIP32 child key. bool Derive(CKey& keyChild, unsigned char ccChild[32], unsigned int nChild, const unsigned char cc[32]) const; + + // Load private key and check that public key matches. + bool Load(CPrivKey &privkey, CPubKey &vchPubKey, bool fSkipCheck); }; struct CExtPubKey { diff --git a/src/leveldb/Makefile b/src/leveldb/Makefile index 96af7765be..20c9c4f287 100644 --- a/src/leveldb/Makefile +++ b/src/leveldb/Makefile @@ -31,6 +31,7 @@ TESTHARNESS = ./util/testharness.o $(TESTUTIL) TESTS = \ arena_test \ + autocompact_test \ bloom_test \ c_test \ cache_test \ @@ -70,7 +71,7 @@ SHARED = $(SHARED1) else # Update db.h if you change these. SHARED_MAJOR = 1 -SHARED_MINOR = 12 +SHARED_MINOR = 13 SHARED1 = libleveldb.$(PLATFORM_SHARED_EXT) SHARED2 = $(SHARED1).$(SHARED_MAJOR) SHARED3 = $(SHARED1).$(SHARED_MAJOR).$(SHARED_MINOR) @@ -114,6 +115,9 @@ leveldbutil: db/leveldb_main.o $(LIBOBJECTS) arena_test: util/arena_test.o $(LIBOBJECTS) $(TESTHARNESS) $(CXX) $(LDFLAGS) util/arena_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS) +autocompact_test: db/autocompact_test.o $(LIBOBJECTS) $(TESTHARNESS) + $(CXX) $(LDFLAGS) db/autocompact_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS) + bloom_test: util/bloom_test.o $(LIBOBJECTS) $(TESTHARNESS) $(CXX) $(LDFLAGS) util/bloom_test.o $(LIBOBJECTS) $(TESTHARNESS) -o $@ $(LIBS) diff --git a/src/leveldb/db/autocompact_test.cc b/src/leveldb/db/autocompact_test.cc new file mode 100644 index 0000000000..d20a2362c3 --- /dev/null +++ b/src/leveldb/db/autocompact_test.cc @@ -0,0 +1,118 @@ +// Copyright (c) 2013 The LevelDB Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. See the AUTHORS file for names of contributors. + +#include "leveldb/db.h" +#include "db/db_impl.h" +#include "leveldb/cache.h" +#include "util/testharness.h" +#include "util/testutil.h" + +namespace leveldb { + +class AutoCompactTest { + public: + std::string dbname_; + Cache* tiny_cache_; + Options options_; + DB* db_; + + AutoCompactTest() { + dbname_ = test::TmpDir() + "/autocompact_test"; + tiny_cache_ = NewLRUCache(100); + options_.block_cache = tiny_cache_; + DestroyDB(dbname_, options_); + options_.create_if_missing = true; + options_.compression = kNoCompression; + ASSERT_OK(DB::Open(options_, dbname_, &db_)); + } + + ~AutoCompactTest() { + delete db_; + DestroyDB(dbname_, Options()); + delete tiny_cache_; + } + + std::string Key(int i) { + char buf[100]; + snprintf(buf, sizeof(buf), "key%06d", i); + return std::string(buf); + } + + uint64_t Size(const Slice& start, const Slice& limit) { + Range r(start, limit); + uint64_t size; + db_->GetApproximateSizes(&r, 1, &size); + return size; + } + + void DoReads(int n); +}; + +static const int kValueSize = 200 * 1024; +static const int kTotalSize = 100 * 1024 * 1024; +static const int kCount = kTotalSize / kValueSize; + +// Read through the first n keys repeatedly and check that they get +// compacted (verified by checking the size of the key space). +void AutoCompactTest::DoReads(int n) { + std::string value(kValueSize, 'x'); + DBImpl* dbi = reinterpret_cast<DBImpl*>(db_); + + // Fill database + for (int i = 0; i < kCount; i++) { + ASSERT_OK(db_->Put(WriteOptions(), Key(i), value)); + } + ASSERT_OK(dbi->TEST_CompactMemTable()); + + // Delete everything + for (int i = 0; i < kCount; i++) { + ASSERT_OK(db_->Delete(WriteOptions(), Key(i))); + } + ASSERT_OK(dbi->TEST_CompactMemTable()); + + // Get initial measurement of the space we will be reading. + const int64_t initial_size = Size(Key(0), Key(n)); + const int64_t initial_other_size = Size(Key(n), Key(kCount)); + + // Read until size drops significantly. + std::string limit_key = Key(n); + for (int read = 0; true; read++) { + ASSERT_LT(read, 100) << "Taking too long to compact"; + Iterator* iter = db_->NewIterator(ReadOptions()); + for (iter->SeekToFirst(); + iter->Valid() && iter->key().ToString() < limit_key; + iter->Next()) { + // Drop data + } + delete iter; + // Wait a little bit to allow any triggered compactions to complete. + Env::Default()->SleepForMicroseconds(1000000); + uint64_t size = Size(Key(0), Key(n)); + fprintf(stderr, "iter %3d => %7.3f MB [other %7.3f MB]\n", + read+1, size/1048576.0, Size(Key(n), Key(kCount))/1048576.0); + if (size <= initial_size/10) { + break; + } + } + + // Verify that the size of the key space not touched by the reads + // is pretty much unchanged. + const int64_t final_other_size = Size(Key(n), Key(kCount)); + ASSERT_LE(final_other_size, initial_other_size + 1048576); + ASSERT_GE(final_other_size, initial_other_size/5 - 1048576); +} + +TEST(AutoCompactTest, ReadAll) { + DoReads(kCount); +} + +TEST(AutoCompactTest, ReadHalf) { + DoReads(kCount/2); +} + +} // namespace leveldb + +int main(int argc, char** argv) { + return leveldb::test::RunAllTests(); +} diff --git a/src/leveldb/db/corruption_test.cc b/src/leveldb/db/corruption_test.cc index 31b2d5f416..b37ffdfe64 100644 --- a/src/leveldb/db/corruption_test.cc +++ b/src/leveldb/db/corruption_test.cc @@ -35,6 +35,7 @@ class CorruptionTest { CorruptionTest() { tiny_cache_ = NewLRUCache(100); options_.env = &env_; + options_.block_cache = tiny_cache_; dbname_ = test::TmpDir() + "/db_test"; DestroyDB(dbname_, options_); @@ -50,17 +51,14 @@ class CorruptionTest { delete tiny_cache_; } - Status TryReopen(Options* options = NULL) { + Status TryReopen() { delete db_; db_ = NULL; - Options opt = (options ? *options : options_); - opt.env = &env_; - opt.block_cache = tiny_cache_; - return DB::Open(opt, dbname_, &db_); + return DB::Open(options_, dbname_, &db_); } - void Reopen(Options* options = NULL) { - ASSERT_OK(TryReopen(options)); + void Reopen() { + ASSERT_OK(TryReopen()); } void RepairDB() { @@ -92,6 +90,10 @@ class CorruptionTest { for (iter->SeekToFirst(); iter->Valid(); iter->Next()) { uint64_t key; Slice in(iter->key()); + if (in == "" || in == "~") { + // Ignore boundary keys. + continue; + } if (!ConsumeDecimalNumber(&in, &key) || !in.empty() || key < next_expected) { @@ -233,7 +235,7 @@ TEST(CorruptionTest, TableFile) { dbi->TEST_CompactRange(1, NULL, NULL); Corrupt(kTableFile, 100, 1); - Check(99, 99); + Check(90, 99); } TEST(CorruptionTest, TableFileIndexData) { @@ -299,7 +301,7 @@ TEST(CorruptionTest, CompactionInputError) { ASSERT_EQ(1, Property("leveldb.num-files-at-level" + NumberToString(last))); Corrupt(kTableFile, 100, 1); - Check(9, 9); + Check(5, 9); // Force compactions by writing lots of values Build(10000); @@ -307,32 +309,23 @@ TEST(CorruptionTest, CompactionInputError) { } TEST(CorruptionTest, CompactionInputErrorParanoid) { - Options options; - options.paranoid_checks = true; - options.write_buffer_size = 1048576; - Reopen(&options); + options_.paranoid_checks = true; + options_.write_buffer_size = 512 << 10; + Reopen(); DBImpl* dbi = reinterpret_cast<DBImpl*>(db_); - // Fill levels >= 1 so memtable compaction outputs to level 1 - for (int level = 1; level < config::kNumLevels; level++) { - dbi->Put(WriteOptions(), "", "begin"); - dbi->Put(WriteOptions(), "~", "end"); + // Make multiple inputs so we need to compact. + for (int i = 0; i < 2; i++) { + Build(10); dbi->TEST_CompactMemTable(); + Corrupt(kTableFile, 100, 1); + env_.SleepForMicroseconds(100000); } + dbi->CompactRange(NULL, NULL); - Build(10); - dbi->TEST_CompactMemTable(); - ASSERT_EQ(1, Property("leveldb.num-files-at-level0")); - - Corrupt(kTableFile, 100, 1); - Check(9, 9); - - // Write must eventually fail because of corrupted table - Status s; + // Write must fail because of corrupted table std::string tmp1, tmp2; - for (int i = 0; i < 10000 && s.ok(); i++) { - s = db_->Put(WriteOptions(), Key(i, &tmp1), Value(i, &tmp2)); - } + Status s = db_->Put(WriteOptions(), Key(5, &tmp1), Value(5, &tmp2)); ASSERT_TRUE(!s.ok()) << "write did not fail in corrupted paranoid db"; } diff --git a/src/leveldb/db/db_impl.cc b/src/leveldb/db/db_impl.cc index 395d3172ad..fa1351038b 100644 --- a/src/leveldb/db/db_impl.cc +++ b/src/leveldb/db/db_impl.cc @@ -113,14 +113,14 @@ Options SanitizeOptions(const std::string& dbname, return result; } -DBImpl::DBImpl(const Options& options, const std::string& dbname) - : env_(options.env), - internal_comparator_(options.comparator), - internal_filter_policy_(options.filter_policy), - options_(SanitizeOptions( - dbname, &internal_comparator_, &internal_filter_policy_, options)), - owns_info_log_(options_.info_log != options.info_log), - owns_cache_(options_.block_cache != options.block_cache), +DBImpl::DBImpl(const Options& raw_options, const std::string& dbname) + : env_(raw_options.env), + internal_comparator_(raw_options.comparator), + internal_filter_policy_(raw_options.filter_policy), + options_(SanitizeOptions(dbname, &internal_comparator_, + &internal_filter_policy_, raw_options)), + owns_info_log_(options_.info_log != raw_options.info_log), + owns_cache_(options_.block_cache != raw_options.block_cache), dbname_(dbname), db_lock_(NULL), shutting_down_(NULL), @@ -130,6 +130,7 @@ DBImpl::DBImpl(const Options& options, const std::string& dbname) logfile_(NULL), logfile_number_(0), log_(NULL), + seed_(0), tmp_batch_(new WriteBatch), bg_compaction_scheduled_(false), manual_compaction_(NULL), @@ -138,7 +139,7 @@ DBImpl::DBImpl(const Options& options, const std::string& dbname) has_imm_.Release_Store(NULL); // Reserve ten files or so for other uses and give the rest to TableCache. - const int table_cache_size = options.max_open_files - kNumNonTableCacheFiles; + const int table_cache_size = options_.max_open_files - kNumNonTableCacheFiles; table_cache_ = new TableCache(dbname_, &options_, table_cache_size); versions_ = new VersionSet(dbname_, &options_, table_cache_, @@ -1027,7 +1028,8 @@ static void CleanupIteratorState(void* arg1, void* arg2) { } // namespace Iterator* DBImpl::NewInternalIterator(const ReadOptions& options, - SequenceNumber* latest_snapshot) { + SequenceNumber* latest_snapshot, + uint32_t* seed) { IterState* cleanup = new IterState; mutex_.Lock(); *latest_snapshot = versions_->LastSequence(); @@ -1051,13 +1053,15 @@ Iterator* DBImpl::NewInternalIterator(const ReadOptions& options, cleanup->version = versions_->current(); internal_iter->RegisterCleanup(CleanupIteratorState, cleanup, NULL); + *seed = ++seed_; mutex_.Unlock(); return internal_iter; } Iterator* DBImpl::TEST_NewInternalIterator() { SequenceNumber ignored; - return NewInternalIterator(ReadOptions(), &ignored); + uint32_t ignored_seed; + return NewInternalIterator(ReadOptions(), &ignored, &ignored_seed); } int64_t DBImpl::TEST_MaxNextLevelOverlappingBytes() { @@ -1114,12 +1118,21 @@ Status DBImpl::Get(const ReadOptions& options, Iterator* DBImpl::NewIterator(const ReadOptions& options) { SequenceNumber latest_snapshot; - Iterator* internal_iter = NewInternalIterator(options, &latest_snapshot); + uint32_t seed; + Iterator* iter = NewInternalIterator(options, &latest_snapshot, &seed); return NewDBIterator( - &dbname_, env_, user_comparator(), internal_iter, + this, user_comparator(), iter, (options.snapshot != NULL ? reinterpret_cast<const SnapshotImpl*>(options.snapshot)->number_ - : latest_snapshot)); + : latest_snapshot), + seed); +} + +void DBImpl::RecordReadSample(Slice key) { + MutexLock l(&mutex_); + if (versions_->current()->RecordReadSample(key)) { + MaybeScheduleCompaction(); + } } const Snapshot* DBImpl::GetSnapshot() { diff --git a/src/leveldb/db/db_impl.h b/src/leveldb/db/db_impl.h index 3c8d711ae0..75fd30abe9 100644 --- a/src/leveldb/db/db_impl.h +++ b/src/leveldb/db/db_impl.h @@ -59,13 +59,19 @@ class DBImpl : public DB { // file at a level >= 1. int64_t TEST_MaxNextLevelOverlappingBytes(); + // Record a sample of bytes read at the specified internal key. + // Samples are taken approximately once every config::kReadBytesPeriod + // bytes. + void RecordReadSample(Slice key); + private: friend class DB; struct CompactionState; struct Writer; Iterator* NewInternalIterator(const ReadOptions&, - SequenceNumber* latest_snapshot); + SequenceNumber* latest_snapshot, + uint32_t* seed); Status NewDB(); @@ -135,6 +141,7 @@ class DBImpl : public DB { WritableFile* logfile_; uint64_t logfile_number_; log::Writer* log_; + uint32_t seed_; // For sampling. // Queue of writers. std::deque<Writer*> writers_; diff --git a/src/leveldb/db/db_iter.cc b/src/leveldb/db/db_iter.cc index 87dca2ded4..071a54e3f4 100644 --- a/src/leveldb/db/db_iter.cc +++ b/src/leveldb/db/db_iter.cc @@ -5,12 +5,14 @@ #include "db/db_iter.h" #include "db/filename.h" +#include "db/db_impl.h" #include "db/dbformat.h" #include "leveldb/env.h" #include "leveldb/iterator.h" #include "port/port.h" #include "util/logging.h" #include "util/mutexlock.h" +#include "util/random.h" namespace leveldb { @@ -46,15 +48,16 @@ class DBIter: public Iterator { kReverse }; - DBIter(const std::string* dbname, Env* env, - const Comparator* cmp, Iterator* iter, SequenceNumber s) - : dbname_(dbname), - env_(env), + DBIter(DBImpl* db, const Comparator* cmp, Iterator* iter, SequenceNumber s, + uint32_t seed) + : db_(db), user_comparator_(cmp), iter_(iter), sequence_(s), direction_(kForward), - valid_(false) { + valid_(false), + rnd_(seed), + bytes_counter_(RandomPeriod()) { } virtual ~DBIter() { delete iter_; @@ -100,8 +103,12 @@ class DBIter: public Iterator { } } - const std::string* const dbname_; - Env* const env_; + // Pick next gap with average value of config::kReadBytesPeriod. + ssize_t RandomPeriod() { + return rnd_.Uniform(2*config::kReadBytesPeriod); + } + + DBImpl* db_; const Comparator* const user_comparator_; Iterator* const iter_; SequenceNumber const sequence_; @@ -112,13 +119,23 @@ class DBIter: public Iterator { Direction direction_; bool valid_; + Random rnd_; + ssize_t bytes_counter_; + // No copying allowed DBIter(const DBIter&); void operator=(const DBIter&); }; inline bool DBIter::ParseKey(ParsedInternalKey* ikey) { - if (!ParseInternalKey(iter_->key(), ikey)) { + Slice k = iter_->key(); + ssize_t n = k.size() + iter_->value().size(); + bytes_counter_ -= n; + while (bytes_counter_ < 0) { + bytes_counter_ += RandomPeriod(); + db_->RecordReadSample(k); + } + if (!ParseInternalKey(k, ikey)) { status_ = Status::Corruption("corrupted internal key in DBIter"); return false; } else { @@ -288,12 +305,12 @@ void DBIter::SeekToLast() { } // anonymous namespace Iterator* NewDBIterator( - const std::string* dbname, - Env* env, + DBImpl* db, const Comparator* user_key_comparator, Iterator* internal_iter, - const SequenceNumber& sequence) { - return new DBIter(dbname, env, user_key_comparator, internal_iter, sequence); + SequenceNumber sequence, + uint32_t seed) { + return new DBIter(db, user_key_comparator, internal_iter, sequence, seed); } } // namespace leveldb diff --git a/src/leveldb/db/db_iter.h b/src/leveldb/db/db_iter.h index d9e1b174ab..04927e937b 100644 --- a/src/leveldb/db/db_iter.h +++ b/src/leveldb/db/db_iter.h @@ -11,15 +11,17 @@ namespace leveldb { +class DBImpl; + // Return a new iterator that converts internal keys (yielded by // "*internal_iter") that were live at the specified "sequence" number // into appropriate user keys. extern Iterator* NewDBIterator( - const std::string* dbname, - Env* env, + DBImpl* db, const Comparator* user_key_comparator, Iterator* internal_iter, - const SequenceNumber& sequence); + SequenceNumber sequence, + uint32_t seed); } // namespace leveldb diff --git a/src/leveldb/db/dbformat.h b/src/leveldb/db/dbformat.h index f7f64dafb6..5d8a032bd3 100644 --- a/src/leveldb/db/dbformat.h +++ b/src/leveldb/db/dbformat.h @@ -38,6 +38,9 @@ static const int kL0_StopWritesTrigger = 12; // space if the same key space is being repeatedly overwritten. static const int kMaxMemCompactLevel = 2; +// Approximate gap in bytes between samples of data read during iteration. +static const int kReadBytesPeriod = 1048576; + } // namespace config class InternalKey; diff --git a/src/leveldb/db/version_set.cc b/src/leveldb/db/version_set.cc index 4fd1ddef21..66d73be71f 100644 --- a/src/leveldb/db/version_set.cc +++ b/src/leveldb/db/version_set.cc @@ -289,6 +289,51 @@ static bool NewestFirst(FileMetaData* a, FileMetaData* b) { return a->number > b->number; } +void Version::ForEachOverlapping(Slice user_key, Slice internal_key, + void* arg, + bool (*func)(void*, int, FileMetaData*)) { + // TODO(sanjay): Change Version::Get() to use this function. + const Comparator* ucmp = vset_->icmp_.user_comparator(); + + // Search level-0 in order from newest to oldest. + std::vector<FileMetaData*> tmp; + tmp.reserve(files_[0].size()); + for (uint32_t i = 0; i < files_[0].size(); i++) { + FileMetaData* f = files_[0][i]; + if (ucmp->Compare(user_key, f->smallest.user_key()) >= 0 && + ucmp->Compare(user_key, f->largest.user_key()) <= 0) { + tmp.push_back(f); + } + } + if (!tmp.empty()) { + std::sort(tmp.begin(), tmp.end(), NewestFirst); + for (uint32_t i = 0; i < tmp.size(); i++) { + if (!(*func)(arg, 0, tmp[i])) { + return; + } + } + } + + // Search other levels. + for (int level = 1; level < config::kNumLevels; level++) { + size_t num_files = files_[level].size(); + if (num_files == 0) continue; + + // Binary search to find earliest index whose largest key >= internal_key. + uint32_t index = FindFile(vset_->icmp_, files_[level], internal_key); + if (index < num_files) { + FileMetaData* f = files_[level][index]; + if (ucmp->Compare(user_key, f->smallest.user_key()) < 0) { + // All of "f" is past any data for user_key + } else { + if (!(*func)(arg, level, f)) { + return; + } + } + } + } +} + Status Version::Get(const ReadOptions& options, const LookupKey& k, std::string* value, @@ -401,6 +446,44 @@ bool Version::UpdateStats(const GetStats& stats) { return false; } +bool Version::RecordReadSample(Slice internal_key) { + ParsedInternalKey ikey; + if (!ParseInternalKey(internal_key, &ikey)) { + return false; + } + + struct State { + GetStats stats; // Holds first matching file + int matches; + + static bool Match(void* arg, int level, FileMetaData* f) { + State* state = reinterpret_cast<State*>(arg); + state->matches++; + if (state->matches == 1) { + // Remember first match. + state->stats.seek_file = f; + state->stats.seek_file_level = level; + } + // We can stop iterating once we have a second match. + return state->matches < 2; + } + }; + + State state; + state.matches = 0; + ForEachOverlapping(ikey.user_key, internal_key, &state, &State::Match); + + // Must have at least two matches since we want to merge across + // files. But what if we have a single file that contains many + // overwrites and deletions? Should we have another mechanism for + // finding such files? + if (state.matches >= 2) { + // 1MB cost is about 1 seek (see comment in Builder::Apply). + return UpdateStats(state.stats); + } + return false; +} + void Version::Ref() { ++refs_; } @@ -435,10 +518,13 @@ int Version::PickLevelForMemTableOutput( if (OverlapInLevel(level + 1, &smallest_user_key, &largest_user_key)) { break; } - GetOverlappingInputs(level + 2, &start, &limit, &overlaps); - const int64_t sum = TotalFileSize(overlaps); - if (sum > kMaxGrandParentOverlapBytes) { - break; + if (level + 2 < config::kNumLevels) { + // Check that file does not overlap too many grandparent bytes. + GetOverlappingInputs(level + 2, &start, &limit, &overlaps); + const int64_t sum = TotalFileSize(overlaps); + if (sum > kMaxGrandParentOverlapBytes) { + break; + } } level++; } @@ -452,6 +538,8 @@ void Version::GetOverlappingInputs( const InternalKey* begin, const InternalKey* end, std::vector<FileMetaData*>* inputs) { + assert(level >= 0); + assert(level < config::kNumLevels); inputs->clear(); Slice user_begin, user_end; if (begin != NULL) { diff --git a/src/leveldb/db/version_set.h b/src/leveldb/db/version_set.h index 9d084fdb7d..20de0e2629 100644 --- a/src/leveldb/db/version_set.h +++ b/src/leveldb/db/version_set.h @@ -78,6 +78,12 @@ class Version { // REQUIRES: lock is held bool UpdateStats(const GetStats& stats); + // Record a sample of bytes read at the specified internal key. + // Samples are taken approximately once every config::kReadBytesPeriod + // bytes. Returns true if a new compaction may need to be triggered. + // REQUIRES: lock is held + bool RecordReadSample(Slice key); + // Reference count management (so Versions do not disappear out from // under live iterators) void Ref(); @@ -114,6 +120,15 @@ class Version { class LevelFileNumIterator; Iterator* NewConcatenatingIterator(const ReadOptions&, int level) const; + // Call func(arg, level, f) for every file that overlaps user_key in + // order from newest to oldest. If an invocation of func returns + // false, makes no more calls. + // + // REQUIRES: user portion of internal_key == user_key. + void ForEachOverlapping(Slice user_key, Slice internal_key, + void* arg, + bool (*func)(void*, int, FileMetaData*)); + VersionSet* vset_; // VersionSet to which this Version belongs Version* next_; // Next version in linked list Version* prev_; // Previous version in linked list diff --git a/src/leveldb/include/leveldb/db.h b/src/leveldb/include/leveldb/db.h index da8b11a8c0..57c00a5da0 100644 --- a/src/leveldb/include/leveldb/db.h +++ b/src/leveldb/include/leveldb/db.h @@ -14,7 +14,7 @@ namespace leveldb { // Update Makefile if you change these static const int kMajorVersion = 1; -static const int kMinorVersion = 12; +static const int kMinorVersion = 13; struct Options; struct ReadOptions; diff --git a/src/leveldb/util/env_posix.cc b/src/leveldb/util/env_posix.cc index 6badfdc230..0f5dcfac5a 100644 --- a/src/leveldb/util/env_posix.cc +++ b/src/leveldb/util/env_posix.cc @@ -320,8 +320,39 @@ class PosixMmapFile : public WritableFile { return Status::OK(); } - virtual Status Sync() { + Status SyncDirIfManifest() { + const char* f = filename_.c_str(); + const char* sep = strrchr(f, '/'); + Slice basename; + std::string dir; + if (sep == NULL) { + dir = "."; + basename = f; + } else { + dir = std::string(f, sep - f); + basename = sep + 1; + } Status s; + if (basename.starts_with("MANIFEST")) { + int fd = open(dir.c_str(), O_RDONLY); + if (fd < 0) { + s = IOError(dir, errno); + } else { + if (fsync(fd) < 0) { + s = IOError(dir, errno); + } + close(fd); + } + } + return s; + } + + virtual Status Sync() { + // Ensure new files referred to by the manifest are in the filesystem. + Status s = SyncDirIfManifest(); + if (!s.ok()) { + return s; + } if (pending_sync_) { // Some unmapped data was not synced diff --git a/src/leveldb/util/random.h b/src/leveldb/util/random.h index 07538242ea..ddd51b1c7b 100644 --- a/src/leveldb/util/random.h +++ b/src/leveldb/util/random.h @@ -16,7 +16,12 @@ class Random { private: uint32_t seed_; public: - explicit Random(uint32_t s) : seed_(s & 0x7fffffffu) { } + explicit Random(uint32_t s) : seed_(s & 0x7fffffffu) { + // Avoid bad seeds. + if (seed_ == 0 || seed_ == 2147483647L) { + seed_ = 1; + } + } uint32_t Next() { static const uint32_t M = 2147483647L; // 2^31-1 static const uint64_t A = 16807; // bits 14, 8, 7, 5, 2, 1, 0 diff --git a/src/main.cpp b/src/main.cpp index 0692a5e0e4..ceb1e80d2c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -32,13 +32,8 @@ CTxMemPool mempool; unsigned int nTransactionsUpdated = 0; map<uint256, CBlockIndex*> mapBlockIndex; -std::vector<CBlockIndex*> vBlockIndexByHeight; -CBlockIndex* pindexGenesisBlock = NULL; -int nBestHeight = -1; -uint256 nBestChainWork = 0; +CChain chainActive; uint256 nBestInvalidWork = 0; -uint256 hashBestChain = 0; -CBlockIndex* pindexBest = NULL; set<CBlockIndex*, CBlockIndexWorkComparator> setBlockIndexValid; // may contain all CBlockIndex*'s that have validness >=BLOCK_VALID_TRANSACTIONS, and must contain those who aren't failed int64 nTimeBestReceived = 0; int nScriptCheckThreads = 0; @@ -173,106 +168,83 @@ void static ResendWalletTransactions() // Registration of network node signals. // +int static GetHeight() +{ + LOCK(cs_main); + return chainActive.Height(); +} + void RegisterNodeSignals(CNodeSignals& nodeSignals) { + nodeSignals.GetHeight.connect(&GetHeight); nodeSignals.ProcessMessages.connect(&ProcessMessages); nodeSignals.SendMessages.connect(&SendMessages); } void UnregisterNodeSignals(CNodeSignals& nodeSignals) { + nodeSignals.GetHeight.disconnect(&GetHeight); nodeSignals.ProcessMessages.disconnect(&ProcessMessages); nodeSignals.SendMessages.disconnect(&SendMessages); } ////////////////////////////////////////////////////////////////////////////// // -// CBlockLocator implementation +// CChain implementation // -CBlockLocator::CBlockLocator(uint256 hashBlock) -{ - std::map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hashBlock); - if (mi != mapBlockIndex.end()) - Set((*mi).second); +CBlockIndex *CChain::SetTip(CBlockIndex *pindex) { + if (pindex == NULL) { + vChain.clear(); + return NULL; + } + vChain.resize(pindex->nHeight + 1); + while (pindex && vChain[pindex->nHeight] != pindex) { + vChain[pindex->nHeight] = pindex; + pindex = pindex->pprev; + } + return pindex; } -void CBlockLocator::Set(const CBlockIndex* pindex) -{ - vHave.clear(); +CBlockLocator CChain::GetLocator(const CBlockIndex *pindex) const { int nStep = 1; - while (pindex) - { - vHave.push_back(pindex->GetBlockHash()); + std::vector<uint256> vHave; + vHave.reserve(32); - // Exponentially larger steps back - for (int i = 0; pindex && i < nStep; i++) + if (!pindex) + pindex = Tip(); + while (pindex) { + vHave.push_back(pindex->GetBlockHash()); + // Stop when we have added the genesis block. + if (pindex->nHeight == 0) + break; + // Exponentially larger steps back, plus the genesis block. + int nHeight = std::max(pindex->nHeight - nStep, 0); + // In case pindex is not in this chain, iterate pindex->pprev to find blocks. + while (pindex->nHeight > nHeight && !Contains(pindex)) pindex = pindex->pprev; + // If pindex is in this chain, use direct height-based access. + if (pindex->nHeight > nHeight) + pindex = (*this)[nHeight]; if (vHave.size() > 10) nStep *= 2; } - vHave.push_back(Params().HashGenesisBlock()); -} -int CBlockLocator::GetDistanceBack() -{ - // Retrace how far back it was in the sender's branch - int nDistance = 0; - int nStep = 1; - BOOST_FOREACH(const uint256& hash, vHave) - { - std::map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hash); - if (mi != mapBlockIndex.end()) - { - CBlockIndex* pindex = (*mi).second; - if (pindex->IsInMainChain()) - return nDistance; - } - nDistance += nStep; - if (nDistance > 10) - nStep *= 2; - } - return nDistance; + return CBlockLocator(vHave); } -CBlockIndex *CBlockLocator::GetBlockIndex() -{ +CBlockIndex *CChain::FindFork(const CBlockLocator &locator) const { // Find the first block the caller has in the main chain - BOOST_FOREACH(const uint256& hash, vHave) - { + BOOST_FOREACH(const uint256& hash, locator.vHave) { std::map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hash); if (mi != mapBlockIndex.end()) { CBlockIndex* pindex = (*mi).second; - if (pindex->IsInMainChain()) + if (Contains(pindex)) return pindex; } } - return pindexGenesisBlock; -} - -uint256 CBlockLocator::GetBlockHash() -{ - // Find the first block the caller has in the main chain - BOOST_FOREACH(const uint256& hash, vHave) - { - std::map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hash); - if (mi != mapBlockIndex.end()) - { - CBlockIndex* pindex = (*mi).second; - if (pindex->IsInMainChain()) - return hash; - } - } - return Params().HashGenesisBlock(); -} - -int CBlockLocator::GetHeight() -{ - CBlockIndex* pindex = GetBlockIndex(); - if (!pindex) - return 0; - return pindex->nHeight; + return Genesis(); } ////////////////////////////////////////////////////////////////////////////// @@ -517,7 +489,7 @@ bool IsFinalTx(const CTransaction &tx, int nBlockHeight, int64 nBlockTime) if (tx.nLockTime == 0) return true; if (nBlockHeight == 0) - nBlockHeight = nBestHeight; + nBlockHeight = chainActive.Height(); if (nBlockTime == 0) nBlockTime = GetAdjustedTime(); if ((int64)tx.nLockTime < ((int64)tx.nLockTime < LOCKTIME_THRESHOLD ? (int64)nBlockHeight : nBlockTime)) @@ -644,7 +616,7 @@ int CMerkleTx::SetMerkleBranch(const CBlock* pblock) if (pblock == NULL) { CCoins coins; if (pcoinsTip->GetCoins(GetHash(), coins)) { - CBlockIndex *pindex = FindBlockByHeight(coins.nHeight); + CBlockIndex *pindex = chainActive[coins.nHeight]; if (pindex) { if (!ReadBlockFromDisk(blockTmp, pindex)) return 0; @@ -678,10 +650,10 @@ int CMerkleTx::SetMerkleBranch(const CBlock* pblock) if (mi == mapBlockIndex.end()) return 0; CBlockIndex* pindex = (*mi).second; - if (!pindex || !pindex->IsInMainChain()) + if (!pindex || !chainActive.Contains(pindex)) return 0; - return pindexBest->nHeight - pindex->nHeight + 1; + return chainActive.Height() - pindex->nHeight + 1; } @@ -750,15 +722,18 @@ int64 GetMinFee(const CTransaction& tx, bool fAllowFree, enum GetMinFee_mode mod { // There is a free transaction area in blocks created by most miners, // * If we are relaying we allow transactions up to DEFAULT_BLOCK_PRIORITY_SIZE - 1000 - // to be considered to fall into this category - // * If we are creating a transaction we allow transactions up to DEFAULT_BLOCK_PRIORITY_SIZE - 17000 - // (= 10000) to be considered safe and assume they can likely make it into this section - if (nBytes < (mode == GMF_SEND ? (DEFAULT_BLOCK_PRIORITY_SIZE - 17000) : (DEFAULT_BLOCK_PRIORITY_SIZE - 1000))) + // to be considered to fall into this category. We don't want to encourage sending + // multiple transactions instead of one big transaction to avoid fees. + // * If we are creating a transaction we allow transactions up to 1,000 bytes + // to be considered safe and assume they can likely make it into this section. + if (nBytes < (mode == GMF_SEND ? 1000 : (DEFAULT_BLOCK_PRIORITY_SIZE - 1000))) nMinFee = 0; } - // To limit dust spam, require base fee if any output is less than 0.01 - if (nMinFee < nBaseFee) + // This code can be removed after enough miners have upgraded to version 0.9. + // Until then, be safe when sending and require a fee if any output + // is less than CENT: + if (nMinFee < nBaseFee && mode == GMF_SEND) { BOOST_FOREACH(const CTxOut& txout, tx.vout) if (txout.nValue < CENT) @@ -1078,7 +1053,7 @@ int CMerkleTx::GetDepthInMainChain(CBlockIndex* &pindexRet) const if (mi == mapBlockIndex.end()) return 0; CBlockIndex* pindex = (*mi).second; - if (!pindex || !pindex->IsInMainChain()) + if (!pindex || !chainActive.Contains(pindex)) return 0; // Make sure the merkle branch connects to this block @@ -1090,7 +1065,7 @@ int CMerkleTx::GetDepthInMainChain(CBlockIndex* &pindexRet) const } pindexRet = pindex; - return pindexBest->nHeight - pindex->nHeight + 1; + return chainActive.Height() - pindex->nHeight + 1; } @@ -1173,7 +1148,7 @@ bool GetTransaction(const uint256 &hash, CTransaction &txOut, uint256 &hashBlock nHeight = coins.nHeight; } if (nHeight > 0) - pindexSlow = FindBlockByHeight(nHeight); + pindexSlow = chainActive[nHeight]; } } @@ -1203,14 +1178,6 @@ bool GetTransaction(const uint256 &hash, CTransaction &txOut, uint256 &hashBlock // CBlock and CBlockIndex // -static CBlockIndex* pblockindexFBBHLast; -CBlockIndex* FindBlockByHeight(int nHeight) -{ - if (nHeight >= (int)vBlockIndexByHeight.size()) - return NULL; - return vBlockIndexByHeight[nHeight]; -} - bool WriteBlockToDisk(CBlock& block, CDiskBlockPos& pos) { // Open history file to append @@ -1404,17 +1371,17 @@ int GetNumBlocksOfPeers() bool IsInitialBlockDownload() { - if (pindexBest == NULL || fImporting || fReindex || nBestHeight < Checkpoints::GetTotalBlocksEstimate()) + if (fImporting || fReindex || chainActive.Height() < Checkpoints::GetTotalBlocksEstimate()) return true; static int64 nLastUpdate; static CBlockIndex* pindexLastBest; - if (pindexBest != pindexLastBest) + if (chainActive.Tip() != pindexLastBest) { - pindexLastBest = pindexBest; + pindexLastBest = chainActive.Tip(); nLastUpdate = GetTime(); } return (GetTime() - nLastUpdate < 10 && - pindexBest->GetBlockTime() < GetTime() - 24 * 60 * 60); + chainActive.Tip()->GetBlockTime() < GetTime() - 24 * 60 * 60); } bool fLargeWorkForkFound = false; @@ -1430,10 +1397,10 @@ void CheckForkWarningConditions() // If our best fork is no longer within 72 blocks (+/- 12 hours if no one mines it) // of our head, drop it - if (pindexBestForkTip && nBestHeight - pindexBestForkTip->nHeight >= 72) + if (pindexBestForkTip && chainActive.Height() - pindexBestForkTip->nHeight >= 72) pindexBestForkTip = NULL; - if (pindexBestForkTip || nBestInvalidWork > nBestChainWork + (pindexBest->GetBlockWork() * 6).getuint256()) + if (pindexBestForkTip || nBestInvalidWork > chainActive.Tip()->nChainWork + (chainActive.Tip()->GetBlockWork() * 6).getuint256()) { if (!fLargeWorkForkFound) { @@ -1470,7 +1437,7 @@ void CheckForkWarningConditionsOnNewFork(CBlockIndex* pindexNewForkTip) { // If we are on a fork that is sufficiently large, set a warning flag CBlockIndex* pfork = pindexNewForkTip; - CBlockIndex* plonger = pindexBest; + CBlockIndex* plonger = chainActive.Tip(); while (pfork && pfork != plonger) { while (plonger && plonger->nHeight > pfork->nHeight) @@ -1489,7 +1456,7 @@ void CheckForkWarningConditionsOnNewFork(CBlockIndex* pindexNewForkTip) // the 7-block condition and from this always have the most-likely-to-cause-warning fork if (pfork && (!pindexBestForkTip || (pindexBestForkTip && pindexNewForkTip->nHeight > pindexBestForkTip->nHeight)) && pindexNewForkTip->nChainWork - pfork->nChainWork > (pfork->GetBlockWork() * 7).getuint256() && - nBestHeight - pindexNewForkTip->nHeight < 72) + chainActive.Height() - pindexNewForkTip->nHeight < 72) { pindexBestForkTip = pindexNewForkTip; pindexBestForkBase = pfork; @@ -1511,8 +1478,8 @@ void static InvalidChainFound(CBlockIndex* pindexNew) log(pindexNew->nChainWork.getdouble())/log(2.0), DateTimeStrFormat("%Y-%m-%d %H:%M:%S", pindexNew->GetBlockTime()).c_str()); LogPrintf("InvalidChainFound: current best=%s height=%d log2_work=%.8g date=%s\n", - hashBestChain.ToString().c_str(), nBestHeight, log(nBestChainWork.getdouble())/log(2.0), - DateTimeStrFormat("%Y-%m-%d %H:%M:%S", pindexBest->GetBlockTime()).c_str()); + chainActive.Tip()->GetBlockHash().ToString().c_str(), chainActive.Height(), log(chainActive.Tip()->nChainWork.getdouble())/log(2.0), + DateTimeStrFormat("%Y-%m-%d %H:%M:%S", chainActive.Tip()->GetBlockTime()).c_str()); CheckForkWarningConditions(); } @@ -1521,7 +1488,7 @@ void static InvalidBlockFound(CBlockIndex *pindex) { pblocktree->WriteBlockIndex(CDiskBlockIndex(pindex)); setBlockIndexValid.erase(pindex); InvalidChainFound(pindex); - if (pindex->GetNextInMainChain()) { + if (chainActive.Next(pindex)) { CValidationState stateDummy; ConnectBestBlock(stateDummy); // reorganise away from the failed block } @@ -1538,7 +1505,7 @@ bool ConnectBestBlock(CValidationState &state) { pindexNewBest = *it; } - if (pindexNewBest == pindexBest || (pindexBest && pindexNewBest->nChainWork == pindexBest->nChainWork)) + if (pindexNewBest == chainActive.Tip() || (chainActive.Tip() && pindexNewBest->nChainWork == chainActive.Tip()->nChainWork)) return true; // nothing to do // check ancestry @@ -1558,10 +1525,10 @@ bool ConnectBestBlock(CValidationState &state) { break; } - if (pindexBest == NULL || pindexTest->nChainWork > pindexBest->nChainWork) + if (chainActive.Tip() == NULL || pindexTest->nChainWork > chainActive.Tip()->nChainWork) vAttach.push_back(pindexTest); - if (pindexTest->pprev == NULL || pindexTest->GetNextInMainChain()) { + if (pindexTest->pprev == NULL || chainActive.Next(pindexTest)) { reverse(vAttach.begin(), vAttach.end()); BOOST_FOREACH(CBlockIndex *pindexSwitch, vAttach) { boost::this_thread::interruption_point(); @@ -1881,7 +1848,6 @@ bool ConnectBlock(CBlock& block, CValidationState& state, CBlockIndex* pindex, C // (its coinbase is unspendable) if (block.GetHash() == Params().HashGenesisBlock()) { view.SetBestBlock(pindex); - pindexGenesisBlock = pindex; return true; } @@ -2129,9 +2095,7 @@ bool SetBestChain(CValidationState &state, CBlockIndex* pindexNew) // Proceed by updating the memory structures. // Register new best chain - vBlockIndexByHeight.resize(pindexNew->nHeight + 1); - BOOST_FOREACH(CBlockIndex* pindex, vConnect) - vBlockIndexByHeight[pindex->nHeight] = pindex; + chainActive.SetTip(pindexNew); // Resurrect memory transactions that were in the disconnected branch BOOST_FOREACH(CTransaction& tx, vResurrect) { @@ -2151,29 +2115,21 @@ bool SetBestChain(CValidationState &state, CBlockIndex* pindexNew) // Update best block in wallet (so we can detect restored wallets) if ((pindexNew->nHeight % 20160) == 0 || (!fIsInitialDownload && (pindexNew->nHeight % 144) == 0)) - { - const CBlockLocator locator(pindexNew); - ::SetBestChain(locator); - } + ::SetBestChain(chainActive.GetLocator(pindexNew)); // New best block - hashBestChain = pindexNew->GetBlockHash(); - pindexBest = pindexNew; - pblockindexFBBHLast = NULL; - nBestHeight = pindexBest->nHeight; - nBestChainWork = pindexNew->nChainWork; nTimeBestReceived = GetTime(); nTransactionsUpdated++; LogPrintf("SetBestChain: new best=%s height=%d log2_work=%.8g tx=%lu date=%s progress=%f\n", - hashBestChain.ToString().c_str(), nBestHeight, log(nBestChainWork.getdouble())/log(2.0), (unsigned long)pindexNew->nChainTx, - DateTimeStrFormat("%Y-%m-%d %H:%M:%S", pindexBest->GetBlockTime()).c_str(), - Checkpoints::GuessVerificationProgress(pindexBest)); + chainActive.Tip()->GetBlockHash().ToString().c_str(), chainActive.Height(), log(chainActive.Tip()->nChainWork.getdouble())/log(2.0), (unsigned long)pindexNew->nChainTx, + DateTimeStrFormat("%Y-%m-%d %H:%M:%S", chainActive.Tip()->GetBlockTime()).c_str(), + Checkpoints::GuessVerificationProgress(chainActive.Tip())); // Check the version of the last 100 blocks to see if we need to upgrade: if (!fIsInitialDownload) { int nUpgraded = 0; - const CBlockIndex* pindex = pindexBest; + const CBlockIndex* pindex = chainActive.Tip(); for (int i = 0; i < 100 && pindex != NULL; i++) { if (pindex->nVersion > CBlock::CURRENT_VERSION) @@ -2191,7 +2147,7 @@ bool SetBestChain(CValidationState &state, CBlockIndex* pindexNew) if (!fIsInitialDownload && !strCmd.empty()) { - boost::replace_all(strCmd, "%s", hashBestChain.GetHex()); + boost::replace_all(strCmd, "%s", chainActive.Tip()->GetBlockHash().GetHex()); boost::thread t(runCommand, strCmd); // thread runs free } @@ -2233,7 +2189,7 @@ bool AddToBlockIndex(CBlock& block, CValidationState& state, const CDiskBlockPos if (!ConnectBestBlock(state)) return false; - if (pindexNew == pindexBest) + if (pindexNew == chainActive.Tip()) { // Clear fork warning if its no longer applicable CheckForkWarningConditions(); @@ -2482,11 +2438,11 @@ bool AcceptBlock(CBlock& block, CValidationState& state, CDiskBlockPos* dbp) // Relay inventory, but don't relay old inventory during initial block download int nBlockEstimate = Checkpoints::GetTotalBlocksEstimate(); - if (hashBestChain == hash) + if (chainActive.Tip()->GetBlockHash() == hash) { LOCK(cs_vNodes); BOOST_FOREACH(CNode* pnode, vNodes) - if (nBestHeight > (pnode->nStartingHeight != -1 ? pnode->nStartingHeight - 2000 : nBlockEstimate)) + if (chainActive.Height() > (pnode->nStartingHeight != -1 ? pnode->nStartingHeight - 2000 : nBlockEstimate)) pnode->PushInventory(CInv(MSG_BLOCK, hash)); } @@ -2505,6 +2461,18 @@ bool CBlockIndex::IsSuperMajority(int minVersion, const CBlockIndex* pstart, uns return (nFound >= nRequired); } +int64 CBlockIndex::GetMedianTime() const +{ + const CBlockIndex* pindex = this; + for (int i = 0; i < nMedianTimeSpan/2; i++) + { + if (!chainActive.Next(pindex)) + return GetBlockTime(); + pindex = chainActive.Next(pindex); + } + return pindex->GetMedianTimePast(); +} + void PushGetBlocks(CNode* pnode, CBlockIndex* pindexBegin, uint256 hashEnd) { // Filter out duplicate requests @@ -2513,7 +2481,7 @@ void PushGetBlocks(CNode* pnode, CBlockIndex* pindexBegin, uint256 hashEnd) pnode->pindexLastGetBlocksBegin = pindexBegin; pnode->hashLastGetBlocksEnd = hashEnd; - pnode->PushMessage("getblocks", CBlockLocator(pindexBegin), hashEnd); + pnode->PushMessage("getblocks", chainActive.GetLocator(pindexBegin), hashEnd); } bool ProcessBlock(CValidationState &state, CNode* pfrom, CBlock* pblock, CDiskBlockPos *dbp) @@ -2530,7 +2498,7 @@ bool ProcessBlock(CValidationState &state, CNode* pfrom, CBlock* pblock, CDiskBl return error("ProcessBlock() : CheckBlock FAILED"); CBlockIndex* pcheckpoint = Checkpoints::GetLastCheckpoint(mapBlockIndex); - if (pcheckpoint && pblock->hashPrevBlock != hashBestChain) + if (pcheckpoint && pblock->hashPrevBlock != (chainActive.Tip() ? chainActive.Tip()->GetBlockHash() : uint256(0))) { // Extra checks to prevent "fill up memory by spamming with bogus blocks" int64 deltaTime = pblock->GetBlockTime() - pcheckpoint->nTime; @@ -2561,7 +2529,7 @@ bool ProcessBlock(CValidationState &state, CNode* pfrom, CBlock* pblock, CDiskBl mapOrphanBlocksByPrev.insert(make_pair(pblock2->hashPrevBlock, pblock2)); // Ask this guy to fill in what we're missing - PushGetBlocks(pfrom, pindexBest, GetOrphanRoot(pblock2)); + PushGetBlocks(pfrom, chainActive.Tip(), GetOrphanRoot(pblock2)); } return true; } @@ -2875,48 +2843,39 @@ bool static LoadBlockIndexDB() LogPrintf("LoadBlockIndexDB(): transaction index %s\n", fTxIndex ? "enabled" : "disabled"); // Load hashBestChain pointer to end of best chain - pindexBest = pcoinsTip->GetBestBlock(); - if (pindexBest == NULL) + chainActive.SetTip(pcoinsTip->GetBestBlock()); + if (chainActive.Tip() == NULL) return true; - hashBestChain = pindexBest->GetBlockHash(); - nBestHeight = pindexBest->nHeight; - nBestChainWork = pindexBest->nChainWork; // register best chain - CBlockIndex *pindex = pindexBest; - vBlockIndexByHeight.resize(pindexBest->nHeight + 1); - while(pindex != NULL) { - vBlockIndexByHeight[pindex->nHeight] = pindex; - pindex = pindex->pprev; - } LogPrintf("LoadBlockIndexDB(): hashBestChain=%s height=%d date=%s\n", - hashBestChain.ToString().c_str(), nBestHeight, - DateTimeStrFormat("%Y-%m-%d %H:%M:%S", pindexBest->GetBlockTime()).c_str()); + chainActive.Tip()->GetBlockHash().ToString().c_str(), chainActive.Height(), + DateTimeStrFormat("%Y-%m-%d %H:%M:%S", chainActive.Tip()->GetBlockTime()).c_str()); return true; } bool VerifyDB(int nCheckLevel, int nCheckDepth) { - if (pindexBest == NULL || pindexBest->pprev == NULL) + if (chainActive.Tip() == NULL || chainActive.Tip()->pprev == NULL) return true; // Verify blocks in the best chain if (nCheckDepth <= 0) nCheckDepth = 1000000000; // suffices until the year 19000 - if (nCheckDepth > nBestHeight) - nCheckDepth = nBestHeight; + if (nCheckDepth > chainActive.Height()) + nCheckDepth = chainActive.Height(); nCheckLevel = std::max(0, std::min(4, nCheckLevel)); LogPrintf("Verifying last %i blocks at level %i\n", nCheckDepth, nCheckLevel); CCoinsViewCache coins(*pcoinsTip, true); - CBlockIndex* pindexState = pindexBest; + CBlockIndex* pindexState = chainActive.Tip(); CBlockIndex* pindexFailure = NULL; int nGoodTransactions = 0; CValidationState state; - for (CBlockIndex* pindex = pindexBest; pindex && pindex->pprev; pindex = pindex->pprev) + for (CBlockIndex* pindex = chainActive.Tip(); pindex && pindex->pprev; pindex = pindex->pprev) { boost::this_thread::interruption_point(); - if (pindex->nHeight < nBestHeight-nCheckDepth) + if (pindex->nHeight < chainActive.Height()-nCheckDepth) break; CBlock block; // check level 0: read from disk @@ -2948,14 +2907,14 @@ bool VerifyDB(int nCheckLevel, int nCheckDepth) } } if (pindexFailure) - return error("VerifyDB() : *** coin database inconsistencies found (last %i blocks, %i good transactions before that)\n", pindexBest->nHeight - pindexFailure->nHeight + 1, nGoodTransactions); + return error("VerifyDB() : *** coin database inconsistencies found (last %i blocks, %i good transactions before that)\n", chainActive.Height() - pindexFailure->nHeight + 1, nGoodTransactions); // check level 4: try reconnecting blocks if (nCheckLevel >= 4) { CBlockIndex *pindex = pindexState; - while (pindex != pindexBest) { + while (pindex != chainActive.Tip()) { boost::this_thread::interruption_point(); - pindex = pindex->GetNextInMainChain(); + pindex = chainActive.Next(pindex); CBlock block; if (!ReadBlockFromDisk(block, pindex)) return error("VerifyDB() : *** ReadBlockFromDisk failed at %d, hash=%s", pindex->nHeight, pindex->GetBlockHash().ToString().c_str()); @@ -2964,7 +2923,7 @@ bool VerifyDB(int nCheckLevel, int nCheckDepth) } } - LogPrintf("No coin database inconsistencies in last %i blocks (%i transactions)\n", pindexBest->nHeight - pindexState->nHeight, nGoodTransactions); + LogPrintf("No coin database inconsistencies in last %i blocks (%i transactions)\n", chainActive.Height() - pindexState->nHeight, nGoodTransactions); return true; } @@ -2973,12 +2932,8 @@ void UnloadBlockIndex() { mapBlockIndex.clear(); setBlockIndexValid.clear(); - pindexGenesisBlock = NULL; - nBestHeight = 0; - nBestChainWork = 0; + chainActive.SetTip(NULL); nBestInvalidWork = 0; - hashBestChain = 0; - pindexBest = NULL; } bool LoadBlockIndex() @@ -2992,7 +2947,7 @@ bool LoadBlockIndex() bool InitBlockIndex() { // Check whether we're already initialized - if (pindexGenesisBlock != NULL) + if (chainActive.Genesis() != NULL) return true; // Use the provided setting for -txindex in the new database @@ -3038,7 +2993,7 @@ void PrintBlockTree() } vector<pair<int, CBlockIndex*> > vStack; - vStack.push_back(make_pair(0, pindexGenesisBlock)); + vStack.push_back(make_pair(0, chainActive.Genesis())); int nPrevCol = 0; while (!vStack.empty()) @@ -3081,7 +3036,7 @@ void PrintBlockTree() vector<CBlockIndex*>& vNext = mapNext[pindex]; for (unsigned int i = 0; i < vNext.size(); i++) { - if (vNext[i]->GetNextInMainChain()) + if (chainActive.Next(vNext[i])) { swap(vNext[0], vNext[i]); break; @@ -3328,7 +3283,7 @@ void static ProcessGetData(CNode* pfrom) // and we want it right after the last block so they don't // wait for other stuff first. vector<CInv> vInv; - vInv.push_back(CInv(MSG_BLOCK, hashBestChain)); + vInv.push_back(CInv(MSG_BLOCK, chainActive.Tip()->GetBlockHash())); pfrom->PushMessage("inv", vInv); pfrom->hashContinue = 0; } @@ -3610,7 +3565,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) if (!fImporting && !fReindex) pfrom->AskFor(inv); } else if (inv.type == MSG_BLOCK && mapOrphanBlocks.count(inv.hash)) { - PushGetBlocks(pfrom, pindexBest, GetOrphanRoot(mapOrphanBlocks[inv.hash])); + PushGetBlocks(pfrom, chainActive.Tip(), GetOrphanRoot(mapOrphanBlocks[inv.hash])); } else if (nInv == nLastBlock) { // In case we are on a very long side-chain, it is possible that we already have // the last block in an inv bundle sent in response to getblocks. Try to detect @@ -3654,14 +3609,14 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) vRecv >> locator >> hashStop; // Find the last block the caller has in the main chain - CBlockIndex* pindex = locator.GetBlockIndex(); + CBlockIndex* pindex = chainActive.FindFork(locator); // Send the rest of the chain if (pindex) - pindex = pindex->GetNextInMainChain(); + pindex = chainActive.Next(pindex); int nLimit = 500; LogPrint("net", "getblocks %d to %s limit %d\n", (pindex ? pindex->nHeight : -1), hashStop.ToString().c_str(), nLimit); - for (; pindex; pindex = pindex->GetNextInMainChain()) + for (; pindex; pindex = chainActive.Next(pindex)) { if (pindex->GetBlockHash() == hashStop) { @@ -3699,16 +3654,16 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) else { // Find the last block the caller has in the main chain - pindex = locator.GetBlockIndex(); + pindex = chainActive.FindFork(locator); if (pindex) - pindex = pindex->GetNextInMainChain(); + pindex = chainActive.Next(pindex); } // we must use CBlocks, as CBlockHeaders won't include the 0x00 nTx count at the end vector<CBlock> vHeaders; int nLimit = 2000; LogPrint("net", "getheaders %d to %s\n", (pindex ? pindex->nHeight : -1), hashStop.ToString().c_str()); - for (; pindex; pindex = pindex->GetNextInMainChain()) + for (; pindex; pindex = chainActive.Next(pindex)) { vHeaders.push_back(pindex->GetBlockHeader()); if (--nLimit <= 0 || pindex->GetBlockHash() == hashStop) @@ -3722,7 +3677,6 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) { vector<uint256> vWorkQueue; vector<uint256> vEraseQueue; - CDataStream vMsg(vRecv); CTransaction tx; vRecv >> tx; @@ -3863,6 +3817,63 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) } + else if (strCommand == "pong") + { + int64 pingUsecEnd = GetTimeMicros(); + uint64 nonce = 0; + size_t nAvail = vRecv.in_avail(); + bool bPingFinished = false; + std::string sProblem; + + if (nAvail >= sizeof(nonce)) { + vRecv >> nonce; + + // Only process pong message if there is an outstanding ping (old ping without nonce should never pong) + if (pfrom->nPingNonceSent != 0) { + if (nonce == pfrom->nPingNonceSent) { + // Matching pong received, this ping is no longer outstanding + bPingFinished = true; + int64 pingUsecTime = pingUsecEnd - pfrom->nPingUsecStart; + if (pingUsecTime > 0) { + // Successful ping time measurement, replace previous + pfrom->nPingUsecTime = pingUsecTime; + } else { + // This should never happen + sProblem = "Timing mishap"; + } + } else { + // Nonce mismatches are normal when pings are overlapping + sProblem = "Nonce mismatch"; + if (nonce == 0) { + // This is most likely a bug in another implementation somewhere, cancel this ping + bPingFinished = true; + sProblem = "Nonce zero"; + } + } + } else { + sProblem = "Unsolicited pong without ping"; + } + } else { + // This is most likely a bug in another implementation somewhere, cancel this ping + bPingFinished = true; + sProblem = "Short payload"; + } + + if (!(sProblem.empty())) { + LogPrint("net", "pong %s %s: %s, %"PRI64x" expected, %"PRI64x" received, %"PRIszu" bytes\n", + pfrom->addr.ToString().c_str(), + pfrom->strSubVer.c_str(), + sProblem.c_str(), + pfrom->nPingNonceSent, + nonce, + nAvail); + } + if (bPingFinished) { + pfrom->nPingNonceSent = 0; + } + } + + else if (strCommand == "alert") { CAlert alert; @@ -3961,7 +3972,7 @@ bool static ProcessMessage(CNode* pfrom, string strCommand, CDataStream& vRecv) bool ProcessMessages(CNode* pfrom) { //if (fDebug) - // LogPrintf("ProcessMessages(%zu messages)\n", pfrom->vRecvMsg.size()); + // LogPrintf("ProcessMessages(%"PRIszu" messages)\n", pfrom->vRecvMsg.size()); // // Message format @@ -3986,7 +3997,7 @@ bool ProcessMessages(CNode* pfrom) CNetMessage& msg = *it; //if (fDebug) - // LogPrintf("ProcessMessages(message %u msgsz, %zu bytes, complete:%s)\n", + // LogPrintf("ProcessMessages(message %u msgsz, %"PRIszu" bytes, complete:%s)\n", // msg.hdr.nMessageSize, msg.vRecv.size(), // msg.complete() ? "Y" : "N"); @@ -4084,20 +4095,40 @@ bool SendMessages(CNode* pto, bool fSendTrickle) if (pto->nVersion == 0) return true; - // Keep-alive ping. We send a nonce of zero because we don't use it anywhere - // right now. + // + // Message: ping + // + bool pingSend = false; + if (pto->fPingQueued) { + // RPC ping request by user + pingSend = true; + } if (pto->nLastSend && GetTime() - pto->nLastSend > 30 * 60 && pto->vSendMsg.empty()) { + // Ping automatically sent as a keepalive + pingSend = true; + } + if (pingSend) { uint64 nonce = 0; - if (pto->nVersion > BIP0031_VERSION) + while (nonce == 0) { + RAND_bytes((unsigned char*)&nonce, sizeof(nonce)); + } + pto->nPingNonceSent = nonce; + pto->fPingQueued = false; + if (pto->nVersion > BIP0031_VERSION) { + // Take timestamp as close as possible before transmitting ping + pto->nPingUsecStart = GetTimeMicros(); pto->PushMessage("ping", nonce); - else + } else { + // Peer is too old to support ping command with nonce, pong will never arrive, disable timing + pto->nPingUsecStart = 0; pto->PushMessage("ping"); + } } // Start block sync if (pto->fStartSync && !fImporting && !fReindex) { pto->fStartSync = false; - PushGetBlocks(pto, pindexBest, uint256(0)); + PushGetBlocks(pto, chainActive.Tip(), uint256(0)); } // Resend wallet transactions that haven't gotten in a block yet diff --git a/src/main.h b/src/main.h index 95c755221f..76de47071e 100644 --- a/src/main.h +++ b/src/main.h @@ -74,14 +74,8 @@ extern CScript COINBASE_FLAGS; extern CCriticalSection cs_main; extern std::map<uint256, CBlockIndex*> mapBlockIndex; -extern std::vector<CBlockIndex*> vBlockIndexByHeight; extern std::set<CBlockIndex*, CBlockIndexWorkComparator> setBlockIndexValid; -extern CBlockIndex* pindexGenesisBlock; -extern int nBestHeight; -extern uint256 nBestChainWork; extern uint256 nBestInvalidWork; -extern uint256 hashBestChain; -extern CBlockIndex* pindexBest; extern unsigned int nTransactionsUpdated; extern uint64 nLastBlockTx; extern uint64 nLastBlockSize; @@ -153,8 +147,6 @@ void UnloadBlockIndex(); bool VerifyDB(int nCheckLevel, int nCheckDepth); /** Print the loaded block tree */ void PrintBlockTree(); -/** Find a block by height in the currently-connected chain */ -CBlockIndex* FindBlockByHeight(int nHeight); /** Process protocol messages received from a given node */ bool ProcessMessages(CNode* pfrom); /** Send queued protocol messages to be sent to a give node */ @@ -819,15 +811,6 @@ public: return (CBigNum(1)<<256) / (bnTarget+1); } - bool IsInMainChain() const - { - return nHeight < (int)vBlockIndexByHeight.size() && vBlockIndexByHeight[nHeight] == this; - } - - CBlockIndex *GetNextInMainChain() const { - return nHeight+1 >= (int)vBlockIndexByHeight.size() ? NULL : vBlockIndexByHeight[nHeight+1]; - } - bool CheckIndex() const { return CheckProofOfWork(GetBlockHash(), nBits); @@ -849,17 +832,7 @@ public: return pbegin[(pend - pbegin)/2]; } - int64 GetMedianTime() const - { - const CBlockIndex* pindex = this; - for (int i = 0; i < nMedianTimeSpan/2; i++) - { - if (!pindex->GetNextInMainChain()) - return GetBlockTime(); - pindex = pindex->GetNextInMainChain(); - } - return pindex->GetMedianTimePast(); - } + int64 GetMedianTime() const; /** * Returns true if there are nRequired or more blocks of minVersion or above @@ -870,8 +843,8 @@ public: std::string ToString() const { - return strprintf("CBlockIndex(pprev=%p, pnext=%p, nHeight=%d, merkle=%s, hashBlock=%s)", - pprev, GetNextInMainChain(), nHeight, + return strprintf("CBlockIndex(pprev=%p, nHeight=%d, merkle=%s, hashBlock=%s)", + pprev, nHeight, hashMerkleRoot.ToString().c_str(), GetBlockHash().ToString().c_str()); } @@ -1011,64 +984,67 @@ public: } }; +/** An in-memory indexed chain of blocks. */ +class CChain { +private: + std::vector<CBlockIndex*> vChain; - - - - - -/** Describes a place in the block chain to another node such that if the - * other node doesn't have the same branch, it can find a recent common trunk. - * The further back it is, the further before the fork it may be. - */ -class CBlockLocator -{ -protected: - std::vector<uint256> vHave; public: - CBlockLocator() {} + /** Returns the index entry for the genesis block of this chain, or NULL if none. */ + CBlockIndex *Genesis() const { + return vChain.size() > 0 ? vChain[0] : NULL; + } - explicit CBlockLocator(const CBlockIndex* pindex) - { - Set(pindex); + /** Returns the index entry for the tip of this chain, or NULL if none. */ + CBlockIndex *Tip() const { + return vChain.size() > 0 ? vChain[vChain.size() - 1] : NULL; } - explicit CBlockLocator(uint256 hashBlock); + /** Returns the index entry at a particular height in this chain, or NULL if no such height exists. */ + CBlockIndex *operator[](int nHeight) const { + if (nHeight < 0 || nHeight >= (int)vChain.size()) + return NULL; + return vChain[nHeight]; + } - CBlockLocator(const std::vector<uint256>& vHaveIn) - { - vHave = vHaveIn; + /** Compare two chains efficiently. */ + friend bool operator==(const CChain &a, const CChain &b) { + return a.vChain.size() == b.vChain.size() && + a.vChain[a.vChain.size() - 1] == b.vChain[b.vChain.size() - 1]; } - IMPLEMENT_SERIALIZE - ( - if (!(nType & SER_GETHASH)) - READWRITE(nVersion); - READWRITE(vHave); - ) + /** Efficiently check whether a block is present in this chain. */ + bool Contains(const CBlockIndex *pindex) const { + return (*this)[pindex->nHeight] == pindex; + } - void SetNull() - { - vHave.clear(); + /** Find the successor of a block in this chain, or NULL if the given index is not found or is the tip. */ + CBlockIndex *Next(const CBlockIndex *pindex) const { + if (Contains(pindex)) + return (*this)[pindex->nHeight + 1]; + else + return NULL; } - bool IsNull() - { - return vHave.empty(); + /** Return the maximal height in the chain. Is equal to chain.Tip() ? chain.Tip()->nHeight : -1. */ + int Height() const { + return vChain.size() - 1; } - /** Given a block initialises the locator to that point in the chain. */ - void Set(const CBlockIndex* pindex); - /** Returns the distance in blocks this locator is from our chain head. */ - int GetDistanceBack(); - /** Returns the first best-chain block the locator contains. */ - CBlockIndex* GetBlockIndex(); - /** Returns the hash of the first best chain block the locator contains. */ - uint256 GetBlockHash(); - /** Returns the height of the first best chain block the locator has. */ - int GetHeight(); + /** Set/initialize a chain with a given tip. Returns the forking point. */ + CBlockIndex *SetTip(CBlockIndex *pindex); + + /** Return a CBlockLocator that refers to a block in this chain (by default the tip). */ + CBlockLocator GetLocator(const CBlockIndex *pindex = NULL) const; + + /** Find the last common block between this chain and a locator. */ + CBlockIndex *FindFork(const CBlockLocator &locator) const; }; +/** The currently-connected chain of blocks. */ +extern CChain chainActive; + + diff --git a/src/miner.cpp b/src/miner.cpp index bfe382966a..dca8609e17 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -176,7 +176,7 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn) int64 nFees = 0; { LOCK2(cs_main, mempool.cs); - CBlockIndex* pindexPrev = pindexBest; + CBlockIndex* pindexPrev = chainActive.Tip(); CCoinsViewCache view(*pcoinsTip, true); // Priority order to process transactions @@ -238,9 +238,21 @@ CBlockTemplate* CreateNewBlock(const CScript& scriptPubKeyIn) } if (fMissingInputs) continue; - // Priority is sum(valuein * age) / txsize + // Priority is sum(valuein * age) / modified_txsize unsigned int nTxSize = ::GetSerializeSize(tx, SER_NETWORK, PROTOCOL_VERSION); - dPriority /= nTxSize; + unsigned int nTxSizeMod = nTxSize; + // In order to avoid disincentivizing cleaning up the UTXO set we don't count + // the constant overhead for each txin and up to 110 bytes of scriptSig (which + // is enough to cover a compressed pubkey p2sh redemption) for priority. + // Providing any more cleanup incentive than making additional inputs free would + // risk encouraging people to create junk outputs to redeem later. + BOOST_FOREACH(const CTxIn& txin, tx.vin) + { + unsigned int offset = 41U + min(110U, (unsigned int)txin.scriptSig.size()); + if (nTxSizeMod > offset) + nTxSizeMod -= offset; + } + dPriority /= nTxSizeMod; // This is a more accurate fee-per-kilobyte than is used by the client code, because the // client code rounds up the size to the nearest 1K. That's good, because it gives an @@ -467,7 +479,7 @@ bool CheckWork(CBlock* pblock, CWallet& wallet, CReserveKey& reservekey) // Found a solution { LOCK(cs_main); - if (pblock->hashPrevBlock != hashBestChain) + if (pblock->hashPrevBlock != chainActive.Tip()->GetBlockHash()) return error("BitcoinMiner : generated block is stale"); // Remove key from key pool @@ -510,7 +522,7 @@ void static BitcoinMiner(CWallet *pwallet) // Create new block // unsigned int nTransactionsUpdatedLast = nTransactionsUpdated; - CBlockIndex* pindexPrev = pindexBest; + CBlockIndex* pindexPrev = chainActive.Tip(); auto_ptr<CBlockTemplate> pblocktemplate(CreateNewBlockWithKey(reservekey)); if (!pblocktemplate.get()) @@ -613,7 +625,7 @@ void static BitcoinMiner(CWallet *pwallet) break; if (nTransactionsUpdated != nTransactionsUpdatedLast && GetTime() - nStart > 60) break; - if (pindexPrev != pindexBest) + if (pindexPrev != chainActive.Tip()) break; // Update nTime every few seconds diff --git a/src/net.cpp b/src/net.cpp index 2a3e47fd3e..e0fb3eea68 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -426,8 +426,10 @@ void AddressCurrentlyConnected(const CService& addr) - - +uint64 CNode::nTotalBytesRecv = 0; +uint64 CNode::nTotalBytesSent = 0; +CCriticalSection CNode::cs_totalBytesRecv; +CCriticalSection CNode::cs_totalBytesSent; CNode* FindNode(const CNetAddr& ip) { @@ -540,6 +542,8 @@ void CNode::Cleanup() void CNode::PushVersion() { + int nBestHeight = g_signals.GetHeight().get_value_or(0); + /// when NTP implemented, change to just nTime = GetAdjustedTime() int64 nTime = (fInbound ? GetAdjustedTime() : GetTime()); CAddress addrYou = (addr.IsRoutable() && !IsProxy(addr) ? addr : CAddress(CService("0.0.0.0",0))); @@ -620,6 +624,21 @@ void CNode::copyStats(CNodeStats &stats) X(nSendBytes); X(nRecvBytes); stats.fSyncNode = (this == pnodeSync); + + // It is common for nodes with good ping times to suddenly become lagged, + // due to a new block arriving or other large transfer. + // Merely reporting pingtime might fool the caller into thinking the node was still responsive, + // since pingtime does not update until the ping is complete, which might take a while. + // So, if a ping is taking an unusually long time in flight, + // the caller can immediately detect that this is happening. + int64 nPingUsecWait = 0; + if ((0 != nPingNonceSent) && (0 != nPingUsecStart)) { + nPingUsecWait = GetTimeMicros() - nPingUsecStart; + } + + // Raw ping time is in microseconds, but show it to user as whole seconds (Bitcoin users should be well used to small numbers with many decimal places by now :) + stats.dPingTime = (((double)nPingUsecTime) / 1e6); + stats.dPingWait = (((double)nPingUsecWait) / 1e6); } #undef X @@ -716,6 +735,7 @@ void SocketSendData(CNode *pnode) pnode->nLastSend = GetTime(); pnode->nSendBytes += nBytes; pnode->nSendOffset += nBytes; + pnode->RecordBytesSent(nBytes); if (pnode->nSendOffset == data.size()) { pnode->nSendOffset = 0; pnode->nSendSize -= data.size(); @@ -811,10 +831,9 @@ void ThreadSocketHandler() } } } - if (vNodes.size() != nPrevNodeCount) - { + if(vNodes.size() != nPrevNodeCount) { nPrevNodeCount = vNodes.size(); - uiInterface.NotifyNumConnectionsChanged(vNodes.size()); + uiInterface.NotifyNumConnectionsChanged(nPrevNodeCount); } @@ -993,6 +1012,7 @@ void ThreadSocketHandler() pnode->CloseSocketDisconnect(); pnode->nLastRecv = GetTime(); pnode->nRecvBytes += nBytes; + pnode->RecordBytesRecv(nBytes); } else if (nBytes == 0) { @@ -1467,6 +1487,8 @@ void static StartSync(const vector<CNode*> &vNodes) { CNode *pnodeNewSync = NULL; double dBestScore = 0; + int nBestHeight = g_signals.GetHeight().get_value_or(0); + // Iterate over all nodes BOOST_FOREACH(CNode* pnode, vNodes) { // check preconditions for allowing a sync @@ -1844,3 +1866,27 @@ void RelayTransaction(const CTransaction& tx, const uint256& hash, const CDataSt pnode->PushInventory(inv); } } + +void CNode::RecordBytesRecv(uint64 bytes) +{ + LOCK(cs_totalBytesRecv); + nTotalBytesRecv += bytes; +} + +void CNode::RecordBytesSent(uint64 bytes) +{ + LOCK(cs_totalBytesSent); + nTotalBytesSent += bytes; +} + +uint64 CNode::GetTotalBytesRecv() +{ + LOCK(cs_totalBytesRecv); + return nTotalBytesRecv; +} + +uint64 CNode::GetTotalBytesSent() +{ + LOCK(cs_totalBytesSent); + return nTotalBytesSent; +} @@ -28,7 +28,6 @@ static const unsigned int MAX_INV_SZ = 50000; class CNode; class CBlockIndex; -extern int nBestHeight; @@ -52,6 +51,7 @@ void SocketSendData(CNode *pnode); // Signals for message handling struct CNodeSignals { + boost::signals2::signal<int ()> GetHeight; boost::signals2::signal<bool (CNode*)> ProcessMessages; boost::signals2::signal<bool (CNode*, bool)> SendMessages; }; @@ -119,6 +119,8 @@ public: uint64 nSendBytes; uint64 nRecvBytes; bool fSyncNode; + double dPingTime; + double dPingWait; }; @@ -234,6 +236,12 @@ public: CCriticalSection cs_inventory; std::multimap<int64, CInv> mapAskFor; + // Ping time measurement + uint64 nPingNonceSent; + int64 nPingUsecStart; + int64 nPingUsecTime; + bool fPingQueued; + CNode(SOCKET hSocketIn, CAddress addrIn, std::string addrNameIn = "", bool fInboundIn=false) : ssSend(SER_NETWORK, MIN_PROTO_VERSION) { nServices = 0; @@ -268,6 +276,10 @@ public: fRelayTxes = false; setInventoryKnown.max_size(SendBufferSize() / 1000); pfilter = new CBloomFilter(); + nPingNonceSent = 0; + nPingUsecStart = 0; + nPingUsecTime = 0; + fPingQueued = false; // Be shy and don't send version until we hear if (hSocket != INVALID_SOCKET && !fInbound) @@ -286,8 +298,15 @@ public: } private: + // Network usage totals + static CCriticalSection cs_totalBytesRecv; + static CCriticalSection cs_totalBytesSent; + static uint64 nTotalBytesRecv; + static uint64 nTotalBytesSent; + CNode(const CNode&); void operator=(const CNode&); + public: @@ -301,7 +320,7 @@ public: unsigned int GetTotalRecvSize() { unsigned int total = 0; - BOOST_FOREACH(const CNetMessage &msg, vRecvMsg) + BOOST_FOREACH(const CNetMessage &msg, vRecvMsg) total += msg.vRecv.size() + 24; return total; } @@ -634,6 +653,13 @@ public: static bool IsBanned(CNetAddr ip); bool Misbehaving(int howmuch); // 1 == a little, 100 == a lot void copyStats(CNodeStats &stats); + + // Network stats + static void RecordBytesRecv(uint64 bytes); + static void RecordBytesSent(uint64 bytes); + + static uint64 GetTotalBytesRecv(); + static uint64 GetTotalBytesSent(); }; diff --git a/src/qt/Makefile.am b/src/qt/Makefile.am index 248dcba0ec..5892f6aca0 100644 --- a/src/qt/Makefile.am +++ b/src/qt/Makefile.am @@ -48,10 +48,10 @@ QT_MOC_CPP = moc_aboutdialog.cpp moc_addressbookpage.cpp \ moc_optionsmodel.cpp moc_overviewpage.cpp moc_paymentserver.cpp \ moc_qrcodedialog.cpp moc_qvalidatedlineedit.cpp moc_qvaluecombobox.cpp \ moc_rpcconsole.cpp moc_sendcoinsdialog.cpp moc_sendcoinsentry.cpp \ - moc_signverifymessagedialog.cpp moc_splashscreen.cpp moc_transactiondesc.cpp \ + moc_signverifymessagedialog.cpp moc_splashscreen.cpp moc_trafficgraphwidget.cpp moc_transactiondesc.cpp \ moc_transactiondescdialog.cpp moc_transactionfilterproxy.cpp \ moc_transactiontablemodel.cpp moc_transactionview.cpp moc_walletframe.cpp \ - moc_walletmodel.cpp moc_walletstack.cpp moc_walletview.cpp + moc_walletmodel.cpp moc_walletview.cpp BITCOIN_MM = macdockiconhandler.mm macnotificationhandler.mm QR_CPP = qrcodedialog.cpp @@ -73,9 +73,9 @@ BITCOIN_QT_H = aboutdialog.h addressbookpage.h addresstablemodel.h \ optionsmodel.h overviewpage.h paymentrequestplus.h paymentserver.h \ qrcodedialog.h qvalidatedlineedit.h qvaluecombobox.h rpcconsole.h \ sendcoinsdialog.h sendcoinsentry.h signverifymessagedialog.h splashscreen.h \ - transactiondescdialog.h transactiondesc.h transactionfilterproxy.h \ + trafficgraphwidget.h transactiondescdialog.h transactiondesc.h transactionfilterproxy.h \ transactionrecord.h transactiontablemodel.h transactionview.h walletframe.h \ - walletmodel.h walletmodeltransaction.h walletstack.h walletview.h + walletmodel.h walletmodeltransaction.h walletview.h RES_ICONS = res/icons/bitcoin.png res/icons/address-book.png \ res/icons/quit.png res/icons/send.png res/icons/toolbar.png \ @@ -102,10 +102,10 @@ BITCOIN_QT_CPP = aboutdialog.cpp addressbookpage.cpp \ optionsdialog.cpp optionsmodel.cpp overviewpage.cpp paymentrequestplus.cpp \ paymentserver.cpp qvalidatedlineedit.cpp qvaluecombobox.cpp \ rpcconsole.cpp sendcoinsdialog.cpp sendcoinsentry.cpp \ - signverifymessagedialog.cpp splashscreen.cpp transactiondesc.cpp \ + signverifymessagedialog.cpp splashscreen.cpp trafficgraphwidget.cpp transactiondesc.cpp \ transactiondescdialog.cpp transactionfilterproxy.cpp transactionrecord.cpp \ transactiontablemodel.cpp transactionview.cpp walletframe.cpp \ - walletmodel.cpp walletmodeltransaction.cpp walletstack.cpp walletview.cpp + walletmodel.cpp walletmodeltransaction.cpp walletview.cpp RES_IMAGES = res/images/about.png res/images/splash.png \ res/images/splash_testnet.png diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index 78693971da..e73a82978a 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -333,8 +333,8 @@ int main(int argc, char *argv[]) paymentServer, SLOT(fetchPaymentACK(CWallet*,const SendCoinsRecipient&,QByteArray))); QObject::connect(paymentServer, SIGNAL(receivedPaymentACK(QString)), &window, SLOT(showPaymentACK(QString))); - QObject::connect(paymentServer, SIGNAL(reportError(QString, QString, unsigned int)), - guiref, SLOT(message(QString, QString, unsigned int))); + QObject::connect(paymentServer, SIGNAL(message(QString,QString,unsigned int)), + guiref, SLOT(message(QString,QString,unsigned int))); QTimer::singleShot(100, paymentServer, SLOT(uiReady())); app.exec(); diff --git a/src/qt/bitcoinamountfield.cpp b/src/qt/bitcoinamountfield.cpp index d9d4e3b23d..37b8743eff 100644 --- a/src/qt/bitcoinamountfield.cpp +++ b/src/qt/bitcoinamountfield.cpp @@ -130,9 +130,10 @@ void BitcoinAmountField::setValue(qint64 value) setText(BitcoinUnits::format(currentUnit, value)); } -void BitcoinAmountField::setReadOnly(bool fReadeOnly) +void BitcoinAmountField::setReadOnly(bool fReadOnly) { - // TODO ... + amount->setReadOnly(fReadOnly); + unit->setEnabled(!fReadOnly); } void BitcoinAmountField::unitChanged(int idx) diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 23a221120f..3336a8afd3 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -447,26 +447,31 @@ void BitcoinGUI::aboutClicked() void BitcoinGUI::gotoOverviewPage() { + overviewAction->setChecked(true); if (walletFrame) walletFrame->gotoOverviewPage(); } void BitcoinGUI::gotoHistoryPage() { + historyAction->setChecked(true); if (walletFrame) walletFrame->gotoHistoryPage(); } void BitcoinGUI::gotoAddressBookPage() { + addressBookAction->setChecked(true); if (walletFrame) walletFrame->gotoAddressBookPage(); } void BitcoinGUI::gotoReceiveCoinsPage() { + receiveCoinsAction->setChecked(true); if (walletFrame) walletFrame->gotoReceiveCoinsPage(); } void BitcoinGUI::gotoSendCoinsPage(QString addr) { + sendCoinsAction->setChecked(true); if (walletFrame) walletFrame->gotoSendCoinsPage(addr); } diff --git a/src/qt/bitcoingui.h b/src/qt/bitcoingui.h index e2dd5dc6bc..e5a92fed93 100644 --- a/src/qt/bitcoingui.h +++ b/src/qt/bitcoingui.h @@ -61,14 +61,6 @@ public: void removeAllWallets(); - /** Used by WalletView to allow access to needed QActions */ - // Todo: Use Qt signals for these - QAction * getOverviewAction() { return overviewAction; } - QAction * getHistoryAction() { return historyAction; } - QAction * getAddressBookAction() { return addressBookAction; } - QAction * getReceiveCoinsAction() { return receiveCoinsAction; } - QAction * getSendCoinsAction() { return sendCoinsAction; } - protected: void changeEvent(QEvent *e); void closeEvent(QCloseEvent *event); diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index b33f534f7c..212fa6974a 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -42,7 +42,7 @@ int ClientModel::getNumConnections() const int ClientModel::getNumBlocks() const { - return nBestHeight; + return chainActive.Height(); } int ClientModel::getNumBlocksAtStartup() @@ -51,19 +51,27 @@ int ClientModel::getNumBlocksAtStartup() return numBlocksAtStartup; } +quint64 ClientModel::getTotalBytesRecv() const +{ + return CNode::GetTotalBytesRecv(); +} + +quint64 ClientModel::getTotalBytesSent() const +{ + return CNode::GetTotalBytesSent(); +} + QDateTime ClientModel::getLastBlockDate() const { - if (pindexBest) - return QDateTime::fromTime_t(pindexBest->GetBlockTime()); - else if(!isTestNet()) - return QDateTime::fromTime_t(1231006505); // Genesis block's time + if (chainActive.Tip()) + return QDateTime::fromTime_t(chainActive.Tip()->GetBlockTime()); else - return QDateTime::fromTime_t(1296688602); // Genesis block's time (testnet) + return QDateTime::fromTime_t(Params().GenesisBlock().nTime); // Genesis block's time of current network } double ClientModel::getVerificationProgress() const { - return Checkpoints::GuessVerificationProgress(pindexBest); + return Checkpoints::GuessVerificationProgress(chainActive.Tip()); } void ClientModel::updateTimer() @@ -85,6 +93,8 @@ void ClientModel::updateTimer() // ensure we return the maximum of newNumBlocksOfPeers and newNumBlocks to not create weird displays in the GUI emit numBlocksChanged(newNumBlocks, std::max(newNumBlocksOfPeers, newNumBlocks)); } + + emit bytesChanged(getTotalBytesRecv(), getTotalBytesSent()); } void ClientModel::updateNumConnections(int numConnections) diff --git a/src/qt/clientmodel.h b/src/qt/clientmodel.h index 15074300b4..925f20acd9 100644 --- a/src/qt/clientmodel.h +++ b/src/qt/clientmodel.h @@ -35,6 +35,9 @@ public: int getNumBlocks() const; int getNumBlocksAtStartup(); + quint64 getTotalBytesRecv() const; + quint64 getTotalBytesSent() const; + double getVerificationProgress() const; QDateTime getLastBlockDate() const; @@ -74,6 +77,7 @@ signals: void numConnectionsChanged(int count); void numBlocksChanged(int count, int countOfPeers); void alertsChanged(const QString &warnings); + void bytesChanged(quint64 totalBytesIn, quint64 totalBytesOut); //! Asynchronous message notification void message(const QString &title, const QString &message, unsigned int style); diff --git a/src/qt/forms/rpcconsole.ui b/src/qt/forms/rpcconsole.ui index d1d8ab42a0..54c41ffb67 100644 --- a/src/qt/forms/rpcconsole.ui +++ b/src/qt/forms/rpcconsole.ui @@ -445,10 +445,271 @@ </item> </layout> </widget> + <widget class="QWidget" name="tab"> + <attribute name="title"> + <string>&Network Traffic</string> + </attribute> + <layout class="QHBoxLayout" name="horizontalLayout_3"> + <item> + <layout class="QVBoxLayout" name="verticalLayout_4"> + <item> + <widget class="TrafficGraphWidget" name="trafficGraph" native="true"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Expanding" vsizetype="Expanding"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + </widget> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_2"> + <item> + <widget class="QSlider" name="sldGraphRange"> + <property name="minimum"> + <number>1</number> + </property> + <property name="maximum"> + <number>288</number> + </property> + <property name="pageStep"> + <number>12</number> + </property> + <property name="value"> + <number>6</number> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="lblGraphRange"> + <property name="minimumSize"> + <size> + <width>100</width> + <height>0</height> + </size> + </property> + <property name="alignment"> + <set>Qt::AlignCenter</set> + </property> + </widget> + </item> + <item> + <widget class="QPushButton" name="btnClearTrafficGraph"> + <property name="text"> + <string>&Clear</string> + </property> + </widget> + </item> + </layout> + </item> + </layout> + </item> + <item> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Totals</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_5"> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_4"> + <item> + <widget class="Line" name="line"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>10</width> + <height>0</height> + </size> + </property> + <property name="palette"> + <palette> + <active> + <colorrole role="Light"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>0</red> + <green>255</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + </active> + <inactive> + <colorrole role="Light"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>0</red> + <green>255</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + </inactive> + <disabled> + <colorrole role="Light"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>0</red> + <green>255</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + </disabled> + </palette> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_16"> + <property name="text"> + <string>In:</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="lblBytesIn"> + <property name="minimumSize"> + <size> + <width>50</width> + <height>0</height> + </size> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + </layout> + </item> + <item> + <layout class="QHBoxLayout" name="horizontalLayout_5"> + <item> + <widget class="Line" name="line_2"> + <property name="sizePolicy"> + <sizepolicy hsizetype="Fixed" vsizetype="Fixed"> + <horstretch>0</horstretch> + <verstretch>0</verstretch> + </sizepolicy> + </property> + <property name="minimumSize"> + <size> + <width>10</width> + <height>0</height> + </size> + </property> + <property name="palette"> + <palette> + <active> + <colorrole role="Light"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>0</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + </active> + <inactive> + <colorrole role="Light"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>0</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + </inactive> + <disabled> + <colorrole role="Light"> + <brush brushstyle="SolidPattern"> + <color alpha="255"> + <red>255</red> + <green>0</green> + <blue>0</blue> + </color> + </brush> + </colorrole> + </disabled> + </palette> + </property> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="label_17"> + <property name="text"> + <string>Out:</string> + </property> + </widget> + </item> + <item> + <widget class="QLabel" name="lblBytesOut"> + <property name="minimumSize"> + <size> + <width>50</width> + <height>0</height> + </size> + </property> + <property name="alignment"> + <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> + </property> + </widget> + </item> + </layout> + </item> + <item> + <spacer name="verticalSpacer_4"> + <property name="orientation"> + <enum>Qt::Vertical</enum> + </property> + <property name="sizeHint" stdset="0"> + <size> + <width>20</width> + <height>407</height> + </size> + </property> + </spacer> + </item> + </layout> + </widget> + </item> + </layout> + </item> + </layout> + </widget> </widget> </item> </layout> </widget> + <customwidgets> + <customwidget> + <class>TrafficGraphWidget</class> + <extends>QWidget</extends> + <header>trafficgraphwidget.h</header> + <container>1</container> + <slots> + <slot>clear()</slot> + </slots> + </customwidget> + </customwidgets> <resources> <include location="../bitcoin.qrc"/> </resources> diff --git a/src/qt/forms/sendcoinsentry.ui b/src/qt/forms/sendcoinsentry.ui index 2c1cec600c..5c6afd6c71 100644 --- a/src/qt/forms/sendcoinsentry.ui +++ b/src/qt/forms/sendcoinsentry.ui @@ -621,7 +621,7 @@ <set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set> </property> <property name="buddy"> - <cstring>payAmount</cstring> + <cstring>payAmount_s</cstring> </property> </widget> </item> @@ -640,15 +640,9 @@ </item> <item row="5" column="2"> <widget class="BitcoinAmountField" name="payAmount_s"> - <property name="enabled"> - <bool>false</bool> - </property> <property name="acceptDrops"> <bool>false</bool> </property> - <property name="readOnly"> - <bool>true</bool> - </property> </widget> </item> <item row="3" column="2"> diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index ae5217e931..af75d6b4e5 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -48,6 +48,7 @@ const int BITCOIN_IPC_CONNECT_TIMEOUT = 1000; // milliseconds const QString BITCOIN_IPC_PREFIX("bitcoin:"); const char* BITCOIN_REQUEST_MIMETYPE = "application/bitcoin-paymentrequest"; const char* BITCOIN_PAYMENTACK_MIMETYPE = "application/bitcoin-paymentack"; +const char* BITCOIN_PAYMENTACK_CONTENTTYPE = "application/bitcoin-payment"; X509_STORE* PaymentServer::certStore = NULL; void PaymentServer::freeCertStore() @@ -188,7 +189,7 @@ bool PaymentServer::ipcSendCommandLine(int argc, char* argv[]) if (arg.startsWith("-")) continue; - if (arg.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin: + if (arg.startsWith(BITCOIN_IPC_PREFIX, Qt::CaseInsensitive)) // bitcoin: URI { savedPaymentRequests.append(arg); @@ -219,9 +220,9 @@ bool PaymentServer::ipcSendCommandLine(int argc, char* argv[]) } else { - qDebug() << "PaymentServer::ipcSendCommandLine : Payment request file does not exist: " << argv[i]; // Printing to debug.log is about the best we can do here, the // GUI hasn't started yet so we can't pop up a message box. + qDebug() << "PaymentServer::ipcSendCommandLine : Payment request file does not exist: " << arg; } } @@ -245,6 +246,7 @@ bool PaymentServer::ipcSendCommandLine(int argc, char* argv[]) delete socket; fResult = true; } + return fResult; } @@ -254,7 +256,9 @@ PaymentServer::PaymentServer(QObject* parent, bool startLocalServer) : QObject(p // compatible with the version of the headers we compiled against. GOOGLE_PROTOBUF_VERIFY_VERSION; - // Install global event filter to catch QFileOpenEvents on the mac (sent when you click bitcoin: links) + // Install global event filter to catch QFileOpenEvents + // on Mac: sent when you click bitcoin: links + // other OSes: helpful when dealing with payment-request files (in the future) if (parent) parent->installEventFilter(this); @@ -309,7 +313,7 @@ void PaymentServer::initNetManager() if (netManager != NULL) delete netManager; - // netManager is used to fetch paymentrequests given in bitcoin: URI's + // netManager is used to fetch paymentrequests given in bitcoin: URIs netManager = new QNetworkAccessManager(this); // Use proxy settings from optionsModel: @@ -359,7 +363,8 @@ void PaymentServer::handleURIOrFile(const QString& s) #endif if (uri.hasQueryItem("request")) { - QByteArray temp; temp.append(uri.queryItemValue("request")); + QByteArray temp; + temp.append(uri.queryItemValue("request")); QString decoded = QUrl::fromPercentEncoding(temp); QUrl fetchUrl(decoded, QUrl::StrictMode); @@ -369,13 +374,17 @@ void PaymentServer::handleURIOrFile(const QString& s) if (fetchUrl.isValid()) fetchRequest(fetchUrl); else - qDebug() << "PaymentServer::handleURIOrFile : Invalid url: " << fetchUrl; + qDebug() << "PaymentServer::handleURIOrFile : Invalid URL: " << fetchUrl; return; } SendCoinsRecipient recipient; if (GUIUtil::parseBitcoinURI(s, &recipient)) emit receivedPaymentRequest(recipient); + else + emit message(tr("URI handling"), + tr("URI can not be parsed! This can be caused by an invalid Bitcoin address or malformed URI parameters."), + CClientUIInterface::ICON_WARNING); return; } @@ -407,10 +416,10 @@ void PaymentServer::handleURIConnection() if (clientConnection->bytesAvailable() < (int)sizeof(quint16)) { return; } - QString message; - in >> message; + QString msg; + in >> msg; - handleURIOrFile(message); + handleURIOrFile(msg); } bool PaymentServer::readPaymentRequest(const QString& filename, PaymentRequestPlus& request) @@ -443,11 +452,11 @@ bool PaymentServer::processPaymentRequest(PaymentRequestPlus& request, QList<Sen foreach(const PAIRTYPE(CScript, qint64)& sendingTo, sendingTos) { CTxOut txOut(sendingTo.second, sendingTo.first); if (txOut.IsDust(CTransaction::nMinRelayTxFee)) { - QString message = QObject::tr("Requested payment amount (%1) too small") + QString msg = QObject::tr("Requested payment amount (%1) too small") .arg(BitcoinUnits::formatWithUnit(optionsModel->getDisplayUnit(), sendingTo.second)); - qDebug() << "PaymentServer::processPaymentRequest : " << message; - emit reportError(tr("Payment request error"), message, CClientUIInterface::MODAL); + qDebug() << "PaymentServer::processPaymentRequest : " << msg; + emit message(tr("Payment request error"), msg, CClientUIInterface::MSG_ERROR); return false; } @@ -471,11 +480,7 @@ bool PaymentServer::processPaymentRequest(PaymentRequestPlus& request, QList<Sen recipients.append(SendCoinsRecipient()); recipients[i].amount = sendingTo.second; QString memo = QString::fromStdString(request.getDetails().memo()); -#if QT_VERSION < 0x050000 - recipients[i].label = Qt::escape(memo); -#else - recipients[i].label = memo.toHtmlEscaped(); -#endif + recipients[i].label = GUIUtil::HtmlEscape(memo); CTxDestination dest; if (ExtractDestination(sendingTo.first, dest)) { if (i == 0) // Tie request to first pay-to, we don't want multiple ACKs @@ -488,9 +493,9 @@ bool PaymentServer::processPaymentRequest(PaymentRequestPlus& request, QList<Sen // Insecure payments to custom bitcoin addresses are not supported // (there is no good way to tell the user where they are paying in a way // they'd have a chance of understanding). - emit reportError(tr("Payment request error"), - tr("Insecure requests to custom payment scripts unsupported"), - CClientUIInterface::MODAL); + emit message(tr("Payment request error"), + tr("Insecure requests to custom payment scripts unsupported"), + CClientUIInterface::MSG_ERROR); return false; } } @@ -518,7 +523,7 @@ void PaymentServer::fetchPaymentACK(CWallet* wallet, SendCoinsRecipient recipien QNetworkRequest netRequest; netRequest.setAttribute(QNetworkRequest::User, "PaymentACK"); netRequest.setUrl(QString::fromStdString(details.payment_url())); - netRequest.setHeader(QNetworkRequest::ContentTypeHeader, "application/bitcoin-payment"); + netRequest.setHeader(QNetworkRequest::ContentTypeHeader, BITCOIN_PAYMENTACK_CONTENTTYPE); netRequest.setRawHeader("User-Agent", CLIENT_NAME.c_str()); netRequest.setRawHeader("Accept", BITCOIN_PAYMENTACK_MIMETYPE); @@ -569,11 +574,11 @@ void PaymentServer::netRequestFinished(QNetworkReply* reply) reply->deleteLater(); if (reply->error() != QNetworkReply::NoError) { - QString message = QObject::tr("Error communicating with %1: %2") + QString msg = QObject::tr("Error communicating with %1: %2") .arg(reply->request().url().toString()) .arg(reply->errorString()); - qDebug() << "PaymentServer::netRequestFinished : " << message; - emit reportError(tr("Network request error"), message, CClientUIInterface::MODAL); + qDebug() << "PaymentServer::netRequestFinished : " << msg; + emit message(tr("Network request error"), msg, CClientUIInterface::MSG_ERROR); return; } @@ -598,10 +603,10 @@ void PaymentServer::netRequestFinished(QNetworkReply* reply) payments::PaymentACK paymentACK; if (!paymentACK.ParseFromArray(data.data(), data.size())) { - QString message = QObject::tr("Bad response from server %1") + QString msg = QObject::tr("Bad response from server %1") .arg(reply->request().url().toString()); - qDebug() << "PaymentServer::netRequestFinished : " << message; - emit reportError(tr("Network request error"), message, CClientUIInterface::MODAL); + qDebug() << "PaymentServer::netRequestFinished : " << msg; + emit message(tr("Network request error"), msg, CClientUIInterface::MSG_ERROR); } else { emit receivedPaymentACK(QString::fromStdString(paymentACK.memo())); @@ -618,7 +623,7 @@ void PaymentServer::reportSslErrors(QNetworkReply* reply, const QList<QSslError> qDebug() << "PaymentServer::reportSslErrors : " << err; errString += err.errorString() + "\n"; } - emit reportError(tr("Network request error"), errString, CClientUIInterface::MODAL); + emit message(tr("Network request error"), errString, CClientUIInterface::MSG_ERROR); } void PaymentServer::setOptionsModel(OptionsModel *optionsModel) diff --git a/src/qt/paymentserver.h b/src/qt/paymentserver.h index f9d827204b..042c41ef64 100644 --- a/src/qt/paymentserver.h +++ b/src/qt/paymentserver.h @@ -90,8 +90,8 @@ signals: // Fired when a valid PaymentACK is received void receivedPaymentACK(QString); - // Fired when an error should be reported to the user - void reportError(QString, QString, unsigned int); + // Fired when a message should be reported to the user + void message(const QString &title, const QString &message, unsigned int style); public slots: // Signal this when the main window's UI is ready diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index 8953c36579..e7dcdf62a1 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -22,6 +22,8 @@ const int CONSOLE_HISTORY = 50; const QSize ICON_SIZE(24, 24); +const int INITIAL_TRAFFIC_GRAPH_MINS = 30; + const struct { const char *url; const char *source; @@ -204,6 +206,7 @@ RPCConsole::RPCConsole(QWidget *parent) : ui->openSSLVersion->setText(SSLeay_version(SSLEAY_VERSION)); startExecutor(); + setTrafficGraphRange(INITIAL_TRAFFIC_GRAPH_MINS); clear(); } @@ -253,7 +256,8 @@ bool RPCConsole::eventFilter(QObject* obj, QEvent *event) void RPCConsole::setClientModel(ClientModel *model) { - this->clientModel = model; + clientModel = model; + ui->trafficGraph->setClientModel(model); if(model) { // Keep up to date with client @@ -263,6 +267,9 @@ void RPCConsole::setClientModel(ClientModel *model) setNumBlocks(model->getNumBlocks(), model->getNumBlocksOfPeers()); connect(model, SIGNAL(numBlocksChanged(int,int)), this, SLOT(setNumBlocks(int,int))); + updateTrafficStats(model->getTotalBytesRecv(), model->getTotalBytesSent()); + connect(model, SIGNAL(bytesChanged(quint64,quint64)), this, SLOT(updateTrafficStats(quint64, quint64))); + // Provide initial values ui->clientVersion->setText(model->formatFullVersion()); ui->clientName->setText(model->clientName()); @@ -431,3 +438,49 @@ void RPCConsole::on_showCLOptionsButton_clicked() GUIUtil::HelpMessageBox help; help.exec(); } + +void RPCConsole::on_sldGraphRange_valueChanged(int value) +{ + const int multiplier = 5; // each position on the slider represents 5 min + int mins = value * multiplier; + setTrafficGraphRange(mins); +} + +QString RPCConsole::FormatBytes(quint64 bytes) +{ + if(bytes < 1024) + return QString(tr("%1 B")).arg(bytes); + if(bytes < 1024 * 1024) + return QString(tr("%1 KB")).arg(bytes / 1024); + if(bytes < 1024 * 1024 * 1024) + return QString(tr("%1 MB")).arg(bytes / 1024 / 1024); + + return QString(tr("%1 GB")).arg(bytes / 1024 / 1024 / 1024); +} + +void RPCConsole::setTrafficGraphRange(int mins) +{ + ui->trafficGraph->setGraphRangeMins(mins); + if(mins < 60) { + ui->lblGraphRange->setText(QString(tr("%1 m")).arg(mins)); + } else { + int hours = mins / 60; + int minsLeft = mins % 60; + if(minsLeft == 0) { + ui->lblGraphRange->setText(QString(tr("%1 h")).arg(hours)); + } else { + ui->lblGraphRange->setText(QString(tr("%1 h %2 m")).arg(hours).arg(minsLeft)); + } + } +} + +void RPCConsole::updateTrafficStats(quint64 totalBytesIn, quint64 totalBytesOut) +{ + ui->lblBytesIn->setText(FormatBytes(totalBytesIn)); + ui->lblBytesOut->setText(FormatBytes(totalBytesOut)); +} + +void RPCConsole::on_btnClearTrafficGraph_clicked() +{ + ui->trafficGraph->clear(); +} diff --git a/src/qt/rpcconsole.h b/src/qt/rpcconsole.h index 3c38b4b8de..af92b55770 100644 --- a/src/qt/rpcconsole.h +++ b/src/qt/rpcconsole.h @@ -37,6 +37,12 @@ private slots: void on_openDebugLogfileButton_clicked(); /** display messagebox with program parameters (same as bitcoin-qt --help) */ void on_showCLOptionsButton_clicked(); + /** change the time range of the network traffic graph */ + void on_sldGraphRange_valueChanged(int value); + /** update traffic statistics */ + void updateTrafficStats(quint64 totalBytesIn, quint64 totalBytesOut); + /** clear traffic graph */ + void on_btnClearTrafficGraph_clicked(); public slots: void clear(); @@ -55,6 +61,9 @@ signals: void cmdRequest(const QString &command); private: + static QString FormatBytes(quint64 bytes); + void setTrafficGraphRange(int mins); + Ui::RPCConsole *ui; ClientModel *clientModel; QStringList history; diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index 00cea463ef..3fd4a26e76 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -365,9 +365,8 @@ bool SendCoinsDialog::handlePaymentRequest(const SendCoinsRecipient &rv) else { CBitcoinAddress address(rv.address.toStdString()); if (!address.IsValid()) { - QString strAddress(address.ToString().c_str()); QMessageBox::warning(this, strSendCoins, - tr("Invalid payment address %1").arg(strAddress)); + tr("Invalid payment address %1").arg(rv.address)); return false; } } diff --git a/src/qt/sendcoinsentry.cpp b/src/qt/sendcoinsentry.cpp index ee84f7bc11..188b8860a9 100644 --- a/src/qt/sendcoinsentry.cpp +++ b/src/qt/sendcoinsentry.cpp @@ -60,12 +60,7 @@ void SendCoinsEntry::on_addressBookButton_clicked() void SendCoinsEntry::on_payTo_textChanged(const QString &address) { - if(!model) - return; - // Fill in label from address book, if address has an associated label - QString associatedLabel = model->getAddressTableModel()->labelForAddress(address); - if(!associatedLabel.isEmpty()) - ui->addAsLabel->setText(associatedLabel); + updateLabel(address); } void SendCoinsEntry::setModel(WalletModel *model) @@ -85,10 +80,17 @@ void SendCoinsEntry::setRemoveEnabled(bool enabled) void SendCoinsEntry::clear() { + // clear UI elements for insecure payments ui->payTo->clear(); ui->addAsLabel->clear(); ui->payAmount->clear(); + // and the ones for secure payments just to be sure + ui->payTo_s->clear(); + ui->memoTextLabel_s->clear(); + ui->payAmount_s->clear(); + ui->payTo->setFocus(); + // update the display unit, to not use the default ("BTC") updateDisplayUnit(); } @@ -154,17 +156,20 @@ void SendCoinsEntry::setValue(const SendCoinsRecipient &value) { recipient = value; - ui->payTo->setText(value.address); - ui->addAsLabel->setText(value.label); - ui->payAmount->setValue(value.amount); - - if (!recipient.authenticatedMerchant.isEmpty()) + if (recipient.authenticatedMerchant.isEmpty()) + { + ui->payTo->setText(recipient.address); + ui->addAsLabel->setText(recipient.label); + ui->payAmount->setValue(recipient.amount); + } + else { - const payments::PaymentDetails& details = value.paymentRequest.getDetails(); + const payments::PaymentDetails& details = recipient.paymentRequest.getDetails(); - ui->payTo_s->setText(value.authenticatedMerchant); + ui->payTo_s->setText(recipient.authenticatedMerchant); ui->memoTextLabel_s->setText(QString::fromStdString(details.memo())); - ui->payAmount_s->setValue(value.amount); + ui->payAmount_s->setValue(recipient.amount); + ui->payAmount_s->setReadOnly(true); setCurrentWidget(ui->SendCoinsSecure); } } @@ -194,3 +199,19 @@ void SendCoinsEntry::updateDisplayUnit() ui->payAmount_s->setDisplayUnit(model->getOptionsModel()->getDisplayUnit()); } } + +bool SendCoinsEntry::updateLabel(const QString &address) +{ + if(!model) + return false; + + // Fill in label from address book, if address has an associated label + QString associatedLabel = model->getAddressTableModel()->labelForAddress(address); + if(!associatedLabel.isEmpty()) + { + ui->addAsLabel->setText(associatedLabel); + return true; + } + + return false; +} diff --git a/src/qt/sendcoinsentry.h b/src/qt/sendcoinsentry.h index 49e622daf1..66d9752909 100644 --- a/src/qt/sendcoinsentry.h +++ b/src/qt/sendcoinsentry.h @@ -58,6 +58,8 @@ private: SendCoinsRecipient recipient; Ui::SendCoinsEntry *ui; WalletModel *model; + + bool updateLabel(const QString &address); }; #endif // SENDCOINSENTRY_H diff --git a/src/qt/trafficgraphwidget.cpp b/src/qt/trafficgraphwidget.cpp new file mode 100644 index 0000000000..d49bc31f3e --- /dev/null +++ b/src/qt/trafficgraphwidget.cpp @@ -0,0 +1,169 @@ +#include "trafficgraphwidget.h" +#include "clientmodel.h" + +#include <QPainter> +#include <QColor> +#include <QTimer> + +#include <cmath> + +#define DESIRED_SAMPLES 800 + +#define XMARGIN 10 +#define YMARGIN 10 + +TrafficGraphWidget::TrafficGraphWidget(QWidget *parent) : + QWidget(parent), + timer(0), + fMax(0.0f), + nMins(0), + vSamplesIn(), + vSamplesOut(), + nLastBytesIn(0), + nLastBytesOut(0), + clientModel(0) +{ + timer = new QTimer(this); + connect(timer, SIGNAL(timeout()), SLOT(updateRates())); +} + +void TrafficGraphWidget::setClientModel(ClientModel *model) +{ + clientModel = model; + if(model) { + nLastBytesIn = model->getTotalBytesRecv(); + nLastBytesOut = model->getTotalBytesSent(); + } +} + +int TrafficGraphWidget::getGraphRangeMins() const +{ + return nMins; +} + +void TrafficGraphWidget::paintPath(QPainterPath &path, QQueue<float> &samples) +{ + int h = height() - YMARGIN * 2, w = width() - XMARGIN * 2; + int sampleCount = samples.size(), x = XMARGIN + w, y; + if(sampleCount > 0) { + path.moveTo(x, YMARGIN + h); + for(int i = 0; i < sampleCount; ++i) { + x = XMARGIN + w - w * i / DESIRED_SAMPLES; + y = YMARGIN + h - (int)(h * samples.at(i) / fMax); + path.lineTo(x, y); + } + path.lineTo(x, YMARGIN + h); + } +} + +void TrafficGraphWidget::paintEvent(QPaintEvent *) +{ + QPainter painter(this); + painter.fillRect(rect(), Qt::black); + + if(fMax <= 0.0f) return; + + QColor axisCol(Qt::gray); + int h = height() - YMARGIN * 2; + painter.setPen(axisCol); + painter.drawLine(XMARGIN, YMARGIN + h, width() - XMARGIN, YMARGIN + h); + + // decide what order of magnitude we are + int base = floor(log10(fMax)); + float val = pow(10.0f, base); + + const QString units = tr("KB/s"); + // draw lines + painter.setPen(axisCol); + painter.drawText(XMARGIN, YMARGIN + h - h * val / fMax, QString("%1 %2").arg(val).arg(units)); + for(float y = val; y < fMax; y += val) { + int yy = YMARGIN + h - h * y / fMax; + painter.drawLine(XMARGIN, yy, width() - XMARGIN, yy); + } + // if we drew 3 or fewer lines, break them up at the next lower order of magnitude + if(fMax / val <= 3.0f) { + axisCol = axisCol.darker(); + val = pow(10.0f, base - 1); + painter.setPen(axisCol); + painter.drawText(XMARGIN, YMARGIN + h - h * val / fMax, QString("%1 %2").arg(val).arg(units)); + int count = 1; + for(float y = val; y < fMax; y += val, count++) { + // don't overwrite lines drawn above + if(count % 10 == 0) + continue; + int yy = YMARGIN + h - h * y / fMax; + painter.drawLine(XMARGIN, yy, width() - XMARGIN, yy); + } + } + + if(!vSamplesIn.empty()) { + QPainterPath p; + paintPath(p, vSamplesIn); + painter.fillPath(p, QColor(0, 255, 0, 128)); + painter.setPen(Qt::green); + painter.drawPath(p); + } + if(!vSamplesOut.empty()) { + QPainterPath p; + paintPath(p, vSamplesOut); + painter.fillPath(p, QColor(255, 0, 0, 128)); + painter.setPen(Qt::red); + painter.drawPath(p); + } +} + +void TrafficGraphWidget::updateRates() +{ + if(!clientModel) return; + + quint64 bytesIn = clientModel->getTotalBytesRecv(), + bytesOut = clientModel->getTotalBytesSent(); + float inRate = (bytesIn - nLastBytesIn) / 1024.0f * 1000 / timer->interval(); + float outRate = (bytesOut - nLastBytesOut) / 1024.0f * 1000 / timer->interval(); + vSamplesIn.push_front(inRate); + vSamplesOut.push_front(outRate); + nLastBytesIn = bytesIn; + nLastBytesOut = bytesOut; + + while(vSamplesIn.size() > DESIRED_SAMPLES) { + vSamplesIn.pop_back(); + } + while(vSamplesOut.size() > DESIRED_SAMPLES) { + vSamplesOut.pop_back(); + } + + float tmax = 0.0f; + foreach(float f, vSamplesIn) { + if(f > tmax) tmax = f; + } + foreach(float f, vSamplesOut) { + if(f > tmax) tmax = f; + } + fMax = tmax; + update(); +} + +void TrafficGraphWidget::setGraphRangeMins(int mins) +{ + nMins = mins; + int msecsPerSample = nMins * 60 * 1000 / DESIRED_SAMPLES; + timer->stop(); + timer->setInterval(msecsPerSample); + + clear(); +} + +void TrafficGraphWidget::clear() +{ + timer->stop(); + + vSamplesOut.clear(); + vSamplesIn.clear(); + fMax = 0.0f; + + if(clientModel) { + nLastBytesIn = clientModel->getTotalBytesRecv(); + nLastBytesOut = clientModel->getTotalBytesSent(); + } + timer->start(); +} diff --git a/src/qt/trafficgraphwidget.h b/src/qt/trafficgraphwidget.h new file mode 100644 index 0000000000..b31d1d5b0a --- /dev/null +++ b/src/qt/trafficgraphwidget.h @@ -0,0 +1,44 @@ +#ifndef TRAFFICGRAPHWIDGET_H +#define TRAFFICGRAPHWIDGET_H + +#include <QWidget> +#include <QQueue> + +class ClientModel; + +QT_BEGIN_NAMESPACE +class QPaintEvent; +class QTimer; +QT_END_NAMESPACE + +class TrafficGraphWidget : public QWidget +{ + Q_OBJECT + +public: + explicit TrafficGraphWidget(QWidget *parent = 0); + void setClientModel(ClientModel *model); + int getGraphRangeMins() const; + +protected: + void paintEvent(QPaintEvent *); + +public slots: + void updateRates(); + void setGraphRangeMins(int mins); + void clear(); + +private: + void paintPath(QPainterPath &path, QQueue<float> &samples); + + QTimer *timer; + float fMax; + int nMins; + QQueue<float> vSamplesIn; + QQueue<float> vSamplesOut; + quint64 nLastBytesIn; + quint64 nLastBytesOut; + ClientModel *clientModel; +}; + +#endif // TRAFFICGRAPHWIDGET_H diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index e27aa93a4a..93fc8cab22 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -17,7 +17,7 @@ QString TransactionDesc::FormatTxStatus(const CWalletTx& wtx) if (!IsFinalTx(wtx)) { if (wtx.nLockTime < LOCKTIME_THRESHOLD) - return tr("Open for %n more block(s)", "", wtx.nLockTime - nBestHeight + 1); + return tr("Open for %n more block(s)", "", wtx.nLockTime - chainActive.Height() + 1); else return tr("Open until %1").arg(GUIUtil::dateTimeStr(wtx.nLockTime)); } diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index ea2c1f0a5c..162908a9a4 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -160,14 +160,14 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx) idx); status.confirmed = wtx.IsConfirmed(); status.depth = wtx.GetDepthInMainChain(); - status.cur_num_blocks = nBestHeight; + status.cur_num_blocks = chainActive.Height(); if (!IsFinalTx(wtx)) { if (wtx.nLockTime < LOCKTIME_THRESHOLD) { status.status = TransactionStatus::OpenUntilBlock; - status.open_for = wtx.nLockTime - nBestHeight + 1; + status.open_for = wtx.nLockTime - chainActive.Height() + 1; } else { @@ -221,7 +221,7 @@ void TransactionRecord::updateStatus(const CWalletTx &wtx) bool TransactionRecord::statusUpdateNeeded() { - return status.cur_num_blocks != nBestHeight; + return status.cur_num_blocks != chainActive.Height(); } QString TransactionRecord::getTxID() const diff --git a/src/qt/transactiontablemodel.cpp b/src/qt/transactiontablemodel.cpp index 07f6a62150..6f7a5933ab 100644 --- a/src/qt/transactiontablemodel.cpp +++ b/src/qt/transactiontablemodel.cpp @@ -250,9 +250,9 @@ void TransactionTableModel::updateTransaction(const QString &hash, int status) void TransactionTableModel::updateConfirmations() { - if(nBestHeight != cachedNumBlocks) + if(chainActive.Height() != cachedNumBlocks) { - cachedNumBlocks = nBestHeight; + cachedNumBlocks = chainActive.Height(); // Blocks came in since last poll. // Invalidate status (number of confirmations) and (possibly) description // for all rows. Qt is smart enough to only actually request the data for the diff --git a/src/qt/walletframe.cpp b/src/qt/walletframe.cpp index 8d6a1b387e..f754bd5e71 100644 --- a/src/qt/walletframe.cpp +++ b/src/qt/walletframe.cpp @@ -5,20 +5,21 @@ * The Bitcoin Developers 2011-2013 */ #include "walletframe.h" +#include "walletview.h" #include "bitcoingui.h" -#include "walletstack.h" #include <QHBoxLayout> #include <QMessageBox> +#include <QStackedWidget> WalletFrame::WalletFrame(BitcoinGUI *_gui) : - QFrame(_gui) + QFrame(_gui), + gui(_gui) { // Leave HBox hook for adding a list view later QHBoxLayout *walletFrameLayout = new QHBoxLayout(this); setContentsMargins(0,0,0,0); - walletStack = new WalletStack(this); - walletStack->setBitcoinGUI(_gui); + walletStack = new QStackedWidget(this); walletFrameLayout->setContentsMargins(0,0,0,0); walletFrameLayout->addWidget(walletStack); } @@ -29,95 +30,157 @@ WalletFrame::~WalletFrame() void WalletFrame::setClientModel(ClientModel *clientModel) { - if (clientModel) - walletStack->setClientModel(clientModel); + this->clientModel = clientModel; } bool WalletFrame::addWallet(const QString& name, WalletModel *walletModel) { - return walletStack->addWallet(name, walletModel); + if (!gui || !clientModel || !walletModel || mapWalletViews.count(name) > 0) + return false; + + WalletView *walletView = new WalletView(this); + walletView->setBitcoinGUI(gui); + walletView->setClientModel(clientModel); + walletView->setWalletModel(walletModel); + walletView->showOutOfSyncWarning(bOutOfSync); + + /* TODO we should goto the currently selected page once dynamically adding wallets is supported */ + walletView->gotoOverviewPage(); + walletStack->addWidget(walletView); + mapWalletViews[name] = walletView; + + // Ensure a walletView is able to show the main window + connect(walletView, SIGNAL(showNormalIfMinimized()), gui, SLOT(showNormalIfMinimized())); + + return true; } bool WalletFrame::setCurrentWallet(const QString& name) { - // TODO: Check if valid name - return walletStack->setCurrentWallet(name); + if (mapWalletViews.count(name) == 0) + return false; + + WalletView *walletView = mapWalletViews.value(name); + walletStack->setCurrentWidget(walletView); + walletView->setEncryptionStatus(); + return true; +} + +bool WalletFrame::removeWallet(const QString &name) +{ + if (mapWalletViews.count(name) == 0) + return false; + + WalletView *walletView = mapWalletViews.take(name); + walletStack->removeWidget(walletView); + return true; } void WalletFrame::removeAllWallets() { - walletStack->removeAllWallets(); + QMap<QString, WalletView*>::const_iterator i; + for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) + walletStack->removeWidget(i.value()); + mapWalletViews.clear(); } bool WalletFrame::handlePaymentRequest(const SendCoinsRecipient &recipient) { - return walletStack->handlePaymentRequest(recipient); + WalletView *walletView = (WalletView*)walletStack->currentWidget(); + if (!walletView) + return false; + + return walletView->handlePaymentRequest(recipient); } void WalletFrame::showOutOfSyncWarning(bool fShow) { - if (!walletStack) - return; - - walletStack->showOutOfSyncWarning(fShow); + bOutOfSync = fShow; + QMap<QString, WalletView*>::const_iterator i; + for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) + i.value()->showOutOfSyncWarning(fShow); } void WalletFrame::gotoOverviewPage() { - walletStack->gotoOverviewPage(); + QMap<QString, WalletView*>::const_iterator i; + for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) + i.value()->gotoOverviewPage(); } void WalletFrame::gotoHistoryPage() { - walletStack->gotoHistoryPage(); + QMap<QString, WalletView*>::const_iterator i; + for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) + i.value()->gotoHistoryPage(); } void WalletFrame::gotoAddressBookPage() { - walletStack->gotoAddressBookPage(); + QMap<QString, WalletView*>::const_iterator i; + for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) + i.value()->gotoAddressBookPage(); } void WalletFrame::gotoReceiveCoinsPage() { - walletStack->gotoReceiveCoinsPage(); + QMap<QString, WalletView*>::const_iterator i; + for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) + i.value()->gotoReceiveCoinsPage(); } void WalletFrame::gotoSendCoinsPage(QString addr) { - walletStack->gotoSendCoinsPage(addr); + QMap<QString, WalletView*>::const_iterator i; + for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) + i.value()->gotoSendCoinsPage(addr); } void WalletFrame::gotoSignMessageTab(QString addr) { - walletStack->gotoSignMessageTab(addr); + WalletView *walletView = (WalletView*)walletStack->currentWidget(); + if (walletView) + walletView->gotoSignMessageTab(addr); } void WalletFrame::gotoVerifyMessageTab(QString addr) { - walletStack->gotoSignMessageTab(addr); + WalletView *walletView = (WalletView*)walletStack->currentWidget(); + if (walletView) + walletView->gotoVerifyMessageTab(addr); } void WalletFrame::encryptWallet(bool status) { - walletStack->encryptWallet(status); + WalletView *walletView = (WalletView*)walletStack->currentWidget(); + if (walletView) + walletView->encryptWallet(status); } void WalletFrame::backupWallet() { - walletStack->backupWallet(); + WalletView *walletView = (WalletView*)walletStack->currentWidget(); + if (walletView) + walletView->backupWallet(); } void WalletFrame::changePassphrase() { - walletStack->changePassphrase(); + WalletView *walletView = (WalletView*)walletStack->currentWidget(); + if (walletView) + walletView->changePassphrase(); } void WalletFrame::unlockWallet() { - walletStack->unlockWallet(); + WalletView *walletView = (WalletView*)walletStack->currentWidget(); + if (walletView) + walletView->unlockWallet(); } void WalletFrame::setEncryptionStatus() { - walletStack->setEncryptionStatus(); + WalletView *walletView = (WalletView*)walletStack->currentWidget(); + if (walletView) + walletView->setEncryptionStatus(); } diff --git a/src/qt/walletframe.h b/src/qt/walletframe.h index eaae053ccd..5011987963 100644 --- a/src/qt/walletframe.h +++ b/src/qt/walletframe.h @@ -8,12 +8,17 @@ #define WALLETFRAME_H #include <QFrame> +#include <QMap> class BitcoinGUI; class ClientModel; class SendCoinsRecipient; class WalletModel; -class WalletStack; +class WalletView; + +QT_BEGIN_NAMESPACE +class QStackedWidget; +QT_END_NAMESPACE class WalletFrame : public QFrame { @@ -27,7 +32,7 @@ public: bool addWallet(const QString& name, WalletModel *walletModel); bool setCurrentWallet(const QString& name); - + bool removeWallet(const QString &name); void removeAllWallets(); bool handlePaymentRequest(const SendCoinsRecipient& recipient); @@ -35,7 +40,12 @@ public: void showOutOfSyncWarning(bool fShow); private: - WalletStack *walletStack; + QStackedWidget *walletStack; + BitcoinGUI *gui; + ClientModel *clientModel; + QMap<QString, WalletView*> mapWalletViews; + + bool bOutOfSync; public slots: /** Switch to overview (home) page */ diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index bda39b675f..417bac9928 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -73,10 +73,10 @@ void WalletModel::updateStatus() void WalletModel::pollBalanceChanged() { - if(nBestHeight != cachedNumBlocks) + if(chainActive.Height() != cachedNumBlocks) { // Balance and number of transactions might have changed - cachedNumBlocks = nBestHeight; + cachedNumBlocks = chainActive.Height(); checkBalanceChanged(); } } @@ -258,22 +258,26 @@ WalletModel::SendCoinsReturn WalletModel::sendCoins(WalletModelTransaction &tran // and emit coinsSent signal for each recipient foreach(const SendCoinsRecipient &rcp, transaction.getRecipients()) { - std::string strAddress = rcp.address.toStdString(); - CTxDestination dest = CBitcoinAddress(strAddress).Get(); - std::string strLabel = rcp.label.toStdString(); + // Don't touch the address book when we have a secure payment-request + if (rcp.authenticatedMerchant.isEmpty()) { - LOCK(wallet->cs_wallet); - - std::map<CTxDestination, CAddressBookData>::iterator mi = wallet->mapAddressBook.find(dest); - - // Check if we have a new address or an updated label - if (mi == wallet->mapAddressBook.end()) - { - wallet->SetAddressBook(dest, strLabel, "send"); - } - else if (mi->second.name != strLabel) + std::string strAddress = rcp.address.toStdString(); + CTxDestination dest = CBitcoinAddress(strAddress).Get(); + std::string strLabel = rcp.label.toStdString(); { - wallet->SetAddressBook(dest, strLabel, ""); // "" means don't change purpose + LOCK(wallet->cs_wallet); + + std::map<CTxDestination, CAddressBookData>::iterator mi = wallet->mapAddressBook.find(dest); + + // Check if we have a new address or an updated label + if (mi == wallet->mapAddressBook.end()) + { + wallet->SetAddressBook(dest, strLabel, "send"); + } + else if (mi->second.name != strLabel) + { + wallet->SetAddressBook(dest, strLabel, ""); // "" means don't change purpose + } } } emit coinsSent(wallet, rcp, transaction_array); diff --git a/src/qt/walletstack.cpp b/src/qt/walletstack.cpp deleted file mode 100644 index 4ef87aed52..0000000000 --- a/src/qt/walletstack.cpp +++ /dev/null @@ -1,174 +0,0 @@ -/* - * Qt4 bitcoin GUI. - * - * W.J. van der Laan 2011-2012 - * The Bitcoin Developers 2011-2013 - */ -#include "walletstack.h" -#include "walletview.h" -#include "bitcoingui.h" - -#include <QMap> -#include <QMessageBox> - -WalletStack::WalletStack(QWidget *parent) : - QStackedWidget(parent), - gui(0), - clientModel(0), - bOutOfSync(true) -{ - setContentsMargins(0,0,0,0); -} - -WalletStack::~WalletStack() -{ -} - -bool WalletStack::addWallet(const QString& name, WalletModel *walletModel) -{ - if (!gui || !clientModel || !walletModel || mapWalletViews.count(name) > 0) - return false; - - WalletView *walletView = new WalletView(this, gui); - walletView->setBitcoinGUI(gui); - walletView->setClientModel(clientModel); - walletView->setWalletModel(walletModel); - walletView->showOutOfSyncWarning(bOutOfSync); - addWidget(walletView); - mapWalletViews[name] = walletView; - - // Ensure a walletView is able to show the main window - connect(walletView, SIGNAL(showNormalIfMinimized()), gui, SLOT(showNormalIfMinimized())); - - return true; -} - -bool WalletStack::removeWallet(const QString& name) -{ - if (mapWalletViews.count(name) == 0) - return false; - - WalletView *walletView = mapWalletViews.take(name); - removeWidget(walletView); - return true; -} - -void WalletStack::removeAllWallets() -{ - QMap<QString, WalletView*>::const_iterator i; - for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) - removeWidget(i.value()); - mapWalletViews.clear(); -} - -bool WalletStack::handlePaymentRequest(const SendCoinsRecipient &recipient) -{ - WalletView *walletView = (WalletView*)currentWidget(); - if (!walletView) - return false; - - return walletView->handlePaymentRequest(recipient); -} - -void WalletStack::showOutOfSyncWarning(bool fShow) -{ - bOutOfSync = fShow; - QMap<QString, WalletView*>::const_iterator i; - for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) - i.value()->showOutOfSyncWarning(fShow); -} - -void WalletStack::gotoOverviewPage() -{ - QMap<QString, WalletView*>::const_iterator i; - for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) - i.value()->gotoOverviewPage(); -} - -void WalletStack::gotoHistoryPage() -{ - QMap<QString, WalletView*>::const_iterator i; - for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) - i.value()->gotoHistoryPage(); -} - -void WalletStack::gotoAddressBookPage() -{ - QMap<QString, WalletView*>::const_iterator i; - for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) - i.value()->gotoAddressBookPage(); -} - -void WalletStack::gotoReceiveCoinsPage() -{ - QMap<QString, WalletView*>::const_iterator i; - for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) - i.value()->gotoReceiveCoinsPage(); -} - -void WalletStack::gotoSendCoinsPage(QString addr) -{ - QMap<QString, WalletView*>::const_iterator i; - for (i = mapWalletViews.constBegin(); i != mapWalletViews.constEnd(); ++i) - i.value()->gotoSendCoinsPage(addr); -} - -void WalletStack::gotoSignMessageTab(QString addr) -{ - WalletView *walletView = (WalletView*)currentWidget(); - if (walletView) - walletView->gotoSignMessageTab(addr); -} - -void WalletStack::gotoVerifyMessageTab(QString addr) -{ - WalletView *walletView = (WalletView*)currentWidget(); - if (walletView) - walletView->gotoVerifyMessageTab(addr); -} - -void WalletStack::encryptWallet(bool status) -{ - WalletView *walletView = (WalletView*)currentWidget(); - if (walletView) - walletView->encryptWallet(status); -} - -void WalletStack::backupWallet() -{ - WalletView *walletView = (WalletView*)currentWidget(); - if (walletView) - walletView->backupWallet(); -} - -void WalletStack::changePassphrase() -{ - WalletView *walletView = (WalletView*)currentWidget(); - if (walletView) - walletView->changePassphrase(); -} - -void WalletStack::unlockWallet() -{ - WalletView *walletView = (WalletView*)currentWidget(); - if (walletView) - walletView->unlockWallet(); -} - -void WalletStack::setEncryptionStatus() -{ - WalletView *walletView = (WalletView*)currentWidget(); - if (walletView) - walletView->setEncryptionStatus(); -} - -bool WalletStack::setCurrentWallet(const QString& name) -{ - if (mapWalletViews.count(name) == 0) - return false; - - WalletView *walletView = mapWalletViews.value(name); - setCurrentWidget(walletView); - walletView->setEncryptionStatus(); - return true; -} diff --git a/src/qt/walletstack.h b/src/qt/walletstack.h deleted file mode 100644 index 74b9f09081..0000000000 --- a/src/qt/walletstack.h +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Qt4 bitcoin GUI. - * - * W.J. van der Laan 2011-2012 - * The Bitcoin Developers 2011-2013 - */ -#ifndef WALLETSTACK_H -#define WALLETSTACK_H - -#include <QStackedWidget> -#include <QMap> -#include <boost/shared_ptr.hpp> - -class BitcoinGUI; -class TransactionTableModel; -class ClientModel; -class WalletModel; -class WalletView; -class TransactionView; -class OverviewPage; -class AddressBookPage; -class SendCoinsDialog; -class SendCoinsRecipient; -class SignVerifyMessageDialog; -class Notificator; -class RPCConsole; - -class CWalletManager; - -QT_BEGIN_NAMESPACE -class QLabel; -class QModelIndex; -QT_END_NAMESPACE - -/* - WalletStack class. This class is a container for WalletView instances. It takes the place of centralWidget. - It was added to support multiple wallet functionality. It communicates with both the client and the - wallet models to give the user an up-to-date view of the current core state. It manages all the wallet views - it contains and updates them accordingly. - */ -class WalletStack : public QStackedWidget -{ - Q_OBJECT - -public: - explicit WalletStack(QWidget *parent = 0); - ~WalletStack(); - - void setBitcoinGUI(BitcoinGUI *gui) { this->gui = gui; } - - void setClientModel(ClientModel *clientModel) { this->clientModel = clientModel; } - - bool addWallet(const QString& name, WalletModel *walletModel); - bool removeWallet(const QString& name); - - void removeAllWallets(); - - bool handlePaymentRequest(const SendCoinsRecipient &recipient); - - void showOutOfSyncWarning(bool fShow); - -private: - BitcoinGUI *gui; - ClientModel *clientModel; - QMap<QString, WalletView*> mapWalletViews; - - bool bOutOfSync; - -public slots: - bool setCurrentWallet(const QString& name); - - /** Switch to overview (home) page */ - void gotoOverviewPage(); - /** Switch to history (transactions) page */ - void gotoHistoryPage(); - /** Switch to address book page */ - void gotoAddressBookPage(); - /** Switch to receive coins page */ - void gotoReceiveCoinsPage(); - /** Switch to send coins page */ - void gotoSendCoinsPage(QString addr = ""); - - /** Show Sign/Verify Message dialog and switch to sign message tab */ - void gotoSignMessageTab(QString addr = ""); - /** Show Sign/Verify Message dialog and switch to verify message tab */ - void gotoVerifyMessageTab(QString addr = ""); - - /** Encrypt the wallet */ - void encryptWallet(bool status); - /** Backup the wallet */ - void backupWallet(); - /** Change encrypted wallet passphrase */ - void changePassphrase(); - /** Ask for passphrase to unlock wallet temporarily */ - void unlockWallet(); - - /** Set the encryption status as shown in the UI. - @param[in] status current encryption status - @see WalletModel::EncryptionStatus - */ - void setEncryptionStatus(); -}; - -#endif // WALLETSTACK_H diff --git a/src/qt/walletview.cpp b/src/qt/walletview.cpp index efb74efaa0..d7cef971ed 100644 --- a/src/qt/walletview.cpp +++ b/src/qt/walletview.cpp @@ -29,9 +29,9 @@ #include <QFileDialog> #include <QPushButton> -WalletView::WalletView(QWidget *parent, BitcoinGUI *_gui): +WalletView::WalletView(QWidget *parent): QStackedWidget(parent), - gui(_gui), + gui(0), clientModel(0), walletModel(0) { @@ -54,12 +54,8 @@ WalletView::WalletView(QWidget *parent, BitcoinGUI *_gui): transactionsPage->setLayout(vbox); addressBookPage = new AddressBookPage(AddressBookPage::ForEditing, AddressBookPage::SendingTab); - receiveCoinsPage = new AddressBookPage(AddressBookPage::ForEditing, AddressBookPage::ReceivingTab); - - sendCoinsPage = new SendCoinsDialog(gui); - - signVerifyMessageDialog = new SignVerifyMessageDialog(gui); + sendCoinsPage = new SendCoinsDialog(); addWidget(overviewPage); addWidget(transactionsPage); @@ -68,7 +64,6 @@ WalletView::WalletView(QWidget *parent, BitcoinGUI *_gui): addWidget(sendCoinsPage); // Clicking on a transaction on the overview page simply sends you to transaction history page - connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), this, SLOT(gotoHistoryPage())); connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), transactionView, SLOT(focusTransaction(QModelIndex))); // Double-clicking on a transaction on the transaction history page shows details @@ -82,8 +77,6 @@ WalletView::WalletView(QWidget *parent, BitcoinGUI *_gui): connect(receiveCoinsPage, SIGNAL(signMessage(QString)), this, SLOT(gotoSignMessageTab(QString))); // Clicking on "Export" allows to export the transaction list connect(exportButton, SIGNAL(clicked()), transactionView, SLOT(exportClicked())); - - gotoOverviewPage(); } WalletView::~WalletView() @@ -93,6 +86,10 @@ WalletView::~WalletView() void WalletView::setBitcoinGUI(BitcoinGUI *gui) { this->gui = gui; + if(gui) + { + connect(overviewPage, SIGNAL(transactionClicked(QModelIndex)), gui, SLOT(gotoHistoryPage())); + } } void WalletView::setClientModel(ClientModel *clientModel) @@ -109,7 +106,7 @@ void WalletView::setClientModel(ClientModel *clientModel) void WalletView::setWalletModel(WalletModel *walletModel) { this->walletModel = walletModel; - if (walletModel) + if (walletModel && gui) { // Receive and report messages from wallet thread connect(walletModel, SIGNAL(message(QString,QString,unsigned int)), gui, SLOT(message(QString,QString,unsigned int))); @@ -120,7 +117,6 @@ void WalletView::setWalletModel(WalletModel *walletModel) addressBookPage->setModel(walletModel->getAddressTableModel()); receiveCoinsPage->setModel(walletModel->getAddressTableModel()); sendCoinsPage->setModel(walletModel); - signVerifyMessageDialog->setModel(walletModel); setEncryptionStatus(); connect(walletModel, SIGNAL(encryptionStatusChanged(int)), gui, SLOT(setEncryptionStatus(int))); @@ -152,31 +148,26 @@ void WalletView::incomingTransaction(const QModelIndex& parent, int start, int / void WalletView::gotoOverviewPage() { - gui->getOverviewAction()->setChecked(true); setCurrentWidget(overviewPage); } void WalletView::gotoHistoryPage() { - gui->getHistoryAction()->setChecked(true); setCurrentWidget(transactionsPage); } void WalletView::gotoAddressBookPage() { - gui->getAddressBookAction()->setChecked(true); setCurrentWidget(addressBookPage); } void WalletView::gotoReceiveCoinsPage() { - gui->getReceiveCoinsAction()->setChecked(true); setCurrentWidget(receiveCoinsPage); } void WalletView::gotoSendCoinsPage(QString addr) { - gui->getSendCoinsAction()->setChecked(true); setCurrentWidget(sendCoinsPage); if (!addr.isEmpty()) @@ -185,7 +176,10 @@ void WalletView::gotoSendCoinsPage(QString addr) void WalletView::gotoSignMessageTab(QString addr) { - // call show() in showTab_SM() + // calls show() in showTab_SM() + SignVerifyMessageDialog *signVerifyMessageDialog = new SignVerifyMessageDialog(this); + signVerifyMessageDialog->setAttribute(Qt::WA_DeleteOnClose); + signVerifyMessageDialog->setModel(walletModel); signVerifyMessageDialog->showTab_SM(true); if (!addr.isEmpty()) @@ -194,7 +188,10 @@ void WalletView::gotoSignMessageTab(QString addr) void WalletView::gotoVerifyMessageTab(QString addr) { - // call show() in showTab_VM() + // calls show() in showTab_VM() + SignVerifyMessageDialog *signVerifyMessageDialog = new SignVerifyMessageDialog(this); + signVerifyMessageDialog->setAttribute(Qt::WA_DeleteOnClose); + signVerifyMessageDialog->setModel(walletModel); signVerifyMessageDialog->showTab_VM(true); if (!addr.isEmpty()) diff --git a/src/qt/walletview.h b/src/qt/walletview.h index ce4e051098..e3ff253d3c 100644 --- a/src/qt/walletview.h +++ b/src/qt/walletview.h @@ -36,7 +36,7 @@ class WalletView : public QStackedWidget Q_OBJECT public: - explicit WalletView(QWidget *parent, BitcoinGUI *_gui); + explicit WalletView(QWidget *parent); ~WalletView(); void setBitcoinGUI(BitcoinGUI *gui); @@ -64,7 +64,6 @@ private: AddressBookPage *addressBookPage; AddressBookPage *receiveCoinsPage; SendCoinsDialog *sendCoinsPage; - SignVerifyMessageDialog *signVerifyMessageDialog; TransactionView *transactionView; diff --git a/src/rpcblockchain.cpp b/src/rpcblockchain.cpp index 593d8c925b..6ea805a7f1 100644 --- a/src/rpcblockchain.cpp +++ b/src/rpcblockchain.cpp @@ -17,10 +17,10 @@ double GetDifficulty(const CBlockIndex* blockindex) // minimum difficulty = 1.0. if (blockindex == NULL) { - if (pindexBest == NULL) + if (chainActive.Tip() == NULL) return 1.0; else - blockindex = pindexBest; + blockindex = chainActive.Tip(); } int nShift = (blockindex->nBits >> 24) & 0xff; @@ -66,7 +66,7 @@ Object blockToJSON(const CBlock& block, const CBlockIndex* blockindex) if (blockindex->pprev) result.push_back(Pair("previousblockhash", blockindex->pprev->GetBlockHash().GetHex())); - CBlockIndex *pnext = blockindex->GetNextInMainChain(); + CBlockIndex *pnext = chainActive.Next(blockindex); if (pnext) result.push_back(Pair("nextblockhash", pnext->GetBlockHash().GetHex())); return result; @@ -80,7 +80,7 @@ Value getblockcount(const Array& params, bool fHelp) "getblockcount\n" "Returns the number of blocks in the longest block chain."); - return nBestHeight; + return chainActive.Height(); } Value getbestblockhash(const Array& params, bool fHelp) @@ -90,7 +90,7 @@ Value getbestblockhash(const Array& params, bool fHelp) "getbestblockhash\n" "Returns the hash of the best (tip) block in the longest block chain."); - return hashBestChain.GetHex(); + return chainActive.Tip()->GetBlockHash().GetHex(); } Value getdifficulty(const Array& params, bool fHelp) @@ -145,11 +145,11 @@ Value getblockhash(const Array& params, bool fHelp) "Returns hash of block in best-block-chain at <index>."); int nHeight = params[0].get_int(); - if (nHeight < 0 || nHeight > nBestHeight) + if (nHeight < 0 || nHeight > chainActive.Height()) throw runtime_error("Block number out of range."); - CBlockIndex* pblockindex = FindBlockByHeight(nHeight); - return pblockindex->phashBlock->GetHex(); + CBlockIndex* pblockindex = chainActive[nHeight]; + return pblockindex->GetBlockHash().GetHex(); } Value getblock(const Array& params, bool fHelp) diff --git a/src/rpcdump.cpp b/src/rpcdump.cpp index 4be95d10c8..3589b45900 100644 --- a/src/rpcdump.cpp +++ b/src/rpcdump.cpp @@ -102,7 +102,7 @@ Value importprivkey(const Array& params, bool fHelp) throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); if (fRescan) { - pwalletMain->ScanForWalletTransactions(pindexGenesisBlock, true); + pwalletMain->ScanForWalletTransactions(chainActive.Genesis(), true); pwalletMain->ReacceptWalletTransactions(); } } @@ -124,7 +124,7 @@ Value importwallet(const Array& params, bool fHelp) if (!file.is_open()) throw JSONRPCError(RPC_INVALID_PARAMETER, "Cannot open wallet dump file"); - int64 nTimeBegin = pindexBest->nTime; + int64 nTimeBegin = chainActive.Tip()->nTime; bool fGood = true; @@ -175,11 +175,11 @@ Value importwallet(const Array& params, bool fHelp) } file.close(); - CBlockIndex *pindex = pindexBest; + CBlockIndex *pindex = chainActive.Tip(); while (pindex && pindex->pprev && pindex->nTime > nTimeBegin - 7200) pindex = pindex->pprev; - LogPrintf("Rescanning last %i blocks\n", pindexBest->nHeight - pindex->nHeight + 1); + LogPrintf("Rescanning last %i blocks\n", chainActive.Height() - pindex->nHeight + 1); pwalletMain->ScanForWalletTransactions(pindex); pwalletMain->ReacceptWalletTransactions(); pwalletMain->MarkDirty(); @@ -243,8 +243,8 @@ Value dumpwallet(const Array& params, bool fHelp) // produce output file << strprintf("# Wallet dump created by Bitcoin %s (%s)\n", CLIENT_BUILD.c_str(), CLIENT_DATE.c_str()); file << strprintf("# * Created on %s\n", EncodeDumpTime(GetTime()).c_str()); - file << strprintf("# * Best block at time of backup was %i (%s),\n", nBestHeight, hashBestChain.ToString().c_str()); - file << strprintf("# mined on %s\n", EncodeDumpTime(pindexBest->nTime).c_str()); + file << strprintf("# * Best block at time of backup was %i (%s),\n", chainActive.Height(), chainActive.Tip()->GetBlockHash().ToString().c_str()); + file << strprintf("# mined on %s\n", EncodeDumpTime(chainActive.Tip()->nTime).c_str()); file << "\n"; for (std::vector<std::pair<int64, CKeyID> >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) { const CKeyID &keyid = it->second; diff --git a/src/rpcmining.cpp b/src/rpcmining.cpp index 3328d21052..77dc13815d 100644 --- a/src/rpcmining.cpp +++ b/src/rpcmining.cpp @@ -37,10 +37,7 @@ void ShutdownRPCMining() // or from the last difficulty change if 'lookup' is nonpositive. // If 'height' is nonnegative, compute the estimate at the time when a given block was found. Value GetNetworkHashPS(int lookup, int height) { - CBlockIndex *pb = pindexBest; - - if (height >= 0 && height < nBestHeight) - pb = FindBlockByHeight(height); + CBlockIndex *pb = chainActive[height]; if (pb == NULL || !pb->nHeight) return 0; @@ -148,7 +145,7 @@ Value getmininginfo(const Array& params, bool fHelp) "Returns an object containing mining-related information."); Object obj; - obj.push_back(Pair("blocks", (int)nBestHeight)); + obj.push_back(Pair("blocks", (int)chainActive.Height())); obj.push_back(Pair("currentblocksize", (uint64_t)nLastBlockSize)); obj.push_back(Pair("currentblocktx", (uint64_t)nLastBlockTx)); obj.push_back(Pair("difficulty", (double)GetDifficulty())); @@ -192,10 +189,10 @@ Value getwork(const Array& params, bool fHelp) static CBlockIndex* pindexPrev; static int64 nStart; static CBlockTemplate* pblocktemplate; - if (pindexPrev != pindexBest || + if (pindexPrev != chainActive.Tip() || (nTransactionsUpdated != nTransactionsUpdatedLast && GetTime() - nStart > 60)) { - if (pindexPrev != pindexBest) + if (pindexPrev != chainActive.Tip()) { // Deallocate old blocks since they're obsolete now mapNewBlock.clear(); @@ -209,7 +206,7 @@ Value getwork(const Array& params, bool fHelp) // Store the pindexBest used before CreateNewBlock, to avoid races nTransactionsUpdatedLast = nTransactionsUpdated; - CBlockIndex* pindexPrevNew = pindexBest; + CBlockIndex* pindexPrevNew = chainActive.Tip(); nStart = GetTime(); // Create new block @@ -328,7 +325,7 @@ Value getblocktemplate(const Array& params, bool fHelp) static CBlockIndex* pindexPrev; static int64 nStart; static CBlockTemplate* pblocktemplate; - if (pindexPrev != pindexBest || + if (pindexPrev != chainActive.Tip() || (nTransactionsUpdated != nTransactionsUpdatedLast && GetTime() - nStart > 5)) { // Clear pindexPrev so future calls make a new block, despite any failures from here on @@ -336,7 +333,7 @@ Value getblocktemplate(const Array& params, bool fHelp) // Store the pindexBest used before CreateNewBlock, to avoid races nTransactionsUpdatedLast = nTransactionsUpdated; - CBlockIndex* pindexPrevNew = pindexBest; + CBlockIndex* pindexPrevNew = chainActive.Tip(); nStart = GetTime(); // Create new block diff --git a/src/rpcnet.cpp b/src/rpcnet.cpp index c9656adab4..7685dec57b 100644 --- a/src/rpcnet.cpp +++ b/src/rpcnet.cpp @@ -19,6 +19,24 @@ Value getconnectioncount(const Array& params, bool fHelp) return (int)vNodes.size(); } +Value ping(const Array& params, bool fHelp) +{ + if (fHelp || params.size() != 0) + throw runtime_error( + "ping\n" + "Requests that a ping be sent to all other nodes, to measure ping time.\n" + "Results provided in getpeerinfo, pingtime and pingwait fields are decimal seconds.\n" + "Ping command is handled in queue with all other commands, so it measures processing backlog, not just network ping."); + + // Request that each node send a ping during next message processing pass + LOCK(cs_vNodes); + BOOST_FOREACH(CNode* pNode, vNodes) { + pNode->fPingQueued = true; + } + + return Value::null; +} + static void CopyNodeStats(std::vector<CNodeStats>& vstats) { vstats.clear(); @@ -54,6 +72,9 @@ Value getpeerinfo(const Array& params, bool fHelp) obj.push_back(Pair("bytessent", (boost::int64_t)stats.nSendBytes)); obj.push_back(Pair("bytesrecv", (boost::int64_t)stats.nRecvBytes)); obj.push_back(Pair("conntime", (boost::int64_t)stats.nTimeConnected)); + obj.push_back(Pair("pingtime", stats.dPingTime)); + if (stats.dPingWait > 0.0) + obj.push_back(Pair("pingwait", stats.dPingWait)); obj.push_back(Pair("version", stats.nVersion)); obj.push_back(Pair("subver", stats.strSubVer)); obj.push_back(Pair("inbound", stats.fInbound)); @@ -202,3 +223,17 @@ Value getaddednodeinfo(const Array& params, bool fHelp) return ret; } +Value getnettotals(const Array& params, bool fHelp) +{ + if (fHelp || params.size() > 0) + throw runtime_error( + "getnettotals\n" + "Returns information about network traffic, including bytes in, bytes out,\n" + "and current time."); + + Object obj; + obj.push_back(Pair("totalbytesrecv", static_cast< boost::uint64_t>(CNode::GetTotalBytesRecv()))); + obj.push_back(Pair("totalbytessent", static_cast<boost::uint64_t>(CNode::GetTotalBytesSent()))); + obj.push_back(Pair("timemillis", static_cast<boost::int64_t>(GetTimeMillis()))); + return obj; +} diff --git a/src/rpcrawtransaction.cpp b/src/rpcrawtransaction.cpp index fe365643fb..d5bd39cb4d 100644 --- a/src/rpcrawtransaction.cpp +++ b/src/rpcrawtransaction.cpp @@ -87,9 +87,9 @@ void TxToJSON(const CTransaction& tx, const uint256 hashBlock, Object& entry) if (mi != mapBlockIndex.end() && (*mi).second) { CBlockIndex* pindex = (*mi).second; - if (pindex->IsInMainChain()) + if (chainActive.Contains(pindex)) { - entry.push_back(Pair("confirmations", 1 + nBestHeight - pindex->nHeight)); + entry.push_back(Pair("confirmations", 1 + chainActive.Height() - pindex->nHeight)); entry.push_back(Pair("time", (boost::int64_t)pindex->nTime)); entry.push_back(Pair("blocktime", (boost::int64_t)pindex->nTime)); } diff --git a/src/rpcwallet.cpp b/src/rpcwallet.cpp index 920652bcaf..a72c254e76 100644 --- a/src/rpcwallet.cpp +++ b/src/rpcwallet.cpp @@ -76,7 +76,7 @@ Value getinfo(const Array& params, bool fHelp) obj.push_back(Pair("walletversion", pwalletMain->GetVersion())); obj.push_back(Pair("balance", ValueFromAmount(pwalletMain->GetBalance()))); } - obj.push_back(Pair("blocks", (int)nBestHeight)); + obj.push_back(Pair("blocks", (int)chainActive.Height())); obj.push_back(Pair("timeoffset", (boost::int64_t)GetTimeOffset())); obj.push_back(Pair("connections", (int)vNodes.size())); obj.push_back(Pair("proxy", (proxy.first.IsValid() ? proxy.first.ToStringIPPort() : string()))); @@ -1169,7 +1169,9 @@ Value listsinceblock(const Array& params, bool fHelp) uint256 blockId = 0; blockId.SetHex(params[0].get_str()); - pindex = CBlockLocator(blockId).GetBlockIndex(); + std::map<uint256, CBlockIndex*>::iterator it = mapBlockIndex.find(blockId); + if (it != mapBlockIndex.end()) + pindex = it->second; } if (params.size() > 1) @@ -1180,7 +1182,7 @@ Value listsinceblock(const Array& params, bool fHelp) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter"); } - int depth = pindex ? (1 + nBestHeight - pindex->nHeight) : -1; + int depth = pindex ? (1 + chainActive.Height() - pindex->nHeight) : -1; Array transactions; @@ -1192,23 +1194,8 @@ Value listsinceblock(const Array& params, bool fHelp) ListTransactions(tx, "*", 0, true, transactions); } - uint256 lastblock; - - if (target_confirms == 1) - { - lastblock = hashBestChain; - } - else - { - int target_height = pindexBest->nHeight + 1 - target_confirms; - - CBlockIndex *block; - for (block = pindexBest; - block && block->nHeight > target_height; - block = block->pprev) { } - - lastblock = block ? block->GetBlockHash() : 0; - } + CBlockIndex *pblockLast = chainActive[chainActive.Height() + 1 - target_confirms]; + uint256 lastblock = pblockLast ? pblockLast->GetBlockHash() : 0; Object ret; ret.push_back(Pair("transactions", transactions)); diff --git a/src/script.cpp b/src/script.cpp index d68b38c5d9..4e4e95a678 100644 --- a/src/script.cpp +++ b/src/script.cpp @@ -971,62 +971,118 @@ bool EvalScript(vector<vector<unsigned char> >& stack, const CScript& script, co +namespace { +/** Wrapper that serializes like CTransaction, but with the modifications + * required for the signature hash done in-place + */ +class CTransactionSignatureSerializer { +private: + const CTransaction &txTo; // reference to the spending transaction (the one being serialized) + const CScript &scriptCode; // output script being consumed + const unsigned int nIn; // input index of txTo being signed + const bool fAnyoneCanPay; // whether the hashtype has the SIGHASH_ANYONECANPAY flag set + const bool fHashSingle; // whether the hashtype is SIGHASH_SINGLE + const bool fHashNone; // whether the hashtype is SIGHASH_NONE - -uint256 SignatureHash(CScript scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType) -{ - if (nIn >= txTo.vin.size()) - { - LogPrintf("ERROR: SignatureHash() : nIn=%d out of range\n", nIn); - return 1; +public: + CTransactionSignatureSerializer(const CTransaction &txToIn, const CScript &scriptCodeIn, unsigned int nInIn, int nHashTypeIn) : + txTo(txToIn), scriptCode(scriptCodeIn), nIn(nInIn), + fAnyoneCanPay(!!(nHashTypeIn & SIGHASH_ANYONECANPAY)), + fHashSingle((nHashTypeIn & 0x1f) == SIGHASH_SINGLE), + fHashNone((nHashTypeIn & 0x1f) == SIGHASH_NONE) {} + + /** Serialize the passed scriptCode, skipping OP_CODESEPARATORs */ + template<typename S> + void SerializeScriptCode(S &s, int nType, int nVersion) const { + CScript::const_iterator it = scriptCode.begin(); + CScript::const_iterator itBegin = it; + opcodetype opcode; + unsigned int nCodeSeparators = 0; + while (scriptCode.GetOp(it, opcode)) { + if (opcode == OP_CODESEPARATOR) + nCodeSeparators++; + } + ::WriteCompactSize(s, scriptCode.size() - nCodeSeparators); + it = itBegin; + while (scriptCode.GetOp(it, opcode)) { + if (opcode == OP_CODESEPARATOR) { + s.write((char*)&itBegin[0], it-itBegin-1); + itBegin = it; + } + } + s.write((char*)&itBegin[0], it-itBegin); } - CTransaction txTmp(txTo); - // In case concatenating two scripts ends up with two codeseparators, - // or an extra one at the end, this prevents all those possible incompatibilities. - scriptCode.FindAndDelete(CScript(OP_CODESEPARATOR)); + /** Serialize an input of txTo */ + template<typename S> + void SerializeInput(S &s, unsigned int nInput, int nType, int nVersion) const { + // In case of SIGHASH_ANYONECANPAY, only the input being signed is serialized + if (fAnyoneCanPay) + nInput = nIn; + // Serialize the prevout + ::Serialize(s, txTo.vin[nInput].prevout, nType, nVersion); + // Serialize the script + if (nInput != nIn) + // Blank out other inputs' signatures + ::Serialize(s, CScript(), nType, nVersion); + else + SerializeScriptCode(s, nType, nVersion); + // Serialize the nSequence + if (nInput != nIn && (fHashSingle || fHashNone)) + // let the others update at will + ::Serialize(s, (int)0, nType, nVersion); + else + ::Serialize(s, txTo.vin[nInput].nSequence, nType, nVersion); + } - // Blank out other inputs' signatures - for (unsigned int i = 0; i < txTmp.vin.size(); i++) - txTmp.vin[i].scriptSig = CScript(); - txTmp.vin[nIn].scriptSig = scriptCode; + /** Serialize an output of txTo */ + template<typename S> + void SerializeOutput(S &s, unsigned int nOutput, int nType, int nVersion) const { + if (fHashSingle && nOutput != nIn) + // Do not lock-in the txout payee at other indices as txin + ::Serialize(s, CTxOut(), nType, nVersion); + else + ::Serialize(s, txTo.vout[nOutput], nType, nVersion); + } - // Blank out some of the outputs - if ((nHashType & 0x1f) == SIGHASH_NONE) - { - // Wildcard payee - txTmp.vout.clear(); + /** Serialize txTo */ + template<typename S> + void Serialize(S &s, int nType, int nVersion) const { + // Serialize nVersion + ::Serialize(s, txTo.nVersion, nType, nVersion); + // Serialize vin + unsigned int nInputs = fAnyoneCanPay ? 1 : txTo.vin.size(); + ::WriteCompactSize(s, nInputs); + for (unsigned int nInput = 0; nInput < nInputs; nInput++) + SerializeInput(s, nInput, nType, nVersion); + // Serialize vout + unsigned int nOutputs = fHashNone ? 0 : (fHashSingle ? nIn+1 : txTo.vout.size()); + ::WriteCompactSize(s, nOutputs); + for (unsigned int nOutput = 0; nOutput < nOutputs; nOutput++) + SerializeOutput(s, nOutput, nType, nVersion); + // Serialie nLockTime + ::Serialize(s, txTo.nLockTime, nType, nVersion); + } +}; +} - // Let the others update at will - for (unsigned int i = 0; i < txTmp.vin.size(); i++) - if (i != nIn) - txTmp.vin[i].nSequence = 0; +uint256 SignatureHash(const CScript &scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType) +{ + if (nIn >= txTo.vin.size()) { + LogPrintf("ERROR: SignatureHash() : nIn=%d out of range\n", nIn); + return 1; } - else if ((nHashType & 0x1f) == SIGHASH_SINGLE) - { - // Only lock-in the txout payee at same index as txin - unsigned int nOut = nIn; - if (nOut >= txTmp.vout.size()) - { - LogPrintf("ERROR: SignatureHash() : nOut=%d out of range\n", nOut); + + // Check for invalid use of SIGHASH_SINGLE + if ((nHashType & 0x1f) == SIGHASH_SINGLE) { + if (nIn >= txTo.vout.size()) { + LogPrintf("ERROR: SignatureHash() : nOut=%d out of range\n", nIn); return 1; } - txTmp.vout.resize(nOut+1); - for (unsigned int i = 0; i < nOut; i++) - txTmp.vout[i].SetNull(); - - // Let the others update at will - for (unsigned int i = 0; i < txTmp.vin.size(); i++) - if (i != nIn) - txTmp.vin[i].nSequence = 0; } - // Blank out other inputs completely, not recommended for open transactions - if (nHashType & SIGHASH_ANYONECANPAY) - { - txTmp.vin[0] = txTmp.vin[nIn]; - txTmp.vin.resize(1); - } + // Wrapper to serialize only the necessary parts of the transaction being signed + CTransactionSignatureSerializer txTmp(txTo, scriptCode, nIn, nHashType); // Serialize and hash CHashWriter ss(SER_GETHASH, 0); diff --git a/src/serialize.h b/src/serialize.h index bafd6afa6a..4d9aec3426 100644 --- a/src/serialize.h +++ b/src/serialize.h @@ -216,18 +216,24 @@ uint64 ReadCompactSize(Stream& is) unsigned short xSize; READDATA(is, xSize); nSizeRet = xSize; + if (nSizeRet < 253) + throw std::ios_base::failure("non-canonical ReadCompactSize()"); } else if (chSize == 254) { unsigned int xSize; READDATA(is, xSize); nSizeRet = xSize; + if (nSizeRet < 0x10000u) + throw std::ios_base::failure("non-canonical ReadCompactSize()"); } else { uint64 xSize; READDATA(is, xSize); nSizeRet = xSize; + if (nSizeRet < 0x100000000LLu) + throw std::ios_base::failure("non-canonical ReadCompactSize()"); } if (nSizeRet > (uint64)MAX_SIZE) throw std::ios_base::failure("ReadCompactSize() : size too large"); diff --git a/src/test/Makefile.am b/src/test/Makefile.am index a859eb1de8..c3495095d9 100644 --- a/src/test/Makefile.am +++ b/src/test/Makefile.am @@ -34,7 +34,7 @@ test_bitcoin_SOURCES = accounting_tests.cpp alert_tests.cpp \ netbase_tests.cpp pmt_tests.cpp rpc_tests.cpp script_P2SH_tests.cpp \ script_tests.cpp serialize_tests.cpp sigopcount_tests.cpp test_bitcoin.cpp \ transaction_tests.cpp uint160_tests.cpp uint256_tests.cpp util_tests.cpp \ - wallet_tests.cpp $(JSON_TEST_FILES) $(RAW_TEST_FILES) + wallet_tests.cpp sighash_tests.cpp $(JSON_TEST_FILES) $(RAW_TEST_FILES) nodist_test_bitcoin_SOURCES = $(BUILT_SOURCES) diff --git a/src/test/miner_tests.cpp b/src/test/miner_tests.cpp index eeeacb0ad4..67165760b2 100644 --- a/src/test/miner_tests.cpp +++ b/src/test/miner_tests.cpp @@ -65,10 +65,10 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) { CBlock *pblock = &pblocktemplate->block; // pointer for convenience pblock->nVersion = 1; - pblock->nTime = pindexBest->GetMedianTimePast()+1; + pblock->nTime = chainActive.Tip()->GetMedianTimePast()+1; pblock->vtx[0].vin[0].scriptSig = CScript(); pblock->vtx[0].vin[0].scriptSig.push_back(blockinfo[i].extranonce); - pblock->vtx[0].vin[0].scriptSig.push_back(pindexBest->nHeight); + pblock->vtx[0].vin[0].scriptSig.push_back(chainActive.Height()); pblock->vtx[0].vout[0].scriptPubKey = CScript(); if (txFirst.size() < 2) txFirst.push_back(new CTransaction(pblock->vtx[0])); @@ -193,14 +193,14 @@ BOOST_AUTO_TEST_CASE(CreateNewBlock_validity) mempool.clear(); // subsidy changing - int nHeight = pindexBest->nHeight; - pindexBest->nHeight = 209999; + int nHeight = chainActive.Height(); + chainActive.Tip()->nHeight = 209999; BOOST_CHECK(pblocktemplate = CreateNewBlockWithKey(reservekey)); delete pblocktemplate; - pindexBest->nHeight = 210000; + chainActive.Tip()->nHeight = 210000; BOOST_CHECK(pblocktemplate = CreateNewBlockWithKey(reservekey)); delete pblocktemplate; - pindexBest->nHeight = nHeight; + chainActive.Tip()->nHeight = nHeight; BOOST_FOREACH(CTransaction *tx, txFirst) delete tx; diff --git a/src/test/multisig_tests.cpp b/src/test/multisig_tests.cpp index 9ef932b5b4..7f6f141c62 100644 --- a/src/test/multisig_tests.cpp +++ b/src/test/multisig_tests.cpp @@ -19,7 +19,7 @@ using namespace boost::assign; typedef vector<unsigned char> valtype; -extern uint256 SignatureHash(CScript scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType); +extern uint256 SignatureHash(const CScript &scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType); BOOST_AUTO_TEST_SUITE(multisig_tests) diff --git a/src/test/script_tests.cpp b/src/test/script_tests.cpp index dfa5529b87..32be914414 100644 --- a/src/test/script_tests.cpp +++ b/src/test/script_tests.cpp @@ -21,7 +21,7 @@ using namespace std; using namespace json_spirit; using namespace boost::algorithm; -extern uint256 SignatureHash(CScript scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType); +extern uint256 SignatureHash(const CScript &scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType); static const unsigned int flags = SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_STRICTENC; diff --git a/src/test/serialize_tests.cpp b/src/test/serialize_tests.cpp index 19ffdcab66..50139df09e 100644 --- a/src/test/serialize_tests.cpp +++ b/src/test/serialize_tests.cpp @@ -39,7 +39,67 @@ BOOST_AUTO_TEST_CASE(varints) ss >> VARINT(j); BOOST_CHECK_MESSAGE(i == j, "decoded:" << j << " expected:" << i); } +} + +BOOST_AUTO_TEST_CASE(compactsize) +{ + CDataStream ss(SER_DISK, 0); + vector<char>::size_type i, j; + + for (i = 1; i <= MAX_SIZE; i *= 2) + { + WriteCompactSize(ss, i-1); + WriteCompactSize(ss, i); + } + for (i = 1; i <= MAX_SIZE; i *= 2) + { + j = ReadCompactSize(ss); + BOOST_CHECK_MESSAGE((i-1) == j, "decoded:" << j << " expected:" << (i-1)); + j = ReadCompactSize(ss); + BOOST_CHECK_MESSAGE(i == j, "decoded:" << j << " expected:" << i); + } +} + +static bool isCanonicalException(const std::ios_base::failure& ex) +{ + return std::string("non-canonical ReadCompactSize()") == ex.what(); +} + +BOOST_AUTO_TEST_CASE(noncanonical) +{ + // Write some non-canonical CompactSize encodings, and + // make sure an exception is thrown when read back. + CDataStream ss(SER_DISK, 0); + vector<char>::size_type n; + + // zero encoded with three bytes: + ss.write("\xfd\x00\x00", 3); + BOOST_CHECK_EXCEPTION(ReadCompactSize(ss), std::ios_base::failure, isCanonicalException); + + // 0xfc encoded with three bytes: + ss.write("\xfd\xfc\x00", 3); + BOOST_CHECK_EXCEPTION(ReadCompactSize(ss), std::ios_base::failure, isCanonicalException); + + // 0xfd encoded with three bytes is OK: + ss.write("\xfd\xfd\x00", 3); + n = ReadCompactSize(ss); + BOOST_CHECK(n == 0xfd); + + // zero encoded with five bytes: + ss.write("\xfe\x00\x00\x00\x00", 5); + BOOST_CHECK_EXCEPTION(ReadCompactSize(ss), std::ios_base::failure, isCanonicalException); + + // 0xffff encoded with five bytes: + ss.write("\xfe\xff\xff\x00\x00", 5); + BOOST_CHECK_EXCEPTION(ReadCompactSize(ss), std::ios_base::failure, isCanonicalException); + + // zero encoded with nine bytes: + ss.write("\xff\x00\x00\x00\x00\x00\x00\x00\x00", 9); + BOOST_CHECK_EXCEPTION(ReadCompactSize(ss), std::ios_base::failure, isCanonicalException); + // 0x01ffffff encoded with nine bytes: + ss.write("\xff\xff\xff\xff\x01\x00\x00\x00\x00", 9); + BOOST_CHECK_EXCEPTION(ReadCompactSize(ss), std::ios_base::failure, isCanonicalException); } BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/sighash_tests.cpp b/src/test/sighash_tests.cpp new file mode 100644 index 0000000000..f098d46186 --- /dev/null +++ b/src/test/sighash_tests.cpp @@ -0,0 +1,120 @@ +#include <boost/test/unit_test.hpp> + +#include "main.h" +#include "util.h" + +extern uint256 SignatureHash(const CScript &scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType); + +// Old script.cpp SignatureHash function +uint256 static SignatureHashOld(CScript scriptCode, const CTransaction& txTo, unsigned int nIn, int nHashType) +{ + if (nIn >= txTo.vin.size()) + { + printf("ERROR: SignatureHash() : nIn=%d out of range\n", nIn); + return 1; + } + CTransaction txTmp(txTo); + + // In case concatenating two scripts ends up with two codeseparators, + // or an extra one at the end, this prevents all those possible incompatibilities. + scriptCode.FindAndDelete(CScript(OP_CODESEPARATOR)); + + // Blank out other inputs' signatures + for (unsigned int i = 0; i < txTmp.vin.size(); i++) + txTmp.vin[i].scriptSig = CScript(); + txTmp.vin[nIn].scriptSig = scriptCode; + + // Blank out some of the outputs + if ((nHashType & 0x1f) == SIGHASH_NONE) + { + // Wildcard payee + txTmp.vout.clear(); + + // Let the others update at will + for (unsigned int i = 0; i < txTmp.vin.size(); i++) + if (i != nIn) + txTmp.vin[i].nSequence = 0; + } + else if ((nHashType & 0x1f) == SIGHASH_SINGLE) + { + // Only lock-in the txout payee at same index as txin + unsigned int nOut = nIn; + if (nOut >= txTmp.vout.size()) + { + printf("ERROR: SignatureHash() : nOut=%d out of range\n", nOut); + return 1; + } + txTmp.vout.resize(nOut+1); + for (unsigned int i = 0; i < nOut; i++) + txTmp.vout[i].SetNull(); + + // Let the others update at will + for (unsigned int i = 0; i < txTmp.vin.size(); i++) + if (i != nIn) + txTmp.vin[i].nSequence = 0; + } + + // Blank out other inputs completely, not recommended for open transactions + if (nHashType & SIGHASH_ANYONECANPAY) + { + txTmp.vin[0] = txTmp.vin[nIn]; + txTmp.vin.resize(1); + } + + // Serialize and hash + CHashWriter ss(SER_GETHASH, 0); + ss << txTmp << nHashType; + return ss.GetHash(); +} + +void static RandomScript(CScript &script) { + static const opcodetype oplist[] = {OP_FALSE, OP_1, OP_2, OP_3, OP_CHECKSIG, OP_IF, OP_VERIF, OP_RETURN, OP_CODESEPARATOR}; + script = CScript(); + int ops = (insecure_rand() % 10); + for (int i=0; i<ops; i++) + script << oplist[insecure_rand() % (sizeof(oplist)/sizeof(oplist[0]))]; +} + +void static RandomTransaction(CTransaction &tx, bool fSingle) { + tx.nVersion = insecure_rand(); + tx.vin.clear(); + tx.vout.clear(); + tx.nLockTime = (insecure_rand() % 2) ? insecure_rand() : 0; + int ins = (insecure_rand() % 4) + 1; + int outs = fSingle ? ins : (insecure_rand() % 4) + 1; + for (int in = 0; in < ins; in++) { + tx.vin.push_back(CTxIn()); + CTxIn &txin = tx.vin.back(); + txin.prevout.hash = GetRandHash(); + txin.prevout.n = insecure_rand() % 4; + RandomScript(txin.scriptSig); + txin.nSequence = (insecure_rand() % 2) ? insecure_rand() : (unsigned int)-1; + } + for (int out = 0; out < outs; out++) { + tx.vout.push_back(CTxOut()); + CTxOut &txout = tx.vout.back(); + txout.nValue = insecure_rand() % 100000000; + RandomScript(txout.scriptPubKey); + } +} + +BOOST_AUTO_TEST_SUITE(sighash_tests) + +BOOST_AUTO_TEST_CASE(sighash_test) +{ + seed_insecure_rand(false); + + for (int i=0; i<50000; i++) { + int nHashType = insecure_rand(); + CTransaction txTo; + RandomTransaction(txTo, (nHashType & 0x1f) == SIGHASH_SINGLE); + CScript scriptCode; + RandomScript(scriptCode); + int nIn = insecure_rand() % txTo.vin.size(); + BOOST_CHECK(SignatureHash(scriptCode, txTo, nIn, nHashType) == + SignatureHashOld(scriptCode, txTo, nIn, nHashType)); + } +} + +BOOST_AUTO_TEST_SUITE_END() + diff --git a/src/txdb.cpp b/src/txdb.cpp index d1f26e9f64..5e7b78296c 100644 --- a/src/txdb.cpp +++ b/src/txdb.cpp @@ -223,10 +223,6 @@ bool CBlockTreeDB::LoadBlockIndexGuts() pindexNew->nStatus = diskindex.nStatus; pindexNew->nTx = diskindex.nTx; - // Watch for genesis block - if (pindexGenesisBlock == NULL && diskindex.GetBlockHash() == Params().HashGenesisBlock()) - pindexGenesisBlock = pindexNew; - if (!pindexNew->CheckIndex()) return error("LoadBlockIndex() : CheckIndex failed: %s", pindexNew->ToString().c_str()); diff --git a/src/wallet.cpp b/src/wallet.cpp index cdefb16fa5..5eb2808920 100644 --- a/src/wallet.cpp +++ b/src/wallet.cpp @@ -799,7 +799,7 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate) // no need to read and scan block, if block was created before // our wallet birthday (as adjusted for block time variability) if (nTimeFirstKey && (pindex->nTime < (nTimeFirstKey - 7200))) { - pindex = pindex->GetNextInMainChain(); + pindex = chainActive.Next(pindex); continue; } @@ -810,7 +810,7 @@ int CWallet::ScanForWalletTransactions(CBlockIndex* pindexStart, bool fUpdate) if (AddToWalletIfInvolvingMe(tx.GetHash(), tx, &block, fUpdate)) ret++; } - pindex = pindex->GetNextInMainChain(); + pindex = chainActive.Next(pindex); } } return ret; @@ -864,7 +864,7 @@ void CWallet::ReacceptWalletTransactions() if (fMissing) { // TODO: optimize this to scan just part of the block chain? - if (ScanForWalletTransactions(pindexGenesisBlock)) + if (ScanForWalletTransactions(chainActive.Genesis())) fRepeat = true; // Found missing transactions: re-do re-accept. } } @@ -1231,9 +1231,10 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64> >& vecSend, } int64 nChange = nValueIn - nValue - nFeeRet; - // if sub-cent change is required, the fee must be raised to at least nMinTxFee - // or until nChange becomes zero - // NOTE: this depends on the exact behaviour of GetMinFee + // The following if statement should be removed once enough miners + // have upgraded to the 0.9 GetMinFee() rules. Until then, this avoids + // creating free transactions that have change outputs less than + // CENT bitcoins. if (nFeeRet < CTransaction::nMinTxFee && nChange > 0 && nChange < CENT) { int64 nMoveToFee = min(nChange, CTransaction::nMinTxFee - nFeeRet); @@ -1299,7 +1300,15 @@ bool CWallet::CreateTransaction(const vector<pair<CScript, int64> >& vecSend, strFailReason = _("Transaction too large"); return false; } - dPriority /= nBytes; + unsigned int nTxSizeMod = nBytes; + // See miner.c's dPriority logic for the matching network-node side code. + BOOST_FOREACH(const CTxIn& txin, (*(CTransaction*)&wtxNew).vin) + { + unsigned int offset = 41U + min(110U, (unsigned int)txin.scriptSig.size()); + if (nTxSizeMod > offset) + nTxSizeMod -= offset; + } + dPriority /= nTxSizeMod; // Check that enough fee is included int64 nPayFee = nTransactionFee * (1 + (int64)nBytes / 1000); @@ -1933,7 +1942,7 @@ void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64> &mapKeyBirth) const { mapKeyBirth[it->first] = it->second.nCreateTime; // map in which we'll infer heights of other keys - CBlockIndex *pindexMax = FindBlockByHeight(std::max(0, nBestHeight - 144)); // the tip can be reorganised; use a 144-block safety margin + CBlockIndex *pindexMax = chainActive[std::max(0, chainActive.Height() - 144)]; // the tip can be reorganised; use a 144-block safety margin std::map<CKeyID, CBlockIndex*> mapKeyFirstBlock; std::set<CKeyID> setKeys; GetKeys(setKeys); @@ -1953,7 +1962,7 @@ void CWallet::GetKeyBirthTimes(std::map<CKeyID, int64> &mapKeyBirth) const { // iterate over all wallet transactions... const CWalletTx &wtx = (*it).second; std::map<uint256, CBlockIndex*>::const_iterator blit = mapBlockIndex.find(wtx.hashBlock); - if (blit != mapBlockIndex.end() && blit->second->IsInMainChain()) { + if (blit != mapBlockIndex.end() && chainActive.Contains(blit->second)) { // ... which are already in a block int nHeight = blit->second->nHeight; BOOST_FOREACH(const CTxOut &txout, wtx.vout) { diff --git a/src/walletdb.cpp b/src/walletdb.cpp index 6f1f0365ff..831ef8b00d 100644 --- a/src/walletdb.cpp +++ b/src/walletdb.cpp @@ -306,6 +306,8 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, } CKey key; CPrivKey pkey; + uint256 hash = 0; + if (strType == "key") { wss.nKeys++; @@ -315,14 +317,40 @@ ReadKeyValue(CWallet* pwallet, CDataStream& ssKey, CDataStream& ssValue, ssValue >> wkey; pkey = wkey.vchPrivKey; } - if (!key.SetPrivKey(pkey, vchPubKey.IsCompressed())) + + // Old wallets store keys as "key" [pubkey] => [privkey] + // ... which was slow for wallets with lots of keys, because the public key is re-derived from the private key + // using EC operations as a checksum. + // Newer wallets store keys as "key"[pubkey] => [privkey][hash(pubkey,privkey)], which is much faster while + // remaining backwards-compatible. + try { - strErr = "Error reading wallet database: CPrivKey corrupt"; - return false; + ssValue >> hash; + } + catch(...){} + + bool fSkipCheck = false; + + if (hash != 0) + { + // hash pubkey/privkey to accelerate wallet load + std::vector<unsigned char> vchKey; + vchKey.reserve(vchPubKey.size() + pkey.size()); + vchKey.insert(vchKey.end(), vchPubKey.begin(), vchPubKey.end()); + vchKey.insert(vchKey.end(), pkey.begin(), pkey.end()); + + if (Hash(vchKey.begin(), vchKey.end()) != hash) + { + strErr = "Error reading wallet database: CPubKey/CPrivKey corrupt"; + return false; + } + + fSkipCheck = true; } - if (key.GetPubKey() != vchPubKey) + + if (!key.Load(pkey, vchPubKey, fSkipCheck)) { - strErr = "Error reading wallet database: CPrivKey pubkey inconsistency"; + strErr = "Error reading wallet database: CPrivKey corrupt"; return false; } if (!pwallet->LoadKey(key, vchPubKey)) diff --git a/src/walletdb.h b/src/walletdb.h index dd50025a23..b6d0d45449 100644 --- a/src/walletdb.h +++ b/src/walletdb.h @@ -93,8 +93,14 @@ public: if (!Write(std::make_pair(std::string("keymeta"), vchPubKey), keyMeta)) return false; - - return Write(std::make_pair(std::string("key"), vchPubKey), vchPrivKey, false); + + // hash pubkey/privkey to accelerate wallet load + std::vector<unsigned char> vchKey; + vchKey.reserve(vchPubKey.size() + vchPrivKey.size()); + vchKey.insert(vchKey.end(), vchPubKey.begin(), vchPubKey.end()); + vchKey.insert(vchKey.end(), vchPrivKey.begin(), vchPrivKey.end()); + + return Write(std::make_pair(std::string("key"), vchPubKey), std::make_pair(vchPrivKey, Hash(vchKey.begin(), vchKey.end())), false); } bool WriteCryptedKey(const CPubKey& vchPubKey, |