Page MenuHomePhabricator

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/src/httprpc.cpp b/src/httprpc.cpp
index 7ae1dc46e..987e55c6a 100644
--- a/src/httprpc.cpp
+++ b/src/httprpc.cpp
@@ -1,415 +1,416 @@
// Copyright (c) 2015-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 "httprpc.h"
#include "base58.h"
#include "chainparams.h"
#include "config.h"
#include "crypto/hmac_sha256.h"
#include "httpserver.h"
#include "random.h"
#include "rpc/protocol.h"
#include "rpc/server.h"
#include "sync.h"
#include "ui_interface.h"
#include "util.h"
#include "utilstrencodings.h"
#include <boost/algorithm/string.hpp> // boost::trim
#include <cstdio>
/** WWW-Authenticate to present with 401 Unauthorized response */
static const char *WWW_AUTH_HEADER_DATA = "Basic realm=\"jsonrpc\"";
/** RPC auth failure delay to make brute-forcing expensive */
static const int64_t RPC_AUTH_BRUTE_FORCE_DELAY = 250;
/**
* Simple one-shot callback timer to be used by the RPC mechanism to e.g.
* re-lock the wallet.
*/
class HTTPRPCTimer : public RPCTimerBase {
public:
HTTPRPCTimer(struct event_base *eventBase, std::function<void(void)> &func,
int64_t millis)
: ev(eventBase, false, func) {
struct timeval tv;
tv.tv_sec = millis / 1000;
tv.tv_usec = (millis % 1000) * 1000;
ev.trigger(&tv);
}
private:
HTTPEvent ev;
};
class HTTPRPCTimerInterface : public RPCTimerInterface {
public:
explicit HTTPRPCTimerInterface(struct event_base *_base) : base(_base) {}
const char *Name() override { return "HTTP"; }
RPCTimerBase *NewTimer(std::function<void(void)> &func,
int64_t millis) override {
return new HTTPRPCTimer(base, func, millis);
}
private:
struct event_base *base;
};
/* Stored RPC timer interface (for unregistration) */
static std::unique_ptr<HTTPRPCTimerInterface> httpRPCTimerInterface;
static void JSONErrorReply(HTTPRequest *req, const UniValue &objError,
const UniValue &id) {
// Send error reply from json-rpc error object.
int nStatus = HTTP_INTERNAL_SERVER_ERROR;
int code = find_value(objError, "code").get_int();
if (code == RPC_INVALID_REQUEST)
nStatus = HTTP_BAD_REQUEST;
else if (code == RPC_METHOD_NOT_FOUND)
nStatus = HTTP_NOT_FOUND;
std::string strReply = JSONRPCReply(NullUniValue, objError, id);
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(nStatus, strReply);
}
/*
* This function checks username and password against -rpcauth entries from
* config file.
*/
static bool multiUserAuthorized(std::string strUserPass) {
if (strUserPass.find(":") == std::string::npos) {
return false;
}
std::string strUser = strUserPass.substr(0, strUserPass.find(":"));
std::string strPass = strUserPass.substr(strUserPass.find(":") + 1);
for (const std::string &strRPCAuth : gArgs.GetArgs("-rpcauth")) {
// Search for multi-user login/pass "rpcauth" from config
std::vector<std::string> vFields;
boost::split(vFields, strRPCAuth, boost::is_any_of(":$"));
if (vFields.size() != 3) {
// Incorrect formatting in config file
continue;
}
std::string strName = vFields[0];
if (!TimingResistantEqual(strName, strUser)) {
continue;
}
std::string strSalt = vFields[1];
std::string strHash = vFields[2];
static const unsigned int KEY_SIZE = 32;
uint8_t out[KEY_SIZE];
CHMAC_SHA256(reinterpret_cast<const uint8_t *>(strSalt.c_str()),
strSalt.size())
.Write(reinterpret_cast<const uint8_t *>(strPass.c_str()),
strPass.size())
.Finalize(out);
std::vector<uint8_t> hexvec(out, out + KEY_SIZE);
std::string strHashFromPass = HexStr(hexvec);
if (TimingResistantEqual(strHashFromPass, strHash)) {
return true;
}
}
return false;
}
static bool RPCAuthorized(Config &config, const std::string &strAuth,
std::string &strAuthUsernameOut) {
// Belt-and-suspenders measure if InitRPCAuthentication was not called.
if (config.GetRPCUserAndPassword().empty()) {
return false;
}
if (strAuth.substr(0, 6) != "Basic ") {
return false;
}
std::string strUserPass64 = strAuth.substr(6);
boost::trim(strUserPass64);
std::string strUserPass = DecodeBase64(strUserPass64);
if (strUserPass.find(":") != std::string::npos) {
strAuthUsernameOut = strUserPass.substr(0, strUserPass.find(":"));
}
// Check if authorized under single-user field
if (TimingResistantEqual(strUserPass, config.GetRPCUserAndPassword())) {
return true;
}
return multiUserAuthorized(strUserPass);
}
static bool checkCORS(Config &config, HTTPRequest *req) {
// https://www.w3.org/TR/cors/#resource-requests
// 1. If the Origin header is not present terminate this set of steps.
// The request is outside the scope of this specification.
std::pair<bool, std::string> origin = req->GetHeader("origin");
if (!origin.first) {
return false;
}
// 2. If the value of the Origin header is not a case-sensitive match for
// any of the values in list of origins do not set any additional headers
// and terminate this set of steps.
// Note: Always matching is acceptable since the list of origins can be
// unbounded.
if (origin.second != config.GetRPCCORSDomain()) {
return false;
}
if (req->GetRequestMethod() == HTTPRequest::OPTIONS) {
// 6.2 Preflight Request
// In response to a preflight request the resource indicates which
// methods and headers (other than simple methods and simple
// headers) it is willing to handle and whether it supports
// credentials.
// Resources must use the following set of steps to determine which
// additional headers to use in the response:
// 3. Let method be the value as result of parsing the
// Access-Control-Request-Method header.
// If there is no Access-Control-Request-Method header or if parsing
// failed, do not set any additional headers and terminate this set
// of steps. The request is outside the scope of this specification.
std::pair<bool, std::string> method =
req->GetHeader("access-control-request-method");
if (!method.first) {
return false;
}
// 4. Let header field-names be the values as result of parsing
// the Access-Control-Request-Headers headers.
// If there are no Access-Control-Request-Headers headers let header
// field-names be the empty list.
// If parsing failed do not set any additional headers and terminate
// this set of steps. The request is outside the scope of this
// specification.
std::pair<bool, std::string> header_field_names =
req->GetHeader("access-control-request-headers");
// 5. If method is not a case-sensitive match for any of the
// values in list of methods do not set any additional headers
// and terminate this set of steps.
// Note: Always matching is acceptable since the list of methods
// can be unbounded.
if (method.second != "POST") {
return false;
}
// 6. If any of the header field-names is not a ASCII case-
// insensitive match for any of the values in list of headers do not
// set any additional headers and terminate this set of steps.
// Note: Always matching is acceptable since the list of headers can
// be unbounded.
const std::string &list_of_headers = "authorization,content-type";
// 7. If the resource supports credentials add a single
// Access-Control-Allow-Origin header, with the value of the Origin
// header as value, and add a single
// Access-Control-Allow-Credentials header with the case-sensitive
// string "true" as value.
req->WriteHeader("Access-Control-Allow-Origin", origin.second);
req->WriteHeader("Access-Control-Allow-Credentials", "true");
// 8. Optionally add a single Access-Control-Max-Age header with as
// value the amount of seconds the user agent is allowed to cache
// the result of the request.
// 9. If method is a simple method this step may be skipped.
// Add one or more Access-Control-Allow-Methods headers consisting
// of (a subset of) the list of methods.
// If a method is a simple method it does not need to be listed, but
// this is not prohibited.
// Note: Since the list of methods can be unbounded, simply
// returning the method indicated by
// Access-Control-Request-Method (if supported) can be enough.
req->WriteHeader("Access-Control-Allow-Methods", method.second);
// 10. If each of the header field-names is a simple header and none
// is Content-Type, this step may be skipped.
// Add one or more Access-Control-Allow-Headers headers consisting
// of (a subset of) the list of headers.
req->WriteHeader("Access-Control-Allow-Headers",
header_field_names.first ? header_field_names.second
: list_of_headers);
req->WriteReply(HTTP_OK);
return true;
}
// 6.1 Simple Cross-Origin Request, Actual Request, and Redirects
// In response to a simple cross-origin request or actual request the
// resource indicates whether or not to share the response.
// If the resource has been relocated, it indicates whether to share its
// new URL.
// Resources must use the following set of steps to determine which
// additional headers to use in the response:
// 3. If the resource supports credentials add a single
// Access-Control-Allow-Origin header, with the value of the Origin
// header as value, and add a single Access-Control-Allow-Credentials
// header with the case-sensitive string "true" as value.
req->WriteHeader("Access-Control-Allow-Origin", origin.second);
req->WriteHeader("Access-Control-Allow-Credentials", "true");
// 4. If the list of exposed headers is not empty add one or more
// Access-Control-Expose-Headers headers, with as values the header
// field names given in the list of exposed headers.
req->WriteHeader("Access-Control-Expose-Headers", "WWW-Authenticate");
return false;
}
bool HTTPRPCRequestProcessor::ProcessHTTPRequest(HTTPRequest *req) {
// First, check and/or set CORS headers
if (checkCORS(config, req)) {
return true;
}
// JSONRPC handles only POST
if (req->GetRequestMethod() != HTTPRequest::POST) {
req->WriteReply(HTTP_BAD_METHOD,
"JSONRPC server handles only POST requests");
return false;
}
// Check authorization
std::pair<bool, std::string> authHeader = req->GetHeader("authorization");
if (!authHeader.first) {
req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA);
req->WriteReply(HTTP_UNAUTHORIZED);
return false;
}
JSONRPCRequest jreq;
if (!RPCAuthorized(config, authHeader.second, jreq.authUser)) {
LogPrintf("ThreadRPCServer incorrect password attempt from %s\n",
req->GetPeer().ToString());
/**
* Deter brute-forcing.
* If this results in a DoS the user really shouldn't have their RPC
* port exposed.
*/
MilliSleep(RPC_AUTH_BRUTE_FORCE_DELAY);
req->WriteHeader("WWW-Authenticate", WWW_AUTH_HEADER_DATA);
req->WriteReply(HTTP_UNAUTHORIZED);
return false;
}
try {
// Parse request
UniValue valRequest;
if (!valRequest.read(req->ReadBody()))
throw JSONRPCError(RPC_PARSE_ERROR, "Parse error");
// Set the URI
jreq.URI = req->GetURI();
std::string strReply;
// singleton request
if (valRequest.isObject()) {
jreq.parse(valRequest);
UniValue result = rpcServer.ExecuteCommand(config, jreq);
// Send reply
strReply = JSONRPCReply(result, NullUniValue, jreq.id);
} else if (valRequest.isArray()) {
// array of requests
strReply = JSONRPCExecBatch(config, rpcServer, jreq,
valRequest.get_array());
} else {
throw JSONRPCError(RPC_PARSE_ERROR, "Top-level object parse error");
}
req->WriteHeader("Content-Type", "application/json");
req->WriteReply(HTTP_OK, strReply);
} catch (const UniValue &objError) {
JSONErrorReply(req, objError, jreq.id);
return false;
} catch (const std::exception &e) {
JSONErrorReply(req, JSONRPCError(RPC_PARSE_ERROR, e.what()), jreq.id);
return false;
}
return true;
}
static bool InitRPCAuthentication(Config &config) {
if (gArgs.GetArg("-rpcpassword", "") == "") {
LogPrintf("No rpcpassword set - using random cookie authentication\n");
std::string generatedUserAndPassword;
if (!GenerateAuthCookie(&generatedUserAndPassword)) {
// Same message as AbortNode.
uiInterface.ThreadSafeMessageBox(
_("Error: A fatal internal error occurred, see debug.log for "
"details"),
"", CClientUIInterface::MSG_ERROR);
return false;
}
config.SetRPCUserAndPassword(generatedUserAndPassword);
} else {
LogPrintf("Config options rpcuser and rpcpassword will soon be "
"deprecated. Locally-run instances may remove rpcuser to use "
"cookie-based auth, or may be replaced with rpcauth. Please "
"see share/rpcuser for rpcauth auth generation.\n");
config.SetRPCUserAndPassword(gArgs.GetArg("-rpcuser", "") + ":" +
gArgs.GetArg("-rpcpassword", ""));
}
config.SetRPCCORSDomain(gArgs.GetArg("-rpccorsdomain", ""));
return true;
}
bool StartHTTPRPC(Config &config,
HTTPRPCRequestProcessor &httpRPCRequestProcessor) {
LogPrint(BCLog::RPC, "Starting HTTP RPC server\n");
if (!InitRPCAuthentication(config)) {
return false;
}
const std::function<bool(Config &, HTTPRequest *, const std::string &)>
&rpcFunction =
std::bind(&HTTPRPCRequestProcessor::DelegateHTTPRequest,
&httpRPCRequestProcessor, std::placeholders::_2);
RegisterHTTPHandler("/", true, rpcFunction);
#ifdef ENABLE_WALLET
// ifdef can be removed once we switch to better endpoint support and API
// versioning
RegisterHTTPHandler("/wallet/", false, rpcFunction);
#endif
assert(EventBase());
- httpRPCTimerInterface = MakeUnique<HTTPRPCTimerInterface>(EventBase());
+ httpRPCTimerInterface =
+ std::make_unique<HTTPRPCTimerInterface>(EventBase());
RPCSetTimerInterface(httpRPCTimerInterface.get());
return true;
}
void InterruptHTTPRPC() {
LogPrint(BCLog::RPC, "Interrupting HTTP RPC server\n");
}
void StopHTTPRPC() {
LogPrint(BCLog::RPC, "Stopping HTTP RPC server\n");
UnregisterHTTPHandler("/", true);
#ifdef ENABLE_WALLET
UnregisterHTTPHandler("/wallet/", false);
#endif
if (httpRPCTimerInterface) {
RPCUnsetTimerInterface(httpRPCTimerInterface.get());
httpRPCTimerInterface.reset();
}
}
diff --git a/src/net.cpp b/src/net.cpp
index dbba1baf4..b90e5894b 100644
--- a/src/net.cpp
+++ b/src/net.cpp
@@ -1,3096 +1,3096 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-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.
#if defined(HAVE_CONFIG_H)
#include "config/bitcoin-config.h"
#endif
#include "net.h"
#include "addrman.h"
#include "chainparams.h"
#include "clientversion.h"
#include "config.h"
#include "consensus/consensus.h"
#include "crypto/common.h"
#include "crypto/sha256.h"
#include "hash.h"
#include "netbase.h"
#include "primitives/transaction.h"
#include "scheduler.h"
#include "ui_interface.h"
#include "utilstrencodings.h"
#ifdef WIN32
#include <string.h>
#else
#include <fcntl.h>
#endif
#ifdef USE_UPNP
#include <miniupnpc/miniupnpc.h>
#include <miniupnpc/miniwget.h>
#include <miniupnpc/upnpcommands.h>
#include <miniupnpc/upnperrors.h>
#endif
#include <cmath>
// Dump addresses to peers.dat and banlist.dat every 15 minutes (900s)
#define DUMP_ADDRESSES_INTERVAL 900
// We add a random period time (0 to 1 seconds) to feeler connections to prevent
// synchronization.
#define FEELER_SLEEP_WINDOW 1
// MSG_NOSIGNAL is not available on some platforms, if it doesn't exist define
// it as 0
#if !defined(MSG_NOSIGNAL)
#define MSG_NOSIGNAL 0
#endif
// MSG_DONTWAIT is not available on some platforms, if it doesn't exist define
// it as 0
#if !defined(MSG_DONTWAIT)
#define MSG_DONTWAIT 0
#endif
// Fix for ancient MinGW versions, that don't have defined these in ws2tcpip.h.
// Todo: Can be removed when our pull-tester is upgraded to a modern MinGW
// version.
#ifdef WIN32
#ifndef PROTECTION_LEVEL_UNRESTRICTED
#define PROTECTION_LEVEL_UNRESTRICTED 10
#endif
#ifndef IPV6_PROTECTION_LEVEL
#define IPV6_PROTECTION_LEVEL 23
#endif
#endif
/** Used to pass flags to the Bind() function */
enum BindFlags {
BF_NONE = 0,
BF_EXPLICIT = (1U << 0),
BF_REPORT_ERROR = (1U << 1),
BF_WHITELIST = (1U << 2),
};
const static std::string NET_MESSAGE_COMMAND_OTHER = "*other*";
// SHA256("netgroup")[0:8]
static const uint64_t RANDOMIZER_ID_NETGROUP = 0x6c0edd8036ef4036ULL;
// SHA256("localhostnonce")[0:8]
static const uint64_t RANDOMIZER_ID_LOCALHOSTNONCE = 0xd93e69e2bbfa5735ULL;
//
// Global state variables
//
bool fDiscover = true;
bool fListen = true;
bool fRelayTxes = true;
CCriticalSection cs_mapLocalHost;
std::map<CNetAddr, LocalServiceInfo> mapLocalHost;
static bool vfLimited[NET_MAX] = {};
limitedmap<uint256, int64_t> mapAlreadyAskedFor(MAX_INV_SZ);
void CConnman::AddOneShot(const std::string &strDest) {
LOCK(cs_vOneShots);
vOneShots.push_back(strDest);
}
unsigned short GetListenPort() {
return (unsigned short)(gArgs.GetArg("-port", Params().GetDefaultPort()));
}
// find 'best' local address for a particular peer
bool GetLocal(CService &addr, const CNetAddr *paddrPeer) {
if (!fListen) {
return false;
}
int nBestScore = -1;
int nBestReachability = -1;
{
LOCK(cs_mapLocalHost);
for (const auto &entry : mapLocalHost) {
int nScore = entry.second.nScore;
int nReachability = entry.first.GetReachabilityFrom(paddrPeer);
if (nReachability > nBestReachability ||
(nReachability == nBestReachability && nScore > nBestScore)) {
addr = CService(entry.first, entry.second.nPort);
nBestReachability = nReachability;
nBestScore = nScore;
}
}
}
return nBestScore >= 0;
}
//! Convert the pnSeeds6 array into usable address objects.
static std::vector<CAddress>
convertSeed6(const std::vector<SeedSpec6> &vSeedsIn) {
// It'll only connect to one or two seed nodes because once it connects,
// it'll get a pile of addresses with newer timestamps. Seed nodes are given
// a random 'last seen time' of between one and two weeks ago.
const int64_t nOneWeek = 7 * 24 * 60 * 60;
std::vector<CAddress> vSeedsOut;
vSeedsOut.reserve(vSeedsIn.size());
for (const auto &seed_in : vSeedsIn) {
struct in6_addr ip;
memcpy(&ip, seed_in.addr, sizeof(ip));
CAddress addr(CService(ip, seed_in.port),
GetDesirableServiceFlags(NODE_NONE));
addr.nTime = GetTime() - GetRand(nOneWeek) - nOneWeek;
vSeedsOut.push_back(addr);
}
return vSeedsOut;
}
// Get best local address for a particular peer as a CAddress. Otherwise, return
// the unroutable 0.0.0.0 but filled in with the normal parameters, since the IP
// may be changed to a useful one by discovery.
CAddress GetLocalAddress(const CNetAddr *paddrPeer,
ServiceFlags nLocalServices) {
CAddress ret(CService(CNetAddr(), GetListenPort()), nLocalServices);
CService addr;
if (GetLocal(addr, paddrPeer)) {
ret = CAddress(addr, nLocalServices);
}
ret.nTime = GetAdjustedTime();
return ret;
}
static int GetnScore(const CService &addr) {
LOCK(cs_mapLocalHost);
if (mapLocalHost.count(addr) == LOCAL_NONE) {
return 0;
}
return mapLocalHost[addr].nScore;
}
// Is our peer's addrLocal potentially useful as an external IP source?
bool IsPeerAddrLocalGood(CNode *pnode) {
CService addrLocal = pnode->GetAddrLocal();
return fDiscover && pnode->addr.IsRoutable() && addrLocal.IsRoutable() &&
!IsLimited(addrLocal.GetNetwork());
}
// Pushes our own address to a peer.
void AdvertiseLocal(CNode *pnode) {
if (fListen && pnode->fSuccessfullyConnected) {
CAddress addrLocal =
GetLocalAddress(&pnode->addr, pnode->GetLocalServices());
if (gArgs.GetBoolArg("-addrmantest", false)) {
// use IPv4 loopback during addrmantest
addrLocal =
CAddress(CService(LookupNumeric("127.0.0.1", GetListenPort())),
pnode->GetLocalServices());
}
// If discovery is enabled, sometimes give our peer the address it tells
// us that it sees us as in case it has a better idea of our address
// than we do.
if (IsPeerAddrLocalGood(pnode) &&
(!addrLocal.IsRoutable() ||
GetRand((GetnScore(addrLocal) > LOCAL_MANUAL) ? 8 : 2) == 0)) {
addrLocal.SetIP(pnode->GetAddrLocal());
}
if (addrLocal.IsRoutable() || gArgs.GetBoolArg("-addrmantest", false)) {
LogPrint(BCLog::NET, "AdvertiseLocal: advertising address %s\n",
addrLocal.ToString());
FastRandomContext insecure_rand;
pnode->PushAddress(addrLocal, insecure_rand);
}
}
}
// Learn a new local address.
bool AddLocal(const CService &addr, int nScore) {
if (!addr.IsRoutable()) {
return false;
}
if (!fDiscover && nScore < LOCAL_MANUAL) {
return false;
}
if (IsLimited(addr)) {
return false;
}
LogPrintf("AddLocal(%s,%i)\n", addr.ToString(), nScore);
{
LOCK(cs_mapLocalHost);
bool fAlready = mapLocalHost.count(addr) > 0;
LocalServiceInfo &info = mapLocalHost[addr];
if (!fAlready || nScore >= info.nScore) {
info.nScore = nScore + (fAlready ? 1 : 0);
info.nPort = addr.GetPort();
}
}
return true;
}
bool AddLocal(const CNetAddr &addr, int nScore) {
return AddLocal(CService(addr, GetListenPort()), nScore);
}
void RemoveLocal(const CService &addr) {
LOCK(cs_mapLocalHost);
LogPrintf("RemoveLocal(%s)\n", addr.ToString());
mapLocalHost.erase(addr);
}
/**
* Make a particular network entirely off-limits (no automatic connects to it).
*/
void SetLimited(enum Network net, bool fLimited) {
if (net == NET_UNROUTABLE || net == NET_INTERNAL) {
return;
}
LOCK(cs_mapLocalHost);
vfLimited[net] = fLimited;
}
bool IsLimited(enum Network net) {
LOCK(cs_mapLocalHost);
return vfLimited[net];
}
bool IsLimited(const CNetAddr &addr) {
return IsLimited(addr.GetNetwork());
}
/** vote for a local address */
bool SeenLocal(const CService &addr) {
LOCK(cs_mapLocalHost);
if (mapLocalHost.count(addr) == 0) {
return false;
}
mapLocalHost[addr].nScore++;
return true;
}
/** check whether a given address is potentially local */
bool IsLocal(const CService &addr) {
LOCK(cs_mapLocalHost);
return mapLocalHost.count(addr) > 0;
}
/** check whether a given network is one we can probably connect to */
bool IsReachable(enum Network net) {
LOCK(cs_mapLocalHost);
return !vfLimited[net];
}
/** check whether a given address is in a network we can probably connect to */
bool IsReachable(const CNetAddr &addr) {
enum Network net = addr.GetNetwork();
return IsReachable(net);
}
CNode *CConnman::FindNode(const CNetAddr &ip) {
LOCK(cs_vNodes);
for (CNode *pnode : vNodes) {
if (static_cast<CNetAddr>(pnode->addr) == ip) {
return pnode;
}
}
return nullptr;
}
CNode *CConnman::FindNode(const CSubNet &subNet) {
LOCK(cs_vNodes);
for (CNode *pnode : vNodes) {
if (subNet.Match(static_cast<CNetAddr>(pnode->addr))) {
return pnode;
}
}
return nullptr;
}
CNode *CConnman::FindNode(const std::string &addrName) {
LOCK(cs_vNodes);
for (CNode *pnode : vNodes) {
if (pnode->GetAddrName() == addrName) {
return pnode;
}
}
return nullptr;
}
CNode *CConnman::FindNode(const CService &addr) {
LOCK(cs_vNodes);
for (CNode *pnode : vNodes) {
if (static_cast<CService>(pnode->addr) == addr) {
return pnode;
}
}
return nullptr;
}
bool CConnman::CheckIncomingNonce(uint64_t nonce) {
LOCK(cs_vNodes);
for (const CNode *pnode : vNodes) {
if (!pnode->fSuccessfullyConnected && !pnode->fInbound &&
pnode->GetLocalNonce() == nonce)
return false;
}
return true;
}
/** Get the bind address for a socket as CAddress */
static CAddress GetBindAddress(SOCKET sock) {
CAddress addr_bind;
struct sockaddr_storage sockaddr_bind;
socklen_t sockaddr_bind_len = sizeof(sockaddr_bind);
if (sock != INVALID_SOCKET) {
if (!getsockname(sock, (struct sockaddr *)&sockaddr_bind,
&sockaddr_bind_len)) {
addr_bind.SetSockAddr((const struct sockaddr *)&sockaddr_bind);
} else {
LogPrint(BCLog::NET, "Warning: getsockname failed\n");
}
}
return addr_bind;
}
CNode *CConnman::ConnectNode(CAddress addrConnect, const char *pszDest,
bool fCountFailure) {
if (pszDest == nullptr) {
if (IsLocal(addrConnect)) {
return nullptr;
}
// Look for an existing connection
CNode *pnode = FindNode(static_cast<CService>(addrConnect));
if (pnode) {
LogPrintf("Failed to open new connection, already connected\n");
return nullptr;
}
}
/// debug print
LogPrint(BCLog::NET, "trying connection %s lastseen=%.1fhrs\n",
pszDest ? pszDest : addrConnect.ToString(),
pszDest
? 0.0
: (double)(GetAdjustedTime() - addrConnect.nTime) / 3600.0);
// Resolve
const int default_port = Params().GetDefaultPort();
if (pszDest) {
std::vector<CService> resolved;
if (Lookup(pszDest, resolved, default_port,
fNameLookup && !HaveNameProxy(), 256) &&
!resolved.empty()) {
addrConnect =
CAddress(resolved[GetRand(resolved.size())], NODE_NONE);
if (!addrConnect.IsValid()) {
LogPrint(BCLog::NET,
"Resolver returned invalid address %s for %s\n",
addrConnect.ToString(), pszDest);
return nullptr;
}
// It is possible that we already have a connection to the IP/port
// pszDest resolved to. In that case, drop the connection that was
// just created, and return the existing CNode instead. Also store
// the name we used to connect in that CNode, so that future
// FindNode() calls to that name catch this early.
LOCK(cs_vNodes);
CNode *pnode = FindNode(static_cast<CService>(addrConnect));
if (pnode) {
pnode->MaybeSetAddrName(std::string(pszDest));
LogPrintf("Failed to open new connection, already connected\n");
return nullptr;
}
}
}
// Connect
bool connected = false;
SOCKET hSocket = INVALID_SOCKET;
proxyType proxy;
if (addrConnect.IsValid()) {
bool proxyConnectionFailed = false;
if (GetProxy(addrConnect.GetNetwork(), proxy)) {
hSocket = CreateSocket(proxy.proxy);
if (hSocket == INVALID_SOCKET) {
return nullptr;
}
connected = ConnectThroughProxy(
proxy, addrConnect.ToStringIP(), addrConnect.GetPort(), hSocket,
nConnectTimeout, &proxyConnectionFailed);
} else {
// no proxy needed (none set for target network)
hSocket = CreateSocket(addrConnect);
if (hSocket == INVALID_SOCKET) {
return nullptr;
}
connected =
ConnectSocketDirectly(addrConnect, hSocket, nConnectTimeout);
}
if (!proxyConnectionFailed) {
// If a connection to the node was attempted, and failure (if any)
// is not caused by a problem connecting to the proxy, mark this as
// an attempt.
addrman.Attempt(addrConnect, fCountFailure);
}
} else if (pszDest && GetNameProxy(proxy)) {
hSocket = CreateSocket(proxy.proxy);
if (hSocket == INVALID_SOCKET) {
return nullptr;
}
std::string host;
int port = default_port;
SplitHostPort(std::string(pszDest), port, host);
connected = ConnectThroughProxy(proxy, host, port, hSocket,
nConnectTimeout, nullptr);
}
if (!connected) {
CloseSocket(hSocket);
return nullptr;
}
// Add node
NodeId id = GetNewNodeId();
uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE)
.Write(id)
.Finalize();
CAddress addr_bind = GetBindAddress(hSocket);
CNode *pnode = new CNode(id, nLocalServices, GetBestHeight(), hSocket,
addrConnect, CalculateKeyedNetGroup(addrConnect),
nonce, addr_bind, pszDest ? pszDest : "", false);
pnode->AddRef();
return pnode;
}
void CConnman::DumpBanlist() {
// Clean unused entries (if bantime has expired)
SweepBanned();
if (!BannedSetIsDirty()) {
return;
}
int64_t nStart = GetTimeMillis();
CBanDB bandb(config->GetChainParams());
banmap_t banmap;
GetBanned(banmap);
if (bandb.Write(banmap)) {
SetBannedSetDirty(false);
}
LogPrint(BCLog::NET,
"Flushed %d banned node ips/subnets to banlist.dat %dms\n",
banmap.size(), GetTimeMillis() - nStart);
}
void CNode::CloseSocketDisconnect() {
fDisconnect = true;
LOCK(cs_hSocket);
if (hSocket != INVALID_SOCKET) {
LogPrint(BCLog::NET, "disconnecting peer=%d\n", id);
CloseSocket(hSocket);
}
}
void CConnman::ClearBanned() {
{
LOCK(cs_setBanned);
setBanned.clear();
setBannedIsDirty = true;
}
// Store banlist to disk.
DumpBanlist();
if (clientInterface) {
clientInterface->BannedListChanged();
}
}
bool CConnman::IsBanned(CNetAddr ip) {
LOCK(cs_setBanned);
for (const auto &it : setBanned) {
CSubNet subNet = it.first;
CBanEntry banEntry = it.second;
if (subNet.Match(ip) && GetTime() < banEntry.nBanUntil) {
return true;
}
}
return false;
}
bool CConnman::IsBanned(CSubNet subnet) {
LOCK(cs_setBanned);
banmap_t::iterator i = setBanned.find(subnet);
if (i != setBanned.end()) {
CBanEntry banEntry = (*i).second;
if (GetTime() < banEntry.nBanUntil) {
return true;
}
}
return false;
}
void CConnman::Ban(const CNetAddr &addr, const BanReason &banReason,
int64_t bantimeoffset, bool sinceUnixEpoch) {
CSubNet subNet(addr);
Ban(subNet, banReason, bantimeoffset, sinceUnixEpoch);
}
void CConnman::Ban(const CSubNet &subNet, const BanReason &banReason,
int64_t bantimeoffset, bool sinceUnixEpoch) {
CBanEntry banEntry(GetTime());
banEntry.banReason = banReason;
if (bantimeoffset <= 0) {
bantimeoffset = gArgs.GetArg("-bantime", DEFAULT_MISBEHAVING_BANTIME);
sinceUnixEpoch = false;
}
banEntry.nBanUntil = (sinceUnixEpoch ? 0 : GetTime()) + bantimeoffset;
{
LOCK(cs_setBanned);
if (setBanned[subNet].nBanUntil < banEntry.nBanUntil) {
setBanned[subNet] = banEntry;
setBannedIsDirty = true;
} else {
return;
}
}
if (clientInterface) {
clientInterface->BannedListChanged();
}
{
LOCK(cs_vNodes);
for (CNode *pnode : vNodes) {
if (subNet.Match(static_cast<CNetAddr>(pnode->addr))) {
pnode->fDisconnect = true;
}
}
}
if (banReason == BanReasonManuallyAdded) {
// Store banlist to disk immediately if user requested ban.
DumpBanlist();
}
}
bool CConnman::Unban(const CNetAddr &addr) {
CSubNet subNet(addr);
return Unban(subNet);
}
bool CConnman::Unban(const CSubNet &subNet) {
{
LOCK(cs_setBanned);
if (!setBanned.erase(subNet)) {
return false;
}
setBannedIsDirty = true;
}
if (clientInterface) {
clientInterface->BannedListChanged();
}
// Store banlist to disk immediately.
DumpBanlist();
return true;
}
void CConnman::GetBanned(banmap_t &banMap) {
LOCK(cs_setBanned);
// Sweep the banlist so expired bans are not returned
SweepBanned();
// Create a thread safe copy.
banMap = setBanned;
}
void CConnman::SetBanned(const banmap_t &banMap) {
LOCK(cs_setBanned);
setBanned = banMap;
setBannedIsDirty = true;
}
void CConnman::SweepBanned() {
int64_t now = GetTime();
bool notifyUI = false;
{
LOCK(cs_setBanned);
banmap_t::iterator it = setBanned.begin();
while (it != setBanned.end()) {
CSubNet subNet = (*it).first;
CBanEntry banEntry = (*it).second;
if (now > banEntry.nBanUntil) {
setBanned.erase(it++);
setBannedIsDirty = true;
notifyUI = true;
LogPrint(
BCLog::NET,
"%s: Removed banned node ip/subnet from banlist.dat: %s\n",
__func__, subNet.ToString());
} else {
++it;
}
}
}
// update UI
if (notifyUI && clientInterface) {
clientInterface->BannedListChanged();
}
}
bool CConnman::BannedSetIsDirty() {
LOCK(cs_setBanned);
return setBannedIsDirty;
}
void CConnman::SetBannedSetDirty(bool dirty) {
// Reuse setBanned lock for the isDirty flag.
LOCK(cs_setBanned);
setBannedIsDirty = dirty;
}
bool CConnman::IsWhitelistedRange(const CNetAddr &addr) {
for (const CSubNet &subnet : vWhitelistedRange) {
if (subnet.Match(addr)) {
return true;
}
}
return false;
}
std::string CNode::GetAddrName() const {
LOCK(cs_addrName);
return addrName;
}
void CNode::MaybeSetAddrName(const std::string &addrNameIn) {
LOCK(cs_addrName);
if (addrName.empty()) {
addrName = addrNameIn;
}
}
CService CNode::GetAddrLocal() const {
LOCK(cs_addrLocal);
return addrLocal;
}
void CNode::SetAddrLocal(const CService &addrLocalIn) {
LOCK(cs_addrLocal);
if (addrLocal.IsValid()) {
error("Addr local already set for node: %i. Refusing to change from %s "
"to %s",
id, addrLocal.ToString(), addrLocalIn.ToString());
} else {
addrLocal = addrLocalIn;
}
}
void CNode::copyStats(CNodeStats &stats) {
stats.nodeid = this->GetId();
stats.nServices = nServices;
stats.addr = addr;
stats.addrBind = addrBind;
{
LOCK(cs_filter);
stats.fRelayTxes = fRelayTxes;
}
stats.nLastSend = nLastSend;
stats.nLastRecv = nLastRecv;
stats.nTimeConnected = nTimeConnected;
stats.nTimeOffset = nTimeOffset;
stats.addrName = GetAddrName();
stats.nVersion = nVersion;
{
LOCK(cs_SubVer);
stats.cleanSubVer = cleanSubVer;
}
stats.fInbound = fInbound;
stats.m_manual_connection = m_manual_connection;
stats.nStartingHeight = nStartingHeight;
{
LOCK(cs_vSend);
stats.mapSendBytesPerMsgCmd = mapSendBytesPerMsgCmd;
stats.nSendBytes = nSendBytes;
}
{
LOCK(cs_vRecv);
stats.mapRecvBytesPerMsgCmd = mapRecvBytesPerMsgCmd;
stats.nRecvBytes = nRecvBytes;
}
stats.fWhitelisted = fWhitelisted;
// 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_t 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.dMinPing = ((double(nMinPingUsecTime)) / 1e6);
stats.dPingWait = ((double(nPingUsecWait)) / 1e6);
// Leave string empty if addrLocal invalid (not filled in yet)
CService addrLocalUnlocked = GetAddrLocal();
stats.addrLocal =
addrLocalUnlocked.IsValid() ? addrLocalUnlocked.ToString() : "";
}
static bool IsOversizedMessage(const Config &config, const CNetMessage &msg) {
if (!msg.in_data) {
// Header only, cannot be oversized.
return false;
}
return msg.hdr.IsOversized(config);
}
bool CNode::ReceiveMsgBytes(const Config &config, const char *pch,
uint32_t nBytes, bool &complete) {
complete = false;
int64_t nTimeMicros = GetTimeMicros();
LOCK(cs_vRecv);
nLastRecv = nTimeMicros / 1000000;
nRecvBytes += nBytes;
while (nBytes > 0) {
// Get current incomplete message, or create a new one.
if (vRecvMsg.empty() || vRecvMsg.back().complete()) {
vRecvMsg.push_back(CNetMessage(config.GetChainParams().NetMagic(),
SER_NETWORK, INIT_PROTO_VERSION));
}
CNetMessage &msg = vRecvMsg.back();
// Absorb network data.
int handled;
if (!msg.in_data) {
handled = msg.readHeader(config, pch, nBytes);
} else {
handled = msg.readData(pch, nBytes);
}
if (handled < 0) {
return false;
}
if (IsOversizedMessage(config, msg)) {
LogPrint(BCLog::NET,
"Oversized message from peer=%i, disconnecting\n",
GetId());
return false;
}
pch += handled;
nBytes -= handled;
if (msg.complete()) {
// Store received bytes per message command to prevent a memory DOS,
// only allow valid commands.
mapMsgCmdSize::iterator i =
mapRecvBytesPerMsgCmd.find(msg.hdr.pchCommand.data());
if (i == mapRecvBytesPerMsgCmd.end()) {
i = mapRecvBytesPerMsgCmd.find(NET_MESSAGE_COMMAND_OTHER);
}
assert(i != mapRecvBytesPerMsgCmd.end());
i->second += msg.hdr.nMessageSize + CMessageHeader::HEADER_SIZE;
msg.nTime = nTimeMicros;
complete = true;
}
}
return true;
}
void CNode::SetSendVersion(int nVersionIn) {
// Send version may only be changed in the version message, and only one
// version message is allowed per session. We can therefore treat this value
// as const and even atomic as long as it's only used once a version message
// has been successfully processed. Any attempt to set this twice is an
// error.
if (nSendVersion != 0) {
error("Send version already set for node: %i. Refusing to change from "
"%i to %i",
id, nSendVersion, nVersionIn);
} else {
nSendVersion = nVersionIn;
}
}
int CNode::GetSendVersion() const {
// The send version should always be explicitly set to INIT_PROTO_VERSION
// rather than using this value until SetSendVersion has been called.
if (nSendVersion == 0) {
error("Requesting unset send version for node: %i. Using %i", id,
INIT_PROTO_VERSION);
return INIT_PROTO_VERSION;
}
return nSendVersion;
}
int CNetMessage::readHeader(const Config &config, const char *pch,
uint32_t nBytes) {
// copy data to temporary parsing buffer
uint32_t nRemaining = 24 - nHdrPos;
uint32_t nCopy = std::min(nRemaining, nBytes);
memcpy(&hdrbuf[nHdrPos], pch, nCopy);
nHdrPos += nCopy;
// if header incomplete, exit
if (nHdrPos < 24) {
return nCopy;
}
// deserialize to CMessageHeader
try {
hdrbuf >> hdr;
} catch (const std::exception &) {
return -1;
}
// Reject oversized messages
if (hdr.IsOversized(config)) {
LogPrint(BCLog::NET, "Oversized header detected\n");
return -1;
}
// switch state to reading message data
in_data = true;
return nCopy;
}
int CNetMessage::readData(const char *pch, uint32_t nBytes) {
unsigned int nRemaining = hdr.nMessageSize - nDataPos;
unsigned int nCopy = std::min(nRemaining, nBytes);
if (vRecv.size() < nDataPos + nCopy) {
// Allocate up to 256 KiB ahead, but never more than the total message
// size.
vRecv.resize(std::min(hdr.nMessageSize, nDataPos + nCopy + 256 * 1024));
}
hasher.Write((const uint8_t *)pch, nCopy);
memcpy(&vRecv[nDataPos], pch, nCopy);
nDataPos += nCopy;
return nCopy;
}
const uint256 &CNetMessage::GetMessageHash() const {
assert(complete());
if (data_hash.IsNull()) {
hasher.Finalize(data_hash.begin());
}
return data_hash;
}
// requires LOCK(cs_vSend)
size_t CConnman::SocketSendData(CNode *pnode) const {
AssertLockHeld(pnode->cs_vSend);
size_t nSentSize = 0;
size_t nMsgCount = 0;
for (const auto &data : pnode->vSendMsg) {
assert(data.size() > pnode->nSendOffset);
int nBytes = 0;
{
LOCK(pnode->cs_hSocket);
if (pnode->hSocket == INVALID_SOCKET) {
break;
}
nBytes = send(pnode->hSocket,
reinterpret_cast<const char *>(data.data()) +
pnode->nSendOffset,
data.size() - pnode->nSendOffset,
MSG_NOSIGNAL | MSG_DONTWAIT);
}
if (nBytes == 0) {
// couldn't send anything at all
break;
}
if (nBytes < 0) {
// error
int nErr = WSAGetLastError();
if (nErr != WSAEWOULDBLOCK && nErr != WSAEMSGSIZE &&
nErr != WSAEINTR && nErr != WSAEINPROGRESS) {
LogPrintf("socket send error %s\n", NetworkErrorString(nErr));
pnode->CloseSocketDisconnect();
}
break;
}
assert(nBytes > 0);
pnode->nLastSend = GetSystemTimeInSeconds();
pnode->nSendBytes += nBytes;
pnode->nSendOffset += nBytes;
nSentSize += nBytes;
if (pnode->nSendOffset != data.size()) {
// could not send full message; stop sending more
break;
}
pnode->nSendOffset = 0;
pnode->nSendSize -= data.size();
pnode->fPauseSend = pnode->nSendSize > nSendBufferMaxSize;
nMsgCount++;
}
pnode->vSendMsg.erase(pnode->vSendMsg.begin(),
pnode->vSendMsg.begin() + nMsgCount);
if (pnode->vSendMsg.empty()) {
assert(pnode->nSendOffset == 0);
assert(pnode->nSendSize == 0);
}
return nSentSize;
}
struct NodeEvictionCandidate {
NodeId id;
int64_t nTimeConnected;
int64_t nMinPingUsecTime;
int64_t nLastBlockTime;
int64_t nLastTXTime;
bool fRelevantServices;
bool fRelayTxes;
bool fBloomFilter;
CAddress addr;
uint64_t nKeyedNetGroup;
};
static bool ReverseCompareNodeMinPingTime(const NodeEvictionCandidate &a,
const NodeEvictionCandidate &b) {
return a.nMinPingUsecTime > b.nMinPingUsecTime;
}
static bool ReverseCompareNodeTimeConnected(const NodeEvictionCandidate &a,
const NodeEvictionCandidate &b) {
return a.nTimeConnected > b.nTimeConnected;
}
static bool CompareNetGroupKeyed(const NodeEvictionCandidate &a,
const NodeEvictionCandidate &b) {
return a.nKeyedNetGroup < b.nKeyedNetGroup;
}
static bool CompareNodeBlockTime(const NodeEvictionCandidate &a,
const NodeEvictionCandidate &b) {
// There is a fall-through here because it is common for a node to have many
// peers which have not yet relayed a block.
if (a.nLastBlockTime != b.nLastBlockTime) {
return a.nLastBlockTime < b.nLastBlockTime;
}
if (a.fRelevantServices != b.fRelevantServices) {
return b.fRelevantServices;
}
return a.nTimeConnected > b.nTimeConnected;
}
static bool CompareNodeTXTime(const NodeEvictionCandidate &a,
const NodeEvictionCandidate &b) {
// There is a fall-through here because it is common for a node to have more
// than a few peers that have not yet relayed txn.
if (a.nLastTXTime != b.nLastTXTime) {
return a.nLastTXTime < b.nLastTXTime;
}
if (a.fRelayTxes != b.fRelayTxes) {
return b.fRelayTxes;
}
if (a.fBloomFilter != b.fBloomFilter) {
return a.fBloomFilter;
}
return a.nTimeConnected > b.nTimeConnected;
}
//! Sort an array by the specified comparator, then erase the last K elements.
template <typename T, typename Comparator>
static void EraseLastKElements(std::vector<T> &elements, Comparator comparator,
size_t k) {
std::sort(elements.begin(), elements.end(), comparator);
size_t eraseSize = std::min(k, elements.size());
elements.erase(elements.end() - eraseSize, elements.end());
}
/**
* Try to find a connection to evict when the node is full.
* Extreme care must be taken to avoid opening the node to attacker triggered
* network partitioning.
* The strategy used here is to protect a small number of peers for each of
* several distinct characteristics which are difficult to forge. In order to
* partition a node the attacker must be simultaneously better at all of them
* than honest peers.
*/
bool CConnman::AttemptToEvictConnection() {
std::vector<NodeEvictionCandidate> vEvictionCandidates;
{
LOCK(cs_vNodes);
for (CNode *node : vNodes) {
if (node->fWhitelisted || !node->fInbound || node->fDisconnect) {
continue;
}
NodeEvictionCandidate candidate = {
node->GetId(),
node->nTimeConnected,
node->nMinPingUsecTime,
node->nLastBlockTime,
node->nLastTXTime,
HasAllDesirableServiceFlags(node->nServices),
node->fRelayTxes,
node->pfilter != nullptr,
node->addr,
node->nKeyedNetGroup};
vEvictionCandidates.push_back(candidate);
}
}
// Protect connections with certain characteristics
// Deterministically select 4 peers to protect by netgroup.
// An attacker cannot predict which netgroups will be protected
EraseLastKElements(vEvictionCandidates, CompareNetGroupKeyed, 4);
// Protect the 8 nodes with the lowest minimum ping time.
// An attacker cannot manipulate this metric without physically moving nodes
// closer to the target.
EraseLastKElements(vEvictionCandidates, ReverseCompareNodeMinPingTime, 8);
// Protect 4 nodes that most recently sent us transactions.
// An attacker cannot manipulate this metric without performing useful work.
EraseLastKElements(vEvictionCandidates, CompareNodeTXTime, 4);
// Protect 4 nodes that most recently sent us blocks.
// An attacker cannot manipulate this metric without performing useful work.
EraseLastKElements(vEvictionCandidates, CompareNodeBlockTime, 4);
// Protect the half of the remaining nodes which have been connected the
// longest. This replicates the non-eviction implicit behavior, and
// precludes attacks that start later.
EraseLastKElements(vEvictionCandidates, ReverseCompareNodeTimeConnected,
vEvictionCandidates.size() / 2);
if (vEvictionCandidates.empty()) {
return false;
}
// Identify the network group with the most connections and youngest member.
// (vEvictionCandidates is already sorted by reverse connect time)
uint64_t naMostConnections;
unsigned int nMostConnections = 0;
int64_t nMostConnectionsTime = 0;
std::map<uint64_t, std::vector<NodeEvictionCandidate>> mapNetGroupNodes;
for (const NodeEvictionCandidate &node : vEvictionCandidates) {
std::vector<NodeEvictionCandidate> &group =
mapNetGroupNodes[node.nKeyedNetGroup];
group.push_back(node);
int64_t grouptime = group[0].nTimeConnected;
size_t group_size = group.size();
if (group_size > nMostConnections ||
(group_size == nMostConnections &&
grouptime > nMostConnectionsTime)) {
nMostConnections = group_size;
nMostConnectionsTime = grouptime;
naMostConnections = node.nKeyedNetGroup;
}
}
// Reduce to the network group with the most connections
vEvictionCandidates = std::move(mapNetGroupNodes[naMostConnections]);
// Disconnect from the network group with the most connections
NodeId evicted = vEvictionCandidates.front().id;
LOCK(cs_vNodes);
for (CNode *pnode : vNodes) {
if (pnode->GetId() == evicted) {
pnode->fDisconnect = true;
return true;
}
}
return false;
}
void CConnman::AcceptConnection(const ListenSocket &hListenSocket) {
struct sockaddr_storage sockaddr;
socklen_t len = sizeof(sockaddr);
SOCKET hSocket =
accept(hListenSocket.socket, (struct sockaddr *)&sockaddr, &len);
CAddress addr;
int nInbound = 0;
int nMaxInbound = nMaxConnections - (nMaxOutbound + nMaxFeeler);
if (hSocket != INVALID_SOCKET) {
if (!addr.SetSockAddr((const struct sockaddr *)&sockaddr)) {
LogPrintf("Warning: Unknown socket family\n");
}
}
bool whitelisted = hListenSocket.whitelisted || IsWhitelistedRange(addr);
{
LOCK(cs_vNodes);
for (const CNode *pnode : vNodes) {
if (pnode->fInbound) {
nInbound++;
}
}
}
if (hSocket == INVALID_SOCKET) {
int nErr = WSAGetLastError();
if (nErr != WSAEWOULDBLOCK) {
LogPrintf("socket error accept failed: %s\n",
NetworkErrorString(nErr));
}
return;
}
if (!fNetworkActive) {
LogPrintf("connection from %s dropped: not accepting new connections\n",
addr.ToString());
CloseSocket(hSocket);
return;
}
if (!IsSelectableSocket(hSocket)) {
LogPrintf("connection from %s dropped: non-selectable socket\n",
addr.ToString());
CloseSocket(hSocket);
return;
}
// According to the internet TCP_NODELAY is not carried into accepted
// sockets on all platforms. Set it again here just to be sure.
SetSocketNoDelay(hSocket);
if (IsBanned(addr) && !whitelisted) {
LogPrint(BCLog::NET, "connection from %s dropped (banned)\n",
addr.ToString());
CloseSocket(hSocket);
return;
}
if (nInbound >= nMaxInbound) {
if (!AttemptToEvictConnection()) {
// No connection to evict, disconnect the new connection
LogPrint(BCLog::NET, "failed to find an eviction candidate - "
"connection dropped (full)\n");
CloseSocket(hSocket);
return;
}
}
NodeId id = GetNewNodeId();
uint64_t nonce = GetDeterministicRandomizer(RANDOMIZER_ID_LOCALHOSTNONCE)
.Write(id)
.Finalize();
CAddress addr_bind = GetBindAddress(hSocket);
CNode *pnode =
new CNode(id, nLocalServices, GetBestHeight(), hSocket, addr,
CalculateKeyedNetGroup(addr), nonce, addr_bind, "", true);
pnode->AddRef();
pnode->fWhitelisted = whitelisted;
m_msgproc->InitializeNode(*config, pnode);
LogPrint(BCLog::NET, "connection from %s accepted\n", addr.ToString());
{
LOCK(cs_vNodes);
vNodes.push_back(pnode);
}
}
void CConnman::ThreadSocketHandler() {
unsigned int nPrevNodeCount = 0;
while (!interruptNet) {
//
// Disconnect nodes
//
{
LOCK(cs_vNodes);
if (!fNetworkActive) {
// Disconnect any connected nodes
for (CNode *pnode : vNodes) {
if (!pnode->fDisconnect) {
LogPrint(BCLog::NET,
"Network not active, dropping peer=%d\n",
pnode->GetId());
pnode->fDisconnect = true;
}
}
}
// Disconnect unused nodes
std::vector<CNode *> vNodesCopy = vNodes;
for (CNode *pnode : vNodesCopy) {
if (pnode->fDisconnect) {
// remove from vNodes
vNodes.erase(remove(vNodes.begin(), vNodes.end(), pnode),
vNodes.end());
// release outbound grant (if any)
pnode->grantOutbound.Release();
// close socket and cleanup
pnode->CloseSocketDisconnect();
// hold in disconnected pool until all refs are released
pnode->Release();
vNodesDisconnected.push_back(pnode);
}
}
}
{
// Delete disconnected nodes
std::list<CNode *> vNodesDisconnectedCopy = vNodesDisconnected;
for (CNode *pnode : vNodesDisconnectedCopy) {
// wait until threads are done using it
if (pnode->GetRefCount() <= 0) {
bool fDelete = false;
{
TRY_LOCK(pnode->cs_inventory, lockInv);
if (lockInv) {
TRY_LOCK(pnode->cs_vSend, lockSend);
if (lockSend) {
fDelete = true;
}
}
}
if (fDelete) {
vNodesDisconnected.remove(pnode);
DeleteNode(pnode);
}
}
}
}
size_t vNodesSize;
{
LOCK(cs_vNodes);
vNodesSize = vNodes.size();
}
if (vNodesSize != nPrevNodeCount) {
nPrevNodeCount = vNodesSize;
if (clientInterface) {
clientInterface->NotifyNumConnectionsChanged(nPrevNodeCount);
}
}
//
// Find which sockets have data to receive
//
struct timeval timeout;
timeout.tv_sec = 0;
// Frequency to poll pnode->vSend
timeout.tv_usec = 50000;
fd_set fdsetRecv;
fd_set fdsetSend;
fd_set fdsetError;
FD_ZERO(&fdsetRecv);
FD_ZERO(&fdsetSend);
FD_ZERO(&fdsetError);
SOCKET hSocketMax = 0;
bool have_fds = false;
for (const ListenSocket &hListenSocket : vhListenSocket) {
FD_SET(hListenSocket.socket, &fdsetRecv);
hSocketMax = std::max(hSocketMax, hListenSocket.socket);
have_fds = true;
}
{
LOCK(cs_vNodes);
for (CNode *pnode : vNodes) {
// Implement the following logic:
// * If there is data to send, select() for sending data. As
// this only happens when optimistic write failed, we choose to
// first drain the write buffer in this case before receiving
// more. This avoids needlessly queueing received data, if the
// remote peer is not themselves receiving data. This means
// properly utilizing TCP flow control signalling.
// * Otherwise, if there is space left in the receive buffer,
// select() for receiving data.
// * Hand off all complete messages to the processor, to be
// handled without blocking here.
bool select_recv = !pnode->fPauseRecv;
bool select_send;
{
LOCK(pnode->cs_vSend);
select_send = !pnode->vSendMsg.empty();
}
LOCK(pnode->cs_hSocket);
if (pnode->hSocket == INVALID_SOCKET) {
continue;
}
FD_SET(pnode->hSocket, &fdsetError);
hSocketMax = std::max(hSocketMax, pnode->hSocket);
have_fds = true;
if (select_send) {
FD_SET(pnode->hSocket, &fdsetSend);
continue;
}
if (select_recv) {
FD_SET(pnode->hSocket, &fdsetRecv);
}
}
}
int nSelect = select(have_fds ? hSocketMax + 1 : 0, &fdsetRecv,
&fdsetSend, &fdsetError, &timeout);
if (interruptNet) {
return;
}
if (nSelect == SOCKET_ERROR) {
if (have_fds) {
int nErr = WSAGetLastError();
LogPrintf("socket select error %s\n", NetworkErrorString(nErr));
for (unsigned int i = 0; i <= hSocketMax; i++) {
FD_SET(i, &fdsetRecv);
}
}
FD_ZERO(&fdsetSend);
FD_ZERO(&fdsetError);
if (!interruptNet.sleep_for(
std::chrono::milliseconds(timeout.tv_usec / 1000))) {
return;
}
}
//
// Accept new connections
//
for (const ListenSocket &hListenSocket : vhListenSocket) {
if (hListenSocket.socket != INVALID_SOCKET &&
FD_ISSET(hListenSocket.socket, &fdsetRecv)) {
AcceptConnection(hListenSocket);
}
}
//
// Service each socket
//
std::vector<CNode *> vNodesCopy;
{
LOCK(cs_vNodes);
vNodesCopy = vNodes;
for (CNode *pnode : vNodesCopy) {
pnode->AddRef();
}
}
for (CNode *pnode : vNodesCopy) {
if (interruptNet) {
return;
}
//
// Receive
//
bool recvSet = false;
bool sendSet = false;
bool errorSet = false;
{
LOCK(pnode->cs_hSocket);
if (pnode->hSocket == INVALID_SOCKET) {
continue;
}
recvSet = FD_ISSET(pnode->hSocket, &fdsetRecv);
sendSet = FD_ISSET(pnode->hSocket, &fdsetSend);
errorSet = FD_ISSET(pnode->hSocket, &fdsetError);
}
if (recvSet || errorSet) {
// typical socket buffer is 8K-64K
char pchBuf[0x10000];
int32_t nBytes = 0;
{
LOCK(pnode->cs_hSocket);
if (pnode->hSocket == INVALID_SOCKET) {
continue;
}
nBytes = recv(pnode->hSocket, pchBuf, sizeof(pchBuf),
MSG_DONTWAIT);
}
if (nBytes > 0) {
bool notify = false;
if (!pnode->ReceiveMsgBytes(*config, pchBuf, nBytes,
notify)) {
pnode->CloseSocketDisconnect();
}
RecordBytesRecv(nBytes);
if (notify) {
size_t nSizeAdded = 0;
auto it(pnode->vRecvMsg.begin());
for (; it != pnode->vRecvMsg.end(); ++it) {
if (!it->complete()) {
break;
}
nSizeAdded +=
it->vRecv.size() + CMessageHeader::HEADER_SIZE;
}
{
LOCK(pnode->cs_vProcessMsg);
pnode->vProcessMsg.splice(
pnode->vProcessMsg.end(), pnode->vRecvMsg,
pnode->vRecvMsg.begin(), it);
pnode->nProcessQueueSize += nSizeAdded;
pnode->fPauseRecv =
pnode->nProcessQueueSize > nReceiveFloodSize;
}
WakeMessageHandler();
}
} else if (nBytes == 0) {
// socket closed gracefully
if (!pnode->fDisconnect) {
LogPrint(BCLog::NET, "socket closed\n");
}
pnode->CloseSocketDisconnect();
} else if (nBytes < 0) {
// error
int nErr = WSAGetLastError();
if (nErr != WSAEWOULDBLOCK && nErr != WSAEMSGSIZE &&
nErr != WSAEINTR && nErr != WSAEINPROGRESS) {
if (!pnode->fDisconnect) {
LogPrintf("socket recv error %s\n",
NetworkErrorString(nErr));
}
pnode->CloseSocketDisconnect();
}
}
}
//
// Send
//
if (sendSet) {
LOCK(pnode->cs_vSend);
size_t nBytes = SocketSendData(pnode);
if (nBytes) {
RecordBytesSent(nBytes);
}
}
//
// Inactivity checking
//
int64_t nTime = GetSystemTimeInSeconds();
if (nTime - pnode->nTimeConnected > 60) {
if (pnode->nLastRecv == 0 || pnode->nLastSend == 0) {
LogPrint(BCLog::NET,
"socket no message in first 60 seconds, %d %d "
"from %d\n",
pnode->nLastRecv != 0, pnode->nLastSend != 0,
pnode->GetId());
pnode->fDisconnect = true;
} else if (nTime - pnode->nLastSend > TIMEOUT_INTERVAL) {
LogPrintf("socket sending timeout: %is\n",
nTime - pnode->nLastSend);
pnode->fDisconnect = true;
} else if (nTime - pnode->nLastRecv >
(pnode->nVersion > BIP0031_VERSION ? TIMEOUT_INTERVAL
: 90 * 60)) {
LogPrintf("socket receive timeout: %is\n",
nTime - pnode->nLastRecv);
pnode->fDisconnect = true;
} else if (pnode->nPingNonceSent &&
pnode->nPingUsecStart + TIMEOUT_INTERVAL * 1000000 <
GetTimeMicros()) {
LogPrintf("ping timeout: %fs\n",
0.000001 *
(GetTimeMicros() - pnode->nPingUsecStart));
pnode->fDisconnect = true;
} else if (!pnode->fSuccessfullyConnected) {
LogPrint(BCLog::NET, "version handshake timeout from %d\n",
pnode->GetId());
pnode->fDisconnect = true;
}
}
}
{
LOCK(cs_vNodes);
for (CNode *pnode : vNodesCopy) {
pnode->Release();
}
}
}
}
void CConnman::WakeMessageHandler() {
{
std::lock_guard<std::mutex> lock(mutexMsgProc);
fMsgProcWake = true;
}
condMsgProc.notify_one();
}
#ifdef USE_UPNP
static CThreadInterrupt g_upnp_interrupt;
static std::thread g_upnp_thread;
static void ThreadMapPort() {
std::string port = strprintf("%u", GetListenPort());
const char *multicastif = nullptr;
const char *minissdpdpath = nullptr;
struct UPNPDev *devlist = nullptr;
char lanaddr[64];
#ifndef UPNPDISCOVER_SUCCESS
/* miniupnpc 1.5 */
devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0);
#elif MINIUPNPC_API_VERSION < 14
/* miniupnpc 1.6 */
int error = 0;
devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, &error);
#else
/* miniupnpc 1.9.20150730 */
int error = 0;
devlist = upnpDiscover(2000, multicastif, minissdpdpath, 0, 0, 2, &error);
#endif
struct UPNPUrls urls;
struct IGDdatas data;
int r;
r = UPNP_GetValidIGD(devlist, &urls, &data, lanaddr, sizeof(lanaddr));
if (r == 1) {
if (fDiscover) {
char externalIPAddress[40];
r = UPNP_GetExternalIPAddress(
urls.controlURL, data.first.servicetype, externalIPAddress);
if (r != UPNPCOMMAND_SUCCESS) {
LogPrintf("UPnP: GetExternalIPAddress() returned %d\n", r);
} else {
if (externalIPAddress[0]) {
CNetAddr resolved;
if (LookupHost(externalIPAddress, resolved, false)) {
LogPrintf("UPnP: ExternalIPAddress = %s\n",
resolved.ToString().c_str());
AddLocal(resolved, LOCAL_UPNP);
}
} else {
LogPrintf("UPnP: GetExternalIPAddress failed.\n");
}
}
}
std::string strDesc = "Bitcoin " + FormatFullVersion();
do {
#ifndef UPNPDISCOVER_SUCCESS
/* miniupnpc 1.5 */
r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype,
port.c_str(), port.c_str(), lanaddr,
strDesc.c_str(), "TCP", 0);
#else
/* miniupnpc 1.6 */
r = UPNP_AddPortMapping(urls.controlURL, data.first.servicetype,
port.c_str(), port.c_str(), lanaddr,
strDesc.c_str(), "TCP", 0, "0");
#endif
if (r != UPNPCOMMAND_SUCCESS) {
LogPrintf(
"AddPortMapping(%s, %s, %s) failed with code %d (%s)\n",
port, port, lanaddr, r, strupnperror(r));
} else {
LogPrintf("UPnP Port Mapping successful.\n");
}
} while (g_upnp_interrupt.sleep_for(std::chrono::minutes(20)));
r = UPNP_DeletePortMapping(urls.controlURL, data.first.servicetype,
port.c_str(), "TCP", 0);
LogPrintf("UPNP_DeletePortMapping() returned: %d\n", r);
freeUPNPDevlist(devlist);
devlist = nullptr;
FreeUPNPUrls(&urls);
} else {
LogPrintf("No valid UPnP IGDs found\n");
freeUPNPDevlist(devlist);
devlist = nullptr;
if (r != 0) {
FreeUPNPUrls(&urls);
}
}
}
void StartMapPort() {
if (!g_upnp_thread.joinable()) {
assert(!g_upnp_interrupt);
g_upnp_thread = std::thread(
(std::bind(&TraceThread<void (*)()>, "upnp", &ThreadMapPort)));
}
}
void InterruptMapPort() {
if (g_upnp_thread.joinable()) {
g_upnp_interrupt();
}
}
void StopMapPort() {
if (g_upnp_thread.joinable()) {
g_upnp_thread.join();
g_upnp_interrupt.reset();
}
}
#else
void StartMapPort() {
// Intentionally left blank.
}
void InterruptMapPort() {
// Intentionally left blank.
}
void StopMapPort() {
// Intentionally left blank.
}
#endif
void CConnman::ThreadDNSAddressSeed() {
// goal: only query DNS seeds if address need is acute.
// Avoiding DNS seeds when we don't need them improves user privacy by
// creating fewer identifying DNS requests, reduces trust by giving seeds
// less influence on the network topology, and reduces traffic to the seeds.
if ((addrman.size() > 0) &&
(!gArgs.GetBoolArg("-forcednsseed", DEFAULT_FORCEDNSSEED))) {
if (!interruptNet.sleep_for(std::chrono::seconds(11))) {
return;
}
LOCK(cs_vNodes);
int nRelevant = 0;
for (const CNode *pnode : vNodes) {
nRelevant += pnode->fSuccessfullyConnected && !pnode->fFeeler &&
!pnode->fOneShot && !pnode->m_manual_connection &&
!pnode->fInbound;
}
if (nRelevant >= 2) {
LogPrintf("P2P peers available. Skipped DNS seeding.\n");
return;
}
}
const std::vector<std::string> &vSeeds =
config->GetChainParams().DNSSeeds();
int found = 0;
LogPrintf("Loading addresses from DNS seeds (could take a while)\n");
for (const std::string &seed : vSeeds) {
if (interruptNet) {
return;
}
if (HaveNameProxy()) {
AddOneShot(seed);
} else {
std::vector<CNetAddr> vIPs;
std::vector<CAddress> vAdd;
ServiceFlags requiredServiceBits =
GetDesirableServiceFlags(NODE_NONE);
std::string host = strprintf("x%x.%s", requiredServiceBits, seed);
CNetAddr resolveSource;
if (!resolveSource.SetInternal(host)) {
continue;
}
// Limits number of IPs learned from a DNS seed
unsigned int nMaxIPs = 256;
if (LookupHost(host.c_str(), vIPs, nMaxIPs, true)) {
for (const CNetAddr &ip : vIPs) {
int nOneDay = 24 * 3600;
CAddress addr = CAddress(
CService(ip, config->GetChainParams().GetDefaultPort()),
requiredServiceBits);
// Use a random age between 3 and 7 days old.
addr.nTime = GetTime() - 3 * nOneDay - GetRand(4 * nOneDay);
vAdd.push_back(addr);
found++;
}
addrman.Add(vAdd, resolveSource);
} else {
// We now avoid directly using results from DNS Seeds which do
// not support service bit filtering, instead using them as a
// oneshot to get nodes with our desired service bits.
AddOneShot(seed);
}
}
}
LogPrintf("%d addresses found from DNS seeds\n", found);
}
void CConnman::DumpAddresses() {
int64_t nStart = GetTimeMillis();
CAddrDB adb(config->GetChainParams());
adb.Write(addrman);
LogPrint(BCLog::NET, "Flushed %d addresses to peers.dat %dms\n",
addrman.size(), GetTimeMillis() - nStart);
}
void CConnman::DumpData() {
DumpAddresses();
DumpBanlist();
}
void CConnman::ProcessOneShot() {
std::string strDest;
{
LOCK(cs_vOneShots);
if (vOneShots.empty()) {
return;
}
strDest = vOneShots.front();
vOneShots.pop_front();
}
CAddress addr;
CSemaphoreGrant grant(*semOutbound, true);
if (grant) {
OpenNetworkConnection(addr, false, &grant, strDest.c_str(), true);
}
}
bool CConnman::GetTryNewOutboundPeer() {
return m_try_another_outbound_peer;
}
void CConnman::SetTryNewOutboundPeer(bool flag) {
m_try_another_outbound_peer = flag;
LogPrint(BCLog::NET, "net: setting try another outbound peer=%s\n",
flag ? "true" : "false");
}
// Return the number of peers we have over our outbound connection limit.
// Exclude peers that are marked for disconnect, or are going to be disconnected
// soon (eg one-shots and feelers).
// Also exclude peers that haven't finished initial connection handshake yet (so
// that we don't decide we're over our desired connection limit, and then evict
// some peer that has finished the handshake).
int CConnman::GetExtraOutboundCount() {
int nOutbound = 0;
{
LOCK(cs_vNodes);
for (const CNode *pnode : vNodes) {
if (!pnode->fInbound && !pnode->m_manual_connection &&
!pnode->fFeeler && !pnode->fDisconnect && !pnode->fOneShot &&
pnode->fSuccessfullyConnected) {
++nOutbound;
}
}
}
return std::max(nOutbound - nMaxOutbound, 0);
}
void CConnman::ThreadOpenConnections(const std::vector<std::string> connect) {
// Connect to specific addresses
if (!connect.empty()) {
for (int64_t nLoop = 0;; nLoop++) {
ProcessOneShot();
for (const std::string &strAddr : connect) {
CAddress addr(CService(), NODE_NONE);
OpenNetworkConnection(addr, false, nullptr, strAddr.c_str(),
false, false, true);
for (int i = 0; i < 10 && i < nLoop; i++) {
if (!interruptNet.sleep_for(
std::chrono::milliseconds(500))) {
return;
}
}
}
if (!interruptNet.sleep_for(std::chrono::milliseconds(500))) {
return;
}
}
}
// Initiate network connections
int64_t nStart = GetTime();
// Minimum time before next feeler connection (in microseconds).
int64_t nNextFeeler =
PoissonNextSend(nStart * 1000 * 1000, FEELER_INTERVAL);
while (!interruptNet) {
ProcessOneShot();
if (!interruptNet.sleep_for(std::chrono::milliseconds(500))) {
return;
}
CSemaphoreGrant grant(*semOutbound);
if (interruptNet) {
return;
}
// Add seed nodes if DNS seeds are all down (an infrastructure attack?).
if (addrman.size() == 0 && (GetTime() - nStart > 60)) {
static bool done = false;
if (!done) {
LogPrintf("Adding fixed seed nodes as DNS doesn't seem to be "
"available.\n");
CNetAddr local;
local.SetInternal("fixedseeds");
addrman.Add(convertSeed6(config->GetChainParams().FixedSeeds()),
local);
done = true;
}
}
//
// Choose an address to connect to based on most recently seen
//
CAddress addrConnect;
// Only connect out to one peer per network group (/16 for IPv4). Do
// this here so we don't have to critsect vNodes inside mapAddresses
// critsect.
int nOutbound = 0;
std::set<std::vector<uint8_t>> setConnected;
{
LOCK(cs_vNodes);
for (const CNode *pnode : vNodes) {
if (!pnode->fInbound && !pnode->m_manual_connection) {
// Netgroups for inbound and addnode peers are not excluded
// because our goal here is to not use multiple of our
// limited outbound slots on a single netgroup but inbound
// and addnode peers do not use our outbound slots. Inbound
// peers also have the added issue that they're attacker
// controlled and could be used to prevent us from
// connecting to particular hosts if we used them here.
setConnected.insert(pnode->addr.GetGroup());
nOutbound++;
}
}
}
// Feeler Connections
//
// Design goals:
// * Increase the number of connectable addresses in the tried table.
//
// Method:
// * Choose a random address from new and attempt to connect to it if
// we can connect successfully it is added to tried.
// * Start attempting feeler connections only after node finishes
// making outbound connections.
// * Only make a feeler connection once every few minutes.
//
bool fFeeler = false;
if (nOutbound >= nMaxOutbound && !GetTryNewOutboundPeer()) {
// The current time right now (in microseconds).
int64_t nTime = GetTimeMicros();
if (nTime > nNextFeeler) {
nNextFeeler = PoissonNextSend(nTime, FEELER_INTERVAL);
fFeeler = true;
} else {
continue;
}
}
addrman.ResolveCollisions();
int64_t nANow = GetAdjustedTime();
int nTries = 0;
while (!interruptNet) {
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)) {
break;
}
// If we didn't find an appropriate destination after trying 100
// addresses fetched from addrman, stop this loop, and let the outer
// loop run again (which sleeps, adds seed nodes, recalculates
// already-connected network ranges, ...) before trying new addrman
// addresses.
nTries++;
if (nTries > 100) {
break;
}
if (IsLimited(addr)) {
continue;
}
// only consider very recently tried nodes after 30 failed attempts
if (nANow - addr.nLastTry < 600 && nTries < 30) {
continue;
}
// for non-feelers, require all the services we'll want,
// for feelers, only require they be a full node (only because most
// SPV clients don't have a good address DB available)
if (!fFeeler && !HasAllDesirableServiceFlags(addr.nServices)) {
continue;
}
if (fFeeler && !MayHaveUsefulAddressDB(addr.nServices)) {
continue;
}
// do not allow non-default ports, unless after 50 invalid addresses
// selected already.
if (addr.GetPort() != config->GetChainParams().GetDefaultPort() &&
nTries < 50) {
continue;
}
addrConnect = addr;
break;
}
if (addrConnect.IsValid()) {
if (fFeeler) {
// Add small amount of random noise before connection to avoid
// synchronization.
int randsleep = GetRandInt(FEELER_SLEEP_WINDOW * 1000);
if (!interruptNet.sleep_for(
std::chrono::milliseconds(randsleep))) {
return;
}
LogPrint(BCLog::NET, "Making feeler connection to %s\n",
addrConnect.ToString());
}
OpenNetworkConnection(addrConnect,
(int)setConnected.size() >=
std::min(nMaxConnections - 1, 2),
&grant, nullptr, false, fFeeler);
}
}
}
std::vector<AddedNodeInfo> CConnman::GetAddedNodeInfo() {
std::vector<AddedNodeInfo> ret;
std::list<std::string> lAddresses(0);
{
LOCK(cs_vAddedNodes);
ret.reserve(vAddedNodes.size());
std::copy(vAddedNodes.cbegin(), vAddedNodes.cend(),
std::back_inserter(lAddresses));
}
// Build a map of all already connected addresses (by IP:port and by name)
// to inbound/outbound and resolved CService
std::map<CService, bool> mapConnected;
std::map<std::string, std::pair<bool, CService>> mapConnectedByName;
{
LOCK(cs_vNodes);
for (const CNode *pnode : vNodes) {
if (pnode->addr.IsValid()) {
mapConnected[pnode->addr] = pnode->fInbound;
}
std::string addrName = pnode->GetAddrName();
if (!addrName.empty()) {
mapConnectedByName[std::move(addrName)] =
std::make_pair(pnode->fInbound,
static_cast<const CService &>(pnode->addr));
}
}
}
for (const std::string &strAddNode : lAddresses) {
CService service(
LookupNumeric(strAddNode.c_str(), Params().GetDefaultPort()));
AddedNodeInfo addedNode{strAddNode, CService(), false, false};
if (service.IsValid()) {
// strAddNode is an IP:port
auto it = mapConnected.find(service);
if (it != mapConnected.end()) {
addedNode.resolvedAddress = service;
addedNode.fConnected = true;
addedNode.fInbound = it->second;
}
} else {
// strAddNode is a name
auto it = mapConnectedByName.find(strAddNode);
if (it != mapConnectedByName.end()) {
addedNode.resolvedAddress = it->second.second;
addedNode.fConnected = true;
addedNode.fInbound = it->second.first;
}
}
ret.emplace_back(std::move(addedNode));
}
return ret;
}
void CConnman::ThreadOpenAddedConnections() {
while (true) {
CSemaphoreGrant grant(*semAddnode);
std::vector<AddedNodeInfo> vInfo = GetAddedNodeInfo();
bool tried = false;
for (const AddedNodeInfo &info : vInfo) {
if (!info.fConnected) {
if (!grant.TryAcquire()) {
// If we've used up our semaphore and need a new one, lets
// not wait here since while we are waiting the
// addednodeinfo state might change.
break;
}
tried = true;
CAddress addr(CService(), NODE_NONE);
OpenNetworkConnection(addr, false, &grant,
info.strAddedNode.c_str(), false, false,
true);
if (!interruptNet.sleep_for(std::chrono::milliseconds(500))) {
return;
}
}
}
// Retry every 60 seconds if a connection was attempted, otherwise two
// seconds.
if (!interruptNet.sleep_for(std::chrono::seconds(tried ? 60 : 2))) {
return;
}
}
}
// If successful, this moves the passed grant to the constructed node.
void CConnman::OpenNetworkConnection(const CAddress &addrConnect,
bool fCountFailure,
CSemaphoreGrant *grantOutbound,
const char *pszDest, bool fOneShot,
bool fFeeler, bool manual_connection) {
//
// Initiate outbound network connection
//
if (interruptNet) {
return;
}
if (!fNetworkActive) {
return;
}
if (!pszDest) {
if (IsLocal(addrConnect) ||
FindNode(static_cast<CNetAddr>(addrConnect)) ||
IsBanned(addrConnect) || FindNode(addrConnect.ToStringIPPort())) {
return;
}
} else if (FindNode(std::string(pszDest))) {
return;
}
CNode *pnode = ConnectNode(addrConnect, pszDest, fCountFailure);
if (!pnode) {
return;
}
if (grantOutbound) {
grantOutbound->MoveTo(pnode->grantOutbound);
}
if (fOneShot) {
pnode->fOneShot = true;
}
if (fFeeler) {
pnode->fFeeler = true;
}
if (manual_connection) {
pnode->m_manual_connection = true;
}
m_msgproc->InitializeNode(*config, pnode);
{
LOCK(cs_vNodes);
vNodes.push_back(pnode);
}
}
void CConnman::ThreadMessageHandler() {
while (!flagInterruptMsgProc) {
std::vector<CNode *> vNodesCopy;
{
LOCK(cs_vNodes);
vNodesCopy = vNodes;
for (CNode *pnode : vNodesCopy) {
pnode->AddRef();
}
}
bool fMoreWork = false;
for (CNode *pnode : vNodesCopy) {
if (pnode->fDisconnect) {
continue;
}
// Receive messages
bool fMoreNodeWork = m_msgproc->ProcessMessages(
*config, pnode, flagInterruptMsgProc);
fMoreWork |= (fMoreNodeWork && !pnode->fPauseSend);
if (flagInterruptMsgProc) {
return;
}
// Send messages
{
LOCK(pnode->cs_sendProcessing);
m_msgproc->SendMessages(*config, pnode, flagInterruptMsgProc);
}
if (flagInterruptMsgProc) {
return;
}
}
{
LOCK(cs_vNodes);
for (CNode *pnode : vNodesCopy) {
pnode->Release();
}
}
WAIT_LOCK(mutexMsgProc, lock);
if (!fMoreWork) {
condMsgProc.wait_until(lock,
std::chrono::steady_clock::now() +
std::chrono::milliseconds(100),
[this] { return fMsgProcWake; });
}
fMsgProcWake = false;
}
}
bool CConnman::BindListenPort(const CService &addrBind, std::string &strError,
bool fWhitelisted) {
strError = "";
int nOne = 1;
// Create socket for listening for incoming connections
struct sockaddr_storage sockaddr;
socklen_t len = sizeof(sockaddr);
if (!addrBind.GetSockAddr((struct sockaddr *)&sockaddr, &len)) {
strError = strprintf("Error: Bind address family for %s not supported",
addrBind.ToString());
LogPrintf("%s\n", strError);
return false;
}
SOCKET hListenSocket = CreateSocket(addrBind);
if (hListenSocket == INVALID_SOCKET) {
strError = strprintf("Error: Couldn't open socket for incoming "
"connections (socket returned error %s)",
NetworkErrorString(WSAGetLastError()));
LogPrintf("%s\n", strError);
return false;
}
// Allow binding if the port is still in TIME_WAIT state after
// the program was closed and restarted.
setsockopt(hListenSocket, SOL_SOCKET, SO_REUSEADDR, (sockopt_arg_type)&nOne,
sizeof(int));
// Some systems don't have IPV6_V6ONLY but are always v6only; others do have
// the option and enable it by default or not. Try to enable it, if
// possible.
if (addrBind.IsIPv6()) {
#ifdef IPV6_V6ONLY
setsockopt(hListenSocket, IPPROTO_IPV6, IPV6_V6ONLY,
(sockopt_arg_type)&nOne, sizeof(int));
#endif
#ifdef WIN32
int nProtLevel = PROTECTION_LEVEL_UNRESTRICTED;
setsockopt(hListenSocket, IPPROTO_IPV6, IPV6_PROTECTION_LEVEL,
(sockopt_arg_type)&nProtLevel, sizeof(int));
#endif
}
if (::bind(hListenSocket, (struct sockaddr *)&sockaddr, len) ==
SOCKET_ERROR) {
int nErr = WSAGetLastError();
if (nErr == WSAEADDRINUSE) {
strError = strprintf(_("Unable to bind to %s on this computer. %s "
"is probably already running."),
addrBind.ToString(), _(PACKAGE_NAME));
} else {
strError = strprintf(_("Unable to bind to %s on this computer "
"(bind returned error %s)"),
addrBind.ToString(), NetworkErrorString(nErr));
}
LogPrintf("%s\n", strError);
CloseSocket(hListenSocket);
return false;
}
LogPrintf("Bound to %s\n", addrBind.ToString());
// Listen for incoming connections
if (listen(hListenSocket, SOMAXCONN) == SOCKET_ERROR) {
strError = strprintf(_("Error: Listening for incoming connections "
"failed (listen returned error %s)"),
NetworkErrorString(WSAGetLastError()));
LogPrintf("%s\n", strError);
CloseSocket(hListenSocket);
return false;
}
vhListenSocket.push_back(ListenSocket(hListenSocket, fWhitelisted));
if (addrBind.IsRoutable() && fDiscover && !fWhitelisted) {
AddLocal(addrBind, LOCAL_BIND);
}
return true;
}
void Discover() {
if (!fDiscover) {
return;
}
#ifdef WIN32
// Get local host IP
char pszHostName[256] = "";
if (gethostname(pszHostName, sizeof(pszHostName)) != SOCKET_ERROR) {
std::vector<CNetAddr> vaddr;
if (LookupHost(pszHostName, vaddr, 0, true)) {
for (const CNetAddr &addr : vaddr) {
if (AddLocal(addr, LOCAL_IF)) {
LogPrintf("%s: %s - %s\n", __func__, pszHostName,
addr.ToString());
}
}
}
}
#else
// Get local host ip
struct ifaddrs *myaddrs;
if (getifaddrs(&myaddrs) == 0) {
for (struct ifaddrs *ifa = myaddrs; ifa != nullptr;
ifa = ifa->ifa_next) {
if (ifa->ifa_addr == nullptr || (ifa->ifa_flags & IFF_UP) == 0 ||
strcmp(ifa->ifa_name, "lo") == 0 ||
strcmp(ifa->ifa_name, "lo0") == 0) {
continue;
}
if (ifa->ifa_addr->sa_family == AF_INET) {
struct sockaddr_in *s4 =
reinterpret_cast<struct sockaddr_in *>(ifa->ifa_addr);
CNetAddr addr(s4->sin_addr);
if (AddLocal(addr, LOCAL_IF)) {
LogPrintf("%s: IPv4 %s: %s\n", __func__, ifa->ifa_name,
addr.ToString());
}
} else if (ifa->ifa_addr->sa_family == AF_INET6) {
struct sockaddr_in6 *s6 =
reinterpret_cast<struct sockaddr_in6 *>(ifa->ifa_addr);
CNetAddr addr(s6->sin6_addr);
if (AddLocal(addr, LOCAL_IF)) {
LogPrintf("%s: IPv6 %s: %s\n", __func__, ifa->ifa_name,
addr.ToString());
}
}
}
freeifaddrs(myaddrs);
}
#endif
}
void CConnman::SetNetworkActive(bool active) {
LogPrint(BCLog::NET, "SetNetworkActive: %s\n", active);
if (fNetworkActive == active) {
return;
}
fNetworkActive = active;
uiInterface.NotifyNetworkActiveChanged(fNetworkActive);
}
CConnman::CConnman(const Config &configIn, uint64_t nSeed0In, uint64_t nSeed1In)
: config(&configIn), nSeed0(nSeed0In), nSeed1(nSeed1In) {
fNetworkActive = true;
setBannedIsDirty = false;
fAddressesInitialized = false;
nLastNodeId = 0;
nSendBufferMaxSize = 0;
nReceiveFloodSize = 0;
flagInterruptMsgProc = false;
SetTryNewOutboundPeer(false);
Options connOptions;
Init(connOptions);
}
NodeId CConnman::GetNewNodeId() {
return nLastNodeId.fetch_add(1, std::memory_order_relaxed);
}
bool CConnman::Bind(const CService &addr, unsigned int flags) {
if (!(flags & BF_EXPLICIT) && IsLimited(addr)) {
return false;
}
std::string strError;
if (!BindListenPort(addr, strError, (flags & BF_WHITELIST) != 0)) {
if ((flags & BF_REPORT_ERROR) && clientInterface) {
clientInterface->ThreadSafeMessageBox(
strError, "", CClientUIInterface::MSG_ERROR);
}
return false;
}
return true;
}
bool CConnman::InitBinds(const std::vector<CService> &binds,
const std::vector<CService> &whiteBinds) {
bool fBound = false;
for (const auto &addrBind : binds) {
fBound |= Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR));
}
for (const auto &addrBind : whiteBinds) {
fBound |=
Bind(addrBind, (BF_EXPLICIT | BF_REPORT_ERROR | BF_WHITELIST));
}
if (binds.empty() && whiteBinds.empty()) {
struct in_addr inaddr_any;
inaddr_any.s_addr = INADDR_ANY;
struct in6_addr inaddr6_any = IN6ADDR_ANY_INIT;
fBound |= Bind(CService(inaddr6_any, GetListenPort()), BF_NONE);
fBound |= Bind(CService(inaddr_any, GetListenPort()),
!fBound ? BF_REPORT_ERROR : BF_NONE);
}
return fBound;
}
bool CConnman::Start(CScheduler &scheduler, const Options &connOptions) {
Init(connOptions);
nTotalBytesRecv = 0;
nTotalBytesSent = 0;
nMaxOutboundTotalBytesSentInCycle = 0;
nMaxOutboundCycleStartTime = 0;
if (fListen && !InitBinds(connOptions.vBinds, connOptions.vWhiteBinds)) {
if (clientInterface) {
clientInterface->ThreadSafeMessageBox(
_("Failed to listen on any port. Use -listen=0 if you want "
"this."),
"", CClientUIInterface::MSG_ERROR);
}
return false;
}
for (const auto &strDest : connOptions.vSeedNodes) {
AddOneShot(strDest);
}
if (clientInterface) {
clientInterface->InitMessage(_("Loading P2P addresses..."));
}
// Load addresses from peers.dat
int64_t nStart = GetTimeMillis();
{
CAddrDB adb(config->GetChainParams());
if (adb.Read(addrman)) {
LogPrintf("Loaded %i addresses from peers.dat %dms\n",
addrman.size(), GetTimeMillis() - nStart);
} else {
// Addrman can be in an inconsistent state after failure, reset it
addrman.Clear();
LogPrintf("Invalid or missing peers.dat; recreating\n");
DumpAddresses();
}
}
if (clientInterface) {
clientInterface->InitMessage(_("Loading banlist..."));
}
// Load addresses from banlist.dat
nStart = GetTimeMillis();
CBanDB bandb(config->GetChainParams());
banmap_t banmap;
if (bandb.Read(banmap)) {
// thread save setter
SetBanned(banmap);
// no need to write down, just read data
SetBannedSetDirty(false);
// sweep out unused entries
SweepBanned();
LogPrint(BCLog::NET,
"Loaded %d banned node ips/subnets from banlist.dat %dms\n",
banmap.size(), GetTimeMillis() - nStart);
} else {
LogPrintf("Invalid or missing banlist.dat; recreating\n");
// force write
SetBannedSetDirty(true);
DumpBanlist();
}
uiInterface.InitMessage(_("Starting network threads..."));
fAddressesInitialized = true;
if (semOutbound == nullptr) {
// initialize semaphore
- semOutbound = MakeUnique<CSemaphore>(
+ semOutbound = std::make_unique<CSemaphore>(
std::min((nMaxOutbound + nMaxFeeler), nMaxConnections));
}
if (semAddnode == nullptr) {
// initialize semaphore
- semAddnode = MakeUnique<CSemaphore>(nMaxAddnode);
+ semAddnode = std::make_unique<CSemaphore>(nMaxAddnode);
}
//
// Start threads
//
assert(m_msgproc);
InterruptSocks5(false);
interruptNet.reset();
flagInterruptMsgProc = false;
{
LOCK(mutexMsgProc);
fMsgProcWake = false;
}
// Send and receive from sockets, accept connections
threadSocketHandler = std::thread(
&TraceThread<std::function<void()>>, "net",
std::function<void()>(std::bind(&CConnman::ThreadSocketHandler, this)));
if (!gArgs.GetBoolArg("-dnsseed", true)) {
LogPrintf("DNS seeding disabled\n");
} else {
threadDNSAddressSeed =
std::thread(&TraceThread<std::function<void()>>, "dnsseed",
std::function<void()>(
std::bind(&CConnman::ThreadDNSAddressSeed, this)));
}
// Initiate outbound connections from -addnode
threadOpenAddedConnections =
std::thread(&TraceThread<std::function<void()>>, "addcon",
std::function<void()>(std::bind(
&CConnman::ThreadOpenAddedConnections, this)));
if (connOptions.m_use_addrman_outgoing &&
!connOptions.m_specified_outgoing.empty()) {
if (clientInterface) {
clientInterface->ThreadSafeMessageBox(
_("Cannot provide specific connections and have addrman find "
"outgoing connections at the same."),
"", CClientUIInterface::MSG_ERROR);
}
return false;
}
if (connOptions.m_use_addrman_outgoing ||
!connOptions.m_specified_outgoing.empty()) {
threadOpenConnections =
std::thread(&TraceThread<std::function<void()>>, "opencon",
std::function<void()>(
std::bind(&CConnman::ThreadOpenConnections, this,
connOptions.m_specified_outgoing)));
}
// Process messages
threadMessageHandler =
std::thread(&TraceThread<std::function<void()>>, "msghand",
std::function<void()>(
std::bind(&CConnman::ThreadMessageHandler, this)));
// Dump network addresses
scheduler.scheduleEvery(
[this]() {
this->DumpData();
return true;
},
DUMP_ADDRESSES_INTERVAL * 1000);
return true;
}
class CNetCleanup {
public:
CNetCleanup() {}
~CNetCleanup() {
#ifdef WIN32
// Shutdown Windows Sockets
WSACleanup();
#endif
}
} instance_of_cnetcleanup;
void CConnman::Interrupt() {
{
std::lock_guard<std::mutex> lock(mutexMsgProc);
flagInterruptMsgProc = true;
}
condMsgProc.notify_all();
interruptNet();
InterruptSocks5(true);
if (semOutbound) {
for (int i = 0; i < (nMaxOutbound + nMaxFeeler); i++) {
semOutbound->post();
}
}
if (semAddnode) {
for (int i = 0; i < nMaxAddnode; i++) {
semAddnode->post();
}
}
}
void CConnman::Stop() {
if (threadMessageHandler.joinable()) {
threadMessageHandler.join();
}
if (threadOpenConnections.joinable()) {
threadOpenConnections.join();
}
if (threadOpenAddedConnections.joinable()) {
threadOpenAddedConnections.join();
}
if (threadDNSAddressSeed.joinable()) {
threadDNSAddressSeed.join();
}
if (threadSocketHandler.joinable()) {
threadSocketHandler.join();
}
if (fAddressesInitialized) {
DumpData();
fAddressesInitialized = false;
}
// Close sockets
for (CNode *pnode : vNodes) {
pnode->CloseSocketDisconnect();
}
for (ListenSocket &hListenSocket : vhListenSocket) {
if (hListenSocket.socket != INVALID_SOCKET) {
if (!CloseSocket(hListenSocket.socket)) {
LogPrintf("CloseSocket(hListenSocket) failed with error %s\n",
NetworkErrorString(WSAGetLastError()));
}
}
}
// clean up some globals (to help leak detection)
for (CNode *pnode : vNodes) {
DeleteNode(pnode);
}
for (CNode *pnode : vNodesDisconnected) {
DeleteNode(pnode);
}
vNodes.clear();
vNodesDisconnected.clear();
vhListenSocket.clear();
semOutbound.reset();
semAddnode.reset();
}
void CConnman::DeleteNode(CNode *pnode) {
assert(pnode);
bool fUpdateConnectionTime = false;
m_msgproc->FinalizeNode(*config, pnode->GetId(), fUpdateConnectionTime);
if (fUpdateConnectionTime) {
addrman.Connected(pnode->addr);
}
delete pnode;
}
CConnman::~CConnman() {
Interrupt();
Stop();
}
size_t CConnman::GetAddressCount() const {
return addrman.size();
}
void CConnman::SetServices(const CService &addr, ServiceFlags nServices) {
addrman.SetServices(addr, nServices);
}
void CConnman::MarkAddressGood(const CAddress &addr) {
addrman.Good(addr);
}
void CConnman::AddNewAddresses(const std::vector<CAddress> &vAddr,
const CAddress &addrFrom, int64_t nTimePenalty) {
addrman.Add(vAddr, addrFrom, nTimePenalty);
}
std::vector<CAddress> CConnman::GetAddresses() {
return addrman.GetAddr();
}
bool CConnman::AddNode(const std::string &strNode) {
LOCK(cs_vAddedNodes);
for (const std::string &it : vAddedNodes) {
if (strNode == it) {
return false;
}
}
vAddedNodes.push_back(strNode);
return true;
}
bool CConnman::RemoveAddedNode(const std::string &strNode) {
LOCK(cs_vAddedNodes);
for (std::vector<std::string>::iterator it = vAddedNodes.begin();
it != vAddedNodes.end(); ++it) {
if (strNode == *it) {
vAddedNodes.erase(it);
return true;
}
}
return false;
}
size_t CConnman::GetNodeCount(NumConnections flags) {
LOCK(cs_vNodes);
// Shortcut if we want total
if (flags == CConnman::CONNECTIONS_ALL) {
return vNodes.size();
}
int nNum = 0;
for (const auto &pnode : vNodes) {
if (flags & (pnode->fInbound ? CONNECTIONS_IN : CONNECTIONS_OUT)) {
nNum++;
}
}
return nNum;
}
void CConnman::GetNodeStats(std::vector<CNodeStats> &vstats) {
vstats.clear();
LOCK(cs_vNodes);
vstats.reserve(vNodes.size());
for (CNode *pnode : vNodes) {
vstats.emplace_back();
pnode->copyStats(vstats.back());
}
}
bool CConnman::DisconnectNode(const std::string &strNode) {
LOCK(cs_vNodes);
if (CNode *pnode = FindNode(strNode)) {
pnode->fDisconnect = true;
return true;
}
return false;
}
bool CConnman::DisconnectNode(NodeId id) {
LOCK(cs_vNodes);
for (CNode *pnode : vNodes) {
if (id == pnode->GetId()) {
pnode->fDisconnect = true;
return true;
}
}
return false;
}
void CConnman::RecordBytesRecv(uint64_t bytes) {
LOCK(cs_totalBytesRecv);
nTotalBytesRecv += bytes;
}
void CConnman::RecordBytesSent(uint64_t bytes) {
LOCK(cs_totalBytesSent);
nTotalBytesSent += bytes;
uint64_t now = GetTime();
if (nMaxOutboundCycleStartTime + nMaxOutboundTimeframe < now) {
// timeframe expired, reset cycle
nMaxOutboundCycleStartTime = now;
nMaxOutboundTotalBytesSentInCycle = 0;
}
// TODO, exclude whitebind peers
nMaxOutboundTotalBytesSentInCycle += bytes;
}
void CConnman::SetMaxOutboundTarget(uint64_t limit) {
LOCK(cs_totalBytesSent);
nMaxOutboundLimit = limit;
}
uint64_t CConnman::GetMaxOutboundTarget() {
LOCK(cs_totalBytesSent);
return nMaxOutboundLimit;
}
uint64_t CConnman::GetMaxOutboundTimeframe() {
LOCK(cs_totalBytesSent);
return nMaxOutboundTimeframe;
}
uint64_t CConnman::GetMaxOutboundTimeLeftInCycle() {
LOCK(cs_totalBytesSent);
if (nMaxOutboundLimit == 0) {
return 0;
}
if (nMaxOutboundCycleStartTime == 0) {
return nMaxOutboundTimeframe;
}
uint64_t cycleEndTime = nMaxOutboundCycleStartTime + nMaxOutboundTimeframe;
uint64_t now = GetTime();
return (cycleEndTime < now) ? 0 : cycleEndTime - GetTime();
}
void CConnman::SetMaxOutboundTimeframe(uint64_t timeframe) {
LOCK(cs_totalBytesSent);
if (nMaxOutboundTimeframe != timeframe) {
// reset measure-cycle in case of changing the timeframe.
nMaxOutboundCycleStartTime = GetTime();
}
nMaxOutboundTimeframe = timeframe;
}
bool CConnman::OutboundTargetReached(bool historicalBlockServingLimit) {
LOCK(cs_totalBytesSent);
if (nMaxOutboundLimit == 0) {
return false;
}
if (historicalBlockServingLimit) {
// keep a large enough buffer to at least relay each block once.
uint64_t timeLeftInCycle = GetMaxOutboundTimeLeftInCycle();
uint64_t buffer = timeLeftInCycle / 600 * ONE_MEGABYTE;
if (buffer >= nMaxOutboundLimit ||
nMaxOutboundTotalBytesSentInCycle >= nMaxOutboundLimit - buffer) {
return true;
}
} else if (nMaxOutboundTotalBytesSentInCycle >= nMaxOutboundLimit) {
return true;
}
return false;
}
uint64_t CConnman::GetOutboundTargetBytesLeft() {
LOCK(cs_totalBytesSent);
if (nMaxOutboundLimit == 0) {
return 0;
}
return (nMaxOutboundTotalBytesSentInCycle >= nMaxOutboundLimit)
? 0
: nMaxOutboundLimit - nMaxOutboundTotalBytesSentInCycle;
}
uint64_t CConnman::GetTotalBytesRecv() {
LOCK(cs_totalBytesRecv);
return nTotalBytesRecv;
}
uint64_t CConnman::GetTotalBytesSent() {
LOCK(cs_totalBytesSent);
return nTotalBytesSent;
}
ServiceFlags CConnman::GetLocalServices() const {
return nLocalServices;
}
void CConnman::SetBestHeight(int height) {
nBestHeight.store(height, std::memory_order_release);
}
int CConnman::GetBestHeight() const {
return nBestHeight.load(std::memory_order_acquire);
}
unsigned int CConnman::GetReceiveFloodSize() const {
return nReceiveFloodSize;
}
CNode::CNode(NodeId idIn, ServiceFlags nLocalServicesIn,
int nMyStartingHeightIn, SOCKET hSocketIn, const CAddress &addrIn,
uint64_t nKeyedNetGroupIn, uint64_t nLocalHostNonceIn,
const CAddress &addrBindIn, const std::string &addrNameIn,
bool fInboundIn)
: nTimeConnected(GetSystemTimeInSeconds()), addr(addrIn),
addrBind(addrBindIn), fInbound(fInboundIn),
nKeyedNetGroup(nKeyedNetGroupIn), addrKnown(5000, 0.001),
filterInventoryKnown(50000, 0.000001), id(idIn),
nLocalHostNonce(nLocalHostNonceIn), nLocalServices(nLocalServicesIn),
nMyStartingHeight(nMyStartingHeightIn), nSendVersion(0) {
nServices = NODE_NONE;
hSocket = hSocketIn;
nRecvVersion = INIT_PROTO_VERSION;
nLastSend = 0;
nLastRecv = 0;
nSendBytes = 0;
nRecvBytes = 0;
nTimeOffset = 0;
addrName = addrNameIn == "" ? addr.ToStringIPPort() : addrNameIn;
nVersion = 0;
strSubVer = "";
fWhitelisted = false;
fOneShot = false;
m_manual_connection = false;
// set by version message
fClient = false;
// set by version message
m_limited_node = false;
fFeeler = false;
fSuccessfullyConnected = false;
fDisconnect = false;
nRefCount = 0;
nSendSize = 0;
nSendOffset = 0;
hashContinue = uint256();
nStartingHeight = -1;
filterInventoryKnown.reset();
fSendMempool = false;
fGetAddr = false;
nNextLocalAddrSend = 0;
nNextAddrSend = 0;
nNextInvSend = 0;
fRelayTxes = false;
fSentAddr = false;
- pfilter = MakeUnique<CBloomFilter>();
+ pfilter = std::make_unique<CBloomFilter>();
timeLastMempoolReq = 0;
nLastBlockTime = 0;
nLastTXTime = 0;
nPingNonceSent = 0;
nPingUsecStart = 0;
nPingUsecTime = 0;
fPingQueued = false;
nMinPingUsecTime = std::numeric_limits<int64_t>::max();
minFeeFilter = Amount::zero();
lastSentFeeFilter = Amount::zero();
nextSendTimeFeeFilter = 0;
fPauseRecv = false;
fPauseSend = false;
nProcessQueueSize = 0;
for (const std::string &msg : getAllNetMessageTypes()) {
mapRecvBytesPerMsgCmd[msg] = 0;
}
mapRecvBytesPerMsgCmd[NET_MESSAGE_COMMAND_OTHER] = 0;
if (fLogIPs) {
LogPrint(BCLog::NET, "Added connection to %s peer=%d\n", addrName, id);
} else {
LogPrint(BCLog::NET, "Added connection peer=%d\n", id);
}
}
CNode::~CNode() {
CloseSocket(hSocket);
}
void CNode::AskFor(const CInv &inv) {
if (mapAskFor.size() > MAPASKFOR_MAX_SZ ||
setAskFor.size() > SETASKFOR_MAX_SZ) {
return;
}
// a peer may not have multiple non-responded queue positions for a single
// inv item.
if (!setAskFor.insert(inv.hash).second) {
return;
}
// We're using mapAskFor as a priority queue, the key is the earliest time
// the request can be sent.
int64_t nRequestTime;
limitedmap<uint256, int64_t>::const_iterator it =
mapAlreadyAskedFor.find(inv.hash);
if (it != mapAlreadyAskedFor.end()) {
nRequestTime = it->second;
} else {
nRequestTime = 0;
}
LogPrint(BCLog::NET, "askfor %s %d (%s) peer=%d\n", inv.ToString(),
nRequestTime, FormatISO8601DateTime(nRequestTime / 1000000), id);
// Make sure not to reuse time indexes to keep things in the same order
int64_t nNow = GetTimeMicros() - 1000000;
static int64_t nLastTime;
++nLastTime;
nNow = std::max(nNow, nLastTime);
nLastTime = nNow;
// Each retry is 2 minutes after the last
nRequestTime = std::max(nRequestTime + 2 * 60 * 1000000, nNow);
if (it != mapAlreadyAskedFor.end()) {
mapAlreadyAskedFor.update(it, nRequestTime);
} else {
mapAlreadyAskedFor.insert(std::make_pair(inv.hash, nRequestTime));
}
mapAskFor.insert(std::make_pair(nRequestTime, inv));
}
bool CConnman::NodeFullyConnected(const CNode *pnode) {
return pnode && pnode->fSuccessfullyConnected && !pnode->fDisconnect;
}
void CConnman::PushMessage(CNode *pnode, CSerializedNetMsg &&msg) {
size_t nMessageSize = msg.data.size();
size_t nTotalSize = nMessageSize + CMessageHeader::HEADER_SIZE;
LogPrint(BCLog::NET, "sending %s (%d bytes) peer=%d\n",
SanitizeString(msg.command.c_str()), nMessageSize, pnode->GetId());
std::vector<uint8_t> serializedHeader;
serializedHeader.reserve(CMessageHeader::HEADER_SIZE);
uint256 hash = Hash(msg.data.data(), msg.data.data() + nMessageSize);
CMessageHeader hdr(config->GetChainParams().NetMagic(), msg.command.c_str(),
nMessageSize);
memcpy(hdr.pchChecksum, hash.begin(), CMessageHeader::CHECKSUM_SIZE);
CVectorWriter{SER_NETWORK, INIT_PROTO_VERSION, serializedHeader, 0, hdr};
size_t nBytesSent = 0;
{
LOCK(pnode->cs_vSend);
bool optimisticSend(pnode->vSendMsg.empty());
// log total amount of bytes per command
pnode->mapSendBytesPerMsgCmd[msg.command] += nTotalSize;
pnode->nSendSize += nTotalSize;
if (pnode->nSendSize > nSendBufferMaxSize) {
pnode->fPauseSend = true;
}
pnode->vSendMsg.push_back(std::move(serializedHeader));
if (nMessageSize) {
pnode->vSendMsg.push_back(std::move(msg.data));
}
// If write queue empty, attempt "optimistic write"
if (optimisticSend == true) {
nBytesSent = SocketSendData(pnode);
}
}
if (nBytesSent) {
RecordBytesSent(nBytesSent);
}
}
bool CConnman::ForNode(NodeId id, std::function<bool(CNode *pnode)> func) {
CNode *found = nullptr;
LOCK(cs_vNodes);
for (auto &&pnode : vNodes) {
if (pnode->GetId() == id) {
found = pnode;
break;
}
}
return found != nullptr && NodeFullyConnected(found) && func(found);
}
int64_t PoissonNextSend(int64_t nNow, int average_interval_seconds) {
return nNow + int64_t(log1p(GetRand(1ULL << 48) *
-0.0000000000000035527136788 /* -1/2^48 */) *
average_interval_seconds * -1000000.0 +
0.5);
}
CSipHasher CConnman::GetDeterministicRandomizer(uint64_t id) const {
return CSipHasher(nSeed0, nSeed1).Write(id);
}
uint64_t CConnman::CalculateKeyedNetGroup(const CAddress &ad) const {
std::vector<uint8_t> vchNetGroup(ad.GetGroup());
return GetDeterministicRandomizer(RANDOMIZER_ID_NETGROUP)
.Write(vchNetGroup.data(), vchNetGroup.size())
.Finalize();
}
/**
* This function convert MaxBlockSize from byte to
* MB with a decimal precision one digit rounded down
* E.g.
* 1660000 -> 1.6
* 2010000 -> 2.0
* 1000000 -> 1.0
* 230000 -> 0.2
* 50000 -> 0.0
*
* NB behavior for EB<1MB not standardized yet still
* the function applies the same algo used for
* EB greater or equal to 1MB
*/
std::string getSubVersionEB(uint64_t MaxBlockSize) {
// Prepare EB string we are going to add to SubVer:
// 1) translate from byte to MB and convert to string
// 2) limit the EB string to the first decimal digit (floored)
std::stringstream ebMBs;
ebMBs << (MaxBlockSize / (ONE_MEGABYTE / 10));
std::string eb = ebMBs.str();
eb.insert(eb.size() - 1, ".", 1);
if (eb.substr(0, 1) == ".") {
eb = "0" + eb;
}
return eb;
}
std::string userAgent(const Config &config) {
// format excessive blocksize value
std::string eb = getSubVersionEB(config.GetMaxBlockSize());
std::vector<std::string> uacomments;
uacomments.push_back("EB" + eb);
// Comments are checked for char compliance at startup, it is safe to add
// them to the user agent string
for (const std::string &cmt : gArgs.GetArgs("-uacomment")) {
uacomments.push_back(cmt);
}
// Size compliance is checked at startup, it is safe to not check it again
std::string subversion =
FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, uacomments);
return subversion;
}
diff --git a/src/radix.h b/src/radix.h
index a4b68d6de..c93b358b5 100644
--- a/src/radix.h
+++ b/src/radix.h
@@ -1,286 +1,286 @@
// Copyright (c) 2019 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#ifndef BITCOIN_RADIX_H
#define BITCOIN_RADIX_H
#include <rcu.h>
#include <util.h>
#include <boost/noncopyable.hpp>
#include <array>
#include <atomic>
#include <cstdint>
#include <memory>
#include <type_traits>
/**
* This is a radix tree storing values identified by a unique key.
*
* The tree is composed of nodes (RadixNode) containing an array of
* RadixElement. The key is split into chunks of a few bits that serve as an
* index into that array. RadixElement is a discriminated union of either a
* RadixNode* representing the next level in the tree, or a T* representing a
* leaf. New RadixNode are added lazily when two leaves would go in the same
* slot.
*
* Reads walk the tree using sequential atomic loads, and insertions are done
* using CAS, which ensures both can be executed lock free. Removing any
* elements from the tree can also be done using CAS, but requires waiting for
* other readers before being destroyed. The tree uses RCU to track which thread
* is reading the tree, which allows deletion to wait for other readers to be up
* to speed before destroying anything. It is therefore crucial that the lock be
* taken before reading anything in the tree.
*
* It is not possible to delete anything from the tree at this time. The tree
* itself cannot be destroyed and will leak memory instead of cleaning up after
* itself. This obviously needs to be fixed in subsequent revisions.
*/
template <typename T> struct RadixTree : public boost::noncopyable {
private:
static const int BITS = 4;
static const int MASK = (1 << BITS) - 1;
static const size_t CHILD_PER_LEVEL = 1 << BITS;
typedef typename std::remove_reference<decltype(
std::declval<T &>().getId())>::type K;
static const size_t KEY_BITS = 8 * sizeof(K);
static const uint32_t TOP_LEVEL = (KEY_BITS - 1) / BITS;
struct RadixElement;
struct RadixNode;
std::atomic<RadixElement> root;
public:
RadixTree() : root(RadixElement()) {}
~RadixTree() { root.load().release(); }
/**
* Insert a value into the tree.
* Returns true if the value was inserted, false if it was already present.
*/
bool insert(const RCUPtr<T> &value) {
return insert(value->getId(), value);
}
/**
* Get the value corresponding to a key.
* Returns the value if found, nullptr if not.
*/
RCUPtr<T> get(const K &key) {
uint32_t level = TOP_LEVEL;
RCULock lock;
RadixElement e = root.load();
// Find a leaf.
while (e.isNode()) {
e = e.getNode()->get(level--, key)->load();
}
T *leaf = e.getLeaf();
if (leaf == nullptr || leaf->getId() != key) {
// We failed to find the proper element.
return RCUPtr<T>();
}
// The leaf is non-null and the keys match. We have our guy.
return RCUPtr<T>::copy(leaf);
}
RCUPtr<const T> get(const K &key) const {
T const *ptr = const_cast<RadixTree *>(this)->get(key).release();
return RCUPtr<const T>::acquire(ptr);
}
/**
* Remove an element from the tree.
* Returns the removed element, or nullptr if there isn't one.
*/
RCUPtr<T> remove(const K &key) {
uint32_t level = TOP_LEVEL;
RCULock lock;
std::atomic<RadixElement> *eptr = &root;
RadixElement e = eptr->load();
// Walk down the tree until we find a leaf for our node.
while (e.isNode()) {
Node:
eptr = e.getNode()->get(level--, key);
e = eptr->load();
}
T *leaf = e.getLeaf();
if (leaf == nullptr || leaf->getId() != key) {
// We failed to find the proper element.
return RCUPtr<T>();
}
// We have the proper element, try to delete it.
if (eptr->compare_exchange_strong(e, RadixElement())) {
return RCUPtr<T>::acquire(leaf);
}
// The element was replaced, either by a subtree or another element.
if (e.isNode()) {
goto Node;
}
// The element in the slot is not the one we are looking for.
return RCUPtr<T>();
}
private:
bool insert(const K &key, RCUPtr<T> value) {
uint32_t level = TOP_LEVEL;
RCULock lock;
std::atomic<RadixElement> *eptr = &root;
while (true) {
RadixElement e = eptr->load();
// Walk down the tree until we find a leaf for our node.
while (e.isNode()) {
Node:
eptr = e.getNode()->get(level--, key);
e = eptr->load();
}
// If the slot is empty, try to insert right there.
if (e.getLeaf() == nullptr) {
if (eptr->compare_exchange_strong(e,
RadixElement(value.get()))) {
value.release();
return true;
}
// CAS failed, we may have a node in there now.
if (e.isNode()) {
goto Node;
}
}
// The element was already in the tree.
const K &leafKey = e.getLeaf()->getId();
if (key == leafKey) {
return false;
}
// There is an element there, but it isn't a subtree. We need to
// convert it into a subtree and resume insertion into that subtree.
std::unique_ptr<RadixNode> newChild =
- MakeUnique<RadixNode>(level, leafKey, e);
+ std::make_unique<RadixNode>(level, leafKey, e);
if (eptr->compare_exchange_strong(e,
RadixElement(newChild.get()))) {
// We have a subtree, resume normal operations from there.
newChild.release();
} else {
// We failed to insert our subtree, clean it before it is freed.
newChild->get(level, leafKey)->store(RadixElement());
}
}
}
struct RadixElement {
private:
union {
RadixNode *node;
T *leaf;
uintptr_t raw;
};
static const uintptr_t DISCRIMINANT = 0x01;
bool getDiscriminant() const { return (raw & DISCRIMINANT) != 0; }
public:
explicit RadixElement() noexcept : raw(DISCRIMINANT) {}
explicit RadixElement(RadixNode *nodeIn) noexcept : node(nodeIn) {}
explicit RadixElement(T *leafIn) noexcept : leaf(leafIn) {
raw |= DISCRIMINANT;
}
/**
* RadixElement is designed to be a dumb wrapper. This allows any
* container to release what is held by the RadixElement.
*/
void release() {
if (isNode()) {
RadixNode *ptr = getNode();
RCUPtr<RadixNode>::acquire(ptr);
} else {
T *ptr = getLeaf();
RCUPtr<T>::acquire(ptr);
}
}
/**
* Node features.
*/
bool isNode() const { return !getDiscriminant(); }
RadixNode *getNode() {
assert(isNode());
return node;
}
const RadixNode *getNode() const {
assert(isNode());
return node;
}
/**
* Leaf features.
*/
bool isLeaf() const { return getDiscriminant(); }
T *getLeaf() {
assert(isLeaf());
return reinterpret_cast<T *>(raw & ~DISCRIMINANT);
}
const T *getLeaf() const {
assert(isLeaf());
return const_cast<RadixElement *>(this)->getLeaf();
}
};
struct RadixNode : public boost::noncopyable {
IMPLEMENT_RCU_REFCOUNT(uint64_t);
private:
union {
std::array<std::atomic<RadixElement>, CHILD_PER_LEVEL> childs;
std::array<RadixElement, CHILD_PER_LEVEL>
non_atomic_childs_DO_NOT_USE;
};
public:
RadixNode(uint32_t level, const K &key, RadixElement e)
: non_atomic_childs_DO_NOT_USE() {
get(level, key)->store(e);
}
~RadixNode() {
for (RadixElement e : non_atomic_childs_DO_NOT_USE) {
e.release();
}
}
std::atomic<RadixElement> *get(uint32_t level, const K &key) {
return &childs[(key >> (level * BITS)) & MASK];
}
};
// Make sure the alignment works for T and RadixElement.
static_assert(alignof(T) > 1, "T's alignment must be 2 or more.");
static_assert(alignof(RadixNode) > 1,
"RadixNode alignment must be 2 or more.");
};
#endif // BITCOIN_RADIX_H
diff --git a/src/test/dbwrapper_tests.cpp b/src/test/dbwrapper_tests.cpp
index 5cfa8fe79..b408de08d 100644
--- a/src/test/dbwrapper_tests.cpp
+++ b/src/test/dbwrapper_tests.cpp
@@ -1,311 +1,311 @@
// Copyright (c) 2012-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 "dbwrapper.h"
#include "random.h"
#include "test/test_bitcoin.h"
#include "uint256.h"
#include <boost/test/unit_test.hpp>
// Test if a string consists entirely of null characters
bool is_null_key(const std::vector<uint8_t> &key) {
bool isnull = true;
for (unsigned int i = 0; i < key.size(); i++)
isnull &= (key[i] == '\x00');
return isnull;
}
BOOST_FIXTURE_TEST_SUITE(dbwrapper_tests, BasicTestingSetup)
BOOST_AUTO_TEST_CASE(dbwrapper) {
// Perform tests both obfuscated and non-obfuscated.
for (bool obfuscate : {false, true}) {
fs::path ph = fs::temp_directory_path() / fs::unique_path();
CDBWrapper dbw(ph, (1 << 20), true, false, obfuscate);
char key = 'k';
uint256 in = InsecureRand256();
uint256 res;
// Ensure that we're doing real obfuscation when obfuscate=true
BOOST_CHECK(obfuscate !=
is_null_key(dbwrapper_private::GetObfuscateKey(dbw)));
BOOST_CHECK(dbw.Write(key, in));
BOOST_CHECK(dbw.Read(key, res));
BOOST_CHECK_EQUAL(res.ToString(), in.ToString());
}
}
// Test batch operations
BOOST_AUTO_TEST_CASE(dbwrapper_batch) {
// Perform tests both obfuscated and non-obfuscated.
for (bool obfuscate : {false, true}) {
fs::path ph = fs::temp_directory_path() / fs::unique_path();
CDBWrapper dbw(ph, (1 << 20), true, false, obfuscate);
char key = 'i';
uint256 in = InsecureRand256();
char key2 = 'j';
uint256 in2 = InsecureRand256();
char key3 = 'k';
uint256 in3 = InsecureRand256();
uint256 res;
CDBBatch batch(dbw);
batch.Write(key, in);
batch.Write(key2, in2);
batch.Write(key3, in3);
// Remove key3 before it's even been written
batch.Erase(key3);
dbw.WriteBatch(batch);
BOOST_CHECK(dbw.Read(key, res));
BOOST_CHECK_EQUAL(res.ToString(), in.ToString());
BOOST_CHECK(dbw.Read(key2, res));
BOOST_CHECK_EQUAL(res.ToString(), in2.ToString());
// key3 should've never been written
BOOST_CHECK(dbw.Read(key3, res) == false);
}
}
BOOST_AUTO_TEST_CASE(dbwrapper_iterator) {
// Perform tests both obfuscated and non-obfuscated.
for (bool obfuscate : {false, true}) {
fs::path ph = fs::temp_directory_path() / fs::unique_path();
CDBWrapper dbw(ph, (1 << 20), true, false, obfuscate);
// The two keys are intentionally chosen for ordering
char key = 'j';
uint256 in = InsecureRand256();
BOOST_CHECK(dbw.Write(key, in));
char key2 = 'k';
uint256 in2 = InsecureRand256();
BOOST_CHECK(dbw.Write(key2, in2));
std::unique_ptr<CDBIterator> it(
const_cast<CDBWrapper &>(dbw).NewIterator());
// Be sure to seek past the obfuscation key (if it exists)
it->Seek(key);
char key_res;
uint256 val_res;
it->GetKey(key_res);
it->GetValue(val_res);
BOOST_CHECK_EQUAL(key_res, key);
BOOST_CHECK_EQUAL(val_res.ToString(), in.ToString());
it->Next();
it->GetKey(key_res);
it->GetValue(val_res);
BOOST_CHECK_EQUAL(key_res, key2);
BOOST_CHECK_EQUAL(val_res.ToString(), in2.ToString());
it->Next();
BOOST_CHECK_EQUAL(it->Valid(), false);
}
}
// Test that we do not obfuscation if there is existing data.
BOOST_AUTO_TEST_CASE(existing_data_no_obfuscate) {
// We're going to share this fs::path between two wrappers
fs::path ph = fs::temp_directory_path() / fs::unique_path();
create_directories(ph);
// Set up a non-obfuscated wrapper to write some initial data.
std::unique_ptr<CDBWrapper> dbw =
- MakeUnique<CDBWrapper>(ph, (1 << 10), false, false, false);
+ std::make_unique<CDBWrapper>(ph, (1 << 10), false, false, false);
char key = 'k';
uint256 in = InsecureRand256();
uint256 res;
BOOST_CHECK(dbw->Write(key, in));
BOOST_CHECK(dbw->Read(key, res));
BOOST_CHECK_EQUAL(res.ToString(), in.ToString());
// Call the destructor to free leveldb LOCK
dbw.reset();
// Now, set up another wrapper that wants to obfuscate the same directory
CDBWrapper odbw(ph, (1 << 10), false, false, true);
// Check that the key/val we wrote with unobfuscated wrapper exists and
// is readable.
uint256 res2;
BOOST_CHECK(odbw.Read(key, res2));
BOOST_CHECK_EQUAL(res2.ToString(), in.ToString());
// There should be existing data
BOOST_CHECK(!odbw.IsEmpty());
// The key should be an empty string
BOOST_CHECK(is_null_key(dbwrapper_private::GetObfuscateKey(odbw)));
uint256 in2 = InsecureRand256();
uint256 res3;
// Check that we can write successfully
BOOST_CHECK(odbw.Write(key, in2));
BOOST_CHECK(odbw.Read(key, res3));
BOOST_CHECK_EQUAL(res3.ToString(), in2.ToString());
}
// Ensure that we start obfuscating during a reindex.
BOOST_AUTO_TEST_CASE(existing_data_reindex) {
// We're going to share this fs::path between two wrappers
fs::path ph = fs::temp_directory_path() / fs::unique_path();
create_directories(ph);
// Set up a non-obfuscated wrapper to write some initial data.
std::unique_ptr<CDBWrapper> dbw =
- MakeUnique<CDBWrapper>(ph, (1 << 10), false, false, false);
+ std::make_unique<CDBWrapper>(ph, (1 << 10), false, false, false);
char key = 'k';
uint256 in = InsecureRand256();
uint256 res;
BOOST_CHECK(dbw->Write(key, in));
BOOST_CHECK(dbw->Read(key, res));
BOOST_CHECK_EQUAL(res.ToString(), in.ToString());
// Call the destructor to free leveldb LOCK
dbw.reset();
// Simulate a -reindex by wiping the existing data store
CDBWrapper odbw(ph, (1 << 10), false, true, true);
// Check that the key/val we wrote with unobfuscated wrapper doesn't exist
uint256 res2;
BOOST_CHECK(!odbw.Read(key, res2));
BOOST_CHECK(!is_null_key(dbwrapper_private::GetObfuscateKey(odbw)));
uint256 in2 = InsecureRand256();
uint256 res3;
// Check that we can write successfully
BOOST_CHECK(odbw.Write(key, in2));
BOOST_CHECK(odbw.Read(key, res3));
BOOST_CHECK_EQUAL(res3.ToString(), in2.ToString());
}
BOOST_AUTO_TEST_CASE(iterator_ordering) {
fs::path ph = fs::temp_directory_path() / fs::unique_path();
CDBWrapper dbw(ph, (1 << 20), true, false, false);
for (int x = 0x00; x < 256; ++x) {
uint8_t key = x;
uint32_t value = x * x;
BOOST_CHECK(dbw.Write(key, value));
}
std::unique_ptr<CDBIterator> it(
const_cast<CDBWrapper &>(dbw).NewIterator());
for (int seek_start : {0x00, 0x80}) {
it->Seek((uint8_t)seek_start);
for (int x = seek_start; x < 256; ++x) {
uint8_t key;
uint32_t value;
BOOST_CHECK(it->Valid());
// Avoid spurious errors about invalid iterator's key and value in
// case of failure
if (!it->Valid()) break;
BOOST_CHECK(it->GetKey(key));
BOOST_CHECK(it->GetValue(value));
BOOST_CHECK_EQUAL(key, x);
BOOST_CHECK_EQUAL(value, x * x);
it->Next();
}
BOOST_CHECK(!it->Valid());
}
}
struct StringContentsSerializer {
// Used to make two serialized objects the same while letting them have a
// different lengths. This is a terrible idea.
std::string str;
StringContentsSerializer() {}
explicit StringContentsSerializer(const std::string &inp) : str(inp) {}
StringContentsSerializer &operator+=(const std::string &s) {
str += s;
return *this;
}
StringContentsSerializer &operator+=(const StringContentsSerializer &s) {
return *this += s.str;
}
ADD_SERIALIZE_METHODS;
template <typename Stream, typename Operation>
inline void SerializationOp(Stream &s, Operation ser_action) {
if (ser_action.ForRead()) {
str.clear();
char c = 0;
while (true) {
try {
READWRITE(c);
str.push_back(c);
} catch (const std::ios_base::failure &e) {
break;
}
}
} else {
for (size_t i = 0; i < str.size(); i++)
READWRITE(str[i]);
}
}
};
BOOST_AUTO_TEST_CASE(iterator_string_ordering) {
char buf[10];
fs::path ph = fs::temp_directory_path() / fs::unique_path();
CDBWrapper dbw(ph, (1 << 20), true, false, false);
for (int x = 0x00; x < 10; ++x) {
for (int y = 0; y < 10; y++) {
snprintf(buf, sizeof(buf), "%d", x);
StringContentsSerializer key(buf);
for (int z = 0; z < y; z++)
key += key;
uint32_t value = x * x;
BOOST_CHECK(dbw.Write(key, value));
}
}
std::unique_ptr<CDBIterator> it(
const_cast<CDBWrapper &>(dbw).NewIterator());
for (int seek_start : {0, 5}) {
snprintf(buf, sizeof(buf), "%d", seek_start);
StringContentsSerializer seek_key(buf);
it->Seek(seek_key);
for (int x = seek_start; x < 10; ++x) {
for (int y = 0; y < 10; y++) {
snprintf(buf, sizeof(buf), "%d", x);
std::string exp_key(buf);
for (int z = 0; z < y; z++)
exp_key += exp_key;
StringContentsSerializer key;
uint32_t value;
BOOST_CHECK(it->Valid());
// Avoid spurious errors about invalid iterator's key and value
// in case of failure
if (!it->Valid()) break;
BOOST_CHECK(it->GetKey(key));
BOOST_CHECK(it->GetValue(value));
BOOST_CHECK_EQUAL(key.str, exp_key);
BOOST_CHECK_EQUAL(value, x * x);
it->Next();
}
}
BOOST_CHECK(!it->Valid());
}
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/rpc_server_tests.cpp b/src/test/rpc_server_tests.cpp
index f202a6221..5c2a76d22 100644
--- a/src/test/rpc_server_tests.cpp
+++ b/src/test/rpc_server_tests.cpp
@@ -1,89 +1,90 @@
// Copyright (c) 2018-2019 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include "chainparams.h"
#include "config.h"
#include "rpc/jsonrpcrequest.h"
#include "rpc/server.h"
#include "util.h"
#include "test/test_bitcoin.h"
#include <boost/test/unit_test.hpp>
#include <string>
BOOST_FIXTURE_TEST_SUITE(rpc_server_tests, TestingSetup)
class ArgsTestRPCCommand : public RPCCommandWithArgsContext {
public:
ArgsTestRPCCommand(const std::string &nameIn)
: RPCCommandWithArgsContext(nameIn) {}
UniValue Execute(const UniValue &args) const override {
BOOST_CHECK_EQUAL(args["arg1"].get_str(), "value1");
return UniValue("testing1");
}
};
static bool isRpcMethodNotFound(const UniValue &u) {
return find_value(u, "code").get_int() == int(RPC_METHOD_NOT_FOUND);
}
BOOST_AUTO_TEST_CASE(rpc_server_execute_command) {
DummyConfig config;
RPCServer rpcServer;
const std::string commandName = "testcommand1";
- rpcServer.RegisterCommand(MakeUnique<ArgsTestRPCCommand>(commandName));
+ rpcServer.RegisterCommand(
+ std::make_unique<ArgsTestRPCCommand>(commandName));
UniValue args(UniValue::VOBJ);
args.pushKV("arg1", "value1");
// Registered commands execute and return values correctly
JSONRPCRequest request;
request.strMethod = commandName;
request.params = args;
UniValue output = rpcServer.ExecuteCommand(config, request);
BOOST_CHECK_EQUAL(output.get_str(), "testing1");
// Not-registered commands throw an exception as expected
JSONRPCRequest badCommandRequest;
badCommandRequest.strMethod = "this-command-does-not-exist";
BOOST_CHECK_EXCEPTION(rpcServer.ExecuteCommand(config, badCommandRequest),
UniValue, isRpcMethodNotFound);
}
class RequestContextRPCCommand : public RPCCommand {
public:
RequestContextRPCCommand(const std::string &nameIn) : RPCCommand(nameIn) {}
// Sanity check that Execute(JSONRPCRequest) is called correctly from
// RPCServer
UniValue Execute(const JSONRPCRequest &request) const override {
const UniValue args = request.params;
BOOST_CHECK_EQUAL(request.strMethod, "testcommand2");
BOOST_CHECK_EQUAL(args["arg2"].get_str(), "value2");
return UniValue("testing2");
}
};
BOOST_AUTO_TEST_CASE(rpc_server_execute_command_from_request_context) {
DummyConfig config;
RPCServer rpcServer;
const std::string commandName = "testcommand2";
rpcServer.RegisterCommand(
- MakeUnique<RequestContextRPCCommand>(commandName));
+ std::make_unique<RequestContextRPCCommand>(commandName));
UniValue args(UniValue::VOBJ);
args.pushKV("arg2", "value2");
// Registered commands execute and return values correctly
JSONRPCRequest request;
request.strMethod = commandName;
request.params = args;
UniValue output = rpcServer.ExecuteCommand(config, request);
BOOST_CHECK_EQUAL(output.get_str(), "testing2");
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/util.h b/src/util.h
index 35f81da4b..6352e8a21 100644
--- a/src/util.h
+++ b/src/util.h
@@ -1,281 +1,275 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-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.
/**
* Server/client environment: argument handling, config file parsing,
* thread wrappers, startup time
*/
#ifndef BITCOIN_UTIL_H
#define BITCOIN_UTIL_H
#if defined(HAVE_CONFIG_H)
#include "config/bitcoin-config.h"
#endif
#include "compat.h"
#include "fs.h"
#include "logging.h"
#include "sync.h"
#include "tinyformat.h"
#include "utiltime.h"
#include <atomic>
#include <cstdint>
#include <exception>
#include <map>
#include <set>
#include <string>
#include <unordered_set>
#include <vector>
#include <boost/signals2/signal.hpp>
#include <boost/thread/condition_variable.hpp> // for boost::thread_interrupted
// Application startup time (used for uptime calculation)
int64_t GetStartupTime();
/** Signals for translation. */
class CTranslationInterface {
public:
/** Translate a message to the native language of the user. */
boost::signals2::signal<std::string(const char *psz)> Translate;
};
extern CTranslationInterface translationInterface;
extern const char *const BITCOIN_CONF_FILENAME;
extern const char *const BITCOIN_PID_FILENAME;
/**
* Translation function: Call Translate signal on UI interface, which returns a
* boost::optional result. If no translation slot is registered, nothing is
* returned, and simply return the input.
*/
inline std::string _(const char *psz) {
boost::optional<std::string> rv = translationInterface.Translate(psz);
return rv ? (*rv) : psz;
}
void SetupEnvironment();
bool SetupNetworking();
template <typename... Args> bool error(const char *fmt, const Args &... args) {
LogPrintf("ERROR: " + tfm::format(fmt, args...) + "\n");
return false;
}
void PrintExceptionContinue(const std::exception *pex, const char *pszThread);
void FileCommit(FILE *file);
bool TruncateFile(FILE *file, unsigned int length);
int RaiseFileDescriptorLimit(int nMinFD);
void AllocateFileRange(FILE *file, unsigned int offset, unsigned int length);
bool RenameOver(fs::path src, fs::path dest);
bool TryCreateDirectories(const fs::path &p);
fs::path GetDefaultDataDir();
const fs::path &GetDataDir(bool fNetSpecific = true);
void ClearDatadirCache();
fs::path GetConfigFile(const std::string &confPath);
#ifndef WIN32
fs::path GetPidFile();
void CreatePidFile(const fs::path &path, pid_t pid);
#endif
#ifdef WIN32
fs::path GetSpecialFolderPath(int nFolder, bool fCreate = true);
#endif
void runCommand(const std::string &strCommand);
inline bool IsSwitchChar(char c) {
#ifdef WIN32
return c == '-' || c == '/';
#else
return c == '-';
#endif
}
class ArgsManager {
protected:
friend class ArgsManagerHelper;
mutable CCriticalSection cs_args;
std::map<std::string, std::vector<std::string>> m_override_args;
std::map<std::string, std::vector<std::string>> m_config_args;
std::string m_network;
std::set<std::string> m_network_only_args;
void ReadConfigStream(std::istream &stream);
public:
ArgsManager();
/**
* Select the network in use
*/
void SelectConfigNetwork(const std::string &network);
void ParseParameters(int argc, const char *const argv[]);
void ReadConfigFile(const std::string &confPath);
/**
* Log warnings for options in m_section_only_args when they are specified
* in the default section but not overridden on the command line or in a
* network-specific section in the config file.
*/
void WarnForSectionOnlyArgs();
/**
* Return a vector of strings of the given argument
*
* @param strArg Argument to get (e.g. "-foo")
* @return command-line arguments
*/
std::vector<std::string> GetArgs(const std::string &strArg) const;
/**
* Return true if the given argument has been manually set.
*
* @param strArg Argument to get (e.g. "-foo")
* @return true if the argument has been set
*/
bool IsArgSet(const std::string &strArg) const;
/**
* Return true if the argument was originally passed as a negated option,
* i.e. -nofoo.
*
* @param strArg Argument to get (e.g. "-foo")
* @return true if the argument was passed negated
*/
bool IsArgNegated(const std::string &strArg) const;
/**
* Return string argument or default value.
*
* @param strArg Argument to get (e.g. "-foo")
* @param default (e.g. "1")
* @return command-line argument or default value
*/
std::string GetArg(const std::string &strArg,
const std::string &strDefault) const;
/**
* Return integer argument or default value.
*
* @param strArg Argument to get (e.g. "-foo")
* @param default (e.g. 1)
* @return command-line argument (0 if invalid number) or default value
*/
int64_t GetArg(const std::string &strArg, int64_t nDefault) const;
/**
* Return boolean argument or default value.
*
* @param strArg Argument to get (e.g. "-foo")
* @param default (true or false)
* @return command-line argument or default value
*/
bool GetBoolArg(const std::string &strArg, bool fDefault) const;
/**
* Set an argument if it doesn't already have a value.
*
* @param strArg Argument to set (e.g. "-foo")
* @param strValue Value (e.g. "1")
* @return true if argument gets set, false if it already had a value
*/
bool SoftSetArg(const std::string &strArg, const std::string &strValue);
/**
* Set a boolean argument if it doesn't already have a value.
*
* @param strArg Argument to set (e.g. "-foo")
* @param fValue Value (e.g. false)
* @return true if argument gets set, false if it already had a value
*/
bool SoftSetBoolArg(const std::string &strArg, bool fValue);
// Forces a arg setting, used only in testing
void ForceSetArg(const std::string &strArg, const std::string &strValue);
// Forces a multi arg setting, used only in testing
void ForceSetMultiArg(const std::string &strArg,
const std::string &strValue);
/**
* Looks for -regtest, -testnet and returns the appropriate BIP70 chain
* name.
* @return CBaseChainParams::MAIN by default; raises runtime error if an
* invalid combination is given.
*/
std::string GetChainName() const;
// Remove an arg setting, used only in testing
void ClearArg(const std::string &strArg);
};
extern ArgsManager gArgs;
/**
* @return true if help has been requested via a command-line arg
*/
bool HelpRequested(const ArgsManager &args);
/**
* Format a string to be used as group of options in help messages.
*
* @param message Group name (e.g. "RPC server options:")
* @return the formatted string
*/
std::string HelpMessageGroup(const std::string &message);
/**
* Format a string to be used as option description in help messages.
*
* @param option Option message (e.g. "-rpcuser=<user>")
* @param message Option description (e.g. "Username for JSON-RPC connections")
* @return the formatted string
*/
std::string HelpMessageOpt(const std::string &option,
const std::string &message);
/**
* Return the number of physical cores available on the current system.
* @note This does not count virtual cores, such as those provided by
* HyperThreading when boost is newer than 1.56.
*/
int GetNumCores();
void RenameThread(const char *name);
/**
* .. and a wrapper that just calls func once
*/
template <typename Callable> void TraceThread(const char *name, Callable func) {
std::string s = strprintf("bitcoin-%s", name);
RenameThread(s.c_str());
try {
LogPrintf("%s thread start\n", name);
func();
LogPrintf("%s thread exit\n", name);
} catch (const boost::thread_interrupted &) {
LogPrintf("%s thread interrupt\n", name);
throw;
} catch (const std::exception &e) {
PrintExceptionContinue(&e, name);
throw;
} catch (...) {
PrintExceptionContinue(nullptr, name);
throw;
}
}
std::string CopyrightHolders(const std::string &strPrefix);
-//! Substitute for C++14 std::make_unique.
-template <typename T, typename... Args>
-std::unique_ptr<T> MakeUnique(Args &&... args) {
- return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
-}
-
#endif // BITCOIN_UTIL_H
diff --git a/src/wallet/db.cpp b/src/wallet/db.cpp
index ae48b2d90..dcda9f545 100644
--- a/src/wallet/db.cpp
+++ b/src/wallet/db.cpp
@@ -1,780 +1,781 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-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 "db.h"
#include "addrman.h"
#include "fs.h"
#include "hash.h"
#include "protocol.h"
#include "util.h"
#include "utilstrencodings.h"
#include "wallet/walletutil.h"
#include <boost/thread.hpp>
#include <boost/version.hpp>
#include <cstdint>
#ifndef WIN32
#include <sys/stat.h>
#endif
namespace {
//! Make sure database has a unique fileid within the environment. If it
//! doesn't, throw an error. BDB caches do not work properly when more than one
//! open database has the same fileid (values written to one database may show
//! up in reads to other databases).
//!
//! BerkeleyDB generates unique fileids by default
//! (https://docs.oracle.com/cd/E17275_01/html/programmer_reference/program_copy.html),
//! so bitcoin should never create different databases with the same fileid, but
//! this error can be triggered if users manually copy database files.
void CheckUniqueFileid(const CDBEnv &env, const std::string &filename, Db &db) {
if (env.IsMock()) {
return;
}
u_int8_t fileid[DB_FILE_ID_LEN];
int ret = db.get_mpf()->get_fileid(fileid);
if (ret != 0) {
throw std::runtime_error(
strprintf("CDB: Can't open database %s (get_fileid failed with %d)",
filename, ret));
}
for (const auto &item : env.mapDb) {
u_int8_t item_fileid[DB_FILE_ID_LEN];
if (item.second &&
item.second->get_mpf()->get_fileid(item_fileid) == 0 &&
memcmp(fileid, item_fileid, sizeof(fileid)) == 0) {
const char *item_filename = nullptr;
item.second->get_dbname(&item_filename, nullptr);
throw std::runtime_error(strprintf(
"CDB: Can't open database %s (duplicates fileid %s from %s)",
filename,
HexStr(std::begin(item_fileid), std::end(item_fileid)),
item_filename ? item_filename : "(unknown database)"));
}
}
}
} // namespace
//
// CDB
//
CDBEnv bitdb;
void CDBEnv::EnvShutdown() {
if (!fDbEnvInit) {
return;
}
fDbEnvInit = false;
int ret = dbenv->close(0);
if (ret != 0) {
LogPrintf("CDBEnv::EnvShutdown: Error %d shutting down database "
"environment: %s\n",
ret, DbEnv::strerror(ret));
}
if (!fMockDb) {
DbEnv(uint32_t(0)).remove(strPath.c_str(), 0);
}
}
void CDBEnv::Reset() {
dbenv.reset(new DbEnv(DB_CXX_NO_EXCEPTIONS));
fDbEnvInit = false;
fMockDb = false;
}
CDBEnv::CDBEnv() {
Reset();
}
CDBEnv::~CDBEnv() {
EnvShutdown();
}
void CDBEnv::Close() {
EnvShutdown();
}
bool CDBEnv::Open(const fs::path &pathIn) {
if (fDbEnvInit) {
return true;
}
boost::this_thread::interruption_point();
strPath = pathIn.string();
fs::path pathLogDir = pathIn / "database";
TryCreateDirectories(pathLogDir);
fs::path pathErrorFile = pathIn / "db.log";
LogPrintf("CDBEnv::Open: LogDir=%s ErrorFile=%s\n", pathLogDir.string(),
pathErrorFile.string());
unsigned int nEnvFlags = 0;
if (gArgs.GetBoolArg("-privdb", DEFAULT_WALLET_PRIVDB)) {
nEnvFlags |= DB_PRIVATE;
}
dbenv->set_lg_dir(pathLogDir.string().c_str());
// 1 MiB should be enough for just the wallet
dbenv->set_cachesize(0, 0x100000, 1);
dbenv->set_lg_bsize(0x10000);
dbenv->set_lg_max(1048576);
dbenv->set_lk_max_locks(40000);
dbenv->set_lk_max_objects(40000);
/// debug
dbenv->set_errfile(fsbridge::fopen(pathErrorFile, "a"));
dbenv->set_flags(DB_AUTO_COMMIT, 1);
dbenv->set_flags(DB_TXN_WRITE_NOSYNC, 1);
dbenv->log_set_config(DB_LOG_AUTO_REMOVE, 1);
int ret =
dbenv->open(strPath.c_str(),
DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL |
DB_INIT_TXN | DB_THREAD | DB_RECOVER | nEnvFlags,
S_IRUSR | S_IWUSR);
if (ret != 0) {
return error(
"CDBEnv::Open: Error %d opening database environment: %s\n", ret,
DbEnv::strerror(ret));
}
fDbEnvInit = true;
fMockDb = false;
return true;
}
void CDBEnv::MakeMock() {
if (fDbEnvInit) {
throw std::runtime_error("CDBEnv::MakeMock: Already initialized");
}
boost::this_thread::interruption_point();
LogPrint(BCLog::DB, "CDBEnv::MakeMock\n");
dbenv->set_cachesize(1, 0, 1);
dbenv->set_lg_bsize(10485760 * 4);
dbenv->set_lg_max(10485760);
dbenv->set_lk_max_locks(10000);
dbenv->set_lk_max_objects(10000);
dbenv->set_flags(DB_AUTO_COMMIT, 1);
dbenv->log_set_config(DB_LOG_IN_MEMORY, 1);
int ret =
dbenv->open(nullptr,
DB_CREATE | DB_INIT_LOCK | DB_INIT_LOG | DB_INIT_MPOOL |
DB_INIT_TXN | DB_THREAD | DB_PRIVATE,
S_IRUSR | S_IWUSR);
if (ret > 0) {
throw std::runtime_error(strprintf(
"CDBEnv::MakeMock: Error %d opening database environment.", ret));
}
fDbEnvInit = true;
fMockDb = true;
}
CDBEnv::VerifyResult CDBEnv::Verify(const std::string &strFile,
recoverFunc_type recoverFunc,
std::string &out_backup_filename) {
LOCK(cs_db);
assert(mapFileUseCount.count(strFile) == 0);
Db db(dbenv.get(), 0);
int result = db.verify(strFile.c_str(), nullptr, nullptr, 0);
if (result == 0) {
return VerifyResult::VERIFY_OK;
} else if (recoverFunc == nullptr) {
return VerifyResult::RECOVER_FAIL;
}
// Try to recover:
bool fRecovered = (*recoverFunc)(strFile, out_backup_filename);
return (fRecovered ? VerifyResult::RECOVER_OK : VerifyResult::RECOVER_FAIL);
}
bool CDB::Recover(const std::string &filename, void *callbackDataIn,
bool (*recoverKVcallback)(void *callbackData,
CDataStream ssKey,
CDataStream ssValue),
std::string &newFilename) {
// Recovery procedure:
// Move wallet file to walletfilename.timestamp.bak
// Call Salvage with fAggressive=true to get as much data as possible.
// Rewrite salvaged data to fresh wallet file.
// Set -rescan so any missing transactions will be found.
int64_t now = GetTime();
newFilename = strprintf("%s.%d.bak", filename, now);
int result = bitdb.dbenv->dbrename(nullptr, filename.c_str(), nullptr,
newFilename.c_str(), DB_AUTO_COMMIT);
if (result == 0) {
LogPrintf("Renamed %s to %s\n", filename, newFilename);
} else {
LogPrintf("Failed to rename %s to %s\n", filename, newFilename);
return false;
}
std::vector<CDBEnv::KeyValPair> salvagedData;
bool fSuccess = bitdb.Salvage(newFilename, true, salvagedData);
if (salvagedData.empty()) {
LogPrintf("Salvage(aggressive) found no records in %s.\n", newFilename);
return false;
}
LogPrintf("Salvage(aggressive) found %u records\n", salvagedData.size());
- std::unique_ptr<Db> pdbCopy = MakeUnique<Db>(bitdb.dbenv.get(), 0);
+ std::unique_ptr<Db> pdbCopy = std::make_unique<Db>(bitdb.dbenv.get(), 0);
int ret = pdbCopy->open(nullptr, // Txn pointer
filename.c_str(), // Filename
"main", // Logical db name
DB_BTREE, // Database type
DB_CREATE, // Flags
0);
if (ret > 0) {
LogPrintf("Cannot create database file %s\n", filename);
return false;
}
DbTxn *ptxn = bitdb.TxnBegin();
for (CDBEnv::KeyValPair &row : salvagedData) {
if (recoverKVcallback) {
CDataStream ssKey(row.first, SER_DISK, CLIENT_VERSION);
CDataStream ssValue(row.second, SER_DISK, CLIENT_VERSION);
std::string strType, strErr;
if (!(*recoverKVcallback)(callbackDataIn, ssKey, ssValue)) {
continue;
}
}
Dbt datKey(&row.first[0], row.first.size());
Dbt datValue(&row.second[0], row.second.size());
int ret2 = pdbCopy->put(ptxn, &datKey, &datValue, DB_NOOVERWRITE);
if (ret2 > 0) {
fSuccess = false;
}
}
ptxn->commit(0);
pdbCopy->close(0);
return fSuccess;
}
bool CDB::VerifyEnvironment(const std::string &walletFile,
const fs::path &walletDir, std::string &errorStr) {
LogPrintf("Using BerkeleyDB version %s\n", DbEnv::version(0, 0, 0));
LogPrintf("Using wallet %s\n", walletFile);
// Wallet file must be a plain filename without a directory
if (walletFile != fs::basename(walletFile) + fs::extension(walletFile)) {
errorStr = strprintf(_("Wallet %s resides outside wallet directory %s"),
walletFile, walletDir.string());
return false;
}
if (!bitdb.Open(walletDir)) {
// try moving the database env out of the way
fs::path pathDatabase = walletDir / "database";
fs::path pathDatabaseBak =
walletDir / strprintf("database.%d.bak", GetTime());
try {
fs::rename(pathDatabase, pathDatabaseBak);
LogPrintf("Moved old %s to %s. Retrying.\n", pathDatabase.string(),
pathDatabaseBak.string());
} catch (const fs::filesystem_error &) {
// failure is ok (well, not really, but it's not worse than what we
// started with)
}
// try again
if (!bitdb.Open(walletDir)) {
// if it still fails, it probably means we can't even create the
// database env
errorStr = strprintf(
_("Error initializing wallet database environment %s!"),
walletDir);
return false;
}
}
return true;
}
bool CDB::VerifyDatabaseFile(const std::string &walletFile,
const fs::path &walletDir, std::string &warningStr,
std::string &errorStr,
CDBEnv::recoverFunc_type recoverFunc) {
if (fs::exists(walletDir / walletFile)) {
std::string backup_filename;
CDBEnv::VerifyResult r =
bitdb.Verify(walletFile, recoverFunc, backup_filename);
if (r == CDBEnv::VerifyResult::RECOVER_OK) {
warningStr = strprintf(
_("Warning: Wallet file corrupt, data salvaged!"
" Original %s saved as %s in %s; if"
" your balance or transactions are incorrect you should"
" restore from a backup."),
walletFile, backup_filename, walletDir);
}
if (r == CDBEnv::VerifyResult::RECOVER_FAIL) {
errorStr = strprintf(_("%s corrupt, salvage failed"), walletFile);
return false;
}
}
// also return true if files does not exists
return true;
}
/* End of headers, beginning of key/value data */
static const char *HEADER_END = "HEADER=END";
/* End of key/value data */
static const char *DATA_END = "DATA=END";
bool CDBEnv::Salvage(const std::string &strFile, bool fAggressive,
std::vector<CDBEnv::KeyValPair> &vResult) {
LOCK(cs_db);
assert(mapFileUseCount.count(strFile) == 0);
u_int32_t flags = DB_SALVAGE;
if (fAggressive) {
flags |= DB_AGGRESSIVE;
}
std::stringstream strDump;
Db db(dbenv.get(), 0);
int result = db.verify(strFile.c_str(), nullptr, &strDump, flags);
if (result == DB_VERIFY_BAD) {
LogPrintf("CDBEnv::Salvage: Database salvage found errors, all data "
"may not be recoverable.\n");
if (!fAggressive) {
LogPrintf("CDBEnv::Salvage: Rerun with aggressive mode to ignore "
"errors and continue.\n");
return false;
}
}
if (result != 0 && result != DB_VERIFY_BAD) {
LogPrintf("CDBEnv::Salvage: Database salvage failed with result %d.\n",
result);
return false;
}
// Format of bdb dump is ascii lines:
// header lines...
// HEADER=END
// hexadecimal key
// hexadecimal value
// ... repeated
// DATA=END
std::string strLine;
while (!strDump.eof() && strLine != HEADER_END) {
// Skip past header
getline(strDump, strLine);
}
std::string keyHex, valueHex;
while (!strDump.eof() && keyHex != DATA_END) {
getline(strDump, keyHex);
if (keyHex != DATA_END) {
if (strDump.eof()) {
break;
}
getline(strDump, valueHex);
if (valueHex == DATA_END) {
LogPrintf("CDBEnv::Salvage: WARNING: Number of keys in data "
"does not match number of values.\n");
break;
}
vResult.push_back(make_pair(ParseHex(keyHex), ParseHex(valueHex)));
}
}
if (keyHex != DATA_END) {
LogPrintf("CDBEnv::Salvage: WARNING: Unexpected end of file while "
"reading salvage output.\n");
return false;
}
return (result == 0);
}
void CDBEnv::CheckpointLSN(const std::string &strFile) {
dbenv->txn_checkpoint(0, 0, 0);
if (fMockDb) {
return;
}
dbenv->lsn_reset(strFile.c_str(), 0);
}
CDB::CDB(CWalletDBWrapper &dbw, const char *pszMode, bool fFlushOnCloseIn)
: pdb(nullptr), activeTxn(nullptr) {
int ret;
fReadOnly = (!strchr(pszMode, '+') && !strchr(pszMode, 'w'));
fFlushOnClose = fFlushOnCloseIn;
env = dbw.env;
if (dbw.IsDummy()) {
return;
}
const std::string &strFilename = dbw.strFile;
bool fCreate = strchr(pszMode, 'c') != nullptr;
unsigned int nFlags = DB_THREAD;
if (fCreate) {
nFlags |= DB_CREATE;
}
{
LOCK(env->cs_db);
if (!env->Open(GetWalletDir())) {
throw std::runtime_error(
"CDB: Failed to open database environment.");
}
pdb = env->mapDb[strFilename];
if (pdb == nullptr) {
- std::unique_ptr<Db> pdb_temp = MakeUnique<Db>(env->dbenv.get(), 0);
+ std::unique_ptr<Db> pdb_temp =
+ std::make_unique<Db>(env->dbenv.get(), 0);
bool fMockDb = env->IsMock();
if (fMockDb) {
DbMpoolFile *mpf = pdb_temp->get_mpf();
ret = mpf->set_flags(DB_MPOOL_NOFILE, 1);
if (ret != 0) {
throw std::runtime_error(
strprintf("CDB: Failed to configure for no temp file "
"backing for database %s",
strFilename));
}
}
ret = pdb_temp->open(
nullptr, // Txn pointer
fMockDb ? nullptr : strFilename.c_str(), // Filename
fMockDb ? strFilename.c_str() : "main", // Logical db name
DB_BTREE, // Database type
nFlags, // Flags
0);
if (ret != 0) {
throw std::runtime_error(strprintf(
"CDB: Error %d, can't open database %s", ret, strFilename));
}
CheckUniqueFileid(*env, strFilename, *pdb_temp);
pdb = pdb_temp.release();
env->mapDb[strFilename] = pdb;
if (fCreate && !Exists(std::string("version"))) {
bool fTmp = fReadOnly;
fReadOnly = false;
WriteVersion(CLIENT_VERSION);
fReadOnly = fTmp;
}
}
++env->mapFileUseCount[strFilename];
strFile = strFilename;
}
}
void CDB::Flush() {
if (activeTxn) {
return;
}
// Flush database activity from memory pool to disk log
unsigned int nMinutes = 0;
if (fReadOnly) {
nMinutes = 1;
}
env->dbenv->txn_checkpoint(
nMinutes ? gArgs.GetArg("-dblogsize", DEFAULT_WALLET_DBLOGSIZE) * 1024
: 0,
nMinutes, 0);
}
void CWalletDBWrapper::IncrementUpdateCounter() {
++nUpdateCounter;
}
void CDB::Close() {
if (!pdb) {
return;
}
if (activeTxn) {
activeTxn->abort();
}
activeTxn = nullptr;
pdb = nullptr;
if (fFlushOnClose) {
Flush();
}
LOCK(env->cs_db);
--env->mapFileUseCount[strFile];
}
void CDBEnv::CloseDb(const std::string &strFile) {
LOCK(cs_db);
if (mapDb[strFile] != nullptr) {
// Close the database handle
Db *pdb = mapDb[strFile];
pdb->close(0);
delete pdb;
mapDb[strFile] = nullptr;
}
}
bool CDB::Rewrite(CWalletDBWrapper &dbw, const char *pszSkip) {
if (dbw.IsDummy()) {
return true;
}
CDBEnv *env = dbw.env;
const std::string &strFile = dbw.strFile;
while (true) {
{
LOCK(env->cs_db);
if (!env->mapFileUseCount.count(strFile) ||
env->mapFileUseCount[strFile] == 0) {
// Flush log data to the dat file
env->CloseDb(strFile);
env->CheckpointLSN(strFile);
env->mapFileUseCount.erase(strFile);
bool fSuccess = true;
LogPrintf("CDB::Rewrite: Rewriting %s...\n", strFile);
std::string strFileRes = strFile + ".rewrite";
{
// surround usage of db with extra {}
CDB db(dbw, "r");
std::unique_ptr<Db> pdbCopy =
- MakeUnique<Db>(env->dbenv.get(), 0);
+ std::make_unique<Db>(env->dbenv.get(), 0);
int ret = pdbCopy->open(nullptr, // Txn pointer
strFileRes.c_str(), // Filename
"main", // Logical db name
DB_BTREE, // Database type
DB_CREATE, // Flags
0);
if (ret > 0) {
LogPrintf(
"CDB::Rewrite: Can't create database file %s\n",
strFileRes);
fSuccess = false;
}
Dbc *pcursor = db.GetCursor();
if (pcursor)
while (fSuccess) {
CDataStream ssKey(SER_DISK, CLIENT_VERSION);
CDataStream ssValue(SER_DISK, CLIENT_VERSION);
int ret1 = db.ReadAtCursor(pcursor, ssKey, ssValue);
if (ret1 == DB_NOTFOUND) {
pcursor->close();
break;
}
if (ret1 != 0) {
pcursor->close();
fSuccess = false;
break;
}
if (pszSkip &&
strncmp(ssKey.data(), pszSkip,
std::min(ssKey.size(),
strlen(pszSkip))) == 0) {
continue;
}
if (strncmp(ssKey.data(), "\x07version", 8) == 0) {
// Update version:
ssValue.clear();
ssValue << CLIENT_VERSION;
}
Dbt datKey(ssKey.data(), ssKey.size());
Dbt datValue(ssValue.data(), ssValue.size());
int ret2 = pdbCopy->put(nullptr, &datKey, &datValue,
DB_NOOVERWRITE);
if (ret2 > 0) {
fSuccess = false;
}
}
if (fSuccess) {
db.Close();
env->CloseDb(strFile);
if (pdbCopy->close(0)) {
fSuccess = false;
}
}
}
if (fSuccess) {
Db dbA(env->dbenv.get(), 0);
if (dbA.remove(strFile.c_str(), nullptr, 0)) {
fSuccess = false;
}
Db dbB(env->dbenv.get(), 0);
if (dbB.rename(strFileRes.c_str(), nullptr, strFile.c_str(),
0)) {
fSuccess = false;
}
}
if (!fSuccess) {
LogPrintf(
"CDB::Rewrite: Failed to rewrite database file %s\n",
strFileRes);
}
return fSuccess;
}
}
MilliSleep(100);
}
return false;
}
void CDBEnv::Flush(bool fShutdown) {
int64_t nStart = GetTimeMillis();
// Flush log data to the actual data file on all files that are not in use
LogPrint(BCLog::DB, "CDBEnv::Flush: Flush(%s)%s\n",
fShutdown ? "true" : "false",
fDbEnvInit ? "" : " database not started");
if (!fDbEnvInit) {
return;
}
{
LOCK(cs_db);
std::map<std::string, int>::iterator mi = mapFileUseCount.begin();
while (mi != mapFileUseCount.end()) {
std::string strFile = (*mi).first;
int nRefCount = (*mi).second;
LogPrint(BCLog::DB,
"CDBEnv::Flush: Flushing %s (refcount = %d)...\n", strFile,
nRefCount);
if (nRefCount == 0) {
// Move log data to the dat file
CloseDb(strFile);
LogPrint(BCLog::DB, "CDBEnv::Flush: %s checkpoint\n", strFile);
dbenv->txn_checkpoint(0, 0, 0);
LogPrint(BCLog::DB, "CDBEnv::Flush: %s detach\n", strFile);
if (!fMockDb) {
dbenv->lsn_reset(strFile.c_str(), 0);
}
LogPrint(BCLog::DB, "CDBEnv::Flush: %s closed\n", strFile);
mapFileUseCount.erase(mi++);
} else {
mi++;
}
}
LogPrint(BCLog::DB, "CDBEnv::Flush: Flush(%s)%s took %15dms\n",
fShutdown ? "true" : "false",
fDbEnvInit ? "" : " database not started",
GetTimeMillis() - nStart);
if (fShutdown) {
char **listp;
if (mapFileUseCount.empty()) {
dbenv->log_archive(&listp, DB_ARCH_REMOVE);
Close();
if (!fMockDb) {
fs::remove_all(fs::path(strPath) / "database");
}
}
}
}
}
bool CDB::PeriodicFlush(CWalletDBWrapper &dbw) {
if (dbw.IsDummy()) {
return true;
}
bool ret = false;
CDBEnv *env = dbw.env;
const std::string &strFile = dbw.strFile;
TRY_LOCK(bitdb.cs_db, lockDb);
if (lockDb) {
// Don't do this if any databases are in use
int nRefCount = 0;
std::map<std::string, int>::iterator mit = env->mapFileUseCount.begin();
while (mit != env->mapFileUseCount.end()) {
nRefCount += (*mit).second;
mit++;
}
if (nRefCount == 0) {
boost::this_thread::interruption_point();
std::map<std::string, int>::iterator mi =
env->mapFileUseCount.find(strFile);
if (mi != env->mapFileUseCount.end()) {
LogPrint(BCLog::DB, "Flushing %s\n", strFile);
int64_t nStart = GetTimeMillis();
// Flush wallet file so it's self contained
env->CloseDb(strFile);
env->CheckpointLSN(strFile);
env->mapFileUseCount.erase(mi++);
LogPrint(BCLog::DB, "Flushed %s %dms\n", strFile,
GetTimeMillis() - nStart);
ret = true;
}
}
}
return ret;
}
bool CWalletDBWrapper::Rewrite(const char *pszSkip) {
return CDB::Rewrite(*this, pszSkip);
}
bool CWalletDBWrapper::Backup(const std::string &strDest) {
if (IsDummy()) {
return false;
}
while (true) {
{
LOCK(env->cs_db);
if (!env->mapFileUseCount.count(strFile) ||
env->mapFileUseCount[strFile] == 0) {
// Flush log data to the dat file
env->CloseDb(strFile);
env->CheckpointLSN(strFile);
env->mapFileUseCount.erase(strFile);
// Copy wallet file.
fs::path pathSrc = GetWalletDir() / strFile;
fs::path pathDest(strDest);
if (fs::is_directory(pathDest)) {
pathDest /= strFile;
}
try {
if (fs::equivalent(pathSrc, pathDest)) {
LogPrintf("cannot backup to wallet source file %s\n",
pathDest.string());
return false;
}
fs::copy_file(pathSrc, pathDest,
fs::copy_option::overwrite_if_exists);
LogPrintf("copied %s to %s\n", strFile, pathDest.string());
return true;
} catch (const fs::filesystem_error &e) {
LogPrintf("error copying %s to %s - %s\n", strFile,
pathDest.string(), e.what());
return false;
}
}
}
MilliSleep(100);
}
return false;
}
void CWalletDBWrapper::Flush(bool shutdown) {
if (!IsDummy()) {
env->Flush(shutdown);
}
}
diff --git a/src/wallet/test/wallet_test_fixture.cpp b/src/wallet/test/wallet_test_fixture.cpp
index a9d310bf0..9065eeac7 100644
--- a/src/wallet/test/wallet_test_fixture.cpp
+++ b/src/wallet/test/wallet_test_fixture.cpp
@@ -1,36 +1,36 @@
// Copyright (c) 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 "wallet/test/wallet_test_fixture.h"
#include "chainparams.h"
#include "rpc/server.h"
#include "wallet/db.h"
#include "wallet/rpcdump.h"
#include "wallet/wallet.h"
std::unique_ptr<CWallet> pwalletMain;
WalletTestingSetup::WalletTestingSetup(const std::string &chainName)
: TestingSetup(chainName) {
bitdb.MakeMock();
bool fFirstRun;
std::unique_ptr<CWalletDBWrapper> dbw(
new CWalletDBWrapper(&bitdb, "wallet_test.dat"));
- pwalletMain = MakeUnique<CWallet>(Params(), std::move(dbw));
+ pwalletMain = std::make_unique<CWallet>(Params(), std::move(dbw));
pwalletMain->LoadWallet(fFirstRun);
RegisterValidationInterface(pwalletMain.get());
RegisterWalletRPCCommands(tableRPC);
RegisterDumpRPCCommands(tableRPC);
}
WalletTestingSetup::~WalletTestingSetup() {
UnregisterValidationInterface(pwalletMain.get());
pwalletMain.reset();
bitdb.Flush(true);
bitdb.Reset();
}
diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp
index 47dfc3ead..21135218b 100644
--- a/src/wallet/wallet.cpp
+++ b/src/wallet/wallet.cpp
@@ -1,4468 +1,4468 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-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 "wallet/wallet.h"
#include "chain.h"
#include "checkpoints.h"
#include "config.h"
#include "consensus/consensus.h"
#include "consensus/validation.h"
#include "dstencode.h"
#include "fs.h"
#include "init.h"
#include "key.h"
#include "keystore.h"
#include "net.h"
#include "policy/policy.h"
#include "primitives/block.h"
#include "primitives/transaction.h"
#include "scheduler.h"
#include "script/script.h"
#include "script/sighashtype.h"
#include "script/sign.h"
#include "timedata.h"
#include "txmempool.h"
#include "ui_interface.h"
#include "util.h"
#include "utilmoneystr.h"
#include "validation.h"
#include "wallet/coincontrol.h"
#include "wallet/fees.h"
#include "wallet/finaltx.h"
#include <boost/algorithm/string/replace.hpp>
#include <cassert>
#include <future>
std::vector<CWalletRef> vpwallets;
/** Transaction fee set by the user */
CFeeRate payTxFee(DEFAULT_TRANSACTION_FEE);
bool bSpendZeroConfChange = DEFAULT_SPEND_ZEROCONF_CHANGE;
const char *DEFAULT_WALLET_DAT = "wallet.dat";
const uint32_t BIP32_HARDENED_KEY_LIMIT = 0x80000000;
/**
* If fee estimation does not have enough data to provide estimates, use this
* fee instead. Has no effect if not using fee estimation.
* Override with -fallbackfee
*/
CFeeRate CWallet::fallbackFee = CFeeRate(DEFAULT_FALLBACK_FEE);
const uint256 CMerkleTx::ABANDON_HASH(uint256S(
"0000000000000000000000000000000000000000000000000000000000000001"));
/** @defgroup mapWallet
*
* @{
*/
struct CompareValueOnly {
bool operator()(const CInputCoin &t1, const CInputCoin &t2) const {
return t1.txout.nValue < t2.txout.nValue;
}
};
std::string COutput::ToString() const {
return strprintf("COutput(%s, %d, %d) [%s]", tx->GetId().ToString(), i,
nDepth, FormatMoney(tx->tx->vout[i].nValue));
}
class CAffectedKeysVisitor : public boost::static_visitor<void> {
private:
const CKeyStore &keystore;
std::vector<CKeyID> &vKeys;
public:
CAffectedKeysVisitor(const CKeyStore &keystoreIn,
std::vector<CKeyID> &vKeysIn)
: keystore(keystoreIn), vKeys(vKeysIn) {}
void Process(const CScript &script) {
txnouttype type;
std::vector<CTxDestination> vDest;
int nRequired;
if (ExtractDestinations(script, type, vDest, nRequired)) {
for (const CTxDestination &dest : vDest) {
boost::apply_visitor(*this, dest);
}
}
}
void operator()(const CKeyID &keyId) {
if (keystore.HaveKey(keyId)) {
vKeys.push_back(keyId);
}
}
void operator()(const CScriptID &scriptId) {
CScript script;
if (keystore.GetCScript(scriptId, script)) {
Process(script);
}
}
void operator()(const CNoDestination &none) {}
};
const CWalletTx *CWallet::GetWalletTx(const TxId &txid) const {
LOCK(cs_wallet);
std::map<TxId, CWalletTx>::const_iterator it = mapWallet.find(txid);
if (it == mapWallet.end()) {
return nullptr;
}
return &(it->second);
}
CPubKey CWallet::GenerateNewKey(CWalletDB &walletdb, bool internal) {
// mapKeyMetadata
AssertLockHeld(cs_wallet);
// default to compressed public keys if we want 0.6.0 wallets
bool fCompressed = CanSupportFeature(FEATURE_COMPRPUBKEY);
CKey secret;
// Create new metadata
int64_t nCreationTime = GetTime();
CKeyMetadata metadata(nCreationTime);
// use HD key derivation if HD was enabled during wallet creation
if (IsHDEnabled()) {
DeriveNewChildKey(
walletdb, metadata, secret,
(CanSupportFeature(FEATURE_HD_SPLIT) ? internal : false));
} else {
secret.MakeNewKey(fCompressed);
}
// Compressed public keys were introduced in version 0.6.0
if (fCompressed) {
SetMinVersion(FEATURE_COMPRPUBKEY);
}
CPubKey pubkey = secret.GetPubKey();
assert(secret.VerifyPubKey(pubkey));
mapKeyMetadata[pubkey.GetID()] = metadata;
UpdateTimeFirstKey(nCreationTime);
if (!AddKeyPubKeyWithDB(walletdb, secret, pubkey)) {
throw std::runtime_error(std::string(__func__) + ": AddKey failed");
}
return pubkey;
}
void CWallet::DeriveNewChildKey(CWalletDB &walletdb, CKeyMetadata &metadata,
CKey &secret, bool internal) {
// for now we use a fixed keypath scheme of m/0'/0'/k
// master key seed (256bit)
CKey key;
// hd master key
CExtKey masterKey;
// key at m/0'
CExtKey accountKey;
// key at m/0'/0' (external) or m/0'/1' (internal)
CExtKey chainChildKey;
// key at m/0'/0'/<n>'
CExtKey childKey;
// try to get the master key
if (!GetKey(hdChain.masterKeyID, key)) {
throw std::runtime_error(std::string(__func__) +
": Master key not found");
}
masterKey.SetMaster(key.begin(), key.size());
// derive m/0'
// use hardened derivation (child keys >= 0x80000000 are hardened after
// bip32)
masterKey.Derive(accountKey, BIP32_HARDENED_KEY_LIMIT);
// derive m/0'/0' (external chain) OR m/0'/1' (internal chain)
assert(internal ? CanSupportFeature(FEATURE_HD_SPLIT) : true);
accountKey.Derive(chainChildKey,
BIP32_HARDENED_KEY_LIMIT + (internal ? 1 : 0));
// derive child key at next index, skip keys already known to the wallet
do {
// always derive hardened keys
// childIndex | BIP32_HARDENED_KEY_LIMIT = derive childIndex in hardened
// child-index-range
// example: 1 | BIP32_HARDENED_KEY_LIMIT == 0x80000001 == 2147483649
if (internal) {
chainChildKey.Derive(childKey, hdChain.nInternalChainCounter |
BIP32_HARDENED_KEY_LIMIT);
metadata.hdKeypath = "m/0'/1'/" +
std::to_string(hdChain.nInternalChainCounter) +
"'";
hdChain.nInternalChainCounter++;
} else {
chainChildKey.Derive(childKey, hdChain.nExternalChainCounter |
BIP32_HARDENED_KEY_LIMIT);
metadata.hdKeypath = "m/0'/0'/" +
std::to_string(hdChain.nExternalChainCounter) +
"'";
hdChain.nExternalChainCounter++;
}
} while (HaveKey(childKey.key.GetPubKey().GetID()));
secret = childKey.key;
metadata.hdMasterKeyID = hdChain.masterKeyID;
// update the chain model in the database
if (!walletdb.WriteHDChain(hdChain)) {
throw std::runtime_error(std::string(__func__) +
": Writing HD chain model failed");
}
}
bool CWallet::AddKeyPubKeyWithDB(CWalletDB &walletdb, const CKey &secret,
const CPubKey &pubkey) {
// mapKeyMetadata
AssertLockHeld(cs_wallet);
// CCryptoKeyStore has no concept of wallet databases, but calls
// AddCryptedKey
// which is overridden below. To avoid flushes, the database handle is
// tunneled through to it.
bool needsDB = !pwalletdbEncryption;
if (needsDB) {
pwalletdbEncryption = &walletdb;
}
if (!CCryptoKeyStore::AddKeyPubKey(secret, pubkey)) {
if (needsDB) {
pwalletdbEncryption = nullptr;
}
return false;
}
if (needsDB) {
pwalletdbEncryption = nullptr;
}
// Check if we need to remove from watch-only.
CScript script;
script = GetScriptForDestination(pubkey.GetID());
if (HaveWatchOnly(script)) {
RemoveWatchOnly(script);
}
script = GetScriptForRawPubKey(pubkey);
if (HaveWatchOnly(script)) {
RemoveWatchOnly(script);
}
if (IsCrypted()) {
return true;
}
return walletdb.WriteKey(pubkey, secret.GetPrivKey(),
mapKeyMetadata[pubkey.GetID()]);
}
bool CWallet::AddKeyPubKey(const CKey &secret, const CPubKey &pubkey) {
CWalletDB walletdb(*dbw);
return CWallet::AddKeyPubKeyWithDB(walletdb, secret, pubkey);
}
bool CWallet::AddCryptedKey(const CPubKey &vchPubKey,
const std::vector<uint8_t> &vchCryptedSecret) {
if (!CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret)) {
return false;
}
LOCK(cs_wallet);
if (pwalletdbEncryption) {
return pwalletdbEncryption->WriteCryptedKey(
vchPubKey, vchCryptedSecret, mapKeyMetadata[vchPubKey.GetID()]);
}
return CWalletDB(*dbw).WriteCryptedKey(vchPubKey, vchCryptedSecret,
mapKeyMetadata[vchPubKey.GetID()]);
}
bool CWallet::LoadKeyMetadata(const CKeyID &keyID, const CKeyMetadata &meta) {
// mapKeyMetadata
AssertLockHeld(cs_wallet);
UpdateTimeFirstKey(meta.nCreateTime);
mapKeyMetadata[keyID] = meta;
return true;
}
bool CWallet::LoadScriptMetadata(const CScriptID &script_id,
const CKeyMetadata &meta) {
// m_script_metadata
AssertLockHeld(cs_wallet);
UpdateTimeFirstKey(meta.nCreateTime);
m_script_metadata[script_id] = meta;
return true;
}
bool CWallet::LoadCryptedKey(const CPubKey &vchPubKey,
const std::vector<uint8_t> &vchCryptedSecret) {
return CCryptoKeyStore::AddCryptedKey(vchPubKey, vchCryptedSecret);
}
/**
* Update wallet first key creation time. This should be called whenever keys
* are added to the wallet, with the oldest key creation time.
*/
void CWallet::UpdateTimeFirstKey(int64_t nCreateTime) {
AssertLockHeld(cs_wallet);
if (nCreateTime <= 1) {
// Cannot determine birthday information, so set the wallet birthday to
// the beginning of time.
nTimeFirstKey = 1;
} else if (!nTimeFirstKey || nCreateTime < nTimeFirstKey) {
nTimeFirstKey = nCreateTime;
}
}
bool CWallet::AddCScript(const CScript &redeemScript) {
if (!CCryptoKeyStore::AddCScript(redeemScript)) {
return false;
}
return CWalletDB(*dbw).WriteCScript(Hash160(redeemScript), redeemScript);
}
bool CWallet::LoadCScript(const CScript &redeemScript) {
/**
* A sanity check was added in pull #3843 to avoid adding redeemScripts that
* never can be redeemed. However, old wallets may still contain these. Do
* not add them to the wallet and warn.
*/
if (redeemScript.size() > MAX_SCRIPT_ELEMENT_SIZE) {
std::string strAddr = EncodeDestination(CScriptID(redeemScript));
LogPrintf("%s: Warning: This wallet contains a redeemScript of size %i "
"which exceeds maximum size %i thus can never be redeemed. "
"Do not use address %s.\n",
__func__, redeemScript.size(), MAX_SCRIPT_ELEMENT_SIZE,
strAddr);
return true;
}
return CCryptoKeyStore::AddCScript(redeemScript);
}
bool CWallet::AddWatchOnly(const CScript &dest) {
if (!CCryptoKeyStore::AddWatchOnly(dest)) {
return false;
}
const CKeyMetadata &meta = m_script_metadata[CScriptID(dest)];
UpdateTimeFirstKey(meta.nCreateTime);
NotifyWatchonlyChanged(true);
return CWalletDB(*dbw).WriteWatchOnly(dest, meta);
}
bool CWallet::AddWatchOnly(const CScript &dest, int64_t nCreateTime) {
m_script_metadata[CScriptID(dest)].nCreateTime = nCreateTime;
return AddWatchOnly(dest);
}
bool CWallet::RemoveWatchOnly(const CScript &dest) {
AssertLockHeld(cs_wallet);
if (!CCryptoKeyStore::RemoveWatchOnly(dest)) {
return false;
}
if (!HaveWatchOnly()) {
NotifyWatchonlyChanged(false);
}
return CWalletDB(*dbw).EraseWatchOnly(dest);
}
bool CWallet::LoadWatchOnly(const CScript &dest) {
return CCryptoKeyStore::AddWatchOnly(dest);
}
bool CWallet::Unlock(const SecureString &strWalletPassphrase) {
CCrypter crypter;
CKeyingMaterial _vMasterKey;
LOCK(cs_wallet);
for (const MasterKeyMap::value_type &pMasterKey : mapMasterKeys) {
if (!crypter.SetKeyFromPassphrase(
strWalletPassphrase, pMasterKey.second.vchSalt,
pMasterKey.second.nDeriveIterations,
pMasterKey.second.nDerivationMethod)) {
return false;
}
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey)) {
// try another master key
continue;
}
if (CCryptoKeyStore::Unlock(_vMasterKey)) {
return true;
}
}
return false;
}
bool CWallet::ChangeWalletPassphrase(
const SecureString &strOldWalletPassphrase,
const SecureString &strNewWalletPassphrase) {
bool fWasLocked = IsLocked();
LOCK(cs_wallet);
Lock();
CCrypter crypter;
CKeyingMaterial _vMasterKey;
for (MasterKeyMap::value_type &pMasterKey : mapMasterKeys) {
if (!crypter.SetKeyFromPassphrase(
strOldWalletPassphrase, pMasterKey.second.vchSalt,
pMasterKey.second.nDeriveIterations,
pMasterKey.second.nDerivationMethod)) {
return false;
}
if (!crypter.Decrypt(pMasterKey.second.vchCryptedKey, _vMasterKey)) {
return false;
}
if (CCryptoKeyStore::Unlock(_vMasterKey)) {
int64_t nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strNewWalletPassphrase,
pMasterKey.second.vchSalt,
pMasterKey.second.nDeriveIterations,
pMasterKey.second.nDerivationMethod);
pMasterKey.second.nDeriveIterations =
pMasterKey.second.nDeriveIterations *
(100 / ((double)(GetTimeMillis() - nStartTime)));
nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strNewWalletPassphrase,
pMasterKey.second.vchSalt,
pMasterKey.second.nDeriveIterations,
pMasterKey.second.nDerivationMethod);
pMasterKey.second.nDeriveIterations =
(pMasterKey.second.nDeriveIterations +
pMasterKey.second.nDeriveIterations * 100 /
double(GetTimeMillis() - nStartTime)) /
2;
if (pMasterKey.second.nDeriveIterations < 25000) {
pMasterKey.second.nDeriveIterations = 25000;
}
LogPrintf(
"Wallet passphrase changed to an nDeriveIterations of %i\n",
pMasterKey.second.nDeriveIterations);
if (!crypter.SetKeyFromPassphrase(
strNewWalletPassphrase, pMasterKey.second.vchSalt,
pMasterKey.second.nDeriveIterations,
pMasterKey.second.nDerivationMethod)) {
return false;
}
if (!crypter.Encrypt(_vMasterKey,
pMasterKey.second.vchCryptedKey)) {
return false;
}
CWalletDB(*dbw).WriteMasterKey(pMasterKey.first, pMasterKey.second);
if (fWasLocked) {
Lock();
}
return true;
}
}
return false;
}
void CWallet::SetBestChain(const CBlockLocator &loc) {
CWalletDB walletdb(*dbw);
walletdb.WriteBestBlock(loc);
}
bool CWallet::SetMinVersion(enum WalletFeature nVersion, CWalletDB *pwalletdbIn,
bool fExplicit) {
// nWalletVersion
LOCK(cs_wallet);
if (nWalletVersion >= nVersion) {
return true;
}
// When doing an explicit upgrade, if we pass the max version permitted,
// upgrade all the way.
if (fExplicit && nVersion > nWalletMaxVersion) {
nVersion = FEATURE_LATEST;
}
nWalletVersion = nVersion;
if (nVersion > nWalletMaxVersion) {
nWalletMaxVersion = nVersion;
}
CWalletDB *pwalletdb = pwalletdbIn ? pwalletdbIn : new CWalletDB(*dbw);
if (nWalletVersion > 40000) {
pwalletdb->WriteMinVersion(nWalletVersion);
}
if (!pwalletdbIn) {
delete pwalletdb;
}
return true;
}
bool CWallet::SetMaxVersion(int nVersion) {
// nWalletVersion, nWalletMaxVersion
LOCK(cs_wallet);
// Cannot downgrade below current version
if (nWalletVersion > nVersion) {
return false;
}
nWalletMaxVersion = nVersion;
return true;
}
std::set<TxId> CWallet::GetConflicts(const TxId &txid) const {
std::set<TxId> result;
AssertLockHeld(cs_wallet);
std::map<TxId, CWalletTx>::const_iterator it = mapWallet.find(txid);
if (it == mapWallet.end()) {
return result;
}
const CWalletTx &wtx = it->second;
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range;
for (const CTxIn &txin : wtx.tx->vin) {
if (mapTxSpends.count(txin.prevout) <= 1) {
// No conflict if zero or one spends.
continue;
}
range = mapTxSpends.equal_range(txin.prevout);
for (TxSpends::const_iterator _it = range.first; _it != range.second;
++_it) {
result.insert(_it->second);
}
}
return result;
}
bool CWallet::HasWalletSpend(const TxId &txid) const {
AssertLockHeld(cs_wallet);
auto iter = mapTxSpends.lower_bound(COutPoint(txid, 0));
return (iter != mapTxSpends.end() && iter->first.GetTxId() == txid);
}
void CWallet::Flush(bool shutdown) {
dbw->Flush(shutdown);
}
void CWallet::SyncMetaData(
std::pair<TxSpends::iterator, TxSpends::iterator> range) {
// We want all the wallet transactions in range to have the same metadata as
// the oldest (smallest nOrderPos).
// So: find smallest nOrderPos:
int nMinOrderPos = std::numeric_limits<int>::max();
const CWalletTx *copyFrom = nullptr;
for (TxSpends::iterator it = range.first; it != range.second; ++it) {
const CWalletTx *wtx = &mapWallet.at(it->second);
if (wtx->nOrderPos < nMinOrderPos) {
nMinOrderPos = wtx->nOrderPos;
copyFrom = wtx;
}
}
// Now copy data from copyFrom to rest:
for (TxSpends::iterator it = range.first; it != range.second; ++it) {
const TxId &txid = it->second;
CWalletTx *copyTo = &mapWallet.at(txid);
if (copyFrom == copyTo) {
continue;
}
assert(
copyFrom &&
"Oldest wallet transaction in range assumed to have been found.");
if (!copyFrom->IsEquivalentTo(*copyTo)) {
continue;
}
copyTo->mapValue = copyFrom->mapValue;
copyTo->vOrderForm = copyFrom->vOrderForm;
// fTimeReceivedIsTxTime not copied on purpose nTimeReceived not copied
// on purpose.
copyTo->nTimeSmart = copyFrom->nTimeSmart;
copyTo->fFromMe = copyFrom->fFromMe;
copyTo->strFromAccount = copyFrom->strFromAccount;
// nOrderPos not copied on purpose cached members not copied on purpose.
}
}
/**
* Outpoint is spent if any non-conflicted transaction, spends it:
*/
bool CWallet::IsSpent(const TxId &txid, uint32_t n) const {
const COutPoint outpoint(txid, n);
std::pair<TxSpends::const_iterator, TxSpends::const_iterator> range =
mapTxSpends.equal_range(outpoint);
for (TxSpends::const_iterator it = range.first; it != range.second; ++it) {
const TxId &wtxid = it->second;
std::map<TxId, CWalletTx>::const_iterator mit = mapWallet.find(wtxid);
if (mit != mapWallet.end()) {
int depth = mit->second.GetDepthInMainChain();
if (depth > 0 || (depth == 0 && !mit->second.isAbandoned())) {
// Spent
return true;
}
}
}
return false;
}
void CWallet::AddToSpends(const COutPoint &outpoint, const TxId &wtxid) {
mapTxSpends.insert(std::make_pair(outpoint, wtxid));
std::pair<TxSpends::iterator, TxSpends::iterator> range;
range = mapTxSpends.equal_range(outpoint);
SyncMetaData(range);
}
void CWallet::AddToSpends(const TxId &wtxid) {
auto it = mapWallet.find(wtxid);
assert(it != mapWallet.end());
CWalletTx &thisTx = it->second;
// Coinbases don't spend anything!
if (thisTx.IsCoinBase()) {
return;
}
for (const CTxIn &txin : thisTx.tx->vin) {
AddToSpends(txin.prevout, wtxid);
}
}
bool CWallet::EncryptWallet(const SecureString &strWalletPassphrase) {
if (IsCrypted()) {
return false;
}
CKeyingMaterial _vMasterKey;
_vMasterKey.resize(WALLET_CRYPTO_KEY_SIZE);
GetStrongRandBytes(&_vMasterKey[0], WALLET_CRYPTO_KEY_SIZE);
CMasterKey kMasterKey;
kMasterKey.vchSalt.resize(WALLET_CRYPTO_SALT_SIZE);
GetStrongRandBytes(&kMasterKey.vchSalt[0], WALLET_CRYPTO_SALT_SIZE);
CCrypter crypter;
int64_t nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt, 25000,
kMasterKey.nDerivationMethod);
kMasterKey.nDeriveIterations =
2500000 / ((double)(GetTimeMillis() - nStartTime));
nStartTime = GetTimeMillis();
crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt,
kMasterKey.nDeriveIterations,
kMasterKey.nDerivationMethod);
kMasterKey.nDeriveIterations =
(kMasterKey.nDeriveIterations +
kMasterKey.nDeriveIterations * 100 /
((double)(GetTimeMillis() - nStartTime))) /
2;
if (kMasterKey.nDeriveIterations < 25000) {
kMasterKey.nDeriveIterations = 25000;
}
LogPrintf("Encrypting Wallet with an nDeriveIterations of %i\n",
kMasterKey.nDeriveIterations);
if (!crypter.SetKeyFromPassphrase(strWalletPassphrase, kMasterKey.vchSalt,
kMasterKey.nDeriveIterations,
kMasterKey.nDerivationMethod)) {
return false;
}
if (!crypter.Encrypt(_vMasterKey, kMasterKey.vchCryptedKey)) {
return false;
}
{
LOCK(cs_wallet);
mapMasterKeys[++nMasterKeyMaxID] = kMasterKey;
assert(!pwalletdbEncryption);
pwalletdbEncryption = new CWalletDB(*dbw);
if (!pwalletdbEncryption->TxnBegin()) {
delete pwalletdbEncryption;
pwalletdbEncryption = nullptr;
return false;
}
pwalletdbEncryption->WriteMasterKey(nMasterKeyMaxID, kMasterKey);
if (!EncryptKeys(_vMasterKey)) {
pwalletdbEncryption->TxnAbort();
delete pwalletdbEncryption;
// We now probably have half of our keys encrypted in memory, and
// half not... die and let the user reload the unencrypted wallet.
assert(false);
}
// Encryption was introduced in version 0.4.0
SetMinVersion(FEATURE_WALLETCRYPT, pwalletdbEncryption, true);
if (!pwalletdbEncryption->TxnCommit()) {
delete pwalletdbEncryption;
// We now have keys encrypted in memory, but not on disk... die to
// avoid confusion and let the user reload the unencrypted wallet.
assert(false);
}
delete pwalletdbEncryption;
pwalletdbEncryption = nullptr;
Lock();
Unlock(strWalletPassphrase);
// If we are using HD, replace the HD master key (seed) with a new one.
if (IsHDEnabled()) {
CKey key;
CPubKey masterPubKey = GenerateNewHDMasterKey();
// preserve the old chains version to not break backward
// compatibility
CHDChain oldChain = GetHDChain();
if (!SetHDMasterKey(masterPubKey, &oldChain)) {
return false;
}
}
NewKeyPool();
Lock();
// Need to completely rewrite the wallet file; if we don't, bdb might
// keep bits of the unencrypted private key in slack space in the
// database file.
dbw->Rewrite();
}
NotifyStatusChanged(this);
return true;
}
DBErrors CWallet::ReorderTransactions() {
LOCK(cs_wallet);
CWalletDB walletdb(*dbw);
// Old wallets didn't have any defined order for transactions. Probably a
// bad idea to change the output of this.
// First: get all CWalletTx and CAccountingEntry into a sorted-by-time
// multimap.
TxItems txByTime;
for (auto &entry : mapWallet) {
CWalletTx *wtx = &entry.second;
txByTime.insert(
std::make_pair(wtx->nTimeReceived, TxPair(wtx, nullptr)));
}
std::list<CAccountingEntry> acentries;
walletdb.ListAccountCreditDebit("", acentries);
for (CAccountingEntry &entry : acentries) {
txByTime.insert(std::make_pair(entry.nTime, TxPair(nullptr, &entry)));
}
nOrderPosNext = 0;
std::vector<int64_t> nOrderPosOffsets;
for (TxItems::iterator it = txByTime.begin(); it != txByTime.end(); ++it) {
CWalletTx *const pwtx = (*it).second.first;
CAccountingEntry *const pacentry = (*it).second.second;
int64_t &nOrderPos =
(pwtx != nullptr) ? pwtx->nOrderPos : pacentry->nOrderPos;
if (nOrderPos == -1) {
nOrderPos = nOrderPosNext++;
nOrderPosOffsets.push_back(nOrderPos);
if (pwtx) {
if (!walletdb.WriteTx(*pwtx)) {
return DBErrors::LOAD_FAIL;
}
} else if (!walletdb.WriteAccountingEntry(pacentry->nEntryNo,
*pacentry)) {
return DBErrors::LOAD_FAIL;
}
} else {
int64_t nOrderPosOff = 0;
for (const int64_t &nOffsetStart : nOrderPosOffsets) {
if (nOrderPos >= nOffsetStart) {
++nOrderPosOff;
}
}
nOrderPos += nOrderPosOff;
nOrderPosNext = std::max(nOrderPosNext, nOrderPos + 1);
if (!nOrderPosOff) {
continue;
}
// Since we're changing the order, write it back.
if (pwtx) {
if (!walletdb.WriteTx(*pwtx)) {
return DBErrors::LOAD_FAIL;
}
} else if (!walletdb.WriteAccountingEntry(pacentry->nEntryNo,
*pacentry)) {
return DBErrors::LOAD_FAIL;
}
}
}
walletdb.WriteOrderPosNext(nOrderPosNext);
return DBErrors::LOAD_OK;
}
int64_t CWallet::IncOrderPosNext(CWalletDB *pwalletdb) {
// nOrderPosNext
AssertLockHeld(cs_wallet);
int64_t nRet = nOrderPosNext++;
if (pwalletdb) {
pwalletdb->WriteOrderPosNext(nOrderPosNext);
} else {
CWalletDB(*dbw).WriteOrderPosNext(nOrderPosNext);
}
return nRet;
}
bool CWallet::AccountMove(std::string strFrom, std::string strTo,
const Amount nAmount, std::string strComment) {
CWalletDB walletdb(*dbw);
if (!walletdb.TxnBegin()) {
return false;
}
int64_t nNow = GetAdjustedTime();
// Debit
CAccountingEntry debit;
debit.nOrderPos = IncOrderPosNext(&walletdb);
debit.strAccount = strFrom;
debit.nCreditDebit = -nAmount;
debit.nTime = nNow;
debit.strOtherAccount = strTo;
debit.strComment = strComment;
AddAccountingEntry(debit, &walletdb);
// Credit
CAccountingEntry credit;
credit.nOrderPos = IncOrderPosNext(&walletdb);
credit.strAccount = strTo;
credit.nCreditDebit = nAmount;
credit.nTime = nNow;
credit.strOtherAccount = strFrom;
credit.strComment = strComment;
AddAccountingEntry(credit, &walletdb);
return walletdb.TxnCommit();
}
bool CWallet::GetLabelAddress(CPubKey &pubKey, const std::string &label,
bool bForceNew) {
CWalletDB walletdb(*dbw);
CAccount account;
walletdb.ReadAccount(label, account);
if (!bForceNew) {
if (!account.vchPubKey.IsValid()) {
bForceNew = true;
} else {
// Check if the current key has been used.
CScript scriptPubKey =
GetScriptForDestination(account.vchPubKey.GetID());
for (std::map<TxId, CWalletTx>::iterator it = mapWallet.begin();
it != mapWallet.end() && account.vchPubKey.IsValid(); ++it) {
for (const CTxOut &txout : (*it).second.tx->vout) {
if (txout.scriptPubKey == scriptPubKey) {
bForceNew = true;
break;
}
}
}
}
}
// Generate a new key
if (bForceNew) {
if (!GetKeyFromPool(account.vchPubKey, false)) {
return false;
}
SetAddressBook(account.vchPubKey.GetID(), label, "receive");
walletdb.WriteAccount(label, account);
}
pubKey = account.vchPubKey;
return true;
}
void CWallet::MarkDirty() {
LOCK(cs_wallet);
for (std::pair<const TxId, CWalletTx> &item : mapWallet) {
item.second.MarkDirty();
}
}
bool CWallet::AddToWallet(const CWalletTx &wtxIn, bool fFlushOnClose) {
LOCK(cs_wallet);
CWalletDB walletdb(*dbw, "r+", fFlushOnClose);
const TxId &txid = wtxIn.GetId();
// Inserts only if not already there, returns tx inserted or tx found.
std::pair<std::map<TxId, CWalletTx>::iterator, bool> ret =
mapWallet.insert(std::make_pair(txid, wtxIn));
CWalletTx &wtx = (*ret.first).second;
wtx.BindWallet(this);
bool fInsertedNew = ret.second;
if (fInsertedNew) {
wtx.nTimeReceived = GetAdjustedTime();
wtx.nOrderPos = IncOrderPosNext(&walletdb);
wtxOrdered.insert(std::make_pair(wtx.nOrderPos, TxPair(&wtx, nullptr)));
wtx.nTimeSmart = ComputeTimeSmart(wtx);
AddToSpends(txid);
}
bool fUpdated = false;
if (!fInsertedNew) {
// Merge
if (!wtxIn.hashUnset() && wtxIn.hashBlock != wtx.hashBlock) {
wtx.hashBlock = wtxIn.hashBlock;
fUpdated = true;
}
// If no longer abandoned, update
if (wtxIn.hashBlock.IsNull() && wtx.isAbandoned()) {
wtx.hashBlock = wtxIn.hashBlock;
fUpdated = true;
}
if (wtxIn.nIndex != -1 && (wtxIn.nIndex != wtx.nIndex)) {
wtx.nIndex = wtxIn.nIndex;
fUpdated = true;
}
if (wtxIn.fFromMe && wtxIn.fFromMe != wtx.fFromMe) {
wtx.fFromMe = wtxIn.fFromMe;
fUpdated = true;
}
}
//// debug print
LogPrintf("AddToWallet %s %s%s\n", wtxIn.GetId().ToString(),
(fInsertedNew ? "new" : ""), (fUpdated ? "update" : ""));
// Write to disk
if ((fInsertedNew || fUpdated) && !walletdb.WriteTx(wtx)) {
return false;
}
// Break debit/credit balance caches:
wtx.MarkDirty();
// Notify UI of new or updated transaction.
NotifyTransactionChanged(this, txid, fInsertedNew ? CT_NEW : CT_UPDATED);
// Notify an external script when a wallet transaction comes in or is
// updated.
std::string strCmd = gArgs.GetArg("-walletnotify", "");
if (!strCmd.empty()) {
boost::replace_all(strCmd, "%s", wtxIn.GetId().GetHex());
std::thread t(runCommand, strCmd);
// Thread runs free.
t.detach();
}
return true;
}
bool CWallet::LoadToWallet(const CWalletTx &wtxIn) {
const TxId &txid = wtxIn.GetId();
CWalletTx &wtx = mapWallet.emplace(txid, wtxIn).first->second;
wtx.BindWallet(this);
wtxOrdered.insert(std::make_pair(wtx.nOrderPos, TxPair(&wtx, nullptr)));
AddToSpends(txid);
for (const CTxIn &txin : wtx.tx->vin) {
auto it = mapWallet.find(txin.prevout.GetTxId());
if (it != mapWallet.end()) {
CWalletTx &prevtx = it->second;
if (prevtx.nIndex == -1 && !prevtx.hashUnset()) {
MarkConflicted(prevtx.hashBlock, wtx.GetId());
}
}
}
return true;
}
/**
* Add a transaction to the wallet, or update it. pIndex and posInBlock should
* be set when the transaction was known to be included in a block. When pIndex
* == nullptr, then wallet state is not updated in AddToWallet, but
* notifications happen and cached balances are marked dirty.
*
* If fUpdate is true, existing transactions will be updated.
* TODO: One exception to this is that the abandoned state is cleared under the
* assumption that any further notification of a transaction that was considered
* abandoned is an indication that it is not safe to be considered abandoned.
* Abandoned state should probably be more carefuly tracked via different
* posInBlock signals or by checking mempool presence when necessary.
*/
bool CWallet::AddToWalletIfInvolvingMe(const CTransactionRef &ptx,
const CBlockIndex *pIndex,
int posInBlock, bool fUpdate) {
const CTransaction &tx = *ptx;
AssertLockHeld(cs_wallet);
if (pIndex != nullptr) {
for (const CTxIn &txin : tx.vin) {
std::pair<TxSpends::const_iterator, TxSpends::const_iterator>
range = mapTxSpends.equal_range(txin.prevout);
while (range.first != range.second) {
if (range.first->second != tx.GetId()) {
LogPrintf("Transaction %s (in block %s) conflicts with "
"wallet transaction %s (both spend %s:%i)\n",
tx.GetId().ToString(),
pIndex->GetBlockHash().ToString(),
range.first->second.ToString(),
range.first->first.GetTxId().ToString(),
range.first->first.GetN());
MarkConflicted(pIndex->GetBlockHash(), range.first->second);
}
range.first++;
}
}
}
bool fExisted = mapWallet.count(tx.GetId()) != 0;
if (fExisted && !fUpdate) {
return false;
}
if (fExisted || IsMine(tx) || IsFromMe(tx)) {
/**
* Check if any keys in the wallet keypool that were supposed to be
* unused have appeared in a new transaction. If so, remove those keys
* from the keypool. This can happen when restoring an old wallet backup
* that does not contain the mostly recently created transactions from
* newer versions of the wallet.
*/
// loop though all outputs
for (const CTxOut &txout : tx.vout) {
// extract addresses and check if they match with an unused keypool
// key
std::vector<CKeyID> vAffected;
CAffectedKeysVisitor(*this, vAffected).Process(txout.scriptPubKey);
for (const CKeyID &keyid : vAffected) {
std::map<CKeyID, int64_t>::const_iterator mi =
m_pool_key_to_index.find(keyid);
if (mi != m_pool_key_to_index.end()) {
LogPrintf("%s: Detected a used keypool key, mark all "
"keypool key up to this key as used\n",
__func__);
MarkReserveKeysAsUsed(mi->second);
if (!TopUpKeyPool()) {
LogPrintf(
"%s: Topping up keypool failed (locked wallet)\n",
__func__);
}
}
}
}
CWalletTx wtx(this, ptx);
// Get merkle branch if transaction was found in a block
if (pIndex != nullptr) {
wtx.SetMerkleBranch(pIndex, posInBlock);
}
return AddToWallet(wtx, false);
}
return false;
}
bool CWallet::TransactionCanBeAbandoned(const TxId &txid) const {
LOCK2(cs_main, cs_wallet);
const CWalletTx *wtx = GetWalletTx(txid);
return wtx && !wtx->isAbandoned() && wtx->GetDepthInMainChain() <= 0 &&
!wtx->InMempool();
}
bool CWallet::AbandonTransaction(const TxId &txid) {
LOCK2(cs_main, cs_wallet);
CWalletDB walletdb(*dbw, "r+");
std::set<TxId> todo;
std::set<TxId> done;
// Can't mark abandoned if confirmed or in mempool
auto it = mapWallet.find(txid);
assert(it != mapWallet.end());
CWalletTx &origtx = it->second;
if (origtx.GetDepthInMainChain() > 0 || origtx.InMempool()) {
return false;
}
todo.insert(txid);
while (!todo.empty()) {
const TxId now = *todo.begin();
todo.erase(now);
done.insert(now);
it = mapWallet.find(now);
assert(it != mapWallet.end());
CWalletTx &wtx = it->second;
int currentconfirm = wtx.GetDepthInMainChain();
// If the orig tx was not in block, none of its spends can be.
assert(currentconfirm <= 0);
// If (currentconfirm < 0) {Tx and spends are already conflicted, no
// need to abandon}
if (currentconfirm == 0 && !wtx.isAbandoned()) {
// If the orig tx was not in block/mempool, none of its spends can
// be in mempool.
assert(!wtx.InMempool());
wtx.nIndex = -1;
wtx.setAbandoned();
wtx.MarkDirty();
walletdb.WriteTx(wtx);
NotifyTransactionChanged(this, wtx.GetId(), CT_UPDATED);
// Iterate over all its outputs, and mark transactions in the wallet
// that spend them abandoned too.
TxSpends::const_iterator iter =
mapTxSpends.lower_bound(COutPoint(now, 0));
while (iter != mapTxSpends.end() && iter->first.GetTxId() == now) {
if (!done.count(iter->second)) {
todo.insert(iter->second);
}
iter++;
}
// If a transaction changes 'conflicted' state, that changes the
// balance available of the outputs it spends. So force those to be
// recomputed.
for (const CTxIn &txin : wtx.tx->vin) {
auto it2 = mapWallet.find(txin.prevout.GetTxId());
if (it2 != mapWallet.end()) {
it2->second.MarkDirty();
}
}
}
}
return true;
}
void CWallet::MarkConflicted(const uint256 &hashBlock, const TxId &txid) {
LOCK2(cs_main, cs_wallet);
int conflictconfirms = 0;
if (mapBlockIndex.count(hashBlock)) {
CBlockIndex *pindex = mapBlockIndex[hashBlock];
if (chainActive.Contains(pindex)) {
conflictconfirms = -(chainActive.Height() - pindex->nHeight + 1);
}
}
// If number of conflict confirms cannot be determined, this means that the
// block is still unknown or not yet part of the main chain, for example
// when loading the wallet during a reindex. Do nothing in that case.
if (conflictconfirms >= 0) {
return;
}
// Do not flush the wallet here for performance reasons.
CWalletDB walletdb(*dbw, "r+", false);
std::set<TxId> todo;
std::set<TxId> done;
todo.insert(txid);
while (!todo.empty()) {
const TxId now = *todo.begin();
todo.erase(now);
done.insert(now);
auto it = mapWallet.find(now);
assert(it != mapWallet.end());
CWalletTx &wtx = it->second;
int currentconfirm = wtx.GetDepthInMainChain();
if (conflictconfirms < currentconfirm) {
// Block is 'more conflicted' than current confirm; update.
// Mark transaction as conflicted with this block.
wtx.nIndex = -1;
wtx.hashBlock = hashBlock;
wtx.MarkDirty();
walletdb.WriteTx(wtx);
// Iterate over all its outputs, and mark transactions in the wallet
// that spend them conflicted too.
TxSpends::const_iterator iter =
mapTxSpends.lower_bound(COutPoint(now, 0));
while (iter != mapTxSpends.end() && iter->first.GetTxId() == now) {
if (!done.count(iter->second)) {
todo.insert(iter->second);
}
iter++;
}
// If a transaction changes 'conflicted' state, that changes the
// balance available of the outputs it spends. So force those to be
// recomputed.
for (const CTxIn &txin : wtx.tx->vin) {
auto it2 = mapWallet.find(txin.prevout.GetTxId());
if (it2 != mapWallet.end()) {
it2->second.MarkDirty();
}
}
}
}
}
void CWallet::SyncTransaction(const CTransactionRef &ptx,
const CBlockIndex *pindex, int posInBlock) {
const CTransaction &tx = *ptx;
if (!AddToWalletIfInvolvingMe(ptx, pindex, posInBlock, true)) {
// Not one of ours
return;
}
// If a transaction changes 'conflicted' state, that changes the balance
// available of the outputs it spends. So force those to be
// recomputed, also:
for (const CTxIn &txin : tx.vin) {
auto it = mapWallet.find(txin.prevout.GetTxId());
if (it != mapWallet.end()) {
it->second.MarkDirty();
}
}
}
void CWallet::TransactionAddedToMempool(const CTransactionRef &ptx) {
LOCK2(cs_main, cs_wallet);
SyncTransaction(ptx);
auto it = mapWallet.find(ptx->GetId());
if (it != mapWallet.end()) {
it->second.fInMempool = true;
}
}
void CWallet::TransactionRemovedFromMempool(const CTransactionRef &ptx) {
LOCK(cs_wallet);
auto it = mapWallet.find(ptx->GetId());
if (it != mapWallet.end()) {
it->second.fInMempool = false;
}
}
void CWallet::BlockConnected(
const std::shared_ptr<const CBlock> &pblock, const CBlockIndex *pindex,
const std::vector<CTransactionRef> &vtxConflicted) {
LOCK2(cs_main, cs_wallet);
// TODO: Tempoarily ensure that mempool removals are notified before
// connected transactions. This shouldn't matter, but the abandoned state of
// transactions in our wallet is currently cleared when we receive another
// notification and there is a race condition where notification of a
// connected conflict might cause an outside process to abandon a
// transaction and then have it inadvertantly cleared by the notification
// that the conflicted transaction was evicted.
for (const CTransactionRef &ptx : vtxConflicted) {
SyncTransaction(ptx);
TransactionRemovedFromMempool(ptx);
}
for (size_t i = 0; i < pblock->vtx.size(); i++) {
SyncTransaction(pblock->vtx[i], pindex, i);
TransactionRemovedFromMempool(pblock->vtx[i]);
}
m_last_block_processed = pindex;
}
void CWallet::BlockDisconnected(const std::shared_ptr<const CBlock> &pblock) {
LOCK2(cs_main, cs_wallet);
for (const CTransactionRef &ptx : pblock->vtx) {
SyncTransaction(ptx);
}
}
void CWallet::BlockUntilSyncedToCurrentChain() {
AssertLockNotHeld(cs_main);
AssertLockNotHeld(cs_wallet);
{
// Skip the queue-draining stuff if we know we're caught up with
// chainActive.Tip()...
// We could also take cs_wallet here, and call m_last_block_processed
// protected by cs_wallet instead of cs_main, but as long as we need
// cs_main here anyway, its easier to just call it cs_main-protected.
LOCK(cs_main);
const CBlockIndex *initialChainTip = chainActive.Tip();
if (m_last_block_processed->GetAncestor(initialChainTip->nHeight) ==
initialChainTip) {
return;
}
}
// ...otherwise put a callback in the validation interface queue and wait
// for the queue to drain enough to execute it (indicating we are caught up
// at least with the time we entered this function).
SyncWithValidationInterfaceQueue();
}
isminetype CWallet::IsMine(const CTxIn &txin) const {
LOCK(cs_wallet);
std::map<TxId, CWalletTx>::const_iterator mi =
mapWallet.find(txin.prevout.GetTxId());
if (mi != mapWallet.end()) {
const CWalletTx &prev = (*mi).second;
if (txin.prevout.GetN() < prev.tx->vout.size()) {
return IsMine(prev.tx->vout[txin.prevout.GetN()]);
}
}
return ISMINE_NO;
}
// Note that this function doesn't distinguish between a 0-valued input, and a
// not-"is mine" (according to the filter) input.
Amount CWallet::GetDebit(const CTxIn &txin, const isminefilter &filter) const {
LOCK(cs_wallet);
std::map<TxId, CWalletTx>::const_iterator mi =
mapWallet.find(txin.prevout.GetTxId());
if (mi != mapWallet.end()) {
const CWalletTx &prev = (*mi).second;
if (txin.prevout.GetN() < prev.tx->vout.size()) {
if (IsMine(prev.tx->vout[txin.prevout.GetN()]) & filter) {
return prev.tx->vout[txin.prevout.GetN()].nValue;
}
}
}
return Amount::zero();
}
isminetype CWallet::IsMine(const CTxOut &txout) const {
return ::IsMine(*this, txout.scriptPubKey);
}
Amount CWallet::GetCredit(const CTxOut &txout,
const isminefilter &filter) const {
if (!MoneyRange(txout.nValue)) {
throw std::runtime_error(std::string(__func__) +
": value out of range");
}
return (IsMine(txout) & filter) ? txout.nValue : Amount::zero();
}
bool CWallet::IsChange(const CTxOut &txout) const {
// TODO: fix handling of 'change' outputs. The assumption is that any
// payment to a script that is ours, but is not in the address book is
// change. That assumption is likely to break when we implement
// multisignature wallets that return change back into a
// multi-signature-protected address; a better way of identifying which
// outputs are 'the send' and which are 'the change' will need to be
// implemented (maybe extend CWalletTx to remember which output, if any, was
// change).
if (::IsMine(*this, txout.scriptPubKey)) {
CTxDestination address;
if (!ExtractDestination(txout.scriptPubKey, address)) {
return true;
}
LOCK(cs_wallet);
if (!mapAddressBook.count(address)) {
return true;
}
}
return false;
}
Amount CWallet::GetChange(const CTxOut &txout) const {
if (!MoneyRange(txout.nValue)) {
throw std::runtime_error(std::string(__func__) +
": value out of range");
}
return (IsChange(txout) ? txout.nValue : Amount::zero());
}
bool CWallet::IsMine(const CTransaction &tx) const {
for (const CTxOut &txout : tx.vout) {
if (IsMine(txout)) {
return true;
}
}
return false;
}
bool CWallet::IsFromMe(const CTransaction &tx) const {
return GetDebit(tx, ISMINE_ALL) > Amount::zero();
}
Amount CWallet::GetDebit(const CTransaction &tx,
const isminefilter &filter) const {
Amount nDebit = Amount::zero();
for (const CTxIn &txin : tx.vin) {
nDebit += GetDebit(txin, filter);
if (!MoneyRange(nDebit)) {
throw std::runtime_error(std::string(__func__) +
": value out of range");
}
}
return nDebit;
}
bool CWallet::IsAllFromMe(const CTransaction &tx,
const isminefilter &filter) const {
LOCK(cs_wallet);
for (const CTxIn &txin : tx.vin) {
auto mi = mapWallet.find(txin.prevout.GetTxId());
if (mi == mapWallet.end()) {
// Any unknown inputs can't be from us.
return false;
}
const CWalletTx &prev = (*mi).second;
if (txin.prevout.GetN() >= prev.tx->vout.size()) {
// Invalid input!
return false;
}
if (!(IsMine(prev.tx->vout[txin.prevout.GetN()]) & filter)) {
return false;
}
}
return true;
}
Amount CWallet::GetCredit(const CTransaction &tx,
const isminefilter &filter) const {
Amount nCredit = Amount::zero();
for (const CTxOut &txout : tx.vout) {
nCredit += GetCredit(txout, filter);
if (!MoneyRange(nCredit)) {
throw std::runtime_error(std::string(__func__) +
": value out of range");
}
}
return nCredit;
}
Amount CWallet::GetChange(const CTransaction &tx) const {
Amount nChange = Amount::zero();
for (const CTxOut &txout : tx.vout) {
nChange += GetChange(txout);
if (!MoneyRange(nChange)) {
throw std::runtime_error(std::string(__func__) +
": value out of range");
}
}
return nChange;
}
CPubKey CWallet::GenerateNewHDMasterKey() {
CKey key;
key.MakeNewKey(true);
int64_t nCreationTime = GetTime();
CKeyMetadata metadata(nCreationTime);
// Calculate the pubkey.
CPubKey pubkey = key.GetPubKey();
assert(key.VerifyPubKey(pubkey));
// Set the hd keypath to "m" -> Master, refers the masterkeyid to itself.
metadata.hdKeypath = "m";
metadata.hdMasterKeyID = pubkey.GetID();
LOCK(cs_wallet);
// mem store the metadata
mapKeyMetadata[pubkey.GetID()] = metadata;
// Write the key&metadata to the database.
if (!AddKeyPubKey(key, pubkey)) {
throw std::runtime_error(std::string(__func__) +
": AddKeyPubKey failed");
}
return pubkey;
}
bool CWallet::SetHDMasterKey(const CPubKey &pubkey,
CHDChain *possibleOldChain) {
LOCK(cs_wallet);
// Store the keyid (hash160) together with the child index counter in the
// database as a hdchain object.
CHDChain newHdChain;
if (possibleOldChain) {
// preserve the old chains version
newHdChain.nVersion = possibleOldChain->nVersion;
}
newHdChain.masterKeyID = pubkey.GetID();
SetHDChain(newHdChain, false);
return true;
}
bool CWallet::SetHDChain(const CHDChain &chain, bool memonly) {
LOCK(cs_wallet);
if (!memonly && !CWalletDB(*dbw).WriteHDChain(chain)) {
throw std::runtime_error(std::string(__func__) +
": writing chain failed");
}
hdChain = chain;
return true;
}
bool CWallet::IsHDEnabled() {
return !hdChain.masterKeyID.IsNull();
}
int64_t CWalletTx::GetTxTime() const {
int64_t n = nTimeSmart;
return n ? n : nTimeReceived;
}
int CWalletTx::GetRequestCount() const {
LOCK(pwallet->cs_wallet);
// Returns -1 if it wasn't being tracked.
int nRequests = -1;
if (IsCoinBase()) {
// Generated block.
if (!hashUnset()) {
std::map<uint256, int>::const_iterator mi =
pwallet->mapRequestCount.find(hashBlock);
if (mi != pwallet->mapRequestCount.end()) {
nRequests = (*mi).second;
}
}
} else {
// Did anyone request this transaction?
std::map<uint256, int>::const_iterator mi =
pwallet->mapRequestCount.find(GetId());
if (mi != pwallet->mapRequestCount.end()) {
nRequests = (*mi).second;
// How about the block it's in?
if (nRequests == 0 && !hashUnset()) {
std::map<uint256, int>::const_iterator _mi =
pwallet->mapRequestCount.find(hashBlock);
if (_mi != pwallet->mapRequestCount.end()) {
nRequests = (*_mi).second;
} else {
// If it's in someone else's block it must have got out.
nRequests = 1;
}
}
}
}
return nRequests;
}
void CWalletTx::GetAmounts(std::list<COutputEntry> &listReceived,
std::list<COutputEntry> &listSent, Amount &nFee,
std::string &strSentAccount,
const isminefilter &filter) const {
nFee = Amount::zero();
listReceived.clear();
listSent.clear();
strSentAccount = strFromAccount;
// Compute fee:
Amount nDebit = GetDebit(filter);
// debit>0 means we signed/sent this transaction.
if (nDebit > Amount::zero()) {
Amount nValueOut = tx->GetValueOut();
nFee = (nDebit - nValueOut);
}
// Sent/received.
for (unsigned int i = 0; i < tx->vout.size(); ++i) {
const CTxOut &txout = tx->vout[i];
isminetype fIsMine = pwallet->IsMine(txout);
// Only need to handle txouts if AT LEAST one of these is true:
// 1) they debit from us (sent)
// 2) the output is to us (received)
if (nDebit > Amount::zero()) {
// Don't report 'change' txouts
if (pwallet->IsChange(txout)) {
continue;
}
} else if (!(fIsMine & filter)) {
continue;
}
// In either case, we need to get the destination address.
CTxDestination address;
if (!ExtractDestination(txout.scriptPubKey, address) &&
!txout.scriptPubKey.IsUnspendable()) {
LogPrintf("CWalletTx::GetAmounts: Unknown transaction type found, "
"txid %s\n",
this->GetId().ToString());
address = CNoDestination();
}
COutputEntry output = {address, txout.nValue, (int)i};
// If we are debited by the transaction, add the output as a "sent"
// entry.
if (nDebit > Amount::zero()) {
listSent.push_back(output);
}
// If we are receiving the output, add it as a "received" entry.
if (fIsMine & filter) {
listReceived.push_back(output);
}
}
}
/**
* Scan active chain for relevant transactions after importing keys. This should
* be called whenever new keys are added to the wallet, with the oldest key
* creation time.
*
* @return Earliest timestamp that could be successfully scanned from. Timestamp
* returned will be higher than startTime if relevant blocks could not be read.
*/
int64_t CWallet::RescanFromTime(int64_t startTime, bool update) {
AssertLockHeld(cs_main);
AssertLockHeld(cs_wallet);
// Find starting block. May be null if nCreateTime is greater than the
// highest blockchain timestamp, in which case there is nothing that needs
// to be scanned.
CBlockIndex *const startBlock =
chainActive.FindEarliestAtLeast(startTime - TIMESTAMP_WINDOW);
LogPrintf("%s: Rescanning last %i blocks\n", __func__,
startBlock ? chainActive.Height() - startBlock->nHeight + 1 : 0);
if (startBlock) {
const CBlockIndex *const failedBlock =
ScanForWalletTransactions(startBlock, nullptr, update);
if (failedBlock) {
return failedBlock->GetBlockTimeMax() + TIMESTAMP_WINDOW + 1;
}
}
return startTime;
}
/**
* Scan the block chain (starting in pindexStart) for transactions from or to
* us. If fUpdate is true, found transactions that already exist in the wallet
* will be updated.
*
* Returns null if scan was successful. Otherwise, if a complete rescan was not
* possible (due to pruning or corruption), returns pointer to the most recent
* block that could not be scanned.
*
* If pindexStop is not a nullptr, the scan will stop at the block-index defined
* by pindexStop.
*/
CBlockIndex *CWallet::ScanForWalletTransactions(CBlockIndex *pindexStart,
CBlockIndex *pindexStop,
bool fUpdate) {
int64_t nNow = GetTime();
if (pindexStop) {
assert(pindexStop->nHeight >= pindexStart->nHeight);
}
CBlockIndex *pindex = pindexStart;
CBlockIndex *ret = nullptr;
LOCK2(cs_main, cs_wallet);
fAbortRescan = false;
fScanningWallet = true;
// Show rescan progress in GUI as dialog or on splashscreen, if -rescan on
// startup.
ShowProgress(_("Rescanning..."), 0);
double dProgressStart =
GuessVerificationProgress(chainParams.TxData(), pindex);
double dProgressTip =
GuessVerificationProgress(chainParams.TxData(), chainActive.Tip());
while (pindex && !fAbortRescan) {
if (pindex->nHeight % 100 == 0 && dProgressTip - dProgressStart > 0.0) {
ShowProgress(
_("Rescanning..."),
std::max(1,
std::min<int>(99, (GuessVerificationProgress(
chainParams.TxData(), pindex) -
dProgressStart) /
(dProgressTip - dProgressStart) *
100)));
}
CBlock block;
if (ReadBlockFromDisk(block, pindex, GetConfig())) {
for (size_t posInBlock = 0; posInBlock < block.vtx.size();
++posInBlock) {
AddToWalletIfInvolvingMe(block.vtx[posInBlock], pindex,
posInBlock, fUpdate);
}
} else {
ret = pindex;
}
if (pindex == pindexStop) {
break;
}
pindex = chainActive.Next(pindex);
if (GetTime() >= nNow + 60) {
nNow = GetTime();
LogPrintf("Still rescanning. At block %d. Progress=%f\n",
pindex->nHeight,
GuessVerificationProgress(chainParams.TxData(), pindex));
}
}
if (pindex && fAbortRescan) {
LogPrintf("Rescan aborted at block %d. Progress=%f\n", pindex->nHeight,
GuessVerificationProgress(chainParams.TxData(), pindex));
}
// Hide progress dialog in GUI.
ShowProgress(_("Rescanning..."), 100);
fScanningWallet = false;
return ret;
}
void CWallet::ReacceptWalletTransactions() {
// If transactions aren't being broadcasted, don't let them into local
// mempool either.
if (!fBroadcastTransactions) {
return;
}
LOCK2(cs_main, cs_wallet);
std::map<int64_t, CWalletTx *> mapSorted;
// Sort pending wallet transactions based on their initial wallet insertion
// order.
for (std::pair<const TxId, CWalletTx> &item : mapWallet) {
const TxId &wtxid = item.first;
CWalletTx &wtx = item.second;
assert(wtx.GetId() == wtxid);
int nDepth = wtx.GetDepthInMainChain();
if (!wtx.IsCoinBase() && (nDepth == 0 && !wtx.isAbandoned())) {
mapSorted.insert(std::make_pair(wtx.nOrderPos, &wtx));
}
}
// Try to add wallet transactions to memory pool.
for (std::pair<const int64_t, CWalletTx *> &item : mapSorted) {
CWalletTx &wtx = *(item.second);
LOCK(g_mempool.cs);
CValidationState state;
wtx.AcceptToMemoryPool(maxTxFee, state);
}
}
bool CWalletTx::RelayWalletTransaction(CConnman *connman) {
assert(pwallet->GetBroadcastTransactions());
if (IsCoinBase() || isAbandoned() || GetDepthInMainChain() != 0) {
return false;
}
CValidationState state;
// GetDepthInMainChain already catches known conflicts.
if (InMempool() || AcceptToMemoryPool(maxTxFee, state)) {
LogPrintf("Relaying wtx %s\n", GetId().ToString());
if (connman) {
CInv inv(MSG_TX, GetId());
connman->ForEachNode(
[&inv](CNode *pnode) { pnode->PushInventory(inv); });
return true;
}
}
return false;
}
std::set<TxId> CWalletTx::GetConflicts() const {
std::set<TxId> result;
if (pwallet != nullptr) {
const TxId &txid = GetId();
result = pwallet->GetConflicts(txid);
result.erase(txid);
}
return result;
}
Amount CWalletTx::GetDebit(const isminefilter &filter) const {
if (tx->vin.empty()) {
return Amount::zero();
}
Amount debit = Amount::zero();
if (filter & ISMINE_SPENDABLE) {
if (fDebitCached) {
debit += nDebitCached;
} else {
nDebitCached = pwallet->GetDebit(*this, ISMINE_SPENDABLE);
fDebitCached = true;
debit += nDebitCached;
}
}
if (filter & ISMINE_WATCH_ONLY) {
if (fWatchDebitCached) {
debit += nWatchDebitCached;
} else {
nWatchDebitCached = pwallet->GetDebit(*this, ISMINE_WATCH_ONLY);
fWatchDebitCached = true;
debit += Amount(nWatchDebitCached);
}
}
return debit;
}
Amount CWalletTx::GetCredit(const isminefilter &filter) const {
// Must wait until coinbase is safely deep enough in the chain before
// valuing it.
if (IsImmatureCoinBase()) {
return Amount::zero();
}
Amount credit = Amount::zero();
if (filter & ISMINE_SPENDABLE) {
// GetBalance can assume transactions in mapWallet won't change.
if (fCreditCached) {
credit += nCreditCached;
} else {
nCreditCached = pwallet->GetCredit(*this, ISMINE_SPENDABLE);
fCreditCached = true;
credit += nCreditCached;
}
}
if (filter & ISMINE_WATCH_ONLY) {
if (fWatchCreditCached) {
credit += nWatchCreditCached;
} else {
nWatchCreditCached = pwallet->GetCredit(*this, ISMINE_WATCH_ONLY);
fWatchCreditCached = true;
credit += nWatchCreditCached;
}
}
return credit;
}
Amount CWalletTx::GetImmatureCredit(bool fUseCache) const {
if (IsImmatureCoinBase() && IsInMainChain()) {
if (fUseCache && fImmatureCreditCached) {
return nImmatureCreditCached;
}
nImmatureCreditCached = pwallet->GetCredit(*this, ISMINE_SPENDABLE);
fImmatureCreditCached = true;
return nImmatureCreditCached;
}
return Amount::zero();
}
Amount CWalletTx::GetAvailableCredit(bool fUseCache) const {
if (pwallet == nullptr) {
return Amount::zero();
}
// Must wait until coinbase is safely deep enough in the chain before
// valuing it.
if (IsImmatureCoinBase()) {
return Amount::zero();
}
if (fUseCache && fAvailableCreditCached) {
return nAvailableCreditCached;
}
Amount nCredit = Amount::zero();
for (uint32_t i = 0; i < tx->vout.size(); i++) {
if (!pwallet->IsSpent(GetId(), i)) {
const CTxOut &txout = tx->vout[i];
nCredit += pwallet->GetCredit(txout, ISMINE_SPENDABLE);
if (!MoneyRange(nCredit)) {
throw std::runtime_error(
"CWalletTx::GetAvailableCredit() : value out of range");
}
}
}
nAvailableCreditCached = nCredit;
fAvailableCreditCached = true;
return nCredit;
}
Amount CWalletTx::GetImmatureWatchOnlyCredit(const bool fUseCache) const {
if (IsImmatureCoinBase() && IsInMainChain()) {
if (fUseCache && fImmatureWatchCreditCached) {
return nImmatureWatchCreditCached;
}
nImmatureWatchCreditCached =
pwallet->GetCredit(*this, ISMINE_WATCH_ONLY);
fImmatureWatchCreditCached = true;
return nImmatureWatchCreditCached;
}
return Amount::zero();
}
Amount CWalletTx::GetAvailableWatchOnlyCredit(const bool fUseCache) const {
if (pwallet == nullptr) {
return Amount::zero();
}
// Must wait until coinbase is safely deep enough in the chain before
// valuing it.
if (IsCoinBase() && GetBlocksToMaturity() > 0) {
return Amount::zero();
}
if (fUseCache && fAvailableWatchCreditCached) {
return nAvailableWatchCreditCached;
}
Amount nCredit = Amount::zero();
for (uint32_t i = 0; i < tx->vout.size(); i++) {
if (!pwallet->IsSpent(GetId(), i)) {
const CTxOut &txout = tx->vout[i];
nCredit += pwallet->GetCredit(txout, ISMINE_WATCH_ONLY);
if (!MoneyRange(nCredit)) {
throw std::runtime_error(
"CWalletTx::GetAvailableCredit() : value out of range");
}
}
}
nAvailableWatchCreditCached = nCredit;
fAvailableWatchCreditCached = true;
return nCredit;
}
Amount CWalletTx::GetChange() const {
if (fChangeCached) {
return nChangeCached;
}
nChangeCached = pwallet->GetChange(*this);
fChangeCached = true;
return nChangeCached;
}
bool CWalletTx::InMempool() const {
return fInMempool;
}
bool CWalletTx::IsTrusted() const {
// Quick answer in most cases
if (!CheckFinalTx(*this)) {
return false;
}
int nDepth = GetDepthInMainChain();
if (nDepth >= 1) {
return true;
}
if (nDepth < 0) {
return false;
}
// using wtx's cached debit
if (!bSpendZeroConfChange || !IsFromMe(ISMINE_ALL)) {
return false;
}
// Don't trust unconfirmed transactions from us unless they are in the
// mempool.
if (!InMempool()) {
return false;
}
// Trusted if all inputs are from us and are in the mempool:
for (const CTxIn &txin : tx->vin) {
// Transactions not sent by us: not trusted
const CWalletTx *parent = pwallet->GetWalletTx(txin.prevout.GetTxId());
if (parent == nullptr) {
return false;
}
const CTxOut &parentOut = parent->tx->vout[txin.prevout.GetN()];
if (pwallet->IsMine(parentOut) != ISMINE_SPENDABLE) {
return false;
}
}
return true;
}
bool CWalletTx::IsEquivalentTo(const CWalletTx &_tx) const {
CMutableTransaction tx1 = *this->tx;
CMutableTransaction tx2 = *_tx.tx;
for (CTxIn &in : tx1.vin) {
in.scriptSig = CScript();
}
for (CTxIn &in : tx2.vin) {
in.scriptSig = CScript();
}
return CTransaction(tx1) == CTransaction(tx2);
}
std::vector<uint256>
CWallet::ResendWalletTransactionsBefore(int64_t nTime, CConnman *connman) {
std::vector<uint256> result;
LOCK(cs_wallet);
// Sort them in chronological order
std::multimap<unsigned int, CWalletTx *> mapSorted;
for (std::pair<const TxId, CWalletTx> &item : mapWallet) {
CWalletTx &wtx = item.second;
// Don't rebroadcast if newer than nTime:
if (wtx.nTimeReceived > nTime) {
continue;
}
mapSorted.insert(std::make_pair(wtx.nTimeReceived, &wtx));
}
for (std::pair<const unsigned int, CWalletTx *> &item : mapSorted) {
CWalletTx &wtx = *item.second;
if (wtx.RelayWalletTransaction(connman)) {
result.push_back(wtx.GetId());
}
}
return result;
}
void CWallet::ResendWalletTransactions(int64_t nBestBlockTime,
CConnman *connman) {
// Do this infrequently and randomly to avoid giving away that these are our
// transactions.
if (GetTime() < nNextResend || !fBroadcastTransactions) {
return;
}
bool fFirst = (nNextResend == 0);
nNextResend = GetTime() + GetRand(30 * 60);
if (fFirst) {
return;
}
// Only do it if there's been a new block since last time
if (nBestBlockTime < nLastResend) {
return;
}
nLastResend = GetTime();
// Rebroadcast unconfirmed txes older than 5 minutes before the last block
// was found:
std::vector<uint256> relayed =
ResendWalletTransactionsBefore(nBestBlockTime - 5 * 60, connman);
if (!relayed.empty()) {
LogPrintf("%s: rebroadcast %u unconfirmed transactions\n", __func__,
relayed.size());
}
}
/** @} */ // end of mapWallet
/**
* @defgroup Actions
*
* @{
*/
Amount CWallet::GetBalance() const {
LOCK2(cs_main, cs_wallet);
Amount nTotal = Amount::zero();
for (const auto &entry : mapWallet) {
const CWalletTx *pcoin = &entry.second;
if (pcoin->IsTrusted()) {
nTotal += pcoin->GetAvailableCredit();
}
}
return nTotal;
}
Amount CWallet::GetUnconfirmedBalance() const {
LOCK2(cs_main, cs_wallet);
Amount nTotal = Amount::zero();
for (const auto &entry : mapWallet) {
const CWalletTx *pcoin = &entry.second;
if (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0 &&
pcoin->InMempool()) {
nTotal += pcoin->GetAvailableCredit();
}
}
return nTotal;
}
Amount CWallet::GetImmatureBalance() const {
LOCK2(cs_main, cs_wallet);
Amount nTotal = Amount::zero();
for (const auto &entry : mapWallet) {
const CWalletTx *pcoin = &entry.second;
nTotal += pcoin->GetImmatureCredit();
}
return nTotal;
}
Amount CWallet::GetWatchOnlyBalance() const {
LOCK2(cs_main, cs_wallet);
Amount nTotal = Amount::zero();
for (const auto &entry : mapWallet) {
const CWalletTx *pcoin = &entry.second;
if (pcoin->IsTrusted()) {
nTotal += pcoin->GetAvailableWatchOnlyCredit();
}
}
return nTotal;
}
Amount CWallet::GetUnconfirmedWatchOnlyBalance() const {
LOCK2(cs_main, cs_wallet);
Amount nTotal = Amount::zero();
for (const auto &entry : mapWallet) {
const CWalletTx *pcoin = &entry.second;
if (!pcoin->IsTrusted() && pcoin->GetDepthInMainChain() == 0 &&
pcoin->InMempool()) {
nTotal += pcoin->GetAvailableWatchOnlyCredit();
}
}
return nTotal;
}
Amount CWallet::GetImmatureWatchOnlyBalance() const {
LOCK2(cs_main, cs_wallet);
Amount nTotal = Amount::zero();
for (const auto &entry : mapWallet) {
const CWalletTx *pcoin = &entry.second;
nTotal += pcoin->GetImmatureWatchOnlyCredit();
}
return nTotal;
}
// Calculate total balance in a different way from GetBalance. The biggest
// difference is that GetBalance sums up all unspent TxOuts paying to the
// wallet, while this sums up both spent and unspent TxOuts paying to the
// wallet, and then subtracts the values of TxIns spending from the wallet. This
// also has fewer restrictions on which unconfirmed transactions are considered
// trusted.
Amount CWallet::GetLegacyBalance(const isminefilter &filter, int minDepth,
const std::string *account) const {
LOCK2(cs_main, cs_wallet);
Amount balance = Amount::zero();
for (const auto &entry : mapWallet) {
const CWalletTx &wtx = entry.second;
const int depth = wtx.GetDepthInMainChain();
if (depth < 0 || !CheckFinalTx(*wtx.tx) || wtx.IsImmatureCoinBase()) {
continue;
}
// Loop through tx outputs and add incoming payments. For outgoing txs,
// treat change outputs specially, as part of the amount debited.
Amount debit = wtx.GetDebit(filter);
const bool outgoing = debit > Amount::zero();
for (const CTxOut &out : wtx.tx->vout) {
if (outgoing && IsChange(out)) {
debit -= out.nValue;
} else if (IsMine(out) & filter && depth >= minDepth &&
(!account ||
*account == GetLabelName(out.scriptPubKey))) {
balance += out.nValue;
}
}
// For outgoing txs, subtract amount debited.
if (outgoing && (!account || *account == wtx.strFromAccount)) {
balance -= debit;
}
}
if (account) {
balance += CWalletDB(*dbw).GetAccountCreditDebit(*account);
}
return balance;
}
Amount CWallet::GetAvailableBalance(const CCoinControl *coinControl) const {
LOCK2(cs_main, cs_wallet);
Amount balance = Amount::zero();
std::vector<COutput> vCoins;
AvailableCoins(vCoins, true, coinControl);
for (const COutput &out : vCoins) {
if (out.fSpendable) {
balance += out.tx->tx->vout[out.i].nValue;
}
}
return balance;
}
void CWallet::AvailableCoins(std::vector<COutput> &vCoins, bool fOnlySafe,
const CCoinControl *coinControl,
const Amount nMinimumAmount,
const Amount nMaximumAmount,
const Amount nMinimumSumAmount,
const uint64_t nMaximumCount, const int nMinDepth,
const int nMaxDepth) const {
vCoins.clear();
Amount nTotal = Amount::zero();
LOCK2(cs_main, cs_wallet);
for (const auto &entry : mapWallet) {
const TxId &wtxid = entry.first;
const CWalletTx *pcoin = &entry.second;
if (!CheckFinalTx(*pcoin)) {
continue;
}
if (pcoin->IsImmatureCoinBase()) {
continue;
}
int nDepth = pcoin->GetDepthInMainChain();
if (nDepth < 0) {
continue;
}
// We should not consider coins which aren't at least in our mempool.
// It's possible for these to be conflicted via ancestors which we may
// never be able to detect.
if (nDepth == 0 && !pcoin->InMempool()) {
continue;
}
bool safeTx = pcoin->IsTrusted();
// Bitcoin-ABC: Removed check that prevents consideration of coins from
// transactions that are replacing other transactions. This check based
// on pcoin->mapValue.count("replaces_txid") which was not being set
// anywhere.
// Similarly, we should not consider coins from transactions that have
// been replaced. In the example above, we would want to prevent
// creation of a transaction A' spending an output of A, because if
// transaction B were initially confirmed, conflicting with A and A', we
// wouldn't want to the user to create a transaction D intending to
// replace A', but potentially resulting in a scenario where A, A', and
// D could all be accepted (instead of just B and D, or just A and A'
// like the user would want).
// Bitcoin-ABC: retained this check as 'replaced_by_txid' is still set
// in the wallet code.
if (nDepth == 0 && pcoin->mapValue.count("replaced_by_txid")) {
safeTx = false;
}
if (fOnlySafe && !safeTx) {
continue;
}
if (nDepth < nMinDepth || nDepth > nMaxDepth) {
continue;
}
for (uint32_t i = 0; i < pcoin->tx->vout.size(); i++) {
if (pcoin->tx->vout[i].nValue < nMinimumAmount ||
pcoin->tx->vout[i].nValue > nMaximumAmount) {
continue;
}
if (coinControl && coinControl->HasSelected() &&
!coinControl->fAllowOtherInputs &&
!coinControl->IsSelected(COutPoint(entry.first, i))) {
continue;
}
if (IsLockedCoin(entry.first, i)) {
continue;
}
if (IsSpent(wtxid, i)) {
continue;
}
isminetype mine = IsMine(pcoin->tx->vout[i]);
if (mine == ISMINE_NO) {
continue;
}
bool fSpendableIn = ((mine & ISMINE_SPENDABLE) != ISMINE_NO) ||
(coinControl && coinControl->fAllowWatchOnly &&
(mine & ISMINE_WATCH_SOLVABLE) != ISMINE_NO);
bool fSolvableIn =
(mine & (ISMINE_SPENDABLE | ISMINE_WATCH_SOLVABLE)) !=
ISMINE_NO;
vCoins.push_back(
COutput(pcoin, i, nDepth, fSpendableIn, fSolvableIn, safeTx));
// Checks the sum amount of all UTXO's.
if (nMinimumSumAmount != MAX_MONEY) {
nTotal += pcoin->tx->vout[i].nValue;
if (nTotal >= nMinimumSumAmount) {
return;
}
}
// Checks the maximum number of UTXO's.
if (nMaximumCount > 0 && vCoins.size() >= nMaximumCount) {
return;
}
}
}
}
std::map<CTxDestination, std::vector<COutput>> CWallet::ListCoins() const {
// TODO: Add AssertLockHeld(cs_wallet) here.
//
// Because the return value from this function contains pointers to
// CWalletTx objects, callers to this function really should acquire the
// cs_wallet lock before calling it. However, the current caller doesn't
// acquire this lock yet. There was an attempt to add the missing lock in
// https://github.com/bitcoin/bitcoin/pull/10340, but that change has been
// postponed until after https://github.com/bitcoin/bitcoin/pull/10244 to
// avoid adding some extra complexity to the Qt code.
std::map<CTxDestination, std::vector<COutput>> result;
std::vector<COutput> availableCoins;
AvailableCoins(availableCoins);
LOCK2(cs_main, cs_wallet);
for (auto &coin : availableCoins) {
CTxDestination address;
if (coin.fSpendable &&
ExtractDestination(
FindNonChangeParentOutput(*coin.tx->tx, coin.i).scriptPubKey,
address)) {
result[address].emplace_back(std::move(coin));
}
}
std::vector<COutPoint> lockedCoins;
ListLockedCoins(lockedCoins);
for (const auto &output : lockedCoins) {
auto it = mapWallet.find(output.GetTxId());
if (it != mapWallet.end()) {
int depth = it->second.GetDepthInMainChain();
if (depth >= 0 && output.GetN() < it->second.tx->vout.size() &&
IsMine(it->second.tx->vout[output.GetN()]) ==
ISMINE_SPENDABLE) {
CTxDestination address;
if (ExtractDestination(
FindNonChangeParentOutput(*it->second.tx, output.GetN())
.scriptPubKey,
address)) {
result[address].emplace_back(
&it->second, output.GetN(), depth, true /* spendable */,
true /* solvable */, false /* safe */);
}
}
}
}
return result;
}
const CTxOut &CWallet::FindNonChangeParentOutput(const CTransaction &tx,
int output) const {
const CTransaction *ptx = &tx;
int n = output;
while (IsChange(ptx->vout[n]) && ptx->vin.size() > 0) {
const COutPoint &prevout = ptx->vin[0].prevout;
auto it = mapWallet.find(prevout.GetTxId());
if (it == mapWallet.end() ||
it->second.tx->vout.size() <= prevout.GetN() ||
!IsMine(it->second.tx->vout[prevout.GetN()])) {
break;
}
ptx = it->second.tx.get();
n = prevout.GetN();
}
return ptx->vout[n];
}
static void ApproximateBestSubset(const std::vector<CInputCoin> &vValue,
const Amount &nTotalLower,
const Amount &nTargetValue,
std::vector<char> &vfBest, Amount &nBest,
int iterations = 1000) {
std::vector<char> vfIncluded;
vfBest.assign(vValue.size(), true);
nBest = nTotalLower;
FastRandomContext insecure_rand;
for (int nRep = 0; nRep < iterations && nBest != nTargetValue; nRep++) {
vfIncluded.assign(vValue.size(), false);
Amount nTotal = Amount::zero();
bool fReachedTarget = false;
for (int nPass = 0; nPass < 2 && !fReachedTarget; nPass++) {
for (size_t i = 0; i < vValue.size(); i++) {
// The solver here uses a randomized algorithm, the randomness
// serves no real security purpose but is just needed to prevent
// degenerate behavior and it is important that the rng is fast.
// We do not use a constant random sequence, because there may
// be some privacy improvement by making the selection random.
if (nPass == 0 ? insecure_rand.randbool() : !vfIncluded[i]) {
nTotal += vValue[i].txout.nValue;
vfIncluded[i] = true;
if (nTotal >= nTargetValue) {
fReachedTarget = true;
if (nTotal < nBest) {
nBest = nTotal;
vfBest = vfIncluded;
}
nTotal -= vValue[i].txout.nValue;
vfIncluded[i] = false;
}
}
}
}
}
}
bool CWallet::SelectCoinsMinConf(const Amount nTargetValue, const int nConfMine,
const int nConfTheirs,
const uint64_t nMaxAncestors,
std::vector<COutput> vCoins,
std::set<CInputCoin> &setCoinsRet,
Amount &nValueRet) const {
setCoinsRet.clear();
nValueRet = Amount::zero();
// List of values less than target
boost::optional<CInputCoin> coinLowestLarger;
std::vector<CInputCoin> vValue;
Amount nTotalLower = Amount::zero();
random_shuffle(vCoins.begin(), vCoins.end(), GetRandInt);
for (const COutput &output : vCoins) {
if (!output.fSpendable) {
continue;
}
const CWalletTx *pcoin = output.tx;
if (output.nDepth <
(pcoin->IsFromMe(ISMINE_ALL) ? nConfMine : nConfTheirs)) {
continue;
}
if (!g_mempool.TransactionWithinChainLimit(pcoin->GetId(),
nMaxAncestors)) {
continue;
}
int i = output.i;
CInputCoin coin = CInputCoin(pcoin, i);
if (coin.txout.nValue == nTargetValue) {
setCoinsRet.insert(coin);
nValueRet += coin.txout.nValue;
return true;
} else if (coin.txout.nValue < nTargetValue + MIN_CHANGE) {
vValue.push_back(coin);
nTotalLower += coin.txout.nValue;
} else if (!coinLowestLarger ||
coin.txout.nValue < coinLowestLarger->txout.nValue) {
coinLowestLarger = coin;
}
}
if (nTotalLower == nTargetValue) {
for (unsigned int i = 0; i < vValue.size(); ++i) {
setCoinsRet.insert(vValue[i]);
nValueRet += vValue[i].txout.nValue;
}
return true;
}
if (nTotalLower < nTargetValue) {
if (!coinLowestLarger) {
return false;
}
setCoinsRet.insert(coinLowestLarger.get());
nValueRet += coinLowestLarger->txout.nValue;
return true;
}
// Solve subset sum by stochastic approximation
std::sort(vValue.begin(), vValue.end(), CompareValueOnly());
std::reverse(vValue.begin(), vValue.end());
std::vector<char> vfBest;
Amount nBest;
ApproximateBestSubset(vValue, nTotalLower, nTargetValue, vfBest, nBest);
if (nBest != nTargetValue && nTotalLower >= nTargetValue + MIN_CHANGE) {
ApproximateBestSubset(vValue, nTotalLower, nTargetValue + MIN_CHANGE,
vfBest, nBest);
}
// If we have a bigger coin and (either the stochastic approximation didn't
// find a good solution, or the next bigger coin is closer), return the
// bigger coin.
if (coinLowestLarger &&
((nBest != nTargetValue && nBest < nTargetValue + MIN_CHANGE) ||
coinLowestLarger->txout.nValue <= nBest)) {
setCoinsRet.insert(coinLowestLarger.get());
nValueRet += coinLowestLarger->txout.nValue;
} else {
for (unsigned int i = 0; i < vValue.size(); i++) {
if (vfBest[i]) {
setCoinsRet.insert(vValue[i]);
nValueRet += vValue[i].txout.nValue;
}
}
if (LogAcceptCategory(BCLog::SELECTCOINS)) {
LogPrint(BCLog::SELECTCOINS, "SelectCoins() best subset: ");
for (size_t i = 0; i < vValue.size(); i++) {
if (vfBest[i]) {
LogPrint(BCLog::SELECTCOINS, "%s ",
FormatMoney(vValue[i].txout.nValue));
}
}
LogPrint(BCLog::SELECTCOINS, "total %s\n", FormatMoney(nBest));
}
}
return true;
}
bool CWallet::SelectCoins(const std::vector<COutput> &vAvailableCoins,
const Amount nTargetValue,
std::set<CInputCoin> &setCoinsRet, Amount &nValueRet,
const CCoinControl *coinControl) const {
std::vector<COutput> vCoins(vAvailableCoins);
// coin control -> return all selected outputs (we want all selected to go
// into the transaction for sure).
if (coinControl && coinControl->HasSelected() &&
!coinControl->fAllowOtherInputs) {
for (const COutput &out : vCoins) {
if (!out.fSpendable) {
continue;
}
nValueRet += out.tx->tx->vout[out.i].nValue;
setCoinsRet.insert(CInputCoin(out.tx, out.i));
}
return (nValueRet >= nTargetValue);
}
// Calculate value from preset inputs and store them.
std::set<CInputCoin> setPresetCoins;
Amount nValueFromPresetInputs = Amount::zero();
std::vector<COutPoint> vPresetInputs;
if (coinControl) {
coinControl->ListSelected(vPresetInputs);
}
for (const COutPoint &outpoint : vPresetInputs) {
std::map<TxId, CWalletTx>::const_iterator it =
mapWallet.find(outpoint.GetTxId());
if (it == mapWallet.end()) {
// TODO: Allow non-wallet inputs
return false;
}
const CWalletTx *pcoin = &it->second;
// Clearly invalid input, fail.
if (pcoin->tx->vout.size() <= outpoint.GetN()) {
return false;
}
nValueFromPresetInputs += pcoin->tx->vout[outpoint.GetN()].nValue;
setPresetCoins.insert(CInputCoin(pcoin, outpoint.GetN()));
}
// Remove preset inputs from vCoins.
for (std::vector<COutput>::iterator it = vCoins.begin();
it != vCoins.end() && coinControl && coinControl->HasSelected();) {
if (setPresetCoins.count(CInputCoin(it->tx, it->i))) {
it = vCoins.erase(it);
} else {
++it;
}
}
size_t nMaxChainLength = std::min(
gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT),
gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT));
bool fRejectLongChains = gArgs.GetBoolArg(
"-walletrejectlongchains", DEFAULT_WALLET_REJECT_LONG_CHAINS);
bool res =
nTargetValue <= nValueFromPresetInputs ||
SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 1, 6, 0,
vCoins, setCoinsRet, nValueRet) ||
SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 1, 1, 0,
vCoins, setCoinsRet, nValueRet) ||
(bSpendZeroConfChange &&
SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1, 2,
vCoins, setCoinsRet, nValueRet)) ||
(bSpendZeroConfChange &&
SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1,
std::min((size_t)4, nMaxChainLength / 3), vCoins,
setCoinsRet, nValueRet)) ||
(bSpendZeroConfChange &&
SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1,
nMaxChainLength / 2, vCoins, setCoinsRet,
nValueRet)) ||
(bSpendZeroConfChange &&
SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1,
nMaxChainLength, vCoins, setCoinsRet, nValueRet)) ||
(bSpendZeroConfChange && !fRejectLongChains &&
SelectCoinsMinConf(nTargetValue - nValueFromPresetInputs, 0, 1,
std::numeric_limits<uint64_t>::max(), vCoins,
setCoinsRet, nValueRet));
// Because SelectCoinsMinConf clears the setCoinsRet, we now add the
// possible inputs to the coinset.
setCoinsRet.insert(setPresetCoins.begin(), setPresetCoins.end());
// Add preset inputs to the total value selected.
nValueRet += nValueFromPresetInputs;
return res;
}
bool CWallet::SignTransaction(CMutableTransaction &tx) {
// sign the new tx
CTransaction txNewConst(tx);
int nIn = 0;
for (auto &input : tx.vin) {
auto mi = mapWallet.find(input.prevout.GetTxId());
if (mi == mapWallet.end() ||
input.prevout.GetN() >= mi->second.tx->vout.size()) {
return false;
}
const CScript &scriptPubKey =
mi->second.tx->vout[input.prevout.GetN()].scriptPubKey;
const Amount amount = mi->second.tx->vout[input.prevout.GetN()].nValue;
SignatureData sigdata;
SigHashType sigHashType = SigHashType().withForkId();
if (!ProduceSignature(TransactionSignatureCreator(
this, &txNewConst, nIn, amount, sigHashType),
scriptPubKey, sigdata)) {
return false;
}
UpdateTransaction(tx, nIn, sigdata);
nIn++;
}
return true;
}
bool CWallet::FundTransaction(CMutableTransaction &tx, Amount &nFeeRet,
int &nChangePosInOut, std::string &strFailReason,
bool lockUnspents,
const std::set<int> &setSubtractFeeFromOutputs,
CCoinControl coinControl, bool keepReserveKey) {
std::vector<CRecipient> vecSend;
// Turn the txout set into a CRecipient vector.
for (size_t idx = 0; idx < tx.vout.size(); idx++) {
const CTxOut &txOut = tx.vout[idx];
CRecipient recipient = {txOut.scriptPubKey, txOut.nValue,
setSubtractFeeFromOutputs.count(idx) == 1};
vecSend.push_back(recipient);
}
coinControl.fAllowOtherInputs = true;
for (const CTxIn &txin : tx.vin) {
coinControl.Select(txin.prevout);
}
CReserveKey reservekey(this);
CTransactionRef tx_new;
if (!CreateTransaction(vecSend, tx_new, reservekey, nFeeRet,
nChangePosInOut, strFailReason, coinControl,
false)) {
return false;
}
if (nChangePosInOut != -1) {
tx.vout.insert(tx.vout.begin() + nChangePosInOut,
tx_new->vout[nChangePosInOut]);
}
// Copy output sizes from new transaction; they may have had the fee
// subtracted from them.
for (size_t idx = 0; idx < tx.vout.size(); idx++) {
tx.vout[idx].nValue = tx_new->vout[idx].nValue;
}
// Add new txins (keeping original txin scriptSig/order)
for (const CTxIn &txin : tx_new->vin) {
if (!coinControl.IsSelected(txin.prevout)) {
tx.vin.push_back(txin);
if (lockUnspents) {
LOCK2(cs_main, cs_wallet);
LockCoin(txin.prevout);
}
}
}
// Optionally keep the change output key.
if (keepReserveKey) {
reservekey.KeepKey();
}
return true;
}
bool CWallet::CreateTransaction(const std::vector<CRecipient> &vecSend,
CTransactionRef &tx, CReserveKey &reservekey,
Amount &nFeeRet, int &nChangePosInOut,
std::string &strFailReason,
const CCoinControl &coinControl, bool sign) {
Amount nValue = Amount::zero();
int nChangePosRequest = nChangePosInOut;
unsigned int nSubtractFeeFromAmount = 0;
for (const auto &recipient : vecSend) {
if (nValue < Amount::zero() || recipient.nAmount < Amount::zero()) {
strFailReason = _("Transaction amounts must not be negative");
return false;
}
nValue += recipient.nAmount;
if (recipient.fSubtractFeeFromAmount) {
nSubtractFeeFromAmount++;
}
}
if (vecSend.empty()) {
strFailReason = _("Transaction must have at least one recipient");
return false;
}
CMutableTransaction txNew;
// Discourage fee sniping.
//
// For a large miner the value of the transactions in the best block and the
// mempool can exceed the cost of deliberately attempting to mine two blocks
// to orphan the current best block. By setting nLockTime such that only the
// next block can include the transaction, we discourage this practice as
// the height restricted and limited blocksize gives miners considering fee
// sniping fewer options for pulling off this attack.
//
// A simple way to think about this is from the wallet's point of view we
// always want the blockchain to move forward. By setting nLockTime this way
// we're basically making the statement that we only want this transaction
// to appear in the next block; we don't want to potentially encourage
// reorgs by allowing transactions to appear at lower heights than the next
// block in forks of the best chain.
//
// Of course, the subsidy is high enough, and transaction volume low enough,
// that fee sniping isn't a problem yet, but by implementing a fix now we
// ensure code won't be written that makes assumptions about nLockTime that
// preclude a fix later.
txNew.nLockTime = chainActive.Height();
// Secondly occasionally randomly pick a nLockTime even further back, so
// that transactions that are delayed after signing for whatever reason,
// e.g. high-latency mix networks and some CoinJoin implementations, have
// better privacy.
if (GetRandInt(10) == 0) {
txNew.nLockTime = std::max(0, (int)txNew.nLockTime - GetRandInt(100));
}
assert(txNew.nLockTime <= (unsigned int)chainActive.Height());
assert(txNew.nLockTime < LOCKTIME_THRESHOLD);
{
std::set<CInputCoin> setCoins;
LOCK2(cs_main, cs_wallet);
std::vector<COutput> vAvailableCoins;
AvailableCoins(vAvailableCoins, true, &coinControl);
// Create change script that will be used if we need change
// TODO: pass in scriptChange instead of reservekey so
// change transaction isn't always pay-to-bitcoin-address
CScript scriptChange;
// coin control: send change to custom address
if (!boost::get<CNoDestination>(&coinControl.destChange)) {
scriptChange = GetScriptForDestination(coinControl.destChange);
// no coin control: send change to newly generated address
} else {
// Note: We use a new key here to keep it from being obvious
// which side is the change.
// The drawback is that by not reusing a previous key, the
// change may be lost if a backup is restored, if the backup
// doesn't have the new private key for the change. If we
// reused the old key, it would be possible to add code to look
// for and rediscover unknown transactions that were written
// with keys of ours to recover post-backup change.
// Reserve a new key pair from key pool
CPubKey vchPubKey;
bool ret;
ret = reservekey.GetReservedKey(vchPubKey, true);
if (!ret) {
strFailReason =
_("Keypool ran out, please call keypoolrefill first");
return false;
}
scriptChange = GetScriptForDestination(vchPubKey.GetID());
}
CTxOut change_prototype_txout(Amount::zero(), scriptChange);
size_t change_prototype_size =
GetSerializeSize(change_prototype_txout, SER_DISK, 0);
nFeeRet = Amount::zero();
bool pick_new_inputs = true;
Amount nValueIn = Amount::zero();
// Start with no fee and loop until there is enough fee
while (true) {
nChangePosInOut = nChangePosRequest;
txNew.vin.clear();
txNew.vout.clear();
bool fFirst = true;
Amount nValueToSelect = nValue;
if (nSubtractFeeFromAmount == 0) {
nValueToSelect += nFeeRet;
}
double dPriority = 0;
// vouts to the payees
for (const auto &recipient : vecSend) {
CTxOut txout(recipient.nAmount, recipient.scriptPubKey);
if (recipient.fSubtractFeeFromAmount) {
// Subtract fee equally from each selected recipient.
txout.nValue -= nFeeRet / int(nSubtractFeeFromAmount);
// First receiver pays the remainder not divisible by output
// count.
if (fFirst) {
fFirst = false;
txout.nValue -= nFeeRet % int(nSubtractFeeFromAmount);
}
}
if (txout.IsDust(dustRelayFee)) {
if (recipient.fSubtractFeeFromAmount &&
nFeeRet > Amount::zero()) {
if (txout.nValue < Amount::zero()) {
strFailReason = _("The transaction amount is "
"too small to pay the fee");
} else {
strFailReason =
_("The transaction amount is too small to "
"send after the fee has been deducted");
}
} else {
strFailReason = _("Transaction amount too small");
}
return false;
}
txNew.vout.push_back(txout);
}
// Choose coins to use
if (pick_new_inputs) {
nValueIn = Amount::zero();
setCoins.clear();
if (!SelectCoins(vAvailableCoins, nValueToSelect, setCoins,
nValueIn, &coinControl)) {
strFailReason = _("Insufficient funds");
return false;
}
}
for (const auto &pcoin : setCoins) {
Amount nCredit = pcoin.txout.nValue;
// The coin age after the next block (depth+1) is used instead
// of the current, reflecting an assumption the user would
// accept a bit more delay for a chance at a free transaction.
// But mempool inputs might still be in the mempool, so their
// age stays 0.
int age = pcoin.wtx->GetDepthInMainChain();
assert(age >= 0);
if (age != 0) {
age += 1;
}
dPriority += (age * nCredit) / SATOSHI;
}
const Amount nChange = nValueIn - nValueToSelect;
if (nChange > Amount::zero()) {
// Fill a vout to ourself.
CTxOut newTxOut(nChange, scriptChange);
// We do not move dust-change to fees, because the sender would
// end up paying more than requested. This would be against the
// purpose of the all-inclusive feature. So instead we raise the
// change and deduct from the recipient.
if (nSubtractFeeFromAmount > 0 &&
newTxOut.IsDust(dustRelayFee)) {
Amount nDust = newTxOut.GetDustThreshold(dustRelayFee) -
newTxOut.nValue;
// Raise change until no more dust.
newTxOut.nValue += nDust;
// Subtract from first recipient.
for (unsigned int i = 0; i < vecSend.size(); i++) {
if (vecSend[i].fSubtractFeeFromAmount) {
txNew.vout[i].nValue -= nDust;
if (txNew.vout[i].IsDust(dustRelayFee)) {
strFailReason =
_("The transaction amount is too small "
"to send after the fee has been "
"deducted");
return false;
}
break;
}
}
}
// Never create dust outputs; if we would, just add the dust to
// the fee.
if (newTxOut.IsDust(dustRelayFee)) {
nChangePosInOut = -1;
nFeeRet += nChange;
} else {
if (nChangePosInOut == -1) {
// Insert change txn at random position:
nChangePosInOut = GetRandInt(txNew.vout.size() + 1);
} else if ((unsigned int)nChangePosInOut >
txNew.vout.size()) {
strFailReason = _("Change index out of range");
return false;
}
std::vector<CTxOut>::iterator position =
txNew.vout.begin() + nChangePosInOut;
txNew.vout.insert(position, newTxOut);
}
} else {
nChangePosInOut = -1;
}
// Fill vin
//
// Note how the sequence number is set to non-maxint so that the
// nLockTime set above actually works.
for (const auto &coin : setCoins) {
txNew.vin.push_back(
CTxIn(coin.outpoint, CScript(),
std::numeric_limits<uint32_t>::max() - 1));
}
// Fill in dummy signatures for fee calculation.
if (!DummySignTx(txNew, setCoins)) {
strFailReason = _("Signing transaction failed");
return false;
}
CTransaction txNewConst(txNew);
unsigned int nBytes = txNewConst.GetTotalSize();
dPriority = txNewConst.ComputePriority(dPriority, nBytes);
// Remove scriptSigs to eliminate the fee calculation dummy
// signatures.
for (auto &vin : txNew.vin) {
vin.scriptSig = CScript();
}
Amount nFeeNeeded = GetMinimumFee(nBytes, g_mempool, coinControl);
// If we made it here and we aren't even able to meet the relay fee
// on the next pass, give up because we must be at the maximum
// allowed fee.
Amount minFee = GetConfig().GetMinFeePerKB().GetFeeCeiling(nBytes);
if (nFeeNeeded < minFee) {
strFailReason = _("Transaction too large for fee policy");
return false;
}
if (nFeeRet >= nFeeNeeded) {
// Reduce fee to only the needed amount if possible. This
// prevents potential overpayment in fees if the coins selected
// to meet nFeeNeeded result in a transaction that requires less
// fee than the prior iteration.
// TODO: The case where nSubtractFeeFromAmount > 0 remains to be
// addressed because it requires returning the fee to the payees
// and not the change output.
// If we have no change and a big enough excess fee, then try to
// construct transaction again only without picking new inputs.
// We now know we only need the smaller fee (because of reduced
// tx size) and so we should add a change output. Only try this
// once.
Amount fee_needed_for_change = GetMinimumFee(
change_prototype_size, g_mempool, coinControl);
Amount minimum_value_for_change =
change_prototype_txout.GetDustThreshold(dustRelayFee);
Amount max_excess_fee =
fee_needed_for_change + minimum_value_for_change;
if (nFeeRet > nFeeNeeded + max_excess_fee &&
nChangePosInOut == -1 && nSubtractFeeFromAmount == 0 &&
pick_new_inputs) {
pick_new_inputs = false;
nFeeRet = nFeeNeeded + fee_needed_for_change;
continue;
}
// If we have change output already, just increase it
if (nFeeRet > nFeeNeeded && nChangePosInOut != -1 &&
nSubtractFeeFromAmount == 0) {
Amount extraFeePaid = nFeeRet - nFeeNeeded;
std::vector<CTxOut>::iterator change_position =
txNew.vout.begin() + nChangePosInOut;
change_position->nValue += extraFeePaid;
nFeeRet -= extraFeePaid;
}
// Done, enough fee included.
break;
} else if (!pick_new_inputs) {
// This shouldn't happen, we should have had enough excess fee
// to pay for the new output and still meet nFeeNeeded
strFailReason =
_("Transaction fee and change calculation failed");
return false;
}
// Try to reduce change to include necessary fee.
if (nChangePosInOut != -1 && nSubtractFeeFromAmount == 0) {
Amount additionalFeeNeeded = nFeeNeeded - nFeeRet;
std::vector<CTxOut>::iterator change_position =
txNew.vout.begin() + nChangePosInOut;
// Only reduce change if remaining amount is still a large
// enough output.
if (change_position->nValue >=
MIN_FINAL_CHANGE + additionalFeeNeeded) {
change_position->nValue -= additionalFeeNeeded;
nFeeRet += additionalFeeNeeded;
// Done, able to increase fee from change.
break;
}
}
// Include more fee and try again.
nFeeRet = nFeeNeeded;
continue;
}
if (nChangePosInOut == -1) {
// Return any reserved key if we don't have change
reservekey.ReturnKey();
}
if (sign) {
SigHashType sigHashType = SigHashType().withForkId();
CTransaction txNewConst(txNew);
int nIn = 0;
for (const auto &coin : setCoins) {
const CScript &scriptPubKey = coin.txout.scriptPubKey;
SignatureData sigdata;
if (!ProduceSignature(TransactionSignatureCreator(
this, &txNewConst, nIn,
coin.txout.nValue, sigHashType),
scriptPubKey, sigdata)) {
strFailReason = _("Signing transaction failed");
return false;
}
UpdateTransaction(txNew, nIn, sigdata);
nIn++;
}
}
// Return the constructed transaction data.
tx = MakeTransactionRef(std::move(txNew));
// Limit size.
if (tx->GetTotalSize() >= MAX_STANDARD_TX_SIZE) {
strFailReason = _("Transaction too large");
return false;
}
}
if (gArgs.GetBoolArg("-walletrejectlongchains",
DEFAULT_WALLET_REJECT_LONG_CHAINS)) {
// Lastly, ensure this tx will pass the mempool's chain limits.
LockPoints lp;
CTxMemPoolEntry entry(tx, Amount::zero(), 0, 0, 0, Amount::zero(),
false, 0, lp);
CTxMemPool::setEntries setAncestors;
size_t nLimitAncestors =
gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT);
size_t nLimitAncestorSize =
gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT) *
1000;
size_t nLimitDescendants =
gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT);
size_t nLimitDescendantSize =
gArgs.GetArg("-limitdescendantsize",
DEFAULT_DESCENDANT_SIZE_LIMIT) *
1000;
std::string errString;
if (!g_mempool.CalculateMemPoolAncestors(
entry, setAncestors, nLimitAncestors, nLimitAncestorSize,
nLimitDescendants, nLimitDescendantSize, errString)) {
strFailReason = _("Transaction has too long of a mempool chain");
return false;
}
}
return true;
}
/**
* Call after CreateTransaction unless you want to abort
*/
bool CWallet::CommitTransaction(
CTransactionRef tx, mapValue_t mapValue,
std::vector<std::pair<std::string, std::string>> orderForm,
std::string fromAccount, CReserveKey &reservekey, CConnman *connman,
CValidationState &state) {
LOCK2(cs_main, cs_wallet);
CWalletTx wtxNew(this, std::move(tx));
wtxNew.mapValue = std::move(mapValue);
wtxNew.vOrderForm = std::move(orderForm);
wtxNew.strFromAccount = std::move(fromAccount);
wtxNew.fTimeReceivedIsTxTime = true;
wtxNew.fFromMe = true;
LogPrintf("CommitTransaction:\n%s", wtxNew.tx->ToString());
// Take key pair from key pool so it won't be used again.
reservekey.KeepKey();
// Add tx to wallet, because if it has change it's also ours, otherwise just
// for transaction history.
AddToWallet(wtxNew);
// Notify that old coins are spent.
for (const CTxIn &txin : wtxNew.tx->vin) {
CWalletTx &coin = mapWallet.at(txin.prevout.GetTxId());
coin.BindWallet(this);
NotifyTransactionChanged(this, coin.GetId(), CT_UPDATED);
}
// Track how many getdata requests our transaction gets.
mapRequestCount[wtxNew.GetId()] = 0;
// Get the inserted-CWalletTx from mapWallet so that the
// fInMempool flag is cached properly
CWalletTx &wtx = mapWallet.at(wtxNew.GetId());
if (fBroadcastTransactions) {
// Broadcast
if (!wtx.AcceptToMemoryPool(maxTxFee, state)) {
LogPrintf("CommitTransaction(): Transaction cannot be broadcast "
"immediately, %s\n",
state.GetRejectReason());
// TODO: if we expect the failure to be long term or permanent,
// instead delete wtx from the wallet and return failure.
} else {
wtx.RelayWalletTransaction(connman);
}
}
return true;
}
void CWallet::ListAccountCreditDebit(const std::string &strAccount,
std::list<CAccountingEntry> &entries) {
CWalletDB walletdb(*dbw);
return walletdb.ListAccountCreditDebit(strAccount, entries);
}
bool CWallet::AddAccountingEntry(const CAccountingEntry &acentry) {
CWalletDB walletdb(*dbw);
return AddAccountingEntry(acentry, &walletdb);
}
bool CWallet::AddAccountingEntry(const CAccountingEntry &acentry,
CWalletDB *pwalletdb) {
if (!pwalletdb->WriteAccountingEntry(++nAccountingEntryNumber, acentry)) {
return false;
}
laccentries.push_back(acentry);
CAccountingEntry &entry = laccentries.back();
wtxOrdered.insert(std::make_pair(entry.nOrderPos, TxPair(nullptr, &entry)));
return true;
}
DBErrors CWallet::LoadWallet(bool &fFirstRunRet) {
LOCK2(cs_main, cs_wallet);
fFirstRunRet = false;
DBErrors nLoadWalletRet = CWalletDB(*dbw, "cr+").LoadWallet(this);
if (nLoadWalletRet == DBErrors::NEED_REWRITE) {
if (dbw->Rewrite("\x04pool")) {
setInternalKeyPool.clear();
setExternalKeyPool.clear();
m_pool_key_to_index.clear();
// Note: can't top-up keypool here, because wallet is locked.
// User will be prompted to unlock wallet the next operation
// that requires a new key.
}
}
// This wallet is in its first run if all of these are empty
fFirstRunRet = mapKeys.empty() && mapCryptedKeys.empty() &&
mapWatchKeys.empty() && setWatchOnly.empty() &&
mapScripts.empty();
if (nLoadWalletRet != DBErrors::LOAD_OK) {
return nLoadWalletRet;
}
uiInterface.LoadWallet(this);
return DBErrors::LOAD_OK;
}
DBErrors CWallet::ZapSelectTx(std::vector<TxId> &txIdsIn,
std::vector<TxId> &txIdsOut) {
AssertLockHeld(cs_wallet); // mapWallet
DBErrors nZapSelectTxRet =
CWalletDB(*dbw, "cr+").ZapSelectTx(txIdsIn, txIdsOut);
for (const TxId &txid : txIdsOut) {
mapWallet.erase(txid);
}
if (nZapSelectTxRet == DBErrors::NEED_REWRITE) {
if (dbw->Rewrite("\x04pool")) {
setInternalKeyPool.clear();
setExternalKeyPool.clear();
m_pool_key_to_index.clear();
// Note: can't top-up keypool here, because wallet is locked.
// User will be prompted to unlock wallet the next operation
// that requires a new key.
}
}
if (nZapSelectTxRet != DBErrors::LOAD_OK) {
return nZapSelectTxRet;
}
MarkDirty();
return DBErrors::LOAD_OK;
}
DBErrors CWallet::ZapWalletTx(std::vector<CWalletTx> &vWtx) {
DBErrors nZapWalletTxRet = CWalletDB(*dbw, "cr+").ZapWalletTx(vWtx);
if (nZapWalletTxRet == DBErrors::NEED_REWRITE) {
if (dbw->Rewrite("\x04pool")) {
LOCK(cs_wallet);
setInternalKeyPool.clear();
setExternalKeyPool.clear();
m_pool_key_to_index.clear();
// Note: can't top-up keypool here, because wallet is locked.
// User will be prompted to unlock wallet the next operation
// that requires a new key.
}
}
if (nZapWalletTxRet != DBErrors::LOAD_OK) {
return nZapWalletTxRet;
}
return DBErrors::LOAD_OK;
}
bool CWallet::SetAddressBook(const CTxDestination &address,
const std::string &strName,
const std::string &strPurpose) {
bool fUpdated = false;
{
// mapAddressBook
LOCK(cs_wallet);
std::map<CTxDestination, CAddressBookData>::iterator mi =
mapAddressBook.find(address);
fUpdated = mi != mapAddressBook.end();
mapAddressBook[address].name = strName;
// Update purpose only if requested.
if (!strPurpose.empty()) {
mapAddressBook[address].purpose = strPurpose;
}
}
NotifyAddressBookChanged(this, address, strName,
::IsMine(*this, address) != ISMINE_NO, strPurpose,
(fUpdated ? CT_UPDATED : CT_NEW));
if (!strPurpose.empty() &&
!CWalletDB(*dbw).WritePurpose(address, strPurpose)) {
return false;
}
return CWalletDB(*dbw).WriteName(address, strName);
}
bool CWallet::DelAddressBook(const CTxDestination &address) {
{
// mapAddressBook
LOCK(cs_wallet);
// Delete destdata tuples associated with address.
for (const std::pair<std::string, std::string> &item :
mapAddressBook[address].destdata) {
CWalletDB(*dbw).EraseDestData(address, item.first);
}
mapAddressBook.erase(address);
}
NotifyAddressBookChanged(this, address, "",
::IsMine(*this, address) != ISMINE_NO, "",
CT_DELETED);
CWalletDB(*dbw).ErasePurpose(address);
return CWalletDB(*dbw).EraseName(address);
}
const std::string &CWallet::GetLabelName(const CScript &scriptPubKey) const {
CTxDestination address;
if (ExtractDestination(scriptPubKey, address) &&
!scriptPubKey.IsUnspendable()) {
auto mi = mapAddressBook.find(address);
if (mi != mapAddressBook.end()) {
return mi->second.name;
}
}
// A scriptPubKey that doesn't have an entry in the address book is
// associated with the default label ("").
const static std::string DEFAULT_LABEL_NAME;
return DEFAULT_LABEL_NAME;
}
/**
* Mark old keypool keys as used, and generate all new keys.
*/
bool CWallet::NewKeyPool() {
LOCK(cs_wallet);
CWalletDB walletdb(*dbw);
for (int64_t nIndex : setInternalKeyPool) {
walletdb.ErasePool(nIndex);
}
setInternalKeyPool.clear();
for (int64_t nIndex : setExternalKeyPool) {
walletdb.ErasePool(nIndex);
}
setExternalKeyPool.clear();
m_pool_key_to_index.clear();
if (!TopUpKeyPool()) {
return false;
}
LogPrintf("CWallet::NewKeyPool rewrote keypool\n");
return true;
}
size_t CWallet::KeypoolCountExternalKeys() {
// setExternalKeyPool
AssertLockHeld(cs_wallet);
return setExternalKeyPool.size();
}
void CWallet::LoadKeyPool(int64_t nIndex, const CKeyPool &keypool) {
AssertLockHeld(cs_wallet);
if (keypool.fInternal) {
setInternalKeyPool.insert(nIndex);
} else {
setExternalKeyPool.insert(nIndex);
}
m_max_keypool_index = std::max(m_max_keypool_index, nIndex);
m_pool_key_to_index[keypool.vchPubKey.GetID()] = nIndex;
// If no metadata exists yet, create a default with the pool key's
// creation time. Note that this may be overwritten by actually
// stored metadata for that key later, which is fine.
CKeyID keyid = keypool.vchPubKey.GetID();
if (mapKeyMetadata.count(keyid) == 0) {
mapKeyMetadata[keyid] = CKeyMetadata(keypool.nTime);
}
}
bool CWallet::TopUpKeyPool(unsigned int kpSize) {
LOCK(cs_wallet);
if (IsLocked()) {
return false;
}
// Top up key pool
unsigned int nTargetSize;
if (kpSize > 0) {
nTargetSize = kpSize;
} else {
nTargetSize = std::max<int64_t>(
gArgs.GetArg("-keypool", DEFAULT_KEYPOOL_SIZE), 0);
}
// count amount of available keys (internal, external)
// make sure the keypool of external and internal keys fits the user
// selected target (-keypool)
int64_t missingExternal = std::max<int64_t>(
std::max<int64_t>(nTargetSize, 1) - setExternalKeyPool.size(), 0);
int64_t missingInternal = std::max<int64_t>(
std::max<int64_t>(nTargetSize, 1) - setInternalKeyPool.size(), 0);
if (!IsHDEnabled() || !CanSupportFeature(FEATURE_HD_SPLIT)) {
// don't create extra internal keys
missingInternal = 0;
}
bool internal = false;
CWalletDB walletdb(*dbw);
for (int64_t i = missingInternal + missingExternal; i--;) {
if (i < missingInternal) {
internal = true;
}
// How in the hell did you use so many keys?
assert(m_max_keypool_index < std::numeric_limits<int64_t>::max());
int64_t index = ++m_max_keypool_index;
CPubKey pubkey(GenerateNewKey(walletdb, internal));
if (!walletdb.WritePool(index, CKeyPool(pubkey, internal))) {
throw std::runtime_error(std::string(__func__) +
": writing generated key failed");
}
if (internal) {
setInternalKeyPool.insert(index);
} else {
setExternalKeyPool.insert(index);
}
m_pool_key_to_index[pubkey.GetID()] = index;
}
if (missingInternal + missingExternal > 0) {
LogPrintf(
"keypool added %d keys (%d internal), size=%u (%u internal)\n",
missingInternal + missingExternal, missingInternal,
setInternalKeyPool.size() + setExternalKeyPool.size(),
setInternalKeyPool.size());
}
return true;
}
void CWallet::ReserveKeyFromKeyPool(int64_t &nIndex, CKeyPool &keypool,
bool fRequestedInternal) {
nIndex = -1;
keypool.vchPubKey = CPubKey();
LOCK(cs_wallet);
if (!IsLocked()) {
TopUpKeyPool();
}
bool fReturningInternal = IsHDEnabled() &&
CanSupportFeature(FEATURE_HD_SPLIT) &&
fRequestedInternal;
std::set<int64_t> &setKeyPool =
fReturningInternal ? setInternalKeyPool : setExternalKeyPool;
// Get the oldest key
if (setKeyPool.empty()) {
return;
}
CWalletDB walletdb(*dbw);
auto it = setKeyPool.begin();
nIndex = *it;
setKeyPool.erase(it);
if (!walletdb.ReadPool(nIndex, keypool)) {
throw std::runtime_error(std::string(__func__) + ": read failed");
}
if (!HaveKey(keypool.vchPubKey.GetID())) {
throw std::runtime_error(std::string(__func__) +
": unknown key in key pool");
}
if (keypool.fInternal != fReturningInternal) {
throw std::runtime_error(std::string(__func__) +
": keypool entry misclassified");
}
assert(keypool.vchPubKey.IsValid());
m_pool_key_to_index.erase(keypool.vchPubKey.GetID());
LogPrintf("keypool reserve %d\n", nIndex);
}
void CWallet::KeepKey(int64_t nIndex) {
// Remove from key pool.
CWalletDB walletdb(*dbw);
walletdb.ErasePool(nIndex);
LogPrintf("keypool keep %d\n", nIndex);
}
void CWallet::ReturnKey(int64_t nIndex, bool fInternal, const CPubKey &pubkey) {
// Return to key pool
{
LOCK(cs_wallet);
if (fInternal) {
setInternalKeyPool.insert(nIndex);
} else {
setExternalKeyPool.insert(nIndex);
}
m_pool_key_to_index[pubkey.GetID()] = nIndex;
}
LogPrintf("keypool return %d\n", nIndex);
}
bool CWallet::GetKeyFromPool(CPubKey &result, bool internal) {
CKeyPool keypool;
LOCK(cs_wallet);
int64_t nIndex = 0;
ReserveKeyFromKeyPool(nIndex, keypool, internal);
if (nIndex == -1) {
if (IsLocked()) {
return false;
}
CWalletDB walletdb(*dbw);
result = GenerateNewKey(walletdb, internal);
return true;
}
KeepKey(nIndex);
result = keypool.vchPubKey;
return true;
}
static int64_t GetOldestKeyTimeInPool(const std::set<int64_t> &setKeyPool,
CWalletDB &walletdb) {
if (setKeyPool.empty()) {
return GetTime();
}
CKeyPool keypool;
int64_t nIndex = *(setKeyPool.begin());
if (!walletdb.ReadPool(nIndex, keypool)) {
throw std::runtime_error(std::string(__func__) +
": read oldest key in keypool failed");
}
assert(keypool.vchPubKey.IsValid());
return keypool.nTime;
}
int64_t CWallet::GetOldestKeyPoolTime() {
LOCK(cs_wallet);
CWalletDB walletdb(*dbw);
// load oldest key from keypool, get time and return
int64_t oldestKey = GetOldestKeyTimeInPool(setExternalKeyPool, walletdb);
if (IsHDEnabled() && CanSupportFeature(FEATURE_HD_SPLIT)) {
oldestKey = std::max(
GetOldestKeyTimeInPool(setInternalKeyPool, walletdb), oldestKey);
}
return oldestKey;
}
std::map<CTxDestination, Amount> CWallet::GetAddressBalances() {
std::map<CTxDestination, Amount> balances;
LOCK(cs_wallet);
for (std::pair<TxId, CWalletTx> walletEntry : mapWallet) {
CWalletTx *pcoin = &walletEntry.second;
if (!pcoin->IsTrusted()) {
continue;
}
if (pcoin->IsImmatureCoinBase()) {
continue;
}
int nDepth = pcoin->GetDepthInMainChain();
if (nDepth < (pcoin->IsFromMe(ISMINE_ALL) ? 0 : 1)) {
continue;
}
for (uint32_t i = 0; i < pcoin->tx->vout.size(); i++) {
CTxDestination addr;
if (!IsMine(pcoin->tx->vout[i])) {
continue;
}
if (!ExtractDestination(pcoin->tx->vout[i].scriptPubKey, addr)) {
continue;
}
Amount n = IsSpent(walletEntry.first, i)
? Amount::zero()
: pcoin->tx->vout[i].nValue;
if (!balances.count(addr)) {
balances[addr] = Amount::zero();
}
balances[addr] += n;
}
}
return balances;
}
std::set<std::set<CTxDestination>> CWallet::GetAddressGroupings() {
// mapWallet
AssertLockHeld(cs_wallet);
std::set<std::set<CTxDestination>> groupings;
std::set<CTxDestination> grouping;
for (std::pair<uint256, CWalletTx> walletEntry : mapWallet) {
CWalletTx *pcoin = &walletEntry.second;
if (pcoin->tx->vin.size() > 0) {
bool any_mine = false;
// Group all input addresses with each other.
for (CTxIn txin : pcoin->tx->vin) {
CTxDestination address;
// If this input isn't mine, ignore it.
if (!IsMine(txin)) {
continue;
}
if (!ExtractDestination(mapWallet.at(txin.prevout.GetTxId())
.tx->vout[txin.prevout.GetN()]
.scriptPubKey,
address)) {
continue;
}
grouping.insert(address);
any_mine = true;
}
// Group change with input addresses.
if (any_mine) {
for (CTxOut txout : pcoin->tx->vout) {
if (IsChange(txout)) {
CTxDestination txoutAddr;
if (!ExtractDestination(txout.scriptPubKey,
txoutAddr)) {
continue;
}
grouping.insert(txoutAddr);
}
}
}
if (grouping.size() > 0) {
groupings.insert(grouping);
grouping.clear();
}
}
// Group lone addrs by themselves.
for (unsigned int i = 0; i < pcoin->tx->vout.size(); i++)
if (IsMine(pcoin->tx->vout[i])) {
CTxDestination address;
if (!ExtractDestination(pcoin->tx->vout[i].scriptPubKey,
address)) {
continue;
}
grouping.insert(address);
groupings.insert(grouping);
grouping.clear();
}
}
// A set of pointers to groups of addresses.
std::set<std::set<CTxDestination> *> uniqueGroupings;
// Map addresses to the unique group containing it.
std::map<CTxDestination, std::set<CTxDestination> *> setmap;
for (std::set<CTxDestination> _grouping : groupings) {
// Make a set of all the groups hit by this new group.
std::set<std::set<CTxDestination> *> hits;
std::map<CTxDestination, std::set<CTxDestination> *>::iterator it;
for (CTxDestination address : _grouping) {
if ((it = setmap.find(address)) != setmap.end()) {
hits.insert((*it).second);
}
}
// Merge all hit groups into a new single group and delete old groups.
std::set<CTxDestination> *merged =
new std::set<CTxDestination>(_grouping);
for (std::set<CTxDestination> *hit : hits) {
merged->insert(hit->begin(), hit->end());
uniqueGroupings.erase(hit);
delete hit;
}
uniqueGroupings.insert(merged);
// Update setmap.
for (CTxDestination element : *merged) {
setmap[element] = merged;
}
}
std::set<std::set<CTxDestination>> ret;
for (std::set<CTxDestination> *uniqueGrouping : uniqueGroupings) {
ret.insert(*uniqueGrouping);
delete uniqueGrouping;
}
return ret;
}
std::set<CTxDestination>
CWallet::GetLabelAddresses(const std::string &label) const {
LOCK(cs_wallet);
std::set<CTxDestination> result;
for (const std::pair<CTxDestination, CAddressBookData> &item :
mapAddressBook) {
const CTxDestination &address = item.first;
const std::string &strName = item.second.name;
if (strName == label) {
result.insert(address);
}
}
return result;
}
bool CReserveKey::GetReservedKey(CPubKey &pubkey, bool internal) {
if (nIndex == -1) {
CKeyPool keypool;
pwallet->ReserveKeyFromKeyPool(nIndex, keypool, internal);
if (nIndex == -1) {
return false;
}
vchPubKey = keypool.vchPubKey;
fInternal = keypool.fInternal;
}
assert(vchPubKey.IsValid());
pubkey = vchPubKey;
return true;
}
void CReserveKey::KeepKey() {
if (nIndex != -1) {
pwallet->KeepKey(nIndex);
}
nIndex = -1;
vchPubKey = CPubKey();
}
void CReserveKey::ReturnKey() {
if (nIndex != -1) {
pwallet->ReturnKey(nIndex, fInternal, vchPubKey);
}
nIndex = -1;
vchPubKey = CPubKey();
}
void CWallet::MarkReserveKeysAsUsed(int64_t keypool_id) {
AssertLockHeld(cs_wallet);
bool internal = setInternalKeyPool.count(keypool_id);
if (!internal) {
assert(setExternalKeyPool.count(keypool_id));
}
std::set<int64_t> *setKeyPool =
internal ? &setInternalKeyPool : &setExternalKeyPool;
auto it = setKeyPool->begin();
CWalletDB walletdb(*dbw);
while (it != std::end(*setKeyPool)) {
const int64_t &index = *(it);
if (index > keypool_id) {
// set*KeyPool is ordered
break;
}
CKeyPool keypool;
if (walletdb.ReadPool(index, keypool)) {
// TODO: This should be unnecessary
m_pool_key_to_index.erase(keypool.vchPubKey.GetID());
}
walletdb.ErasePool(index);
it = setKeyPool->erase(it);
}
}
bool CWallet::HasUnusedKeys(size_t min_keys) const {
return setExternalKeyPool.size() >= min_keys &&
(setInternalKeyPool.size() >= min_keys ||
!CanSupportFeature(FEATURE_HD_SPLIT));
}
void CWallet::GetScriptForMining(std::shared_ptr<CReserveScript> &script) {
std::shared_ptr<CReserveKey> rKey = std::make_shared<CReserveKey>(this);
CPubKey pubkey;
if (!rKey->GetReservedKey(pubkey)) {
return;
}
script = rKey;
script->reserveScript = CScript() << ToByteVector(pubkey) << OP_CHECKSIG;
}
void CWallet::LockCoin(const COutPoint &output) {
// setLockedCoins
AssertLockHeld(cs_wallet);
setLockedCoins.insert(output);
}
void CWallet::UnlockCoin(const COutPoint &output) {
// setLockedCoins
AssertLockHeld(cs_wallet);
setLockedCoins.erase(output);
}
void CWallet::UnlockAllCoins() {
// setLockedCoins
AssertLockHeld(cs_wallet);
setLockedCoins.clear();
}
bool CWallet::IsLockedCoin(const TxId &txid, uint32_t n) const {
// setLockedCoins
AssertLockHeld(cs_wallet);
COutPoint outpt(txid, n);
return setLockedCoins.count(outpt) > 0;
}
void CWallet::ListLockedCoins(std::vector<COutPoint> &vOutpts) const {
// setLockedCoins
AssertLockHeld(cs_wallet);
for (COutPoint outpoint : setLockedCoins) {
vOutpts.push_back(outpoint);
}
}
/** @} */ // end of Actions
void CWallet::GetKeyBirthTimes(
std::map<CTxDestination, int64_t> &mapKeyBirth) const {
// mapKeyMetadata
AssertLockHeld(cs_wallet);
mapKeyBirth.clear();
// Get birth times for keys with metadata.
for (const auto &entry : mapKeyMetadata) {
if (entry.second.nCreateTime) {
mapKeyBirth[entry.first] = entry.second.nCreateTime;
}
}
// Map in which we'll infer heights of other keys the tip can be
// reorganized; use a 144-block safety margin.
CBlockIndex *pindexMax =
chainActive[std::max(0, chainActive.Height() - 144)];
std::map<CKeyID, CBlockIndex *> mapKeyFirstBlock;
for (const CKeyID &keyid : GetKeys()) {
if (mapKeyBirth.count(keyid) == 0) {
mapKeyFirstBlock[keyid] = pindexMax;
}
}
// If there are no such keys, we're done.
if (mapKeyFirstBlock.empty()) {
return;
}
// Find first block that affects those keys, if there are any left.
std::vector<CKeyID> vAffected;
for (const auto &entry : mapWallet) {
// iterate over all wallet transactions...
const CWalletTx &wtx = entry.second;
BlockMap::const_iterator blit = mapBlockIndex.find(wtx.hashBlock);
if (blit != mapBlockIndex.end() && chainActive.Contains(blit->second)) {
// ... which are already in a block.
int nHeight = blit->second->nHeight;
for (const CTxOut &txout : wtx.tx->vout) {
// Iterate over all their outputs...
CAffectedKeysVisitor(*this, vAffected)
.Process(txout.scriptPubKey);
for (const CKeyID &keyid : vAffected) {
// ... and all their affected keys.
std::map<CKeyID, CBlockIndex *>::iterator rit =
mapKeyFirstBlock.find(keyid);
if (rit != mapKeyFirstBlock.end() &&
nHeight < rit->second->nHeight) {
rit->second = blit->second;
}
}
vAffected.clear();
}
}
}
// Extract block timestamps for those keys.
for (const auto &entry : mapKeyFirstBlock) {
// block times can be 2h off
mapKeyBirth[entry.first] =
entry.second->GetBlockTime() - TIMESTAMP_WINDOW;
}
}
/**
* Compute smart timestamp for a transaction being added to the wallet.
*
* Logic:
* - If sending a transaction, assign its timestamp to the current time.
* - If receiving a transaction outside a block, assign its timestamp to the
* current time.
* - If receiving a block with a future timestamp, assign all its (not already
* known) transactions' timestamps to the current time.
* - If receiving a block with a past timestamp, before the most recent known
* transaction (that we care about), assign all its (not already known)
* transactions' timestamps to the same timestamp as that most-recent-known
* transaction.
* - If receiving a block with a past timestamp, but after the most recent known
* transaction, assign all its (not already known) transactions' timestamps to
* the block time.
*
* For more information see CWalletTx::nTimeSmart,
* https://bitcointalk.org/?topic=54527, or
* https://github.com/bitcoin/bitcoin/pull/1393.
*/
unsigned int CWallet::ComputeTimeSmart(const CWalletTx &wtx) const {
unsigned int nTimeSmart = wtx.nTimeReceived;
if (!wtx.hashUnset()) {
if (mapBlockIndex.count(wtx.hashBlock)) {
int64_t latestNow = wtx.nTimeReceived;
int64_t latestEntry = 0;
// Tolerate times up to the last timestamp in the wallet not more
// than 5 minutes into the future
int64_t latestTolerated = latestNow + 300;
const TxItems &txOrdered = wtxOrdered;
for (auto it = txOrdered.rbegin(); it != txOrdered.rend(); ++it) {
CWalletTx *const pwtx = it->second.first;
if (pwtx == &wtx) {
continue;
}
CAccountingEntry *const pacentry = it->second.second;
int64_t nSmartTime;
if (pwtx) {
nSmartTime = pwtx->nTimeSmart;
if (!nSmartTime) {
nSmartTime = pwtx->nTimeReceived;
}
} else {
nSmartTime = pacentry->nTime;
}
if (nSmartTime <= latestTolerated) {
latestEntry = nSmartTime;
if (nSmartTime > latestNow) {
latestNow = nSmartTime;
}
break;
}
}
int64_t blocktime = mapBlockIndex[wtx.hashBlock]->GetBlockTime();
nTimeSmart = std::max(latestEntry, std::min(blocktime, latestNow));
} else {
LogPrintf("%s: found %s in block %s not in index\n", __func__,
wtx.GetId().ToString(), wtx.hashBlock.ToString());
}
}
return nTimeSmart;
}
bool CWallet::AddDestData(const CTxDestination &dest, const std::string &key,
const std::string &value) {
if (boost::get<CNoDestination>(&dest)) {
return false;
}
mapAddressBook[dest].destdata.insert(std::make_pair(key, value));
return CWalletDB(*dbw).WriteDestData(dest, key, value);
}
bool CWallet::EraseDestData(const CTxDestination &dest,
const std::string &key) {
if (!mapAddressBook[dest].destdata.erase(key)) {
return false;
}
return CWalletDB(*dbw).EraseDestData(dest, key);
}
bool CWallet::LoadDestData(const CTxDestination &dest, const std::string &key,
const std::string &value) {
mapAddressBook[dest].destdata.insert(std::make_pair(key, value));
return true;
}
bool CWallet::GetDestData(const CTxDestination &dest, const std::string &key,
std::string *value) const {
std::map<CTxDestination, CAddressBookData>::const_iterator i =
mapAddressBook.find(dest);
if (i != mapAddressBook.end()) {
CAddressBookData::StringMap::const_iterator j =
i->second.destdata.find(key);
if (j != i->second.destdata.end()) {
if (value) {
*value = j->second;
}
return true;
}
}
return false;
}
std::vector<std::string>
CWallet::GetDestValues(const std::string &prefix) const {
LOCK(cs_wallet);
std::vector<std::string> values;
for (const auto &address : mapAddressBook) {
for (const auto &data : address.second.destdata) {
if (!data.first.compare(0, prefix.size(), prefix)) {
values.emplace_back(data.second);
}
}
}
return values;
}
CWallet *CWallet::CreateWalletFromFile(const CChainParams &chainParams,
const std::string walletFile) {
// Needed to restore wallet transaction meta data after -zapwallettxes
std::vector<CWalletTx> vWtx;
if (gArgs.GetBoolArg("-zapwallettxes", false)) {
uiInterface.InitMessage(_("Zapping all transactions from wallet..."));
std::unique_ptr<CWalletDBWrapper> dbw(
new CWalletDBWrapper(&bitdb, walletFile));
std::unique_ptr<CWallet> tempWallet =
- MakeUnique<CWallet>(chainParams, std::move(dbw));
+ std::make_unique<CWallet>(chainParams, std::move(dbw));
DBErrors nZapWalletRet = tempWallet->ZapWalletTx(vWtx);
if (nZapWalletRet != DBErrors::LOAD_OK) {
InitError(
strprintf(_("Error loading %s: Wallet corrupted"), walletFile));
return nullptr;
}
}
uiInterface.InitMessage(_("Loading wallet..."));
int64_t nStart = GetTimeMillis();
bool fFirstRun = true;
std::unique_ptr<CWalletDBWrapper> dbw(
new CWalletDBWrapper(&bitdb, walletFile));
CWallet *walletInstance = new CWallet(chainParams, std::move(dbw));
DBErrors nLoadWalletRet = walletInstance->LoadWallet(fFirstRun);
if (nLoadWalletRet != DBErrors::LOAD_OK) {
if (nLoadWalletRet == DBErrors::CORRUPT) {
InitError(
strprintf(_("Error loading %s: Wallet corrupted"), walletFile));
return nullptr;
}
if (nLoadWalletRet == DBErrors::NONCRITICAL_ERROR) {
InitWarning(strprintf(
_("Error reading %s! All keys read correctly, but transaction "
"data"
" or address book entries might be missing or incorrect."),
walletFile));
} else if (nLoadWalletRet == DBErrors::TOO_NEW) {
InitError(strprintf(
_("Error loading %s: Wallet requires newer version of %s"),
walletFile, _(PACKAGE_NAME)));
return nullptr;
} else if (nLoadWalletRet == DBErrors::NEED_REWRITE) {
InitError(strprintf(
_("Wallet needed to be rewritten: restart %s to complete"),
_(PACKAGE_NAME)));
return nullptr;
} else {
InitError(strprintf(_("Error loading %s"), walletFile));
return nullptr;
}
}
if (gArgs.GetBoolArg("-upgradewallet", fFirstRun)) {
int nMaxVersion = gArgs.GetArg("-upgradewallet", 0);
// The -upgradewallet without argument case
if (nMaxVersion == 0) {
LogPrintf("Performing wallet upgrade to %i\n", FEATURE_LATEST);
nMaxVersion = CLIENT_VERSION;
// permanently upgrade the wallet immediately
walletInstance->SetMinVersion(FEATURE_LATEST);
} else {
LogPrintf("Allowing wallet upgrade up to %i\n", nMaxVersion);
}
if (nMaxVersion < walletInstance->GetVersion()) {
InitError(_("Cannot downgrade wallet"));
return nullptr;
}
walletInstance->SetMaxVersion(nMaxVersion);
}
if (fFirstRun) {
// Create new keyUser and set as default key.
if (gArgs.GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET) &&
!walletInstance->IsHDEnabled()) {
// Ensure this wallet.dat can only be opened by clients supporting
// HD with chain split.
walletInstance->SetMinVersion(FEATURE_HD_SPLIT);
// Generate a new master key.
CPubKey masterPubKey = walletInstance->GenerateNewHDMasterKey();
if (!walletInstance->SetHDMasterKey(masterPubKey)) {
throw std::runtime_error(std::string(__func__) +
": Storing master key failed");
}
}
// Top up the keypool
if (!walletInstance->TopUpKeyPool()) {
InitError(_("Unable to generate initial keys") += "\n");
return nullptr;
}
walletInstance->SetBestChain(chainActive.GetLocator());
} else if (gArgs.IsArgSet("-usehd")) {
bool useHD = gArgs.GetBoolArg("-usehd", DEFAULT_USE_HD_WALLET);
if (walletInstance->IsHDEnabled() && !useHD) {
InitError(strprintf(_("Error loading %s: You can't disable HD on a "
"already existing HD wallet"),
walletFile));
return nullptr;
}
if (!walletInstance->IsHDEnabled() && useHD) {
InitError(strprintf(_("Error loading %s: You can't enable HD on a "
"already existing non-HD wallet"),
walletFile));
return nullptr;
}
}
LogPrintf(" wallet %15dms\n", GetTimeMillis() - nStart);
// Try to top up keypool. No-op if the wallet is locked.
walletInstance->TopUpKeyPool();
CBlockIndex *pindexRescan = chainActive.Genesis();
if (!gArgs.GetBoolArg("-rescan", false)) {
CWalletDB walletdb(*walletInstance->dbw);
CBlockLocator locator;
if (walletdb.ReadBestBlock(locator)) {
pindexRescan = FindForkInGlobalIndex(chainActive, locator);
}
}
walletInstance->m_last_block_processed = chainActive.Tip();
RegisterValidationInterface(walletInstance);
if (chainActive.Tip() && chainActive.Tip() != pindexRescan) {
// We can't rescan beyond non-pruned blocks, stop and throw an error.
// This might happen if a user uses a old wallet within a pruned node or
// if he ran -disablewallet for a longer time, then decided to
// re-enable.
if (fPruneMode) {
CBlockIndex *block = chainActive.Tip();
while (block && block->pprev && block->pprev->nStatus.hasData() &&
block->pprev->nTx > 0 && pindexRescan != block) {
block = block->pprev;
}
if (pindexRescan != block) {
InitError(_("Prune: last wallet synchronisation goes beyond "
"pruned data. You need to -reindex (download the "
"whole blockchain again in case of pruned node)"));
return nullptr;
}
}
uiInterface.InitMessage(_("Rescanning..."));
LogPrintf("Rescanning last %i blocks (from block %i)...\n",
chainActive.Height() - pindexRescan->nHeight,
pindexRescan->nHeight);
// No need to read and scan block if block was created before our wallet
// birthday (as adjusted for block time variability)
while (pindexRescan && walletInstance->nTimeFirstKey &&
(pindexRescan->GetBlockTime() <
(walletInstance->nTimeFirstKey - TIMESTAMP_WINDOW))) {
pindexRescan = chainActive.Next(pindexRescan);
}
nStart = GetTimeMillis();
walletInstance->ScanForWalletTransactions(pindexRescan, nullptr, true);
LogPrintf(" rescan %15dms\n", GetTimeMillis() - nStart);
walletInstance->SetBestChain(chainActive.GetLocator());
walletInstance->dbw->IncrementUpdateCounter();
// Restore wallet transaction metadata after -zapwallettxes=1
if (gArgs.GetBoolArg("-zapwallettxes", false) &&
gArgs.GetArg("-zapwallettxes", "1") != "2") {
CWalletDB walletdb(*walletInstance->dbw);
for (const CWalletTx &wtxOld : vWtx) {
const TxId txid = wtxOld.GetId();
std::map<TxId, CWalletTx>::iterator mi =
walletInstance->mapWallet.find(txid);
if (mi != walletInstance->mapWallet.end()) {
const CWalletTx *copyFrom = &wtxOld;
CWalletTx *copyTo = &mi->second;
copyTo->mapValue = copyFrom->mapValue;
copyTo->vOrderForm = copyFrom->vOrderForm;
copyTo->nTimeReceived = copyFrom->nTimeReceived;
copyTo->nTimeSmart = copyFrom->nTimeSmart;
copyTo->fFromMe = copyFrom->fFromMe;
copyTo->strFromAccount = copyFrom->strFromAccount;
copyTo->nOrderPos = copyFrom->nOrderPos;
walletdb.WriteTx(*copyTo);
}
}
}
}
walletInstance->SetBroadcastTransactions(
gArgs.GetBoolArg("-walletbroadcast", DEFAULT_WALLETBROADCAST));
LOCK(walletInstance->cs_wallet);
LogPrintf("setKeyPool.size() = %u\n", walletInstance->GetKeyPoolSize());
LogPrintf("mapWallet.size() = %u\n", walletInstance->mapWallet.size());
LogPrintf("mapAddressBook.size() = %u\n",
walletInstance->mapAddressBook.size());
return walletInstance;
}
std::atomic<bool> CWallet::fFlushScheduled(false);
void CWallet::postInitProcess(CScheduler &scheduler) {
// Add wallet transactions that aren't already in a block to mempool.
// Do this here as mempool requires genesis block to be loaded.
ReacceptWalletTransactions();
// Run a thread to flush wallet periodically.
if (!CWallet::fFlushScheduled.exchange(true)) {
scheduler.scheduleEvery(
[]() {
MaybeCompactWalletDB();
return true;
},
500);
}
}
bool CWallet::BackupWallet(const std::string &strDest) {
return dbw->Backup(strDest);
}
CKeyPool::CKeyPool() {
nTime = GetTime();
fInternal = false;
}
CKeyPool::CKeyPool(const CPubKey &vchPubKeyIn, bool internalIn) {
nTime = GetTime();
vchPubKey = vchPubKeyIn;
fInternal = internalIn;
}
CWalletKey::CWalletKey(int64_t nExpires) {
nTimeCreated = (nExpires ? GetTime() : 0);
nTimeExpires = nExpires;
}
void CMerkleTx::SetMerkleBranch(const CBlockIndex *pindex, int posInBlock) {
// Update the tx's hashBlock
hashBlock = pindex->GetBlockHash();
// Set the position of the transaction in the block.
nIndex = posInBlock;
}
int CMerkleTx::GetDepthInMainChain(const CBlockIndex *&pindexRet) const {
if (hashUnset()) {
return 0;
}
AssertLockHeld(cs_main);
// Find the block it claims to be in.
BlockMap::iterator mi = mapBlockIndex.find(hashBlock);
if (mi == mapBlockIndex.end()) {
return 0;
}
CBlockIndex *pindex = (*mi).second;
if (!pindex || !chainActive.Contains(pindex)) {
return 0;
}
pindexRet = pindex;
return ((nIndex == -1) ? (-1) : 1) *
(chainActive.Height() - pindex->nHeight + 1);
}
int CMerkleTx::GetBlocksToMaturity() const {
if (!IsCoinBase()) {
return 0;
}
return std::max(0, (COINBASE_MATURITY + 1) - GetDepthInMainChain());
}
bool CMerkleTx::IsImmatureCoinBase() const {
// note GetBlocksToMaturity is 0 for non-coinbase tx
return GetBlocksToMaturity() > 0;
}
bool CWalletTx::AcceptToMemoryPool(const Amount nAbsurdFee,
CValidationState &state) {
// We must set fInMempool here - while it will be re-set to true by the
// entered-mempool callback, if we did not there would be a race where a
// user could call sendmoney in a loop and hit spurious out of funds errors
// because we think that the transaction they just generated's change is
// unavailable as we're not yet aware its in mempool.
bool ret = ::AcceptToMemoryPool(
GetConfig(), g_mempool, state, tx, true /* fLimitFree */,
nullptr /* pfMissingInputs */, false /* fOverrideMempoolLimit */,
nAbsurdFee);
fInMempool = ret;
return ret;
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, May 22, 00:57 (14 h, 45 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5866233
Default Alt Text
(328 KB)

Event Timeline