diff --git a/src/init.cpp b/src/init.cpp index 0d32df4e26c..848929c4c4b 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -338,6 +338,7 @@ std::string HelpMessage(HelpMessageMode mode) strUsage += HelpMessageOpt("-?", _("Print this help message and exit")); strUsage += HelpMessageOpt("-version", _("Print version and exit")); strUsage += HelpMessageOpt("-alertnotify=", _("Execute command when a relevant alert is received or we see a really long fork (%s in cmd is replaced by message)")); + strUsage += HelpMessageOpt("-autorequestblocks", strprintf(_("Automatic block request, if disabled, blocks will not be requested automatically (default: %u)"), DEFAULT_AUTOMATIC_BLOCK_REQUESTS)); strUsage += HelpMessageOpt("-blocknotify=", _("Execute command when the best block changes (%s in cmd is replaced by block hash)")); if (showDebug) strUsage += HelpMessageOpt("-blocksonly", strprintf(_("Whether to operate in a blocks only mode (default: %u)"), DEFAULT_BLOCKSONLY)); @@ -970,6 +971,7 @@ bool AppInitParameterInteraction() } fCheckBlockIndex = GetBoolArg("-checkblockindex", chainparams.DefaultConsistencyChecks()); fCheckpointsEnabled = GetBoolArg("-checkpoints", DEFAULT_CHECKPOINTS_ENABLED); + SetAutoRequestBlocks(GetBoolArg("-autorequestblocks", DEFAULT_AUTOMATIC_BLOCK_REQUESTS)); hashAssumeValid = uint256S(GetArg("-assumevalid", chainparams.GetConsensus().defaultAssumeValid.GetHex())); if (!hashAssumeValid.IsNull()) diff --git a/src/net_processing.cpp b/src/net_processing.cpp index a743f04dd15..506ae4dabb5 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -61,6 +61,8 @@ static std::vector> vExtraTxnForCompact GUAR static const uint64_t RANDOMIZER_ID_ADDRESS_RELAY = 0x3cac0035b5866b90ULL; // SHA256("main address relay")[0:8] +static std::atomic fAutoRequestBlocks(DEFAULT_AUTOMATIC_BLOCK_REQUESTS); + // Internal stuff namespace { /** Number of nodes with fSyncStarted. */ @@ -104,6 +106,7 @@ namespace { const CBlockIndex* pindex; //!< Optional. bool fValidatedHeaders; //!< Whether this block has validated headers at the time of request. std::unique_ptr partialBlock; //!< Optional, used for CMPCTBLOCK downloads + bool priorityRequest; //!< Whether its a priority download }; std::map::iterator> > mapBlocksInFlight; @@ -121,6 +124,13 @@ namespace { MapRelay mapRelay; /** Expiration-time ordered list of (expire time, relay map entry) pairs, protected by cs_main). */ std::deque> vRelayExpiration; + + struct PriorityBlockRequest { + const CBlockIndex* pindex; + bool downloaded; + }; + + std::vector blocksToDownloadFirst; } // namespace ////////////////////////////////////////////////////////////////////////////// @@ -309,9 +319,13 @@ void FinalizeNode(NodeId nodeid, bool& fUpdateConnectionTime) { } // Requires cs_main. -// Returns a bool indicating whether we requested this block. +// Returns a MarkBlockAsReceivedResult struct to indicating whether we requested this block and if it was via the priority request queue // Also used if a block was /not/ received and timed out or started with another peer -bool MarkBlockAsReceived(const uint256& hash) { +struct MarkBlockAsReceivedResult { + bool fRequested; + bool fPriorityRequest; + }; +const MarkBlockAsReceivedResult MarkBlockAsReceived(const uint256& hash) { std::map::iterator> >::iterator itInFlight = mapBlocksInFlight.find(hash); if (itInFlight != mapBlocksInFlight.end()) { CNodeState *state = State(itInFlight->second.first); @@ -324,19 +338,28 @@ bool MarkBlockAsReceived(const uint256& hash) { // First block on the queue was received, update the start download time for the next one state->nDownloadingSince = std::max(state->nDownloadingSince, GetTimeMicros()); } - state->vBlocksInFlight.erase(itInFlight->second.second); + bool priorityRequest = itInFlight->second.second->priorityRequest; state->nBlocksInFlight--; state->nStallingSince = 0; + if (priorityRequest) { + // mark as downloaded + auto it = std::find_if(blocksToDownloadFirst.begin(), blocksToDownloadFirst.end(), [&itInFlight](const PriorityBlockRequest &r) { return r.pindex == itInFlight->second.second->pindex; }); + if (it != blocksToDownloadFirst.end()) { + (*it).downloaded = true; + } + } + state->vBlocksInFlight.erase(itInFlight->second.second); mapBlocksInFlight.erase(itInFlight); - return true; + + return {true, priorityRequest}; } - return false; + return {false, false}; } // Requires cs_main. // returns false, still setting pit, if the block was already in flight from the same peer // pit will only be valid as long as the same cs_main lock is being held -bool MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, const CBlockIndex* pindex = NULL, std::list::iterator** pit = NULL) { +bool MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, const CBlockIndex* pindex = NULL, std::list::iterator** pit = NULL, bool priorityRequest = false) { CNodeState *state = State(nodeid); assert(state != NULL); @@ -353,7 +376,7 @@ bool MarkBlockAsInFlight(NodeId nodeid, const uint256& hash, const CBlockIndex* MarkBlockAsReceived(hash); std::list::iterator it = state->vBlocksInFlight.insert(state->vBlocksInFlight.end(), - {hash, pindex, pindex != NULL, std::unique_ptr(pit ? new PartiallyDownloadedBlock(&mempool) : NULL)}); + {hash, pindex, pindex != NULL, std::unique_ptr(pit ? new PartiallyDownloadedBlock(&mempool) : NULL), priorityRequest}); state->nBlocksInFlight++; state->nBlocksInFlightValidHeaders += it->fValidatedHeaders; if (state->nBlocksInFlight == 1) { @@ -454,10 +477,12 @@ bool PeerHasHeader(CNodeState *state, const CBlockIndex *pindex) } /** Update pindexLastCommonBlock and add not-in-flight missing successors to vBlocks, until it has - * at most count entries. */ -void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector& vBlocks, NodeId& nodeStaller, const Consensus::Params& consensusParams) { + * at most count entries. + * returns true if priority downloads where used + */ +bool FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vector& vBlocks, NodeId& nodeStaller, const Consensus::Params& consensusParams) { if (count == 0) - return; + return false; vBlocks.reserve(vBlocks.size() + count); CNodeState *state = State(nodeid); @@ -466,9 +491,26 @@ void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vectorpindexBestKnownBlock != NULL && state->pindexBestKnownBlock->nHeight >= r.pindex->nHeight && !mapBlocksInFlight.count(r.pindex->GetBlockHash())) { + vBlocks.push_back(r.pindex); + if (vBlocks.size() == count) { + return true; + } + } + } + return true; + } + + if (!fAutoRequestBlocks) { + return false; + } + if (state->pindexBestKnownBlock == NULL || state->pindexBestKnownBlock->nChainWork < chainActive.Tip()->nChainWork || state->pindexBestKnownBlock->nChainWork < UintToArith256(consensusParams.nMinimumChainWork)) { // This peer has nothing interesting. - return; + return false; } if (state->pindexLastCommonBlock == NULL) { @@ -481,7 +523,7 @@ void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vectorpindexLastCommonBlock = LastCommonAncestor(state->pindexLastCommonBlock, state->pindexBestKnownBlock); if (state->pindexLastCommonBlock == state->pindexBestKnownBlock) - return; + return false; std::vector vToFetch; const CBlockIndex *pindexWalk = state->pindexLastCommonBlock; @@ -510,11 +552,11 @@ void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vectorIsValid(BLOCK_VALID_TREE)) { // We consider the chain that this peer is on invalid. - return; + return false; } if (!State(nodeid)->fHaveWitness && IsWitnessEnabled(pindex->pprev, consensusParams)) { // We wouldn't download this block or its descendants from this peer. - return; + return false; } if (pindex->nStatus & BLOCK_HAVE_DATA || chainActive.Contains(pindex)) { if (pindex->nChainTx) @@ -527,11 +569,11 @@ void FindNextBlocksToDownload(NodeId nodeid, unsigned int count, std::vectornHeight); } else { std::vector vGetData; + // Do not request blocks if autorequest is disabled + if (!fAutoRequestBlocks) { + return true; + } // Download as much as possible, from earliest to latest. for (const CBlockIndex *pindex : reverse_iterate(vToFetch)) { if (nodestate->nBlocksInFlight >= MAX_BLOCKS_IN_TRANSIT_PER_PEER) { @@ -2378,18 +2425,23 @@ bool static ProcessMessage(CNode* pfrom, const std::string& strCommand, CDataStr // Such an unrequested block may still be processed, subject to the // conditions in AcceptBlock(). bool forceProcessing = pfrom->fWhitelisted && !IsInitialBlockDownload(); + MarkBlockAsReceivedResult result; const uint256 hash(pblock->GetHash()); { LOCK(cs_main); // Also always process if we requested the block explicitly, as we may // need it even though it is not a candidate for a new best tip. - forceProcessing |= MarkBlockAsReceived(hash); + result = MarkBlockAsReceived(hash); + forceProcessing |= result.fRequested; // mapBlockSource is only used for sending reject messages and DoS scores, // so the race between here and cs_main in ProcessNewBlock is fine. mapBlockSource.emplace(hash, std::make_pair(pfrom->GetId(), true)); } bool fNewBlock = false; - ProcessNewBlock(chainparams, pblock, forceProcessing, &fNewBlock); + ProcessNewBlock(chainparams, pblock, forceProcessing, &fNewBlock, !result.fPriorityRequest); + if (result.fPriorityRequest) { + ProcessPriorityRequests(pblock); + } if (fNewBlock) pfrom->nLastBlockTime = GetTime(); } @@ -3231,12 +3283,12 @@ bool SendMessages(CNode* pto, CConnman& connman, const std::atomic& interr if (!pto->fClient && (fFetch || !IsInitialBlockDownload()) && state.nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) { std::vector vToDownload; NodeId staller = -1; - FindNextBlocksToDownload(pto->GetId(), MAX_BLOCKS_IN_TRANSIT_PER_PEER - state.nBlocksInFlight, vToDownload, staller, consensusParams); + bool priorityRequest = FindNextBlocksToDownload(pto->GetId(), MAX_BLOCKS_IN_TRANSIT_PER_PEER - state.nBlocksInFlight, vToDownload, staller, consensusParams); for (const CBlockIndex *pindex : vToDownload) { uint32_t nFetchFlags = GetFetchFlags(pto); vGetData.push_back(CInv(MSG_BLOCK | nFetchFlags, pindex->GetBlockHash())); - MarkBlockAsInFlight(pto->GetId(), pindex->GetBlockHash(), pindex); - LogPrint(BCLog::NET, "Requesting block %s (%d) peer=%d\n", pindex->GetBlockHash().ToString(), + MarkBlockAsInFlight(pto->GetId(), pindex->GetBlockHash(), pindex, NULL, priorityRequest); + LogPrint(BCLog::NET, "Requesting%s block %s (%d) peer=%d\n", (priorityRequest ? " (priority)" : " "), pindex->GetBlockHash().ToString(), pindex->nHeight, pto->GetId()); } if (state.nBlocksInFlight == 0 && staller != -1) { @@ -3302,6 +3354,74 @@ bool SendMessages(CNode* pto, CConnman& connman, const std::atomic& interr return true; } +void AddPriorityDownload(std::vector& blocksToDownload) { + LOCK(cs_main); + for (const CBlockIndex* pindex: blocksToDownload) { + // we add blocks regardless of duplicates + blocksToDownloadFirst.push_back({pindex, false}); + } +} + +void ProcessPriorityRequests(const std::shared_ptr blockRef) { + LOCK(cs_main); + if (blocksToDownloadFirst.empty()) { + return; + } + auto it = std::begin(blocksToDownloadFirst); + while (it != std::end(blocksToDownloadFirst)) { + std::shared_ptr currentBlock; + const PriorityBlockRequest &r = *it; + // make sure we process blocks in order + if (!r.downloaded) { + break; + } + if (r.pindex && blockRef && blockRef->GetHash() == r.pindex->GetBlockHash()) { + // the passed in block, no need to load again from disk + currentBlock = blockRef; + } + else if (r.pindex->nStatus & BLOCK_HAVE_DATA) { + CBlock loadBlock; + if (!ReadBlockFromDisk(loadBlock, r.pindex, Params().GetConsensus())) { + throw std::runtime_error(std::string(__func__) + "Can't read block from disk"); + } + currentBlock = std::make_shared(loadBlock); + } + else { + break; + } + + // allow processing through signal + GetMainSignals().ProcessPriorityRequest(currentBlock, r.pindex); + LogPrint(BCLog::NET, "process priority block request (%s) height=%d\n", r.pindex->GetBlockHash().ToString(), r.pindex->nHeight); + + // remove processed block from queue + it = blocksToDownloadFirst.erase(std::remove_if(blocksToDownloadFirst.begin(), blocksToDownloadFirst.end(), [&r](const PriorityBlockRequest &rB) { + return rB.pindex == r.pindex; + }), blocksToDownloadFirst.end()); + } +} + +bool FlushPriorityDownloads() { + LOCK(cs_main); + bool ret = blocksToDownloadFirst.empty(); + blocksToDownloadFirst.clear(); + return !ret; +} + +size_t CountPriorityDownloads() { + // return a copy + LOCK(cs_main); + return blocksToDownloadFirst.size(); +} + +void SetAutoRequestBlocks(bool state) { + fAutoRequestBlocks = state; +} + +bool isAutoRequestingBlocks() { + return fAutoRequestBlocks; +} + class CNetProcessingCleanup { public: diff --git a/src/net_processing.h b/src/net_processing.h index db6d81e6b67..b0a23d0e028 100644 --- a/src/net_processing.h +++ b/src/net_processing.h @@ -27,6 +27,9 @@ void RegisterNodeSignals(CNodeSignals& nodeSignals); /** Unregister a network node */ void UnregisterNodeSignals(CNodeSignals& nodeSignals); +/** if disabled, blocks will not be requested automatically, useful for low-resources-available mode */ +static const bool DEFAULT_AUTOMATIC_BLOCK_REQUESTS = true; + class PeerLogicValidation : public CValidationInterface { private: CConnman* connman; @@ -64,4 +67,18 @@ bool ProcessMessages(CNode* pfrom, CConnman& connman, const std::atomic& i */ bool SendMessages(CNode* pto, CConnman& connman, const std::atomic& interrupt); +/** + * Prioritize a block for downloading + * Blocks requested with priority will be downloaded and processed first + * Downloaded blocks will not trigger ActivateBestChain + */ +void AddPriorityDownload(std::vector& blocksToDownload); +void ProcessPriorityRequests(const std::shared_ptr block); +bool FlushPriorityDownloads(); +size_t CountPriorityDownloads(); +void ProcessPriorityRequests(const std::shared_ptr block); + +void SetAutoRequestBlocks(bool state); +bool isAutoRequestingBlocks(); + #endif // BITCOIN_NET_PROCESSING_H diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index d65e107e3c4..de1045b78f3 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -13,6 +13,7 @@ #include "consensus/validation.h" #include "validation.h" #include "core_io.h" +#include "net_processing.h" #include "policy/feerate.h" #include "policy/policy.h" #include "primitives/transaction.h" @@ -83,6 +84,8 @@ UniValue blockheaderToJSON(const CBlockIndex* blockindex) // Only report confirmations if the block is on the main chain if (chainActive.Contains(blockindex)) confirmations = chainActive.Height() - blockindex->nHeight + 1; + + result.push_back(Pair("validated", ((blockindex->nStatus & BLOCK_VALID_MASK) >= BLOCK_VALID_SCRIPTS))); result.push_back(Pair("confirmations", confirmations)); result.push_back(Pair("height", blockindex->nHeight)); result.push_back(Pair("version", blockindex->nVersion)); @@ -111,6 +114,7 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* blockindex, bool tx // Only report confirmations if the block is on the main chain if (chainActive.Contains(blockindex)) confirmations = chainActive.Height() - blockindex->nHeight + 1; + result.push_back(Pair("validated", (blockindex->nStatus & BLOCK_VALID_MASK) >= BLOCK_VALID_SCRIPTS)); result.push_back(Pair("confirmations", confirmations)); result.push_back(Pair("strippedsize", (int)::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS))); result.push_back(Pair("size", (int)::GetSerializeSize(block, SER_NETWORK, PROTOCOL_VERSION))); @@ -640,6 +644,7 @@ UniValue getblockheader(const JSONRPCRequest& request) "{\n" " \"hash\" : \"hash\", (string) the block hash (same as provided)\n" " \"confirmations\" : n, (numeric) The number of confirmations, or -1 if the block is not on the main chain\n" + " \"validated\" : n, (boolean) True if the block has been validated\n" " \"height\" : n, (numeric) The block height or index\n" " \"version\" : n, (numeric) The block version\n" " \"versionHex\" : \"00000000\", (string) The block version formatted in hexadecimal\n" @@ -1532,6 +1537,60 @@ UniValue getchaintxstats(const JSONRPCRequest& request) return ret; } +UniValue requestblocks(const JSONRPCRequest& request) +{ + if (request.fHelp || request.params.size() < 1 || request.params.size() > 3) + throw std::runtime_error( + "requestblocks (add|flush|status) ([\"hash_0\", \"hash_1\", ...])\n" + "\nPriorize blocks downloads.\n" + "\nArguments:\n" + "1. action (string, required) the action to execute\n" + " add = add new blocks to the priority download\n" + " flush = flush the queue (blocks in-flight will still be downloaded)\n" + " status = get info about the queue\n" + "2. blockhashes (array, optional) the hashes of the blocks to download\n" + "\nResult:\n" + " add: \n" + " flush: (if the the queue wasn't empty)\n" + " status: {\"count\": \"\"}\n" + "\nExamples:\n" + + HelpExampleCli("requestblocks", "add, \"'[\"\"]'\"") + + HelpExampleRpc("requestblocks", "add, \"'[\"\"]'\"") + ); + + if (request.params[0].get_str() == "flush") { + return UniValue(FlushPriorityDownloads()); + } + else if (request.params[0].get_str() == "status") { + UniValue ret(UniValue::VOBJ); + ret.push_back(Pair("count", (uint64_t)CountPriorityDownloads())); + return ret; + } + else if (request.params[0].get_str() == "add") { + if (request.params.size() < 2) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Missing blocks array"); + } + std::vector blocksToDownload; + { + LOCK(cs_main); //mapBlockIndex + for (const UniValue& strHashU : request.params[1].get_array().getValues()) + { + uint256 hash(uint256S(strHashU.get_str())); + BlockMap::iterator mi = mapBlockIndex.find(hash); + if (mi == mapBlockIndex.end()) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Block not found"); + blocksToDownload.push_back(mi->second); + } + } + + AddPriorityDownload(blocksToDownload); + return NullUniValue; + } + else { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Unkown action"); + } +} + static const CRPCCommand commands[] = { // category name actor (function) okSafe argNames // --------------------- ------------------------ ----------------------- ------ ---------- @@ -1553,8 +1612,8 @@ static const CRPCCommand commands[] = { "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, true, {} }, { "blockchain", "pruneblockchain", &pruneblockchain, true, {"height"} }, { "blockchain", "verifychain", &verifychain, true, {"checklevel","nblocks"} }, - { "blockchain", "preciousblock", &preciousblock, true, {"blockhash"} }, + { "blockchain", "requestblocks", &requestblocks, true, {"action", "blockhashes"} }, /* Not shown in help */ { "hidden", "invalidateblock", &invalidateblock, true, {"blockhash"} }, diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 41794537824..4eba0aacddb 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -129,6 +129,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "logging", 0, "include" }, { "logging", 1, "exclude" }, { "disconnectnode", 1, "nodeid" }, + { "requestblocks", 1, "blockhashes" }, // Echo with conversion (For testing only) { "echojson", 0, "arg0" }, { "echojson", 1, "arg1" }, diff --git a/src/validation.cpp b/src/validation.cpp index d223715c46f..7f48b8cb55a 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -3088,7 +3088,7 @@ bool ProcessNewBlockHeaders(const std::vector& headers, CValidatio } /** Store block on disk. If dbp is non-NULL, the file is known to already reside on disk */ -static bool AcceptBlock(const std::shared_ptr& pblock, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex, bool fRequested, const CDiskBlockPos* dbp, bool* fNewBlock) +static bool AcceptBlock(const std::shared_ptr& pblock, CValidationState& state, const CChainParams& chainparams, CBlockIndex** ppindex, bool fRequested, const CDiskBlockPos* dbp, bool* fNewBlock, bool checkFarAhead = true) { const CBlock& block = *pblock; @@ -3124,7 +3124,7 @@ static bool AcceptBlock(const std::shared_ptr& pblock, CValidation if (!fRequested) { // If we didn't ask for it: if (pindex->nTx != 0) return true; // This is a previously-processed block that was pruned if (!fHasMoreWork) return true; // Don't process less-work chains - if (fTooFarAhead) return true; // Block height is too high + if (checkFarAhead && fTooFarAhead) return true; // Block height is too high } if (fNewBlock) *fNewBlock = true; @@ -3167,7 +3167,7 @@ static bool AcceptBlock(const std::shared_ptr& pblock, CValidation return true; } -bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr pblock, bool fForceProcessing, bool *fNewBlock) +bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr pblock, bool fForceProcessing, bool *fNewBlock, bool activateBestChain) { { CBlockIndex *pindex = NULL; @@ -3181,7 +3181,8 @@ bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr pblock, bool fForceProcessing, bool* fNewBlock); +bool ProcessNewBlock(const CChainParams& chainparams, const std::shared_ptr pblock, bool fForceProcessing, bool* fNewBlock, bool activateBestChain = true); /** * Process incoming block headers. diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp index bf20d606f83..7d345e93234 100644 --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -24,6 +24,7 @@ struct MainSignalsInstance { boost::signals2::signal Broadcast; boost::signals2::signal BlockChecked; boost::signals2::signal&)> NewPoWValidBlock; + boost::signals2::signal &, const CBlockIndex *pindex)> ProcessPriorityRequest; // We are not allowed to assume the scheduler only runs in one thread, // but must ensure all callbacks happen in-order, so we end up creating @@ -63,9 +64,12 @@ void RegisterValidationInterface(CValidationInterface* pwalletIn) { g_signals.m_internals->Broadcast.connect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, _1, _2)); g_signals.m_internals->BlockChecked.connect(boost::bind(&CValidationInterface::BlockChecked, pwalletIn, _1, _2)); g_signals.m_internals->NewPoWValidBlock.connect(boost::bind(&CValidationInterface::NewPoWValidBlock, pwalletIn, _1, _2)); + g_signals.m_internals->ProcessPriorityRequest.connect(boost::bind(&CValidationInterface::ProcessPriorityRequest, pwalletIn, _1, _2)); + } void UnregisterValidationInterface(CValidationInterface* pwalletIn) { + g_signals.m_internals->ProcessPriorityRequest.disconnect(boost::bind(&CValidationInterface::ProcessPriorityRequest, pwalletIn, _1, _2)); g_signals.m_internals->BlockChecked.disconnect(boost::bind(&CValidationInterface::BlockChecked, pwalletIn, _1, _2)); g_signals.m_internals->Broadcast.disconnect(boost::bind(&CValidationInterface::ResendWalletTransactions, pwalletIn, _1, _2)); g_signals.m_internals->Inventory.disconnect(boost::bind(&CValidationInterface::Inventory, pwalletIn, _1)); @@ -78,6 +82,7 @@ void UnregisterValidationInterface(CValidationInterface* pwalletIn) { } void UnregisterAllValidationInterfaces() { + g_signals.m_internals->ProcessPriorityRequest.disconnect_all_slots(); g_signals.m_internals->BlockChecked.disconnect_all_slots(); g_signals.m_internals->Broadcast.disconnect_all_slots(); g_signals.m_internals->Inventory.disconnect_all_slots(); @@ -124,3 +129,7 @@ void CMainSignals::BlockChecked(const CBlock& block, const CValidationState& sta void CMainSignals::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr &block) { m_internals->NewPoWValidBlock(pindex, block); } + +void CMainSignals::ProcessPriorityRequest(const std::shared_ptr &pblock, const CBlockIndex *pindex) { + m_internals->ProcessPriorityRequest(pblock, pindex); +} diff --git a/src/validationinterface.h b/src/validationinterface.h index d6da2bc1fd2..45a6b53ac81 100644 --- a/src/validationinterface.h +++ b/src/validationinterface.h @@ -60,6 +60,10 @@ class CValidationInterface { * Notifies listeners that a block which builds directly on our current tip * has been received and connected to the headers tree, though not validated yet */ virtual void NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ptr& block) {}; + /** + * Notifies listeners of that a new priorit block request is ready to process + */ + virtual void ProcessPriorityRequest(const std::shared_ptr &block, const CBlockIndex *pindex) {}; friend void ::RegisterValidationInterface(CValidationInterface*); friend void ::UnregisterValidationInterface(CValidationInterface*); friend void ::UnregisterAllValidationInterfaces(); @@ -91,6 +95,7 @@ class CMainSignals { void Broadcast(int64_t nBestBlockTime, CConnman* connman); void BlockChecked(const CBlock&, const CValidationState&); void NewPoWValidBlock(const CBlockIndex *, const std::shared_ptr&); + void ProcessPriorityRequest(const std::shared_ptr &, const CBlockIndex *pindex); }; CMainSignals& GetMainSignals(); diff --git a/src/zmq/zmqnotificationinterface.cpp b/src/zmq/zmqnotificationinterface.cpp index c0638980568..d4fb9da4156 100644 --- a/src/zmq/zmqnotificationinterface.cpp +++ b/src/zmq/zmqnotificationinterface.cpp @@ -173,6 +173,14 @@ void CZMQNotificationInterface::BlockConnected(const std::shared_ptr& pblock, const CBlockIndex* pindexConnected) +{ + for (const CTransactionRef& ptx : pblock->vtx) { + // Do a normal notify for each transaction added in the block + TransactionAddedToMempool(ptx); + } +} + void CZMQNotificationInterface::BlockDisconnected(const std::shared_ptr& pblock) { for (const CTransactionRef& ptx : pblock->vtx) { diff --git a/src/zmq/zmqnotificationinterface.h b/src/zmq/zmqnotificationinterface.h index eec6f7bc645..81b10ba48dc 100644 --- a/src/zmq/zmqnotificationinterface.h +++ b/src/zmq/zmqnotificationinterface.h @@ -27,6 +27,7 @@ class CZMQNotificationInterface : public CValidationInterface // CValidationInterface void TransactionAddedToMempool(const CTransactionRef& tx) override; void BlockConnected(const std::shared_ptr& pblock, const CBlockIndex* pindexConnected, const std::vector& vtxConflicted) override; + void ProcessPriorityRequest(const std::shared_ptr& pblock, const CBlockIndex* pindexConnected) override; void BlockDisconnected(const std::shared_ptr& pblock) override; void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) override; diff --git a/test/functional/requestblocks.py b/test/functional/requestblocks.py new file mode 100755 index 00000000000..e0301b4b633 --- /dev/null +++ b/test/functional/requestblocks.py @@ -0,0 +1,63 @@ +#!/usr/bin/env python3 +# Copyright (c) 2017 The Bitcoin Core developers +# Distributed under the MIT software license, see the accompanying +# file COPYING or http://www.opensource.org/licenses/mit-license.php. + + +from test_framework.test_framework import BitcoinTestFramework +from test_framework.util import * + +class RequestBlockRequestTest (BitcoinTestFramework): + def __init__(self): + super().__init__() + self.setup_clean_chain = True + self.num_nodes = 2 + self.extra_args = [[], ['-autorequestblocks=0']] + + def run_test(self): + self.nodes[0].generate(50) + timeout = 20 + ctps = self.nodes[1].getchaintips() + while timeout > 0: + ctps = self.nodes[1].getchaintips() + headerHeightReached = False + for ct in ctps: + if ct['status'] == "headers-only": + if ct['height'] == 50: + headerHeightReached = True + if ct['status'] == "active": + assert(ct['height'] == 0) + time.sleep(1) + timeout-=1 + if headerHeightReached == True: + break + assert(timeout>0) + + node0bbhash = self.nodes[0].getbestblockhash() + # best block should not be validated, header must be available + bh = self.nodes[1].getblockheader(node0bbhash, True) + + assert(bh['validated'] == False) + # block must not be available + assert_raises_jsonrpc(-1, "Block not found on disk", self.nodes[1].getblock, node0bbhash, True) + + # request best block (auxiliary) + self.nodes[1].requestblocks("add", [node0bbhash]) + timeout = 20 + while timeout > 0: + if self.nodes[1].requestblocks("status")['count'] == 0: + break; + time.sleep(1) + timeout-=1 + assert(timeout>0) + + # block must now be available + block = self.nodes[1].getblock(node0bbhash, True) + assert(block['hash'] == node0bbhash) + assert(block['validated'] == False) + + #prevblock must not be available + assert_raises_jsonrpc(-1, "Block not found on disk", self.nodes[1].getblock, block['previousblockhash'], True) + +if __name__ == '__main__': + RequestBlockRequestTest ().main () \ No newline at end of file diff --git a/test/functional/test_runner.py b/test/functional/test_runner.py index a043560ea81..b39ed9e4f48 100755 --- a/test/functional/test_runner.py +++ b/test/functional/test_runner.py @@ -116,6 +116,7 @@ 'p2p-leaktests.py', 'wallet-encryption.py', 'uptime.py', + 'requestblocks.py', ] EXTENDED_SCRIPTS = [