diff options
Diffstat (limited to 'src')
52 files changed, 926 insertions, 598 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 8de7f7ff7a..0a9370c85c 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -105,6 +105,7 @@ BITCOIN_CORE_H = \ indirectmap.h \ init.h \ key.h \ + key_io.h \ keystore.h \ dbwrapper.h \ limitedmap.h \ @@ -327,6 +328,7 @@ libbitcoin_common_a_SOURCES = \ core_read.cpp \ core_write.cpp \ key.cpp \ + key_io.cpp \ keystore.cpp \ netaddress.cpp \ netbase.cpp \ diff --git a/src/Makefile.test.include b/src/Makefile.test.include index d4d972b2bb..4ee9102519 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -9,13 +9,13 @@ TEST_SRCDIR = test TEST_BINARY=test/test_bitcoin$(EXEEXT) JSON_TEST_FILES = \ - test/data/script_tests.json \ - test/data/base58_keys_valid.json \ test/data/base58_encode_decode.json \ - test/data/base58_keys_invalid.json \ + test/data/key_io_valid.json \ + test/data/key_io_invalid.json \ + test/data/script_tests.json \ + test/data/sighash.json \ test/data/tx_invalid.json \ - test/data/tx_valid.json \ - test/data/sighash.json + test/data/tx_valid.json RAW_TEST_FILES = @@ -45,6 +45,7 @@ BITCOIN_TESTS =\ test/DoS_tests.cpp \ test/getarg_tests.cpp \ test/hash_tests.cpp \ + test/key_io_tests.cpp \ test/key_tests.cpp \ test/limitedmap_tests.cpp \ test/dbwrapper_tests.cpp \ diff --git a/src/addrman.cpp b/src/addrman.cpp index 9eefffb45b..e811dd4bea 100644 --- a/src/addrman.cpp +++ b/src/addrman.cpp @@ -187,7 +187,7 @@ void CAddrMan::MakeTried(CAddrInfo& info, int nId) info.fInTried = true; } -void CAddrMan::Good_(const CService& addr, int64_t nTime) +void CAddrMan::Good_(const CService& addr, bool test_before_evict, int64_t nTime) { int nId; @@ -233,10 +233,22 @@ void CAddrMan::Good_(const CService& addr, int64_t nTime) if (nUBucket == -1) return; - LogPrint(BCLog::ADDRMAN, "Moving %s to tried\n", addr.ToString()); + // which tried bucket to move the entry to + int tried_bucket = info.GetTriedBucket(nKey); + int tried_bucket_pos = info.GetBucketPosition(nKey, false, tried_bucket); + + // Will moving this address into tried evict another entry? + if (test_before_evict && (vvTried[tried_bucket][tried_bucket_pos] != -1)) { + LogPrint(BCLog::ADDRMAN, "Collision inserting element into tried table, moving %s to m_tried_collisions=%d\n", addr.ToString(), m_tried_collisions.size()); + if (m_tried_collisions.size() < ADDRMAN_SET_TRIED_COLLISION_SIZE) { + m_tried_collisions.insert(nId); + } + } else { + LogPrint(BCLog::ADDRMAN, "Moving %s to tried\n", addr.ToString()); - // move nId to the tried tables - MakeTried(info, nId); + // move nId to the tried tables + MakeTried(info, nId); + } } bool CAddrMan::Add_(const CAddress& addr, const CNetAddr& source, int64_t nTimePenalty) @@ -521,3 +533,82 @@ void CAddrMan::SetServices_(const CService& addr, ServiceFlags nServices) int CAddrMan::RandomInt(int nMax){ return GetRandInt(nMax); } + +void CAddrMan::ResolveCollisions_() +{ + for (std::set<int>::iterator it = m_tried_collisions.begin(); it != m_tried_collisions.end();) { + int id_new = *it; + + bool erase_collision = false; + + // If id_new not found in mapInfo remove it from m_tried_collisions + if (mapInfo.count(id_new) != 1) { + erase_collision = true; + } else { + CAddrInfo& info_new = mapInfo[id_new]; + + // Which tried bucket to move the entry to. + int tried_bucket = info_new.GetTriedBucket(nKey); + int tried_bucket_pos = info_new.GetBucketPosition(nKey, false, tried_bucket); + if (!info_new.IsValid()) { // id_new may no longer map to a valid address + erase_collision = true; + } else if (vvTried[tried_bucket][tried_bucket_pos] != -1) { // The position in the tried bucket is not empty + + // Get the to-be-evicted address that is being tested + int id_old = vvTried[tried_bucket][tried_bucket_pos]; + CAddrInfo& info_old = mapInfo[id_old]; + + // Has successfully connected in last X hours + if (GetAdjustedTime() - info_old.nLastSuccess < ADDRMAN_REPLACEMENT_HOURS*(60*60)) { + erase_collision = true; + } else if (GetAdjustedTime() - info_old.nLastTry < ADDRMAN_REPLACEMENT_HOURS*(60*60)) { // attempted to connect and failed in last X hours + + // Give address at least 60 seconds to successfully connect + if (GetAdjustedTime() - info_old.nLastTry > 60) { + LogPrint(BCLog::ADDRMAN, "Swapping %s for %s in tried table\n", info_new.ToString(), info_old.ToString()); + + // Replaces an existing address already in the tried table with the new address + Good_(info_new, false, GetAdjustedTime()); + erase_collision = true; + } + } + } else { // Collision is not actually a collision anymore + Good_(info_new, false, GetAdjustedTime()); + erase_collision = true; + } + } + + if (erase_collision) { + m_tried_collisions.erase(it++); + } else { + it++; + } + } +} + +CAddrInfo CAddrMan::SelectTriedCollision_() +{ + if (m_tried_collisions.size() == 0) return CAddrInfo(); + + std::set<int>::iterator it = m_tried_collisions.begin(); + + // Selects a random element from m_tried_collisions + std::advance(it, GetRandInt(m_tried_collisions.size())); + int id_new = *it; + + // If id_new not found in mapInfo remove it from m_tried_collisions + if (mapInfo.count(id_new) != 1) { + m_tried_collisions.erase(it); + return CAddrInfo(); + } + + CAddrInfo& newInfo = mapInfo[id_new]; + + // which tried bucket to move the entry to + int tried_bucket = newInfo.GetTriedBucket(nKey); + int tried_bucket_pos = newInfo.GetBucketPosition(nKey, false, tried_bucket); + + int id_old = vvTried[tried_bucket][tried_bucket_pos]; + + return mapInfo[id_old]; +} diff --git a/src/addrman.h b/src/addrman.h index 38da754afb..67423c6c55 100644 --- a/src/addrman.h +++ b/src/addrman.h @@ -165,6 +165,9 @@ public: //! ... in at least this many days #define ADDRMAN_MIN_FAIL_DAYS 7 +//! how recent a successful connection should be before we allow an address to be evicted from tried +#define ADDRMAN_REPLACEMENT_HOURS 4 + //! the maximum percentage of nodes to return in a getaddr call #define ADDRMAN_GETADDR_MAX_PCT 23 @@ -176,6 +179,9 @@ public: #define ADDRMAN_NEW_BUCKET_COUNT (1 << ADDRMAN_NEW_BUCKET_COUNT_LOG2) #define ADDRMAN_BUCKET_SIZE (1 << ADDRMAN_BUCKET_SIZE_LOG2) +//! the maximum number of tried addr collisions to store +#define ADDRMAN_SET_TRIED_COLLISION_SIZE 10 + /** * Stochastical (IP) address manager */ @@ -212,6 +218,9 @@ private: //! last time Good was called (memory only) int64_t nLastGood; + //! Holds addrs inserted into tried table that collide with existing entries. Test-before-evict discpline used to resolve these collisions. + std::set<int> m_tried_collisions; + protected: //! secret key to randomize bucket select with uint256 nKey; @@ -239,7 +248,7 @@ protected: void ClearNew(int nUBucket, int nUBucketPos); //! Mark an entry "good", possibly moving it from "new" to "tried". - void Good_(const CService &addr, int64_t nTime); + void Good_(const CService &addr, bool test_before_evict, int64_t time); //! Add an entry to the "new" table. bool Add_(const CAddress &addr, const CNetAddr& source, int64_t nTimePenalty); @@ -250,6 +259,12 @@ protected: //! Select an address to connect to, if newOnly is set to true, only the new table is selected from. CAddrInfo Select_(bool newOnly); + //! See if any to-be-evicted tried table entries have been tested and if so resolve the collisions. + void ResolveCollisions_(); + + //! Return a random to-be-evicted tried table address. + CAddrInfo SelectTriedCollision_(); + //! Wraps GetRandInt to allow tests to override RandomInt and make it determinismistic. virtual int RandomInt(int nMax); @@ -537,11 +552,11 @@ public: } //! Mark an entry as accessible. - void Good(const CService &addr, int64_t nTime = GetAdjustedTime()) + void Good(const CService &addr, bool test_before_evict = true, int64_t nTime = GetAdjustedTime()) { LOCK(cs); Check(); - Good_(addr, nTime); + Good_(addr, test_before_evict, nTime); Check(); } @@ -554,6 +569,28 @@ public: Check(); } + //! See if any to-be-evicted tried table entries have been tested and if so resolve the collisions. + void ResolveCollisions() + { + LOCK(cs); + Check(); + ResolveCollisions_(); + Check(); + } + + //! Randomly select an address in tried that another address is attempting to evict. + CAddrInfo SelectTriedCollision() + { + CAddrInfo ret; + { + LOCK(cs); + Check(); + ret = SelectTriedCollision_(); + Check(); + } + return ret; + } + /** * Choose an address to connect to. */ diff --git a/src/arith_uint256.h b/src/arith_uint256.h index dc2627592e..3f4cc8c2bf 100644 --- a/src/arith_uint256.h +++ b/src/arith_uint256.h @@ -85,7 +85,7 @@ public: base_uint ret; for (int i = 0; i < WIDTH; i++) ret.pn[i] = ~pn[i]; - ret++; + ++ret; return ret; } diff --git a/src/base58.cpp b/src/base58.cpp index 499afbe382..982e123a1d 100644 --- a/src/base58.cpp +++ b/src/base58.cpp @@ -4,20 +4,12 @@ #include <base58.h> -#include <bech32.h> #include <hash.h> -#include <script/script.h> #include <uint256.h> -#include <utilstrencodings.h> -#include <boost/variant/apply_visitor.hpp> -#include <boost/variant/static_visitor.hpp> - -#include <algorithm> #include <assert.h> #include <string.h> - /** All alphanumeric characters except for "0", "I", "O", and "l" */ static const char* pszBase58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; @@ -151,227 +143,3 @@ bool DecodeBase58Check(const std::string& str, std::vector<unsigned char>& vchRe { return DecodeBase58Check(str.c_str(), vchRet); } - -CBase58Data::CBase58Data() -{ - vchVersion.clear(); - vchData.clear(); -} - -void CBase58Data::SetData(const std::vector<unsigned char>& vchVersionIn, const void* pdata, size_t nSize) -{ - vchVersion = vchVersionIn; - vchData.resize(nSize); - if (!vchData.empty()) - memcpy(vchData.data(), pdata, nSize); -} - -void CBase58Data::SetData(const std::vector<unsigned char>& vchVersionIn, const unsigned char* pbegin, const unsigned char* pend) -{ - SetData(vchVersionIn, (void*)pbegin, pend - pbegin); -} - -bool CBase58Data::SetString(const char* psz, unsigned int nVersionBytes) -{ - std::vector<unsigned char> vchTemp; - bool rc58 = DecodeBase58Check(psz, vchTemp); - if ((!rc58) || (vchTemp.size() < nVersionBytes)) { - vchData.clear(); - vchVersion.clear(); - return false; - } - vchVersion.assign(vchTemp.begin(), vchTemp.begin() + nVersionBytes); - vchData.resize(vchTemp.size() - nVersionBytes); - if (!vchData.empty()) - memcpy(vchData.data(), vchTemp.data() + nVersionBytes, vchData.size()); - memory_cleanse(vchTemp.data(), vchTemp.size()); - return true; -} - -bool CBase58Data::SetString(const std::string& str) -{ - return SetString(str.c_str()); -} - -std::string CBase58Data::ToString() const -{ - std::vector<unsigned char> vch = vchVersion; - vch.insert(vch.end(), vchData.begin(), vchData.end()); - return EncodeBase58Check(vch); -} - -int CBase58Data::CompareTo(const CBase58Data& b58) const -{ - if (vchVersion < b58.vchVersion) - return -1; - if (vchVersion > b58.vchVersion) - return 1; - if (vchData < b58.vchData) - return -1; - if (vchData > b58.vchData) - return 1; - return 0; -} - -namespace -{ -class DestinationEncoder : public boost::static_visitor<std::string> -{ -private: - const CChainParams& m_params; - -public: - DestinationEncoder(const CChainParams& params) : m_params(params) {} - - std::string operator()(const CKeyID& id) const - { - std::vector<unsigned char> data = m_params.Base58Prefix(CChainParams::PUBKEY_ADDRESS); - data.insert(data.end(), id.begin(), id.end()); - return EncodeBase58Check(data); - } - - std::string operator()(const CScriptID& id) const - { - std::vector<unsigned char> data = m_params.Base58Prefix(CChainParams::SCRIPT_ADDRESS); - data.insert(data.end(), id.begin(), id.end()); - return EncodeBase58Check(data); - } - - std::string operator()(const WitnessV0KeyHash& id) const - { - std::vector<unsigned char> data = {0}; - ConvertBits<8, 5, true>(data, id.begin(), id.end()); - return bech32::Encode(m_params.Bech32HRP(), data); - } - - std::string operator()(const WitnessV0ScriptHash& id) const - { - std::vector<unsigned char> data = {0}; - ConvertBits<8, 5, true>(data, id.begin(), id.end()); - return bech32::Encode(m_params.Bech32HRP(), data); - } - - std::string operator()(const WitnessUnknown& id) const - { - if (id.version < 1 || id.version > 16 || id.length < 2 || id.length > 40) { - return {}; - } - std::vector<unsigned char> data = {(unsigned char)id.version}; - ConvertBits<8, 5, true>(data, id.program, id.program + id.length); - return bech32::Encode(m_params.Bech32HRP(), data); - } - - std::string operator()(const CNoDestination& no) const { return {}; } -}; - -CTxDestination DecodeDestination(const std::string& str, const CChainParams& params) -{ - std::vector<unsigned char> data; - uint160 hash; - if (DecodeBase58Check(str, data)) { - // base58-encoded Bitcoin addresses. - // Public-key-hash-addresses have version 0 (or 111 testnet). - // The data vector contains RIPEMD160(SHA256(pubkey)), where pubkey is the serialized public key. - const std::vector<unsigned char>& pubkey_prefix = params.Base58Prefix(CChainParams::PUBKEY_ADDRESS); - if (data.size() == hash.size() + pubkey_prefix.size() && std::equal(pubkey_prefix.begin(), pubkey_prefix.end(), data.begin())) { - std::copy(data.begin() + pubkey_prefix.size(), data.end(), hash.begin()); - return CKeyID(hash); - } - // Script-hash-addresses have version 5 (or 196 testnet). - // The data vector contains RIPEMD160(SHA256(cscript)), where cscript is the serialized redemption script. - const std::vector<unsigned char>& script_prefix = params.Base58Prefix(CChainParams::SCRIPT_ADDRESS); - if (data.size() == hash.size() + script_prefix.size() && std::equal(script_prefix.begin(), script_prefix.end(), data.begin())) { - std::copy(data.begin() + script_prefix.size(), data.end(), hash.begin()); - return CScriptID(hash); - } - } - data.clear(); - auto bech = bech32::Decode(str); - if (bech.second.size() > 0 && bech.first == params.Bech32HRP()) { - // Bech32 decoding - int version = bech.second[0]; // The first 5 bit symbol is the witness version (0-16) - // The rest of the symbols are converted witness program bytes. - if (ConvertBits<5, 8, false>(data, bech.second.begin() + 1, bech.second.end())) { - if (version == 0) { - { - WitnessV0KeyHash keyid; - if (data.size() == keyid.size()) { - std::copy(data.begin(), data.end(), keyid.begin()); - return keyid; - } - } - { - WitnessV0ScriptHash scriptid; - if (data.size() == scriptid.size()) { - std::copy(data.begin(), data.end(), scriptid.begin()); - return scriptid; - } - } - return CNoDestination(); - } - if (version > 16 || data.size() < 2 || data.size() > 40) { - return CNoDestination(); - } - WitnessUnknown unk; - unk.version = version; - std::copy(data.begin(), data.end(), unk.program); - unk.length = data.size(); - return unk; - } - } - return CNoDestination(); -} -} // namespace - -void CBitcoinSecret::SetKey(const CKey& vchSecret) -{ - assert(vchSecret.IsValid()); - SetData(Params().Base58Prefix(CChainParams::SECRET_KEY), vchSecret.begin(), vchSecret.size()); - if (vchSecret.IsCompressed()) - vchData.push_back(1); -} - -CKey CBitcoinSecret::GetKey() -{ - CKey ret; - assert(vchData.size() >= 32); - ret.Set(vchData.begin(), vchData.begin() + 32, vchData.size() > 32 && vchData[32] == 1); - return ret; -} - -bool CBitcoinSecret::IsValid() const -{ - bool fExpectedFormat = vchData.size() == 32 || (vchData.size() == 33 && vchData[32] == 1); - bool fCorrectVersion = vchVersion == Params().Base58Prefix(CChainParams::SECRET_KEY); - return fExpectedFormat && fCorrectVersion; -} - -bool CBitcoinSecret::SetString(const char* pszSecret) -{ - return CBase58Data::SetString(pszSecret) && IsValid(); -} - -bool CBitcoinSecret::SetString(const std::string& strSecret) -{ - return SetString(strSecret.c_str()); -} - -std::string EncodeDestination(const CTxDestination& dest) -{ - return boost::apply_visitor(DestinationEncoder(Params()), dest); -} - -CTxDestination DecodeDestination(const std::string& str) -{ - return DecodeDestination(str, Params()); -} - -bool IsValidDestinationString(const std::string& str, const CChainParams& params) -{ - return IsValidDestination(DecodeDestination(str, params)); -} - -bool IsValidDestinationString(const std::string& str) -{ - return IsValidDestinationString(str, Params()); -} diff --git a/src/base58.h b/src/base58.h index 39eb4eacc2..8f2833bec9 100644 --- a/src/base58.h +++ b/src/base58.h @@ -14,12 +14,6 @@ #ifndef BITCOIN_BASE58_H #define BITCOIN_BASE58_H -#include <chainparams.h> -#include <key.h> -#include <pubkey.h> -#include <script/standard.h> -#include <support/allocators/zeroafterfree.h> - #include <string> #include <vector> @@ -56,95 +50,12 @@ std::string EncodeBase58Check(const std::vector<unsigned char>& vchIn); * Decode a base58-encoded string (psz) that includes a checksum into a byte * vector (vchRet), return true if decoding is successful */ -inline bool DecodeBase58Check(const char* psz, std::vector<unsigned char>& vchRet); +bool DecodeBase58Check(const char* psz, std::vector<unsigned char>& vchRet); /** * Decode a base58-encoded string (str) that includes a checksum into a byte * vector (vchRet), return true if decoding is successful */ -inline bool DecodeBase58Check(const std::string& str, std::vector<unsigned char>& vchRet); - -/** - * Base class for all base58-encoded data - */ -class CBase58Data -{ -protected: - //! the version byte(s) - std::vector<unsigned char> vchVersion; - - //! the actually encoded data - typedef std::vector<unsigned char, zero_after_free_allocator<unsigned char> > vector_uchar; - vector_uchar vchData; - - CBase58Data(); - void SetData(const std::vector<unsigned char> &vchVersionIn, const void* pdata, size_t nSize); - void SetData(const std::vector<unsigned char> &vchVersionIn, const unsigned char *pbegin, const unsigned char *pend); - -public: - bool SetString(const char* psz, unsigned int nVersionBytes = 1); - bool SetString(const std::string& str); - std::string ToString() const; - int CompareTo(const CBase58Data& b58) const; - - bool operator==(const CBase58Data& b58) const { return CompareTo(b58) == 0; } - bool operator<=(const CBase58Data& b58) const { return CompareTo(b58) <= 0; } - bool operator>=(const CBase58Data& b58) const { return CompareTo(b58) >= 0; } - bool operator< (const CBase58Data& b58) const { return CompareTo(b58) < 0; } - bool operator> (const CBase58Data& b58) const { return CompareTo(b58) > 0; } -}; - -/** - * A base58-encoded secret key - */ -class CBitcoinSecret : public CBase58Data -{ -public: - void SetKey(const CKey& vchSecret); - CKey GetKey(); - bool IsValid() const; - bool SetString(const char* pszSecret); - bool SetString(const std::string& strSecret); - - CBitcoinSecret(const CKey& vchSecret) { SetKey(vchSecret); } - CBitcoinSecret() {} -}; - -template<typename K, int Size, CChainParams::Base58Type Type> class CBitcoinExtKeyBase : public CBase58Data -{ -public: - void SetKey(const K &key) { - unsigned char vch[Size]; - key.Encode(vch); - SetData(Params().Base58Prefix(Type), vch, vch+Size); - } - - K GetKey() { - K ret; - if (vchData.size() == Size) { - // If base58 encoded data does not hold an ext key, return a !IsValid() key - ret.Decode(vchData.data()); - } - return ret; - } - - CBitcoinExtKeyBase(const K &key) { - SetKey(key); - } - - CBitcoinExtKeyBase(const std::string& strBase58c) { - SetString(strBase58c.c_str(), Params().Base58Prefix(Type).size()); - } - - CBitcoinExtKeyBase() {} -}; - -typedef CBitcoinExtKeyBase<CExtKey, BIP32_EXTKEY_SIZE, CChainParams::EXT_SECRET_KEY> CBitcoinExtKey; -typedef CBitcoinExtKeyBase<CExtPubKey, BIP32_EXTKEY_SIZE, CChainParams::EXT_PUBLIC_KEY> CBitcoinExtPubKey; - -std::string EncodeDestination(const CTxDestination& dest); -CTxDestination DecodeDestination(const std::string& str); -bool IsValidDestinationString(const std::string& str); -bool IsValidDestinationString(const std::string& str, const CChainParams& params); +bool DecodeBase58Check(const std::string& str, std::vector<unsigned char>& vchRet); #endif // BITCOIN_BASE58_H diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index a9f7264f68..fcd836fb45 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -6,11 +6,11 @@ #include <config/bitcoin-config.h> #endif -#include <base58.h> #include <clientversion.h> #include <coins.h> #include <consensus/consensus.h> #include <core_io.h> +#include <key_io.h> #include <keystore.h> #include <policy/policy.h> #include <policy/rbf.h> @@ -563,12 +563,10 @@ static void MutateTxSign(CMutableTransaction& tx, const std::string& flagStr) for (unsigned int kidx = 0; kidx < keysObj.size(); kidx++) { if (!keysObj[kidx].isStr()) throw std::runtime_error("privatekey not a std::string"); - CBitcoinSecret vchSecret; - bool fGood = vchSecret.SetString(keysObj[kidx].getValStr()); - if (!fGood) + CKey key = DecodeSecret(keysObj[kidx].getValStr()); + if (!key.IsValid()) { throw std::runtime_error("privatekey not valid"); - - CKey key = vchSecret.GetKey(); + } tempKeystore.AddKey(key); } diff --git a/src/core_write.cpp b/src/core_write.cpp index ab6918e41d..91742b7d1b 100644 --- a/src/core_write.cpp +++ b/src/core_write.cpp @@ -4,9 +4,9 @@ #include <core_io.h> -#include <base58.h> #include <consensus/consensus.h> #include <consensus/validation.h> +#include <key_io.h> #include <script/script.h> #include <script/standard.h> #include <serialize.h> diff --git a/src/httprpc.cpp b/src/httprpc.cpp index 5e9e419744..82ae733006 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -4,9 +4,9 @@ #include <httprpc.h> -#include <base58.h> #include <chainparams.h> #include <httpserver.h> +#include <key_io.h> #include <rpc/protocol.h> #include <rpc/server.h> #include <random.h> diff --git a/src/key_io.cpp b/src/key_io.cpp new file mode 100644 index 0000000000..20df945d8d --- /dev/null +++ b/src/key_io.cpp @@ -0,0 +1,223 @@ +// Copyright (c) 2014-2016 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <key_io.h> + +#include <base58.h> +#include <bech32.h> +#include <script/script.h> +#include <utilstrencodings.h> + +#include <boost/variant/apply_visitor.hpp> +#include <boost/variant/static_visitor.hpp> + +#include <assert.h> +#include <string.h> +#include <algorithm> + +namespace +{ +class DestinationEncoder : public boost::static_visitor<std::string> +{ +private: + const CChainParams& m_params; + +public: + DestinationEncoder(const CChainParams& params) : m_params(params) {} + + std::string operator()(const CKeyID& id) const + { + std::vector<unsigned char> data = m_params.Base58Prefix(CChainParams::PUBKEY_ADDRESS); + data.insert(data.end(), id.begin(), id.end()); + return EncodeBase58Check(data); + } + + std::string operator()(const CScriptID& id) const + { + std::vector<unsigned char> data = m_params.Base58Prefix(CChainParams::SCRIPT_ADDRESS); + data.insert(data.end(), id.begin(), id.end()); + return EncodeBase58Check(data); + } + + std::string operator()(const WitnessV0KeyHash& id) const + { + std::vector<unsigned char> data = {0}; + ConvertBits<8, 5, true>(data, id.begin(), id.end()); + return bech32::Encode(m_params.Bech32HRP(), data); + } + + std::string operator()(const WitnessV0ScriptHash& id) const + { + std::vector<unsigned char> data = {0}; + ConvertBits<8, 5, true>(data, id.begin(), id.end()); + return bech32::Encode(m_params.Bech32HRP(), data); + } + + std::string operator()(const WitnessUnknown& id) const + { + if (id.version < 1 || id.version > 16 || id.length < 2 || id.length > 40) { + return {}; + } + std::vector<unsigned char> data = {(unsigned char)id.version}; + ConvertBits<8, 5, true>(data, id.program, id.program + id.length); + return bech32::Encode(m_params.Bech32HRP(), data); + } + + std::string operator()(const CNoDestination& no) const { return {}; } +}; + +CTxDestination DecodeDestination(const std::string& str, const CChainParams& params) +{ + std::vector<unsigned char> data; + uint160 hash; + if (DecodeBase58Check(str, data)) { + // base58-encoded Bitcoin addresses. + // Public-key-hash-addresses have version 0 (or 111 testnet). + // The data vector contains RIPEMD160(SHA256(pubkey)), where pubkey is the serialized public key. + const std::vector<unsigned char>& pubkey_prefix = params.Base58Prefix(CChainParams::PUBKEY_ADDRESS); + if (data.size() == hash.size() + pubkey_prefix.size() && std::equal(pubkey_prefix.begin(), pubkey_prefix.end(), data.begin())) { + std::copy(data.begin() + pubkey_prefix.size(), data.end(), hash.begin()); + return CKeyID(hash); + } + // Script-hash-addresses have version 5 (or 196 testnet). + // The data vector contains RIPEMD160(SHA256(cscript)), where cscript is the serialized redemption script. + const std::vector<unsigned char>& script_prefix = params.Base58Prefix(CChainParams::SCRIPT_ADDRESS); + if (data.size() == hash.size() + script_prefix.size() && std::equal(script_prefix.begin(), script_prefix.end(), data.begin())) { + std::copy(data.begin() + script_prefix.size(), data.end(), hash.begin()); + return CScriptID(hash); + } + } + data.clear(); + auto bech = bech32::Decode(str); + if (bech.second.size() > 0 && bech.first == params.Bech32HRP()) { + // Bech32 decoding + int version = bech.second[0]; // The first 5 bit symbol is the witness version (0-16) + // The rest of the symbols are converted witness program bytes. + if (ConvertBits<5, 8, false>(data, bech.second.begin() + 1, bech.second.end())) { + if (version == 0) { + { + WitnessV0KeyHash keyid; + if (data.size() == keyid.size()) { + std::copy(data.begin(), data.end(), keyid.begin()); + return keyid; + } + } + { + WitnessV0ScriptHash scriptid; + if (data.size() == scriptid.size()) { + std::copy(data.begin(), data.end(), scriptid.begin()); + return scriptid; + } + } + return CNoDestination(); + } + if (version > 16 || data.size() < 2 || data.size() > 40) { + return CNoDestination(); + } + WitnessUnknown unk; + unk.version = version; + std::copy(data.begin(), data.end(), unk.program); + unk.length = data.size(); + return unk; + } + } + return CNoDestination(); +} +} // namespace + +CKey DecodeSecret(const std::string& str) +{ + CKey key; + std::vector<unsigned char> data; + if (DecodeBase58Check(str, data)) { + const std::vector<unsigned char>& privkey_prefix = Params().Base58Prefix(CChainParams::SECRET_KEY); + if ((data.size() == 32 + privkey_prefix.size() || (data.size() == 33 + privkey_prefix.size() && data.back() == 1)) && + std::equal(privkey_prefix.begin(), privkey_prefix.end(), data.begin())) { + bool compressed = data.size() == 33 + privkey_prefix.size(); + key.Set(data.begin() + privkey_prefix.size(), data.begin() + privkey_prefix.size() + 32, compressed); + } + } + memory_cleanse(data.data(), data.size()); + return key; +} + +std::string EncodeSecret(const CKey& key) +{ + assert(key.IsValid()); + std::vector<unsigned char> data = Params().Base58Prefix(CChainParams::SECRET_KEY); + data.insert(data.end(), key.begin(), key.end()); + if (key.IsCompressed()) { + data.push_back(1); + } + std::string ret = EncodeBase58Check(data); + memory_cleanse(data.data(), data.size()); + return ret; +} + +CExtPubKey DecodeExtPubKey(const std::string& str) +{ + CExtPubKey key; + std::vector<unsigned char> data; + if (DecodeBase58Check(str, data)) { + const std::vector<unsigned char>& prefix = Params().Base58Prefix(CChainParams::EXT_PUBLIC_KEY); + if (data.size() == BIP32_EXTKEY_SIZE + prefix.size() && std::equal(prefix.begin(), prefix.end(), data.begin())) { + key.Decode(data.data() + prefix.size()); + } + } + return key; +} + +std::string EncodeExtPubKey(const CExtPubKey& key) +{ + std::vector<unsigned char> data = Params().Base58Prefix(CChainParams::EXT_PUBLIC_KEY); + size_t size = data.size(); + data.resize(size + BIP32_EXTKEY_SIZE); + key.Encode(data.data() + size); + std::string ret = EncodeBase58Check(data); + return ret; +} + +CExtKey DecodeExtKey(const std::string& str) +{ + CExtKey key; + std::vector<unsigned char> data; + if (DecodeBase58Check(str, data)) { + const std::vector<unsigned char>& prefix = Params().Base58Prefix(CChainParams::EXT_SECRET_KEY); + if (data.size() == BIP32_EXTKEY_SIZE + prefix.size() && std::equal(prefix.begin(), prefix.end(), data.begin())) { + key.Decode(data.data() + prefix.size()); + } + } + return key; +} + +std::string EncodeExtKey(const CExtKey& key) +{ + std::vector<unsigned char> data = Params().Base58Prefix(CChainParams::EXT_SECRET_KEY); + size_t size = data.size(); + data.resize(size + BIP32_EXTKEY_SIZE); + key.Encode(data.data() + size); + std::string ret = EncodeBase58Check(data); + memory_cleanse(data.data(), data.size()); + return ret; +} + +std::string EncodeDestination(const CTxDestination& dest) +{ + return boost::apply_visitor(DestinationEncoder(Params()), dest); +} + +CTxDestination DecodeDestination(const std::string& str) +{ + return DecodeDestination(str, Params()); +} + +bool IsValidDestinationString(const std::string& str, const CChainParams& params) +{ + return IsValidDestination(DecodeDestination(str, params)); +} + +bool IsValidDestinationString(const std::string& str) +{ + return IsValidDestinationString(str, Params()); +} diff --git a/src/key_io.h b/src/key_io.h new file mode 100644 index 0000000000..6fc9a8059a --- /dev/null +++ b/src/key_io.h @@ -0,0 +1,29 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-2015 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#ifndef BITCOIN_KEYIO_H +#define BITCOIN_KEYIO_H + +#include <chainparams.h> +#include <key.h> +#include <pubkey.h> +#include <script/standard.h> + +#include <string> + +CKey DecodeSecret(const std::string& str); +std::string EncodeSecret(const CKey& key); + +CExtKey DecodeExtKey(const std::string& str); +std::string EncodeExtKey(const CExtKey& extkey); +CExtPubKey DecodeExtPubKey(const std::string& str); +std::string EncodeExtPubKey(const CExtPubKey& extpubkey); + +std::string EncodeDestination(const CTxDestination& dest); +CTxDestination DecodeDestination(const std::string& str); +bool IsValidDestinationString(const std::string& str); +bool IsValidDestinationString(const std::string& str, const CChainParams& params); + +#endif // BITCOIN_KEYIO_H diff --git a/src/net.cpp b/src/net.cpp index c7c19f83a1..f7e6c300b1 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -1828,11 +1828,18 @@ void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) } } + addrman.ResolveCollisions(); + int64_t nANow = GetAdjustedTime(); int nTries = 0; while (!interruptNet) { - CAddrInfo addr = addrman.Select(fFeeler); + CAddrInfo addr = addrman.SelectTriedCollision(); + + // SelectTriedCollision returns an invalid address if it is empty. + if (!fFeeler || !addr.IsValid()) { + addr = addrman.Select(fFeeler); + } // if we selected an invalid address, restart if (!addr.IsValid() || setConnected.count(addr.GetGroup()) || IsLocal(addr)) diff --git a/src/net_processing.cpp b/src/net_processing.cpp index ddf0dbbbea..482a206c8b 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -401,6 +401,12 @@ void UpdateBlockAvailability(NodeId nodeid, const uint256 &hash) { } } +/** + * When a peer sends us a valid block, instruct it to announce blocks to us + * using CMPCTBLOCK if possible by adding its nodeid to the end of + * lNodesAnnouncingHeaderAndIDs, and keeping that list under a certain size by + * removing the first element if necessary. + */ void MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid, CConnman* connman) { AssertLockHeld(cs_main); CNodeState* nodestate = State(nodeid); @@ -749,7 +755,11 @@ unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans) return nEvicted; } -// Requires cs_main. +/** + * Mark a misbehaving peer to be banned depending upon the value of `-banscore`. + * + * Requires cs_main. + */ void Misbehaving(NodeId pnode, int howmuch, const std::string& message) { if (howmuch == 0) @@ -808,6 +818,10 @@ PeerLogicValidation::PeerLogicValidation(CConnman* connmanIn, CScheduler &schedu scheduler.scheduleEvery(std::bind(&PeerLogicValidation::CheckForStaleTipAndEvictPeers, this, consensusParams), EXTRA_PEER_CHECK_INTERVAL * 1000); } +/** + * Evict orphan txn pool entries (EraseOrphanTx) based on a newly connected + * block. Also save the time of the last tip update. + */ void PeerLogicValidation::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindex, const std::vector<CTransactionRef>& vtxConflicted) { LOCK(g_cs_orphans); @@ -828,7 +842,7 @@ void PeerLogicValidation::BlockConnected(const std::shared_ptr<const CBlock>& pb } } - // Erase orphan transactions include or precluded by this block + // Erase orphan transactions included or precluded by this block if (vOrphanErase.size()) { int nErased = 0; for (uint256 &orphanHash : vOrphanErase) { @@ -847,6 +861,10 @@ static std::shared_ptr<const CBlockHeaderAndShortTxIDs> most_recent_compact_bloc static uint256 most_recent_block_hash; static bool fWitnessesPresentInMostRecentCompactBlock; +/** + * Maintain state about the best-seen block and fast-announce a compact block + * to compatible peers. + */ void PeerLogicValidation::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& pblock) { std::shared_ptr<const CBlockHeaderAndShortTxIDs> pcmpctblock = std::make_shared<const CBlockHeaderAndShortTxIDs> (*pblock, true); const CNetMsgMaker msgMaker(PROTOCOL_VERSION); @@ -888,6 +906,10 @@ void PeerLogicValidation::NewPoWValidBlock(const CBlockIndex *pindex, const std: }); } +/** + * Update our best height and announce any block hashes which weren't previously + * in chainActive to our peers. + */ void PeerLogicValidation::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) { const int nNewHeight = pindexNew->nHeight; connman->SetBestHeight(nNewHeight); @@ -920,6 +942,10 @@ void PeerLogicValidation::UpdatedBlockTip(const CBlockIndex *pindexNew, const CB nTimeBestReceived = GetTime(); } +/** + * Handle invalid block rejection and consequent peer banning, maintain which + * peers announce compact blocks. + */ void PeerLogicValidation::BlockChecked(const CBlock& block, const CValidationState& state) { LOCK(cs_main); diff --git a/src/net_processing.h b/src/net_processing.h index b534ef01c3..ff1ebc59da 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -42,13 +42,26 @@ private: public: explicit PeerLogicValidation(CConnman* connman, CScheduler &scheduler); + /** + * Overridden from CValidationInterface. + */ void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexConnected, const std::vector<CTransactionRef>& vtxConflicted) override; + /** + * Overridden from CValidationInterface. + */ void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) override; + /** + * Overridden from CValidationInterface. + */ void BlockChecked(const CBlock& block, const CValidationState& state) override; + /** + * Overridden from CValidationInterface. + */ void NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr<const CBlock>& pblock) override; - + /** Initialize a peer by adding it to mapNodeState and pushing a message requesting its version */ void InitializeNode(CNode* pnode) override; + /** Handle removal of a peer by updating various state and removing it from mapNodeState */ void FinalizeNode(NodeId nodeid, bool& fUpdateConnectionTime) override; /** Process protocol messages received from a given node */ bool ProcessMessages(CNode* pfrom, std::atomic<bool>& interrupt) override; @@ -61,8 +74,11 @@ public: */ bool SendMessages(CNode* pto, std::atomic<bool>& interrupt) override; + /** Consider evicting an outbound peer based on the amount of time they've been behind our tip */ void ConsiderEviction(CNode *pto, int64_t time_in_seconds); + /** Evict extra outbound peers. If we think our tip may be stale, connect to an extra outbound */ void CheckForStaleTipAndEvictPeers(const Consensus::Params &consensusParams); + /** If we have extra outbound peers, try to disconnect the one with the oldest block announcement */ void EvictExtraOutboundPeers(int64_t time_in_seconds); private: diff --git a/src/qt/addresstablemodel.cpp b/src/qt/addresstablemodel.cpp index ffb5bff4de..4f9a79d654 100644 --- a/src/qt/addresstablemodel.cpp +++ b/src/qt/addresstablemodel.cpp @@ -7,10 +7,9 @@ #include <qt/guiutil.h> #include <qt/walletmodel.h> -#include <base58.h> +#include <key_io.h> #include <wallet/wallet.h> - #include <QFont> #include <QDebug> diff --git a/src/qt/bitcoinaddressvalidator.cpp b/src/qt/bitcoinaddressvalidator.cpp index 395ab447d2..6a76358a78 100644 --- a/src/qt/bitcoinaddressvalidator.cpp +++ b/src/qt/bitcoinaddressvalidator.cpp @@ -4,7 +4,7 @@ #include <qt/bitcoinaddressvalidator.h> -#include <base58.h> +#include <key_io.h> /* Base58 characters are: "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" diff --git a/src/qt/bitcoingui.cpp b/src/qt/bitcoingui.cpp index 4e868b7c17..427eb95a84 100644 --- a/src/qt/bitcoingui.cpp +++ b/src/qt/bitcoingui.cpp @@ -923,6 +923,7 @@ void BitcoinGUI::message(const QString &title, const QString &message, unsigned showNormalIfMinimized(); QMessageBox mBox(static_cast<QMessageBox::Icon>(nMBoxIcon), strTitle, message, buttons, this); + mBox.setTextFormat(Qt::PlainText); int r = mBox.exec(); if (ret != nullptr) *ret = r == QMessageBox::Ok; diff --git a/src/qt/coincontroldialog.cpp b/src/qt/coincontroldialog.cpp index 8d2e5619e0..b83755ab30 100644 --- a/src/qt/coincontroldialog.cpp +++ b/src/qt/coincontroldialog.cpp @@ -15,6 +15,7 @@ #include <wallet/coincontrol.h> #include <init.h> +#include <key_io.h> #include <policy/fees.h> #include <policy/policy.h> #include <validation.h> // For mempool diff --git a/src/qt/guiutil.cpp b/src/qt/guiutil.cpp index a46e0561b9..7c3c68bfef 100644 --- a/src/qt/guiutil.cpp +++ b/src/qt/guiutil.cpp @@ -9,7 +9,10 @@ #include <qt/qvalidatedlineedit.h> #include <qt/walletmodel.h> +#include <base58.h> +#include <chainparams.h> #include <primitives/transaction.h> +#include <key_io.h> #include <init.h> #include <policy/policy.h> #include <protocol.h> diff --git a/src/qt/paymentrequestplus.cpp b/src/qt/paymentrequestplus.cpp index b0ef475b35..357e98a53c 100644 --- a/src/qt/paymentrequestplus.cpp +++ b/src/qt/paymentrequestplus.cpp @@ -9,6 +9,7 @@ #include <qt/paymentrequestplus.h> +#include <script/script.h> #include <util.h> #include <stdexcept> diff --git a/src/qt/paymentrequestplus.h b/src/qt/paymentrequestplus.h index be3923304f..b1b60cf582 100644 --- a/src/qt/paymentrequestplus.h +++ b/src/qt/paymentrequestplus.h @@ -10,7 +10,8 @@ #include <qt/paymentrequest.pb.h> #pragma GCC diagnostic pop -#include <base58.h> +#include <amount.h> +#include <script/script.h> #include <openssl/x509.h> diff --git a/src/qt/paymentserver.cpp b/src/qt/paymentserver.cpp index bc69d4f945..8ad4fa31f1 100644 --- a/src/qt/paymentserver.cpp +++ b/src/qt/paymentserver.cpp @@ -8,9 +8,9 @@ #include <qt/guiutil.h> #include <qt/optionsmodel.h> -#include <base58.h> #include <chainparams.h> #include <policy/policy.h> +#include <key_io.h> #include <ui_interface.h> #include <util.h> #include <wallet/wallet.h> diff --git a/src/qt/sendcoinsdialog.cpp b/src/qt/sendcoinsdialog.cpp index ef36aab1a4..ec7edd48cd 100644 --- a/src/qt/sendcoinsdialog.cpp +++ b/src/qt/sendcoinsdialog.cpp @@ -14,8 +14,8 @@ #include <qt/platformstyle.h> #include <qt/sendcoinsentry.h> -#include <base58.h> #include <chainparams.h> +#include <key_io.h> #include <wallet/coincontrol.h> #include <validation.h> // mempool and minRelayTxFee #include <ui_interface.h> @@ -376,6 +376,12 @@ void SendCoinsDialog::on_sendButton_clicked() void SendCoinsDialog::clear() { + // Clear coin control settings + CoinControlDialog::coinControl()->UnSelectAll(); + ui->checkBoxCoinControlChange->setChecked(false); + ui->lineEditCoinControlChange->clear(); + coinControlUpdateLabels(); + // Remove entries until only one left while(ui->entries->count()) { diff --git a/src/qt/signverifymessagedialog.cpp b/src/qt/signverifymessagedialog.cpp index 364dcd6f45..8dade8df79 100644 --- a/src/qt/signverifymessagedialog.cpp +++ b/src/qt/signverifymessagedialog.cpp @@ -10,8 +10,8 @@ #include <qt/platformstyle.h> #include <qt/walletmodel.h> -#include <base58.h> #include <init.h> +#include <key_io.h> #include <validation.h> // For strMessageMagic #include <wallet/wallet.h> diff --git a/src/qt/test/paymentservertests.cpp b/src/qt/test/paymentservertests.cpp index 6e80625123..29ef4b4c9e 100644 --- a/src/qt/test/paymentservertests.cpp +++ b/src/qt/test/paymentservertests.cpp @@ -8,6 +8,7 @@ #include <qt/test/paymentrequestdata.h> #include <amount.h> +#include <chainparams.h> #include <random.h> #include <script/script.h> #include <script/standard.h> diff --git a/src/qt/test/wallettests.cpp b/src/qt/test/wallettests.cpp index cd49292138..5ba75cc91d 100644 --- a/src/qt/test/wallettests.cpp +++ b/src/qt/test/wallettests.cpp @@ -10,6 +10,7 @@ #include <qt/transactiontablemodel.h> #include <qt/transactionview.h> #include <qt/walletmodel.h> +#include <key_io.h> #include <test/test_bitcoin.h> #include <validation.h> #include <wallet/wallet.h> diff --git a/src/qt/transactiondesc.cpp b/src/qt/transactiondesc.cpp index 6f30e56327..c3f7ea601e 100644 --- a/src/qt/transactiondesc.cpp +++ b/src/qt/transactiondesc.cpp @@ -9,8 +9,8 @@ #include <qt/paymentserver.h> #include <qt/transactionrecord.h> -#include <base58.h> #include <consensus/consensus.h> +#include <key_io.h> #include <validation.h> #include <script/script.h> #include <timedata.h> diff --git a/src/qt/transactionrecord.cpp b/src/qt/transactionrecord.cpp index de3e885e8f..2830149655 100644 --- a/src/qt/transactionrecord.cpp +++ b/src/qt/transactionrecord.cpp @@ -4,8 +4,8 @@ #include <qt/transactionrecord.h> -#include <base58.h> #include <consensus/consensus.h> +#include <key_io.h> #include <validation.h> #include <timedata.h> #include <wallet/wallet.h> diff --git a/src/qt/walletmodel.cpp b/src/qt/walletmodel.cpp index 34954a6bfa..e7d9d276d7 100644 --- a/src/qt/walletmodel.cpp +++ b/src/qt/walletmodel.cpp @@ -14,8 +14,8 @@ #include <qt/sendcoinsdialog.h> #include <qt/transactiontablemodel.h> -#include <base58.h> #include <chain.h> +#include <key_io.h> #include <keystore.h> #include <validation.h> #include <net.h> // for g_connman diff --git a/src/qt/walletmodel.h b/src/qt/walletmodel.h index 9e13de79be..811996b98f 100644 --- a/src/qt/walletmodel.h +++ b/src/qt/walletmodel.h @@ -5,6 +5,11 @@ #ifndef BITCOIN_QT_WALLETMODEL_H #define BITCOIN_QT_WALLETMODEL_H +#include <amount.h> +#include <key.h> +#include <serialize.h> +#include <script/standard.h> + #include <qt/paymentrequestplus.h> #include <qt/walletmodeltransaction.h> diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 8007cebc37..f2a1fd048f 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -372,6 +372,9 @@ std::string EntryDescriptionString() " \"wtxid\" : hash, (string) hash of serialized transaction, including witness data\n" " \"depends\" : [ (array) unconfirmed transactions used as inputs for this transaction\n" " \"transactionid\", (string) parent transaction id\n" + " ... ]\n" + " \"spentby\" : [ (array) unconfirmed transactions spending outputs from this transaction\n" + " \"transactionid\", (string) child transaction id\n" " ... ]\n"; } @@ -406,6 +409,15 @@ void entryToJSON(UniValue &info, const CTxMemPoolEntry &e) } info.pushKV("depends", depends); + + UniValue spent(UniValue::VARR); + const CTxMemPool::txiter &it = mempool.mapTx.find(tx.GetHash()); + const CTxMemPool::setEntries &setChildren = mempool.GetMemPoolChildren(it); + for (const CTxMemPool::txiter &childiter : setChildren) { + spent.push_back(childiter->GetTx().GetHash().ToString()); + } + + info.pushKV("spentby", spent); } UniValue mempoolToJSON(bool fVerbose) diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 3f3bfa0cfd..3073a49d0d 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -3,7 +3,6 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <base58.h> #include <amount.h> #include <chain.h> #include <chainparams.h> @@ -13,6 +12,7 @@ #include <core_io.h> #include <init.h> #include <validation.h> +#include <key_io.h> #include <miner.h> #include <net.h> #include <policy/fees.h> diff --git a/src/rpc/misc.cpp b/src/rpc/misc.cpp index f573c7dbeb..49e865a64a 100644 --- a/src/rpc/misc.cpp +++ b/src/rpc/misc.cpp @@ -3,12 +3,12 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <base58.h> #include <chain.h> #include <clientversion.h> #include <core_io.h> #include <crypto/ripemd160.h> #include <init.h> +#include <key_io.h> #include <validation.h> #include <httpserver.h> #include <net.h> @@ -224,13 +224,10 @@ UniValue signmessagewithprivkey(const JSONRPCRequest& request) std::string strPrivkey = request.params[0].get_str(); std::string strMessage = request.params[1].get_str(); - CBitcoinSecret vchSecret; - bool fGood = vchSecret.SetString(strPrivkey); - if (!fGood) + CKey key = DecodeSecret(strPrivkey); + if (!key.IsValid()) { throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key"); - CKey key = vchSecret.GetKey(); - if (!key.IsValid()) - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range"); + } CHashWriter ss(SER_GETHASH, 0); ss << strMessageMagic; diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 813afde4db..8dcfb48e9a 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -3,7 +3,6 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <base58.h> #include <chain.h> #include <coins.h> #include <consensus/validation.h> @@ -12,6 +11,7 @@ #include <keystore.h> #include <validation.h> #include <validationinterface.h> +#include <key_io.h> #include <merkleblock.h> #include <net.h> #include <policy/policy.h> @@ -896,13 +896,9 @@ UniValue signrawtransactionwithkey(const JSONRPCRequest& request) const UniValue& keys = request.params[1].get_array(); for (unsigned int idx = 0; idx < keys.size(); ++idx) { UniValue k = keys[idx]; - CBitcoinSecret vchSecret; - if (!vchSecret.SetString(k.get_str())) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key"); - } - CKey key = vchSecret.GetKey(); + CKey key = DecodeSecret(k.get_str()); if (!key.IsValid()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key"); } keystore.AddKey(key); } diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index e5b4f6ca77..35401bf876 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -5,9 +5,9 @@ #include <rpc/server.h> -#include <base58.h> #include <fs.h> #include <init.h> +#include <key_io.h> #include <random.h> #include <sync.h> #include <ui_interface.h> diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index cdcb68d15f..593962e710 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -2,7 +2,7 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <base58.h> +#include <key_io.h> #include <keystore.h> #include <pubkey.h> #include <rpc/protocol.h> diff --git a/src/test/addrman_tests.cpp b/src/test/addrman_tests.cpp index b338d6d366..0d8bd90119 100644 --- a/src/test/addrman_tests.cpp +++ b/src/test/addrman_tests.cpp @@ -52,6 +52,17 @@ public: { CAddrMan::Delete(nId); } + + // Simulates connection failure so that we can test eviction of offline nodes + void SimConnFail(CService& addr) + { + int64_t nLastSuccess = 1; + Good_(addr, true, nLastSuccess); // Set last good connection in the deep past. + + bool count_failure = false; + int64_t nLastTry = GetAdjustedTime()-61; + Attempt(addr, count_failure, nLastTry); + } }; static CNetAddr ResolveIP(const char* ip) @@ -226,7 +237,7 @@ BOOST_AUTO_TEST_CASE(addrman_new_collisions) BOOST_CHECK_EQUAL(addrman.size(), 0); for (unsigned int i = 1; i < 18; i++) { - CService addr = ResolveService("250.1.1." + boost::to_string(i)); + CService addr = ResolveService("250.1.1." + std::to_string(i)); addrman.Add(CAddress(addr, NODE_NONE), source); //Test: No collision in new table yet. @@ -252,7 +263,7 @@ BOOST_AUTO_TEST_CASE(addrman_tried_collisions) BOOST_CHECK_EQUAL(addrman.size(), 0); for (unsigned int i = 1; i < 80; i++) { - CService addr = ResolveService("250.1.1." + boost::to_string(i)); + CService addr = ResolveService("250.1.1." + std::to_string(i)); addrman.Add(CAddress(addr, NODE_NONE), source); addrman.Good(CAddress(addr, NODE_NONE)); @@ -385,7 +396,7 @@ BOOST_AUTO_TEST_CASE(addrman_getaddr) for (unsigned int i = 1; i < (8 * 256); i++) { int octet1 = i % 256; int octet2 = i >> 8 % 256; - std::string strAddr = boost::to_string(octet1) + "." + boost::to_string(octet2) + ".1.23"; + std::string strAddr = std::to_string(octet1) + "." + std::to_string(octet2) + ".1.23"; CAddress addr = CAddress(ResolveService(strAddr), NODE_NONE); // Ensure that for all addrs in addrman, isTerrible == false. @@ -436,8 +447,8 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_tried_bucket) std::set<int> buckets; for (int i = 0; i < 255; i++) { CAddrInfo infoi = CAddrInfo( - CAddress(ResolveService("250.1.1." + boost::to_string(i)), NODE_NONE), - ResolveIP("250.1.1." + boost::to_string(i))); + CAddress(ResolveService("250.1.1." + std::to_string(i)), NODE_NONE), + ResolveIP("250.1.1." + std::to_string(i))); int bucket = infoi.GetTriedBucket(nKey1); buckets.insert(bucket); } @@ -448,8 +459,8 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_tried_bucket) buckets.clear(); for (int j = 0; j < 255; j++) { CAddrInfo infoj = CAddrInfo( - CAddress(ResolveService("250." + boost::to_string(j) + ".1.1"), NODE_NONE), - ResolveIP("250." + boost::to_string(j) + ".1.1")); + CAddress(ResolveService("250." + std::to_string(j) + ".1.1"), NODE_NONE), + ResolveIP("250." + std::to_string(j) + ".1.1")); int bucket = infoj.GetTriedBucket(nKey1); buckets.insert(bucket); } @@ -488,8 +499,8 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket) std::set<int> buckets; for (int i = 0; i < 255; i++) { CAddrInfo infoi = CAddrInfo( - CAddress(ResolveService("250.1.1." + boost::to_string(i)), NODE_NONE), - ResolveIP("250.1.1." + boost::to_string(i))); + CAddress(ResolveService("250.1.1." + std::to_string(i)), NODE_NONE), + ResolveIP("250.1.1." + std::to_string(i))); int bucket = infoi.GetNewBucket(nKey1); buckets.insert(bucket); } @@ -501,7 +512,7 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket) for (int j = 0; j < 4 * 255; j++) { CAddrInfo infoj = CAddrInfo(CAddress( ResolveService( - boost::to_string(250 + (j / 255)) + "." + boost::to_string(j % 256) + ".1.1"), NODE_NONE), + std::to_string(250 + (j / 255)) + "." + std::to_string(j % 256) + ".1.1"), NODE_NONE), ResolveIP("251.4.1.1")); int bucket = infoj.GetNewBucket(nKey1); buckets.insert(bucket); @@ -514,7 +525,7 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket) for (int p = 0; p < 255; p++) { CAddrInfo infoj = CAddrInfo( CAddress(ResolveService("250.1.1.1"), NODE_NONE), - ResolveIP("250." + boost::to_string(p) + ".1.1")); + ResolveIP("250." + std::to_string(p) + ".1.1")); int bucket = infoj.GetNewBucket(nKey1); buckets.insert(bucket); } @@ -522,4 +533,158 @@ BOOST_AUTO_TEST_CASE(caddrinfo_get_new_bucket) // than 64 buckets. BOOST_CHECK(buckets.size() > 64); } + + +BOOST_AUTO_TEST_CASE(addrman_selecttriedcollision) +{ + CAddrManTest addrman; + + // Set addrman addr placement to be deterministic. + addrman.MakeDeterministic(); + + BOOST_CHECK(addrman.size() == 0); + + // Empty addrman should return blank addrman info. + BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); + + // Add twenty two addresses. + CNetAddr source = ResolveIP("252.2.2.2"); + for (unsigned int i = 1; i < 23; i++) { + CService addr = ResolveService("250.1.1."+std::to_string(i)); + addrman.Add(CAddress(addr, NODE_NONE), source); + addrman.Good(addr); + + // No collisions yet. + BOOST_CHECK(addrman.size() == i); + BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); + } + + // Ensure Good handles duplicates well. + for (unsigned int i = 1; i < 23; i++) { + CService addr = ResolveService("250.1.1."+std::to_string(i)); + addrman.Good(addr); + + BOOST_CHECK(addrman.size() == 22); + BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); + } + +} + +BOOST_AUTO_TEST_CASE(addrman_noevict) +{ + CAddrManTest addrman; + + // Set addrman addr placement to be deterministic. + addrman.MakeDeterministic(); + + // Add twenty two addresses. + CNetAddr source = ResolveIP("252.2.2.2"); + for (unsigned int i = 1; i < 23; i++) { + CService addr = ResolveService("250.1.1."+std::to_string(i)); + addrman.Add(CAddress(addr, NODE_NONE), source); + addrman.Good(addr); + + // No collision yet. + BOOST_CHECK(addrman.size() == i); + BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); + } + + // Collision between 23 and 19. + CService addr23 = ResolveService("250.1.1.23"); + addrman.Add(CAddress(addr23, NODE_NONE), source); + addrman.Good(addr23); + + BOOST_CHECK(addrman.size() == 23); + BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "250.1.1.19:0"); + + // 23 should be discarded and 19 not evicted. + addrman.ResolveCollisions(); + BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); + + // Lets create two collisions. + for (unsigned int i = 24; i < 33; i++) { + CService addr = ResolveService("250.1.1."+std::to_string(i)); + addrman.Add(CAddress(addr, NODE_NONE), source); + addrman.Good(addr); + + BOOST_CHECK(addrman.size() == i); + BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); + } + + // Cause a collision. + CService addr33 = ResolveService("250.1.1.33"); + addrman.Add(CAddress(addr33, NODE_NONE), source); + addrman.Good(addr33); + BOOST_CHECK(addrman.size() == 33); + + BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "250.1.1.27:0"); + + // Cause a second collision. + addrman.Add(CAddress(addr23, NODE_NONE), source); + addrman.Good(addr23); + BOOST_CHECK(addrman.size() == 33); + + BOOST_CHECK(addrman.SelectTriedCollision().ToString() != "[::]:0"); + addrman.ResolveCollisions(); + BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); +} + +BOOST_AUTO_TEST_CASE(addrman_evictionworks) +{ + CAddrManTest addrman; + + // Set addrman addr placement to be deterministic. + addrman.MakeDeterministic(); + + BOOST_CHECK(addrman.size() == 0); + + // Empty addrman should return blank addrman info. + BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); + + // Add twenty two addresses. + CNetAddr source = ResolveIP("252.2.2.2"); + for (unsigned int i = 1; i < 23; i++) { + CService addr = ResolveService("250.1.1."+std::to_string(i)); + addrman.Add(CAddress(addr, NODE_NONE), source); + addrman.Good(addr); + + // No collision yet. + BOOST_CHECK(addrman.size() == i); + BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); + } + + // Collision between 23 and 19. + CService addr = ResolveService("250.1.1.23"); + addrman.Add(CAddress(addr, NODE_NONE), source); + addrman.Good(addr); + + BOOST_CHECK(addrman.size() == 23); + CAddrInfo info = addrman.SelectTriedCollision(); + BOOST_CHECK(info.ToString() == "250.1.1.19:0"); + + // Ensure test of address fails, so that it is evicted. + addrman.SimConnFail(info); + + // Should swap 23 for 19. + addrman.ResolveCollisions(); + BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); + + // If 23 was swapped for 19, then this should cause no collisions. + addrman.Add(CAddress(addr, NODE_NONE), source); + addrman.Good(addr); + + BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); + + // If we insert 19 is should collide with 23. + CService addr19 = ResolveService("250.1.1.19"); + addrman.Add(CAddress(addr19, NODE_NONE), source); + addrman.Good(addr19); + + BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "250.1.1.23:0"); + + addrman.ResolveCollisions(); + BOOST_CHECK(addrman.SelectTriedCollision().ToString() == "[::]:0"); +} + + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/base58_tests.cpp b/src/test/base58_tests.cpp index a2d4f82695..f90d4f90cb 100644 --- a/src/test/base58_tests.cpp +++ b/src/test/base58_tests.cpp @@ -2,17 +2,10 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <base58.h> - #include <test/data/base58_encode_decode.json.h> -#include <test/data/base58_keys_invalid.json.h> -#include <test/data/base58_keys_valid.json.h> -#include <key.h> -#include <script/script.h> +#include <base58.h> #include <test/test_bitcoin.h> -#include <uint256.h> -#include <util.h> #include <utilstrencodings.h> #include <univalue.h> @@ -73,135 +66,4 @@ BOOST_AUTO_TEST_CASE(base58_DecodeBase58) BOOST_CHECK_EQUAL_COLLECTIONS(result.begin(), result.end(), expected.begin(), expected.end()); } -// Goal: check that parsed keys match test payload -BOOST_AUTO_TEST_CASE(base58_keys_valid_parse) -{ - UniValue tests = read_json(std::string(json_tests::base58_keys_valid, json_tests::base58_keys_valid + sizeof(json_tests::base58_keys_valid))); - CBitcoinSecret secret; - CTxDestination destination; - SelectParams(CBaseChainParams::MAIN); - - for (unsigned int idx = 0; idx < tests.size(); idx++) { - UniValue test = tests[idx]; - std::string strTest = test.write(); - if (test.size() < 3) { // Allow for extra stuff (useful for comments) - BOOST_ERROR("Bad test: " << strTest); - continue; - } - std::string exp_base58string = test[0].get_str(); - std::vector<unsigned char> exp_payload = ParseHex(test[1].get_str()); - const UniValue &metadata = test[2].get_obj(); - bool isPrivkey = find_value(metadata, "isPrivkey").get_bool(); - SelectParams(find_value(metadata, "chain").get_str()); - bool try_case_flip = find_value(metadata, "tryCaseFlip").isNull() ? false : find_value(metadata, "tryCaseFlip").get_bool(); - if (isPrivkey) { - bool isCompressed = find_value(metadata, "isCompressed").get_bool(); - // Must be valid private key - BOOST_CHECK_MESSAGE(secret.SetString(exp_base58string), "!SetString:"+ strTest); - BOOST_CHECK_MESSAGE(secret.IsValid(), "!IsValid:" + strTest); - CKey privkey = secret.GetKey(); - BOOST_CHECK_MESSAGE(privkey.IsCompressed() == isCompressed, "compressed mismatch:" + strTest); - BOOST_CHECK_MESSAGE(privkey.size() == exp_payload.size() && std::equal(privkey.begin(), privkey.end(), exp_payload.begin()), "key mismatch:" + strTest); - - // Private key must be invalid public key - destination = DecodeDestination(exp_base58string); - BOOST_CHECK_MESSAGE(!IsValidDestination(destination), "IsValid privkey as pubkey:" + strTest); - } else { - // Must be valid public key - destination = DecodeDestination(exp_base58string); - CScript script = GetScriptForDestination(destination); - BOOST_CHECK_MESSAGE(IsValidDestination(destination), "!IsValid:" + strTest); - BOOST_CHECK_EQUAL(HexStr(script), HexStr(exp_payload)); - - // Try flipped case version - for (char& c : exp_base58string) { - if (c >= 'a' && c <= 'z') { - c = (c - 'a') + 'A'; - } else if (c >= 'A' && c <= 'Z') { - c = (c - 'A') + 'a'; - } - } - destination = DecodeDestination(exp_base58string); - BOOST_CHECK_MESSAGE(IsValidDestination(destination) == try_case_flip, "!IsValid case flipped:" + strTest); - if (IsValidDestination(destination)) { - script = GetScriptForDestination(destination); - BOOST_CHECK_EQUAL(HexStr(script), HexStr(exp_payload)); - } - - // Public key must be invalid private key - secret.SetString(exp_base58string); - BOOST_CHECK_MESSAGE(!secret.IsValid(), "IsValid pubkey as privkey:" + strTest); - } - } -} - -// Goal: check that generated keys match test vectors -BOOST_AUTO_TEST_CASE(base58_keys_valid_gen) -{ - UniValue tests = read_json(std::string(json_tests::base58_keys_valid, json_tests::base58_keys_valid + sizeof(json_tests::base58_keys_valid))); - - for (unsigned int idx = 0; idx < tests.size(); idx++) { - UniValue test = tests[idx]; - std::string strTest = test.write(); - if (test.size() < 3) // Allow for extra stuff (useful for comments) - { - BOOST_ERROR("Bad test: " << strTest); - continue; - } - std::string exp_base58string = test[0].get_str(); - std::vector<unsigned char> exp_payload = ParseHex(test[1].get_str()); - const UniValue &metadata = test[2].get_obj(); - bool isPrivkey = find_value(metadata, "isPrivkey").get_bool(); - SelectParams(find_value(metadata, "chain").get_str()); - if (isPrivkey) { - bool isCompressed = find_value(metadata, "isCompressed").get_bool(); - CKey key; - key.Set(exp_payload.begin(), exp_payload.end(), isCompressed); - assert(key.IsValid()); - CBitcoinSecret secret; - secret.SetKey(key); - BOOST_CHECK_MESSAGE(secret.ToString() == exp_base58string, "result mismatch: " + strTest); - } else { - CTxDestination dest; - CScript exp_script(exp_payload.begin(), exp_payload.end()); - ExtractDestination(exp_script, dest); - std::string address = EncodeDestination(dest); - - BOOST_CHECK_EQUAL(address, exp_base58string); - } - } - - SelectParams(CBaseChainParams::MAIN); -} - - -// Goal: check that base58 parsing code is robust against a variety of corrupted data -BOOST_AUTO_TEST_CASE(base58_keys_invalid) -{ - UniValue tests = read_json(std::string(json_tests::base58_keys_invalid, json_tests::base58_keys_invalid + sizeof(json_tests::base58_keys_invalid))); // Negative testcases - CBitcoinSecret secret; - CTxDestination destination; - - for (unsigned int idx = 0; idx < tests.size(); idx++) { - UniValue test = tests[idx]; - std::string strTest = test.write(); - if (test.size() < 1) // Allow for extra stuff (useful for comments) - { - BOOST_ERROR("Bad test: " << strTest); - continue; - } - std::string exp_base58string = test[0].get_str(); - - // must be invalid as public and as private key - for (auto chain : { CBaseChainParams::MAIN, CBaseChainParams::TESTNET, CBaseChainParams::REGTEST }) { - SelectParams(chain); - destination = DecodeDestination(exp_base58string); - BOOST_CHECK_MESSAGE(!IsValidDestination(destination), "IsValid pubkey in mainnet:" + strTest); - secret.SetString(exp_base58string); - BOOST_CHECK_MESSAGE(!secret.IsValid(), "IsValid privkey in mainnet:" + strTest); - } - } -} - - BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/bip32_tests.cpp b/src/test/bip32_tests.cpp index 438ddc177d..3c9ff1877d 100644 --- a/src/test/bip32_tests.cpp +++ b/src/test/bip32_tests.cpp @@ -4,8 +4,8 @@ #include <boost/test/unit_test.hpp> -#include <base58.h> #include <key.h> +#include <key_io.h> #include <uint256.h> #include <util.h> #include <utilstrencodings.h> @@ -99,20 +99,12 @@ void RunTest(const TestVector &test) { pubkey.Encode(data); // Test private key - CBitcoinExtKey b58key; b58key.SetKey(key); - BOOST_CHECK(b58key.ToString() == derive.prv); - - CBitcoinExtKey b58keyDecodeCheck(derive.prv); - CExtKey checkKey = b58keyDecodeCheck.GetKey(); - assert(checkKey == key); //ensure a base58 decoded key also matches + BOOST_CHECK(EncodeExtKey(key) == derive.prv); + BOOST_CHECK(DecodeExtKey(derive.prv) == key); //ensure a base58 decoded key also matches // Test public key - CBitcoinExtPubKey b58pubkey; b58pubkey.SetKey(pubkey); - BOOST_CHECK(b58pubkey.ToString() == derive.pub); - - CBitcoinExtPubKey b58PubkeyDecodeCheck(derive.pub); - CExtPubKey checkPubKey = b58PubkeyDecodeCheck.GetKey(); - assert(checkPubKey == pubkey); //ensure a base58 decoded pubkey also matches + BOOST_CHECK(EncodeExtPubKey(pubkey) == derive.pub); + BOOST_CHECK(DecodeExtPubKey(derive.pub) == pubkey); //ensure a base58 decoded pubkey also matches // Derive new keys CExtKey keyNew; diff --git a/src/test/bloom_tests.cpp b/src/test/bloom_tests.cpp index af5533b109..73c8eb5168 100644 --- a/src/test/bloom_tests.cpp +++ b/src/test/bloom_tests.cpp @@ -4,9 +4,9 @@ #include <bloom.h> -#include <base58.h> #include <clientversion.h> #include <key.h> +#include <key_io.h> #include <merkleblock.h> #include <primitives/block.h> #include <random.h> @@ -85,10 +85,7 @@ BOOST_AUTO_TEST_CASE(bloom_create_insert_serialize_with_tweak) BOOST_AUTO_TEST_CASE(bloom_create_insert_key) { std::string strSecret = std::string("5Kg1gnAjaLfKiwhhPpGS3QfRg2m6awQvaj98JCZBZQ5SuS2F15C"); - CBitcoinSecret vchSecret; - BOOST_CHECK(vchSecret.SetString(strSecret)); - - CKey key = vchSecret.GetKey(); + CKey key = DecodeSecret(strSecret); CPubKey pubkey = key.GetPubKey(); std::vector<unsigned char> vchPubKey(pubkey.begin(), pubkey.end()); diff --git a/src/test/data/base58_keys_invalid.json b/src/test/data/key_io_invalid.json index 2056c7491c..2056c7491c 100644 --- a/src/test/data/base58_keys_invalid.json +++ b/src/test/data/key_io_invalid.json diff --git a/src/test/data/base58_keys_valid.json b/src/test/data/key_io_valid.json index 8418a6002d..8418a6002d 100644 --- a/src/test/data/base58_keys_valid.json +++ b/src/test/data/key_io_valid.json diff --git a/src/test/data/tx_valid.json b/src/test/data/tx_valid.json index 7e39ec7599..0bcecc58fe 100644 --- a/src/test/data/tx_valid.json +++ b/src/test/data/tx_valid.json @@ -516,5 +516,9 @@ [[["9628667ad48219a169b41b020800162287d2c0f713c04157e95c484a8dcb7592", 7500, "0x00 0x20 0x9b66c15b4e0b4eb49fa877982cafded24859fe5b0e2dbfbe4f0df1de7743fd52", 200000]], "010000000001019275cb8d4a485ce95741c013f7c0d28722160008021bb469a11982d47a6628964c1d000000ffffffff0101000000000000000007004830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c0395960101022102966f109c54e85d3aee8321301136cedeb9fc710fdef58a9de8a73942f8e567c021034ffc99dd9a79dd3cb31e2ab3e0b09e0e67db41ac068c625cd1f491576016c84e9552af4830450220487fb382c4974de3f7d834c1b617fe15860828c7f96454490edd6d891556dcc9022100baf95feb48f845d5bfc9882eb6aeefa1bc3790e39f59eaa46ff7f15ae626c53e0148304502205286f726690b2e9b0207f0345711e63fa7012045b9eb0f19c2458ce1db90cf43022100e89f17f86abc5b149eba4115d4f128bcf45d77fb3ecdd34f594091340c039596017500000000", "P2SH,WITNESS"], +["Test long outputs, which are streamed using length-prefixed bitcoin strings. This might be surprising."], +[[["1111111111111111111111111111111111111111111111111111111111111111", 0, "0x00 0x14 0x751e76e8199196d454941c45d1b3a323f1433bd6", 5000000]], +"0100000000010111111111111111111111111111111111111111111111111111111111111111110000000000ffffffff0130244c0000000000fd02014cdc1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111175210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798ac02483045022100c1a4a6581996a7fdfea77d58d537955a5655c1d619b6f3ab6874f28bb2e19708022056402db6fede03caae045a3be616a1a2d0919a475ed4be828dc9ff21f24063aa01210279be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f8179800000000", "P2SH,WITNESS"], + ["Make diffs cleaner by leaving a comment here without comma at the end"] ] diff --git a/src/test/key_io_tests.cpp b/src/test/key_io_tests.cpp new file mode 100644 index 0000000000..1ac1e0015b --- /dev/null +++ b/src/test/key_io_tests.cpp @@ -0,0 +1,149 @@ +// Copyright (c) 2011-2016 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include <test/data/key_io_invalid.json.h> +#include <test/data/key_io_valid.json.h> + +#include <key.h> +#include <key_io.h> +#include <script/script.h> +#include <utilstrencodings.h> +#include <test/test_bitcoin.h> + +#include <boost/test/unit_test.hpp> + +#include <univalue.h> + +extern UniValue read_json(const std::string& jsondata); + +BOOST_FIXTURE_TEST_SUITE(key_io_tests, BasicTestingSetup) + +// Goal: check that parsed keys match test payload +BOOST_AUTO_TEST_CASE(key_io_valid_parse) +{ + UniValue tests = read_json(std::string(json_tests::key_io_valid, json_tests::key_io_valid + sizeof(json_tests::key_io_valid))); + CKey privkey; + CTxDestination destination; + SelectParams(CBaseChainParams::MAIN); + + for (unsigned int idx = 0; idx < tests.size(); idx++) { + UniValue test = tests[idx]; + std::string strTest = test.write(); + if (test.size() < 3) { // Allow for extra stuff (useful for comments) + BOOST_ERROR("Bad test: " << strTest); + continue; + } + std::string exp_base58string = test[0].get_str(); + std::vector<unsigned char> exp_payload = ParseHex(test[1].get_str()); + const UniValue &metadata = test[2].get_obj(); + bool isPrivkey = find_value(metadata, "isPrivkey").get_bool(); + SelectParams(find_value(metadata, "chain").get_str()); + bool try_case_flip = find_value(metadata, "tryCaseFlip").isNull() ? false : find_value(metadata, "tryCaseFlip").get_bool(); + if (isPrivkey) { + bool isCompressed = find_value(metadata, "isCompressed").get_bool(); + // Must be valid private key + privkey = DecodeSecret(exp_base58string); + BOOST_CHECK_MESSAGE(privkey.IsValid(), "!IsValid:" + strTest); + BOOST_CHECK_MESSAGE(privkey.IsCompressed() == isCompressed, "compressed mismatch:" + strTest); + BOOST_CHECK_MESSAGE(privkey.size() == exp_payload.size() && std::equal(privkey.begin(), privkey.end(), exp_payload.begin()), "key mismatch:" + strTest); + + // Private key must be invalid public key + destination = DecodeDestination(exp_base58string); + BOOST_CHECK_MESSAGE(!IsValidDestination(destination), "IsValid privkey as pubkey:" + strTest); + } else { + // Must be valid public key + destination = DecodeDestination(exp_base58string); + CScript script = GetScriptForDestination(destination); + BOOST_CHECK_MESSAGE(IsValidDestination(destination), "!IsValid:" + strTest); + BOOST_CHECK_EQUAL(HexStr(script), HexStr(exp_payload)); + + // Try flipped case version + for (char& c : exp_base58string) { + if (c >= 'a' && c <= 'z') { + c = (c - 'a') + 'A'; + } else if (c >= 'A' && c <= 'Z') { + c = (c - 'A') + 'a'; + } + } + destination = DecodeDestination(exp_base58string); + BOOST_CHECK_MESSAGE(IsValidDestination(destination) == try_case_flip, "!IsValid case flipped:" + strTest); + if (IsValidDestination(destination)) { + script = GetScriptForDestination(destination); + BOOST_CHECK_EQUAL(HexStr(script), HexStr(exp_payload)); + } + + // Public key must be invalid private key + privkey = DecodeSecret(exp_base58string); + BOOST_CHECK_MESSAGE(!privkey.IsValid(), "IsValid pubkey as privkey:" + strTest); + } + } +} + +// Goal: check that generated keys match test vectors +BOOST_AUTO_TEST_CASE(key_io_valid_gen) +{ + UniValue tests = read_json(std::string(json_tests::key_io_valid, json_tests::key_io_valid + sizeof(json_tests::key_io_valid))); + + for (unsigned int idx = 0; idx < tests.size(); idx++) { + UniValue test = tests[idx]; + std::string strTest = test.write(); + if (test.size() < 3) // Allow for extra stuff (useful for comments) + { + BOOST_ERROR("Bad test: " << strTest); + continue; + } + std::string exp_base58string = test[0].get_str(); + std::vector<unsigned char> exp_payload = ParseHex(test[1].get_str()); + const UniValue &metadata = test[2].get_obj(); + bool isPrivkey = find_value(metadata, "isPrivkey").get_bool(); + SelectParams(find_value(metadata, "chain").get_str()); + if (isPrivkey) { + bool isCompressed = find_value(metadata, "isCompressed").get_bool(); + CKey key; + key.Set(exp_payload.begin(), exp_payload.end(), isCompressed); + assert(key.IsValid()); + BOOST_CHECK_MESSAGE(EncodeSecret(key) == exp_base58string, "result mismatch: " + strTest); + } else { + CTxDestination dest; + CScript exp_script(exp_payload.begin(), exp_payload.end()); + ExtractDestination(exp_script, dest); + std::string address = EncodeDestination(dest); + + BOOST_CHECK_EQUAL(address, exp_base58string); + } + } + + SelectParams(CBaseChainParams::MAIN); +} + + +// Goal: check that base58 parsing code is robust against a variety of corrupted data +BOOST_AUTO_TEST_CASE(key_io_invalid) +{ + UniValue tests = read_json(std::string(json_tests::key_io_invalid, json_tests::key_io_invalid + sizeof(json_tests::key_io_invalid))); // Negative testcases + CKey privkey; + CTxDestination destination; + + for (unsigned int idx = 0; idx < tests.size(); idx++) { + UniValue test = tests[idx]; + std::string strTest = test.write(); + if (test.size() < 1) // Allow for extra stuff (useful for comments) + { + BOOST_ERROR("Bad test: " << strTest); + continue; + } + std::string exp_base58string = test[0].get_str(); + + // must be invalid as public and as private key + for (auto chain : { CBaseChainParams::MAIN, CBaseChainParams::TESTNET, CBaseChainParams::REGTEST }) { + SelectParams(chain); + destination = DecodeDestination(exp_base58string); + BOOST_CHECK_MESSAGE(!IsValidDestination(destination), "IsValid pubkey in mainnet:" + strTest); + privkey = DecodeSecret(exp_base58string); + BOOST_CHECK_MESSAGE(!privkey.IsValid(), "IsValid privkey in mainnet:" + strTest); + } + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/key_tests.cpp b/src/test/key_tests.cpp index 55ee1ecf6b..64c57f0705 100644 --- a/src/test/key_tests.cpp +++ b/src/test/key_tests.cpp @@ -4,7 +4,7 @@ #include <key.h> -#include <base58.h> +#include <key_io.h> #include <script/script.h> #include <uint256.h> #include <util.h> @@ -32,21 +32,16 @@ BOOST_FIXTURE_TEST_SUITE(key_tests, BasicTestingSetup) BOOST_AUTO_TEST_CASE(key_test1) { - CBitcoinSecret bsecret1, bsecret2, bsecret1C, bsecret2C, baddress1; - BOOST_CHECK( bsecret1.SetString (strSecret1)); - BOOST_CHECK( bsecret2.SetString (strSecret2)); - BOOST_CHECK( bsecret1C.SetString(strSecret1C)); - BOOST_CHECK( bsecret2C.SetString(strSecret2C)); - BOOST_CHECK(!baddress1.SetString(strAddressBad)); - - CKey key1 = bsecret1.GetKey(); - BOOST_CHECK(key1.IsCompressed() == false); - CKey key2 = bsecret2.GetKey(); - BOOST_CHECK(key2.IsCompressed() == false); - CKey key1C = bsecret1C.GetKey(); - BOOST_CHECK(key1C.IsCompressed() == true); - CKey key2C = bsecret2C.GetKey(); - BOOST_CHECK(key2C.IsCompressed() == true); + CKey key1 = DecodeSecret(strSecret1); + BOOST_CHECK(key1.IsValid() && !key1.IsCompressed()); + CKey key2 = DecodeSecret(strSecret2); + BOOST_CHECK(key2.IsValid() && !key2.IsCompressed()); + CKey key1C = DecodeSecret(strSecret1C); + BOOST_CHECK(key1C.IsValid() && key1C.IsCompressed()); + CKey key2C = DecodeSecret(strSecret2C); + BOOST_CHECK(key2C.IsValid() && key2C.IsCompressed()); + CKey bad_key = DecodeSecret(strAddressBad); + BOOST_CHECK(!bad_key.IsValid()); CPubKey pubkey1 = key1. GetPubKey(); CPubKey pubkey2 = key2. GetPubKey(); diff --git a/src/test/rpc_tests.cpp b/src/test/rpc_tests.cpp index 108c1a063e..892e4f2dac 100644 --- a/src/test/rpc_tests.cpp +++ b/src/test/rpc_tests.cpp @@ -5,8 +5,8 @@ #include <rpc/server.h> #include <rpc/client.h> -#include <base58.h> #include <core_io.h> +#include <key_io.h> #include <netbase.h> #include <test/test_bitcoin.h> diff --git a/src/validation.cpp b/src/validation.cpp index a77362f5d6..51e40c17b5 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -1856,12 +1856,65 @@ bool CChainState::ConnectBlock(const CBlock& block, CValidationState& state, CBl // before the first had been spent. Since those coinbases are sufficiently buried its no longer possible to create further // duplicate transactions descending from the known pairs either. // If we're on the known chain at height greater than where BIP34 activated, we can save the db accesses needed for the BIP30 check. + + // BIP34 requires that a block at height X (block X) has its coinbase + // scriptSig start with a CScriptNum of X (indicated height X). The above + // logic of no longer requiring BIP30 once BIP34 activates is flawed in the + // case that there is a block X before the BIP34 height of 227,931 which has + // an indicated height Y where Y is greater than X. The coinbase for block + // X would also be a valid coinbase for block Y, which could be a BIP30 + // violation. An exhaustive search of all mainnet coinbases before the + // BIP34 height which have an indicated height greater than the block height + // reveals many occurrences. The 3 lowest indicated heights found are + // 209,921, 490,897, and 1,983,702 and thus coinbases for blocks at these 3 + // heights would be the first opportunity for BIP30 to be violated. + + // The search reveals a great many blocks which have an indicated height + // greater than 1,983,702, so we simply remove the optimization to skip + // BIP30 checking for blocks at height 1,983,702 or higher. Before we reach + // that block in another 25 years or so, we should take advantage of a + // future consensus change to do a new and improved version of BIP34 that + // will actually prevent ever creating any duplicate coinbases in the + // future. + static constexpr int BIP34_IMPLIES_BIP30_LIMIT = 1983702; + + // There is no potential to create a duplicate coinbase at block 209,921 + // because this is still before the BIP34 height and so explicit BIP30 + // checking is still active. + + // The final case is block 176,684 which has an indicated height of + // 490,897. Unfortunately, this issue was not discovered until about 2 weeks + // before block 490,897 so there was not much opportunity to address this + // case other than to carefully analyze it and determine it would not be a + // problem. Block 490,897 was, in fact, mined with a different coinbase than + // block 176,684, but it is important to note that even if it hadn't been or + // is remined on an alternate fork with a duplicate coinbase, we would still + // not run into a BIP30 violation. This is because the coinbase for 176,684 + // is spent in block 185,956 in transaction + // d4f7fbbf92f4a3014a230b2dc70b8058d02eb36ac06b4a0736d9d60eaa9e8781. This + // spending transaction can't be duplicated because it also spends coinbase + // 0328dd85c331237f18e781d692c92de57649529bd5edf1d01036daea32ffde29. This + // coinbase has an indicated height of over 4.2 billion, and wouldn't be + // duplicatable until that height, and it's currently impossible to create a + // chain that long. Nevertheless we may wish to consider a future soft fork + // which retroactively prevents block 490,897 from creating a duplicate + // coinbase. The two historical BIP30 violations often provide a confusing + // edge case when manipulating the UTXO and it would be simpler not to have + // another edge case to deal with. + + // testnet3 has no blocks before the BIP34 height with indicated heights + // post BIP34 before approximately height 486,000,000 and presumably will + // be reset before it reaches block 1,983,702 and starts doing unnecessary + // BIP30 checking again. assert(pindex->pprev); CBlockIndex *pindexBIP34height = pindex->pprev->GetAncestor(chainparams.GetConsensus().BIP34Height); //Only continue to enforce if we're below BIP34 activation height or the block hash at that height doesn't correspond. fEnforceBIP30 = fEnforceBIP30 && (!pindexBIP34height || !(pindexBIP34height->GetBlockHash() == chainparams.GetConsensus().BIP34Hash)); - if (fEnforceBIP30) { + // TODO: Remove BIP30 checking from block height 1,983,702 on, once we have a + // consensus change that ensures coinbases at those heights can not + // duplicate earlier coinbases. + if (fEnforceBIP30 || pindex->nHeight >= BIP34_IMPLIES_BIP30_LIMIT) { for (const auto& tx : block.vtx) { for (size_t o = 0; o < tx->vout.size(); o++) { if (view.HaveCoin(COutPoint(tx->GetHash(), o))) { diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 930e8bbbb4..0edc8d8d66 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -2,8 +2,8 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. -#include <base58.h> #include <chain.h> +#include <key_io.h> #include <rpc/safemode.h> #include <rpc/server.h> #include <wallet/init.h> @@ -147,13 +147,8 @@ UniValue importprivkey(const JSONRPCRequest& request) throw JSONRPCError(RPC_WALLET_ERROR, "Wallet is currently rescanning. Abort existing rescan or wait."); } - CBitcoinSecret vchSecret; - bool fGood = vchSecret.SetString(strSecret); - - if (!fGood) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); - - CKey key = vchSecret.GetKey(); - if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range"); + CKey key = DecodeSecret(strSecret); + if (!key.IsValid()) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); CPubKey pubkey = key.GetPubKey(); assert(key.VerifyPubKey(pubkey)); @@ -554,9 +549,8 @@ UniValue importwallet(const JSONRPCRequest& request) boost::split(vstr, line, boost::is_any_of(" ")); if (vstr.size() < 2) continue; - CBitcoinSecret vchSecret; - if (vchSecret.SetString(vstr[0])) { - CKey key = vchSecret.GetKey(); + CKey key = DecodeSecret(vstr[0]); + if (key.IsValid()) { CPubKey pubkey = key.GetPubKey(); assert(key.VerifyPubKey(pubkey)); CKeyID keyid = pubkey.GetID(); @@ -659,7 +653,7 @@ UniValue dumpprivkey(const JSONRPCRequest& request) if (!pwallet->GetKey(keyid, vchSecret)) { throw JSONRPCError(RPC_WALLET_ERROR, "Private key for address " + strAddress + " is not known"); } - return CBitcoinSecret(vchSecret).ToString(); + return EncodeSecret(vchSecret); } @@ -742,10 +736,7 @@ UniValue dumpwallet(const JSONRPCRequest& request) CExtKey masterKey; masterKey.SetMaster(key.begin(), key.size()); - CBitcoinExtKey b58extkey; - b58extkey.SetKey(masterKey); - - file << "# extended private masterkey: " << b58extkey.ToString() << "\n\n"; + file << "# extended private masterkey: " << EncodeExtKey(masterKey) << "\n\n"; } } for (std::vector<std::pair<int64_t, CKeyID> >::const_iterator it = vKeyBirth.begin(); it != vKeyBirth.end(); it++) { @@ -755,7 +746,7 @@ UniValue dumpwallet(const JSONRPCRequest& request) std::string strLabel; CKey key; if (pwallet->GetKey(keyid, key)) { - file << strprintf("%s %s ", CBitcoinSecret(key).ToString(), strTime); + file << strprintf("%s %s ", EncodeSecret(key), strTime); if (GetWalletAddressesForKey(pwallet, keyid, strAddr, strLabel)) { file << strprintf("label=%s", strLabel); } else if (keyid == masterKeyID) { @@ -911,17 +902,10 @@ UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int6 for (size_t i = 0; i < keys.size(); i++) { const std::string& privkey = keys[i].get_str(); - CBitcoinSecret vchSecret; - bool fGood = vchSecret.SetString(privkey); - - if (!fGood) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); - } - - CKey key = vchSecret.GetKey(); + CKey key = DecodeSecret(privkey); if (!key.IsValid()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); } CPubKey pubkey = key.GetPubKey(); @@ -1018,16 +1002,10 @@ UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int6 const std::string& strPrivkey = keys[0].get_str(); // Checks. - CBitcoinSecret vchSecret; - bool fGood = vchSecret.SetString(strPrivkey); - - if (!fGood) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); - } + CKey key = DecodeSecret(strPrivkey); - CKey key = vchSecret.GetKey(); if (!key.IsValid()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key outside allowed range"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); } CPubKey pubKey = key.GetPubKey(); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index dfa8546076..457abec1bc 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -4,12 +4,12 @@ // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <amount.h> -#include <base58.h> #include <chain.h> #include <consensus/validation.h> #include <core_io.h> #include <httpserver.h> #include <validation.h> +#include <key_io.h> #include <net.h> #include <policy/feerate.h> #include <policy/fees.h> diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index bb7be2df33..a014f5b2a0 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -5,7 +5,6 @@ #include <wallet/wallet.h> -#include <base58.h> #include <checkpoints.h> #include <chain.h> #include <wallet/coincontrol.h> @@ -14,6 +13,7 @@ #include <fs.h> #include <wallet/init.h> #include <key.h> +#include <key_io.h> #include <keystore.h> #include <validation.h> #include <net.h> diff --git a/src/wallet/walletdb.cpp b/src/wallet/walletdb.cpp index dd6835a06f..c14fbd1e8c 100644 --- a/src/wallet/walletdb.cpp +++ b/src/wallet/walletdb.cpp @@ -5,10 +5,10 @@ #include <wallet/walletdb.h> -#include <base58.h> #include <consensus/tx_verify.h> #include <consensus/validation.h> #include <fs.h> +#include <key_io.h> #include <protocol.h> #include <serialize.h> #include <sync.h> |