Page MenuHomePhabricator

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/src/avalanche/test/processor_tests.cpp b/src/avalanche/test/processor_tests.cpp
index 803375e23..db1f0fdc5 100644
--- a/src/avalanche/test/processor_tests.cpp
+++ b/src/avalanche/test/processor_tests.cpp
@@ -1,948 +1,949 @@
// Copyright (c) 2018-2020 The Bitcoin developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <avalanche/processor.h>
#include <avalanche/peermanager.h>
#include <avalanche/proofbuilder.h>
#include <chain.h>
#include <config.h>
#include <net_processing.h> // For PeerLogicValidation
#include <util/time.h>
// D6970 moved LookupBlockIndex from chain.h to validation.h TODO: remove this
// when LookupBlockIndex is refactored out of validation
#include <validation.h>
#include <test/util/setup_common.h>
#include <boost/test/unit_test.hpp>
using namespace avalanche;
namespace avalanche {
namespace {
struct AvalancheTest {
static void runEventLoop(avalanche::Processor &p) { p.runEventLoop(); }
static std::vector<CInv> getInvsForNextPoll(Processor &p) {
return p.getInvsForNextPoll(false);
}
static NodeId getSuitableNodeToQuery(Processor &p) {
return p.getSuitableNodeToQuery();
}
static PeerManager &getPeerManager(Processor &p) {
LOCK(p.cs_peerManager);
return *p.peerManager;
}
static uint64_t getRound(const Processor &p) { return p.round; }
};
} // namespace
} // namespace avalanche
namespace {
struct CConnmanTest : public CConnman {
using CConnman::CConnman;
void AddNode(CNode &node) {
LOCK(cs_vNodes);
vNodes.push_back(&node);
}
void ClearNodes() {
LOCK(cs_vNodes);
for (CNode *node : vNodes) {
delete node;
}
vNodes.clear();
}
};
CService ip(uint32_t i) {
struct in_addr s;
s.s_addr = i;
return CService(CNetAddr(s), Params().GetDefaultPort());
}
struct AvalancheTestingSetup : public TestChain100Setup {
const Config &config;
CConnmanTest *m_connman;
CKey masterpriv;
AvalancheTestingSetup()
: TestChain100Setup(), config(GetConfig()), masterpriv() {
// Deterministic randomness for tests.
auto connman = std::make_unique<CConnmanTest>(config, 0x1337, 0x1337);
m_connman = connman.get();
m_node.connman = std::move(connman);
m_node.peer_logic = std::make_unique<PeerLogicValidation>(
- m_connman, m_node.banman.get(), *m_node.scheduler);
+ m_connman, m_node.banman.get(), *m_node.scheduler,
+ *m_node.chainman);
m_node.chain = interfaces::MakeChain(m_node, config.GetChainParams());
// The master private key we delegate to.
masterpriv.MakeNewKey(true);
}
~AvalancheTestingSetup() { m_connman->ClearNodes(); }
CNode *ConnectNode(ServiceFlags nServices) {
static NodeId id = 0;
CAddress addr(ip(GetRandInt(0xffffffff)), NODE_NONE);
auto node = new CNode(id++, ServiceFlags(NODE_NETWORK), 0,
INVALID_SOCKET, addr, 0, 0, CAddress(), "",
/*fInboundIn=*/false);
node->SetSendVersion(PROTOCOL_VERSION);
node->nServices = nServices;
m_node.peer_logic->InitializeNode(config, node);
node->nVersion = 1;
node->fSuccessfullyConnected = true;
m_connman->AddNode(*node);
return node;
}
size_t next_coinbase = 0;
Proof GetProof() {
size_t current_coinbase = next_coinbase++;
const CTransaction &coinbase = *m_coinbase_txns[current_coinbase];
ProofBuilder pb(0, 0, masterpriv.GetPubKey());
BOOST_CHECK(pb.addUTXO(COutPoint(coinbase.GetId(), 0),
coinbase.vout[0].nValue, current_coinbase + 1,
true, coinbaseKey));
return pb.build();
}
std::array<CNode *, 8> ConnectNodes(Processor &p) {
PeerManager &pm = AvalancheTest::getPeerManager(p);
Proof proof = GetProof();
std::array<CNode *, 8> nodes;
for (CNode *&n : nodes) {
n = ConnectNode(NODE_AVALANCHE);
BOOST_CHECK(pm.addNode(n->GetId(), proof, masterpriv.GetPubKey()));
}
return nodes;
}
};
} // namespace
BOOST_FIXTURE_TEST_SUITE(processor_tests, AvalancheTestingSetup)
#define REGISTER_VOTE_AND_CHECK(vr, vote, state, finalized, confidence) \
vr.registerVote(NO_NODE, vote); \
BOOST_CHECK_EQUAL(vr.isAccepted(), state); \
BOOST_CHECK_EQUAL(vr.hasFinalized(), finalized); \
BOOST_CHECK_EQUAL(vr.getConfidence(), confidence);
BOOST_AUTO_TEST_CASE(vote_record) {
VoteRecord vraccepted(true);
// Check initial state.
BOOST_CHECK_EQUAL(vraccepted.isAccepted(), true);
BOOST_CHECK_EQUAL(vraccepted.hasFinalized(), false);
BOOST_CHECK_EQUAL(vraccepted.getConfidence(), 0);
VoteRecord vr(false);
// Check initial state.
BOOST_CHECK_EQUAL(vr.isAccepted(), false);
BOOST_CHECK_EQUAL(vr.hasFinalized(), false);
BOOST_CHECK_EQUAL(vr.getConfidence(), 0);
// We need to register 6 positive votes before we start counting.
for (int i = 0; i < 6; i++) {
REGISTER_VOTE_AND_CHECK(vr, 0, false, false, 0);
}
// Next vote will flip state, and confidence will increase as long as we
// vote yes.
REGISTER_VOTE_AND_CHECK(vr, 0, true, false, 0);
// A single neutral vote do not change anything.
REGISTER_VOTE_AND_CHECK(vr, -1, true, false, 1);
for (int i = 2; i < 8; i++) {
REGISTER_VOTE_AND_CHECK(vr, 0, true, false, i);
}
// Two neutral votes will stall progress.
REGISTER_VOTE_AND_CHECK(vr, -1, true, false, 7);
REGISTER_VOTE_AND_CHECK(vr, -1, true, false, 7);
for (int i = 2; i < 8; i++) {
REGISTER_VOTE_AND_CHECK(vr, 0, true, false, 7);
}
// Now confidence will increase as long as we vote yes.
for (int i = 8; i < AVALANCHE_FINALIZATION_SCORE; i++) {
REGISTER_VOTE_AND_CHECK(vr, 0, true, false, i);
}
// The next vote will finalize the decision.
REGISTER_VOTE_AND_CHECK(vr, 1, true, true, AVALANCHE_FINALIZATION_SCORE);
// Now that we have two no votes, confidence stop increasing.
for (int i = 0; i < 5; i++) {
REGISTER_VOTE_AND_CHECK(vr, 1, true, true,
AVALANCHE_FINALIZATION_SCORE);
}
// Next vote will flip state, and confidence will increase as long as we
// vote no.
REGISTER_VOTE_AND_CHECK(vr, 1, false, false, 0);
// A single neutral vote do not change anything.
REGISTER_VOTE_AND_CHECK(vr, -1, false, false, 1);
for (int i = 2; i < 8; i++) {
REGISTER_VOTE_AND_CHECK(vr, 1, false, false, i);
}
// Two neutral votes will stall progress.
REGISTER_VOTE_AND_CHECK(vr, -1, false, false, 7);
REGISTER_VOTE_AND_CHECK(vr, -1, false, false, 7);
for (int i = 2; i < 8; i++) {
REGISTER_VOTE_AND_CHECK(vr, 1, false, false, 7);
}
// Now confidence will increase as long as we vote no.
for (int i = 8; i < AVALANCHE_FINALIZATION_SCORE; i++) {
REGISTER_VOTE_AND_CHECK(vr, 1, false, false, i);
}
// The next vote will finalize the decision.
REGISTER_VOTE_AND_CHECK(vr, 0, false, true, AVALANCHE_FINALIZATION_SCORE);
// Check that inflight accounting work as expected.
VoteRecord vrinflight(false);
for (int i = 0; i < 2 * AVALANCHE_MAX_INFLIGHT_POLL; i++) {
bool shouldPoll = vrinflight.shouldPoll();
BOOST_CHECK_EQUAL(shouldPoll, i < AVALANCHE_MAX_INFLIGHT_POLL);
BOOST_CHECK_EQUAL(vrinflight.registerPoll(), shouldPoll);
}
// Clear various number of inflight requests and check everything behaves as
// expected.
for (int i = 1; i < AVALANCHE_MAX_INFLIGHT_POLL; i++) {
vrinflight.clearInflightRequest(i);
BOOST_CHECK(vrinflight.shouldPoll());
for (int j = 1; j < i; j++) {
BOOST_CHECK(vrinflight.registerPoll());
BOOST_CHECK(vrinflight.shouldPoll());
}
BOOST_CHECK(vrinflight.registerPoll());
BOOST_CHECK(!vrinflight.shouldPoll());
}
}
BOOST_AUTO_TEST_CASE(block_update) {
CBlockIndex index;
CBlockIndex *pindex = &index;
std::set<BlockUpdate::Status> status{
BlockUpdate::Status::Invalid,
BlockUpdate::Status::Rejected,
BlockUpdate::Status::Accepted,
BlockUpdate::Status::Finalized,
};
for (auto s : status) {
BlockUpdate abu(pindex, s);
BOOST_CHECK(abu.getBlockIndex() == pindex);
BOOST_CHECK_EQUAL(abu.getStatus(), s);
}
}
namespace {
Response next(Response &r) {
auto copy = r;
r = {r.getRound() + 1, r.getCooldown(), r.GetVotes()};
return copy;
}
} // namespace
BOOST_AUTO_TEST_CASE(block_register) {
Processor p(*m_node.chain, m_node.connman.get());
std::vector<BlockUpdate> updates;
CBlock block = CreateAndProcessBlock({}, CScript());
const BlockHash blockHash = block.GetHash();
const CBlockIndex *pindex;
{
LOCK(cs_main);
pindex = LookupBlockIndex(blockHash);
}
// Create nodes that supports avalanche.
auto avanodes = ConnectNodes(p);
// Querying for random block returns false.
BOOST_CHECK(!p.isAccepted(pindex));
// Add a new block. Check it is added to the polls.
BOOST_CHECK(p.addBlockToReconcile(pindex));
auto invs = AvalancheTest::getInvsForNextPoll(p);
BOOST_CHECK_EQUAL(invs.size(), 1);
BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK);
BOOST_CHECK(invs[0].hash == blockHash);
// Newly added blocks' state reflect the blockchain.
BOOST_CHECK(p.isAccepted(pindex));
int nextNodeIndex = 0;
auto registerNewVote = [&](const Response &resp) {
AvalancheTest::runEventLoop(p);
auto nodeid = avanodes[nextNodeIndex++ % avanodes.size()]->GetId();
BOOST_CHECK(p.registerVotes(nodeid, resp, updates));
};
// Let's vote for this block a few times.
Response resp{0, 0, {Vote(0, blockHash)}};
for (int i = 0; i < 6; i++) {
registerNewVote(next(resp));
BOOST_CHECK(p.isAccepted(pindex));
BOOST_CHECK_EQUAL(p.getConfidence(pindex), 0);
BOOST_CHECK_EQUAL(updates.size(), 0);
}
// A single neutral vote do not change anything.
resp = {AvalancheTest::getRound(p), 0, {Vote(-1, blockHash)}};
registerNewVote(next(resp));
BOOST_CHECK(p.isAccepted(pindex));
BOOST_CHECK_EQUAL(p.getConfidence(pindex), 0);
BOOST_CHECK_EQUAL(updates.size(), 0);
resp = {AvalancheTest::getRound(p), 0, {Vote(0, blockHash)}};
for (int i = 1; i < 7; i++) {
registerNewVote(next(resp));
BOOST_CHECK(p.isAccepted(pindex));
BOOST_CHECK_EQUAL(p.getConfidence(pindex), i);
BOOST_CHECK_EQUAL(updates.size(), 0);
}
// Two neutral votes will stall progress.
resp = {AvalancheTest::getRound(p), 0, {Vote(-1, blockHash)}};
registerNewVote(next(resp));
BOOST_CHECK(p.isAccepted(pindex));
BOOST_CHECK_EQUAL(p.getConfidence(pindex), 6);
BOOST_CHECK_EQUAL(updates.size(), 0);
registerNewVote(next(resp));
BOOST_CHECK(p.isAccepted(pindex));
BOOST_CHECK_EQUAL(p.getConfidence(pindex), 6);
BOOST_CHECK_EQUAL(updates.size(), 0);
resp = {AvalancheTest::getRound(p), 0, {Vote(0, blockHash)}};
for (int i = 2; i < 8; i++) {
registerNewVote(next(resp));
BOOST_CHECK(p.isAccepted(pindex));
BOOST_CHECK_EQUAL(p.getConfidence(pindex), 6);
BOOST_CHECK_EQUAL(updates.size(), 0);
}
// We vote for it numerous times to finalize it.
for (int i = 7; i < AVALANCHE_FINALIZATION_SCORE; i++) {
registerNewVote(next(resp));
BOOST_CHECK(p.isAccepted(pindex));
BOOST_CHECK_EQUAL(p.getConfidence(pindex), i);
BOOST_CHECK_EQUAL(updates.size(), 0);
}
// As long as it is not finalized, we poll.
invs = AvalancheTest::getInvsForNextPoll(p);
BOOST_CHECK_EQUAL(invs.size(), 1);
BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK);
BOOST_CHECK(invs[0].hash == blockHash);
// Now finalize the decision.
registerNewVote(next(resp));
BOOST_CHECK_EQUAL(updates.size(), 1);
BOOST_CHECK(updates[0].getBlockIndex() == pindex);
BOOST_CHECK_EQUAL(updates[0].getStatus(), BlockUpdate::Status::Finalized);
updates = {};
// Once the decision is finalized, there is no poll for it.
invs = AvalancheTest::getInvsForNextPoll(p);
BOOST_CHECK_EQUAL(invs.size(), 0);
// Now let's undo this and finalize rejection.
BOOST_CHECK(p.addBlockToReconcile(pindex));
invs = AvalancheTest::getInvsForNextPoll(p);
BOOST_CHECK_EQUAL(invs.size(), 1);
BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK);
BOOST_CHECK(invs[0].hash == blockHash);
resp = {AvalancheTest::getRound(p), 0, {Vote(1, blockHash)}};
for (int i = 0; i < 6; i++) {
registerNewVote(next(resp));
BOOST_CHECK(p.isAccepted(pindex));
BOOST_CHECK_EQUAL(updates.size(), 0);
}
// Now the state will flip.
registerNewVote(next(resp));
BOOST_CHECK(!p.isAccepted(pindex));
BOOST_CHECK_EQUAL(updates.size(), 1);
BOOST_CHECK(updates[0].getBlockIndex() == pindex);
BOOST_CHECK_EQUAL(updates[0].getStatus(), BlockUpdate::Status::Rejected);
updates = {};
// Now it is rejected, but we can vote for it numerous times.
for (int i = 1; i < AVALANCHE_FINALIZATION_SCORE; i++) {
registerNewVote(next(resp));
BOOST_CHECK(!p.isAccepted(pindex));
BOOST_CHECK_EQUAL(updates.size(), 0);
}
// As long as it is not finalized, we poll.
invs = AvalancheTest::getInvsForNextPoll(p);
BOOST_CHECK_EQUAL(invs.size(), 1);
BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK);
BOOST_CHECK(invs[0].hash == blockHash);
// Now finalize the decision.
registerNewVote(next(resp));
BOOST_CHECK(!p.isAccepted(pindex));
BOOST_CHECK_EQUAL(updates.size(), 1);
BOOST_CHECK(updates[0].getBlockIndex() == pindex);
BOOST_CHECK_EQUAL(updates[0].getStatus(), BlockUpdate::Status::Invalid);
updates = {};
// Once the decision is finalized, there is no poll for it.
invs = AvalancheTest::getInvsForNextPoll(p);
BOOST_CHECK_EQUAL(invs.size(), 0);
// Adding the block twice does nothing.
BOOST_CHECK(p.addBlockToReconcile(pindex));
BOOST_CHECK(!p.addBlockToReconcile(pindex));
BOOST_CHECK(p.isAccepted(pindex));
}
BOOST_AUTO_TEST_CASE(multi_block_register) {
Processor p(*m_node.chain, m_node.connman.get());
CBlockIndex indexA, indexB;
std::vector<BlockUpdate> updates;
// Create several nodes that support avalanche.
auto avanodes = ConnectNodes(p);
// Make sure the block has a hash.
CBlock blockA = CreateAndProcessBlock({}, CScript());
const BlockHash blockHashA = blockA.GetHash();
CBlock blockB = CreateAndProcessBlock({}, CScript());
const BlockHash blockHashB = blockB.GetHash();
const CBlockIndex *pindexA;
const CBlockIndex *pindexB;
{
LOCK(cs_main);
pindexA = LookupBlockIndex(blockHashA);
pindexB = LookupBlockIndex(blockHashB);
}
// Querying for random block returns false.
BOOST_CHECK(!p.isAccepted(pindexA));
BOOST_CHECK(!p.isAccepted(pindexB));
// Start voting on block A.
BOOST_CHECK(p.addBlockToReconcile(pindexA));
auto invs = AvalancheTest::getInvsForNextPoll(p);
BOOST_CHECK_EQUAL(invs.size(), 1);
BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK);
BOOST_CHECK(invs[0].hash == blockHashA);
uint64_t round = AvalancheTest::getRound(p);
AvalancheTest::runEventLoop(p);
BOOST_CHECK(p.registerVotes(avanodes[0]->GetId(),
{round, 0, {Vote(0, blockHashA)}}, updates));
BOOST_CHECK_EQUAL(updates.size(), 0);
// Start voting on block B after one vote.
Response resp{round + 1, 0, {Vote(0, blockHashB), Vote(0, blockHashA)}};
BOOST_CHECK(p.addBlockToReconcile(pindexB));
invs = AvalancheTest::getInvsForNextPoll(p);
BOOST_CHECK_EQUAL(invs.size(), 2);
// Ensure B comes before A because it has accumulated more PoW.
BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK);
BOOST_CHECK(invs[0].hash == blockHashB);
BOOST_CHECK_EQUAL(invs[1].type, MSG_BLOCK);
BOOST_CHECK(invs[1].hash == blockHashA);
// Let's vote for these blocks a few times.
for (int i = 0; i < 4; i++) {
NodeId nodeid = AvalancheTest::getSuitableNodeToQuery(p);
AvalancheTest::runEventLoop(p);
BOOST_CHECK(p.registerVotes(nodeid, next(resp), updates));
BOOST_CHECK_EQUAL(updates.size(), 0);
}
// Now it is accepted, but we can vote for it numerous times.
for (int i = 0; i < AVALANCHE_FINALIZATION_SCORE; i++) {
NodeId nodeid = AvalancheTest::getSuitableNodeToQuery(p);
AvalancheTest::runEventLoop(p);
BOOST_CHECK(p.registerVotes(nodeid, next(resp), updates));
BOOST_CHECK_EQUAL(updates.size(), 0);
}
// Running two iterration of the event loop so that vote gets triggered on A
// and B.
NodeId firstNodeid = AvalancheTest::getSuitableNodeToQuery(p);
AvalancheTest::runEventLoop(p);
NodeId secondNodeid = AvalancheTest::getSuitableNodeToQuery(p);
AvalancheTest::runEventLoop(p);
BOOST_CHECK(firstNodeid != secondNodeid);
// Next vote will finalize block A.
BOOST_CHECK(p.registerVotes(firstNodeid, next(resp), updates));
BOOST_CHECK_EQUAL(updates.size(), 1);
BOOST_CHECK(updates[0].getBlockIndex() == pindexA);
BOOST_CHECK_EQUAL(updates[0].getStatus(), BlockUpdate::Status::Finalized);
updates = {};
// We do not vote on A anymore.
invs = AvalancheTest::getInvsForNextPoll(p);
BOOST_CHECK_EQUAL(invs.size(), 1);
BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK);
BOOST_CHECK(invs[0].hash == blockHashB);
// Next vote will finalize block B.
BOOST_CHECK(p.registerVotes(secondNodeid, resp, updates));
BOOST_CHECK_EQUAL(updates.size(), 1);
BOOST_CHECK(updates[0].getBlockIndex() == pindexB);
BOOST_CHECK_EQUAL(updates[0].getStatus(), BlockUpdate::Status::Finalized);
updates = {};
// There is nothing left to vote on.
invs = AvalancheTest::getInvsForNextPoll(p);
BOOST_CHECK_EQUAL(invs.size(), 0);
}
BOOST_AUTO_TEST_CASE(poll_and_response) {
Processor p(*m_node.chain, m_node.connman.get());
std::vector<BlockUpdate> updates;
CBlock block = CreateAndProcessBlock({}, CScript());
const BlockHash blockHash = block.GetHash();
const CBlockIndex *pindex;
{
LOCK(cs_main);
pindex = LookupBlockIndex(blockHash);
}
// There is no node to query.
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), NO_NODE);
// Create a node that supports avalanche and one that doesn't.
ConnectNode(NODE_NONE);
auto avanode = ConnectNode(NODE_AVALANCHE);
NodeId avanodeid = avanode->GetId();
BOOST_CHECK(p.addNode(avanodeid, GetProof(), CPubKey()));
// It returns the avalanche peer.
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid);
// Register a block and check it is added to the list of elements to poll.
BOOST_CHECK(p.addBlockToReconcile(pindex));
auto invs = AvalancheTest::getInvsForNextPoll(p);
BOOST_CHECK_EQUAL(invs.size(), 1);
BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK);
BOOST_CHECK(invs[0].hash == blockHash);
// Trigger a poll on avanode.
uint64_t round = AvalancheTest::getRound(p);
AvalancheTest::runEventLoop(p);
// There is no more suitable peer available, so return nothing.
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), NO_NODE);
// Respond to the request.
Response resp = {round, 0, {Vote(0, blockHash)}};
BOOST_CHECK(p.registerVotes(avanodeid, resp, updates));
BOOST_CHECK_EQUAL(updates.size(), 0);
// Now that avanode fullfilled his request, it is added back to the list of
// queriable nodes.
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid);
// Sending a response when not polled fails.
BOOST_CHECK(!p.registerVotes(avanodeid, next(resp), updates));
BOOST_CHECK_EQUAL(updates.size(), 0);
// Trigger a poll on avanode.
round = AvalancheTest::getRound(p);
AvalancheTest::runEventLoop(p);
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), NO_NODE);
// Sending responses that do not match the request also fails.
// 1. Too many results.
resp = {round, 0, {Vote(0, blockHash), Vote(0, blockHash)}};
AvalancheTest::runEventLoop(p);
BOOST_CHECK(!p.registerVotes(avanodeid, resp, updates));
BOOST_CHECK_EQUAL(updates.size(), 0);
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid);
// 2. Not enough results.
resp = {AvalancheTest::getRound(p), 0, {}};
AvalancheTest::runEventLoop(p);
BOOST_CHECK(!p.registerVotes(avanodeid, resp, updates));
BOOST_CHECK_EQUAL(updates.size(), 0);
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid);
// 3. Do not match the poll.
resp = {AvalancheTest::getRound(p), 0, {Vote()}};
AvalancheTest::runEventLoop(p);
BOOST_CHECK(!p.registerVotes(avanodeid, resp, updates));
BOOST_CHECK_EQUAL(updates.size(), 0);
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid);
// 4. Invalid round count. Request is not discarded.
uint64_t queryRound = AvalancheTest::getRound(p);
AvalancheTest::runEventLoop(p);
resp = {queryRound + 1, 0, {Vote()}};
BOOST_CHECK(!p.registerVotes(avanodeid, resp, updates));
BOOST_CHECK_EQUAL(updates.size(), 0);
resp = {queryRound - 1, 0, {Vote()}};
BOOST_CHECK(!p.registerVotes(avanodeid, resp, updates));
BOOST_CHECK_EQUAL(updates.size(), 0);
// 5. Making request for invalid nodes do not work. Request is not
// discarded.
resp = {queryRound, 0, {Vote(0, blockHash)}};
BOOST_CHECK(!p.registerVotes(avanodeid + 1234, resp, updates));
BOOST_CHECK_EQUAL(updates.size(), 0);
// Proper response gets processed and avanode is available again.
resp = {queryRound, 0, {Vote(0, blockHash)}};
BOOST_CHECK(p.registerVotes(avanodeid, resp, updates));
BOOST_CHECK_EQUAL(updates.size(), 0);
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid);
// Out of order response are rejected.
CBlock block2 = CreateAndProcessBlock({}, CScript());
const BlockHash blockHash2 = block2.GetHash();
CBlockIndex *pindex2;
{
LOCK(cs_main);
pindex2 = LookupBlockIndex(blockHash2);
}
BOOST_CHECK(p.addBlockToReconcile(pindex2));
resp = {AvalancheTest::getRound(p),
0,
{Vote(0, blockHash), Vote(0, blockHash2)}};
AvalancheTest::runEventLoop(p);
BOOST_CHECK(!p.registerVotes(avanodeid, resp, updates));
BOOST_CHECK_EQUAL(updates.size(), 0);
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid);
// But they are accepted in order.
resp = {AvalancheTest::getRound(p),
0,
{Vote(0, blockHash2), Vote(0, blockHash)}};
AvalancheTest::runEventLoop(p);
BOOST_CHECK(p.registerVotes(avanodeid, resp, updates));
BOOST_CHECK_EQUAL(updates.size(), 0);
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid);
// When a block is marked invalid, stop polling.
pindex2->nStatus = pindex2->nStatus.withFailed();
resp = {AvalancheTest::getRound(p), 0, {Vote(0, blockHash)}};
AvalancheTest::runEventLoop(p);
BOOST_CHECK(p.registerVotes(avanodeid, resp, updates));
BOOST_CHECK_EQUAL(updates.size(), 0);
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), avanodeid);
}
BOOST_AUTO_TEST_CASE(poll_inflight_timeout, *boost::unit_test::timeout(60)) {
Processor p(*m_node.chain, m_node.connman.get());
std::vector<BlockUpdate> updates;
CBlock block = CreateAndProcessBlock({}, CScript());
const BlockHash blockHash = block.GetHash();
const CBlockIndex *pindex;
{
LOCK(cs_main);
pindex = LookupBlockIndex(blockHash);
}
// Add the block
BOOST_CHECK(p.addBlockToReconcile(pindex));
// Create a node that supports avalanche.
auto avanode = ConnectNode(NODE_AVALANCHE);
NodeId avanodeid = avanode->GetId();
BOOST_CHECK(p.addNode(avanodeid, GetProof(), CPubKey()));
// Expire requests after some time.
auto queryTimeDuration = std::chrono::milliseconds(10);
p.setQueryTimeoutDuration(queryTimeDuration);
for (int i = 0; i < 10; i++) {
Response resp = {AvalancheTest::getRound(p), 0, {Vote(0, blockHash)}};
auto start = std::chrono::steady_clock::now();
AvalancheTest::runEventLoop(p);
// We cannot guarantee that we'll wait for just 1ms, so we have to bail
// if we aren't within the proper time range.
std::this_thread::sleep_for(std::chrono::milliseconds(1));
AvalancheTest::runEventLoop(p);
bool ret = p.registerVotes(avanodeid, next(resp), updates);
if (std::chrono::steady_clock::now() > start + queryTimeDuration) {
// We waited for too long, bail. Because we can't know for sure when
// previous steps ran, ret is not deterministic and we do not check
// it.
i--;
continue;
}
// We are within time bounds, so the vote should have worked.
BOOST_CHECK(ret);
// Now try again but wait for expiration.
AvalancheTest::runEventLoop(p);
std::this_thread::sleep_for(queryTimeDuration);
AvalancheTest::runEventLoop(p);
BOOST_CHECK(!p.registerVotes(avanodeid, next(resp), updates));
}
}
BOOST_AUTO_TEST_CASE(poll_inflight_count) {
Processor p(*m_node.chain, m_node.connman.get());
// Create enough nodes so that we run into the inflight request limit.
PeerManager &pm = AvalancheTest::getPeerManager(p);
Proof proof = GetProof();
std::array<CNode *, AVALANCHE_MAX_INFLIGHT_POLL + 1> nodes;
for (auto &n : nodes) {
n = ConnectNode(NODE_AVALANCHE);
BOOST_CHECK(pm.addNode(n->GetId(), proof, CPubKey()));
}
// Add a block to poll
CBlock block = CreateAndProcessBlock({}, CScript());
const BlockHash blockHash = block.GetHash();
const CBlockIndex *pindex;
{
LOCK(cs_main);
pindex = LookupBlockIndex(blockHash);
}
BOOST_CHECK(p.addBlockToReconcile(pindex));
// Ensure there are enough requests in flight.
std::map<NodeId, uint64_t> node_round_map;
for (int i = 0; i < AVALANCHE_MAX_INFLIGHT_POLL; i++) {
NodeId nodeid = AvalancheTest::getSuitableNodeToQuery(p);
BOOST_CHECK(node_round_map.find(nodeid) == node_round_map.end());
node_round_map[nodeid] = AvalancheTest::getRound(p);
auto invs = AvalancheTest::getInvsForNextPoll(p);
BOOST_CHECK_EQUAL(invs.size(), 1);
BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK);
BOOST_CHECK(invs[0].hash == blockHash);
AvalancheTest::runEventLoop(p);
}
// Now that we have enough in flight requests, we shouldn't poll.
auto suitablenodeid = AvalancheTest::getSuitableNodeToQuery(p);
BOOST_CHECK(suitablenodeid != NO_NODE);
auto invs = AvalancheTest::getInvsForNextPoll(p);
BOOST_CHECK_EQUAL(invs.size(), 0);
AvalancheTest::runEventLoop(p);
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), suitablenodeid);
std::vector<BlockUpdate> updates;
// Send one response, now we can poll again.
auto it = node_round_map.begin();
Response resp = {it->second, 0, {Vote(0, blockHash)}};
BOOST_CHECK(p.registerVotes(it->first, resp, updates));
node_round_map.erase(it);
invs = AvalancheTest::getInvsForNextPoll(p);
BOOST_CHECK_EQUAL(invs.size(), 1);
BOOST_CHECK_EQUAL(invs[0].type, MSG_BLOCK);
BOOST_CHECK(invs[0].hash == blockHash);
}
BOOST_AUTO_TEST_CASE(quorum_diversity) {
Processor p(*m_node.chain, m_node.connman.get());
std::vector<BlockUpdate> updates;
CBlock block = CreateAndProcessBlock({}, CScript());
const BlockHash blockHash = block.GetHash();
const CBlockIndex *pindex;
{
LOCK(cs_main);
pindex = LookupBlockIndex(blockHash);
}
// Create nodes that supports avalanche.
auto avanodes = ConnectNodes(p);
// Querying for random block returns false.
BOOST_CHECK(!p.isAccepted(pindex));
// Add a new block. Check it is added to the polls.
BOOST_CHECK(p.addBlockToReconcile(pindex));
// Do one valid round of voting.
uint64_t round = AvalancheTest::getRound(p);
Response resp{round, 0, {Vote(0, blockHash)}};
// Check that all nodes can vote.
for (size_t i = 0; i < avanodes.size(); i++) {
AvalancheTest::runEventLoop(p);
BOOST_CHECK(p.registerVotes(avanodes[i]->GetId(), next(resp), updates));
}
// Generate a query for every single node.
const NodeId firstNodeId = AvalancheTest::getSuitableNodeToQuery(p);
std::map<NodeId, uint64_t> node_round_map;
round = AvalancheTest::getRound(p);
for (size_t i = 0; i < avanodes.size(); i++) {
NodeId nodeid = AvalancheTest::getSuitableNodeToQuery(p);
BOOST_CHECK(node_round_map.find(nodeid) == node_round_map.end());
node_round_map[nodeid] = AvalancheTest::getRound(p);
AvalancheTest::runEventLoop(p);
}
// Now only tge first node can vote. All others would be duplicate in the
// quorum.
auto confidence = p.getConfidence(pindex);
BOOST_REQUIRE(confidence > 0);
for (auto &pair : node_round_map) {
NodeId nodeid = pair.first;
uint64_t r = pair.second;
if (nodeid == firstNodeId) {
// Node 0 is the only one which can vote at this stage.
round = r;
continue;
}
BOOST_CHECK(
p.registerVotes(nodeid, {r, 0, {Vote(0, blockHash)}}, updates));
BOOST_CHECK_EQUAL(p.getConfidence(pindex), confidence);
}
BOOST_CHECK(p.registerVotes(firstNodeId, {round, 0, {Vote(0, blockHash)}},
updates));
BOOST_CHECK_EQUAL(p.getConfidence(pindex), confidence + 1);
}
BOOST_AUTO_TEST_CASE(event_loop) {
Processor p(*m_node.chain, m_node.connman.get());
CScheduler s;
CBlock block = CreateAndProcessBlock({}, CScript());
const BlockHash blockHash = block.GetHash();
const CBlockIndex *pindex;
{
LOCK(cs_main);
pindex = LookupBlockIndex(blockHash);
}
// Starting the event loop.
BOOST_CHECK(p.startEventLoop(s));
// There is one task planned in the next hour (our event loop).
std::chrono::system_clock::time_point start, stop;
BOOST_CHECK_EQUAL(s.getQueueInfo(start, stop), 1);
// Starting twice doesn't start it twice.
BOOST_CHECK(!p.startEventLoop(s));
// Start the scheduler thread.
std::thread schedulerThread(std::bind(&CScheduler::serviceQueue, &s));
// Create a node that supports avalanche.
auto avanode = ConnectNode(NODE_AVALANCHE);
NodeId nodeid = avanode->GetId();
BOOST_CHECK(p.addNode(nodeid, GetProof(), CPubKey()));
// There is no query in flight at the moment.
BOOST_CHECK_EQUAL(AvalancheTest::getSuitableNodeToQuery(p), nodeid);
// Add a new block. Check it is added to the polls.
uint64_t queryRound = AvalancheTest::getRound(p);
BOOST_CHECK(p.addBlockToReconcile(pindex));
for (int i = 0; i < 60 * 1000; i++) {
// Technically, this is a race condition, but this should do just fine
// as we wait up to 1 minute for an event that should take 10ms.
UninterruptibleSleep(std::chrono::milliseconds(1));
if (AvalancheTest::getRound(p) != queryRound) {
break;
}
}
// Check that we effectively got a request and not timed out.
BOOST_CHECK(AvalancheTest::getRound(p) > queryRound);
// Respond and check the cooldown time is respected.
uint64_t responseRound = AvalancheTest::getRound(p);
auto queryTime =
std::chrono::steady_clock::now() + std::chrono::milliseconds(100);
std::vector<BlockUpdate> updates;
p.registerVotes(nodeid, {queryRound, 100, {Vote(0, blockHash)}}, updates);
for (int i = 0; i < 10000; i++) {
// We make sure that we do not get a request before queryTime.
UninterruptibleSleep(std::chrono::milliseconds(1));
if (AvalancheTest::getRound(p) != responseRound) {
BOOST_CHECK(std::chrono::steady_clock::now() > queryTime);
break;
}
}
// But we eventually get one.
BOOST_CHECK(AvalancheTest::getRound(p) > responseRound);
// Stop event loop.
BOOST_CHECK(p.stopEventLoop());
// We don't have any task scheduled anymore.
BOOST_CHECK_EQUAL(s.getQueueInfo(start, stop), 0);
// Can't stop the event loop twice.
BOOST_CHECK(!p.stopEventLoop());
// Wait for the scheduler to stop.
s.stop(true);
schedulerThread.join();
}
BOOST_AUTO_TEST_CASE(destructor) {
CScheduler s;
std::chrono::system_clock::time_point start, stop;
std::thread schedulerThread;
{
Processor p(*m_node.chain, m_node.connman.get());
BOOST_CHECK(p.startEventLoop(s));
BOOST_CHECK_EQUAL(s.getQueueInfo(start, stop), 1);
// Start the service thread after the queue size check to prevent a
// race condition where the thread may be processing the event loop
// task during the check.
schedulerThread = std::thread(std::bind(&CScheduler::serviceQueue, &s));
}
// Now that avalanche is destroyed, there is no more scheduled tasks.
BOOST_CHECK_EQUAL(s.getQueueInfo(start, stop), 0);
// Wait for the scheduler to stop.
s.stop(true);
schedulerThread.join();
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/init.cpp b/src/init.cpp
index 002c59b8b..a157f80c3 100644
--- a/src/init.cpp
+++ b/src/init.cpp
@@ -1,2892 +1,2893 @@
// Copyright (c) 2009-2010 Satoshi Nakamoto
// Copyright (c) 2009-2018 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 <init.h>
#include <addrman.h>
#include <amount.h>
#include <avalanche/processor.h>
#include <banman.h>
#include <blockdb.h>
#include <blockfilter.h>
#include <chain.h>
#include <chainparams.h>
#include <checkpoints.h>
#include <compat/sanity.h>
#include <config.h>
#include <consensus/validation.h>
#include <flatfile.h>
#include <fs.h>
#include <hash.h>
#include <httprpc.h>
#include <httpserver.h>
#include <index/blockfilterindex.h>
#include <index/txindex.h>
#include <interfaces/chain.h>
#include <key.h>
#include <miner.h>
#include <net.h>
#include <net_permissions.h>
#include <net_processing.h>
#include <netbase.h>
#include <network.h>
#include <node/context.h>
#include <policy/mempool.h>
#include <policy/policy.h>
#include <policy/settings.h>
#include <rpc/blockchain.h>
#include <rpc/register.h>
#include <rpc/server.h>
#include <rpc/util.h>
#include <scheduler.h>
#include <script/scriptcache.h>
#include <script/sigcache.h>
#include <script/standard.h>
#include <shutdown.h>
#include <timedata.h>
#include <torcontrol.h>
#include <txdb.h>
#include <txmempool.h>
#include <ui_interface.h>
#include <util/asmap.h>
#include <util/moneystr.h>
#include <util/threadnames.h>
#include <util/translation.h>
#include <validation.h>
#include <validationinterface.h>
#include <walletinitinterface.h>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/replace.hpp>
#include <boost/algorithm/string/split.hpp>
#include <boost/thread.hpp>
#if ENABLE_ZMQ
#include <zmq/zmqabstractnotifier.h>
#include <zmq/zmqnotificationinterface.h>
#include <zmq/zmqrpc.h>
#endif
#ifndef WIN32
#include <attributes.h>
#include <cerrno>
#include <csignal>
#include <sys/stat.h>
#endif
#include <cstdint>
#include <cstdio>
#include <functional>
#include <set>
static const bool DEFAULT_PROXYRANDOMIZE = true;
static const bool DEFAULT_REST_ENABLE = false;
static const bool DEFAULT_STOPAFTERBLOCKIMPORT = false;
#ifdef WIN32
// Win32 LevelDB doesn't use filedescriptors, and the ones used for accessing
// block files don't count towards the fd_set size limit anyway.
#define MIN_CORE_FILEDESCRIPTORS 0
#else
#define MIN_CORE_FILEDESCRIPTORS 150
#endif
static const char *DEFAULT_ASMAP_FILENAME = "ip_asn.map";
/**
* The PID file facilities.
*/
static const char *BITCOIN_PID_FILENAME = "bitcoind.pid";
static fs::path GetPidFile() {
return AbsPathForConfigVal(
fs::path(gArgs.GetArg("-pid", BITCOIN_PID_FILENAME)));
}
NODISCARD static bool CreatePidFile() {
fsbridge::ofstream file{GetPidFile()};
if (file) {
#ifdef WIN32
tfm::format(file, "%d\n", GetCurrentProcessId());
#else
tfm::format(file, "%d\n", getpid());
#endif
return true;
} else {
return InitError(strprintf(_("Unable to create the PID file '%s': %s"),
GetPidFile().string(),
std::strerror(errno)));
}
}
//////////////////////////////////////////////////////////////////////////////
//
// Shutdown
//
//
// Thread management and startup/shutdown:
//
// The network-processing threads are all part of a thread group created by
// AppInit() or the Qt main() function.
//
// A clean exit happens when StartShutdown() or the SIGTERM signal handler sets
// fRequestShutdown, which makes main thread's WaitForShutdown() interrupts the
// thread group.
// And then, WaitForShutdown() makes all other on-going threads in the thread
// group join the main thread.
// Shutdown() is then called to clean up database connections, and stop other
// threads that should only be stopped after the main network-processing threads
// have exited.
//
// Shutdown for Qt is very similar, only it uses a QTimer to detect
// ShutdownRequested() getting set, and then does the normal Qt shutdown thing.
//
static std::unique_ptr<ECCVerifyHandle> globalVerifyHandle;
static boost::thread_group threadGroup;
void Interrupt(NodeContext &node) {
InterruptHTTPServer();
InterruptHTTPRPC();
InterruptRPC();
InterruptREST();
InterruptTorControl();
InterruptMapPort();
if (g_avalanche) {
// Avalanche needs to be stopped before we interrupt the thread group as
// the scheduler will stop working then.
g_avalanche->stopEventLoop();
}
if (node.connman) {
node.connman->Interrupt();
}
if (g_txindex) {
g_txindex->Interrupt();
}
ForEachBlockFilterIndex([](BlockFilterIndex &index) { index.Interrupt(); });
}
void Shutdown(NodeContext &node) {
LogPrintf("%s: In progress...\n", __func__);
static RecursiveMutex cs_Shutdown;
TRY_LOCK(cs_Shutdown, lockShutdown);
if (!lockShutdown) {
return;
}
/// Note: Shutdown() must be able to handle cases in which initialization
/// failed part of the way, for example if the data directory was found to
/// be locked. Be sure that anything that writes files or flushes caches
/// only does this if the respective module was initialized.
util::ThreadRename("shutoff");
g_mempool.AddTransactionsUpdated(1);
StopHTTPRPC();
StopREST();
StopRPC();
StopHTTPServer();
for (const auto &client : node.chain_clients) {
client->flush();
}
StopMapPort();
// Because avalanche and the network depend on each other, it is important
// to shut them down in this order:
// 1. Stop avalanche event loop.
// 2. Shutdown network processing.
// 3. Destroy avalanche::Processor.
// 4. Destroy CConnman
if (g_avalanche) {
g_avalanche->stopEventLoop();
}
// Because these depend on each-other, we make sure that neither can be
// using the other before destroying them.
if (node.peer_logic) {
UnregisterValidationInterface(node.peer_logic.get());
}
// Follow the lock order requirements:
// * CheckForStaleTipAndEvictPeers locks cs_main before indirectly calling
// GetExtraOutboundCount which locks cs_vNodes.
// * ProcessMessage locks cs_main and g_cs_orphans before indirectly calling
// ForEachNode which locks cs_vNodes.
// * CConnman::Stop calls DeleteNode, which calls FinalizeNode, which locks
// cs_main and calls EraseOrphansFor, which locks g_cs_orphans.
//
// Thus the implicit locking order requirement is:
// (1) cs_main, (2) g_cs_orphans, (3) cs_vNodes.
if (node.connman) {
node.connman->StopThreads();
LOCK2(::cs_main, ::g_cs_orphans);
node.connman->StopNodes();
}
if (g_txindex) {
g_txindex->Stop();
}
ForEachBlockFilterIndex([](BlockFilterIndex &index) { index.Stop(); });
StopTorControl();
// After everything has been shut down, but before things get flushed, stop
// the CScheduler/checkqueue threadGroup
if (node.scheduler) {
node.scheduler->stop();
}
threadGroup.interrupt_all();
threadGroup.join_all();
// After the threads that potentially access these pointers have been
// stopped, destruct and reset all to nullptr.
node.peer_logic.reset();
// Destroy various global instances
g_avalanche.reset();
node.connman.reset();
node.banman.reset();
g_txindex.reset();
DestroyAllBlockFilterIndexes();
if (::g_mempool.IsLoaded() &&
gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) {
DumpMempool(::g_mempool);
}
// FlushStateToDisk generates a ChainStateFlushed callback, which we should
// avoid missing
if (node.chainman) {
LOCK(cs_main);
for (CChainState *chainstate : node.chainman->GetAll()) {
if (chainstate->CanFlushToDisk()) {
chainstate->ForceFlushStateToDisk();
}
}
}
// After there are no more peers/RPC left to give us new data which may
// generate CValidationInterface callbacks, flush them...
GetMainSignals().FlushBackgroundCallbacks();
// Any future callbacks will be dropped. This should absolutely be safe - if
// missing a callback results in an unrecoverable situation, unclean
// shutdown would too. The only reason to do the above flushes is to let the
// wallet catch up with our current chain to avoid any strange pruning edge
// cases and make next startup faster by avoiding rescan.
if (node.chainman) {
LOCK(cs_main);
for (CChainState *chainstate : node.chainman->GetAll()) {
if (chainstate->CanFlushToDisk()) {
chainstate->ForceFlushStateToDisk();
chainstate->ResetCoinsViews();
}
}
pblocktree.reset();
}
for (const auto &client : node.chain_clients) {
client->stop();
}
#if ENABLE_ZMQ
if (g_zmq_notification_interface) {
UnregisterValidationInterface(g_zmq_notification_interface);
delete g_zmq_notification_interface;
g_zmq_notification_interface = nullptr;
}
#endif
try {
if (!fs::remove(GetPidFile())) {
LogPrintf("%s: Unable to remove PID file: File does not exist\n",
__func__);
}
} catch (const fs::filesystem_error &e) {
LogPrintf("%s: Unable to remove PID file: %s\n", __func__,
fsbridge::get_filesystem_error_message(e));
}
node.chain_clients.clear();
UnregisterAllValidationInterfaces();
GetMainSignals().UnregisterBackgroundSignalScheduler();
globalVerifyHandle.reset();
ECC_Stop();
node.mempool = nullptr;
node.chainman = nullptr;
node.scheduler.reset();
LogPrintf("%s: done\n", __func__);
}
/**
* Signal handlers are very limited in what they are allowed to do.
* The execution context the handler is invoked in is not guaranteed,
* so we restrict handler operations to just touching variables:
*/
#ifndef WIN32
static void HandleSIGTERM(int) {
StartShutdown();
}
static void HandleSIGHUP(int) {
LogInstance().m_reopen_file = true;
}
#else
static BOOL WINAPI consoleCtrlHandler(DWORD dwCtrlType) {
StartShutdown();
Sleep(INFINITE);
return true;
}
#endif
#ifndef WIN32
static void registerSignalHandler(int signal, void (*handler)(int)) {
struct sigaction sa;
sa.sa_handler = handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
sigaction(signal, &sa, NULL);
}
#endif
static boost::signals2::connection rpc_notify_block_change_connection;
static void OnRPCStarted() {
rpc_notify_block_change_connection = uiInterface.NotifyBlockTip_connect(
std::bind(RPCNotifyBlockChange, std::placeholders::_2));
}
static void OnRPCStopped() {
rpc_notify_block_change_connection.disconnect();
RPCNotifyBlockChange(nullptr);
g_best_block_cv.notify_all();
LogPrint(BCLog::RPC, "RPC stopped.\n");
}
void SetupServerArgs() {
SetupHelpOptions(gArgs);
// server-only for now
gArgs.AddArg("-help-debug",
"Print help message with debugging options and exit", false,
OptionsCategory::DEBUG_TEST);
const auto defaultBaseParams =
CreateBaseChainParams(CBaseChainParams::MAIN);
const auto testnetBaseParams =
CreateBaseChainParams(CBaseChainParams::TESTNET);
const auto regtestBaseParams =
CreateBaseChainParams(CBaseChainParams::REGTEST);
const auto defaultChainParams = CreateChainParams(CBaseChainParams::MAIN);
const auto testnetChainParams =
CreateChainParams(CBaseChainParams::TESTNET);
const auto regtestChainParams =
CreateChainParams(CBaseChainParams::REGTEST);
// Hidden Options
std::vector<std::string> hidden_args = {
"-dbcrashratio", "-forcecompactdb", "-parkdeepreorg",
"-automaticunparking", "-replayprotectionactivationtime",
"-enableminerfund",
// GUI args. These will be overwritten by SetupUIArgs for the GUI
"-allowselfsignedrootcertificates", "-choosedatadir", "-lang=<lang>",
"-min", "-resetguisettings", "-rootcertificates=<file>", "-splash",
"-uiplatform",
// TODO remove after the November 2020 upgrade
"-axionactivationtime"};
// Set all of the args and their help
// When adding new options to the categories, please keep and ensure
// alphabetical ordering. Do not translate _(...) -help-debug options, Many
// technical terms, and only a very small audience, so is unnecessary stress
// to translators.
gArgs.AddArg("-version", "Print version and exit", ArgsManager::ALLOW_ANY,
OptionsCategory::OPTIONS);
#if defined(HAVE_SYSTEM)
gArgs.AddArg("-alertnotify=<cmd>",
"Execute command when a relevant alert is received or we see "
"a really long fork (%s in cmd is replaced by message)",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
#endif
gArgs.AddArg(
"-assumevalid=<hex>",
strprintf(
"If this block is in the chain assume that it and its ancestors "
"are valid and potentially skip their script verification (0 to "
"verify all, default: %s, testnet: %s)",
defaultChainParams->GetConsensus().defaultAssumeValid.GetHex(),
testnetChainParams->GetConsensus().defaultAssumeValid.GetHex()),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-blocksdir=<dir>",
"Specify directory to hold blocks subdirectory for *.dat "
"files (default: <datadir>)",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
#if defined(HAVE_SYSTEM)
gArgs.AddArg("-blocknotify=<cmd>",
"Execute command when the best block changes (%s in cmd is "
"replaced by block hash)",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
#endif
gArgs.AddArg("-blockreconstructionextratxn=<n>",
strprintf("Extra transactions to keep in memory for compact "
"block reconstructions (default: %u)",
DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg(
"-blocksonly",
strprintf(
"Whether to reject transactions from network peers. Transactions "
"from the wallet, RPC and relay whitelisted inbound peers RPC are"
" not affected. (default: %u)",
DEFAULT_BLOCKSONLY),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg(
"-conf=<file>",
strprintf("Specify path to read-only configuration file. Relative "
"paths will be prefixed by datadir location. (default: %s)",
BITCOIN_CONF_FILENAME),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-datadir=<dir>", "Specify data directory",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg(
"-dbbatchsize",
strprintf("Maximum database write batch size in bytes (default: %u)",
DEFAULT_DB_BATCH_SIZE),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::OPTIONS);
gArgs.AddArg(
"-dbcache=<n>",
strprintf("Set database cache size in MiB (%d to %d, default: %d)",
MIN_DB_CACHE_MB, MAX_DB_CACHE_MB, DEFAULT_DB_CACHE_MB),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-debuglogfile=<file>",
strprintf("Specify location of debug log file. Relative paths "
"will be prefixed by a net-specific datadir "
"location. (0 to disable; default: %s)",
DEFAULT_DEBUGLOGFILE),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-feefilter",
strprintf("Tell other nodes to filter invs to us by our "
"mempool min fee (default: %d)",
DEFAULT_FEEFILTER),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::OPTIONS);
gArgs.AddArg("-finalizationdelay=<n>",
strprintf("Set the minimum amount of time to wait between a "
"block header reception and the block finalization. "
"Unit is seconds (default: %d)",
DEFAULT_MIN_FINALIZATION_DELAY),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg(
"-includeconf=<file>",
"Specify additional configuration file, relative to the -datadir path "
"(only useable from configuration file, not command line)",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-maxreorgdepth=<n>",
strprintf("Configure at what depth blocks are considered "
"final (default: %d). Use -1 to disable.",
DEFAULT_MAX_REORG_DEPTH),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-loadblock=<file>",
"Imports blocks from external file on startup",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-maxmempool=<n>",
strprintf("Keep the transaction memory pool below <n> "
"megabytes (default: %u)",
DEFAULT_MAX_MEMPOOL_SIZE),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-maxorphantx=<n>",
strprintf("Keep at most <n> unconnectable transactions in "
"memory (default: %u)",
DEFAULT_MAX_ORPHAN_TRANSACTIONS),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-mempoolexpiry=<n>",
strprintf("Do not keep transactions in the mempool longer "
"than <n> hours (default: %u)",
DEFAULT_MEMPOOL_EXPIRY),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg(
"-minimumchainwork=<hex>",
strprintf(
"Minimum work assumed to exist on a valid chain in hex "
"(default: %s, testnet: %s)",
defaultChainParams->GetConsensus().nMinimumChainWork.GetHex(),
testnetChainParams->GetConsensus().nMinimumChainWork.GetHex()),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::OPTIONS);
gArgs.AddArg(
"-par=<n>",
strprintf("Set the number of script verification threads (%u to %d, 0 "
"= auto, <0 = leave that many cores free, default: %d)",
-GetNumCores(), MAX_SCRIPTCHECK_THREADS,
DEFAULT_SCRIPTCHECK_THREADS),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-persistmempool",
strprintf("Whether to save the mempool on shutdown and load "
"on restart (default: %u)",
DEFAULT_PERSIST_MEMPOOL),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-pid=<file>",
strprintf("Specify pid file. Relative paths will be prefixed "
"by a net-specific datadir location. (default: %s)",
BITCOIN_PID_FILENAME),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg(
"-prune=<n>",
strprintf("Reduce storage requirements by enabling pruning (deleting) "
"of old blocks. This allows the pruneblockchain RPC to be "
"called to delete specific blocks, and enables automatic "
"pruning of old blocks if a target size in MiB is provided. "
"This mode is incompatible with -txindex and -rescan. "
"Warning: Reverting this setting requires re-downloading the "
"entire blockchain. (default: 0 = disable pruning blocks, 1 "
"= allow manual pruning via RPC, >=%u = automatically prune "
"block files to stay under the specified target size in MiB)",
MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-reindex-chainstate",
"Rebuild chain state from the currently indexed blocks. When "
"in pruning mode or if blocks on disk might be corrupted, use "
"full -reindex instead.",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg(
"-reindex",
"Rebuild chain state and block index from the blk*.dat files on disk",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg(
"-settings=<file>",
strprintf(
"Specify path to dynamic settings data file. Can be disabled with "
"-nosettings. File is written at runtime and not meant to be "
"edited by users (use %s instead for custom settings). Relative "
"paths will be prefixed by datadir location. (default: %s)",
BITCOIN_CONF_FILENAME, BITCOIN_SETTINGS_FILENAME),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
#ifndef WIN32
gArgs.AddArg(
"-sysperms",
"Create new files with system default permissions, instead of umask "
"077 (only effective with disabled wallet functionality)",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
#else
hidden_args.emplace_back("-sysperms");
#endif
gArgs.AddArg("-txindex",
strprintf("Maintain a full transaction index, used by the "
"getrawtransaction rpc call (default: %d)",
DEFAULT_TXINDEX),
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-blockfilterindex=<type>",
strprintf("Maintain an index of compact filters by block "
"(default: %s, values: %s).",
DEFAULT_BLOCKFILTERINDEX, ListBlockFilterTypes()) +
" If <type> is not supplied or if <type> = 1, indexes for "
"all known types are enabled.",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-usecashaddr",
"Use Cash Address for destination encoding instead of base58 "
"(activate by default on Jan, 14)",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
gArgs.AddArg("-addnode=<ip>",
"Add a node to connect to and attempt to keep the connection "
"open (see the `addnode` RPC command help for more info)",
ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY,
OptionsCategory::CONNECTION);
gArgs.AddArg("-asmap=<file>",
strprintf("Specify asn mapping used for bucketing of the "
"peers (default: %s). Relative paths will be "
"prefixed by the net-specific datadir location.",
DEFAULT_ASMAP_FILENAME),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg("-banscore=<n>",
strprintf("Threshold for disconnecting and discouraging "
"misbehaving peers (default: %u)",
DEFAULT_BANSCORE_THRESHOLD),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg("-bantime=<n>",
strprintf("Default duration (in seconds) of manually "
"configured bans (default: %u)",
DEFAULT_MISBEHAVING_BANTIME),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg("-bind=<addr>",
"Bind to given address and always listen on it. Use "
"[host]:port notation for IPv6",
ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY,
OptionsCategory::CONNECTION);
gArgs.AddArg(
"-connect=<ip>",
"Connect only to the specified node(s); -connect=0 disables automatic "
"connections (the rules for this peer are the same as for -addnode)",
ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY,
OptionsCategory::CONNECTION);
gArgs.AddArg("-discover",
"Discover own IP addresses (default: 1 when listening and no "
"-externalip or -proxy)",
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg("-dns",
strprintf("Allow DNS lookups for -addnode, -seednode and "
"-connect (default: %d)",
DEFAULT_NAME_LOOKUP),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg("-dnsseed",
"Query for peer addresses via DNS lookup, if low on addresses "
"(default: 1 unless -connect used)",
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg("-externalip=<ip>", "Specify your own public address",
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg(
"-forcednsseed",
strprintf(
"Always query for peer addresses via DNS lookup (default: %d)",
DEFAULT_FORCEDNSSEED),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg(
"-listen",
"Accept connections from outside (default: 1 if no -proxy or -connect)",
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg(
"-listenonion",
strprintf("Automatically create Tor hidden service (default: %d)",
DEFAULT_LISTEN_ONION),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg(
"-maxconnections=<n>",
strprintf("Maintain at most <n> connections to peers (default: %u)",
DEFAULT_MAX_PEER_CONNECTIONS),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg("-maxreceivebuffer=<n>",
strprintf("Maximum per-connection receive buffer, <n>*1000 "
"bytes (default: %u)",
DEFAULT_MAXRECEIVEBUFFER),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg(
"-maxsendbuffer=<n>",
strprintf(
"Maximum per-connection send buffer, <n>*1000 bytes (default: %u)",
DEFAULT_MAXSENDBUFFER),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg(
"-maxtimeadjustment",
strprintf("Maximum allowed median peer time offset adjustment. Local "
"perspective of time may be influenced by peers forward or "
"backward by this amount. (default: %u seconds)",
DEFAULT_MAX_TIME_ADJUSTMENT),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg("-onion=<ip:port>",
strprintf("Use separate SOCKS5 proxy to reach peers via Tor "
"hidden services (default: %s)",
"-proxy"),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg(
"-onlynet=<net>",
"Make outgoing connections only through network <net> (ipv4, ipv6 or "
"onion). Incoming connections are not affected by this option. This "
"option can be specified multiple times to allow multiple networks.",
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg("-peerbloomfilters",
strprintf("Support filtering of blocks and transaction with "
"bloom filters (default: %d)",
DEFAULT_PEERBLOOMFILTERS),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg(
"-peerblockfilters",
strprintf(
"Serve compact block filters to peers per BIP 157 (default: %u)",
DEFAULT_PEERBLOCKFILTERS),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg("-permitbaremultisig",
strprintf("Relay non-P2SH multisig (default: %d)",
DEFAULT_PERMIT_BAREMULTISIG),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg("-port=<port>",
strprintf("Listen for connections on <port> (default: %u, "
"testnet: %u, regtest: %u)",
defaultChainParams->GetDefaultPort(),
testnetChainParams->GetDefaultPort(),
regtestChainParams->GetDefaultPort()),
ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY,
OptionsCategory::CONNECTION);
gArgs.AddArg("-proxy=<ip:port>", "Connect through SOCKS5 proxy",
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg("-proxyrandomize",
strprintf("Randomize credentials for every proxy connection. "
"This enables Tor stream isolation (default: %d)",
DEFAULT_PROXYRANDOMIZE),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg("-seednode=<ip>",
"Connect to a node to retrieve peer addresses, and disconnect",
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg("-timeout=<n>",
strprintf("Specify connection timeout in milliseconds "
"(minimum: 1, default: %d)",
DEFAULT_CONNECT_TIMEOUT),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg(
"-peertimeout=<n>",
strprintf("Specify p2p connection timeout in seconds. This option "
"determines the amount of time a peer may be inactive before "
"the connection to it is dropped. (minimum: 1, default: %d)",
DEFAULT_PEER_CONNECT_TIMEOUT),
true, OptionsCategory::CONNECTION);
gArgs.AddArg(
"-torcontrol=<ip>:<port>",
strprintf(
"Tor control port to use if onion listening enabled (default: %s)",
DEFAULT_TOR_CONTROL),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg("-torpassword=<pass>",
"Tor control port password (default: empty)",
ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE,
OptionsCategory::CONNECTION);
#ifdef USE_UPNP
#if USE_UPNP
gArgs.AddArg("-upnp",
"Use UPnP to map the listening port (default: 1 when "
"listening and no -proxy)",
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
#else
gArgs.AddArg(
"-upnp",
strprintf("Use UPnP to map the listening port (default: %u)", 0),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
#endif
#else
hidden_args.emplace_back("-upnp");
#endif
gArgs.AddArg("-whitebind=<[permissions@]addr>",
"Bind to given address and whitelist peers connecting to it."
" Use [host]:port notation for IPv6. Allowed permissions are "
"bloomfilter (allow requesting BIP37 filtered blocks and "
"transactions), noban (do not ban for misbehavior), "
"forcerelay (relay even non-standard transactions), "
"relay (relay even in -blocksonly mode), "
"and mempool (allow requesting BIP35 mempool contents). "
"Specify multiple permissions separated by commas (default: "
"noban,mempool,relay). Can be specified multiple times.",
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg("-whitelist=<[permissions@]IP address or network>",
"Whitelist peers connecting from the given IP address "
"(e.g. 1.2.3.4) or CIDR notated network(e.g. 1.2.3.0/24). "
"Uses same permissions as -whitebind. Can be specified "
"multiple times.",
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
gArgs.AddArg(
"-maxuploadtarget=<n>",
strprintf("Tries to keep outbound traffic under the given target (in "
"MiB per 24h), 0 = no limit (default: %d)",
DEFAULT_MAX_UPLOAD_TARGET),
ArgsManager::ALLOW_ANY, OptionsCategory::CONNECTION);
g_wallet_init_interface.AddWalletOptions();
#if ENABLE_ZMQ
gArgs.AddArg("-zmqpubhashblock=<address>",
"Enable publish hash block in <address>",
ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ);
gArgs.AddArg("-zmqpubhashtx=<address>",
"Enable publish hash transaction in <address>",
ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ);
gArgs.AddArg("-zmqpubrawblock=<address>",
"Enable publish raw block in <address>",
ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ);
gArgs.AddArg("-zmqpubrawtx=<address>",
"Enable publish raw transaction in <address>",
ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ);
gArgs.AddArg("-zmqpubhashblockhwm=<n>",
strprintf("Set publish hash block outbound message high water "
"mark (default: %d)",
CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM),
false, OptionsCategory::ZMQ);
gArgs.AddArg("-zmqpubhashtxhwm=<n>",
strprintf("Set publish hash transaction outbound message high "
"water mark (default: %d)",
CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM),
false, OptionsCategory::ZMQ);
gArgs.AddArg("-zmqpubrawblockhwm=<n>",
strprintf("Set publish raw block outbound message high water "
"mark (default: %d)",
CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM),
false, OptionsCategory::ZMQ);
gArgs.AddArg("-zmqpubrawtxhwm=<n>",
strprintf("Set publish raw transaction outbound message high "
"water mark (default: %d)",
CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM),
false, OptionsCategory::ZMQ);
#else
hidden_args.emplace_back("-zmqpubhashblock=<address>");
hidden_args.emplace_back("-zmqpubhashtx=<address>");
hidden_args.emplace_back("-zmqpubrawblock=<address>");
hidden_args.emplace_back("-zmqpubrawtx=<address>");
hidden_args.emplace_back("-zmqpubhashblockhwm=<n>");
hidden_args.emplace_back("-zmqpubhashtxhwm=<n>");
hidden_args.emplace_back("-zmqpubrawblockhwm=<n>");
hidden_args.emplace_back("-zmqpubrawtxhwm=<n>");
#endif
gArgs.AddArg(
"-checkblocks=<n>",
strprintf("How many blocks to check at startup (default: %u, 0 = all)",
DEFAULT_CHECKBLOCKS),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
gArgs.AddArg(
"-checklevel=<n>",
strprintf("How thorough the block verification of "
"-checkblocks is: "
"level 0 reads the blocks from disk, "
"level 1 verifies block validity, "
"level 2 verifies undo data, "
"level 3 checks disconnection of tip blocks, "
"and level 4 tries to reconnect the blocks. "
"Each level includes the checks of the previous levels "
"(0-4, default: %u)",
DEFAULT_CHECKLEVEL),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
gArgs.AddArg("-checkblockindex",
strprintf("Do a consistency check for the block tree, "
"chainstate, and other validation data structures "
"occasionally. (default: %u, regtest: %u)",
defaultChainParams->DefaultConsistencyChecks(),
regtestChainParams->DefaultConsistencyChecks()),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
gArgs.AddArg(
"-checkmempool=<n>",
strprintf(
"Run checks every <n> transactions (default: %u, regtest: %u)",
defaultChainParams->DefaultConsistencyChecks(),
regtestChainParams->DefaultConsistencyChecks()),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
gArgs.AddArg("-checkpoints",
strprintf("Only accept block chain matching built-in "
"checkpoints (default: %d)",
DEFAULT_CHECKPOINTS_ENABLED),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
gArgs.AddArg("-deprecatedrpc=<method>",
"Allows deprecated RPC method(s) to be used",
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
gArgs.AddArg("-dropmessagestest=<n>",
"Randomly drop 1 of every <n> network messages",
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
gArgs.AddArg(
"-stopafterblockimport",
strprintf("Stop running after importing blocks from disk (default: %d)",
DEFAULT_STOPAFTERBLOCKIMPORT),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
gArgs.AddArg("-stopatheight",
strprintf("Stop running after reaching the given height in "
"the main chain (default: %u)",
DEFAULT_STOPATHEIGHT),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
gArgs.AddArg("-limitancestorcount=<n>",
strprintf("Do not accept transactions if number of in-mempool "
"ancestors is <n> or more (default: %u)",
DEFAULT_ANCESTOR_LIMIT),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
gArgs.AddArg(
"-limitancestorsize=<n>",
strprintf("Do not accept transactions whose size with all in-mempool "
"ancestors exceeds <n> kilobytes (default: %u)",
DEFAULT_ANCESTOR_SIZE_LIMIT),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
gArgs.AddArg(
"-limitdescendantcount=<n>",
strprintf("Do not accept transactions if any ancestor would have <n> "
"or more in-mempool descendants (default: %u)",
DEFAULT_DESCENDANT_LIMIT),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
gArgs.AddArg(
"-limitdescendantsize=<n>",
strprintf("Do not accept transactions if any ancestor would have more "
"than <n> kilobytes of in-mempool descendants (default: %u).",
DEFAULT_DESCENDANT_SIZE_LIMIT),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
gArgs.AddArg("-addrmantest", "Allows to test address relay on localhost",
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
gArgs.AddArg("-debug=<category>",
strprintf("Output debugging information (default: %u, "
"supplying <category> is optional)",
0) +
". " +
"If <category> is not supplied or if <category> = 1, "
"output all debugging information."
"<category> can be: " +
ListLogCategories() + ".",
ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
gArgs.AddArg(
"-debugexclude=<category>",
strprintf("Exclude debugging information for a category. Can be used "
"in conjunction with -debug=1 to output debug logs for all "
"categories except one or more specified categories."),
ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
gArgs.AddArg("-logips",
strprintf("Include IP addresses in debug output (default: %d)",
DEFAULT_LOGIPS),
ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
gArgs.AddArg("-logtimestamps",
strprintf("Prepend debug output with timestamp (default: %d)",
DEFAULT_LOGTIMESTAMPS),
ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
gArgs.AddArg(
"-logthreadnames",
strprintf(
"Prepend debug output with name of the originating thread (only "
"available on platforms supporting thread_local) (default: %u)",
DEFAULT_LOGTHREADNAMES),
ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
gArgs.AddArg(
"-logtimemicros",
strprintf("Add microsecond precision to debug timestamps (default: %d)",
DEFAULT_LOGTIMEMICROS),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
gArgs.AddArg("-mocktime=<n>",
"Replace actual time with " + UNIX_EPOCH_TIME +
" (default: 0)",
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
gArgs.AddArg(
"-maxsigcachesize=<n>",
strprintf("Limit size of signature cache to <n> MiB (default: %u)",
DEFAULT_MAX_SIG_CACHE_SIZE),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
gArgs.AddArg(
"-maxscriptcachesize=<n>",
strprintf("Limit size of script cache to <n> MiB (default: %u)",
DEFAULT_MAX_SCRIPT_CACHE_SIZE),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
gArgs.AddArg("-maxtipage=<n>",
strprintf("Maximum tip age in seconds to consider node in "
"initial block download (default: %u)",
DEFAULT_MAX_TIP_AGE),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
gArgs.AddArg(
"-printtoconsole",
"Send trace/debug info to console instead of debug.log file (default: "
"1 when no -daemon. To disable logging to file, set debuglogfile=0)",
ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
gArgs.AddArg("-printpriority",
strprintf("Log transaction priority and fee per kB when "
"mining blocks (default: %d)",
DEFAULT_PRINTPRIORITY),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::DEBUG_TEST);
gArgs.AddArg(
"-shrinkdebugfile",
"Shrink debug.log file on client startup (default: 1 when no -debug)",
ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
gArgs.AddArg("-uacomment=<cmt>", "Append comment to the user agent string",
ArgsManager::ALLOW_ANY, OptionsCategory::DEBUG_TEST);
SetupChainParamsBaseOptions();
gArgs.AddArg(
"-acceptnonstdtxn",
strprintf(
"Relay and mine \"non-standard\" transactions (%sdefault: %u)",
"testnet/regtest only; ", defaultChainParams->RequireStandard()),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::NODE_RELAY);
gArgs.AddArg("-excessiveblocksize=<n>",
strprintf("Do not accept blocks larger than this limit, in "
"bytes (default: %d)",
DEFAULT_MAX_BLOCK_SIZE),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::NODE_RELAY);
gArgs.AddArg(
"-dustrelayfee=<amt>",
strprintf("Fee rate (in %s/kB) used to define dust, the value of an "
"output such that it will cost about 1/3 of its value in "
"fees at this fee rate to spend it. (default: %s)",
CURRENCY_UNIT, FormatMoney(DUST_RELAY_TX_FEE)),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::NODE_RELAY);
gArgs.AddArg("-bytespersigop",
strprintf("Equivalent bytes per sigop in transactions for "
"relay and mining (default: %u)",
DEFAULT_BYTES_PER_SIGOP),
ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
gArgs.AddArg(
"-datacarrier",
strprintf("Relay and mine data carrier transactions (default: %d)",
DEFAULT_ACCEPT_DATACARRIER),
ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
gArgs.AddArg("-datacarriersize",
strprintf("Maximum size of data in data carrier transactions "
"we relay and mine (default: %u)",
MAX_OP_RETURN_RELAY),
ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
gArgs.AddArg(
"-minrelaytxfee=<amt>",
strprintf("Fees (in %s/kB) smaller than this are rejected for "
"relaying, mining and transaction creation (default: %s)",
CURRENCY_UNIT, FormatMoney(DEFAULT_MIN_RELAY_TX_FEE_PER_KB)),
ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
gArgs.AddArg(
"-whitelistrelay",
strprintf("Add 'relay' permission to whitelisted inbound peers "
"with default permissions. This will accept relayed "
"transactions even when not relaying transactions "
"(default: %d)",
DEFAULT_WHITELISTRELAY),
ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
gArgs.AddArg(
"-whitelistforcerelay",
strprintf("Add 'forcerelay' permission to whitelisted inbound peers"
" with default permissions. This will relay transactions "
"even if the transactions were already in the mempool or "
"violate local relay policy (default: %d)",
DEFAULT_WHITELISTFORCERELAY),
ArgsManager::ALLOW_ANY, OptionsCategory::NODE_RELAY);
// Not sure this really belongs here, but it will do for now.
// FIXME: This doesn't work anyways.
gArgs.AddArg("-excessutxocharge=<amt>",
strprintf("Fees (in %s/kB) to charge per utxo created for "
"relaying, and mining (default: %s)",
CURRENCY_UNIT, FormatMoney(DEFAULT_UTXO_FEE)),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::NODE_RELAY);
gArgs.AddArg("-blockmaxsize=<n>",
strprintf("Set maximum block size in bytes (default: %d)",
DEFAULT_MAX_GENERATED_BLOCK_SIZE),
ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION);
gArgs.AddArg("-blockmintxfee=<amt>",
strprintf("Set lowest fee rate (in %s/kB) for transactions to "
"be included in block creation. (default: %s)",
CURRENCY_UNIT,
FormatMoney(DEFAULT_BLOCK_MIN_TX_FEE_PER_KB)),
ArgsManager::ALLOW_ANY, OptionsCategory::BLOCK_CREATION);
gArgs.AddArg("-blockversion=<n>",
"Override block version to test forking scenarios",
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::BLOCK_CREATION);
gArgs.AddArg("-server", "Accept command line and JSON-RPC commands",
ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
gArgs.AddArg("-rest",
strprintf("Accept public REST requests (default: %d)",
DEFAULT_REST_ENABLE),
ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
gArgs.AddArg(
"-rpcbind=<addr>[:port]",
"Bind to given address to listen for JSON-RPC connections. Do not "
"expose the RPC server to untrusted networks such as the public "
"internet! This option is ignored unless -rpcallowip is also passed. "
"Port is optional and overrides -rpcport. Use [host]:port notation "
"for IPv6. This option can be specified multiple times (default: "
"127.0.0.1 and ::1 i.e., localhost)",
ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY |
ArgsManager::SENSITIVE,
OptionsCategory::RPC);
gArgs.AddArg("-rpccookiefile=<loc>",
"Location of the auth cookie. Relative paths will be prefixed "
"by a net-specific datadir location. (default: data dir)",
ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
gArgs.AddArg("-rpcuser=<user>", "Username for JSON-RPC connections",
ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE,
OptionsCategory::RPC);
gArgs.AddArg("-rpcpassword=<pw>", "Password for JSON-RPC connections",
ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE,
OptionsCategory::RPC);
gArgs.AddArg(
"-rpcwhitelist=<whitelist>",
"Set a whitelist to filter incoming RPC calls for a specific user. The "
"field <whitelist> comes in the format: <USERNAME>:<rpc 1>,<rpc "
"2>,...,<rpc n>. If multiple whitelists are set for a given user, they "
"are set-intersected. See -rpcwhitelistdefault documentation for "
"information on default whitelist behavior.",
ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
gArgs.AddArg(
"-rpcwhitelistdefault",
"Sets default behavior for rpc whitelisting. Unless "
"rpcwhitelistdefault is set to 0, if any -rpcwhitelist is set, the rpc "
"server acts as if all rpc users are subject to "
"empty-unless-otherwise-specified whitelists. If rpcwhitelistdefault "
"is set to 1 and no -rpcwhitelist is set, rpc server acts as if all "
"rpc users are subject to empty whitelists.",
ArgsManager::ALLOW_BOOL, OptionsCategory::RPC);
gArgs.AddArg(
"-rpcauth=<userpw>",
"Username and HMAC-SHA-256 hashed password for JSON-RPC connections. "
"The field <userpw> comes in the format: <USERNAME>:<SALT>$<HASH>. A "
"canonical python script is included in share/rpcauth. The client then "
"connects normally using the rpcuser=<USERNAME>/rpcpassword=<PASSWORD> "
"pair of arguments. This option can be specified multiple times",
ArgsManager::ALLOW_ANY | ArgsManager::SENSITIVE, OptionsCategory::RPC);
gArgs.AddArg("-rpcport=<port>",
strprintf("Listen for JSON-RPC connections on <port> "
"(default: %u, testnet: %u, regtest: %u)",
defaultBaseParams->RPCPort(),
testnetBaseParams->RPCPort(),
regtestBaseParams->RPCPort()),
ArgsManager::ALLOW_ANY | ArgsManager::NETWORK_ONLY,
OptionsCategory::RPC);
gArgs.AddArg("-rpcallowip=<ip>",
"Allow JSON-RPC connections from specified source. Valid for "
"<ip> are a single IP (e.g. 1.2.3.4), a network/netmask (e.g. "
"1.2.3.4/255.255.255.0) or a network/CIDR (e.g. 1.2.3.4/24). "
"This option can be specified multiple times",
ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
gArgs.AddArg(
"-rpcthreads=<n>",
strprintf(
"Set the number of threads to service RPC calls (default: %d)",
DEFAULT_HTTP_THREADS),
ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
gArgs.AddArg(
"-rpccorsdomain=value",
"Domain from which to accept cross origin requests (browser enforced)",
ArgsManager::ALLOW_ANY, OptionsCategory::RPC);
gArgs.AddArg("-rpcworkqueue=<n>",
strprintf("Set the depth of the work queue to service RPC "
"calls (default: %d)",
DEFAULT_HTTP_WORKQUEUE),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::RPC);
gArgs.AddArg("-rpcservertimeout=<n>",
strprintf("Timeout during HTTP requests (default: %d)",
DEFAULT_HTTP_SERVER_TIMEOUT),
ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY,
OptionsCategory::RPC);
#if HAVE_DECL_DAEMON
gArgs.AddArg("-daemon",
"Run in the background as a daemon and accept commands",
ArgsManager::ALLOW_ANY, OptionsCategory::OPTIONS);
#else
hidden_args.emplace_back("-daemon");
#endif
// Avalanche options.
gArgs.AddArg(
"-enableavalanche",
strprintf("Enable avalanche (default: %u)", AVALANCHE_DEFAULT_ENABLED),
ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE);
gArgs.AddArg(
"-avacooldown",
strprintf("Mandatory cooldown between two avapoll (default: %u)",
AVALANCHE_DEFAULT_COOLDOWN),
ArgsManager::ALLOW_ANY, OptionsCategory::AVALANCHE);
// Add the hidden options
gArgs.AddHiddenArgs(hidden_args);
}
std::string LicenseInfo() {
const std::string URL_SOURCE_CODE =
"<https://github.com/Bitcoin-ABC/bitcoin-abc>";
const std::string URL_WEBSITE = "<https://www.bitcoinabc.org>";
return CopyrightHolders(strprintf(_("Copyright (C) %i-%i").translated, 2009,
COPYRIGHT_YEAR) +
" ") +
"\n" + "\n" +
strprintf(_("Please contribute if you find %s useful. "
"Visit %s for further information about the software.")
.translated,
PACKAGE_NAME, URL_WEBSITE) +
"\n" +
strprintf(_("The source code is available from %s.").translated,
URL_SOURCE_CODE) +
"\n" + "\n" + _("This is experimental software.").translated + "\n" +
strprintf(_("Distributed under the MIT software license, see the "
"accompanying file %s or %s")
.translated,
"COPYING", "<https://opensource.org/licenses/MIT>") +
"\n" + "\n" +
strprintf(_("This product includes software developed by the "
"OpenSSL Project for use in the OpenSSL Toolkit %s and "
"cryptographic software written by Eric Young and UPnP "
"software written by Thomas Bernard.")
.translated,
"<https://www.openssl.org>") +
"\n";
}
#if defined(HAVE_SYSTEM)
static void BlockNotifyCallback(SynchronizationState sync_state,
const CBlockIndex *pBlockIndex) {
if (sync_state != SynchronizationState::POST_INIT || !pBlockIndex) {
return;
}
std::string strCmd = gArgs.GetArg("-blocknotify", "");
if (!strCmd.empty()) {
boost::replace_all(strCmd, "%s", pBlockIndex->GetBlockHash().GetHex());
std::thread t(runCommand, strCmd);
// thread runs free
t.detach();
}
}
#endif
static bool fHaveGenesis = false;
static Mutex g_genesis_wait_mutex;
static std::condition_variable g_genesis_wait_cv;
static void BlockNotifyGenesisWait(const CBlockIndex *pBlockIndex) {
if (pBlockIndex != nullptr) {
{
LOCK(g_genesis_wait_mutex);
fHaveGenesis = true;
}
g_genesis_wait_cv.notify_all();
}
}
struct CImportingNow {
CImportingNow() {
assert(fImporting == false);
fImporting = true;
}
~CImportingNow() {
assert(fImporting == true);
fImporting = false;
}
};
// If we're using -prune with -reindex, then delete block files that will be
// ignored by the reindex. Since reindexing works by starting at block file 0
// and looping until a blockfile is missing, do the same here to delete any
// later block files after a gap. Also delete all rev files since they'll be
// rewritten by the reindex anyway. This ensures that vinfoBlockFile is in sync
// with what's actually on disk by the time we start downloading, so that
// pruning works correctly.
static void CleanupBlockRevFiles() {
std::map<std::string, fs::path> mapBlockFiles;
// Glob all blk?????.dat and rev?????.dat files from the blocks directory.
// Remove the rev files immediately and insert the blk file paths into an
// ordered map keyed by block file index.
LogPrintf("Removing unusable blk?????.dat and rev?????.dat files for "
"-reindex with -prune\n");
const auto directoryIterator = fs::directory_iterator{GetBlocksDir()};
for (const auto &file : directoryIterator) {
const auto fileName = file.path().filename().string();
if (fs::is_regular_file(file) && fileName.length() == 12 &&
fileName.substr(8, 4) == ".dat") {
if (fileName.substr(0, 3) == "blk") {
mapBlockFiles[fileName.substr(3, 5)] = file.path();
} else if (fileName.substr(0, 3) == "rev") {
remove(file.path());
}
}
}
// Remove all block files that aren't part of a contiguous set starting at
// zero by walking the ordered map (keys are block file indices) by keeping
// a separate counter. Once we hit a gap (or if 0 doesn't exist) start
// removing block files.
int contiguousCounter = 0;
for (const auto &item : mapBlockFiles) {
if (atoi(item.first) == contiguousCounter) {
contiguousCounter++;
continue;
}
remove(item.second);
}
}
static void ThreadImport(const Config &config, ChainstateManager &chainman,
std::vector<fs::path> vImportFiles) {
util::ThreadRename("loadblk");
ScheduleBatchPriority();
{
const CChainParams &chainParams = config.GetChainParams();
CImportingNow imp;
// -reindex
if (fReindex) {
int nFile = 0;
while (true) {
FlatFilePos pos(nFile, 0);
if (!fs::exists(GetBlockPosFilename(pos))) {
// No block files left to reindex
break;
}
FILE *file = OpenBlockFile(pos, true);
if (!file) {
// This error is logged in OpenBlockFile
break;
}
LogPrintf("Reindexing block file blk%05u.dat...\n",
(unsigned int)nFile);
LoadExternalBlockFile(config, file, &pos);
nFile++;
}
pblocktree->WriteReindexing(false);
fReindex = false;
LogPrintf("Reindexing finished\n");
// To avoid ending up in a situation without genesis block, re-try
// initializing (no-op if reindexing worked):
LoadGenesisBlock(chainParams);
}
// -loadblock=
for (const fs::path &path : vImportFiles) {
FILE *file = fsbridge::fopen(path, "rb");
if (file) {
LogPrintf("Importing blocks file %s...\n", path.string());
LoadExternalBlockFile(config, file);
} else {
LogPrintf("Warning: Could not open blocks file %s\n",
path.string());
}
}
// Reconsider blocks we know are valid. They may have been marked
// invalid by, for instance, running an outdated version of the node
// software.
const MapCheckpoints &checkpoints =
chainParams.Checkpoints().mapCheckpoints;
for (const MapCheckpoints::value_type &i : checkpoints) {
const BlockHash &hash = i.second;
LOCK(cs_main);
CBlockIndex *pblockindex = LookupBlockIndex(hash);
if (pblockindex && !pblockindex->nStatus.isValid()) {
LogPrintf("Reconsidering checkpointed block %s ...\n",
hash.GetHex());
ResetBlockFailureFlags(pblockindex);
}
}
// scan for better chains in the block chain database, that are not yet
// connected in the active best chain
// We can't hold cs_main during ActivateBestChain even though we're
// accessing the chainman unique_ptrs since ABC requires us not to be
// holding cs_main, so retrieve the relevant pointers before the ABC
// call.
for (CChainState *chainstate :
WITH_LOCK(::cs_main, return chainman.GetAll())) {
BlockValidationState state;
if (!chainstate->ActivateBestChain(config, state, nullptr)) {
LogPrintf("Failed to connect best block (%s)\n",
state.ToString());
StartShutdown();
return;
}
}
if (gArgs.GetBoolArg("-stopafterblockimport",
DEFAULT_STOPAFTERBLOCKIMPORT)) {
LogPrintf("Stopping after block import\n");
StartShutdown();
return;
}
} // End scope of CImportingNow
if (gArgs.GetArg("-persistmempool", DEFAULT_PERSIST_MEMPOOL)) {
LoadMempool(config, ::g_mempool);
}
::g_mempool.SetIsLoaded(!ShutdownRequested());
}
/** Sanity checks
* Ensure that Bitcoin is running in a usable environment with all
* necessary library support.
*/
static bool InitSanityCheck() {
if (!ECC_InitSanityCheck()) {
return InitError(Untranslated(
"Elliptic curve cryptography sanity check failure. Aborting."));
}
if (!glibcxx_sanity_test()) {
return false;
}
if (!Random_SanityCheck()) {
return InitError(Untranslated(
"OS cryptographic RNG sanity check failure. Aborting."));
}
return true;
}
static bool AppInitServers(Config &config,
HTTPRPCRequestProcessor &httpRPCRequestProcessor) {
RPCServerSignals::OnStarted(&OnRPCStarted);
RPCServerSignals::OnStopped(&OnRPCStopped);
if (!InitHTTPServer(config)) {
return false;
}
StartRPC();
if (!StartHTTPRPC(httpRPCRequestProcessor)) {
return false;
}
if (gArgs.GetBoolArg("-rest", DEFAULT_REST_ENABLE)) {
StartREST(httpRPCRequestProcessor.context);
}
StartHTTPServer();
return true;
}
// Parameter interaction based on rules
void InitParameterInteraction() {
// when specifying an explicit binding address, you want to listen on it
// even when -connect or -proxy is specified.
if (gArgs.IsArgSet("-bind")) {
if (gArgs.SoftSetBoolArg("-listen", true)) {
LogPrintf(
"%s: parameter interaction: -bind set -> setting -listen=1\n",
__func__);
}
}
if (gArgs.IsArgSet("-whitebind")) {
if (gArgs.SoftSetBoolArg("-listen", true)) {
LogPrintf("%s: parameter interaction: -whitebind set -> setting "
"-listen=1\n",
__func__);
}
}
if (gArgs.IsArgSet("-connect")) {
// when only connecting to trusted nodes, do not seed via DNS, or listen
// by default.
if (gArgs.SoftSetBoolArg("-dnsseed", false)) {
LogPrintf("%s: parameter interaction: -connect set -> setting "
"-dnsseed=0\n",
__func__);
}
if (gArgs.SoftSetBoolArg("-listen", false)) {
LogPrintf("%s: parameter interaction: -connect set -> setting "
"-listen=0\n",
__func__);
}
}
if (gArgs.IsArgSet("-proxy")) {
// to protect privacy, do not listen by default if a default proxy
// server is specified.
if (gArgs.SoftSetBoolArg("-listen", false)) {
LogPrintf(
"%s: parameter interaction: -proxy set -> setting -listen=0\n",
__func__);
}
// to protect privacy, do not use UPNP when a proxy is set. The user may
// still specify -listen=1 to listen locally, so don't rely on this
// happening through -listen below.
if (gArgs.SoftSetBoolArg("-upnp", false)) {
LogPrintf(
"%s: parameter interaction: -proxy set -> setting -upnp=0\n",
__func__);
}
// to protect privacy, do not discover addresses by default
if (gArgs.SoftSetBoolArg("-discover", false)) {
LogPrintf("%s: parameter interaction: -proxy set -> setting "
"-discover=0\n",
__func__);
}
}
if (!gArgs.GetBoolArg("-listen", DEFAULT_LISTEN)) {
// do not map ports or try to retrieve public IP when not listening
// (pointless)
if (gArgs.SoftSetBoolArg("-upnp", false)) {
LogPrintf(
"%s: parameter interaction: -listen=0 -> setting -upnp=0\n",
__func__);
}
if (gArgs.SoftSetBoolArg("-discover", false)) {
LogPrintf(
"%s: parameter interaction: -listen=0 -> setting -discover=0\n",
__func__);
}
if (gArgs.SoftSetBoolArg("-listenonion", false)) {
LogPrintf("%s: parameter interaction: -listen=0 -> setting "
"-listenonion=0\n",
__func__);
}
}
if (gArgs.IsArgSet("-externalip")) {
// if an explicit public IP is specified, do not try to find others
if (gArgs.SoftSetBoolArg("-discover", false)) {
LogPrintf("%s: parameter interaction: -externalip set -> setting "
"-discover=0\n",
__func__);
}
}
// disable whitelistrelay in blocksonly mode
if (gArgs.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY)) {
if (gArgs.SoftSetBoolArg("-whitelistrelay", false)) {
LogPrintf("%s: parameter interaction: -blocksonly=1 -> setting "
"-whitelistrelay=0\n",
__func__);
}
}
// Forcing relay from whitelisted hosts implies we will accept relays from
// them in the first place.
if (gArgs.GetBoolArg("-whitelistforcerelay", DEFAULT_WHITELISTFORCERELAY)) {
if (gArgs.SoftSetBoolArg("-whitelistrelay", true)) {
LogPrintf("%s: parameter interaction: -whitelistforcerelay=1 -> "
"setting -whitelistrelay=1\n",
__func__);
}
}
}
/**
* Initialize global loggers.
*
* Note that this is called very early in the process lifetime, so you should be
* careful about what global state you rely on here.
*/
void InitLogging() {
LogInstance().m_print_to_file = !gArgs.IsArgNegated("-debuglogfile");
LogInstance().m_file_path = AbsPathForConfigVal(
gArgs.GetArg("-debuglogfile", DEFAULT_DEBUGLOGFILE));
LogInstance().m_print_to_console = gArgs.GetBoolArg(
"-printtoconsole", !gArgs.GetBoolArg("-daemon", false));
LogInstance().m_log_timestamps =
gArgs.GetBoolArg("-logtimestamps", DEFAULT_LOGTIMESTAMPS);
LogInstance().m_log_time_micros =
gArgs.GetBoolArg("-logtimemicros", DEFAULT_LOGTIMEMICROS);
LogInstance().m_log_threadnames =
gArgs.GetBoolArg("-logthreadnames", DEFAULT_LOGTHREADNAMES);
fLogIPs = gArgs.GetBoolArg("-logips", DEFAULT_LOGIPS);
std::string version_string = FormatFullVersion();
#ifdef DEBUG
version_string += " (debug build)";
#else
version_string += " (release build)";
#endif
LogPrintf("%s version %s\n", CLIENT_NAME, version_string);
}
namespace { // Variables internal to initialization process only
int nMaxConnections;
int nUserMaxConnections;
int nFD;
ServiceFlags nLocalServices = ServiceFlags(NODE_NETWORK | NODE_NETWORK_LIMITED);
int64_t peer_connect_timeout;
std::set<BlockFilterType> g_enabled_filter_types;
} // namespace
[[noreturn]] static void new_handler_terminate() {
// Rather than throwing std::bad-alloc if allocation fails, terminate
// immediately to (try to) avoid chain corruption. Since LogPrintf may
// itself allocate memory, set the handler directly to terminate first.
std::set_new_handler(std::terminate);
LogPrintf("Error: Out of memory. Terminating.\n");
// The log was successful, terminate now.
std::terminate();
};
bool AppInitBasicSetup() {
// Step 1: setup
#ifdef _MSC_VER
// Turn off Microsoft heap dump noise
_CrtSetReportMode(_CRT_WARN, _CRTDBG_MODE_FILE);
_CrtSetReportFile(_CRT_WARN, CreateFileA("NUL", GENERIC_WRITE, 0, nullptr,
OPEN_EXISTING, 0, 0));
// Disable confusing "helpful" text message on abort, Ctrl-C
_set_abort_behavior(0, _WRITE_ABORT_MSG | _CALL_REPORTFAULT);
#endif
#ifdef WIN32
// Enable Data Execution Prevention (DEP)
SetProcessDEPPolicy(PROCESS_DEP_ENABLE);
#endif
if (!SetupNetworking()) {
return InitError(Untranslated("Initializing networking failed"));
}
#ifndef WIN32
if (!gArgs.GetBoolArg("-sysperms", false)) {
umask(077);
}
// Clean shutdown on SIGTERM
registerSignalHandler(SIGTERM, HandleSIGTERM);
registerSignalHandler(SIGINT, HandleSIGTERM);
// Reopen debug.log on SIGHUP
registerSignalHandler(SIGHUP, HandleSIGHUP);
// Ignore SIGPIPE, otherwise it will bring the daemon down if the client
// closes unexpectedly
signal(SIGPIPE, SIG_IGN);
#else
SetConsoleCtrlHandler(consoleCtrlHandler, true);
#endif
std::set_new_handler(new_handler_terminate);
return true;
}
bool AppInitParameterInteraction(Config &config) {
const CChainParams &chainparams = config.GetChainParams();
// Step 2: parameter interactions
// also see: InitParameterInteraction()
// Warn if network-specific options (-addnode, -connect, etc) are
// specified in default section of config file, but not overridden
// on the command line or in this network's section of the config file.
std::string network = gArgs.GetChainName();
for (const auto &arg : gArgs.GetUnsuitableSectionOnlyArgs()) {
return InitError(strprintf(_("Config setting for %s only applied on %s "
"network when in [%s] section."),
arg, network, network));
}
// Warn if unrecognized section name are present in the config file.
for (const auto &section : gArgs.GetUnrecognizedSections()) {
InitWarning(strprintf(
"%s:%i " + _("Section [%s] is not recognized.").translated,
section.m_file, section.m_line, section.m_name));
}
if (!fs::is_directory(GetBlocksDir())) {
return InitError(
strprintf(_("Specified blocks directory \"%s\" does not exist."),
gArgs.GetArg("-blocksdir", "")));
}
// parse and validate enabled filter types
std::string blockfilterindex_value =
gArgs.GetArg("-blockfilterindex", DEFAULT_BLOCKFILTERINDEX);
if (blockfilterindex_value == "" || blockfilterindex_value == "1") {
g_enabled_filter_types = AllBlockFilterTypes();
} else if (blockfilterindex_value != "0") {
const std::vector<std::string> names =
gArgs.GetArgs("-blockfilterindex");
for (const auto &name : names) {
BlockFilterType filter_type;
if (!BlockFilterTypeByName(name, filter_type)) {
return InitError(
strprintf(_("Unknown -blockfilterindex value %s."), name));
}
g_enabled_filter_types.insert(filter_type);
}
}
// Basic filters are the only supported filters. The basic filters index
// must be enabled to serve compact filters
if (gArgs.GetBoolArg("-peerblockfilters", DEFAULT_PEERBLOCKFILTERS) &&
g_enabled_filter_types.count(BlockFilterType::BASIC) != 1) {
return InitError(
_("Cannot set -peerblockfilters without -blockfilterindex."));
}
// if using block pruning, then disallow txindex
if (gArgs.GetArg("-prune", 0)) {
if (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)) {
return InitError(_("Prune mode is incompatible with -txindex."));
}
if (!g_enabled_filter_types.empty()) {
return InitError(
_("Prune mode is incompatible with -blockfilterindex."));
}
}
// -bind and -whitebind can't be set when not listening
size_t nUserBind =
gArgs.GetArgs("-bind").size() + gArgs.GetArgs("-whitebind").size();
if (nUserBind != 0 && !gArgs.GetBoolArg("-listen", DEFAULT_LISTEN)) {
return InitError(Untranslated(
"Cannot set -bind or -whitebind together with -listen=0"));
}
// Make sure enough file descriptors are available
int nBind = std::max(nUserBind, size_t(1));
nUserMaxConnections =
gArgs.GetArg("-maxconnections", DEFAULT_MAX_PEER_CONNECTIONS);
nMaxConnections = std::max(nUserMaxConnections, 0);
// Trim requested connection counts, to fit into system limitations
// <int> in std::min<int>(...) to work around FreeBSD compilation issue
// described in #2695
nFD = RaiseFileDescriptorLimit(nMaxConnections + MIN_CORE_FILEDESCRIPTORS +
MAX_ADDNODE_CONNECTIONS);
#ifdef USE_POLL
int fd_max = nFD;
#else
int fd_max = FD_SETSIZE;
#endif
nMaxConnections =
std::max(std::min<int>(nMaxConnections, fd_max - nBind -
MIN_CORE_FILEDESCRIPTORS -
MAX_ADDNODE_CONNECTIONS),
0);
if (nFD < MIN_CORE_FILEDESCRIPTORS) {
return InitError(_("Not enough file descriptors available."));
}
nMaxConnections =
std::min(nFD - MIN_CORE_FILEDESCRIPTORS - MAX_ADDNODE_CONNECTIONS,
nMaxConnections);
if (nMaxConnections < nUserMaxConnections) {
InitWarning(strprintf(_("Reducing -maxconnections from %d to %d, "
"because of system limitations.")
.translated,
nUserMaxConnections, nMaxConnections));
}
// Step 3: parameter-to-internal-flags
if (gArgs.IsArgSet("-debug")) {
// Special-case: if -debug=0/-nodebug is set, turn off debugging
// messages
const std::vector<std::string> &categories = gArgs.GetArgs("-debug");
if (std::none_of(
categories.begin(), categories.end(),
[](std::string cat) { return cat == "0" || cat == "none"; })) {
for (const auto &cat : categories) {
if (!LogInstance().EnableCategory(cat)) {
InitWarning(strprintf(
_("Unsupported logging category %s=%s.").translated,
"-debug", cat));
}
}
}
}
// Now remove the logging categories which were explicitly excluded
for (const std::string &cat : gArgs.GetArgs("-debugexclude")) {
if (!LogInstance().DisableCategory(cat)) {
InitWarning(
strprintf(_("Unsupported logging category %s=%s.").translated,
"-debugexclude", cat));
}
}
// Checkmempool and checkblockindex default to true in regtest mode
int ratio = std::min<int>(
std::max<int>(
gArgs.GetArg("-checkmempool",
chainparams.DefaultConsistencyChecks() ? 1 : 0),
0),
1000000);
if (ratio != 0) {
g_mempool.setSanityCheck(1.0 / ratio);
}
fCheckBlockIndex = gArgs.GetBoolArg("-checkblockindex",
chainparams.DefaultConsistencyChecks());
fCheckpointsEnabled =
gArgs.GetBoolArg("-checkpoints", DEFAULT_CHECKPOINTS_ENABLED);
if (fCheckpointsEnabled) {
LogPrintf("Checkpoints will be verified.\n");
} else {
LogPrintf("Skipping checkpoint verification.\n");
}
hashAssumeValid = BlockHash::fromHex(
gArgs.GetArg("-assumevalid",
chainparams.GetConsensus().defaultAssumeValid.GetHex()));
if (!hashAssumeValid.IsNull()) {
LogPrintf("Assuming ancestors of block %s have valid signatures.\n",
hashAssumeValid.GetHex());
} else {
LogPrintf("Validating signatures for all blocks.\n");
}
if (gArgs.IsArgSet("-minimumchainwork")) {
const std::string minChainWorkStr =
gArgs.GetArg("-minimumchainwork", "");
if (!IsHexNumber(minChainWorkStr)) {
return InitError(strprintf(
Untranslated(
"Invalid non-hex (%s) minimum chain work value specified"),
minChainWorkStr));
}
nMinimumChainWork = UintToArith256(uint256S(minChainWorkStr));
} else {
nMinimumChainWork =
UintToArith256(chainparams.GetConsensus().nMinimumChainWork);
}
LogPrintf("Setting nMinimumChainWork=%s\n", nMinimumChainWork.GetHex());
if (nMinimumChainWork <
UintToArith256(chainparams.GetConsensus().nMinimumChainWork)) {
LogPrintf("Warning: nMinimumChainWork set below default value of %s\n",
chainparams.GetConsensus().nMinimumChainWork.GetHex());
}
// mempool limits
int64_t nMempoolSizeMax =
gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;
int64_t nMempoolSizeMin =
gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) *
1000 * 40;
if (nMempoolSizeMax < 0 || nMempoolSizeMax < nMempoolSizeMin) {
return InitError(strprintf(_("-maxmempool must be at least %d MB"),
std::ceil(nMempoolSizeMin / 1000000.0)));
}
// Configure excessive block size.
const uint64_t nProposedExcessiveBlockSize =
gArgs.GetArg("-excessiveblocksize", DEFAULT_MAX_BLOCK_SIZE);
if (!config.SetMaxBlockSize(nProposedExcessiveBlockSize)) {
return InitError(
_("Excessive block size must be > 1,000,000 bytes (1MB)"));
}
// Check blockmaxsize does not exceed maximum accepted block size.
const uint64_t nProposedMaxGeneratedBlockSize =
gArgs.GetArg("-blockmaxsize", DEFAULT_MAX_GENERATED_BLOCK_SIZE);
if (nProposedMaxGeneratedBlockSize > config.GetMaxBlockSize()) {
auto msg = _("Max generated block size (blockmaxsize) cannot exceed "
"the excessive block size (excessiveblocksize)");
return InitError(msg);
}
// block pruning; get the amount of disk space (in MiB) to allot for block &
// undo files
int64_t nPruneArg = gArgs.GetArg("-prune", 0);
if (nPruneArg < 0) {
return InitError(
_("Prune cannot be configured with a negative value."));
}
nPruneTarget = (uint64_t)nPruneArg * 1024 * 1024;
if (nPruneArg == 1) {
// manual pruning: -prune=1
LogPrintf("Block pruning enabled. Use RPC call "
"pruneblockchain(height) to manually prune block and undo "
"files.\n");
nPruneTarget = std::numeric_limits<uint64_t>::max();
fPruneMode = true;
} else if (nPruneTarget) {
if (nPruneTarget < MIN_DISK_SPACE_FOR_BLOCK_FILES) {
return InitError(
strprintf(_("Prune configured below the minimum of %d MiB. "
"Please use a higher number."),
MIN_DISK_SPACE_FOR_BLOCK_FILES / 1024 / 1024));
}
LogPrintf("Prune configured to target %u MiB on disk for block and "
"undo files.\n",
nPruneTarget / 1024 / 1024);
fPruneMode = true;
}
nConnectTimeout = gArgs.GetArg("-timeout", DEFAULT_CONNECT_TIMEOUT);
if (nConnectTimeout <= 0) {
nConnectTimeout = DEFAULT_CONNECT_TIMEOUT;
}
peer_connect_timeout =
gArgs.GetArg("-peertimeout", DEFAULT_PEER_CONNECT_TIMEOUT);
if (peer_connect_timeout <= 0) {
return InitError(Untranslated(
"peertimeout cannot be configured with a negative value."));
}
// Obtain the amount to charge excess UTXO
if (gArgs.IsArgSet("-excessutxocharge")) {
Amount n = Amount::zero();
auto parsed = ParseMoney(gArgs.GetArg("-excessutxocharge", ""), n);
if (!parsed || Amount::zero() > n) {
return InitError(AmountErrMsg(
"excessutxocharge", gArgs.GetArg("-excessutxocharge", "")));
}
config.SetExcessUTXOCharge(n);
} else {
config.SetExcessUTXOCharge(DEFAULT_UTXO_FEE);
}
if (gArgs.IsArgSet("-minrelaytxfee")) {
Amount n = Amount::zero();
auto parsed = ParseMoney(gArgs.GetArg("-minrelaytxfee", ""), n);
if (!parsed || n == Amount::zero()) {
return InitError(AmountErrMsg("minrelaytxfee",
gArgs.GetArg("-minrelaytxfee", "")));
}
// High fee check is done afterward in CWallet::CreateWalletFromFile()
::minRelayTxFee = CFeeRate(n);
}
// Sanity check argument for min fee for including tx in block
// TODO: Harmonize which arguments need sanity checking and where that
// happens.
if (gArgs.IsArgSet("-blockmintxfee")) {
Amount n = Amount::zero();
if (!ParseMoney(gArgs.GetArg("-blockmintxfee", ""), n)) {
return InitError(AmountErrMsg("blockmintxfee",
gArgs.GetArg("-blockmintxfee", "")));
}
}
// Feerate used to define dust. Shouldn't be changed lightly as old
// implementations may inadvertently create non-standard transactions.
if (gArgs.IsArgSet("-dustrelayfee")) {
Amount n = Amount::zero();
auto parsed = ParseMoney(gArgs.GetArg("-dustrelayfee", ""), n);
if (!parsed || Amount::zero() == n) {
return InitError(AmountErrMsg("dustrelayfee",
gArgs.GetArg("-dustrelayfee", "")));
}
dustRelayFee = CFeeRate(n);
}
fRequireStandard =
!gArgs.GetBoolArg("-acceptnonstdtxn", !chainparams.RequireStandard());
if (!chainparams.IsTestChain() && !fRequireStandard) {
return InitError(strprintf(
Untranslated(
"acceptnonstdtxn is not currently supported for %s chain"),
chainparams.NetworkIDString()));
}
nBytesPerSigOp = gArgs.GetArg("-bytespersigop", nBytesPerSigOp);
if (!g_wallet_init_interface.ParameterInteraction()) {
return false;
}
fIsBareMultisigStd =
gArgs.GetBoolArg("-permitbaremultisig", DEFAULT_PERMIT_BAREMULTISIG);
fAcceptDatacarrier =
gArgs.GetBoolArg("-datacarrier", DEFAULT_ACCEPT_DATACARRIER);
// Option to startup with mocktime set (used for regression testing):
SetMockTime(gArgs.GetArg("-mocktime", 0)); // SetMockTime(0) is a no-op
if (gArgs.GetBoolArg("-peerbloomfilters", DEFAULT_PEERBLOOMFILTERS)) {
nLocalServices = ServiceFlags(nLocalServices | NODE_BLOOM);
}
nMaxTipAge = gArgs.GetArg("-maxtipage", DEFAULT_MAX_TIP_AGE);
return true;
}
static bool LockDataDirectory(bool probeOnly) {
// Make sure only a single Bitcoin process is using the data directory.
fs::path datadir = GetDataDir();
if (!DirIsWritable(datadir)) {
return InitError(strprintf(
_("Cannot write to data directory '%s'; check permissions."),
datadir.string()));
}
if (!LockDirectory(datadir, ".lock", probeOnly)) {
return InitError(strprintf(_("Cannot obtain a lock on data directory "
"%s. %s is probably already running."),
datadir.string(), PACKAGE_NAME));
}
return true;
}
bool AppInitSanityChecks() {
// Step 4: sanity checks
// Initialize elliptic curve code
std::string sha256_algo = SHA256AutoDetect();
LogPrintf("Using the '%s' SHA256 implementation\n", sha256_algo);
RandomInit();
ECC_Start();
globalVerifyHandle.reset(new ECCVerifyHandle());
// Sanity check
if (!InitSanityCheck()) {
return InitError(strprintf(
_("Initialization sanity check failed. %s is shutting down."),
PACKAGE_NAME));
}
// Probe the data directory lock to give an early error message, if possible
// We cannot hold the data directory lock here, as the forking for daemon()
// hasn't yet happened, and a fork will cause weird behavior to it.
return LockDataDirectory(true);
}
bool AppInitLockDataDirectory() {
// After daemonization get the data directory lock again and hold on to it
// until exit. This creates a slight window for a race condition to happen,
// however this condition is harmless: it will at most make us exit without
// printing a message to console.
if (!LockDataDirectory(false)) {
// Detailed error printed inside LockDataDirectory
return false;
}
return true;
}
bool AppInitMain(Config &config, RPCServer &rpcServer,
HTTPRPCRequestProcessor &httpRPCRequestProcessor,
NodeContext &node) {
// Step 4a: application initialization
const CChainParams &chainparams = config.GetChainParams();
if (!CreatePidFile()) {
// Detailed error printed inside CreatePidFile().
return false;
}
BCLog::Logger &logger = LogInstance();
if (logger.m_print_to_file) {
if (gArgs.GetBoolArg("-shrinkdebugfile",
logger.DefaultShrinkDebugFile())) {
// Do this first since it both loads a bunch of debug.log into
// memory, and because this needs to happen before any other
// debug.log printing.
logger.ShrinkDebugFile();
}
}
if (!logger.StartLogging()) {
return InitError(
strprintf(Untranslated("Could not open debug log file %s"),
logger.m_file_path.string()));
}
if (!logger.m_log_timestamps) {
LogPrintf("Startup time: %s\n", FormatISO8601DateTime(GetTime()));
}
LogPrintf("Default data directory %s\n", GetDefaultDataDir().string());
LogPrintf("Using data directory %s\n", GetDataDir().string());
// Only log conf file usage message if conf file actually exists.
fs::path config_file_path =
GetConfigFile(gArgs.GetArg("-conf", BITCOIN_CONF_FILENAME));
if (fs::exists(config_file_path)) {
LogPrintf("Config file: %s\n", config_file_path.string());
} else if (gArgs.IsArgSet("-conf")) {
// Warn if no conf file exists at path provided by user
InitWarning(strprintf(
_("The specified config file %s does not exist\n").translated,
config_file_path.string()));
} else {
// Not categorizing as "Warning" because it's the default behavior
LogPrintf("Config file: %s (not found, skipping)\n",
config_file_path.string());
}
// Log the config arguments to debug.log
gArgs.LogArgs();
LogPrintf("Using at most %i automatic connections (%i file descriptors "
"available)\n",
nMaxConnections, nFD);
// Warn about relative -datadir path.
if (gArgs.IsArgSet("-datadir") &&
!fs::path(gArgs.GetArg("-datadir", "")).is_absolute()) {
LogPrintf("Warning: relative datadir option '%s' specified, which will "
"be interpreted relative to the current working directory "
"'%s'. This is fragile, because if bitcoin is started in the "
"future from a different location, it will be unable to "
"locate the current data files. There could also be data "
"loss if bitcoin is started while in a temporary "
"directory.\n",
gArgs.GetArg("-datadir", ""), fs::current_path().string());
}
InitSignatureCache();
InitScriptExecutionCache();
int script_threads = gArgs.GetArg("-par", DEFAULT_SCRIPTCHECK_THREADS);
if (script_threads <= 0) {
// -par=0 means autodetect (number of cores - 1 script threads)
// -par=-n means "leave n cores free" (number of cores - n - 1 script
// threads)
script_threads += GetNumCores();
}
// Subtract 1 because the main thread counts towards the par threads
script_threads = std::max(script_threads - 1, 0);
// Number of script-checking threads <= MAX_SCRIPTCHECK_THREADS
script_threads = std::min(script_threads, MAX_SCRIPTCHECK_THREADS);
LogPrintf("Script verification uses %d additional threads\n",
script_threads);
if (script_threads >= 1) {
for (int i = 0; i < script_threads; ++i) {
threadGroup.create_thread([i]() { return ThreadScriptCheck(i); });
}
}
assert(!node.scheduler);
node.scheduler = std::make_unique<CScheduler>();
// Start the lightweight task scheduler thread
CScheduler::Function serviceLoop = [&node] {
node.scheduler->serviceQueue();
};
threadGroup.create_thread(std::bind(&TraceThread<CScheduler::Function>,
"scheduler", serviceLoop));
// Gather some entropy once per minute.
node.scheduler->scheduleEvery(
[] {
RandAddPeriodic();
return true;
},
std::chrono::minutes{1});
GetMainSignals().RegisterBackgroundSignalScheduler(*node.scheduler);
// Create client interfaces for wallets that are supposed to be loaded
// according to -wallet and -disablewallet options. This only constructs
// the interfaces, it doesn't load wallet data. Wallets actually get loaded
// when load() and start() interface methods are called below.
g_wallet_init_interface.Construct(node);
/**
* Register RPC commands regardless of -server setting so they will be
* available in the GUI RPC console even if external calls are disabled.
*/
RegisterAllRPCCommands(config, rpcServer, tableRPC);
for (const auto &client : node.chain_clients) {
client->registerRpcs();
}
#if ENABLE_ZMQ
RegisterZMQRPCCommands(tableRPC);
#endif
/**
* Start the RPC server. It will be started in "warmup" mode and not
* process calls yet (but it will verify that the server is there and will
* be ready later). Warmup mode will be completed when initialisation is
* finished.
*/
if (gArgs.GetBoolArg("-server", false)) {
uiInterface.InitMessage_connect(SetRPCWarmupStatus);
if (!AppInitServers(config, httpRPCRequestProcessor)) {
return InitError(
_("Unable to start HTTP server. See debug log for details."));
}
}
// Step 5: verify wallet database integrity
for (const auto &client : node.chain_clients) {
if (!client->verify(chainparams)) {
return false;
}
}
// Step 6: network initialization
// Note that we absolutely cannot open any actual connections
// until the very end ("start node") as the UTXO/block state
// is not yet setup and may end up being set up twice if we
// need to reindex later.
assert(!node.banman);
node.banman = std::make_unique<BanMan>(
GetDataDir() / "banlist.dat", config.GetChainParams(), &uiInterface,
gArgs.GetArg("-bantime", DEFAULT_MISBEHAVING_BANTIME));
assert(!node.connman);
node.connman = std::make_unique<CConnman>(
config, GetRand(std::numeric_limits<uint64_t>::max()),
GetRand(std::numeric_limits<uint64_t>::max()));
assert(!node.chainman);
node.chainman = &g_chainman;
ChainstateManager &chainman = EnsureChainman(node);
- node.peer_logic.reset(new PeerLogicValidation(
- node.connman.get(), node.banman.get(), *node.scheduler));
+ node.peer_logic.reset(
+ new PeerLogicValidation(node.connman.get(), node.banman.get(),
+ *node.scheduler, *node.chainman));
RegisterValidationInterface(node.peer_logic.get());
// sanitize comments per BIP-0014, format user agent and check total size
std::vector<std::string> uacomments;
for (const std::string &cmt : gArgs.GetArgs("-uacomment")) {
if (cmt != SanitizeString(cmt, SAFE_CHARS_UA_COMMENT)) {
return InitError(strprintf(
_("User Agent comment (%s) contains unsafe characters."), cmt));
}
uacomments.push_back(cmt);
}
const std::string strSubVersion =
FormatSubVersion(CLIENT_NAME, CLIENT_VERSION, uacomments);
if (strSubVersion.size() > MAX_SUBVERSION_LENGTH) {
return InitError(strprintf(
_("Total length of network version string (%i) exceeds maximum "
"length (%i). Reduce the number or size of uacomments."),
strSubVersion.size(), MAX_SUBVERSION_LENGTH));
}
if (gArgs.IsArgSet("-onlynet")) {
std::set<enum Network> nets;
for (const std::string &snet : gArgs.GetArgs("-onlynet")) {
enum Network net = ParseNetwork(snet);
if (net == NET_UNROUTABLE) {
return InitError(strprintf(
_("Unknown network specified in -onlynet: '%s'"), snet));
}
nets.insert(net);
}
for (int n = 0; n < NET_MAX; n++) {
enum Network net = (enum Network)n;
if (!nets.count(net)) {
SetReachable(net, false);
}
}
}
// Check for host lookup allowed before parsing any network related
// parameters
fNameLookup = gArgs.GetBoolArg("-dns", DEFAULT_NAME_LOOKUP);
bool proxyRandomize =
gArgs.GetBoolArg("-proxyrandomize", DEFAULT_PROXYRANDOMIZE);
// -proxy sets a proxy for all outgoing network traffic
// -noproxy (or -proxy=0) as well as the empty string can be used to not set
// a proxy, this is the default
std::string proxyArg = gArgs.GetArg("-proxy", "");
SetReachable(NET_ONION, false);
if (proxyArg != "" && proxyArg != "0") {
CService proxyAddr;
if (!Lookup(proxyArg.c_str(), proxyAddr, 9050, fNameLookup)) {
return InitError(strprintf(
_("Invalid -proxy address or hostname: '%s'"), proxyArg));
}
proxyType addrProxy = proxyType(proxyAddr, proxyRandomize);
if (!addrProxy.IsValid()) {
return InitError(strprintf(
_("Invalid -proxy address or hostname: '%s'"), proxyArg));
}
SetProxy(NET_IPV4, addrProxy);
SetProxy(NET_IPV6, addrProxy);
SetProxy(NET_ONION, addrProxy);
SetNameProxy(addrProxy);
// by default, -proxy sets onion as reachable, unless -noonion later
SetReachable(NET_ONION, true);
}
// -onion can be used to set only a proxy for .onion, or override normal
// proxy for .onion addresses.
// -noonion (or -onion=0) disables connecting to .onion entirely. An empty
// string is used to not override the onion proxy (in which case it defaults
// to -proxy set above, or none)
std::string onionArg = gArgs.GetArg("-onion", "");
if (onionArg != "") {
if (onionArg == "0") {
// Handle -noonion/-onion=0
SetReachable(NET_ONION, false);
} else {
CService onionProxy;
if (!Lookup(onionArg.c_str(), onionProxy, 9050, fNameLookup)) {
return InitError(strprintf(
_("Invalid -onion address or hostname: '%s'"), onionArg));
}
proxyType addrOnion = proxyType(onionProxy, proxyRandomize);
if (!addrOnion.IsValid()) {
return InitError(strprintf(
_("Invalid -onion address or hostname: '%s'"), onionArg));
}
SetProxy(NET_ONION, addrOnion);
SetReachable(NET_ONION, true);
}
}
// see Step 2: parameter interactions for more information about these
fListen = gArgs.GetBoolArg("-listen", DEFAULT_LISTEN);
fDiscover = gArgs.GetBoolArg("-discover", true);
g_relay_txes = !gArgs.GetBoolArg("-blocksonly", DEFAULT_BLOCKSONLY);
for (const std::string &strAddr : gArgs.GetArgs("-externalip")) {
CService addrLocal;
if (Lookup(strAddr.c_str(), addrLocal, GetListenPort(), fNameLookup) &&
addrLocal.IsValid()) {
AddLocal(addrLocal, LOCAL_MANUAL);
} else {
return InitError(
Untranslated(ResolveErrMsg("externalip", strAddr)));
}
}
// Read asmap file if configured
if (gArgs.IsArgSet("-asmap")) {
fs::path asmap_path = fs::path(gArgs.GetArg("-asmap", ""));
if (asmap_path.empty()) {
asmap_path = DEFAULT_ASMAP_FILENAME;
}
if (!asmap_path.is_absolute()) {
asmap_path = GetDataDir() / asmap_path;
}
if (!fs::exists(asmap_path)) {
InitError(strprintf(_("Could not find asmap file %s"), asmap_path));
return false;
}
std::vector<bool> asmap = CAddrMan::DecodeAsmap(asmap_path);
if (asmap.size() == 0) {
InitError(
strprintf(_("Could not parse asmap file %s"), asmap_path));
return false;
}
const uint256 asmap_version = SerializeHash(asmap);
node.connman->SetAsmap(std::move(asmap));
LogPrintf("Using asmap version %s for IP bucketing\n",
asmap_version.ToString());
} else {
LogPrintf("Using /16 prefix for IP bucketing\n");
}
#if ENABLE_ZMQ
g_zmq_notification_interface = CZMQNotificationInterface::Create();
if (g_zmq_notification_interface) {
RegisterValidationInterface(g_zmq_notification_interface);
}
#endif
// unlimited unless -maxuploadtarget is set
uint64_t nMaxOutboundLimit = 0;
uint64_t nMaxOutboundTimeframe = MAX_UPLOAD_TIMEFRAME;
if (gArgs.IsArgSet("-maxuploadtarget")) {
nMaxOutboundLimit =
gArgs.GetArg("-maxuploadtarget", DEFAULT_MAX_UPLOAD_TARGET) * 1024 *
1024;
}
// Step 6.5 (I guess ?): Initialize Avalanche.
g_avalanche =
std::make_unique<avalanche::Processor>(*node.chain, node.connman.get());
// Step 7: load block chain
fReindex = gArgs.GetBoolArg("-reindex", false);
bool fReindexChainState = gArgs.GetBoolArg("-reindex-chainstate", false);
// cache size calculations
int64_t nTotalCache = (gArgs.GetArg("-dbcache", DEFAULT_DB_CACHE_MB) << 20);
// total cache cannot be less than MIN_DB_CACHE_MB
nTotalCache = std::max(nTotalCache, MIN_DB_CACHE_MB << 20);
// total cache cannot be greater than MAX_DB_CACHE_MB
nTotalCache = std::min(nTotalCache, MAX_DB_CACHE_MB << 20);
int64_t nBlockTreeDBCache =
std::min(nTotalCache / 8, MAX_BLOCK_DB_CACHE_MB << 20);
nTotalCache -= nBlockTreeDBCache;
int64_t nTxIndexCache =
std::min(nTotalCache / 8, gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)
? MAX_TX_INDEX_CACHE_MB << 20
: 0);
nTotalCache -= nTxIndexCache;
int64_t filter_index_cache = 0;
if (!g_enabled_filter_types.empty()) {
size_t n_indexes = g_enabled_filter_types.size();
int64_t max_cache =
std::min(nTotalCache / 8, MAX_FILTER_INDEX_CACHE_MB << 20);
filter_index_cache = max_cache / n_indexes;
nTotalCache -= filter_index_cache * n_indexes;
}
// use 25%-50% of the remainder for disk cache
int64_t nCoinDBCache =
std::min(nTotalCache / 2, (nTotalCache / 4) + (1 << 23));
// cap total coins db cache
nCoinDBCache = std::min(nCoinDBCache, MAX_COINS_DB_CACHE_MB << 20);
nTotalCache -= nCoinDBCache;
// the rest goes to in-memory cache
nCoinCacheUsage = nTotalCache;
int64_t nMempoolSizeMax =
gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000;
LogPrintf("Cache configuration:\n");
LogPrintf("* Using %.1f MiB for block index database\n",
nBlockTreeDBCache * (1.0 / 1024 / 1024));
if (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)) {
LogPrintf("* Using %.1f MiB for transaction index database\n",
nTxIndexCache * (1.0 / 1024 / 1024));
}
for (BlockFilterType filter_type : g_enabled_filter_types) {
LogPrintf("* Using %.1f MiB for %s block filter index database\n",
filter_index_cache * (1.0 / 1024 / 1024),
BlockFilterTypeName(filter_type));
}
LogPrintf("* Using %.1f MiB for chain state database\n",
nCoinDBCache * (1.0 / 1024 / 1024));
LogPrintf("* Using %.1f MiB for in-memory UTXO set (plus up to %.1f MiB of "
"unused mempool space)\n",
nCoinCacheUsage * (1.0 / 1024 / 1024),
nMempoolSizeMax * (1.0 / 1024 / 1024));
bool fLoaded = false;
while (!fLoaded && !ShutdownRequested()) {
const bool fReset = fReindex;
auto is_coinsview_empty =
[&](CChainState *chainstate) EXCLUSIVE_LOCKS_REQUIRED(::cs_main) {
return fReset || fReindexChainState ||
chainstate->CoinsTip().GetBestBlock().IsNull();
};
bilingual_str strLoadError;
uiInterface.InitMessage(_("Loading block index...").translated);
do {
bool failed_verification = false;
const int64_t load_block_index_start_time = GetTimeMillis();
try {
LOCK(cs_main);
chainman.InitializeChainstate();
UnloadBlockIndex();
// new CBlockTreeDB tries to delete the existing file, which
// fails if it's still open from the previous loop. Close it
// first:
pblocktree.reset();
pblocktree.reset(
new CBlockTreeDB(nBlockTreeDBCache, false, fReset));
if (fReset) {
pblocktree->WriteReindexing(true);
// If we're reindexing in prune mode, wipe away unusable
// block files and all undo data files
if (fPruneMode) {
CleanupBlockRevFiles();
}
}
const Consensus::Params &params = chainparams.GetConsensus();
// If necessary, upgrade from older database format.
// This is a no-op if we cleared the block tree db with -reindex
// or -reindex-chainstate
if (!pblocktree->Upgrade(params)) {
strLoadError = _("Error upgrading block index database");
break;
}
if (ShutdownRequested()) {
break;
}
// LoadBlockIndex will load fHavePruned if we've ever removed a
// block file from disk.
// Note that it also sets fReindex based on the disk flag!
// From here on out fReindex and fReset mean something
// different!
if (!LoadBlockIndex(params)) {
if (ShutdownRequested()) {
break;
}
strLoadError = _("Error loading block database");
break;
}
// If the loaded chain has a wrong genesis, bail out immediately
// (we're likely using a testnet datadir, or the other way
// around).
if (!::BlockIndex().empty() &&
!LookupBlockIndex(params.hashGenesisBlock)) {
return InitError(_("Incorrect or no genesis block found. "
"Wrong datadir for network?"));
}
// Check for changed -prune state. What we are concerned about
// is a user who has pruned blocks in the past, but is now
// trying to run unpruned.
if (fHavePruned && !fPruneMode) {
strLoadError =
_("You need to rebuild the database using -reindex to "
"go back to unpruned mode. This will redownload the "
"entire blockchain");
break;
}
// At this point blocktree args are consistent with what's on
// disk. If we're not mid-reindex (based on disk + args), add a
// genesis block on disk (otherwise we use the one already on
// disk).
// This is called again in ThreadImport after the reindex
// completes.
if (!fReindex && !LoadGenesisBlock(chainparams)) {
strLoadError = _("Error initializing block database");
break;
}
// At this point we're either in reindex or we've loaded a
// useful block tree into BlockIndex()!
bool failed_chainstate_init = false;
for (CChainState *chainstate : chainman.GetAll()) {
LogPrintf("Initializing chainstate %s\n",
chainstate->ToString());
chainstate->InitCoinsDB(
/* cache_size_bytes */ nCoinDBCache,
/* in_memory */ false,
/* should_wipe */ fReset || fReindexChainState);
chainstate->CoinsErrorCatcher().AddReadErrCallback([]() {
uiInterface.ThreadSafeMessageBox(
_("Error reading from database, shutting down."),
"", CClientUIInterface::MSG_ERROR);
});
// If necessary, upgrade from older database format.
// This is a no-op if we cleared the coinsviewdb with
// -reindex or -reindex-chainstate
if (!chainstate->CoinsDB().Upgrade()) {
strLoadError = _("Error upgrading chainstate database");
failed_chainstate_init = true;
break;
}
// ReplayBlocks is a no-op if we cleared the coinsviewdb
// with -reindex or -reindex-chainstate
if (!chainstate->ReplayBlocks(params)) {
strLoadError = _(
"Unable to replay blocks. You will need to rebuild "
"the database using -reindex-chainstate.");
failed_chainstate_init = true;
break;
}
// The on-disk coinsdb is now in a good state, create the
// cache
chainstate->InitCoinsCache();
assert(chainstate->CanFlushToDisk());
if (!is_coinsview_empty(chainstate)) {
// LoadChainTip initializes the chain based on
// CoinsTip()'s best block
if (!chainstate->LoadChainTip(chainparams)) {
strLoadError =
_("Error initializing block database");
failed_chainstate_init = true;
// out of the per-chainstate loop
break;
}
assert(chainstate->m_chain.Tip() != nullptr);
}
}
if (failed_chainstate_init) {
// out of the chainstate activation do-while
break;
}
for (CChainState *chainstate : chainman.GetAll()) {
if (!is_coinsview_empty(chainstate)) {
uiInterface.InitMessage(
_("Verifying blocks...").translated);
if (fHavePruned &&
gArgs.GetArg("-checkblocks", DEFAULT_CHECKBLOCKS) >
MIN_BLOCKS_TO_KEEP) {
LogPrintf(
"Prune: pruned datadir may not have more than "
"%d blocks; only checking available blocks\n",
MIN_BLOCKS_TO_KEEP);
}
const CBlockIndex *tip = chainstate->m_chain.Tip();
RPCNotifyBlockChange(tip);
if (tip &&
tip->nTime > GetAdjustedTime() + 2 * 60 * 60) {
strLoadError =
_("The block database contains a block which "
"appears to be from the future. "
"This may be due to your computer's date and "
"time being set incorrectly. "
"Only rebuild the block database if you are "
"sure that your computer's date and time are "
"correct");
failed_verification = true;
break;
}
// Only verify the DB of the active chainstate. This is
// fixed in later work when we allow VerifyDB to be
// parameterized by chainstate.
if (&::ChainstateActive() == chainstate &&
!CVerifyDB().VerifyDB(
config, &chainstate->CoinsDB(),
gArgs.GetArg("-checklevel", DEFAULT_CHECKLEVEL),
gArgs.GetArg("-checkblocks",
DEFAULT_CHECKBLOCKS))) {
strLoadError =
_("Corrupted block database detected");
failed_verification = true;
break;
}
}
}
} catch (const std::exception &e) {
LogPrintf("%s\n", e.what());
strLoadError = _("Error opening block database");
failed_verification = true;
break;
}
if (!failed_verification) {
fLoaded = true;
LogPrintf(" block index %15dms\n",
GetTimeMillis() - load_block_index_start_time);
}
} while (false);
if (!fLoaded && !ShutdownRequested()) {
// first suggest a reindex
if (!fReset) {
bool fRet = uiInterface.ThreadSafeQuestion(
strLoadError + Untranslated(".\n\n") +
_("Do you want to rebuild the block database now?"),
strLoadError.original +
".\nPlease restart with -reindex or "
"-reindex-chainstate to recover.",
"",
CClientUIInterface::MSG_ERROR |
CClientUIInterface::BTN_ABORT);
if (fRet) {
fReindex = true;
AbortShutdown();
} else {
LogPrintf("Aborted block database rebuild. Exiting.\n");
return false;
}
} else {
return InitError(strLoadError);
}
}
}
// As LoadBlockIndex can take several minutes, it's possible the user
// requested to kill the GUI during the last operation. If so, exit.
// As the program has not fully started yet, Shutdown() is possibly
// overkill.
if (ShutdownRequested()) {
LogPrintf("Shutdown requested. Exiting.\n");
return false;
}
// Encoded addresses using cashaddr instead of base58.
// We do this by default to avoid confusion with BTC addresses.
config.SetCashAddrEncoding(gArgs.GetBoolArg("-usecashaddr", true));
// Now that the chain state is loaded, make mempool generally available in
// the node context. For example the connection manager, wallet, or RPC
// threads, which are all started after this, may use it from the node
// context.
assert(!node.mempool);
node.mempool = &::g_mempool;
// Step 8: load indexers
if (gArgs.GetBoolArg("-txindex", DEFAULT_TXINDEX)) {
g_txindex = std::make_unique<TxIndex>(nTxIndexCache, false, fReindex);
g_txindex->Start();
}
for (const auto &filter_type : g_enabled_filter_types) {
InitBlockFilterIndex(filter_type, filter_index_cache, false, fReindex);
GetBlockFilterIndex(filter_type)->Start();
}
// Step 9: load wallet
for (const auto &client : node.chain_clients) {
if (!client->load(chainparams)) {
return false;
}
}
// Step 10: data directory maintenance
// if pruning, unset the service bit and perform the initial blockstore
// prune after any wallet rescanning has taken place.
if (fPruneMode) {
LogPrintf("Unsetting NODE_NETWORK on prune mode\n");
nLocalServices = ServiceFlags(nLocalServices & ~NODE_NETWORK);
if (!fReindex) {
LOCK(cs_main);
for (CChainState *chainstate : chainman.GetAll()) {
uiInterface.InitMessage(_("Pruning blockstore...").translated);
chainstate->PruneAndFlush();
}
}
}
// Step 11: import blocks
if (!CheckDiskSpace(GetDataDir())) {
InitError(
strprintf(_("Error: Disk space is low for %s"), GetDataDir()));
return false;
}
if (!CheckDiskSpace(GetBlocksDir())) {
InitError(
strprintf(_("Error: Disk space is low for %s"), GetBlocksDir()));
return false;
}
// Either install a handler to notify us when genesis activates, or set
// fHaveGenesis directly.
// No locking, as this happens before any background thread is started.
boost::signals2::connection block_notify_genesis_wait_connection;
if (::ChainActive().Tip() == nullptr) {
block_notify_genesis_wait_connection =
uiInterface.NotifyBlockTip_connect(
std::bind(BlockNotifyGenesisWait, std::placeholders::_2));
} else {
fHaveGenesis = true;
}
#if defined(HAVE_SYSTEM)
if (gArgs.IsArgSet("-blocknotify")) {
uiInterface.NotifyBlockTip_connect(BlockNotifyCallback);
}
#endif
std::vector<fs::path> vImportFiles;
for (const std::string &strFile : gArgs.GetArgs("-loadblock")) {
vImportFiles.push_back(strFile);
}
threadGroup.create_thread([=, &config, &chainman] {
ThreadImport(config, chainman, vImportFiles);
});
// Wait for genesis block to be processed
{
WAIT_LOCK(g_genesis_wait_mutex, lock);
// We previously could hang here if StartShutdown() is called prior to
// ThreadImport getting started, so instead we just wait on a timer to
// check ShutdownRequested() regularly.
while (!fHaveGenesis && !ShutdownRequested()) {
g_genesis_wait_cv.wait_for(lock, std::chrono::milliseconds(500));
}
block_notify_genesis_wait_connection.disconnect();
}
if (ShutdownRequested()) {
return false;
}
// Step 12: start node
int chain_active_height;
//// debug print
{
LOCK(cs_main);
LogPrintf("block tree size = %u\n", ::BlockIndex().size());
chain_active_height = ::ChainActive().Height();
}
LogPrintf("nBestHeight = %d\n", chain_active_height);
if (gArgs.GetBoolArg("-listenonion", DEFAULT_LISTEN_ONION)) {
StartTorControl();
}
Discover();
// Map ports with UPnP
if (gArgs.GetBoolArg("-upnp", DEFAULT_UPNP)) {
StartMapPort();
}
CConnman::Options connOptions;
connOptions.nLocalServices = nLocalServices;
connOptions.nMaxConnections = nMaxConnections;
connOptions.m_max_outbound_full_relay = std::min(
MAX_OUTBOUND_FULL_RELAY_CONNECTIONS, connOptions.nMaxConnections);
connOptions.m_max_outbound_block_relay = std::min(
MAX_BLOCKS_ONLY_CONNECTIONS,
connOptions.nMaxConnections - connOptions.m_max_outbound_full_relay);
connOptions.nMaxAddnode = MAX_ADDNODE_CONNECTIONS;
connOptions.nMaxFeeler = 1;
connOptions.nBestHeight = chain_active_height;
connOptions.uiInterface = &uiInterface;
connOptions.m_banman = node.banman.get();
connOptions.m_msgproc = node.peer_logic.get();
connOptions.nSendBufferMaxSize =
1000 * gArgs.GetArg("-maxsendbuffer", DEFAULT_MAXSENDBUFFER);
connOptions.nReceiveFloodSize =
1000 * gArgs.GetArg("-maxreceivebuffer", DEFAULT_MAXRECEIVEBUFFER);
connOptions.m_added_nodes = gArgs.GetArgs("-addnode");
connOptions.nMaxOutboundTimeframe = nMaxOutboundTimeframe;
connOptions.nMaxOutboundLimit = nMaxOutboundLimit;
connOptions.m_peer_connect_timeout = peer_connect_timeout;
for (const std::string &strBind : gArgs.GetArgs("-bind")) {
CService addrBind;
if (!Lookup(strBind.c_str(), addrBind, GetListenPort(), false)) {
return InitError(Untranslated(ResolveErrMsg("bind", strBind)));
}
connOptions.vBinds.push_back(addrBind);
}
for (const std::string &strBind : gArgs.GetArgs("-whitebind")) {
NetWhitebindPermissions whitebind;
std::string error;
if (!NetWhitebindPermissions::TryParse(strBind, whitebind, error)) {
return InitError(Untranslated(error));
}
connOptions.vWhiteBinds.push_back(whitebind);
}
for (const auto &net : gArgs.GetArgs("-whitelist")) {
NetWhitelistPermissions subnet;
std::string error;
if (!NetWhitelistPermissions::TryParse(net, subnet, error)) {
return InitError(Untranslated(error));
}
connOptions.vWhitelistedRange.push_back(subnet);
}
connOptions.vSeedNodes = gArgs.GetArgs("-seednode");
// Initiate outbound connections unless connect=0
connOptions.m_use_addrman_outgoing = !gArgs.IsArgSet("-connect");
if (!connOptions.m_use_addrman_outgoing) {
const auto connect = gArgs.GetArgs("-connect");
if (connect.size() != 1 || connect[0] != "0") {
connOptions.m_specified_outgoing = connect;
}
}
if (!node.connman->Start(*node.scheduler, connOptions)) {
return false;
}
// Step 13: finished
SetRPCWarmupFinished();
uiInterface.InitMessage(_("Done loading").translated);
for (const auto &client : node.chain_clients) {
client->start(*node.scheduler);
}
BanMan *banman = node.banman.get();
node.scheduler->scheduleEvery(
[banman] {
banman->DumpBanlist();
return true;
},
DUMP_BANS_INTERVAL);
// Start Avalanche's event loop.
g_avalanche->startEventLoop(*node.scheduler);
return true;
}
diff --git a/src/net_processing.cpp b/src/net_processing.cpp
index 6ee9581e8..bb57a34f3 100644
--- a/src/net_processing.cpp
+++ b/src/net_processing.cpp
@@ -1,5290 +1,5293 @@
// 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 <net_processing.h>
#include <addrman.h>
#include <avalanche/processor.h>
#include <banman.h>
#include <blockdb.h>
#include <blockencodings.h>
#include <blockfilter.h>
#include <blockvalidity.h>
#include <chain.h>
#include <chainparams.h>
#include <config.h>
#include <consensus/validation.h>
#include <hash.h>
#include <index/blockfilterindex.h>
#include <merkleblock.h>
#include <netbase.h>
#include <netmessagemaker.h>
#include <policy/fees.h>
#include <policy/policy.h>
#include <primitives/block.h>
#include <primitives/transaction.h>
#include <random.h>
#include <reverse_iterator.h>
#include <scheduler.h>
#include <tinyformat.h>
#include <txmempool.h>
#include <util/strencodings.h>
#include <util/system.h>
#include <validation.h>
#include <memory>
#include <typeinfo>
#if defined(NDEBUG)
#error "Bitcoin cannot be compiled without assertions."
#endif
/** Expiration time for orphan transactions in seconds */
static constexpr int64_t ORPHAN_TX_EXPIRE_TIME = 20 * 60;
/** Minimum time between orphan transactions expire time checks in seconds */
static constexpr int64_t ORPHAN_TX_EXPIRE_INTERVAL = 5 * 60;
/** How long to cache transactions in mapRelay for normal relay */
static constexpr std::chrono::seconds RELAY_TX_CACHE_TIME{15 * 60};
/**
* Headers download timeout expressed in microseconds.
* Timeout = base + per_header * (expected number of headers)
*/
// 15 minutes
static constexpr int64_t HEADERS_DOWNLOAD_TIMEOUT_BASE = 15 * 60 * 1000000;
// 1ms/header
static constexpr int64_t HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER = 1000;
/**
* Protect at least this many outbound peers from disconnection due to
* slow/behind headers chain.
*/
static constexpr int32_t MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT = 4;
/**
* Timeout for (unprotected) outbound peers to sync to our chainwork, in
* seconds.
*/
// 20 minutes
static constexpr int64_t CHAIN_SYNC_TIMEOUT = 20 * 60;
/** How frequently to check for stale tips, in seconds */
// 10 minutes
static constexpr int64_t STALE_CHECK_INTERVAL = 10 * 60;
/**
* How frequently to check for extra outbound peers and disconnect, in seconds.
*/
static constexpr int64_t EXTRA_PEER_CHECK_INTERVAL = 45;
/**
* Minimum time an outbound-peer-eviction candidate must be connected for, in
* order to evict, in seconds.
*/
static constexpr int64_t MINIMUM_CONNECT_TIME = 30;
/** SHA256("main address relay")[0:8] */
static constexpr uint64_t RANDOMIZER_ID_ADDRESS_RELAY = 0x3cac0035b5866b90ULL;
/// Age after which a stale block will no longer be served if requested as
/// protection against fingerprinting. Set to one month, denominated in seconds.
static constexpr int STALE_RELAY_AGE_LIMIT = 30 * 24 * 60 * 60;
/// Age after which a block is considered historical for purposes of rate
/// limiting block relay. Set to one week, denominated in seconds.
static constexpr int HISTORICAL_BLOCK_AGE = 7 * 24 * 60 * 60;
/** Maximum number of in-flight transactions from a peer */
static constexpr int32_t MAX_PEER_TX_IN_FLIGHT = 100;
/** Maximum number of announced transactions from a peer */
static constexpr int32_t MAX_PEER_TX_ANNOUNCEMENTS = 2 * MAX_INV_SZ;
/** How many microseconds to delay requesting transactions from inbound peers */
static constexpr std::chrono::microseconds INBOUND_PEER_TX_DELAY{
std::chrono::seconds{2}};
/**
* How long to wait (in microseconds) before downloading a transaction from an
* additional peer.
*/
static constexpr std::chrono::microseconds GETDATA_TX_INTERVAL{
std::chrono::seconds{60}};
/**
* Maximum delay (in microseconds) for transaction requests to avoid biasing
* some peers over others.
*/
static constexpr std::chrono::microseconds MAX_GETDATA_RANDOM_DELAY{
std::chrono::seconds{2}};
/**
* How long to wait (in microseconds) before expiring an in-flight getdata
* request to a peer.
*/
static constexpr std::chrono::microseconds TX_EXPIRY_INTERVAL{
GETDATA_TX_INTERVAL * 10};
static_assert(INBOUND_PEER_TX_DELAY >= MAX_GETDATA_RANDOM_DELAY,
"To preserve security, MAX_GETDATA_RANDOM_DELAY should not "
"exceed INBOUND_PEER_DELAY");
/**
* Limit to avoid sending big packets. Not used in processing incoming GETDATA
* for compatibility.
*/
static const unsigned int MAX_GETDATA_SZ = 1000;
/// How many non standard orphan do we consider from a node before ignoring it.
static constexpr uint32_t MAX_NON_STANDARD_ORPHAN_PER_NODE = 5;
struct COrphanTx {
// When modifying, adapt the copy of this definition in tests/DoS_tests.
CTransactionRef tx;
NodeId fromPeer;
int64_t nTimeExpire;
size_t list_pos;
};
RecursiveMutex g_cs_orphans;
std::map<TxId, COrphanTx> mapOrphanTransactions GUARDED_BY(g_cs_orphans);
void EraseOrphansFor(NodeId peer);
/**
* Average delay between local address broadcasts.
*/
static constexpr std::chrono::hours AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL{24};
/**
* Average delay between peer address broadcasts.
*/
static const std::chrono::seconds AVG_ADDRESS_BROADCAST_INTERVAL{30};
/**
* Average delay between trickled inventory transmissions in seconds.
* Blocks and whitelisted receivers bypass this, outbound peers get half this
* delay.
*/
static const unsigned int INVENTORY_BROADCAST_INTERVAL = 5;
/**
* Maximum number of inventory items to send per transmission.
* Limits the impact of low-fee transaction floods.
*/
static constexpr unsigned int INVENTORY_BROADCAST_MAX_PER_MB =
7 * INVENTORY_BROADCAST_INTERVAL;
/**
* Average delay between feefilter broadcasts in seconds.
*/
static constexpr unsigned int AVG_FEEFILTER_BROADCAST_INTERVAL = 10 * 60;
/**
* Maximum feefilter broadcast delay after significant change.
*/
static constexpr unsigned int MAX_FEEFILTER_CHANGE_DELAY = 5 * 60;
/**
* Maximum number of compact filters that may be requested with one
* getcfilters. See BIP 157.
*/
static constexpr uint32_t MAX_GETCFILTERS_SIZE = 1000;
/**
* Maximum number of cf hashes that may be requested with one getcfheaders. See
* BIP 157.
*/
static constexpr uint32_t MAX_GETCFHEADERS_SIZE = 2000;
// Internal stuff
namespace {
/** Number of nodes with fSyncStarted. */
int nSyncStarted GUARDED_BY(cs_main) = 0;
/**
* Sources of received blocks, saved to be able to punish them when processing
* happens afterwards.
* Set mapBlockSource[hash].second to false if the node should not be punished
* if the block is invalid.
*/
std::map<BlockHash, std::pair<NodeId, bool>> mapBlockSource GUARDED_BY(cs_main);
/**
* Filter for transactions that were recently rejected by AcceptToMemoryPool.
* These are not rerequested until the chain tip changes, at which point the
* entire filter is reset.
*
* Without this filter we'd be re-requesting txs from each of our peers,
* increasing bandwidth consumption considerably. For instance, with 100 peers,
* half of which relay a tx we don't accept, that might be a 50x bandwidth
* increase. A flooding attacker attempting to roll-over the filter using
* minimum-sized, 60byte, transactions might manage to send 1000/sec if we have
* fast peers, so we pick 120,000 to give our peers a two minute window to send
* invs to us.
*
* Decreasing the false positive rate is fairly cheap, so we pick one in a
* million to make it highly unlikely for users to have issues with this filter.
*
* Memory used: 1.3 MB
*/
std::unique_ptr<CRollingBloomFilter> recentRejects GUARDED_BY(cs_main);
uint256 hashRecentRejectsChainTip GUARDED_BY(cs_main);
/**
* Blocks that are in flight, and that are in the queue to be downloaded.
*/
struct QueuedBlock {
BlockHash hash;
//! Optional.
const CBlockIndex *pindex;
//! Whether this block has validated headers at the time of request.
bool fValidatedHeaders;
//! Optional, used for CMPCTBLOCK downloads
std::unique_ptr<PartiallyDownloadedBlock> partialBlock;
};
std::map<BlockHash, std::pair<NodeId, std::list<QueuedBlock>::iterator>>
mapBlocksInFlight GUARDED_BY(cs_main);
/** Stack of nodes which we have set to announce using compact blocks */
std::list<NodeId> lNodesAnnouncingHeaderAndIDs GUARDED_BY(cs_main);
/** Number of preferable block download peers. */
int nPreferredDownload GUARDED_BY(cs_main) = 0;
/** Number of peers from which we're downloading blocks. */
int nPeersWithValidatedDownloads GUARDED_BY(cs_main) = 0;
/** Number of outbound peers with m_chain_sync.m_protect. */
int g_outbound_peers_with_protect_from_disconnect GUARDED_BY(cs_main) = 0;
/** When our tip was last updated. */
std::atomic<int64_t> g_last_tip_update(0);
/** Relay map. */
typedef std::map<uint256, CTransactionRef> MapRelay;
MapRelay mapRelay GUARDED_BY(cs_main);
/**
* Expiration-time ordered list of (expire time, relay map entry) pairs,
* protected by cs_main).
*/
std::deque<std::pair<int64_t, MapRelay::iterator>>
vRelayExpiration GUARDED_BY(cs_main);
struct IteratorComparator {
template <typename I> bool operator()(const I &a, const I &b) const {
return &(*a) < &(*b);
}
};
std::map<COutPoint,
std::set<std::map<TxId, COrphanTx>::iterator, IteratorComparator>>
mapOrphanTransactionsByPrev GUARDED_BY(g_cs_orphans);
//! For random eviction
std::vector<std::map<TxId, COrphanTx>::iterator>
g_orphan_list GUARDED_BY(g_cs_orphans);
static size_t vExtraTxnForCompactIt GUARDED_BY(g_cs_orphans) = 0;
static std::vector<std::pair<TxHash, CTransactionRef>>
vExtraTxnForCompact GUARDED_BY(g_cs_orphans);
} // namespace
namespace {
/**
* Maintain validation-specific state about nodes, protected by cs_main, instead
* by CNode's own locks. This simplifies asynchronous operation, where
* processing of incoming data is done after the ProcessMessage call returns,
* and we're no longer holding the node's locks.
*/
struct CNodeState {
//! The peer's address
const CService address;
//! Whether we have a fully established connection.
bool fCurrentlyConnected;
//! Accumulated misbehaviour score for this peer.
int nMisbehavior;
//! Whether this peer should be disconnected and marked as discouraged
//! (unless whitelisted with noban).
bool m_should_discourage;
//! String name of this peer (debugging/logging purposes).
const std::string name;
//! The best known block we know this peer has announced.
const CBlockIndex *pindexBestKnownBlock;
//! The hash of the last unknown block this peer has announced.
BlockHash hashLastUnknownBlock;
//! The last full block we both have.
const CBlockIndex *pindexLastCommonBlock;
//! The best header we have sent our peer.
const CBlockIndex *pindexBestHeaderSent;
//! Length of current-streak of unconnecting headers announcements
int nUnconnectingHeaders;
//! Whether we've started headers synchronization with this peer.
bool fSyncStarted;
//! When to potentially disconnect peer for stalling headers download
int64_t nHeadersSyncTimeout;
//! Since when we're stalling block download progress (in microseconds), or
//! 0.
int64_t nStallingSince;
std::list<QueuedBlock> vBlocksInFlight;
//! When the first entry in vBlocksInFlight started downloading. Don't care
//! when vBlocksInFlight is empty.
int64_t nDownloadingSince;
int nBlocksInFlight;
int nBlocksInFlightValidHeaders;
//! Whether we consider this a preferred download peer.
bool fPreferredDownload;
//! Whether this peer wants invs or headers (when possible) for block
//! announcements.
bool fPreferHeaders;
//! Whether this peer wants invs or cmpctblocks (when possible) for block
//! announcements.
bool fPreferHeaderAndIDs;
/**
* Whether this peer will send us cmpctblocks if we request them.
* This is not used to gate request logic, as we really only care about
* fSupportsDesiredCmpctVersion, but is used as a flag to "lock in" the
* version of compact blocks we send.
*/
bool fProvidesHeaderAndIDs;
/**
* If we've announced NODE_WITNESS to this peer: whether the peer sends
* witnesses in cmpctblocks/blocktxns, otherwise: whether this peer sends
* non-witnesses in cmpctblocks/blocktxns.
*/
bool fSupportsDesiredCmpctVersion;
/**
* State used to enforce CHAIN_SYNC_TIMEOUT
* Only in effect for outbound, non-manual, full-relay connections, with
* m_protect == false
* Algorithm: if a peer's best known block has less work than our tip, set
* a timeout CHAIN_SYNC_TIMEOUT seconds in the future:
* - If at timeout their best known block now has more work than our tip
* when the timeout was set, then either reset the timeout or clear it
* (after comparing against our current tip's work)
* - If at timeout their best known block still has less work than our tip
* did when the timeout was set, then send a getheaders message, and set a
* shorter timeout, HEADERS_RESPONSE_TIME seconds in future. If their best
* known block is still behind when that new timeout is reached, disconnect.
*/
struct ChainSyncTimeoutState {
//! A timeout used for checking whether our peer has sufficiently
//! synced.
int64_t m_timeout;
//! A header with the work we require on our peer's chain.
const CBlockIndex *m_work_header;
//! After timeout is reached, set to true after sending getheaders.
bool m_sent_getheaders;
//! Whether this peer is protected from disconnection due to a bad/slow
//! chain.
bool m_protect;
};
ChainSyncTimeoutState m_chain_sync;
//! Time of last new block announcement
int64_t m_last_block_announcement;
/*
* State associated with transaction download.
*
* Tx download algorithm:
*
* When inv comes in, queue up (process_time, txid) inside the peer's
* CNodeState (m_tx_process_time) as long as m_tx_announced for the peer
* isn't too big (MAX_PEER_TX_ANNOUNCEMENTS).
*
* The process_time for a transaction is set to nNow for outbound peers,
* nNow + 2 seconds for inbound peers. This is the time at which we'll
* consider trying to request the transaction from the peer in
* SendMessages(). The delay for inbound peers is to allow outbound peers
* a chance to announce before we request from inbound peers, to prevent
* an adversary from using inbound connections to blind us to a
* transaction (InvBlock).
*
* When we call SendMessages() for a given peer,
* we will loop over the transactions in m_tx_process_time, looking
* at the transactions whose process_time <= nNow. We'll request each
* such transaction that we don't have already and that hasn't been
* requested from another peer recently, up until we hit the
* MAX_PEER_TX_IN_FLIGHT limit for the peer. Then we'll update
* g_already_asked_for for each requested txid, storing the time of the
* GETDATA request. We use g_already_asked_for to coordinate transaction
* requests amongst our peers.
*
* For transactions that we still need but we have already recently
* requested from some other peer, we'll reinsert (process_time, txid)
* back into the peer's m_tx_process_time at the point in the future at
* which the most recent GETDATA request would time out (ie
* GETDATA_TX_INTERVAL + the request time stored in g_already_asked_for).
* We add an additional delay for inbound peers, again to prefer
* attempting download from outbound peers first.
* We also add an extra small random delay up to 2 seconds
* to avoid biasing some peers over others. (e.g., due to fixed ordering
* of peer processing in ThreadMessageHandler).
*
* When we receive a transaction from a peer, we remove the txid from the
* peer's m_tx_in_flight set and from their recently announced set
* (m_tx_announced). We also clear g_already_asked_for for that entry, so
* that if somehow the transaction is not accepted but also not added to
* the reject filter, then we will eventually redownload from other
* peers.
*/
struct TxDownloadState {
/**
* Track when to attempt download of announced transactions (process
* time in micros -> txid)
*/
std::multimap<std::chrono::microseconds, TxId> m_tx_process_time;
//! Store all the transactions a peer has recently announced
std::set<TxId> m_tx_announced;
//! Store transactions which were requested by us, with timestamp
std::map<TxId, std::chrono::microseconds> m_tx_in_flight;
//! Periodically check for stuck getdata requests
std::chrono::microseconds m_check_expiry_timer{0};
};
TxDownloadState m_tx_download;
struct AvalancheState {
std::chrono::time_point<std::chrono::steady_clock> last_poll;
};
AvalancheState m_avalanche_state;
//! Whether this peer is an inbound connection
bool m_is_inbound;
//! Whether this peer is a manual connection
bool m_is_manual_connection;
CNodeState(CAddress addrIn, std::string addrNameIn, bool is_inbound,
bool is_manual)
: address(addrIn), name(std::move(addrNameIn)),
m_is_inbound(is_inbound), m_is_manual_connection(is_manual) {
fCurrentlyConnected = false;
nMisbehavior = 0;
m_should_discourage = false;
pindexBestKnownBlock = nullptr;
hashLastUnknownBlock = BlockHash();
pindexLastCommonBlock = nullptr;
pindexBestHeaderSent = nullptr;
nUnconnectingHeaders = 0;
fSyncStarted = false;
nHeadersSyncTimeout = 0;
nStallingSince = 0;
nDownloadingSince = 0;
nBlocksInFlight = 0;
nBlocksInFlightValidHeaders = 0;
fPreferredDownload = false;
fPreferHeaders = false;
fPreferHeaderAndIDs = false;
fProvidesHeaderAndIDs = false;
fSupportsDesiredCmpctVersion = false;
m_chain_sync = {0, nullptr, false, false};
m_last_block_announcement = 0;
}
};
// Keeps track of the time (in microseconds) when transactions were requested
// last time
limitedmap<TxId, std::chrono::microseconds>
g_already_asked_for GUARDED_BY(cs_main)(MAX_INV_SZ);
/** Map maintaining per-node state. */
static std::map<NodeId, CNodeState> mapNodeState GUARDED_BY(cs_main);
static CNodeState *State(NodeId pnode) EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
std::map<NodeId, CNodeState>::iterator it = mapNodeState.find(pnode);
if (it == mapNodeState.end()) {
return nullptr;
}
return &it->second;
}
static void UpdatePreferredDownload(const CNode &node, CNodeState *state)
EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
nPreferredDownload -= state->fPreferredDownload;
// Whether this node should be marked as a preferred download node.
state->fPreferredDownload =
(!node.fInbound || node.HasPermission(PF_NOBAN)) && !node.fOneShot &&
!node.fClient;
nPreferredDownload += state->fPreferredDownload;
}
static void PushNodeVersion(const Config &config, CNode &pnode,
CConnman &connman, int64_t nTime) {
// Note that pnode.GetLocalServices() is a reflection of the local
// services we were offering when the CNode object was created for this
// peer.
ServiceFlags nLocalNodeServices = pnode.GetLocalServices();
uint64_t nonce = pnode.GetLocalNonce();
int nNodeStartingHeight = pnode.GetMyStartingHeight();
NodeId nodeid = pnode.GetId();
CAddress addr = pnode.addr;
CAddress addrYou = (addr.IsRoutable() && !IsProxy(addr)
? addr
: CAddress(CService(), addr.nServices));
CAddress addrMe = CAddress(CService(), nLocalNodeServices);
connman.PushMessage(
&pnode, CNetMsgMaker(INIT_PROTO_VERSION)
.Make(NetMsgType::VERSION, PROTOCOL_VERSION,
uint64_t(nLocalNodeServices), nTime, addrYou, addrMe,
nonce, userAgent(config), nNodeStartingHeight,
::g_relay_txes && pnode.m_tx_relay != nullptr));
if (fLogIPs) {
LogPrint(BCLog::NET,
"send version message: version %d, blocks=%d, us=%s, them=%s, "
"peer=%d\n",
PROTOCOL_VERSION, nNodeStartingHeight, addrMe.ToString(),
addrYou.ToString(), nodeid);
} else {
LogPrint(
BCLog::NET,
"send version message: version %d, blocks=%d, us=%s, peer=%d\n",
PROTOCOL_VERSION, nNodeStartingHeight, addrMe.ToString(), nodeid);
}
LogPrint(BCLog::NET, "Cleared nodestate for peer=%d\n", nodeid);
}
// Returns a bool indicating whether we requested this block.
// Also used if a block was /not/ received and timed out or started with another
// peer.
static bool MarkBlockAsReceived(const BlockHash &hash)
EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
std::map<BlockHash,
std::pair<NodeId, std::list<QueuedBlock>::iterator>>::iterator
itInFlight = mapBlocksInFlight.find(hash);
if (itInFlight != mapBlocksInFlight.end()) {
CNodeState *state = State(itInFlight->second.first);
assert(state != nullptr);
state->nBlocksInFlightValidHeaders -=
itInFlight->second.second->fValidatedHeaders;
if (state->nBlocksInFlightValidHeaders == 0 &&
itInFlight->second.second->fValidatedHeaders) {
// Last validated block on the queue was received.
nPeersWithValidatedDownloads--;
}
if (state->vBlocksInFlight.begin() == itInFlight->second.second) {
// 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);
state->nBlocksInFlight--;
state->nStallingSince = 0;
mapBlocksInFlight.erase(itInFlight);
return true;
}
return false;
}
// 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.
static bool
MarkBlockAsInFlight(const Config &config, NodeId nodeid, const BlockHash &hash,
const Consensus::Params &consensusParams,
const CBlockIndex *pindex = nullptr,
std::list<QueuedBlock>::iterator **pit = nullptr)
EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
CNodeState *state = State(nodeid);
assert(state != nullptr);
// Short-circuit most stuff in case it is from the same node.
std::map<BlockHash,
std::pair<NodeId, std::list<QueuedBlock>::iterator>>::iterator
itInFlight = mapBlocksInFlight.find(hash);
if (itInFlight != mapBlocksInFlight.end() &&
itInFlight->second.first == nodeid) {
if (pit) {
*pit = &itInFlight->second.second;
}
return false;
}
// Make sure it's not listed somewhere already.
MarkBlockAsReceived(hash);
std::list<QueuedBlock>::iterator it = state->vBlocksInFlight.insert(
state->vBlocksInFlight.end(),
{hash, pindex, pindex != nullptr,
std::unique_ptr<PartiallyDownloadedBlock>(
pit ? new PartiallyDownloadedBlock(config, &g_mempool)
: nullptr)});
state->nBlocksInFlight++;
state->nBlocksInFlightValidHeaders += it->fValidatedHeaders;
if (state->nBlocksInFlight == 1) {
// We're starting a block download (batch) from this peer.
state->nDownloadingSince = GetTimeMicros();
}
if (state->nBlocksInFlightValidHeaders == 1 && pindex != nullptr) {
nPeersWithValidatedDownloads++;
}
itInFlight = mapBlocksInFlight
.insert(std::make_pair(hash, std::make_pair(nodeid, it)))
.first;
if (pit) {
*pit = &itInFlight->second.second;
}
return true;
}
/** Check whether the last unknown block a peer advertised is not yet known. */
static void ProcessBlockAvailability(NodeId nodeid)
EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
CNodeState *state = State(nodeid);
assert(state != nullptr);
if (!state->hashLastUnknownBlock.IsNull()) {
const CBlockIndex *pindex =
LookupBlockIndex(state->hashLastUnknownBlock);
if (pindex && pindex->nChainWork > 0) {
if (state->pindexBestKnownBlock == nullptr ||
pindex->nChainWork >= state->pindexBestKnownBlock->nChainWork) {
state->pindexBestKnownBlock = pindex;
}
state->hashLastUnknownBlock.SetNull();
}
}
}
/** Update tracking information about which blocks a peer is assumed to have. */
static void UpdateBlockAvailability(NodeId nodeid, const BlockHash &hash)
EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
CNodeState *state = State(nodeid);
assert(state != nullptr);
ProcessBlockAvailability(nodeid);
const CBlockIndex *pindex = LookupBlockIndex(hash);
if (pindex && pindex->nChainWork > 0) {
// An actually better block was announced.
if (state->pindexBestKnownBlock == nullptr ||
pindex->nChainWork >= state->pindexBestKnownBlock->nChainWork) {
state->pindexBestKnownBlock = pindex;
}
} else {
// An unknown block was announced; just assume that the latest one is
// the best one.
state->hashLastUnknownBlock = hash;
}
}
/**
* When a peer sends us a valid block, instruct it to announce blocks to us
* using CMPCTBLOCK if possible by adding its nodeid to the end of
* lNodesAnnouncingHeaderAndIDs, and keeping that list under a certain size by
* removing the first element if necessary.
*/
static void MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid,
CConnman &connman)
EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
AssertLockHeld(cs_main);
CNodeState *nodestate = State(nodeid);
if (!nodestate) {
LogPrint(BCLog::NET, "node state unavailable: peer=%d\n", nodeid);
return;
}
if (!nodestate->fProvidesHeaderAndIDs) {
return;
}
for (std::list<NodeId>::iterator it = lNodesAnnouncingHeaderAndIDs.begin();
it != lNodesAnnouncingHeaderAndIDs.end(); it++) {
if (*it == nodeid) {
lNodesAnnouncingHeaderAndIDs.erase(it);
lNodesAnnouncingHeaderAndIDs.push_back(nodeid);
return;
}
}
connman.ForNode(nodeid, [&connman](CNode *pfrom) {
AssertLockHeld(cs_main);
uint64_t nCMPCTBLOCKVersion = 1;
if (lNodesAnnouncingHeaderAndIDs.size() >= 3) {
// As per BIP152, we only get 3 of our peers to announce
// blocks using compact encodings.
connman.ForNode(
lNodesAnnouncingHeaderAndIDs.front(),
[&connman, nCMPCTBLOCKVersion](CNode *pnodeStop) {
AssertLockHeld(cs_main);
connman.PushMessage(
pnodeStop, CNetMsgMaker(pnodeStop->GetSendVersion())
.Make(NetMsgType::SENDCMPCT,
/*fAnnounceUsingCMPCTBLOCK=*/false,
nCMPCTBLOCKVersion));
return true;
});
lNodesAnnouncingHeaderAndIDs.pop_front();
}
connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion())
.Make(NetMsgType::SENDCMPCT,
/*fAnnounceUsingCMPCTBLOCK=*/true,
nCMPCTBLOCKVersion));
lNodesAnnouncingHeaderAndIDs.push_back(pfrom->GetId());
return true;
});
}
static bool TipMayBeStale(const Consensus::Params &consensusParams)
EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
AssertLockHeld(cs_main);
if (g_last_tip_update == 0) {
g_last_tip_update = GetTime();
}
return g_last_tip_update <
GetTime() - consensusParams.nPowTargetSpacing * 3 &&
mapBlocksInFlight.empty();
}
static bool CanDirectFetch(const Consensus::Params &consensusParams)
EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
return ::ChainActive().Tip()->GetBlockTime() >
GetAdjustedTime() - consensusParams.nPowTargetSpacing * 20;
}
static bool PeerHasHeader(CNodeState *state, const CBlockIndex *pindex)
EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
if (state->pindexBestKnownBlock &&
pindex == state->pindexBestKnownBlock->GetAncestor(pindex->nHeight)) {
return true;
}
if (state->pindexBestHeaderSent &&
pindex == state->pindexBestHeaderSent->GetAncestor(pindex->nHeight)) {
return true;
}
return false;
}
/**
* Update pindexLastCommonBlock and add not-in-flight missing successors to
* vBlocks, until it has at most count entries.
*/
static void FindNextBlocksToDownload(NodeId nodeid, unsigned int count,
std::vector<const CBlockIndex *> &vBlocks,
NodeId &nodeStaller,
const Consensus::Params &consensusParams)
EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
if (count == 0) {
return;
}
vBlocks.reserve(vBlocks.size() + count);
CNodeState *state = State(nodeid);
assert(state != nullptr);
// Make sure pindexBestKnownBlock is up to date, we'll need it.
ProcessBlockAvailability(nodeid);
if (state->pindexBestKnownBlock == nullptr ||
state->pindexBestKnownBlock->nChainWork <
::ChainActive().Tip()->nChainWork ||
state->pindexBestKnownBlock->nChainWork < nMinimumChainWork) {
// This peer has nothing interesting.
return;
}
if (state->pindexLastCommonBlock == nullptr) {
// Bootstrap quickly by guessing a parent of our best tip is the forking
// point. Guessing wrong in either direction is not a problem.
state->pindexLastCommonBlock = ::ChainActive()[std::min(
state->pindexBestKnownBlock->nHeight, ::ChainActive().Height())];
}
// If the peer reorganized, our previous pindexLastCommonBlock may not be an
// ancestor of its current tip anymore. Go back enough to fix that.
state->pindexLastCommonBlock = LastCommonAncestor(
state->pindexLastCommonBlock, state->pindexBestKnownBlock);
if (state->pindexLastCommonBlock == state->pindexBestKnownBlock) {
return;
}
std::vector<const CBlockIndex *> vToFetch;
const CBlockIndex *pindexWalk = state->pindexLastCommonBlock;
// Never fetch further than the best block we know the peer has, or more
// than BLOCK_DOWNLOAD_WINDOW + 1 beyond the last linked block we have in
// common with this peer. The +1 is so we can detect stalling, namely if we
// would be able to download that next block if the window were 1 larger.
int nWindowEnd =
state->pindexLastCommonBlock->nHeight + BLOCK_DOWNLOAD_WINDOW;
int nMaxHeight =
std::min<int>(state->pindexBestKnownBlock->nHeight, nWindowEnd + 1);
NodeId waitingfor = -1;
while (pindexWalk->nHeight < nMaxHeight) {
// Read up to 128 (or more, if more blocks than that are needed)
// successors of pindexWalk (towards pindexBestKnownBlock) into
// vToFetch. We fetch 128, because CBlockIndex::GetAncestor may be as
// expensive as iterating over ~100 CBlockIndex* entries anyway.
int nToFetch = std::min(nMaxHeight - pindexWalk->nHeight,
std::max<int>(count - vBlocks.size(), 128));
vToFetch.resize(nToFetch);
pindexWalk = state->pindexBestKnownBlock->GetAncestor(
pindexWalk->nHeight + nToFetch);
vToFetch[nToFetch - 1] = pindexWalk;
for (unsigned int i = nToFetch - 1; i > 0; i--) {
vToFetch[i - 1] = vToFetch[i]->pprev;
}
// Iterate over those blocks in vToFetch (in forward direction), adding
// the ones that are not yet downloaded and not in flight to vBlocks. In
// the meantime, update pindexLastCommonBlock as long as all ancestors
// are already downloaded, or if it's already part of our chain (and
// therefore don't need it even if pruned).
for (const CBlockIndex *pindex : vToFetch) {
if (!pindex->IsValid(BlockValidity::TREE)) {
// We consider the chain that this peer is on invalid.
return;
}
if (pindex->nStatus.hasData() || ::ChainActive().Contains(pindex)) {
if (pindex->HaveTxsDownloaded()) {
state->pindexLastCommonBlock = pindex;
}
} else if (mapBlocksInFlight.count(pindex->GetBlockHash()) == 0) {
// The block is not already downloaded, and not yet in flight.
if (pindex->nHeight > nWindowEnd) {
// We reached the end of the window.
if (vBlocks.size() == 0 && waitingfor != nodeid) {
// We aren't able to fetch anything, but we would be if
// the download window was one larger.
nodeStaller = waitingfor;
}
return;
}
vBlocks.push_back(pindex);
if (vBlocks.size() == count) {
return;
}
} else if (waitingfor == -1) {
// This is the first already-in-flight block.
waitingfor = mapBlocksInFlight[pindex->GetBlockHash()].first;
}
}
}
}
void EraseTxRequest(const TxId &txid) EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
g_already_asked_for.erase(txid);
}
std::chrono::microseconds GetTxRequestTime(const TxId &txid)
EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
auto it = g_already_asked_for.find(txid);
if (it != g_already_asked_for.end()) {
return it->second;
}
return {};
}
void UpdateTxRequestTime(const TxId &txid,
std::chrono::microseconds request_time)
EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
auto it = g_already_asked_for.find(txid);
if (it == g_already_asked_for.end()) {
g_already_asked_for.insert(std::make_pair(txid, request_time));
} else {
g_already_asked_for.update(it, request_time);
}
}
std::chrono::microseconds
CalculateTxGetDataTime(const TxId &txid, std::chrono::microseconds current_time,
bool use_inbound_delay)
EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
std::chrono::microseconds process_time;
const auto last_request_time = GetTxRequestTime(txid);
// First time requesting this tx
if (last_request_time.count() == 0) {
process_time = current_time;
} else {
// Randomize the delay to avoid biasing some peers over others (such as
// due to fixed ordering of peer processing in ThreadMessageHandler)
process_time = last_request_time + GETDATA_TX_INTERVAL +
GetRandMicros(MAX_GETDATA_RANDOM_DELAY);
}
// We delay processing announcements from inbound peers
if (use_inbound_delay) {
process_time += INBOUND_PEER_TX_DELAY;
}
return process_time;
}
void RequestTx(CNodeState *state, const TxId &txid,
std::chrono::microseconds current_time)
EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
CNodeState::TxDownloadState &peer_download_state = state->m_tx_download;
if (peer_download_state.m_tx_announced.size() >=
MAX_PEER_TX_ANNOUNCEMENTS ||
peer_download_state.m_tx_process_time.size() >=
MAX_PEER_TX_ANNOUNCEMENTS ||
peer_download_state.m_tx_announced.count(txid)) {
// Too many queued announcements from this peer, or we already have
// this announcement
return;
}
peer_download_state.m_tx_announced.insert(txid);
// Calculate the time to try requesting this transaction. Use
// fPreferredDownload as a proxy for outbound peers.
const auto process_time =
CalculateTxGetDataTime(txid, current_time, !state->fPreferredDownload);
peer_download_state.m_tx_process_time.emplace(process_time, txid);
}
} // namespace
// This function is used for testing the stale tip eviction logic, see
// denialofservice_tests.cpp
void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds) {
LOCK(cs_main);
CNodeState *state = State(node);
if (state) {
state->m_last_block_announcement = time_in_seconds;
}
}
// Returns true for outbound peers, excluding manual connections, feelers, and
// one-shots.
static bool IsOutboundDisconnectionCandidate(const CNode &node) {
return !(node.fInbound || node.m_manual_connection || node.fFeeler ||
node.fOneShot);
}
void PeerLogicValidation::InitializeNode(const Config &config, CNode *pnode) {
CAddress addr = pnode->addr;
std::string addrName = pnode->GetAddrName();
NodeId nodeid = pnode->GetId();
{
LOCK(cs_main);
mapNodeState.emplace_hint(
mapNodeState.end(), std::piecewise_construct,
std::forward_as_tuple(nodeid),
std::forward_as_tuple(addr, std::move(addrName), pnode->fInbound,
pnode->m_manual_connection));
}
if (!pnode->fInbound) {
PushNodeVersion(config, *pnode, *connman, GetTime());
}
}
void PeerLogicValidation::FinalizeNode(const Config &config, NodeId nodeid,
bool &fUpdateConnectionTime) {
fUpdateConnectionTime = false;
LOCK(cs_main);
CNodeState *state = State(nodeid);
assert(state != nullptr);
if (state->fSyncStarted) {
nSyncStarted--;
}
if (state->nMisbehavior == 0 && state->fCurrentlyConnected) {
fUpdateConnectionTime = true;
}
for (const QueuedBlock &entry : state->vBlocksInFlight) {
mapBlocksInFlight.erase(entry.hash);
}
EraseOrphansFor(nodeid);
nPreferredDownload -= state->fPreferredDownload;
nPeersWithValidatedDownloads -= (state->nBlocksInFlightValidHeaders != 0);
assert(nPeersWithValidatedDownloads >= 0);
g_outbound_peers_with_protect_from_disconnect -=
state->m_chain_sync.m_protect;
assert(g_outbound_peers_with_protect_from_disconnect >= 0);
mapNodeState.erase(nodeid);
if (mapNodeState.empty()) {
// Do a consistency check after the last peer is removed.
assert(mapBlocksInFlight.empty());
assert(nPreferredDownload == 0);
assert(nPeersWithValidatedDownloads == 0);
assert(g_outbound_peers_with_protect_from_disconnect == 0);
}
LogPrint(BCLog::NET, "Cleared nodestate for peer=%d\n", nodeid);
}
bool GetNodeStateStats(NodeId nodeid, CNodeStateStats &stats) {
LOCK(cs_main);
CNodeState *state = State(nodeid);
if (state == nullptr) {
return false;
}
stats.nMisbehavior = state->nMisbehavior;
stats.nSyncHeight =
state->pindexBestKnownBlock ? state->pindexBestKnownBlock->nHeight : -1;
stats.nCommonHeight = state->pindexLastCommonBlock
? state->pindexLastCommonBlock->nHeight
: -1;
for (const QueuedBlock &queue : state->vBlocksInFlight) {
if (queue.pindex) {
stats.vHeightInFlight.push_back(queue.pindex->nHeight);
}
}
return true;
}
//////////////////////////////////////////////////////////////////////////////
//
// mapOrphanTransactions
//
static void AddToCompactExtraTransactions(const CTransactionRef &tx)
EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) {
size_t max_extra_txn = gArgs.GetArg("-blockreconstructionextratxn",
DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN);
if (max_extra_txn <= 0) {
return;
}
if (!vExtraTxnForCompact.size()) {
vExtraTxnForCompact.resize(max_extra_txn);
}
vExtraTxnForCompact[vExtraTxnForCompactIt] =
std::make_pair(tx->GetHash(), tx);
vExtraTxnForCompactIt = (vExtraTxnForCompactIt + 1) % max_extra_txn;
}
bool AddOrphanTx(const CTransactionRef &tx, NodeId peer)
EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) {
const TxId &txid = tx->GetId();
if (mapOrphanTransactions.count(txid)) {
return false;
}
// Ignore big transactions, to avoid a send-big-orphans memory exhaustion
// attack. If a peer has a legitimate large transaction with a missing
// parent then we assume it will rebroadcast it later, after the parent
// transaction(s) have been mined or received.
// 100 orphans, each of which is at most 100,000 bytes big is at most 10
// megabytes of orphans and somewhat more byprev index (in the worst case):
unsigned int sz = tx->GetTotalSize();
if (sz > MAX_STANDARD_TX_SIZE) {
LogPrint(BCLog::MEMPOOL,
"ignoring large orphan tx (size: %u, hash: %s)\n", sz,
txid.ToString());
return false;
}
auto ret = mapOrphanTransactions.emplace(
txid, COrphanTx{tx, peer, GetTime() + ORPHAN_TX_EXPIRE_TIME,
g_orphan_list.size()});
assert(ret.second);
g_orphan_list.push_back(ret.first);
for (const CTxIn &txin : tx->vin) {
mapOrphanTransactionsByPrev[txin.prevout].insert(ret.first);
}
AddToCompactExtraTransactions(tx);
LogPrint(BCLog::MEMPOOL, "stored orphan tx %s (mapsz %u outsz %u)\n",
txid.ToString(), mapOrphanTransactions.size(),
mapOrphanTransactionsByPrev.size());
return true;
}
static int EraseOrphanTx(const TxId id) EXCLUSIVE_LOCKS_REQUIRED(g_cs_orphans) {
const auto it = mapOrphanTransactions.find(id);
if (it == mapOrphanTransactions.end()) {
return 0;
}
for (const CTxIn &txin : it->second.tx->vin) {
const auto itPrev = mapOrphanTransactionsByPrev.find(txin.prevout);
if (itPrev == mapOrphanTransactionsByPrev.end()) {
continue;
}
itPrev->second.erase(it);
if (itPrev->second.empty()) {
mapOrphanTransactionsByPrev.erase(itPrev);
}
}
size_t old_pos = it->second.list_pos;
assert(g_orphan_list[old_pos] == it);
if (old_pos + 1 != g_orphan_list.size()) {
// Unless we're deleting the last entry in g_orphan_list, move the last
// entry to the position we're deleting.
auto it_last = g_orphan_list.back();
g_orphan_list[old_pos] = it_last;
it_last->second.list_pos = old_pos;
}
g_orphan_list.pop_back();
mapOrphanTransactions.erase(it);
return 1;
}
void EraseOrphansFor(NodeId peer) {
LOCK(g_cs_orphans);
int nErased = 0;
auto iter = mapOrphanTransactions.begin();
while (iter != mapOrphanTransactions.end()) {
// Increment to avoid iterator becoming invalid.
const auto maybeErase = iter++;
if (maybeErase->second.fromPeer == peer) {
nErased += EraseOrphanTx(maybeErase->second.tx->GetId());
}
}
if (nErased > 0) {
LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx from peer=%d\n", nErased,
peer);
}
}
unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans) {
LOCK(g_cs_orphans);
unsigned int nEvicted = 0;
static int64_t nNextSweep;
int64_t nNow = GetTime();
if (nNextSweep <= nNow) {
// Sweep out expired orphan pool entries:
int nErased = 0;
int64_t nMinExpTime =
nNow + ORPHAN_TX_EXPIRE_TIME - ORPHAN_TX_EXPIRE_INTERVAL;
auto iter = mapOrphanTransactions.begin();
while (iter != mapOrphanTransactions.end()) {
const auto maybeErase = iter++;
if (maybeErase->second.nTimeExpire <= nNow) {
nErased += EraseOrphanTx(maybeErase->second.tx->GetId());
} else {
nMinExpTime =
std::min(maybeErase->second.nTimeExpire, nMinExpTime);
}
}
// Sweep again 5 minutes after the next entry that expires in order to
// batch the linear scan.
nNextSweep = nMinExpTime + ORPHAN_TX_EXPIRE_INTERVAL;
if (nErased > 0) {
LogPrint(BCLog::MEMPOOL, "Erased %d orphan tx due to expiration\n",
nErased);
}
}
FastRandomContext rng;
while (mapOrphanTransactions.size() > nMaxOrphans) {
// Evict a random orphan:
size_t randompos = rng.randrange(g_orphan_list.size());
EraseOrphanTx(g_orphan_list[randompos]->first);
++nEvicted;
}
return nEvicted;
}
/**
* Mark a misbehaving peer to be banned depending upon the value of `-banscore`.
*/
void Misbehaving(NodeId pnode, int howmuch, const std::string &reason)
EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
if (howmuch == 0) {
return;
}
CNodeState *state = State(pnode);
if (state == nullptr) {
return;
}
state->nMisbehavior += howmuch;
int banscore = gArgs.GetArg("-banscore", DEFAULT_BANSCORE_THRESHOLD);
if (state->nMisbehavior >= banscore &&
state->nMisbehavior - howmuch < banscore) {
LogPrintf(
"%s: %s peer=%d (%d -> %d) reason: %s BAN THRESHOLD EXCEEDED\n",
__func__, state->name, pnode, state->nMisbehavior - howmuch,
state->nMisbehavior, reason);
state->m_should_discourage = true;
} else {
LogPrintf("%s: %s peer=%d (%d -> %d) reason: %s\n", __func__,
state->name, pnode, state->nMisbehavior - howmuch,
state->nMisbehavior, reason);
}
}
// overloaded variant of above to operate on CNode*s
static void Misbehaving(const CNode &node, int howmuch,
const std::string &reason)
EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
Misbehaving(node.GetId(), howmuch, reason);
}
/**
* Returns true if the given validation state result may result in a peer
* banning/disconnecting us. We use this to determine which unaccepted
* transactions from a whitelisted peer that we can safely relay.
*/
static bool TxRelayMayResultInDisconnect(const TxValidationState &state) {
return state.GetResult() == TxValidationResult::TX_CONSENSUS;
}
/**
* Potentially ban a node based on the contents of a BlockValidationState object
*
* @param[in] via_compact_block: this bool is passed in because net_processing
* should punish peers differently depending on whether the data was provided in
* a compact block message or not. If the compact block had a valid header, but
* contained invalid txs, the peer should not be punished. See BIP 152.
*
* @return Returns true if the peer was punished (probably disconnected)
*/
static bool MaybePunishNodeForBlock(NodeId nodeid,
const BlockValidationState &state,
bool via_compact_block,
const std::string &message = "") {
switch (state.GetResult()) {
case BlockValidationResult::BLOCK_RESULT_UNSET:
break;
// The node is providing invalid data:
case BlockValidationResult::BLOCK_CONSENSUS:
case BlockValidationResult::BLOCK_MUTATED:
if (!via_compact_block) {
LOCK(cs_main);
Misbehaving(nodeid, 100, message);
return true;
}
break;
case BlockValidationResult::BLOCK_CACHED_INVALID: {
LOCK(cs_main);
CNodeState *node_state = State(nodeid);
if (node_state == nullptr) {
break;
}
// Ban outbound (but not inbound) peers if on an invalid chain.
// Exempt HB compact block peers and manual connections.
if (!via_compact_block && !node_state->m_is_inbound &&
!node_state->m_is_manual_connection) {
Misbehaving(nodeid, 100, message);
return true;
}
break;
}
case BlockValidationResult::BLOCK_INVALID_HEADER:
case BlockValidationResult::BLOCK_CHECKPOINT:
case BlockValidationResult::BLOCK_INVALID_PREV: {
LOCK(cs_main);
Misbehaving(nodeid, 100, message);
}
return true;
case BlockValidationResult::BLOCK_FINALIZATION: {
// TODO: Use the state object to report this is probably not the
// best idea. This is effectively unreachable, unless there is a bug
// somewhere.
LOCK(cs_main);
Misbehaving(nodeid, 20, message);
}
return true;
// Conflicting (but not necessarily invalid) data or different policy:
case BlockValidationResult::BLOCK_MISSING_PREV: {
// TODO: Handle this much more gracefully (10 DoS points is super
// arbitrary)
LOCK(cs_main);
Misbehaving(nodeid, 10, message);
}
return true;
case BlockValidationResult::BLOCK_RECENT_CONSENSUS_CHANGE:
case BlockValidationResult::BLOCK_TIME_FUTURE:
break;
}
if (message != "") {
LogPrint(BCLog::NET, "peer=%d: %s\n", nodeid, message);
}
return false;
}
/**
* Potentially ban a node based on the contents of a TxValidationState object
*
* @return Returns true if the peer was punished (probably disconnected)
*
* Changes here may need to be reflected in TxRelayMayResultInDisconnect().
*/
static bool MaybePunishNodeForTx(NodeId nodeid, const TxValidationState &state,
const std::string &message = "") {
switch (state.GetResult()) {
case TxValidationResult::TX_RESULT_UNSET:
break;
// The node is providing invalid data:
case TxValidationResult::TX_CONSENSUS: {
LOCK(cs_main);
Misbehaving(nodeid, 100, message);
return true;
}
// Conflicting (but not necessarily invalid) data or different policy:
case TxValidationResult::TX_RECENT_CONSENSUS_CHANGE:
case TxValidationResult::TX_NOT_STANDARD:
case TxValidationResult::TX_MISSING_INPUTS:
case TxValidationResult::TX_PREMATURE_SPEND:
case TxValidationResult::TX_CONFLICT:
case TxValidationResult::TX_MEMPOOL_POLICY:
break;
}
if (message != "") {
LogPrint(BCLog::NET, "peer=%d: %s\n", nodeid, message);
}
return false;
}
//////////////////////////////////////////////////////////////////////////////
//
// blockchain -> download logic notification
//
// To prevent fingerprinting attacks, only send blocks/headers outside of the
// active chain if they are no more than a month older (both in time, and in
// best equivalent proof of work) than the best header chain we know about and
// we fully-validated them at some point.
static bool BlockRequestAllowed(const CBlockIndex *pindex,
const Consensus::Params &consensusParams)
EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
AssertLockHeld(cs_main);
if (::ChainActive().Contains(pindex)) {
return true;
}
return pindex->IsValid(BlockValidity::SCRIPTS) &&
(pindexBestHeader != nullptr) &&
(pindexBestHeader->GetBlockTime() - pindex->GetBlockTime() <
STALE_RELAY_AGE_LIMIT) &&
(GetBlockProofEquivalentTime(*pindexBestHeader, *pindex,
*pindexBestHeader, consensusParams) <
STALE_RELAY_AGE_LIMIT);
}
PeerLogicValidation::PeerLogicValidation(CConnman *connmanIn, BanMan *banman,
- CScheduler &scheduler)
- : connman(connmanIn), m_banman(banman), m_stale_tip_check_time(0) {
+ CScheduler &scheduler,
+ ChainstateManager &chainman)
+ : connman(connmanIn), m_banman(banman), m_chainman(chainman),
+ m_stale_tip_check_time(0) {
// Initialize global variables that cannot be constructed at startup.
recentRejects.reset(new CRollingBloomFilter(120000, 0.000001));
const Consensus::Params &consensusParams = Params().GetConsensus();
// Stale tip checking and peer eviction are on two different timers, but we
// don't want them to get out of sync due to drift in the scheduler, so we
// combine them in one function and schedule at the quicker (peer-eviction)
// timer.
static_assert(
EXTRA_PEER_CHECK_INTERVAL < STALE_CHECK_INTERVAL,
"peer eviction timer should be less than stale tip check timer");
scheduler.scheduleEvery(
[this, &consensusParams]() {
this->CheckForStaleTipAndEvictPeers(consensusParams);
return true;
},
std::chrono::seconds{EXTRA_PEER_CHECK_INTERVAL});
}
/**
* Evict orphan txn pool entries (EraseOrphanTx) based on a newly connected
* block. Also save the time of the last tip update.
*/
void PeerLogicValidation::BlockConnected(
const std::shared_ptr<const CBlock> &pblock, const CBlockIndex *pindex,
const std::vector<CTransactionRef> &vtxConflicted) {
LOCK(g_cs_orphans);
std::vector<TxId> vOrphanErase;
for (const CTransactionRef &ptx : pblock->vtx) {
const CTransaction &tx = *ptx;
// Which orphan pool entries must we evict?
for (const auto &txin : tx.vin) {
auto itByPrev = mapOrphanTransactionsByPrev.find(txin.prevout);
if (itByPrev == mapOrphanTransactionsByPrev.end()) {
continue;
}
for (auto mi = itByPrev->second.begin();
mi != itByPrev->second.end(); ++mi) {
const CTransaction &orphanTx = *(*mi)->second.tx;
const TxId &orphanId = orphanTx.GetId();
vOrphanErase.push_back(orphanId);
}
}
}
// Erase orphan transactions included or precluded by this block
if (vOrphanErase.size()) {
int nErased = 0;
for (const auto &orphanId : vOrphanErase) {
nErased += EraseOrphanTx(orphanId);
}
LogPrint(BCLog::MEMPOOL,
"Erased %d orphan tx included or conflicted by block\n",
nErased);
}
g_last_tip_update = GetTime();
}
// All of the following cache a recent block, and are protected by
// cs_most_recent_block
static RecursiveMutex cs_most_recent_block;
static std::shared_ptr<const CBlock>
most_recent_block GUARDED_BY(cs_most_recent_block);
static std::shared_ptr<const CBlockHeaderAndShortTxIDs>
most_recent_compact_block GUARDED_BY(cs_most_recent_block);
static uint256 most_recent_block_hash GUARDED_BY(cs_most_recent_block);
/**
* Maintain state about the best-seen block and fast-announce a compact block
* to compatible peers.
*/
void PeerLogicValidation::NewPoWValidBlock(
const CBlockIndex *pindex, const std::shared_ptr<const CBlock> &pblock) {
std::shared_ptr<const CBlockHeaderAndShortTxIDs> pcmpctblock =
std::make_shared<const CBlockHeaderAndShortTxIDs>(*pblock);
const CNetMsgMaker msgMaker(PROTOCOL_VERSION);
LOCK(cs_main);
static int nHighestFastAnnounce = 0;
if (pindex->nHeight <= nHighestFastAnnounce) {
return;
}
nHighestFastAnnounce = pindex->nHeight;
uint256 hashBlock(pblock->GetHash());
{
LOCK(cs_most_recent_block);
most_recent_block_hash = hashBlock;
most_recent_block = pblock;
most_recent_compact_block = pcmpctblock;
}
connman->ForEachNode([this, &pcmpctblock, pindex, &msgMaker,
&hashBlock](CNode *pnode) {
AssertLockHeld(cs_main);
// TODO: Avoid the repeated-serialization here
if (pnode->nVersion < INVALID_CB_NO_BAN_VERSION || pnode->fDisconnect) {
return;
}
ProcessBlockAvailability(pnode->GetId());
CNodeState &state = *State(pnode->GetId());
// If the peer has, or we announced to them the previous block already,
// but we don't think they have this one, go ahead and announce it.
if (state.fPreferHeaderAndIDs && !PeerHasHeader(&state, pindex) &&
PeerHasHeader(&state, pindex->pprev)) {
LogPrint(BCLog::NET, "%s sending header-and-ids %s to peer=%d\n",
"PeerLogicValidation::NewPoWValidBlock",
hashBlock.ToString(), pnode->GetId());
connman->PushMessage(
pnode, msgMaker.Make(NetMsgType::CMPCTBLOCK, *pcmpctblock));
state.pindexBestHeaderSent = pindex;
}
});
}
/**
* Update our best height and announce any block hashes which weren't previously
* in ::ChainActive() to our peers.
*/
void PeerLogicValidation::UpdatedBlockTip(const CBlockIndex *pindexNew,
const CBlockIndex *pindexFork,
bool fInitialDownload) {
const int nNewHeight = pindexNew->nHeight;
connman->SetBestHeight(nNewHeight);
SetServiceFlagsIBDCache(!fInitialDownload);
if (!fInitialDownload) {
// Find the hashes of all blocks that weren't previously in the best
// chain.
std::vector<BlockHash> vHashes;
const CBlockIndex *pindexToAnnounce = pindexNew;
while (pindexToAnnounce != pindexFork) {
vHashes.push_back(pindexToAnnounce->GetBlockHash());
pindexToAnnounce = pindexToAnnounce->pprev;
if (vHashes.size() == MAX_BLOCKS_TO_ANNOUNCE) {
// Limit announcements in case of a huge reorganization. Rely on
// the peer's synchronization mechanism in that case.
break;
}
}
// Relay inventory, but don't relay old inventory during initial block
// download.
connman->ForEachNode([nNewHeight, &vHashes](CNode *pnode) {
if (nNewHeight > (pnode->nStartingHeight != -1
? pnode->nStartingHeight - 2000
: 0)) {
for (const BlockHash &hash : reverse_iterate(vHashes)) {
pnode->PushBlockHash(hash);
}
}
});
connman->WakeMessageHandler();
}
}
/**
* Handle invalid block rejection and consequent peer banning, maintain which
* peers announce compact blocks.
*/
void PeerLogicValidation::BlockChecked(const CBlock &block,
const BlockValidationState &state) {
LOCK(cs_main);
const BlockHash hash = block.GetHash();
std::map<BlockHash, std::pair<NodeId, bool>>::iterator it =
mapBlockSource.find(hash);
// If the block failed validation, we know where it came from and we're
// still connected to that peer, maybe punish.
if (state.IsInvalid() && it != mapBlockSource.end() &&
State(it->second.first)) {
MaybePunishNodeForBlock(/*nodeid=*/it->second.first, state,
/*via_compact_block=*/!it->second.second);
}
// Check that:
// 1. The block is valid
// 2. We're not in initial block download
// 3. This is currently the best block we're aware of. We haven't updated
// the tip yet so we have no way to check this directly here. Instead we
// just check that there are currently no other blocks in flight.
else if (state.IsValid() &&
!::ChainstateActive().IsInitialBlockDownload() &&
mapBlocksInFlight.count(hash) == mapBlocksInFlight.size()) {
if (it != mapBlockSource.end()) {
MaybeSetPeerAsAnnouncingHeaderAndIDs(it->second.first, *connman);
}
}
if (it != mapBlockSource.end()) {
mapBlockSource.erase(it);
}
}
//////////////////////////////////////////////////////////////////////////////
//
// Messages
//
static bool AlreadyHave(const CInv &inv) EXCLUSIVE_LOCKS_REQUIRED(cs_main) {
switch (inv.type) {
case MSG_TX: {
assert(recentRejects);
if (::ChainActive().Tip()->GetBlockHash() !=
hashRecentRejectsChainTip) {
// If the chain tip has changed previously rejected transactions
// might be now valid, e.g. due to a nLockTime'd tx becoming
// valid, or a double-spend. Reset the rejects filter and give
// those txs a second chance.
hashRecentRejectsChainTip =
::ChainActive().Tip()->GetBlockHash();
recentRejects->reset();
}
{
LOCK(g_cs_orphans);
if (mapOrphanTransactions.count(TxId{inv.hash})) {
return true;
}
}
const CCoinsViewCache &coins_cache =
::ChainstateActive().CoinsTip();
// Use pcoinsTip->HaveCoinInCache as a quick approximation to
// exclude requesting or processing some txs which have already been
// included in a block. As this is best effort, we only check for
// output 0 and 1. This works well enough in practice and we get
// diminishing returns with 2 onward.
const TxId txid(inv.hash);
return recentRejects->contains(inv.hash) ||
g_mempool.exists(txid) ||
coins_cache.HaveCoinInCache(COutPoint(txid, 0)) ||
coins_cache.HaveCoinInCache(COutPoint(txid, 1));
}
case MSG_BLOCK:
return LookupBlockIndex(BlockHash(inv.hash)) != nullptr;
}
// Don't know what it is, just say we already got one
return true;
}
void RelayTransaction(const TxId &txid, const CConnman &connman) {
CInv inv(MSG_TX, txid);
connman.ForEachNode([&inv](CNode *pnode) { pnode->PushInventory(inv); });
}
static void RelayAddress(const CAddress &addr, bool fReachable,
const CConnman &connman) {
// Limited relaying of addresses outside our network(s)
unsigned int nRelayNodes = fReachable ? 2 : 1;
// Relay to a limited number of other nodes.
// Use deterministic randomness to send to the same nodes for 24 hours at a
// time so the m_addr_knowns of the chosen nodes prevent repeats
uint64_t hashAddr = addr.GetHash();
const CSipHasher hasher =
connman.GetDeterministicRandomizer(RANDOMIZER_ID_ADDRESS_RELAY)
.Write(hashAddr << 32)
.Write((GetTime() + hashAddr) / (24 * 60 * 60));
FastRandomContext insecure_rand;
std::array<std::pair<uint64_t, CNode *>, 2> best{
{{0, nullptr}, {0, nullptr}}};
assert(nRelayNodes <= best.size());
auto sortfunc = [&best, &hasher, nRelayNodes](CNode *pnode) {
if (pnode->IsAddrRelayPeer()) {
uint64_t hashKey =
CSipHasher(hasher).Write(pnode->GetId()).Finalize();
for (unsigned int i = 0; i < nRelayNodes; i++) {
if (hashKey > best[i].first) {
std::copy(best.begin() + i, best.begin() + nRelayNodes - 1,
best.begin() + i + 1);
best[i] = std::make_pair(hashKey, pnode);
break;
}
}
}
};
auto pushfunc = [&addr, &best, nRelayNodes, &insecure_rand] {
for (unsigned int i = 0; i < nRelayNodes && best[i].first != 0; i++) {
best[i].second->PushAddress(addr, insecure_rand);
}
};
connman.ForEachNodeThen(std::move(sortfunc), std::move(pushfunc));
}
static void ProcessGetBlockData(const Config &config, CNode &pfrom,
const CInv &inv, CConnman &connman,
const std::atomic<bool> &interruptMsgProc) {
const Consensus::Params &consensusParams =
config.GetChainParams().GetConsensus();
const BlockHash hash(inv.hash);
bool send = false;
std::shared_ptr<const CBlock> a_recent_block;
std::shared_ptr<const CBlockHeaderAndShortTxIDs> a_recent_compact_block;
{
LOCK(cs_most_recent_block);
a_recent_block = most_recent_block;
a_recent_compact_block = most_recent_compact_block;
}
bool need_activate_chain = false;
{
LOCK(cs_main);
const CBlockIndex *pindex = LookupBlockIndex(hash);
if (pindex) {
if (pindex->HaveTxsDownloaded() &&
!pindex->IsValid(BlockValidity::SCRIPTS) &&
pindex->IsValid(BlockValidity::TREE)) {
// If we have the block and all of its parents, but have not yet
// validated it, we might be in the middle of connecting it (ie
// in the unlock of cs_main before ActivateBestChain but after
// AcceptBlock). In this case, we need to run ActivateBestChain
// prior to checking the relay conditions below.
need_activate_chain = true;
}
}
} // release cs_main before calling ActivateBestChain
if (need_activate_chain) {
BlockValidationState state;
if (!ActivateBestChain(config, state, a_recent_block)) {
LogPrint(BCLog::NET, "failed to activate chain (%s)\n",
state.ToString());
}
}
LOCK(cs_main);
const CBlockIndex *pindex = LookupBlockIndex(hash);
if (pindex) {
send = BlockRequestAllowed(pindex, consensusParams);
if (!send) {
LogPrint(BCLog::NET,
"%s: ignoring request from peer=%i for old "
"block that isn't in the main chain\n",
__func__, pfrom.GetId());
}
}
const CNetMsgMaker msgMaker(pfrom.GetSendVersion());
// Disconnect node in case we have reached the outbound limit for serving
// historical blocks.
// Never disconnect whitelisted nodes.
if (send && connman.OutboundTargetReached(true) &&
(((pindexBestHeader != nullptr) &&
(pindexBestHeader->GetBlockTime() - pindex->GetBlockTime() >
HISTORICAL_BLOCK_AGE)) ||
inv.type == MSG_FILTERED_BLOCK) &&
!pfrom.HasPermission(PF_NOBAN)) {
LogPrint(BCLog::NET,
"historical block serving limit reached, disconnect peer=%d\n",
pfrom.GetId());
// disconnect node
pfrom.fDisconnect = true;
send = false;
}
// Avoid leaking prune-height by never sending blocks below the
// NODE_NETWORK_LIMITED threshold.
// Add two blocks buffer extension for possible races
if (send && !pfrom.HasPermission(PF_NOBAN) &&
((((pfrom.GetLocalServices() & NODE_NETWORK_LIMITED) ==
NODE_NETWORK_LIMITED) &&
((pfrom.GetLocalServices() & NODE_NETWORK) != NODE_NETWORK) &&
(::ChainActive().Tip()->nHeight - pindex->nHeight >
(int)NODE_NETWORK_LIMITED_MIN_BLOCKS + 2)))) {
LogPrint(BCLog::NET,
"Ignore block request below NODE_NETWORK_LIMITED "
"threshold from peer=%d\n",
pfrom.GetId());
// disconnect node and prevent it from stalling (would otherwise wait
// for the missing block)
pfrom.fDisconnect = true;
send = false;
}
// Pruned nodes may have deleted the block, so check whether it's available
// before trying to send.
if (send && pindex->nStatus.hasData()) {
std::shared_ptr<const CBlock> pblock;
if (a_recent_block &&
a_recent_block->GetHash() == pindex->GetBlockHash()) {
pblock = a_recent_block;
} else {
// Send block from disk
std::shared_ptr<CBlock> pblockRead = std::make_shared<CBlock>();
if (!ReadBlockFromDisk(*pblockRead, pindex, consensusParams)) {
assert(!"cannot load block from disk");
}
pblock = pblockRead;
}
if (inv.type == MSG_BLOCK) {
connman.PushMessage(&pfrom,
msgMaker.Make(NetMsgType::BLOCK, *pblock));
} else if (inv.type == MSG_FILTERED_BLOCK) {
bool sendMerkleBlock = false;
CMerkleBlock merkleBlock;
if (pfrom.m_tx_relay != nullptr) {
LOCK(pfrom.m_tx_relay->cs_filter);
if (pfrom.m_tx_relay->pfilter) {
sendMerkleBlock = true;
merkleBlock =
CMerkleBlock(*pblock, *pfrom.m_tx_relay->pfilter);
}
}
if (sendMerkleBlock) {
connman.PushMessage(
&pfrom,
msgMaker.Make(NetMsgType::MERKLEBLOCK, merkleBlock));
// CMerkleBlock just contains hashes, so also push any
// transactions in the block the client did not see. This avoids
// hurting performance by pointlessly requiring a round-trip.
// Note that there is currently no way for a node to request any
// single transactions we didn't send here - they must either
// disconnect and retry or request the full block. Thus, the
// protocol spec specified allows for us to provide duplicate
// txn here, however we MUST always provide at least what the
// remote peer needs.
typedef std::pair<size_t, uint256> PairType;
for (PairType &pair : merkleBlock.vMatchedTxn) {
connman.PushMessage(
&pfrom, msgMaker.Make(NetMsgType::TX,
*pblock->vtx[pair.first]));
}
}
// else
// no response
} else if (inv.type == MSG_CMPCT_BLOCK) {
// If a peer is asking for old blocks, we're almost guaranteed they
// won't have a useful mempool to match against a compact block, and
// we don't feel like constructing the object for them, so instead
// we respond with the full, non-compact block.
int nSendFlags = 0;
if (CanDirectFetch(consensusParams) &&
pindex->nHeight >=
::ChainActive().Height() - MAX_CMPCTBLOCK_DEPTH) {
CBlockHeaderAndShortTxIDs cmpctblock(*pblock);
connman.PushMessage(
&pfrom, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK,
cmpctblock));
} else {
connman.PushMessage(
&pfrom,
msgMaker.Make(nSendFlags, NetMsgType::BLOCK, *pblock));
}
}
// Trigger the peer node to send a getblocks request for the next batch
// of inventory.
if (hash == pfrom.hashContinue) {
// Bypass PushInventory, this must send even if redundant, and we
// want it right after the last block so they don't wait for other
// stuff first.
std::vector<CInv> vInv;
vInv.push_back(
CInv(MSG_BLOCK, ::ChainActive().Tip()->GetBlockHash()));
connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::INV, vInv));
pfrom.hashContinue = BlockHash();
}
}
}
static void ProcessGetData(const Config &config, CNode &pfrom,
CConnman &connman,
const std::atomic<bool> &interruptMsgProc)
LOCKS_EXCLUDED(cs_main) {
AssertLockNotHeld(cs_main);
std::deque<CInv>::iterator it = pfrom.vRecvGetData.begin();
std::vector<CInv> vNotFound;
const CNetMsgMaker msgMaker(pfrom.GetSendVersion());
// Note that if we receive a getdata for a MSG_TX from a block-relay-only
// outbound peer, we will stop processing further getdata messages from this
// peer (likely resulting in our peer eventually disconnecting us).
if (pfrom.m_tx_relay != nullptr) {
// mempool entries added before this time have likely expired from
// mapRelay
const std::chrono::seconds longlived_mempool_time =
GetTime<std::chrono::seconds>() - RELAY_TX_CACHE_TIME;
const std::chrono::seconds mempool_req =
pfrom.m_tx_relay->m_last_mempool_req.load();
LOCK(cs_main);
while (it != pfrom.vRecvGetData.end() && it->type == MSG_TX) {
if (interruptMsgProc) {
return;
}
// Don't bother if send buffer is too full to respond anyway.
if (pfrom.fPauseSend) {
break;
}
const CInv &inv = *it;
it++;
// Send stream from relay memory
bool push = false;
auto mi = mapRelay.find(inv.hash);
int nSendFlags = 0;
if (mi != mapRelay.end()) {
connman.PushMessage(
&pfrom,
msgMaker.Make(nSendFlags, NetMsgType::TX, *mi->second));
push = true;
} else {
auto txinfo = g_mempool.info(TxId(inv.hash));
// To protect privacy, do not answer getdata using the mempool
// when that TX couldn't have been INVed in reply to a MEMPOOL
// request, or when it's too recent to have expired from
// mapRelay.
if (txinfo.tx &&
((mempool_req.count() && txinfo.m_time <= mempool_req) ||
(txinfo.m_time <= longlived_mempool_time))) {
connman.PushMessage(
&pfrom,
msgMaker.Make(nSendFlags, NetMsgType::TX, *txinfo.tx));
push = true;
}
}
if (!push) {
vNotFound.push_back(inv);
}
}
} // release cs_main
if (it != pfrom.vRecvGetData.end() && !pfrom.fPauseSend) {
const CInv &inv = *it;
if (inv.type == MSG_BLOCK || inv.type == MSG_FILTERED_BLOCK ||
inv.type == MSG_CMPCT_BLOCK) {
it++;
ProcessGetBlockData(config, pfrom, inv, connman, interruptMsgProc);
}
}
// Unknown types in the GetData stay in vRecvGetData and block any future
// message from this peer, see vRecvGetData check in ProcessMessages().
// Depending on future p2p changes, we might either drop unknown getdata on
// the floor or disconnect the peer.
pfrom.vRecvGetData.erase(pfrom.vRecvGetData.begin(), it);
if (!vNotFound.empty()) {
// Let the peer know that we didn't find what it asked for, so it
// doesn't have to wait around forever. SPV clients care about this
// message: it's needed when they are recursively walking the
// dependencies of relevant unconfirmed transactions. SPV clients want
// to do that because they want to know about (and store and rebroadcast
// and risk analyze) the dependencies of transactions relevant to them,
// without having to download the entire memory pool. Also, other nodes
// can use these messages to automatically request a transaction from
// some other peer that annnounced it, and stop waiting for us to
// respond. In normal operation, we often send NOTFOUND messages for
// parents of transactions that we relay; if a peer is missing a parent,
// they may assume we have them and request the parents from us.
connman.PushMessage(&pfrom,
msgMaker.Make(NetMsgType::NOTFOUND, vNotFound));
}
}
inline static void SendBlockTransactions(const CBlock &block,
const BlockTransactionsRequest &req,
CNode &pfrom, CConnman &connman) {
BlockTransactions resp(req);
for (size_t i = 0; i < req.indices.size(); i++) {
if (req.indices[i] >= block.vtx.size()) {
LOCK(cs_main);
Misbehaving(pfrom, 100, "out-of-bound-tx-index");
LogPrintf(
"Peer %d sent us a getblocktxn with out-of-bounds tx indices\n",
pfrom.GetId());
return;
}
resp.txn[i] = block.vtx[req.indices[i]];
}
LOCK(cs_main);
const CNetMsgMaker msgMaker(pfrom.GetSendVersion());
int nSendFlags = 0;
connman.PushMessage(&pfrom,
msgMaker.Make(nSendFlags, NetMsgType::BLOCKTXN, resp));
}
static bool ProcessHeadersMessage(const Config &config, CNode &pfrom,
CConnman &connman,
const std::vector<CBlockHeader> &headers,
bool via_compact_block) {
const CChainParams &chainparams = config.GetChainParams();
const CNetMsgMaker msgMaker(pfrom.GetSendVersion());
size_t nCount = headers.size();
if (nCount == 0) {
// Nothing interesting. Stop asking this peers for more headers.
return true;
}
bool received_new_header = false;
const CBlockIndex *pindexLast = nullptr;
{
LOCK(cs_main);
CNodeState *nodestate = State(pfrom.GetId());
// If this looks like it could be a block announcement (nCount <
// MAX_BLOCKS_TO_ANNOUNCE), use special logic for handling headers that
// don't connect:
// - Send a getheaders message in response to try to connect the chain.
// - The peer can send up to MAX_UNCONNECTING_HEADERS in a row that
// don't connect before giving DoS points
// - Once a headers message is received that is valid and does connect,
// nUnconnectingHeaders gets reset back to 0.
if (!LookupBlockIndex(headers[0].hashPrevBlock) &&
nCount < MAX_BLOCKS_TO_ANNOUNCE) {
nodestate->nUnconnectingHeaders++;
connman.PushMessage(
&pfrom,
msgMaker.Make(NetMsgType::GETHEADERS,
::ChainActive().GetLocator(pindexBestHeader),
uint256()));
LogPrint(
BCLog::NET,
"received header %s: missing prev block %s, sending getheaders "
"(%d) to end (peer=%d, nUnconnectingHeaders=%d)\n",
headers[0].GetHash().ToString(),
headers[0].hashPrevBlock.ToString(), pindexBestHeader->nHeight,
pfrom.GetId(), nodestate->nUnconnectingHeaders);
// Set hashLastUnknownBlock for this peer, so that if we eventually
// get the headers - even from a different peer - we can use this
// peer to download.
UpdateBlockAvailability(pfrom.GetId(), headers.back().GetHash());
if (nodestate->nUnconnectingHeaders % MAX_UNCONNECTING_HEADERS ==
0) {
// The peer is sending us many headers we can't connect.
Misbehaving(pfrom, 20, "too-many-unconnected-headers");
}
return true;
}
BlockHash hashLastBlock;
for (const CBlockHeader &header : headers) {
if (!hashLastBlock.IsNull() &&
header.hashPrevBlock != hashLastBlock) {
Misbehaving(pfrom, 20, "disconnected-header");
return error("non-continuous headers sequence");
}
hashLastBlock = header.GetHash();
}
// If we don't have the last header, then they'll have given us
// something new (if these headers are valid).
if (!LookupBlockIndex(hashLastBlock)) {
received_new_header = true;
}
}
BlockValidationState state;
if (!ProcessNewBlockHeaders(config, headers, state, &pindexLast)) {
if (state.IsInvalid()) {
MaybePunishNodeForBlock(pfrom.GetId(), state, via_compact_block,
"invalid header received");
return false;
}
}
{
LOCK(cs_main);
CNodeState *nodestate = State(pfrom.GetId());
if (nodestate->nUnconnectingHeaders > 0) {
LogPrint(BCLog::NET,
"peer=%d: resetting nUnconnectingHeaders (%d -> 0)\n",
pfrom.GetId(), nodestate->nUnconnectingHeaders);
}
nodestate->nUnconnectingHeaders = 0;
assert(pindexLast);
UpdateBlockAvailability(pfrom.GetId(), pindexLast->GetBlockHash());
// From here, pindexBestKnownBlock should be guaranteed to be non-null,
// because it is set in UpdateBlockAvailability. Some nullptr checks are
// still present, however, as belt-and-suspenders.
if (received_new_header &&
pindexLast->nChainWork > ::ChainActive().Tip()->nChainWork) {
nodestate->m_last_block_announcement = GetTime();
}
if (nCount == MAX_HEADERS_RESULTS) {
// Headers message had its maximum size; the peer may have more
// headers.
// TODO: optimize: if pindexLast is an ancestor of
// ::ChainActive().Tip or pindexBestHeader, continue from there
// instead.
LogPrint(
BCLog::NET,
"more getheaders (%d) to end to peer=%d (startheight:%d)\n",
pindexLast->nHeight, pfrom.GetId(), pfrom.nStartingHeight);
connman.PushMessage(
&pfrom, msgMaker.Make(NetMsgType::GETHEADERS,
::ChainActive().GetLocator(pindexLast),
uint256()));
}
bool fCanDirectFetch = CanDirectFetch(chainparams.GetConsensus());
// If this set of headers is valid and ends in a block with at least as
// much work as our tip, download as much as possible.
if (fCanDirectFetch && pindexLast->IsValid(BlockValidity::TREE) &&
::ChainActive().Tip()->nChainWork <= pindexLast->nChainWork) {
std::vector<const CBlockIndex *> vToFetch;
const CBlockIndex *pindexWalk = pindexLast;
// Calculate all the blocks we'd need to switch to pindexLast, up to
// a limit.
while (pindexWalk && !::ChainActive().Contains(pindexWalk) &&
vToFetch.size() <= MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
if (!pindexWalk->nStatus.hasData() &&
!mapBlocksInFlight.count(pindexWalk->GetBlockHash())) {
// We don't have this block, and it's not yet in flight.
vToFetch.push_back(pindexWalk);
}
pindexWalk = pindexWalk->pprev;
}
// If pindexWalk still isn't on our main chain, we're looking at a
// very large reorg at a time we think we're close to caught up to
// the main chain -- this shouldn't really happen. Bail out on the
// direct fetch and rely on parallel download instead.
if (!::ChainActive().Contains(pindexWalk)) {
LogPrint(
BCLog::NET, "Large reorg, won't direct fetch to %s (%d)\n",
pindexLast->GetBlockHash().ToString(), pindexLast->nHeight);
} else {
std::vector<CInv> vGetData;
// 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) {
// Can't download any more from this peer
break;
}
vGetData.push_back(CInv(MSG_BLOCK, pindex->GetBlockHash()));
MarkBlockAsInFlight(config, pfrom.GetId(),
pindex->GetBlockHash(),
chainparams.GetConsensus(), pindex);
LogPrint(BCLog::NET, "Requesting block %s from peer=%d\n",
pindex->GetBlockHash().ToString(), pfrom.GetId());
}
if (vGetData.size() > 1) {
LogPrint(BCLog::NET,
"Downloading blocks toward %s (%d) via headers "
"direct fetch\n",
pindexLast->GetBlockHash().ToString(),
pindexLast->nHeight);
}
if (vGetData.size() > 0) {
if (nodestate->fSupportsDesiredCmpctVersion &&
vGetData.size() == 1 && mapBlocksInFlight.size() == 1 &&
pindexLast->pprev->IsValid(BlockValidity::CHAIN)) {
// In any case, we want to download using a compact
// block, not a regular one.
vGetData[0] = CInv(MSG_CMPCT_BLOCK, vGetData[0].hash);
}
connman.PushMessage(
&pfrom, msgMaker.Make(NetMsgType::GETDATA, vGetData));
}
}
}
// If we're in IBD, we want outbound peers that will serve us a useful
// chain. Disconnect peers that are on chains with insufficient work.
if (::ChainstateActive().IsInitialBlockDownload() &&
nCount != MAX_HEADERS_RESULTS) {
// When nCount < MAX_HEADERS_RESULTS, we know we have no more
// headers to fetch from this peer.
if (nodestate->pindexBestKnownBlock &&
nodestate->pindexBestKnownBlock->nChainWork <
nMinimumChainWork) {
// This peer has too little work on their headers chain to help
// us sync -- disconnect if using an outbound slot (unless
// whitelisted or addnode).
// Note: We compare their tip to nMinimumChainWork (rather than
// ::ChainActive().Tip()) because we won't start block download
// until we have a headers chain that has at least
// nMinimumChainWork, even if a peer has a chain past our tip,
// as an anti-DoS measure.
if (IsOutboundDisconnectionCandidate(pfrom)) {
LogPrintf("Disconnecting outbound peer %d -- headers "
"chain has insufficient work\n",
pfrom.GetId());
pfrom.fDisconnect = true;
}
}
}
if (!pfrom.fDisconnect && IsOutboundDisconnectionCandidate(pfrom) &&
nodestate->pindexBestKnownBlock != nullptr &&
pfrom.m_tx_relay != nullptr) {
// If this is an outbound full-relay peer, check to see if we should
// protect it from the bad/lagging chain logic. Note that
// block-relay-only peers are already implicitly protected, so we
// only consider setting m_protect for the full-relay peers.
if (g_outbound_peers_with_protect_from_disconnect <
MAX_OUTBOUND_PEERS_TO_PROTECT_FROM_DISCONNECT &&
nodestate->pindexBestKnownBlock->nChainWork >=
::ChainActive().Tip()->nChainWork &&
!nodestate->m_chain_sync.m_protect) {
LogPrint(BCLog::NET,
"Protecting outbound peer=%d from eviction\n",
pfrom.GetId());
nodestate->m_chain_sync.m_protect = true;
++g_outbound_peers_with_protect_from_disconnect;
}
}
}
return true;
}
void static ProcessOrphanTx(const Config &config, CConnman &connman,
std::set<TxId> &orphan_work_set)
EXCLUSIVE_LOCKS_REQUIRED(cs_main, g_cs_orphans) {
AssertLockHeld(cs_main);
AssertLockHeld(g_cs_orphans);
std::unordered_map<NodeId, uint32_t> rejectCountPerNode;
bool done = false;
while (!done && !orphan_work_set.empty()) {
const TxId orphanTxId = *orphan_work_set.begin();
orphan_work_set.erase(orphan_work_set.begin());
auto orphan_it = mapOrphanTransactions.find(orphanTxId);
if (orphan_it == mapOrphanTransactions.end()) {
continue;
}
const CTransactionRef porphanTx = orphan_it->second.tx;
const CTransaction &orphanTx = *porphanTx;
NodeId fromPeer = orphan_it->second.fromPeer;
// Use a new TxValidationState because orphans come from different peers
// (and we call MaybePunishNodeForTx based on the source peer from the
// orphan map, not based on the peer that relayed the previous
// transaction).
TxValidationState orphan_state;
auto it = rejectCountPerNode.find(fromPeer);
if (it != rejectCountPerNode.end() &&
it->second > MAX_NON_STANDARD_ORPHAN_PER_NODE) {
continue;
}
if (AcceptToMemoryPool(config, g_mempool, orphan_state, porphanTx,
false /* bypass_limits */,
Amount::zero() /* nAbsurdFee */)) {
LogPrint(BCLog::MEMPOOL, " accepted orphan tx %s\n",
orphanTxId.ToString());
RelayTransaction(orphanTxId, connman);
for (size_t i = 0; i < orphanTx.vout.size(); i++) {
auto it_by_prev =
mapOrphanTransactionsByPrev.find(COutPoint(orphanTxId, i));
if (it_by_prev != mapOrphanTransactionsByPrev.end()) {
for (const auto &elem : it_by_prev->second) {
orphan_work_set.insert(elem->first);
}
}
}
EraseOrphanTx(orphanTxId);
done = true;
} else if (orphan_state.GetResult() !=
TxValidationResult::TX_MISSING_INPUTS) {
if (orphan_state.IsInvalid()) {
// Punish peer that gave us an invalid orphan tx
MaybePunishNodeForTx(fromPeer, orphan_state);
LogPrint(BCLog::MEMPOOL, " invalid orphan tx %s\n",
orphanTxId.ToString());
}
// Has inputs but not accepted to mempool
// Probably non-standard or insufficient fee
LogPrint(BCLog::MEMPOOL, " removed orphan tx %s\n",
orphanTxId.ToString());
assert(recentRejects);
recentRejects->insert(orphanTxId);
EraseOrphanTx(orphanTxId);
done = true;
}
g_mempool.check(&::ChainstateActive().CoinsTip());
}
}
/**
* Validation logic for compact filters request handling.
*
* May disconnect from the peer in the case of a bad request.
*
* @param[in] pfrom The peer that we received the request from
* @param[in] chain_params Chain parameters
* @param[in] filter_type The filter type the request is for. Must be
* basic filters.
* @param[in] start_height The start height for the request
* @param[in] stop_hash The stop_hash for the request
* @param[in] max_height_diff The maximum number of items permitted to
* request, as specified in BIP 157
* @param[out] stop_index The CBlockIndex for the stop_hash block, if the
* request can be serviced.
* @param[out] filter_index The filter index, if the request can be
* serviced.
* @return True if the request can be serviced.
*/
static bool PrepareBlockFilterRequest(
CNode &pfrom, const CChainParams &chain_params, BlockFilterType filter_type,
uint32_t start_height, const BlockHash &stop_hash, uint32_t max_height_diff,
const CBlockIndex *&stop_index, BlockFilterIndex *&filter_index) {
const bool supported_filter_type =
(filter_type == BlockFilterType::BASIC &&
gArgs.GetBoolArg("-peerblockfilters", DEFAULT_PEERBLOCKFILTERS));
if (!supported_filter_type) {
LogPrint(BCLog::NET,
"peer %d requested unsupported block filter type: %d\n",
pfrom.GetId(), static_cast<uint8_t>(filter_type));
pfrom.fDisconnect = true;
return false;
}
{
LOCK(cs_main);
stop_index = LookupBlockIndex(stop_hash);
// Check that the stop block exists and the peer would be allowed to
// fetch it.
if (!stop_index ||
!BlockRequestAllowed(stop_index, chain_params.GetConsensus())) {
LogPrint(BCLog::NET, "peer %d requested invalid block hash: %s\n",
pfrom.GetId(), stop_hash.ToString());
pfrom.fDisconnect = true;
return false;
}
}
uint32_t stop_height = stop_index->nHeight;
if (start_height > stop_height) {
LogPrint(
BCLog::NET,
"peer %d sent invalid getcfilters/getcfheaders with " /* Continued
*/
"start height %d and stop height %d\n",
pfrom.GetId(), start_height, stop_height);
pfrom.fDisconnect = true;
return false;
}
if (stop_height - start_height >= max_height_diff) {
LogPrint(BCLog::NET,
"peer %d requested too many cfilters/cfheaders: %d / %d\n",
pfrom.GetId(), stop_height - start_height + 1,
max_height_diff);
pfrom.fDisconnect = true;
return false;
}
filter_index = GetBlockFilterIndex(filter_type);
if (!filter_index) {
LogPrint(BCLog::NET, "Filter index for supported type %s not found\n",
BlockFilterTypeName(filter_type));
return false;
}
return true;
}
/**
* Handle a cfilters request.
*
* May disconnect from the peer in the case of a bad request.
*
* @param[in] pfrom The peer that we received the request from
* @param[in] vRecv The raw message received
* @param[in] chain_params Chain parameters
* @param[in] connman Pointer to the connection manager
*/
static void ProcessGetCFilters(CNode &pfrom, CDataStream &vRecv,
const CChainParams &chain_params,
CConnman &connman) {
uint8_t filter_type_ser;
uint32_t start_height;
BlockHash stop_hash;
vRecv >> filter_type_ser >> start_height >> stop_hash;
const BlockFilterType filter_type =
static_cast<BlockFilterType>(filter_type_ser);
const CBlockIndex *stop_index;
BlockFilterIndex *filter_index;
if (!PrepareBlockFilterRequest(
pfrom, chain_params, filter_type, start_height, stop_hash,
MAX_GETCFILTERS_SIZE, stop_index, filter_index)) {
return;
}
std::vector<BlockFilter> filters;
if (!filter_index->LookupFilterRange(start_height, stop_index, filters)) {
LogPrint(BCLog::NET,
"Failed to find block filter in index: filter_type=%s, "
"start_height=%d, stop_hash=%s\n",
BlockFilterTypeName(filter_type), start_height,
stop_hash.ToString());
return;
}
for (const auto &filter : filters) {
CSerializedNetMsg msg = CNetMsgMaker(pfrom.GetSendVersion())
.Make(NetMsgType::CFILTER, filter);
connman.PushMessage(&pfrom, std::move(msg));
}
}
/**
* Handle a cfheaders request.
*
* May disconnect from the peer in the case of a bad request.
*
* @param[in] pfrom The peer that we received the request from
* @param[in] vRecv The raw message received
* @param[in] chain_params Chain parameters
* @param[in] connman Pointer to the connection manager
*/
static void ProcessGetCFHeaders(CNode &pfrom, CDataStream &vRecv,
const CChainParams &chain_params,
CConnman &connman) {
uint8_t filter_type_ser;
uint32_t start_height;
BlockHash stop_hash;
vRecv >> filter_type_ser >> start_height >> stop_hash;
const BlockFilterType filter_type =
static_cast<BlockFilterType>(filter_type_ser);
const CBlockIndex *stop_index;
BlockFilterIndex *filter_index;
if (!PrepareBlockFilterRequest(
pfrom, chain_params, filter_type, start_height, stop_hash,
MAX_GETCFHEADERS_SIZE, stop_index, filter_index)) {
return;
}
uint256 prev_header;
if (start_height > 0) {
const CBlockIndex *const prev_block =
stop_index->GetAncestor(static_cast<int>(start_height - 1));
if (!filter_index->LookupFilterHeader(prev_block, prev_header)) {
LogPrint(BCLog::NET,
"Failed to find block filter header in index: "
"filter_type=%s, block_hash=%s\n",
BlockFilterTypeName(filter_type),
prev_block->GetBlockHash().ToString());
return;
}
}
std::vector<uint256> filter_hashes;
if (!filter_index->LookupFilterHashRange(start_height, stop_index,
filter_hashes)) {
LogPrint(BCLog::NET,
"Failed to find block filter hashes in index: filter_type=%s, "
"start_height=%d, stop_hash=%s\n",
BlockFilterTypeName(filter_type), start_height,
stop_hash.ToString());
return;
}
CSerializedNetMsg msg =
CNetMsgMaker(pfrom.GetSendVersion())
.Make(NetMsgType::CFHEADERS, filter_type_ser,
stop_index->GetBlockHash(), prev_header, filter_hashes);
connman.PushMessage(&pfrom, std::move(msg));
}
/**
* Handle a getcfcheckpt request.
*
* May disconnect from the peer in the case of a bad request.
*
* @param[in] pfrom The peer that we received the request from
* @param[in] vRecv The raw message received
* @param[in] chain_params Chain parameters
* @param[in] connman Pointer to the connection manager
*/
static void ProcessGetCFCheckPt(CNode &pfrom, CDataStream &vRecv,
const CChainParams &chain_params,
CConnman &connman) {
uint8_t filter_type_ser;
BlockHash stop_hash;
vRecv >> filter_type_ser >> stop_hash;
const BlockFilterType filter_type =
static_cast<BlockFilterType>(filter_type_ser);
const CBlockIndex *stop_index;
BlockFilterIndex *filter_index;
if (!PrepareBlockFilterRequest(
pfrom, chain_params, filter_type, /*start_height=*/0, stop_hash,
/*max_height_diff=*/std::numeric_limits<uint32_t>::max(),
stop_index, filter_index)) {
return;
}
std::vector<uint256> headers(stop_index->nHeight / CFCHECKPT_INTERVAL);
// Populate headers.
const CBlockIndex *block_index = stop_index;
for (int i = headers.size() - 1; i >= 0; i--) {
int height = (i + 1) * CFCHECKPT_INTERVAL;
block_index = block_index->GetAncestor(height);
if (!filter_index->LookupFilterHeader(block_index, headers[i])) {
LogPrint(BCLog::NET,
"Failed to find block filter header in index: "
"filter_type=%s, block_hash=%s\n",
BlockFilterTypeName(filter_type),
block_index->GetBlockHash().ToString());
return;
}
}
CSerializedNetMsg msg = CNetMsgMaker(pfrom.GetSendVersion())
.Make(NetMsgType::CFCHECKPT, filter_type_ser,
stop_index->GetBlockHash(), headers);
connman.PushMessage(&pfrom, std::move(msg));
}
bool ProcessMessage(const Config &config, CNode &pfrom,
const std::string &msg_type, CDataStream &vRecv,
- int64_t nTimeReceived, CConnman &connman, BanMan *banman,
+ int64_t nTimeReceived, ChainstateManager &chainman,
+ CConnman &connman, BanMan *banman,
const std::atomic<bool> &interruptMsgProc) {
const CChainParams &chainparams = config.GetChainParams();
LogPrint(BCLog::NET, "received: %s (%u bytes) peer=%d\n",
SanitizeString(msg_type), vRecv.size(), pfrom.GetId());
if (gArgs.IsArgSet("-dropmessagestest") &&
GetRand(gArgs.GetArg("-dropmessagestest", 0)) == 0) {
LogPrintf("dropmessagestest DROPPING RECV MESSAGE\n");
return true;
}
if (!(pfrom.GetLocalServices() & NODE_BLOOM) &&
(msg_type == NetMsgType::FILTERLOAD ||
msg_type == NetMsgType::FILTERADD)) {
if (pfrom.nVersion >= NO_BLOOM_VERSION) {
LOCK(cs_main);
Misbehaving(pfrom, 100, "no-bloom-version");
return false;
} else {
pfrom.fDisconnect = true;
return false;
}
}
if (msg_type == NetMsgType::VERSION) {
// Each connection can only send one version message
if (pfrom.nVersion != 0) {
LOCK(cs_main);
Misbehaving(pfrom, 1, "multiple-version");
return false;
}
int64_t nTime;
CAddress addrMe;
CAddress addrFrom;
uint64_t nNonce = 1;
uint64_t nServiceInt;
ServiceFlags nServices;
int nVersion;
int nSendVersion;
std::string cleanSubVer;
int nStartingHeight = -1;
bool fRelay = true;
vRecv >> nVersion >> nServiceInt >> nTime >> addrMe;
nSendVersion = std::min(nVersion, PROTOCOL_VERSION);
nServices = ServiceFlags(nServiceInt);
if (!pfrom.fInbound) {
connman.SetServices(pfrom.addr, nServices);
}
if (!pfrom.fInbound && !pfrom.fFeeler && !pfrom.m_manual_connection &&
!HasAllDesirableServiceFlags(nServices)) {
LogPrint(BCLog::NET,
"peer=%d does not offer the expected services "
"(%08x offered, %08x expected); disconnecting\n",
pfrom.GetId(), nServices,
GetDesirableServiceFlags(nServices));
pfrom.fDisconnect = true;
return false;
}
if (nVersion < MIN_PEER_PROTO_VERSION) {
// disconnect from peers older than this proto version
LogPrint(BCLog::NET,
"peer=%d using obsolete version %i; disconnecting\n",
pfrom.GetId(), nVersion);
pfrom.fDisconnect = true;
return false;
}
if (!vRecv.empty()) {
vRecv >> addrFrom >> nNonce;
}
if (!vRecv.empty()) {
std::string strSubVer;
vRecv >> LIMITED_STRING(strSubVer, MAX_SUBVERSION_LENGTH);
cleanSubVer = SanitizeString(strSubVer);
}
if (!vRecv.empty()) {
vRecv >> nStartingHeight;
}
if (!vRecv.empty()) {
vRecv >> fRelay;
}
// Disconnect if we connected to ourself
if (pfrom.fInbound && !connman.CheckIncomingNonce(nNonce)) {
LogPrintf("connected to self at %s, disconnecting\n",
pfrom.addr.ToString());
pfrom.fDisconnect = true;
return true;
}
if (pfrom.fInbound && addrMe.IsRoutable()) {
SeenLocal(addrMe);
}
// Be shy and don't send version until we hear
if (pfrom.fInbound) {
PushNodeVersion(config, pfrom, connman, GetAdjustedTime());
}
connman.PushMessage(
&pfrom, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERACK));
pfrom.nServices = nServices;
pfrom.SetAddrLocal(addrMe);
{
LOCK(pfrom.cs_SubVer);
pfrom.cleanSubVer = cleanSubVer;
}
pfrom.nStartingHeight = nStartingHeight;
// set nodes not relaying blocks and tx and not serving (parts) of the
// historical blockchain as "clients"
pfrom.fClient = (!(nServices & NODE_NETWORK) &&
!(nServices & NODE_NETWORK_LIMITED));
// set nodes not capable of serving the complete blockchain history as
// "limited nodes"
pfrom.m_limited_node =
(!(nServices & NODE_NETWORK) && (nServices & NODE_NETWORK_LIMITED));
if (pfrom.m_tx_relay != nullptr) {
LOCK(pfrom.m_tx_relay->cs_filter);
// set to true after we get the first filter* message
pfrom.m_tx_relay->fRelayTxes = fRelay;
}
// Change version
pfrom.SetSendVersion(nSendVersion);
pfrom.nVersion = nVersion;
// Potentially mark this peer as a preferred download peer.
{
LOCK(cs_main);
UpdatePreferredDownload(pfrom, State(pfrom.GetId()));
}
if (!pfrom.fInbound && pfrom.IsAddrRelayPeer()) {
// Advertise our address
if (fListen && !::ChainstateActive().IsInitialBlockDownload()) {
CAddress addr =
GetLocalAddress(&pfrom.addr, pfrom.GetLocalServices());
FastRandomContext insecure_rand;
if (addr.IsRoutable()) {
LogPrint(BCLog::NET,
"ProcessMessages: advertising address %s\n",
addr.ToString());
pfrom.PushAddress(addr, insecure_rand);
} else if (IsPeerAddrLocalGood(&pfrom)) {
addr.SetIP(addrMe);
LogPrint(BCLog::NET,
"ProcessMessages: advertising address %s\n",
addr.ToString());
pfrom.PushAddress(addr, insecure_rand);
}
}
// Get recent addresses
connman.PushMessage(
&pfrom, CNetMsgMaker(nSendVersion).Make(NetMsgType::GETADDR));
pfrom.fGetAddr = true;
connman.MarkAddressGood(pfrom.addr);
}
std::string remoteAddr;
if (fLogIPs) {
remoteAddr = ", peeraddr=" + pfrom.addr.ToString();
}
LogPrint(BCLog::NET,
"receive version message: [%s] %s: version %d, blocks=%d, "
"us=%s, peer=%d%s\n",
pfrom.addr.ToString(), cleanSubVer, pfrom.nVersion,
pfrom.nStartingHeight, addrMe.ToString(), pfrom.GetId(),
remoteAddr);
// Ignore time offsets that are improbable (before the Genesis block)
// and may underflow the nTimeOffset calculation.
int64_t currentTime = GetTime();
if (nTime >= int64_t(chainparams.GenesisBlock().nTime)) {
int64_t nTimeOffset = nTime - currentTime;
pfrom.nTimeOffset = nTimeOffset;
AddTimeData(pfrom.addr, nTimeOffset);
} else {
LOCK(cs_main);
Misbehaving(pfrom, 20,
"Ignoring invalid timestamp in version message");
}
// Feeler connections exist only to verify if address is online.
if (pfrom.fFeeler) {
assert(pfrom.fInbound == false);
pfrom.fDisconnect = true;
}
return true;
}
if (pfrom.nVersion == 0) {
// Must have a version message before anything else
LOCK(cs_main);
Misbehaving(pfrom, 10, "missing-version");
return false;
}
// At this point, the outgoing message serialization version can't change.
const CNetMsgMaker msgMaker(pfrom.GetSendVersion());
if (msg_type == NetMsgType::VERACK) {
pfrom.SetRecvVersion(std::min(pfrom.nVersion.load(), PROTOCOL_VERSION));
if (!pfrom.fInbound) {
// Mark this node as currently connected, so we update its timestamp
// later.
LOCK(cs_main);
State(pfrom.GetId())->fCurrentlyConnected = true;
LogPrintf(
"New outbound peer connected: version: %d, blocks=%d, "
"peer=%d%s (%s)\n",
pfrom.nVersion.load(), pfrom.nStartingHeight, pfrom.GetId(),
(fLogIPs ? strprintf(", peeraddr=%s", pfrom.addr.ToString())
: ""),
pfrom.m_tx_relay == nullptr ? "block-relay" : "full-relay");
}
if (pfrom.nVersion >= SENDHEADERS_VERSION) {
// Tell our peer we prefer to receive headers rather than inv's
// We send this to non-NODE NETWORK peers as well, because even
// non-NODE NETWORK peers can announce blocks (such as pruning
// nodes)
connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDHEADERS));
}
if (pfrom.nVersion >= SHORT_IDS_BLOCKS_VERSION) {
// Tell our peer we are willing to provide version 1 or 2
// cmpctblocks. However, we do not request new block announcements
// using cmpctblock messages. We send this to non-NODE NETWORK peers
// as well, because they may wish to request compact blocks from us.
bool fAnnounceUsingCMPCTBLOCK = false;
uint64_t nCMPCTBLOCKVersion = 1;
connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDCMPCT,
fAnnounceUsingCMPCTBLOCK,
nCMPCTBLOCKVersion));
}
pfrom.fSuccessfullyConnected = true;
return true;
}
if (!pfrom.fSuccessfullyConnected) {
// Must have a verack message before anything else
LOCK(cs_main);
Misbehaving(pfrom, 10, "missing-verack");
return false;
}
if (msg_type == NetMsgType::ADDR) {
std::vector<CAddress> vAddr;
vRecv >> vAddr;
if (!pfrom.IsAddrRelayPeer()) {
return true;
}
if (vAddr.size() > 1000) {
LOCK(cs_main);
Misbehaving(pfrom, 20, "oversized-addr");
return error("message addr size() = %u", vAddr.size());
}
// Store the new addresses
std::vector<CAddress> vAddrOk;
int64_t nNow = GetAdjustedTime();
int64_t nSince = nNow - 10 * 60;
for (CAddress &addr : vAddr) {
if (interruptMsgProc) {
return true;
}
// We only bother storing full nodes, though this may include things
// which we would not make an outbound connection to, in part
// because we may make feeler connections to them.
if (!MayHaveUsefulAddressDB(addr.nServices) &&
!HasAllDesirableServiceFlags(addr.nServices)) {
continue;
}
if (addr.nTime <= 100000000 || addr.nTime > nNow + 10 * 60) {
addr.nTime = nNow - 5 * 24 * 60 * 60;
}
pfrom.AddAddressKnown(addr);
// Do not process banned/discouraged addresses beyond remembering we
// received them
if (banman->IsDiscouraged(addr)) {
continue;
}
if (banman->IsBanned(addr)) {
continue;
}
bool fReachable = IsReachable(addr);
if (addr.nTime > nSince && !pfrom.fGetAddr && vAddr.size() <= 10 &&
addr.IsRoutable()) {
// Relay to a limited number of other nodes
RelayAddress(addr, fReachable, connman);
}
// Do not store addresses outside our network
if (fReachable) {
vAddrOk.push_back(addr);
}
}
connman.AddNewAddresses(vAddrOk, pfrom.addr, 2 * 60 * 60);
if (vAddr.size() < 1000) {
pfrom.fGetAddr = false;
}
if (pfrom.fOneShot) {
pfrom.fDisconnect = true;
}
return true;
}
if (msg_type == NetMsgType::SENDHEADERS) {
LOCK(cs_main);
State(pfrom.GetId())->fPreferHeaders = true;
return true;
}
if (msg_type == NetMsgType::SENDCMPCT) {
bool fAnnounceUsingCMPCTBLOCK = false;
uint64_t nCMPCTBLOCKVersion = 0;
vRecv >> fAnnounceUsingCMPCTBLOCK >> nCMPCTBLOCKVersion;
if (nCMPCTBLOCKVersion == 1) {
LOCK(cs_main);
// fProvidesHeaderAndIDs is used to "lock in" version of compact
// blocks we send.
if (!State(pfrom.GetId())->fProvidesHeaderAndIDs) {
State(pfrom.GetId())->fProvidesHeaderAndIDs = true;
}
State(pfrom.GetId())->fPreferHeaderAndIDs =
fAnnounceUsingCMPCTBLOCK;
if (!State(pfrom.GetId())->fSupportsDesiredCmpctVersion) {
State(pfrom.GetId())->fSupportsDesiredCmpctVersion = true;
}
}
return true;
}
if (msg_type == NetMsgType::INV) {
std::vector<CInv> vInv;
vRecv >> vInv;
if (vInv.size() > MAX_INV_SZ) {
LOCK(cs_main);
Misbehaving(pfrom, 20, "oversized-inv");
return error("message inv size() = %u", vInv.size());
}
// We won't accept tx inv's if we're in blocks-only mode, or this is a
// block-relay-only peer
bool fBlocksOnly = !g_relay_txes || (pfrom.m_tx_relay == nullptr);
// Allow whitelisted peers to send data other than blocks in blocks only
// mode if whitelistrelay is true
if (pfrom.HasPermission(PF_RELAY)) {
fBlocksOnly = false;
}
LOCK(cs_main);
const auto current_time = GetTime<std::chrono::microseconds>();
for (CInv &inv : vInv) {
if (interruptMsgProc) {
return true;
}
bool fAlreadyHave = AlreadyHave(inv);
LogPrint(BCLog::NET, "got inv: %s %s peer=%d\n", inv.ToString(),
fAlreadyHave ? "have" : "new", pfrom.GetId());
if (inv.type == MSG_BLOCK) {
const BlockHash hash(inv.hash);
UpdateBlockAvailability(pfrom.GetId(), hash);
if (!fAlreadyHave && !fImporting && !fReindex &&
!mapBlocksInFlight.count(hash)) {
// We used to request the full block here, but since
// headers-announcements are now the primary method of
// announcement on the network, and since, in the case that
// a node fell back to inv we probably have a reorg which we
// should get the headers for first, we now only provide a
// getheaders response here. When we receive the headers, we
// will then ask for the blocks we need.
connman.PushMessage(
&pfrom, msgMaker.Make(NetMsgType::GETHEADERS,
::ChainActive().GetLocator(
pindexBestHeader),
hash));
LogPrint(BCLog::NET, "getheaders (%d) %s to peer=%d\n",
pindexBestHeader->nHeight, hash.ToString(),
pfrom.GetId());
}
} else {
const TxId txid(inv.hash);
pfrom.AddKnownTx(txid);
if (fBlocksOnly) {
LogPrint(BCLog::NET,
"transaction (%s) inv sent in violation of "
"protocol, disconnecting peer=%d\n",
txid.ToString(), pfrom.GetId());
pfrom.fDisconnect = true;
return true;
} else if (!fAlreadyHave && !fImporting && !fReindex &&
!::ChainstateActive().IsInitialBlockDownload()) {
RequestTx(State(pfrom.GetId()), txid, current_time);
}
}
}
return true;
}
if (msg_type == NetMsgType::GETDATA) {
std::vector<CInv> vInv;
vRecv >> vInv;
if (vInv.size() > MAX_INV_SZ) {
LOCK(cs_main);
Misbehaving(pfrom, 20, "too-many-inv");
return error("message getdata size() = %u", vInv.size());
}
LogPrint(BCLog::NET, "received getdata (%u invsz) peer=%d\n",
vInv.size(), pfrom.GetId());
if (vInv.size() > 0) {
LogPrint(BCLog::NET, "received getdata for: %s peer=%d\n",
vInv[0].ToString(), pfrom.GetId());
}
pfrom.vRecvGetData.insert(pfrom.vRecvGetData.end(), vInv.begin(),
vInv.end());
ProcessGetData(config, pfrom, connman, interruptMsgProc);
return true;
}
if (msg_type == NetMsgType::GETBLOCKS) {
CBlockLocator locator;
uint256 hashStop;
vRecv >> locator >> hashStop;
if (locator.vHave.size() > MAX_LOCATOR_SZ) {
LogPrint(BCLog::NET,
"getblocks locator size %lld > %d, disconnect peer=%d\n",
locator.vHave.size(), MAX_LOCATOR_SZ, pfrom.GetId());
pfrom.fDisconnect = true;
return true;
}
// We might have announced the currently-being-connected tip using a
// compact block, which resulted in the peer sending a getblocks
// request, which we would otherwise respond to without the new block.
// To avoid this situation we simply verify that we are on our best
// known chain now. This is super overkill, but we handle it better
// for getheaders requests, and there are no known nodes which support
// compact blocks but still use getblocks to request blocks.
{
std::shared_ptr<const CBlock> a_recent_block;
{
LOCK(cs_most_recent_block);
a_recent_block = most_recent_block;
}
BlockValidationState state;
if (!ActivateBestChain(config, state, a_recent_block)) {
LogPrint(BCLog::NET, "failed to activate chain (%s)\n",
state.ToString());
}
}
LOCK(cs_main);
// Find the last block the caller has in the main chain
const CBlockIndex *pindex =
FindForkInGlobalIndex(::ChainActive(), locator);
// Send the rest of the chain
if (pindex) {
pindex = ::ChainActive().Next(pindex);
}
int nLimit = 500;
LogPrint(BCLog::NET, "getblocks %d to %s limit %d from peer=%d\n",
(pindex ? pindex->nHeight : -1),
hashStop.IsNull() ? "end" : hashStop.ToString(), nLimit,
pfrom.GetId());
for (; pindex; pindex = ::ChainActive().Next(pindex)) {
if (pindex->GetBlockHash() == hashStop) {
LogPrint(BCLog::NET, " getblocks stopping at %d %s\n",
pindex->nHeight, pindex->GetBlockHash().ToString());
break;
}
// If pruning, don't inv blocks unless we have on disk and are
// likely to still have for some reasonable time window (1 hour)
// that block relay might require.
const int nPrunedBlocksLikelyToHave =
MIN_BLOCKS_TO_KEEP -
3600 / chainparams.GetConsensus().nPowTargetSpacing;
if (fPruneMode &&
(!pindex->nStatus.hasData() ||
pindex->nHeight <= ::ChainActive().Tip()->nHeight -
nPrunedBlocksLikelyToHave)) {
LogPrint(
BCLog::NET,
" getblocks stopping, pruned or too old block at %d %s\n",
pindex->nHeight, pindex->GetBlockHash().ToString());
break;
}
pfrom.PushInventory(CInv(MSG_BLOCK, pindex->GetBlockHash()));
if (--nLimit <= 0) {
// When this block is requested, we'll send an inv that'll
// trigger the peer to getblocks the next batch of inventory.
LogPrint(BCLog::NET, " getblocks stopping at limit %d %s\n",
pindex->nHeight, pindex->GetBlockHash().ToString());
pfrom.hashContinue = pindex->GetBlockHash();
break;
}
}
return true;
}
if (msg_type == NetMsgType::GETBLOCKTXN) {
BlockTransactionsRequest req;
vRecv >> req;
std::shared_ptr<const CBlock> recent_block;
{
LOCK(cs_most_recent_block);
if (most_recent_block_hash == req.blockhash) {
recent_block = most_recent_block;
}
// Unlock cs_most_recent_block to avoid cs_main lock inversion
}
if (recent_block) {
SendBlockTransactions(*recent_block, req, pfrom, connman);
return true;
}
LOCK(cs_main);
const CBlockIndex *pindex = LookupBlockIndex(req.blockhash);
if (!pindex || !pindex->nStatus.hasData()) {
LogPrint(
BCLog::NET,
"Peer %d sent us a getblocktxn for a block we don't have\n",
pfrom.GetId());
return true;
}
if (pindex->nHeight < ::ChainActive().Height() - MAX_BLOCKTXN_DEPTH) {
// If an older block is requested (should never happen in practice,
// but can happen in tests) send a block response instead of a
// blocktxn response. Sending a full block response instead of a
// small blocktxn response is preferable in the case where a peer
// might maliciously send lots of getblocktxn requests to trigger
// expensive disk reads, because it will require the peer to
// actually receive all the data read from disk over the network.
LogPrint(BCLog::NET,
"Peer %d sent us a getblocktxn for a block > %i deep\n",
pfrom.GetId(), MAX_BLOCKTXN_DEPTH);
CInv inv;
inv.type = MSG_BLOCK;
inv.hash = req.blockhash;
pfrom.vRecvGetData.push_back(inv);
// The message processing loop will go around again (without
// pausing) and we'll respond then (without cs_main)
return true;
}
CBlock block;
bool ret = ReadBlockFromDisk(block, pindex, chainparams.GetConsensus());
assert(ret);
SendBlockTransactions(block, req, pfrom, connman);
return true;
}
if (msg_type == NetMsgType::GETHEADERS) {
CBlockLocator locator;
BlockHash hashStop;
vRecv >> locator >> hashStop;
if (locator.vHave.size() > MAX_LOCATOR_SZ) {
LogPrint(BCLog::NET,
"getheaders locator size %lld > %d, disconnect peer=%d\n",
locator.vHave.size(), MAX_LOCATOR_SZ, pfrom.GetId());
pfrom.fDisconnect = true;
return true;
}
LOCK(cs_main);
if (::ChainstateActive().IsInitialBlockDownload() &&
!pfrom.HasPermission(PF_NOBAN)) {
LogPrint(BCLog::NET,
"Ignoring getheaders from peer=%d because node is in "
"initial block download\n",
pfrom.GetId());
return true;
}
CNodeState *nodestate = State(pfrom.GetId());
const CBlockIndex *pindex = nullptr;
if (locator.IsNull()) {
// If locator is null, return the hashStop block
pindex = LookupBlockIndex(hashStop);
if (!pindex) {
return true;
}
if (!BlockRequestAllowed(pindex, chainparams.GetConsensus())) {
LogPrint(BCLog::NET,
"%s: ignoring request from peer=%i for old block "
"header that isn't in the main chain\n",
__func__, pfrom.GetId());
return true;
}
} else {
// Find the last block the caller has in the main chain
pindex = FindForkInGlobalIndex(::ChainActive(), locator);
if (pindex) {
pindex = ::ChainActive().Next(pindex);
}
}
// we must use CBlocks, as CBlockHeaders won't include the 0x00 nTx
// count at the end
std::vector<CBlock> vHeaders;
int nLimit = MAX_HEADERS_RESULTS;
LogPrint(BCLog::NET, "getheaders %d to %s from peer=%d\n",
(pindex ? pindex->nHeight : -1),
hashStop.IsNull() ? "end" : hashStop.ToString(),
pfrom.GetId());
for (; pindex; pindex = ::ChainActive().Next(pindex)) {
vHeaders.push_back(pindex->GetBlockHeader());
if (--nLimit <= 0 || pindex->GetBlockHash() == hashStop) {
break;
}
}
// pindex can be nullptr either if we sent ::ChainActive().Tip() OR
// if our peer has ::ChainActive().Tip() (and thus we are sending an
// empty headers message). In both cases it's safe to update
// pindexBestHeaderSent to be our tip.
//
// It is important that we simply reset the BestHeaderSent value here,
// and not max(BestHeaderSent, newHeaderSent). We might have announced
// the currently-being-connected tip using a compact block, which
// resulted in the peer sending a headers request, which we respond to
// without the new block. By resetting the BestHeaderSent, we ensure we
// will re-announce the new block via headers (or compact blocks again)
// in the SendMessages logic.
nodestate->pindexBestHeaderSent =
pindex ? pindex : ::ChainActive().Tip();
connman.PushMessage(&pfrom,
msgMaker.Make(NetMsgType::HEADERS, vHeaders));
return true;
}
if (msg_type == NetMsgType::TX) {
// Stop processing the transaction early if
// We are in blocks only mode and peer is either not whitelisted or
// whitelistrelay is off or if this peer is supposed to be a
// block-relay-only peer
if ((!g_relay_txes && !pfrom.HasPermission(PF_RELAY)) ||
(pfrom.m_tx_relay == nullptr)) {
LogPrint(BCLog::NET,
"transaction sent in violation of protocol peer=%d\n",
pfrom.GetId());
pfrom.fDisconnect = true;
return true;
}
CTransactionRef ptx;
vRecv >> ptx;
const CTransaction &tx = *ptx;
const TxId &txid = tx.GetId();
pfrom.AddKnownTx(txid);
LOCK2(cs_main, g_cs_orphans);
TxValidationState state;
CNodeState *nodestate = State(pfrom.GetId());
nodestate->m_tx_download.m_tx_announced.erase(txid);
nodestate->m_tx_download.m_tx_in_flight.erase(txid);
EraseTxRequest(txid);
if (!AlreadyHave(CInv(MSG_TX, txid)) &&
AcceptToMemoryPool(config, g_mempool, state, ptx,
false /* bypass_limits */,
Amount::zero() /* nAbsurdFee */)) {
g_mempool.check(&::ChainstateActive().CoinsTip());
RelayTransaction(tx.GetId(), connman);
for (size_t i = 0; i < tx.vout.size(); i++) {
auto it_by_prev =
mapOrphanTransactionsByPrev.find(COutPoint(txid, i));
if (it_by_prev != mapOrphanTransactionsByPrev.end()) {
for (const auto &elem : it_by_prev->second) {
pfrom.orphan_work_set.insert(elem->first);
}
}
}
pfrom.nLastTXTime = GetTime();
LogPrint(BCLog::MEMPOOL,
"AcceptToMemoryPool: peer=%d: accepted %s "
"(poolsz %u txn, %u kB)\n",
pfrom.GetId(), tx.GetId().ToString(), g_mempool.size(),
g_mempool.DynamicMemoryUsage() / 1000);
// Recursively process any orphan transactions that depended on this
// one
ProcessOrphanTx(config, connman, pfrom.orphan_work_set);
} else if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) {
// It may be the case that the orphans parents have all been
// rejected.
bool fRejectedParents = false;
for (const CTxIn &txin : tx.vin) {
if (recentRejects->contains(txin.prevout.GetTxId())) {
fRejectedParents = true;
break;
}
}
if (!fRejectedParents) {
const auto current_time = GetTime<std::chrono::microseconds>();
for (const CTxIn &txin : tx.vin) {
// FIXME: MSG_TX should use a TxHash, not a TxId.
const TxId _txid = txin.prevout.GetTxId();
pfrom.AddKnownTx(_txid);
if (!AlreadyHave(CInv(MSG_TX, _txid))) {
RequestTx(State(pfrom.GetId()), _txid, current_time);
}
}
AddOrphanTx(ptx, pfrom.GetId());
// DoS prevention: do not allow mapOrphanTransactions to grow
// unbounded (see CVE-2012-3789)
unsigned int nMaxOrphanTx = (unsigned int)std::max(
int64_t(0), gArgs.GetArg("-maxorphantx",
DEFAULT_MAX_ORPHAN_TRANSACTIONS));
unsigned int nEvicted = LimitOrphanTxSize(nMaxOrphanTx);
if (nEvicted > 0) {
LogPrint(BCLog::MEMPOOL,
"mapOrphan overflow, removed %u tx\n", nEvicted);
}
} else {
LogPrint(BCLog::MEMPOOL,
"not keeping orphan with rejected parents %s\n",
tx.GetId().ToString());
// We will continue to reject this tx since it has rejected
// parents so avoid re-requesting it from other peers.
recentRejects->insert(tx.GetId());
}
} else {
assert(recentRejects);
recentRejects->insert(tx.GetId());
if (RecursiveDynamicUsage(*ptx) < 100000) {
AddToCompactExtraTransactions(ptx);
}
if (pfrom.HasPermission(PF_FORCERELAY)) {
// Always relay transactions received from whitelisted peers,
// even if they were already in the mempool or rejected from it
// due to policy, allowing the node to function as a gateway for
// nodes hidden behind it.
//
// Never relay transactions that might result in being
// disconnected (or banned).
if (state.IsInvalid() && TxRelayMayResultInDisconnect(state)) {
LogPrintf("Not relaying invalid transaction %s from "
"whitelisted peer=%d (%s)\n",
tx.GetId().ToString(), pfrom.GetId(),
state.ToString());
} else {
LogPrintf("Force relaying tx %s from whitelisted peer=%d\n",
tx.GetId().ToString(), pfrom.GetId());
RelayTransaction(tx.GetId(), connman);
}
}
}
// If a tx has been detected by recentRejects, we will have reached
// this point and the tx will have been ignored. Because we haven't run
// the tx through AcceptToMemoryPool, we won't have computed a DoS
// score for it or determined exactly why we consider it invalid.
//
// This means we won't penalize any peer subsequently relaying a DoSy
// tx (even if we penalized the first peer who gave it to us) because
// we have to account for recentRejects showing false positives. In
// other words, we shouldn't penalize a peer if we aren't *sure* they
// submitted a DoSy tx.
//
// Note that recentRejects doesn't just record DoSy or invalid
// transactions, but any tx not accepted by the mempool, which may be
// due to node policy (vs. consensus). So we can't blanket penalize a
// peer simply for relaying a tx that our recentRejects has caught,
// regardless of false positives.
if (state.IsInvalid()) {
LogPrint(BCLog::MEMPOOLREJ,
"%s from peer=%d was not accepted: %s\n",
tx.GetHash().ToString(), pfrom.GetId(), state.ToString());
MaybePunishNodeForTx(pfrom.GetId(), state);
}
return true;
}
if (msg_type == NetMsgType::CMPCTBLOCK) {
// Ignore cmpctblock received while importing
if (fImporting || fReindex) {
LogPrint(BCLog::NET,
"Unexpected cmpctblock message received from peer %d\n",
pfrom.GetId());
return true;
}
CBlockHeaderAndShortTxIDs cmpctblock;
vRecv >> cmpctblock;
bool received_new_header = false;
{
LOCK(cs_main);
if (!LookupBlockIndex(cmpctblock.header.hashPrevBlock)) {
// Doesn't connect (or is genesis), instead of DoSing in
// AcceptBlockHeader, request deeper headers
if (!::ChainstateActive().IsInitialBlockDownload()) {
connman.PushMessage(
&pfrom, msgMaker.Make(NetMsgType::GETHEADERS,
::ChainActive().GetLocator(
pindexBestHeader),
uint256()));
}
return true;
}
if (!LookupBlockIndex(cmpctblock.header.GetHash())) {
received_new_header = true;
}
}
const CBlockIndex *pindex = nullptr;
BlockValidationState state;
if (!ProcessNewBlockHeaders(config, {cmpctblock.header}, state,
&pindex)) {
if (state.IsInvalid()) {
MaybePunishNodeForBlock(pfrom.GetId(), state,
/*via_compact_block*/ true,
"invalid header via cmpctblock");
return true;
}
}
// When we succeed in decoding a block's txids from a cmpctblock
// message we typically jump to the BLOCKTXN handling code, with a
// dummy (empty) BLOCKTXN message, to re-use the logic there in
// completing processing of the putative block (without cs_main).
bool fProcessBLOCKTXN = false;
CDataStream blockTxnMsg(SER_NETWORK, PROTOCOL_VERSION);
// If we end up treating this as a plain headers message, call that as
// well
// without cs_main.
bool fRevertToHeaderProcessing = false;
// Keep a CBlock for "optimistic" compactblock reconstructions (see
// below)
std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>();
bool fBlockReconstructed = false;
{
LOCK2(cs_main, g_cs_orphans);
// If AcceptBlockHeader returned true, it set pindex
assert(pindex);
UpdateBlockAvailability(pfrom.GetId(), pindex->GetBlockHash());
CNodeState *nodestate = State(pfrom.GetId());
// If this was a new header with more work than our tip, update the
// peer's last block announcement time
if (received_new_header &&
pindex->nChainWork > ::ChainActive().Tip()->nChainWork) {
nodestate->m_last_block_announcement = GetTime();
}
std::map<BlockHash,
std::pair<NodeId, std::list<QueuedBlock>::iterator>>::
iterator blockInFlightIt =
mapBlocksInFlight.find(pindex->GetBlockHash());
bool fAlreadyInFlight = blockInFlightIt != mapBlocksInFlight.end();
if (pindex->nStatus.hasData()) {
// Nothing to do here
return true;
}
if (pindex->nChainWork <=
::ChainActive()
.Tip()
->nChainWork || // We know something better
pindex->nTx != 0) {
// We had this block at some point, but pruned it
if (fAlreadyInFlight) {
// We requested this block for some reason, but our mempool
// will probably be useless so we just grab the block via
// normal getdata.
std::vector<CInv> vInv(1);
vInv[0] = CInv(MSG_BLOCK, cmpctblock.header.GetHash());
connman.PushMessage(
&pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv));
}
return true;
}
// If we're not close to tip yet, give up and let parallel block
// fetch work its magic.
if (!fAlreadyInFlight &&
!CanDirectFetch(chainparams.GetConsensus())) {
return true;
}
// We want to be a bit conservative just to be extra careful about
// DoS possibilities in compact block processing...
if (pindex->nHeight <= ::ChainActive().Height() + 2) {
if ((!fAlreadyInFlight && nodestate->nBlocksInFlight <
MAX_BLOCKS_IN_TRANSIT_PER_PEER) ||
(fAlreadyInFlight &&
blockInFlightIt->second.first == pfrom.GetId())) {
std::list<QueuedBlock>::iterator *queuedBlockIt = nullptr;
if (!MarkBlockAsInFlight(config, pfrom.GetId(),
pindex->GetBlockHash(),
chainparams.GetConsensus(), pindex,
&queuedBlockIt)) {
if (!(*queuedBlockIt)->partialBlock) {
(*queuedBlockIt)
->partialBlock.reset(
new PartiallyDownloadedBlock(config,
&g_mempool));
} else {
// The block was already in flight using compact
// blocks from the same peer.
LogPrint(BCLog::NET, "Peer sent us compact block "
"we were already syncing!\n");
return true;
}
}
PartiallyDownloadedBlock &partialBlock =
*(*queuedBlockIt)->partialBlock;
ReadStatus status =
partialBlock.InitData(cmpctblock, vExtraTxnForCompact);
if (status == READ_STATUS_INVALID) {
// Reset in-flight state in case of whitelist
MarkBlockAsReceived(pindex->GetBlockHash());
Misbehaving(pfrom, 100, "invalid-cmpctblk");
LogPrintf("Peer %d sent us invalid compact block\n",
pfrom.GetId());
return true;
} else if (status == READ_STATUS_FAILED) {
// Duplicate txindices, the block is now in-flight, so
// just request it.
std::vector<CInv> vInv(1);
vInv[0] = CInv(MSG_BLOCK, cmpctblock.header.GetHash());
connman.PushMessage(
&pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv));
return true;
}
BlockTransactionsRequest req;
for (size_t i = 0; i < cmpctblock.BlockTxCount(); i++) {
if (!partialBlock.IsTxAvailable(i)) {
req.indices.push_back(i);
}
}
if (req.indices.empty()) {
// Dirty hack to jump to BLOCKTXN code (TODO: move
// message handling into their own functions)
BlockTransactions txn;
txn.blockhash = cmpctblock.header.GetHash();
blockTxnMsg << txn;
fProcessBLOCKTXN = true;
} else {
req.blockhash = pindex->GetBlockHash();
connman.PushMessage(
&pfrom,
msgMaker.Make(NetMsgType::GETBLOCKTXN, req));
}
} else {
// This block is either already in flight from a different
// peer, or this peer has too many blocks outstanding to
// download from. Optimistically try to reconstruct anyway
// since we might be able to without any round trips.
PartiallyDownloadedBlock tempBlock(config, &g_mempool);
ReadStatus status =
tempBlock.InitData(cmpctblock, vExtraTxnForCompact);
if (status != READ_STATUS_OK) {
// TODO: don't ignore failures
return true;
}
std::vector<CTransactionRef> dummy;
status = tempBlock.FillBlock(*pblock, dummy);
if (status == READ_STATUS_OK) {
fBlockReconstructed = true;
}
}
} else {
if (fAlreadyInFlight) {
// We requested this block, but its far into the future, so
// our mempool will probably be useless - request the block
// normally.
std::vector<CInv> vInv(1);
vInv[0] = CInv(MSG_BLOCK, cmpctblock.header.GetHash());
connman.PushMessage(
&pfrom, msgMaker.Make(NetMsgType::GETDATA, vInv));
return true;
} else {
// If this was an announce-cmpctblock, we want the same
// treatment as a header message.
fRevertToHeaderProcessing = true;
}
}
} // cs_main
if (fProcessBLOCKTXN) {
return ProcessMessage(config, pfrom, NetMsgType::BLOCKTXN,
- blockTxnMsg, nTimeReceived, connman, banman,
- interruptMsgProc);
+ blockTxnMsg, nTimeReceived, chainman, connman,
+ banman, interruptMsgProc);
}
if (fRevertToHeaderProcessing) {
// Headers received from HB compact block peers are permitted to be
// relayed before full validation (see BIP 152), so we don't want to
// disconnect the peer if the header turns out to be for an invalid
// block. Note that if a peer tries to build on an invalid chain,
// that will be detected and the peer will be banned.
return ProcessHeadersMessage(config, pfrom, connman,
{cmpctblock.header},
/*via_compact_block=*/true);
}
if (fBlockReconstructed) {
// If we got here, we were able to optimistically reconstruct a
// block that is in flight from some other peer.
{
LOCK(cs_main);
mapBlockSource.emplace(pblock->GetHash(),
std::make_pair(pfrom.GetId(), false));
}
bool fNewBlock = false;
// Setting fForceProcessing to true means that we bypass some of
// our anti-DoS protections in AcceptBlock, which filters
// unrequested blocks that might be trying to waste our resources
// (eg disk space). Because we only try to reconstruct blocks when
// we're close to caught up (via the CanDirectFetch() requirement
// above, combined with the behavior of not requesting blocks until
// we have a chain with at least nMinimumChainWork), and we ignore
// compact blocks with less work than our tip, it is safe to treat
// reconstructed compact blocks as having been requested.
ProcessNewBlock(config, pblock, /*fForceProcessing=*/true,
&fNewBlock);
if (fNewBlock) {
pfrom.nLastBlockTime = GetTime();
} else {
LOCK(cs_main);
mapBlockSource.erase(pblock->GetHash());
}
// hold cs_main for CBlockIndex::IsValid()
LOCK(cs_main);
if (pindex->IsValid(BlockValidity::TRANSACTIONS)) {
// Clear download state for this block, which is in process from
// some other peer. We do this after calling. ProcessNewBlock so
// that a malleated cmpctblock announcement can't be used to
// interfere with block relay.
MarkBlockAsReceived(pblock->GetHash());
}
}
return true;
}
if (msg_type == NetMsgType::BLOCKTXN) {
// Ignore blocktxn received while importing
if (fImporting || fReindex) {
LogPrint(BCLog::NET,
"Unexpected blocktxn message received from peer %d\n",
pfrom.GetId());
return true;
}
BlockTransactions resp;
vRecv >> resp;
std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>();
bool fBlockRead = false;
{
LOCK(cs_main);
std::map<BlockHash,
std::pair<NodeId, std::list<QueuedBlock>::iterator>>::
iterator it = mapBlocksInFlight.find(resp.blockhash);
if (it == mapBlocksInFlight.end() ||
!it->second.second->partialBlock ||
it->second.first != pfrom.GetId()) {
LogPrint(BCLog::NET,
"Peer %d sent us block transactions for block "
"we weren't expecting\n",
pfrom.GetId());
return true;
}
PartiallyDownloadedBlock &partialBlock =
*it->second.second->partialBlock;
ReadStatus status = partialBlock.FillBlock(*pblock, resp.txn);
if (status == READ_STATUS_INVALID) {
// Reset in-flight state in case of whitelist.
MarkBlockAsReceived(resp.blockhash);
Misbehaving(pfrom, 100, "invalid-cmpctblk-txns");
LogPrintf("Peer %d sent us invalid compact block/non-matching "
"block transactions\n",
pfrom.GetId());
return true;
} else if (status == READ_STATUS_FAILED) {
// Might have collided, fall back to getdata now :(
std::vector<CInv> invs;
invs.push_back(CInv(MSG_BLOCK, resp.blockhash));
connman.PushMessage(&pfrom,
msgMaker.Make(NetMsgType::GETDATA, invs));
} else {
// Block is either okay, or possibly we received
// READ_STATUS_CHECKBLOCK_FAILED.
// Note that CheckBlock can only fail for one of a few reasons:
// 1. bad-proof-of-work (impossible here, because we've already
// accepted the header)
// 2. merkleroot doesn't match the transactions given (already
// caught in FillBlock with READ_STATUS_FAILED, so
// impossible here)
// 3. the block is otherwise invalid (eg invalid coinbase,
// block is too big, too many legacy sigops, etc).
// So if CheckBlock failed, #3 is the only possibility.
// Under BIP 152, we don't DoS-ban unless proof of work is
// invalid (we don't require all the stateless checks to have
// been run). This is handled below, so just treat this as
// though the block was successfully read, and rely on the
// handling in ProcessNewBlock to ensure the block index is
// updated, etc.
// it is now an empty pointer
MarkBlockAsReceived(resp.blockhash);
fBlockRead = true;
// mapBlockSource is used for potentially punishing peers and
// updating which peers send us compact blocks, so the race
// between here and cs_main in ProcessNewBlock is fine.
// BIP 152 permits peers to relay compact blocks after
// validating the header only; we should not punish peers
// if the block turns out to be invalid.
mapBlockSource.emplace(resp.blockhash,
std::make_pair(pfrom.GetId(), false));
}
} // Don't hold cs_main when we call into ProcessNewBlock
if (fBlockRead) {
bool fNewBlock = false;
// Since we requested this block (it was in mapBlocksInFlight),
// force it to be processed, even if it would not be a candidate for
// new tip (missing previous block, chain not long enough, etc)
// This bypasses some anti-DoS logic in AcceptBlock (eg to prevent
// disk-space attacks), but this should be safe due to the
// protections in the compact block handler -- see related comment
// in compact block optimistic reconstruction handling.
ProcessNewBlock(config, pblock, /*fForceProcessing=*/true,
&fNewBlock);
if (fNewBlock) {
pfrom.nLastBlockTime = GetTime();
} else {
LOCK(cs_main);
mapBlockSource.erase(pblock->GetHash());
}
}
return true;
}
if (msg_type == NetMsgType::HEADERS) {
// Ignore headers received while importing
if (fImporting || fReindex) {
LogPrint(BCLog::NET,
"Unexpected headers message received from peer %d\n",
pfrom.GetId());
return true;
}
std::vector<CBlockHeader> headers;
// Bypass the normal CBlock deserialization, as we don't want to risk
// deserializing 2000 full blocks.
unsigned int nCount = ReadCompactSize(vRecv);
if (nCount > MAX_HEADERS_RESULTS) {
LOCK(cs_main);
Misbehaving(pfrom, 20, "too-many-headers");
return error("headers message size = %u", nCount);
}
headers.resize(nCount);
for (unsigned int n = 0; n < nCount; n++) {
vRecv >> headers[n];
// Ignore tx count; assume it is 0.
ReadCompactSize(vRecv);
}
return ProcessHeadersMessage(config, pfrom, connman, headers,
/*via_compact_block=*/false);
}
if (msg_type == NetMsgType::BLOCK) {
// Ignore block received while importing
if (fImporting || fReindex) {
LogPrint(BCLog::NET,
"Unexpected block message received from peer %d\n",
pfrom.GetId());
return true;
}
std::shared_ptr<CBlock> pblock = std::make_shared<CBlock>();
vRecv >> *pblock;
LogPrint(BCLog::NET, "received block %s peer=%d\n",
pblock->GetHash().ToString(), pfrom.GetId());
// Process all blocks from whitelisted peers, even if not requested,
// unless we're still syncing with the network. Such an unrequested
// block may still be processed, subject to the conditions in
// AcceptBlock().
bool forceProcessing = pfrom.HasPermission(PF_NOBAN) &&
!::ChainstateActive().IsInitialBlockDownload();
const BlockHash 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);
// mapBlockSource is only used for punishing peers and setting
// which peers send us compact blocks, 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(config, pblock, forceProcessing, &fNewBlock);
if (fNewBlock) {
pfrom.nLastBlockTime = GetTime();
} else {
LOCK(cs_main);
mapBlockSource.erase(hash);
}
return true;
}
// Ignore avalanche requests while importing
if (msg_type == NetMsgType::AVAPOLL && !fImporting && !fReindex &&
g_avalanche &&
gArgs.GetBoolArg("-enableavalanche", AVALANCHE_DEFAULT_ENABLED)) {
auto now = std::chrono::steady_clock::now();
int64_t cooldown =
gArgs.GetArg("-avacooldown", AVALANCHE_DEFAULT_COOLDOWN);
{
LOCK(cs_main);
auto &node_state = State(pfrom.GetId())->m_avalanche_state;
if (now <
node_state.last_poll + std::chrono::milliseconds(cooldown)) {
Misbehaving(pfrom, 20, "avapool-cooldown");
}
node_state.last_poll = now;
}
uint64_t round;
Unserialize(vRecv, round);
unsigned int nCount = ReadCompactSize(vRecv);
if (nCount > AVALANCHE_MAX_ELEMENT_POLL) {
LOCK(cs_main);
Misbehaving(pfrom, 20, "too-many-ava-poll");
return error("poll message size = %u", nCount);
}
std::vector<avalanche::Vote> votes;
votes.reserve(nCount);
LogPrint(BCLog::NET, "received avalanche poll from peer=%d\n",
pfrom.GetId());
{
LOCK(cs_main);
for (unsigned int n = 0; n < nCount; n++) {
CInv inv;
vRecv >> inv;
const auto insertVote = [&](uint32_t e) {
votes.emplace_back(e, inv.hash);
};
// Not a block.
if (inv.type != MSG_BLOCK) {
insertVote(-1);
continue;
}
// We have a block.
const CBlockIndex *pindex =
LookupBlockIndex(BlockHash(inv.hash));
// Unknown block.
if (!pindex) {
insertVote(-1);
continue;
}
// Invalid block
if (pindex->nStatus.isInvalid()) {
insertVote(1);
continue;
}
// Parked block
if (pindex->nStatus.isOnParkedChain()) {
insertVote(2);
continue;
}
const CBlockIndex *pindexTip = ::ChainActive().Tip();
const CBlockIndex *pindexFork =
LastCommonAncestor(pindex, pindexTip);
// Active block.
if (pindex == pindexFork) {
insertVote(0);
continue;
}
// Fork block.
if (pindexFork != pindexTip) {
insertVote(3);
continue;
}
// Missing block data.
if (!pindex->nStatus.hasData()) {
insertVote(-2);
continue;
}
// This block is built on top of the tip, we have the data, it
// is pending connection or rejection.
insertVote(-3);
}
}
// Send the query to the node.
g_avalanche->sendResponse(
&pfrom, avalanche::Response(round, cooldown, std::move(votes)));
return true;
}
// Ignore avalanche requests while importing
if (msg_type == NetMsgType::AVARESPONSE && !fImporting && !fReindex &&
g_avalanche &&
gArgs.GetBoolArg("-enableavalanche", AVALANCHE_DEFAULT_ENABLED)) {
// As long as QUIC is not implemented, we need to sign response and
// verify response's signatures in order to avoid any manipulation of
// messages at the transport level.
CHashVerifier<CDataStream> verifier(&vRecv);
avalanche::Response response;
verifier >> response;
if (!g_avalanche->forNode(pfrom.GetId(), [&](const avalanche::Node &n) {
std::array<uint8_t, 64> sig;
vRecv >> sig;
// Unfortunately, the verify API require a vector.
std::vector<uint8_t> vchSig{sig.begin(), sig.end()};
return n.pubkey.VerifySchnorr(verifier.GetHash(), vchSig);
})) {
LOCK(cs_main);
Misbehaving(pfrom, 100, "invalid-ava-response-signature");
return true;
}
std::vector<avalanche::BlockUpdate> updates;
if (!g_avalanche->registerVotes(pfrom.GetId(), response, updates)) {
LOCK(cs_main);
Misbehaving(pfrom, 100, "invalid-ava-response-content");
return true;
}
if (updates.size()) {
for (avalanche::BlockUpdate &u : updates) {
CBlockIndex *pindex = u.getBlockIndex();
switch (u.getStatus()) {
case avalanche::BlockUpdate::Status::Invalid:
case avalanche::BlockUpdate::Status::Rejected: {
LogPrintf("Avalanche rejected %s, parking\n",
pindex->GetBlockHash().GetHex());
BlockValidationState state;
::ChainstateActive().ParkBlock(config, state, pindex);
if (!state.IsValid()) {
return error("Database error: %s",
state.GetRejectReason());
}
} break;
case avalanche::BlockUpdate::Status::Accepted:
case avalanche::BlockUpdate::Status::Finalized: {
LogPrintf("Avalanche accepted %s\n",
pindex->GetBlockHash().GetHex());
LOCK(cs_main);
UnparkBlock(pindex);
} break;
}
}
BlockValidationState state;
if (!ActivateBestChain(config, state)) {
LogPrint(BCLog::NET, "failed to activate chain (%s)\n",
state.ToString());
}
}
return true;
}
if (msg_type == NetMsgType::GETADDR) {
// This asymmetric behavior for inbound and outbound connections was
// introduced to prevent a fingerprinting attack: an attacker can send
// specific fake addresses to users' AddrMan and later request them by
// sending getaddr messages. Making nodes which are behind NAT and can
// only make outgoing connections ignore the getaddr message mitigates
// the attack.
if (!pfrom.fInbound) {
LogPrint(BCLog::NET,
"Ignoring \"getaddr\" from outbound connection. peer=%d\n",
pfrom.GetId());
return true;
}
if (!pfrom.IsAddrRelayPeer()) {
LogPrint(BCLog::NET,
"Ignoring \"getaddr\" from block-relay-only connection. "
"peer=%d\n",
pfrom.GetId());
return true;
}
// Only send one GetAddr response per connection to reduce resource
// waste and discourage addr stamping of INV announcements.
if (pfrom.fSentAddr) {
LogPrint(BCLog::NET, "Ignoring repeated \"getaddr\". peer=%d\n",
pfrom.GetId());
return true;
}
pfrom.fSentAddr = true;
pfrom.vAddrToSend.clear();
std::vector<CAddress> vAddr = connman.GetAddresses();
FastRandomContext insecure_rand;
for (const CAddress &addr : vAddr) {
if (!banman->IsDiscouraged(addr) && !banman->IsBanned(addr)) {
pfrom.PushAddress(addr, insecure_rand);
}
}
return true;
}
if (msg_type == NetMsgType::MEMPOOL) {
if (!(pfrom.GetLocalServices() & NODE_BLOOM) &&
!pfrom.HasPermission(PF_MEMPOOL)) {
if (!pfrom.HasPermission(PF_NOBAN)) {
LogPrint(BCLog::NET,
"mempool request with bloom filters disabled, "
"disconnect peer=%d\n",
pfrom.GetId());
pfrom.fDisconnect = true;
}
return true;
}
if (connman.OutboundTargetReached(false) &&
!pfrom.HasPermission(PF_MEMPOOL)) {
if (!pfrom.HasPermission(PF_NOBAN)) {
LogPrint(BCLog::NET,
"mempool request with bandwidth limit reached, "
"disconnect peer=%d\n",
pfrom.GetId());
pfrom.fDisconnect = true;
}
return true;
}
if (pfrom.m_tx_relay != nullptr) {
LOCK(pfrom.m_tx_relay->cs_tx_inventory);
pfrom.m_tx_relay->fSendMempool = true;
}
return true;
}
if (msg_type == NetMsgType::PING) {
if (pfrom.nVersion > BIP0031_VERSION) {
uint64_t nonce = 0;
vRecv >> nonce;
// Echo the message back with the nonce. This allows for two useful
// features:
//
// 1) A remote node can quickly check if the connection is
// operational.
// 2) Remote nodes can measure the latency of the network thread. If
// this node is overloaded it won't respond to pings quickly and the
// remote node can avoid sending us more work, like chain download
// requests.
//
// The nonce stops the remote getting confused between different
// pings: without it, if the remote node sends a ping once per
// second and this node takes 5 seconds to respond to each, the 5th
// ping the remote sends would appear to return very quickly.
connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::PONG, nonce));
}
return true;
}
if (msg_type == NetMsgType::PONG) {
int64_t pingUsecEnd = nTimeReceived;
uint64_t nonce = 0;
size_t nAvail = vRecv.in_avail();
bool bPingFinished = false;
std::string sProblem;
if (nAvail >= sizeof(nonce)) {
vRecv >> nonce;
// Only process pong message if there is an outstanding ping (old
// ping without nonce should never pong)
if (pfrom.nPingNonceSent != 0) {
if (nonce == pfrom.nPingNonceSent) {
// Matching pong received, this ping is no longer
// outstanding
bPingFinished = true;
int64_t pingUsecTime = pingUsecEnd - pfrom.nPingUsecStart;
if (pingUsecTime > 0) {
// Successful ping time measurement, replace previous
pfrom.nPingUsecTime = pingUsecTime;
pfrom.nMinPingUsecTime = std::min(
pfrom.nMinPingUsecTime.load(), pingUsecTime);
} else {
// This should never happen
sProblem = "Timing mishap";
}
} else {
// Nonce mismatches are normal when pings are overlapping
sProblem = "Nonce mismatch";
if (nonce == 0) {
// This is most likely a bug in another implementation
// somewhere; cancel this ping
bPingFinished = true;
sProblem = "Nonce zero";
}
}
} else {
sProblem = "Unsolicited pong without ping";
}
} else {
// This is most likely a bug in another implementation somewhere;
// cancel this ping
bPingFinished = true;
sProblem = "Short payload";
}
if (!(sProblem.empty())) {
LogPrint(BCLog::NET,
"pong peer=%d: %s, %x expected, %x received, %u bytes\n",
pfrom.GetId(), sProblem, pfrom.nPingNonceSent, nonce,
nAvail);
}
if (bPingFinished) {
pfrom.nPingNonceSent = 0;
}
return true;
}
if (msg_type == NetMsgType::FILTERLOAD) {
CBloomFilter filter;
vRecv >> filter;
if (!filter.IsWithinSizeConstraints()) {
// There is no excuse for sending a too-large filter
LOCK(cs_main);
Misbehaving(pfrom, 100, "oversized-bloom-filter");
} else if (pfrom.m_tx_relay != nullptr) {
LOCK(pfrom.m_tx_relay->cs_filter);
pfrom.m_tx_relay->pfilter.reset(new CBloomFilter(filter));
pfrom.m_tx_relay->pfilter->UpdateEmptyFull();
pfrom.m_tx_relay->fRelayTxes = true;
}
return true;
}
if (msg_type == NetMsgType::FILTERADD) {
std::vector<uint8_t> vData;
vRecv >> vData;
// Nodes must NEVER send a data item > 520 bytes (the max size for a
// script data object, and thus, the maximum size any matched object can
// have) in a filteradd message.
bool bad = false;
if (vData.size() > MAX_SCRIPT_ELEMENT_SIZE) {
bad = true;
} else if (pfrom.m_tx_relay != nullptr) {
LOCK(pfrom.m_tx_relay->cs_filter);
if (pfrom.m_tx_relay->pfilter) {
pfrom.m_tx_relay->pfilter->insert(vData);
} else {
bad = true;
}
}
if (bad) {
LOCK(cs_main);
// The structure of this code doesn't really allow for a good error
// code. We'll go generic.
Misbehaving(pfrom, 100, "invalid-filteradd");
}
return true;
}
if (msg_type == NetMsgType::FILTERCLEAR) {
if (pfrom.m_tx_relay == nullptr) {
return true;
}
LOCK(pfrom.m_tx_relay->cs_filter);
if (pfrom.GetLocalServices() & NODE_BLOOM) {
pfrom.m_tx_relay->pfilter.reset(new CBloomFilter());
}
pfrom.m_tx_relay->fRelayTxes = true;
return true;
}
if (msg_type == NetMsgType::FEEFILTER) {
Amount newFeeFilter = Amount::zero();
vRecv >> newFeeFilter;
if (MoneyRange(newFeeFilter)) {
if (pfrom.m_tx_relay != nullptr) {
LOCK(pfrom.m_tx_relay->cs_feeFilter);
pfrom.m_tx_relay->minFeeFilter = newFeeFilter;
}
LogPrint(BCLog::NET, "received: feefilter of %s from peer=%d\n",
CFeeRate(newFeeFilter).ToString(), pfrom.GetId());
}
return true;
}
if (msg_type == NetMsgType::GETCFILTERS) {
ProcessGetCFilters(pfrom, vRecv, chainparams, connman);
return true;
}
if (msg_type == NetMsgType::GETCFHEADERS) {
ProcessGetCFHeaders(pfrom, vRecv, chainparams, connman);
return true;
}
if (msg_type == NetMsgType::GETCFCHECKPT) {
ProcessGetCFCheckPt(pfrom, vRecv, chainparams, connman);
return true;
}
if (msg_type == NetMsgType::NOTFOUND) {
// Remove the NOTFOUND transactions from the peer
LOCK(cs_main);
CNodeState *state = State(pfrom.GetId());
std::vector<CInv> vInv;
vRecv >> vInv;
if (vInv.size() <=
MAX_PEER_TX_IN_FLIGHT + MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
for (CInv &inv : vInv) {
if (inv.type == MSG_TX) {
const TxId txid(inv.hash);
// If we receive a NOTFOUND message for a txid we requested,
// erase it from our data structures for this peer.
auto in_flight_it =
state->m_tx_download.m_tx_in_flight.find(txid);
if (in_flight_it ==
state->m_tx_download.m_tx_in_flight.end()) {
// Skip any further work if this is a spurious NOTFOUND
// message.
continue;
}
state->m_tx_download.m_tx_in_flight.erase(in_flight_it);
state->m_tx_download.m_tx_announced.erase(txid);
}
}
}
return true;
}
// Ignore unknown commands for extensibility
LogPrint(BCLog::NET, "Unknown command \"%s\" from peer=%d\n",
SanitizeString(msg_type), pfrom.GetId());
return true;
}
bool PeerLogicValidation::CheckIfBanned(CNode &pnode) {
AssertLockHeld(cs_main);
CNodeState &state = *State(pnode.GetId());
if (state.m_should_discourage) {
state.m_should_discourage = false;
if (pnode.HasPermission(PF_NOBAN)) {
LogPrintf("Warning: not punishing whitelisted peer %s!\n",
pnode.addr.ToString());
} else if (pnode.m_manual_connection) {
LogPrintf("Warning: not punishing manually-connected peer %s!\n",
pnode.addr.ToString());
} else if (pnode.addr.IsLocal()) {
// Disconnect but don't discourage this local node
LogPrintf(
"Warning: disconnecting but not discouraging local peer %s!\n",
pnode.addr.ToString());
pnode.fDisconnect = true;
} else {
// Disconnect and discourage all nodes sharing the address
LogPrintf("Disconnecting and discouraging peer %s!\n",
pnode.addr.ToString());
if (m_banman) {
m_banman->Discourage(pnode.addr);
}
connman->DisconnectNode(pnode.addr);
}
return true;
}
return false;
}
bool PeerLogicValidation::ProcessMessages(const Config &config, CNode *pfrom,
std::atomic<bool> &interruptMsgProc) {
//
// Message format
// (4) message start
// (12) command
// (4) size
// (4) checksum
// (x) data
//
bool fMoreWork = false;
if (!pfrom->vRecvGetData.empty()) {
ProcessGetData(config, *pfrom, *connman, interruptMsgProc);
}
if (!pfrom->orphan_work_set.empty()) {
LOCK2(cs_main, g_cs_orphans);
ProcessOrphanTx(config, *connman, pfrom->orphan_work_set);
}
if (pfrom->fDisconnect) {
return false;
}
// this maintains the order of responses and prevents vRecvGetData from
// growing unbounded
if (!pfrom->vRecvGetData.empty()) {
return true;
}
if (!pfrom->orphan_work_set.empty()) {
return true;
}
// Don't bother if send buffer is too full to respond anyway
if (pfrom->fPauseSend) {
return false;
}
std::list<CNetMessage> msgs;
{
LOCK(pfrom->cs_vProcessMsg);
if (pfrom->vProcessMsg.empty()) {
return false;
}
// Just take one message
msgs.splice(msgs.begin(), pfrom->vProcessMsg,
pfrom->vProcessMsg.begin());
pfrom->nProcessQueueSize -= msgs.front().m_raw_message_size;
pfrom->fPauseRecv =
pfrom->nProcessQueueSize > connman->GetReceiveFloodSize();
fMoreWork = !pfrom->vProcessMsg.empty();
}
CNetMessage &msg(msgs.front());
msg.SetVersion(pfrom->GetRecvVersion());
// Check network magic
if (!msg.m_valid_netmagic) {
LogPrint(BCLog::NET,
"PROCESSMESSAGE: INVALID MESSAGESTART %s peer=%d\n",
SanitizeString(msg.m_command), pfrom->GetId());
// Make sure we discourage where that come from for some time.
if (m_banman) {
m_banman->Discourage(pfrom->addr);
}
connman->DisconnectNode(pfrom->addr);
pfrom->fDisconnect = true;
return false;
}
// Check header
if (!msg.m_valid_header) {
LogPrint(BCLog::NET, "PROCESSMESSAGE: ERRORS IN HEADER %s peer=%d\n",
SanitizeString(msg.m_command), pfrom->GetId());
return fMoreWork;
}
const std::string &msg_type = msg.m_command;
// Message size
unsigned int nMessageSize = msg.m_message_size;
// Checksum
CDataStream &vRecv = msg.m_recv;
if (!msg.m_valid_checksum) {
LogPrint(BCLog::NET, "%s(%s, %u bytes): CHECKSUM ERROR peer=%d\n",
__func__, SanitizeString(msg_type), nMessageSize,
pfrom->GetId());
if (m_banman) {
m_banman->Discourage(pfrom->addr);
}
connman->DisconnectNode(pfrom->addr);
return fMoreWork;
}
// Process message
bool fRet = false;
try {
fRet = ProcessMessage(config, *pfrom, msg_type, vRecv, msg.m_time,
- *connman, m_banman, interruptMsgProc);
+ m_chainman, *connman, m_banman, interruptMsgProc);
if (interruptMsgProc) {
return false;
}
if (!pfrom->vRecvGetData.empty()) {
fMoreWork = true;
}
} catch (const std::exception &e) {
LogPrint(BCLog::NET, "%s(%s, %u bytes): Exception '%s' (%s) caught\n",
__func__, SanitizeString(msg_type), nMessageSize, e.what(),
typeid(e).name());
} catch (...) {
LogPrint(BCLog::NET, "%s(%s, %u bytes): Unknown exception caught\n",
__func__, SanitizeString(msg_type), nMessageSize);
}
if (!fRet) {
LogPrint(BCLog::NET, "%s(%s, %u bytes) FAILED peer=%d\n", __func__,
SanitizeString(msg_type), nMessageSize, pfrom->GetId());
}
LOCK(cs_main);
CheckIfBanned(*pfrom);
return fMoreWork;
}
void PeerLogicValidation::ConsiderEviction(CNode &pto,
int64_t time_in_seconds) {
AssertLockHeld(cs_main);
CNodeState &state = *State(pto.GetId());
const CNetMsgMaker msgMaker(pto.GetSendVersion());
if (!state.m_chain_sync.m_protect &&
IsOutboundDisconnectionCandidate(pto) && state.fSyncStarted) {
// This is an outbound peer subject to disconnection if they don't
// announce a block with as much work as the current tip within
// CHAIN_SYNC_TIMEOUT + HEADERS_RESPONSE_TIME seconds (note: if their
// chain has more work than ours, we should sync to it, unless it's
// invalid, in which case we should find that out and disconnect from
// them elsewhere).
if (state.pindexBestKnownBlock != nullptr &&
state.pindexBestKnownBlock->nChainWork >=
::ChainActive().Tip()->nChainWork) {
if (state.m_chain_sync.m_timeout != 0) {
state.m_chain_sync.m_timeout = 0;
state.m_chain_sync.m_work_header = nullptr;
state.m_chain_sync.m_sent_getheaders = false;
}
} else if (state.m_chain_sync.m_timeout == 0 ||
(state.m_chain_sync.m_work_header != nullptr &&
state.pindexBestKnownBlock != nullptr &&
state.pindexBestKnownBlock->nChainWork >=
state.m_chain_sync.m_work_header->nChainWork)) {
// Our best block known by this peer is behind our tip, and we're
// either noticing that for the first time, OR this peer was able to
// catch up to some earlier point where we checked against our tip.
// Either way, set a new timeout based on current tip.
state.m_chain_sync.m_timeout = time_in_seconds + CHAIN_SYNC_TIMEOUT;
state.m_chain_sync.m_work_header = ::ChainActive().Tip();
state.m_chain_sync.m_sent_getheaders = false;
} else if (state.m_chain_sync.m_timeout > 0 &&
time_in_seconds > state.m_chain_sync.m_timeout) {
// No evidence yet that our peer has synced to a chain with work
// equal to that of our tip, when we first detected it was behind.
// Send a single getheaders message to give the peer a chance to
// update us.
if (state.m_chain_sync.m_sent_getheaders) {
// They've run out of time to catch up!
LogPrintf(
"Disconnecting outbound peer %d for old chain, best known "
"block = %s\n",
pto.GetId(),
state.pindexBestKnownBlock != nullptr
? state.pindexBestKnownBlock->GetBlockHash().ToString()
: "<none>");
pto.fDisconnect = true;
} else {
assert(state.m_chain_sync.m_work_header);
LogPrint(
BCLog::NET,
"sending getheaders to outbound peer=%d to verify chain "
"work (current best known block:%s, benchmark blockhash: "
"%s)\n",
pto.GetId(),
state.pindexBestKnownBlock != nullptr
? state.pindexBestKnownBlock->GetBlockHash().ToString()
: "<none>",
state.m_chain_sync.m_work_header->GetBlockHash()
.ToString());
connman->PushMessage(
&pto,
msgMaker.Make(NetMsgType::GETHEADERS,
::ChainActive().GetLocator(
state.m_chain_sync.m_work_header->pprev),
uint256()));
state.m_chain_sync.m_sent_getheaders = true;
// 2 minutes
constexpr int64_t HEADERS_RESPONSE_TIME = 120;
// Bump the timeout to allow a response, which could clear the
// timeout (if the response shows the peer has synced), reset
// the timeout (if the peer syncs to the required work but not
// to our tip), or result in disconnect (if we advance to the
// timeout and pindexBestKnownBlock has not sufficiently
// progressed)
state.m_chain_sync.m_timeout =
time_in_seconds + HEADERS_RESPONSE_TIME;
}
}
}
}
void PeerLogicValidation::EvictExtraOutboundPeers(int64_t time_in_seconds) {
// Check whether we have too many outbound peers
int extra_peers = connman->GetExtraOutboundCount();
if (extra_peers <= 0) {
return;
}
// If we have more outbound peers than we target, disconnect one.
// Pick the outbound peer that least recently announced us a new block, with
// ties broken by choosing the more recent connection (higher node id)
NodeId worst_peer = -1;
int64_t oldest_block_announcement = std::numeric_limits<int64_t>::max();
connman->ForEachNode([&](CNode *pnode) {
AssertLockHeld(cs_main);
// Ignore non-outbound peers, or nodes marked for disconnect already
if (!IsOutboundDisconnectionCandidate(*pnode) || pnode->fDisconnect) {
return;
}
CNodeState *state = State(pnode->GetId());
if (state == nullptr) {
// shouldn't be possible, but just in case
return;
}
// Don't evict our protected peers
if (state->m_chain_sync.m_protect) {
return;
}
// Don't evict our block-relay-only peers.
if (pnode->m_tx_relay == nullptr) {
return;
}
if (state->m_last_block_announcement < oldest_block_announcement ||
(state->m_last_block_announcement == oldest_block_announcement &&
pnode->GetId() > worst_peer)) {
worst_peer = pnode->GetId();
oldest_block_announcement = state->m_last_block_announcement;
}
});
if (worst_peer == -1) {
return;
}
bool disconnected = connman->ForNode(worst_peer, [&](CNode *pnode) {
AssertLockHeld(cs_main);
// Only disconnect a peer that has been connected to us for some
// reasonable fraction of our check-frequency, to give it time for new
// information to have arrived.
// Also don't disconnect any peer we're trying to download a block from.
CNodeState &state = *State(pnode->GetId());
if (time_in_seconds - pnode->nTimeConnected > MINIMUM_CONNECT_TIME &&
state.nBlocksInFlight == 0) {
LogPrint(BCLog::NET,
"disconnecting extra outbound peer=%d (last block "
"announcement received at time %d)\n",
pnode->GetId(), oldest_block_announcement);
pnode->fDisconnect = true;
return true;
} else {
LogPrint(BCLog::NET,
"keeping outbound peer=%d chosen for eviction "
"(connect time: %d, blocks_in_flight: %d)\n",
pnode->GetId(), pnode->nTimeConnected,
state.nBlocksInFlight);
return false;
}
});
if (disconnected) {
// If we disconnected an extra peer, that means we successfully
// connected to at least one peer after the last time we detected a
// stale tip. Don't try any more extra peers until we next detect a
// stale tip, to limit the load we put on the network from these extra
// connections.
connman->SetTryNewOutboundPeer(false);
}
}
void PeerLogicValidation::CheckForStaleTipAndEvictPeers(
const Consensus::Params &consensusParams) {
LOCK(cs_main);
if (connman == nullptr) {
return;
}
int64_t time_in_seconds = GetTime();
EvictExtraOutboundPeers(time_in_seconds);
if (time_in_seconds <= m_stale_tip_check_time) {
return;
}
// Check whether our tip is stale, and if so, allow using an extra outbound
// peer.
if (!fImporting && !fReindex && connman->GetNetworkActive() &&
connman->GetUseAddrmanOutgoing() && TipMayBeStale(consensusParams)) {
LogPrintf("Potential stale tip detected, will try using extra outbound "
"peer (last tip update: %d seconds ago)\n",
time_in_seconds - g_last_tip_update);
connman->SetTryNewOutboundPeer(true);
} else if (connman->GetTryNewOutboundPeer()) {
connman->SetTryNewOutboundPeer(false);
}
m_stale_tip_check_time = time_in_seconds + STALE_CHECK_INTERVAL;
}
namespace {
class CompareInvMempoolOrder {
CTxMemPool *mp;
public:
explicit CompareInvMempoolOrder(CTxMemPool *_mempool) { mp = _mempool; }
bool operator()(std::set<TxId>::iterator a, std::set<TxId>::iterator b) {
/**
* As std::make_heap produces a max-heap, we want the entries with the
* fewest ancestors/highest fee to sort later.
*/
return mp->CompareDepthAndScore(*b, *a);
}
};
} // namespace
bool PeerLogicValidation::SendMessages(const Config &config, CNode *pto,
std::atomic<bool> &interruptMsgProc) {
const Consensus::Params &consensusParams =
config.GetChainParams().GetConsensus();
// Don't send anything until the version handshake is complete
if (!pto->fSuccessfullyConnected || pto->fDisconnect) {
return true;
}
// If we get here, the outgoing message serialization version is set and
// can't change.
const CNetMsgMaker msgMaker(pto->GetSendVersion());
//
// Message: ping
//
bool pingSend = false;
if (pto->fPingQueued) {
// RPC ping request by user
pingSend = true;
}
if (pto->nPingNonceSent == 0 &&
pto->nPingUsecStart + PING_INTERVAL * 1000000 < GetTimeMicros()) {
// Ping automatically sent as a latency probe & keepalive.
pingSend = true;
}
if (pingSend) {
uint64_t nonce = 0;
while (nonce == 0) {
GetRandBytes((uint8_t *)&nonce, sizeof(nonce));
}
pto->fPingQueued = false;
pto->nPingUsecStart = GetTimeMicros();
if (pto->nVersion > BIP0031_VERSION) {
pto->nPingNonceSent = nonce;
connman->PushMessage(pto, msgMaker.Make(NetMsgType::PING, nonce));
} else {
// Peer is too old to support ping command with nonce, pong will
// never arrive.
pto->nPingNonceSent = 0;
connman->PushMessage(pto, msgMaker.Make(NetMsgType::PING));
}
}
TRY_LOCK(cs_main, lockMain);
if (!lockMain) {
return true;
}
if (CheckIfBanned(*pto)) {
return true;
}
CNodeState &state = *State(pto->GetId());
// Address refresh broadcast
int64_t nNow = GetTimeMicros();
auto current_time = GetTime<std::chrono::microseconds>();
if (pto->IsAddrRelayPeer() &&
!::ChainstateActive().IsInitialBlockDownload() &&
pto->m_next_local_addr_send < current_time) {
AdvertiseLocal(pto);
pto->m_next_local_addr_send =
PoissonNextSend(current_time, AVG_LOCAL_ADDRESS_BROADCAST_INTERVAL);
}
//
// Message: addr
//
if (pto->IsAddrRelayPeer() && pto->m_next_addr_send < current_time) {
pto->m_next_addr_send =
PoissonNextSend(current_time, AVG_ADDRESS_BROADCAST_INTERVAL);
std::vector<CAddress> vAddr;
vAddr.reserve(pto->vAddrToSend.size());
assert(pto->m_addr_known);
for (const CAddress &addr : pto->vAddrToSend) {
if (!pto->m_addr_known->contains(addr.GetKey())) {
pto->m_addr_known->insert(addr.GetKey());
vAddr.push_back(addr);
// receiver rejects addr messages larger than 1000
if (vAddr.size() >= 1000) {
connman->PushMessage(
pto, msgMaker.Make(NetMsgType::ADDR, vAddr));
vAddr.clear();
}
}
}
pto->vAddrToSend.clear();
if (!vAddr.empty()) {
connman->PushMessage(pto, msgMaker.Make(NetMsgType::ADDR, vAddr));
}
// we only send the big addr message once
if (pto->vAddrToSend.capacity() > 40) {
pto->vAddrToSend.shrink_to_fit();
}
}
// Start block sync
if (pindexBestHeader == nullptr) {
pindexBestHeader = ::ChainActive().Tip();
}
// Download if this is a nice peer, or we have no nice peers and this one
// might do.
bool fFetch = state.fPreferredDownload ||
(nPreferredDownload == 0 && !pto->fClient && !pto->fOneShot);
if (!state.fSyncStarted && !pto->fClient && !fImporting && !fReindex) {
// Only actively request headers from a single peer, unless we're close
// to today.
if ((nSyncStarted == 0 && fFetch) ||
pindexBestHeader->GetBlockTime() >
GetAdjustedTime() - 24 * 60 * 60) {
state.fSyncStarted = true;
state.nHeadersSyncTimeout =
GetTimeMicros() + HEADERS_DOWNLOAD_TIMEOUT_BASE +
HEADERS_DOWNLOAD_TIMEOUT_PER_HEADER *
(GetAdjustedTime() - pindexBestHeader->GetBlockTime()) /
(consensusParams.nPowTargetSpacing);
nSyncStarted++;
const CBlockIndex *pindexStart = pindexBestHeader;
/**
* If possible, start at the block preceding the currently best
* known header. This ensures that we always get a non-empty list of
* headers back as long as the peer is up-to-date. With a non-empty
* response, we can initialise the peer's known best block. This
* wouldn't be possible if we requested starting at pindexBestHeader
* and got back an empty response.
*/
if (pindexStart->pprev) {
pindexStart = pindexStart->pprev;
}
LogPrint(BCLog::NET,
"initial getheaders (%d) to peer=%d (startheight:%d)\n",
pindexStart->nHeight, pto->GetId(), pto->nStartingHeight);
connman->PushMessage(
pto, msgMaker.Make(NetMsgType::GETHEADERS,
::ChainActive().GetLocator(pindexStart),
uint256()));
}
}
//
// Try sending block announcements via headers
//
{
// If we have less than MAX_BLOCKS_TO_ANNOUNCE in our list of block
// hashes we're relaying, and our peer wants headers announcements, then
// find the first header not yet known to our peer but would connect,
// and send. If no header would connect, or if we have too many blocks,
// or if the peer doesn't want headers, just add all to the inv queue.
LOCK(pto->cs_inventory);
std::vector<CBlock> vHeaders;
bool fRevertToInv =
((!state.fPreferHeaders &&
(!state.fPreferHeaderAndIDs ||
pto->vBlockHashesToAnnounce.size() > 1)) ||
pto->vBlockHashesToAnnounce.size() > MAX_BLOCKS_TO_ANNOUNCE);
// last header queued for delivery
const CBlockIndex *pBestIndex = nullptr;
// ensure pindexBestKnownBlock is up-to-date
ProcessBlockAvailability(pto->GetId());
if (!fRevertToInv) {
bool fFoundStartingHeader = false;
// Try to find first header that our peer doesn't have, and then
// send all headers past that one. If we come across an headers that
// aren't on ::ChainActive(), give up.
for (const BlockHash &hash : pto->vBlockHashesToAnnounce) {
const CBlockIndex *pindex = LookupBlockIndex(hash);
assert(pindex);
if (::ChainActive()[pindex->nHeight] != pindex) {
// Bail out if we reorged away from this block
fRevertToInv = true;
break;
}
if (pBestIndex != nullptr && pindex->pprev != pBestIndex) {
// This means that the list of blocks to announce don't
// connect to each other. This shouldn't really be possible
// to hit during regular operation (because reorgs should
// take us to a chain that has some block not on the prior
// chain, which should be caught by the prior check), but
// one way this could happen is by using invalidateblock /
// reconsiderblock repeatedly on the tip, causing it to be
// added multiple times to vBlockHashesToAnnounce. Robustly
// deal with this rare situation by reverting to an inv.
fRevertToInv = true;
break;
}
pBestIndex = pindex;
if (fFoundStartingHeader) {
// add this to the headers message
vHeaders.push_back(pindex->GetBlockHeader());
} else if (PeerHasHeader(&state, pindex)) {
// Keep looking for the first new block.
continue;
} else if (pindex->pprev == nullptr ||
PeerHasHeader(&state, pindex->pprev)) {
// Peer doesn't have this header but they do have the prior
// one.
// Start sending headers.
fFoundStartingHeader = true;
vHeaders.push_back(pindex->GetBlockHeader());
} else {
// Peer doesn't have this header or the prior one --
// nothing will connect, so bail out.
fRevertToInv = true;
break;
}
}
}
if (!fRevertToInv && !vHeaders.empty()) {
if (vHeaders.size() == 1 && state.fPreferHeaderAndIDs) {
// We only send up to 1 block as header-and-ids, as otherwise
// probably means we're doing an initial-ish-sync or they're
// slow.
LogPrint(BCLog::NET,
"%s sending header-and-ids %s to peer=%d\n", __func__,
vHeaders.front().GetHash().ToString(), pto->GetId());
int nSendFlags = 0;
bool fGotBlockFromCache = false;
{
LOCK(cs_most_recent_block);
if (most_recent_block_hash == pBestIndex->GetBlockHash()) {
CBlockHeaderAndShortTxIDs cmpctblock(
*most_recent_block);
connman->PushMessage(
pto,
msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK,
cmpctblock));
fGotBlockFromCache = true;
}
}
if (!fGotBlockFromCache) {
CBlock block;
bool ret =
ReadBlockFromDisk(block, pBestIndex, consensusParams);
assert(ret);
CBlockHeaderAndShortTxIDs cmpctblock(block);
connman->PushMessage(
pto, msgMaker.Make(nSendFlags, NetMsgType::CMPCTBLOCK,
cmpctblock));
}
state.pindexBestHeaderSent = pBestIndex;
} else if (state.fPreferHeaders) {
if (vHeaders.size() > 1) {
LogPrint(BCLog::NET,
"%s: %u headers, range (%s, %s), to peer=%d\n",
__func__, vHeaders.size(),
vHeaders.front().GetHash().ToString(),
vHeaders.back().GetHash().ToString(),
pto->GetId());
} else {
LogPrint(BCLog::NET, "%s: sending header %s to peer=%d\n",
__func__, vHeaders.front().GetHash().ToString(),
pto->GetId());
}
connman->PushMessage(
pto, msgMaker.Make(NetMsgType::HEADERS, vHeaders));
state.pindexBestHeaderSent = pBestIndex;
} else {
fRevertToInv = true;
}
}
if (fRevertToInv) {
// If falling back to using an inv, just try to inv the tip. The
// last entry in vBlockHashesToAnnounce was our tip at some point in
// the past.
if (!pto->vBlockHashesToAnnounce.empty()) {
const BlockHash &hashToAnnounce =
pto->vBlockHashesToAnnounce.back();
const CBlockIndex *pindex = LookupBlockIndex(hashToAnnounce);
assert(pindex);
// Warn if we're announcing a block that is not on the main
// chain. This should be very rare and could be optimized out.
// Just log for now.
if (::ChainActive()[pindex->nHeight] != pindex) {
LogPrint(BCLog::NET,
"Announcing block %s not on main chain (tip=%s)\n",
hashToAnnounce.ToString(),
::ChainActive().Tip()->GetBlockHash().ToString());
}
// If the peer's chain has this block, don't inv it back.
if (!PeerHasHeader(&state, pindex)) {
pto->PushInventory(CInv(MSG_BLOCK, hashToAnnounce));
LogPrint(BCLog::NET, "%s: sending inv peer=%d hash=%s\n",
__func__, pto->GetId(), hashToAnnounce.ToString());
}
}
}
pto->vBlockHashesToAnnounce.clear();
}
//
// Message: inventory
//
std::vector<CInv> vInv;
{
LOCK(pto->cs_inventory);
vInv.reserve(std::max<size_t>(pto->vInventoryBlockToSend.size(),
INVENTORY_BROADCAST_MAX_PER_MB *
config.GetMaxBlockSize() / 1000000));
// Add blocks
for (const BlockHash &hash : pto->vInventoryBlockToSend) {
vInv.push_back(CInv(MSG_BLOCK, hash));
if (vInv.size() == MAX_INV_SZ) {
connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv));
vInv.clear();
}
}
pto->vInventoryBlockToSend.clear();
if (pto->m_tx_relay != nullptr) {
LOCK(pto->m_tx_relay->cs_tx_inventory);
// Check whether periodic sends should happen
bool fSendTrickle = pto->HasPermission(PF_NOBAN);
if (pto->m_tx_relay->nNextInvSend < current_time) {
fSendTrickle = true;
if (pto->fInbound) {
pto->m_tx_relay->nNextInvSend = std::chrono::microseconds{
connman->PoissonNextSendInbound(
nNow, INVENTORY_BROADCAST_INTERVAL)};
} else {
// Skip delay for outbound peers, as there is less
// privacy concern for them.
pto->m_tx_relay->nNextInvSend = current_time;
}
}
// Time to send but the peer has requested we not relay
// transactions.
if (fSendTrickle) {
LOCK(pto->m_tx_relay->cs_filter);
if (!pto->m_tx_relay->fRelayTxes) {
pto->m_tx_relay->setInventoryTxToSend.clear();
}
}
// Respond to BIP35 mempool requests
if (fSendTrickle && pto->m_tx_relay->fSendMempool) {
auto vtxinfo = g_mempool.infoAll();
pto->m_tx_relay->fSendMempool = false;
CFeeRate filterrate;
{
LOCK(pto->m_tx_relay->cs_feeFilter);
filterrate = CFeeRate(pto->m_tx_relay->minFeeFilter);
}
LOCK(pto->m_tx_relay->cs_filter);
for (const auto &txinfo : vtxinfo) {
const TxId &txid = txinfo.tx->GetId();
CInv inv(MSG_TX, txid);
pto->m_tx_relay->setInventoryTxToSend.erase(txid);
// Don't send transactions that peers will not put into
// their mempool
if (txinfo.fee < filterrate.GetFee(txinfo.vsize)) {
continue;
}
if (pto->m_tx_relay->pfilter &&
!pto->m_tx_relay->pfilter->IsRelevantAndUpdate(
*txinfo.tx)) {
continue;
}
pto->m_tx_relay->filterInventoryKnown.insert(txid);
vInv.push_back(inv);
if (vInv.size() == MAX_INV_SZ) {
connman->PushMessage(
pto, msgMaker.Make(NetMsgType::INV, vInv));
vInv.clear();
}
}
pto->m_tx_relay->m_last_mempool_req =
GetTime<std::chrono::seconds>();
}
// Determine transactions to relay
if (fSendTrickle) {
// Produce a vector with all candidates for sending
std::vector<std::set<TxId>::iterator> vInvTx;
vInvTx.reserve(pto->m_tx_relay->setInventoryTxToSend.size());
for (std::set<TxId>::iterator it =
pto->m_tx_relay->setInventoryTxToSend.begin();
it != pto->m_tx_relay->setInventoryTxToSend.end(); it++) {
vInvTx.push_back(it);
}
CFeeRate filterrate;
{
LOCK(pto->m_tx_relay->cs_feeFilter);
filterrate = CFeeRate(pto->m_tx_relay->minFeeFilter);
}
// Topologically and fee-rate sort the inventory we send for
// privacy and priority reasons. A heap is used so that not all
// items need sorting if only a few are being sent.
CompareInvMempoolOrder compareInvMempoolOrder(&g_mempool);
std::make_heap(vInvTx.begin(), vInvTx.end(),
compareInvMempoolOrder);
// No reason to drain out at many times the network's capacity,
// especially since we have many peers and some will draw much
// shorter delays.
unsigned int nRelayedTransactions = 0;
LOCK(pto->m_tx_relay->cs_filter);
while (!vInvTx.empty() &&
nRelayedTransactions < INVENTORY_BROADCAST_MAX_PER_MB *
config.GetMaxBlockSize() /
1000000) {
// Fetch the top element from the heap
std::pop_heap(vInvTx.begin(), vInvTx.end(),
compareInvMempoolOrder);
std::set<TxId>::iterator it = vInvTx.back();
vInvTx.pop_back();
const TxId txid = *it;
// Remove it from the to-be-sent set
pto->m_tx_relay->setInventoryTxToSend.erase(it);
// Check if not in the filter already
if (pto->m_tx_relay->filterInventoryKnown.contains(txid)) {
continue;
}
// Not in the mempool anymore? don't bother sending it.
auto txinfo = g_mempool.info(txid);
if (!txinfo.tx) {
continue;
}
// Peer told you to not send transactions at that feerate?
// Don't bother sending it.
if (txinfo.fee < filterrate.GetFee(txinfo.vsize)) {
continue;
}
if (pto->m_tx_relay->pfilter &&
!pto->m_tx_relay->pfilter->IsRelevantAndUpdate(
*txinfo.tx)) {
continue;
}
// Send
vInv.push_back(CInv(MSG_TX, txid));
nRelayedTransactions++;
{
// Expire old relay messages
while (!vRelayExpiration.empty() &&
vRelayExpiration.front().first < nNow) {
mapRelay.erase(vRelayExpiration.front().second);
vRelayExpiration.pop_front();
}
auto ret = mapRelay.insert(
std::make_pair(txid, std::move(txinfo.tx)));
if (ret.second) {
vRelayExpiration.push_back(
std::make_pair(nNow +
std::chrono::microseconds{
RELAY_TX_CACHE_TIME}
.count(),
ret.first));
}
}
if (vInv.size() == MAX_INV_SZ) {
connman->PushMessage(
pto, msgMaker.Make(NetMsgType::INV, vInv));
vInv.clear();
}
pto->m_tx_relay->filterInventoryKnown.insert(txid);
}
}
}
}
if (!vInv.empty()) {
connman->PushMessage(pto, msgMaker.Make(NetMsgType::INV, vInv));
}
// Detect whether we're stalling
current_time = GetTime<std::chrono::microseconds>();
// nNow is the current system time (GetTimeMicros is not mockable) and
// should be replaced by the mockable current_time eventually
nNow = GetTimeMicros();
if (state.nStallingSince &&
state.nStallingSince < nNow - 1000000 * BLOCK_STALLING_TIMEOUT) {
// Stalling only triggers when the block download window cannot move.
// During normal steady state, the download window should be much larger
// than the to-be-downloaded set of blocks, so disconnection should only
// happen during initial block download.
LogPrintf("Peer=%d is stalling block download, disconnecting\n",
pto->GetId());
pto->fDisconnect = true;
return true;
}
// In case there is a block that has been in flight from this peer for 2 +
// 0.5 * N times the block interval (with N the number of peers from which
// we're downloading validated blocks), disconnect due to timeout. We
// compensate for other peers to prevent killing off peers due to our own
// downstream link being saturated. We only count validated in-flight blocks
// so peers can't advertise non-existing block hashes to unreasonably
// increase our timeout.
if (state.vBlocksInFlight.size() > 0) {
QueuedBlock &queuedBlock = state.vBlocksInFlight.front();
int nOtherPeersWithValidatedDownloads =
nPeersWithValidatedDownloads -
(state.nBlocksInFlightValidHeaders > 0);
if (nNow > state.nDownloadingSince +
consensusParams.nPowTargetSpacing *
(BLOCK_DOWNLOAD_TIMEOUT_BASE +
BLOCK_DOWNLOAD_TIMEOUT_PER_PEER *
nOtherPeersWithValidatedDownloads)) {
LogPrintf("Timeout downloading block %s from peer=%d, "
"disconnecting\n",
queuedBlock.hash.ToString(), pto->GetId());
pto->fDisconnect = true;
return true;
}
}
// Check for headers sync timeouts
if (state.fSyncStarted &&
state.nHeadersSyncTimeout < std::numeric_limits<int64_t>::max()) {
// Detect whether this is a stalling initial-headers-sync peer
if (pindexBestHeader->GetBlockTime() <=
GetAdjustedTime() - 24 * 60 * 60) {
if (nNow > state.nHeadersSyncTimeout && nSyncStarted == 1 &&
(nPreferredDownload - state.fPreferredDownload >= 1)) {
// Disconnect a (non-whitelisted) peer if it is our only sync
// peer, and we have others we could be using instead.
// Note: If all our peers are inbound, then we won't disconnect
// our sync peer for stalling; we have bigger problems if we
// can't get any outbound peers.
if (!pto->HasPermission(PF_NOBAN)) {
LogPrintf("Timeout downloading headers from peer=%d, "
"disconnecting\n",
pto->GetId());
pto->fDisconnect = true;
return true;
} else {
LogPrintf("Timeout downloading headers from whitelisted "
"peer=%d, not disconnecting\n",
pto->GetId());
// Reset the headers sync state so that we have a chance to
// try downloading from a different peer.
// Note: this will also result in at least one more
// getheaders message to be sent to this peer (eventually).
state.fSyncStarted = false;
nSyncStarted--;
state.nHeadersSyncTimeout = 0;
}
}
} else {
// After we've caught up once, reset the timeout so we can't trigger
// disconnect later.
state.nHeadersSyncTimeout = std::numeric_limits<int64_t>::max();
}
}
// Check that outbound peers have reasonable chains GetTime() is used by
// this anti-DoS logic so we can test this using mocktime.
ConsiderEviction(*pto, GetTime());
//
// Message: getdata (blocks)
//
std::vector<CInv> vGetData;
if (!pto->fClient &&
((fFetch && !pto->m_limited_node) ||
!::ChainstateActive().IsInitialBlockDownload()) &&
state.nBlocksInFlight < MAX_BLOCKS_IN_TRANSIT_PER_PEER) {
std::vector<const CBlockIndex *> vToDownload;
NodeId staller = -1;
FindNextBlocksToDownload(pto->GetId(),
MAX_BLOCKS_IN_TRANSIT_PER_PEER -
state.nBlocksInFlight,
vToDownload, staller, consensusParams);
for (const CBlockIndex *pindex : vToDownload) {
vGetData.push_back(CInv(MSG_BLOCK, pindex->GetBlockHash()));
MarkBlockAsInFlight(config, pto->GetId(), pindex->GetBlockHash(),
consensusParams, pindex);
LogPrint(BCLog::NET, "Requesting block %s (%d) peer=%d\n",
pindex->GetBlockHash().ToString(), pindex->nHeight,
pto->GetId());
}
if (state.nBlocksInFlight == 0 && staller != -1) {
if (State(staller)->nStallingSince == 0) {
State(staller)->nStallingSince = nNow;
LogPrint(BCLog::NET, "Stall started peer=%d\n", staller);
}
}
}
//
// Message: getdata (transactions)
//
// For robustness, expire old requests after a long timeout, so that we can
// resume downloading transactions from a peer even if they were
// unresponsive in the past. Eventually we should consider disconnecting
// peers, but this is conservative.
if (state.m_tx_download.m_check_expiry_timer <= current_time) {
for (auto it = state.m_tx_download.m_tx_in_flight.begin();
it != state.m_tx_download.m_tx_in_flight.end();) {
if (it->second <= current_time - TX_EXPIRY_INTERVAL) {
LogPrint(BCLog::NET, "timeout of inflight tx %s from peer=%d\n",
it->first.ToString(), pto->GetId());
state.m_tx_download.m_tx_announced.erase(it->first);
state.m_tx_download.m_tx_in_flight.erase(it++);
} else {
++it;
}
}
// On average, we do this check every TX_EXPIRY_INTERVAL. Randomize
// so that we're not doing this for all peers at the same time.
state.m_tx_download.m_check_expiry_timer =
current_time + TX_EXPIRY_INTERVAL / 2 +
GetRandMicros(TX_EXPIRY_INTERVAL);
}
auto &tx_process_time = state.m_tx_download.m_tx_process_time;
while (!tx_process_time.empty() &&
tx_process_time.begin()->first <= current_time &&
state.m_tx_download.m_tx_in_flight.size() < MAX_PEER_TX_IN_FLIGHT) {
const TxId txid = tx_process_time.begin()->second;
// Erase this entry from tx_process_time (it may be added back for
// processing at a later time, see below)
tx_process_time.erase(tx_process_time.begin());
CInv inv(MSG_TX, txid);
if (!AlreadyHave(inv)) {
// If this transaction was last requested more than 1 minute ago,
// then request.
const auto last_request_time = GetTxRequestTime(txid);
if (last_request_time <= current_time - GETDATA_TX_INTERVAL) {
LogPrint(BCLog::NET, "Requesting %s peer=%d\n", inv.ToString(),
pto->GetId());
vGetData.push_back(inv);
if (vGetData.size() >= MAX_GETDATA_SZ) {
connman->PushMessage(
pto, msgMaker.Make(NetMsgType::GETDATA, vGetData));
vGetData.clear();
}
UpdateTxRequestTime(txid, current_time);
state.m_tx_download.m_tx_in_flight.emplace(txid, current_time);
} else {
// This transaction is in flight from someone else; queue
// up processing to happen after the download times out
// (with a slight delay for inbound peers, to prefer
// requests to outbound peers).
const auto next_process_time = CalculateTxGetDataTime(
txid, current_time, !state.fPreferredDownload);
tx_process_time.emplace(next_process_time, txid);
}
} else {
// We have already seen this transaction, no need to download.
state.m_tx_download.m_tx_announced.erase(txid);
state.m_tx_download.m_tx_in_flight.erase(txid);
}
}
if (!vGetData.empty()) {
connman->PushMessage(pto, msgMaker.Make(NetMsgType::GETDATA, vGetData));
}
//
// Message: feefilter
//
// We don't want white listed peers to filter txs to us if we have
// -whitelistforcerelay
if (pto->m_tx_relay != nullptr && pto->nVersion >= FEEFILTER_VERSION &&
gArgs.GetBoolArg("-feefilter", DEFAULT_FEEFILTER) &&
!pto->HasPermission(PF_FORCERELAY)) {
Amount currentFilter =
g_mempool
.GetMinFee(
gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) *
1000000)
.GetFeePerK();
int64_t timeNow = GetTimeMicros();
if (timeNow > pto->m_tx_relay->nextSendTimeFeeFilter) {
static CFeeRate default_feerate =
CFeeRate(DEFAULT_MIN_RELAY_TX_FEE_PER_KB);
static FeeFilterRounder filterRounder(default_feerate);
Amount filterToSend = filterRounder.round(currentFilter);
filterToSend = std::max(filterToSend, ::minRelayTxFee.GetFeePerK());
if (filterToSend != pto->m_tx_relay->lastSentFeeFilter) {
connman->PushMessage(
pto, msgMaker.Make(NetMsgType::FEEFILTER, filterToSend));
pto->m_tx_relay->lastSentFeeFilter = filterToSend;
}
pto->m_tx_relay->nextSendTimeFeeFilter =
PoissonNextSend(timeNow, AVG_FEEFILTER_BROADCAST_INTERVAL);
}
// If the fee filter has changed substantially and it's still more than
// MAX_FEEFILTER_CHANGE_DELAY until scheduled broadcast, then move the
// broadcast to within MAX_FEEFILTER_CHANGE_DELAY.
else if (timeNow + MAX_FEEFILTER_CHANGE_DELAY * 1000000 <
pto->m_tx_relay->nextSendTimeFeeFilter &&
(currentFilter < 3 * pto->m_tx_relay->lastSentFeeFilter / 4 ||
currentFilter > 4 * pto->m_tx_relay->lastSentFeeFilter / 3)) {
pto->m_tx_relay->nextSendTimeFeeFilter =
timeNow + GetRandInt(MAX_FEEFILTER_CHANGE_DELAY) * 1000000;
}
}
return true;
}
class CNetProcessingCleanup {
public:
CNetProcessingCleanup() {}
~CNetProcessingCleanup() {
// orphan transactions
mapOrphanTransactions.clear();
mapOrphanTransactionsByPrev.clear();
}
};
static CNetProcessingCleanup instance_of_cnetprocessingcleanup;
diff --git a/src/net_processing.h b/src/net_processing.h
index 21daf0172..64467b587 100644
--- a/src/net_processing.h
+++ b/src/net_processing.h
@@ -1,138 +1,140 @@
// 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.
#ifndef BITCOIN_NET_PROCESSING_H
#define BITCOIN_NET_PROCESSING_H
#include <consensus/params.h>
#include <net.h>
#include <sync.h>
#include <validationinterface.h>
extern RecursiveMutex cs_main;
extern RecursiveMutex g_cs_orphans;
+class ChainstateManager;
class Config;
/**
* Default for -maxorphantx, maximum number of orphan transactions kept in
* memory.
*/
static const unsigned int DEFAULT_MAX_ORPHAN_TRANSACTIONS = 100;
/**
* Default number of orphan+recently-replaced txn to keep around for block
* reconstruction.
*/
static const unsigned int DEFAULT_BLOCK_RECONSTRUCTION_EXTRA_TXN = 100;
static const bool DEFAULT_PEERBLOCKFILTERS = false;
class PeerLogicValidation final : public CValidationInterface,
public NetEventsInterface {
private:
CConnman *const connman;
BanMan *const m_banman;
+ ChainstateManager &m_chainman;
bool CheckIfBanned(CNode &pnode) EXCLUSIVE_LOCKS_REQUIRED(cs_main);
public:
PeerLogicValidation(CConnman *connman, BanMan *banman,
- CScheduler &scheduler);
+ CScheduler &scheduler, ChainstateManager &chainman);
/**
* Overridden from CValidationInterface.
*/
void
BlockConnected(const std::shared_ptr<const CBlock> &pblock,
const CBlockIndex *pindexConnected,
const std::vector<CTransactionRef> &vtxConflicted) override;
/**
* Overridden from CValidationInterface.
*/
void UpdatedBlockTip(const CBlockIndex *pindexNew,
const CBlockIndex *pindexFork,
bool fInitialDownload) override;
/**
* Overridden from CValidationInterface.
*/
void BlockChecked(const CBlock &block,
const BlockValidationState &state) override;
/**
* Overridden from CValidationInterface.
*/
void NewPoWValidBlock(const CBlockIndex *pindex,
const std::shared_ptr<const CBlock> &pblock) override;
/**
* Initialize a peer by adding it to mapNodeState and pushing a message
* requesting its version.
*/
void InitializeNode(const Config &config, CNode *pnode) override;
/**
* Handle removal of a peer by updating various state and removing it from
* mapNodeState.
*/
void FinalizeNode(const Config &config, NodeId nodeid,
bool &fUpdateConnectionTime) override;
/**
* Process protocol messages received from a given node.
*/
bool ProcessMessages(const Config &config, CNode *pfrom,
std::atomic<bool> &interrupt) override;
/**
* Send queued protocol messages to be sent to a give node.
*
* @param[in] pto The node which we are sending messages to.
* @param[in] interrupt Interrupt condition for processing threads
* @return True if there is more work to be done
*/
bool SendMessages(const Config &config, CNode *pto,
std::atomic<bool> &interrupt) override
EXCLUSIVE_LOCKS_REQUIRED(pto->cs_sendProcessing);
/**
* Consider evicting an outbound peer based on the amount of time they've
* been behind our tip.
*/
void ConsiderEviction(CNode &pto, int64_t time_in_seconds)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
/**
* Evict extra outbound peers. If we think our tip may be stale, connect to
* an extra outbound.
*/
void
CheckForStaleTipAndEvictPeers(const Consensus::Params &consensusParams);
/**
* If we have extra outbound peers, try to disconnect the one with the
* oldest block announcement.
*/
void EvictExtraOutboundPeers(int64_t time_in_seconds)
EXCLUSIVE_LOCKS_REQUIRED(cs_main);
private:
//! Next time to check for stale tip
int64_t m_stale_tip_check_time;
};
struct CNodeStateStats {
int nMisbehavior = 0;
int nSyncHeight = -1;
int nCommonHeight = -1;
std::vector<int> vHeightInFlight;
};
/** Get statistics from node state */
bool GetNodeStateStats(NodeId nodeid, CNodeStateStats &stats);
/** Increase a node's misbehavior score. */
void Misbehaving(NodeId nodeid, int howmuch, const std::string &reason = "");
/** Relay transaction to every node */
void RelayTransaction(const TxId &txid, const CConnman &connman);
bool ProcessMessage(const Config &config, CNode &pfrom,
const std::string &msg_type, CDataStream &vRecv,
int64_t nTimeReceived, CConnman &connman, BanMan *banman,
const std::atomic<bool> &interruptMsgProc);
#endif // BITCOIN_NET_PROCESSING_H
diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp
index a651e92d3..c9aceefaa 100644
--- a/src/test/denialofservice_tests.cpp
+++ b/src/test/denialofservice_tests.cpp
@@ -1,489 +1,489 @@
// Copyright (c) 2011-2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// Unit tests for denial-of-service detection/prevention code
#include <banman.h>
#include <chain.h>
#include <chainparams.h>
#include <config.h>
#include <net.h>
#include <net_processing.h>
#include <script/sign.h>
#include <script/signingprovider.h>
#include <script/standard.h>
#include <serialize.h>
#include <util/system.h>
#include <util/time.h>
#include <validation.h>
#include <test/util/setup_common.h>
#include <boost/test/unit_test.hpp>
#include <cstdint>
namespace {
struct CConnmanTest : public CConnman {
using CConnman::CConnman;
void AddNode(CNode &node) {
LOCK(cs_vNodes);
vNodes.push_back(&node);
}
void ClearNodes() {
LOCK(cs_vNodes);
for (CNode *node : vNodes) {
delete node;
}
vNodes.clear();
}
};
} // namespace
// Tests these internal-to-net_processing.cpp methods:
extern bool AddOrphanTx(const CTransactionRef &tx, NodeId peer);
extern void EraseOrphansFor(NodeId peer);
extern unsigned int LimitOrphanTxSize(unsigned int nMaxOrphans);
struct COrphanTx {
CTransactionRef tx;
NodeId fromPeer;
int64_t nTimeExpire;
};
extern std::map<uint256, COrphanTx>
mapOrphanTransactions GUARDED_BY(g_cs_orphans);
static CService ip(uint32_t i) {
struct in_addr s;
s.s_addr = i;
return CService(CNetAddr(s), Params().GetDefaultPort());
}
static NodeId id = 0;
void UpdateLastBlockAnnounceTime(NodeId node, int64_t time_in_seconds);
BOOST_FIXTURE_TEST_SUITE(denialofservice_tests, TestingSetup)
// Test eviction of an outbound peer whose chain never advances
// Mock a node connection, and use mocktime to simulate a peer which never sends
// any headers messages. PeerLogic should decide to evict that outbound peer,
// after the appropriate timeouts.
// Note that we protect 4 outbound nodes from being subject to this logic; this
// test takes advantage of that protection only being applied to nodes which
// send headers with sufficient work.
BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) {
const Config &config = GetConfig();
std::atomic<bool> interruptDummy(false);
auto connman = std::make_unique<CConnman>(config, 0x1337, 0x1337);
auto peerLogic = std::make_unique<PeerLogicValidation>(
- connman.get(), nullptr, *m_node.scheduler);
+ connman.get(), nullptr, *m_node.scheduler, *m_node.chainman);
// Mock an outbound peer
CAddress addr1(ip(0xa0b0c001), NODE_NONE);
CNode dummyNode1(id++, ServiceFlags(NODE_NETWORK), 0, INVALID_SOCKET, addr1,
0, 0, CAddress(), "",
/*fInboundIn=*/false);
dummyNode1.SetSendVersion(PROTOCOL_VERSION);
peerLogic->InitializeNode(config, &dummyNode1);
dummyNode1.nVersion = 1;
dummyNode1.fSuccessfullyConnected = true;
// This test requires that we have a chain with non-zero work.
{
LOCK(cs_main);
BOOST_CHECK(::ChainActive().Tip() != nullptr);
BOOST_CHECK(::ChainActive().Tip()->nChainWork > 0);
}
// Test starts here
{
LOCK2(cs_main, dummyNode1.cs_sendProcessing);
// should result in getheaders
BOOST_CHECK(
peerLogic->SendMessages(config, &dummyNode1, interruptDummy));
}
{
LOCK2(cs_main, dummyNode1.cs_vSend);
BOOST_CHECK(dummyNode1.vSendMsg.size() > 0);
dummyNode1.vSendMsg.clear();
}
int64_t nStartTime = GetTime();
// Wait 21 minutes
SetMockTime(nStartTime + 21 * 60);
{
LOCK2(cs_main, dummyNode1.cs_sendProcessing);
// should result in getheaders
BOOST_CHECK(
peerLogic->SendMessages(config, &dummyNode1, interruptDummy));
}
{
LOCK2(cs_main, dummyNode1.cs_vSend);
BOOST_CHECK(dummyNode1.vSendMsg.size() > 0);
}
// Wait 3 more minutes
SetMockTime(nStartTime + 24 * 60);
{
LOCK2(cs_main, dummyNode1.cs_sendProcessing);
// should result in disconnect
BOOST_CHECK(
peerLogic->SendMessages(config, &dummyNode1, interruptDummy));
}
BOOST_CHECK(dummyNode1.fDisconnect == true);
SetMockTime(0);
bool dummy;
peerLogic->FinalizeNode(config, dummyNode1.GetId(), dummy);
}
static void AddRandomOutboundPeer(const Config &config,
std::vector<CNode *> &vNodes,
PeerLogicValidation &peerLogic,
CConnmanTest *connman) {
CAddress addr(ip(g_insecure_rand_ctx.randbits(32)), NODE_NONE);
vNodes.emplace_back(new CNode(id++, ServiceFlags(NODE_NETWORK), 0,
INVALID_SOCKET, addr, 0, 0, CAddress(), "",
/*fInboundIn=*/false));
CNode &node = *vNodes.back();
node.SetSendVersion(PROTOCOL_VERSION);
peerLogic.InitializeNode(config, &node);
node.nVersion = 1;
node.fSuccessfullyConnected = true;
connman->AddNode(node);
}
BOOST_AUTO_TEST_CASE(stale_tip_peer_management) {
const Config &config = GetConfig();
auto connman = std::make_unique<CConnmanTest>(config, 0x1337, 0x1337);
auto peerLogic = std::make_unique<PeerLogicValidation>(
- connman.get(), nullptr, *m_node.scheduler);
+ connman.get(), nullptr, *m_node.scheduler, *m_node.chainman);
const Consensus::Params &consensusParams =
config.GetChainParams().GetConsensus();
constexpr int max_outbound_full_relay = 8;
CConnman::Options options;
options.nMaxConnections = 125;
options.m_max_outbound_full_relay = max_outbound_full_relay;
options.nMaxFeeler = 1;
connman->Init(options);
std::vector<CNode *> vNodes;
// Mock some outbound peers
for (int i = 0; i < max_outbound_full_relay; ++i) {
AddRandomOutboundPeer(config, vNodes, *peerLogic, connman.get());
}
peerLogic->CheckForStaleTipAndEvictPeers(consensusParams);
// No nodes should be marked for disconnection while we have no extra peers
for (const CNode *node : vNodes) {
BOOST_CHECK(node->fDisconnect == false);
}
SetMockTime(GetTime() + 3 * consensusParams.nPowTargetSpacing + 1);
// Now tip should definitely be stale, and we should look for an extra
// outbound peer
peerLogic->CheckForStaleTipAndEvictPeers(consensusParams);
BOOST_CHECK(connman->GetTryNewOutboundPeer());
// Still no peers should be marked for disconnection
for (const CNode *node : vNodes) {
BOOST_CHECK(node->fDisconnect == false);
}
// If we add one more peer, something should get marked for eviction
// on the next check (since we're mocking the time to be in the future, the
// required time connected check should be satisfied).
AddRandomOutboundPeer(config, vNodes, *peerLogic, connman.get());
peerLogic->CheckForStaleTipAndEvictPeers(consensusParams);
for (int i = 0; i < max_outbound_full_relay; ++i) {
BOOST_CHECK(vNodes[i]->fDisconnect == false);
}
// Last added node should get marked for eviction
BOOST_CHECK(vNodes.back()->fDisconnect == true);
vNodes.back()->fDisconnect = false;
// Update the last announced block time for the last
// peer, and check that the next newest node gets evicted.
UpdateLastBlockAnnounceTime(vNodes.back()->GetId(), GetTime());
peerLogic->CheckForStaleTipAndEvictPeers(consensusParams);
for (int i = 0; i < max_outbound_full_relay - 1; ++i) {
BOOST_CHECK(vNodes[i]->fDisconnect == false);
}
BOOST_CHECK(vNodes[max_outbound_full_relay - 1]->fDisconnect == true);
BOOST_CHECK(vNodes.back()->fDisconnect == false);
bool dummy;
for (const CNode *node : vNodes) {
peerLogic->FinalizeNode(config, node->GetId(), dummy);
}
connman->ClearNodes();
}
BOOST_AUTO_TEST_CASE(DoS_banning) {
const Config &config = GetConfig();
std::atomic<bool> interruptDummy(false);
auto banman = std::make_unique<BanMan>(GetDataDir() / "banlist.dat",
config.GetChainParams(), nullptr,
DEFAULT_MISBEHAVING_BANTIME);
auto connman = std::make_unique<CConnman>(config, 0x1337, 0x1337);
auto peerLogic = std::make_unique<PeerLogicValidation>(
- connman.get(), banman.get(), *m_node.scheduler);
+ connman.get(), banman.get(), *m_node.scheduler, *m_node.chainman);
banman->ClearBanned();
CAddress addr1(ip(0xa0b0c001), NODE_NONE);
CNode dummyNode1(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr1, 0, 0,
CAddress(), "", true);
dummyNode1.SetSendVersion(PROTOCOL_VERSION);
peerLogic->InitializeNode(config, &dummyNode1);
dummyNode1.nVersion = 1;
dummyNode1.fSuccessfullyConnected = true;
{
LOCK(cs_main);
// Should get banned.
Misbehaving(dummyNode1.GetId(), 100, "");
}
{
LOCK2(cs_main, dummyNode1.cs_sendProcessing);
BOOST_CHECK(
peerLogic->SendMessages(config, &dummyNode1, interruptDummy));
}
BOOST_CHECK(banman->IsDiscouraged(addr1));
// Different IP, not banned
BOOST_CHECK(!banman->IsDiscouraged(ip(0xa0b0c001 | 0x0000ff00)));
CAddress addr2(ip(0xa0b0c002), NODE_NONE);
CNode dummyNode2(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr2, 1, 1,
CAddress(), "", true);
dummyNode2.SetSendVersion(PROTOCOL_VERSION);
peerLogic->InitializeNode(config, &dummyNode2);
dummyNode2.nVersion = 1;
dummyNode2.fSuccessfullyConnected = true;
{
LOCK(cs_main);
Misbehaving(dummyNode2.GetId(), 50, "");
}
{
LOCK2(cs_main, dummyNode2.cs_sendProcessing);
BOOST_CHECK(
peerLogic->SendMessages(config, &dummyNode2, interruptDummy));
}
// 2 not banned yet...
BOOST_CHECK(!banman->IsDiscouraged(addr2));
// ... but 1 still should be
BOOST_CHECK(banman->IsDiscouraged(addr1));
{
LOCK(cs_main);
Misbehaving(dummyNode2.GetId(), 50, "");
}
{
LOCK2(cs_main, dummyNode2.cs_sendProcessing);
BOOST_CHECK(
peerLogic->SendMessages(config, &dummyNode2, interruptDummy));
}
BOOST_CHECK(banman->IsDiscouraged(addr2));
bool dummy;
peerLogic->FinalizeNode(config, dummyNode1.GetId(), dummy);
peerLogic->FinalizeNode(config, dummyNode2.GetId(), dummy);
}
BOOST_AUTO_TEST_CASE(DoS_banscore) {
const Config &config = GetConfig();
std::atomic<bool> interruptDummy(false);
auto banman = std::make_unique<BanMan>(GetDataDir() / "banlist.dat",
config.GetChainParams(), nullptr,
DEFAULT_MISBEHAVING_BANTIME);
auto connman = std::make_unique<CConnman>(config, 0x1337, 0x1337);
auto peerLogic = std::make_unique<PeerLogicValidation>(
- connman.get(), banman.get(), *m_node.scheduler);
+ connman.get(), banman.get(), *m_node.scheduler, *m_node.chainman);
banman->ClearBanned();
// because 11 is my favorite number.
gArgs.ForceSetArg("-banscore", "111");
CAddress addr1(ip(0xa0b0c001), NODE_NONE);
CNode dummyNode1(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr1, 3, 1,
CAddress(), "", true);
dummyNode1.SetSendVersion(PROTOCOL_VERSION);
peerLogic->InitializeNode(config, &dummyNode1);
dummyNode1.nVersion = 1;
dummyNode1.fSuccessfullyConnected = true;
{
LOCK(cs_main);
Misbehaving(dummyNode1.GetId(), 100, "");
}
{
LOCK2(cs_main, dummyNode1.cs_sendProcessing);
BOOST_CHECK(
peerLogic->SendMessages(config, &dummyNode1, interruptDummy));
}
BOOST_CHECK(!banman->IsDiscouraged(addr1));
{
LOCK(cs_main);
Misbehaving(dummyNode1.GetId(), 10, "");
}
{
LOCK2(cs_main, dummyNode1.cs_sendProcessing);
BOOST_CHECK(
peerLogic->SendMessages(config, &dummyNode1, interruptDummy));
}
BOOST_CHECK(!banman->IsDiscouraged(addr1));
{
LOCK(cs_main);
Misbehaving(dummyNode1.GetId(), 1, "");
}
{
LOCK2(cs_main, dummyNode1.cs_sendProcessing);
BOOST_CHECK(
peerLogic->SendMessages(config, &dummyNode1, interruptDummy));
}
BOOST_CHECK(banman->IsDiscouraged(addr1));
gArgs.ForceSetArg("-banscore", std::to_string(DEFAULT_BANSCORE_THRESHOLD));
bool dummy;
peerLogic->FinalizeNode(config, dummyNode1.GetId(), dummy);
}
BOOST_AUTO_TEST_CASE(DoS_bantime) {
const Config &config = GetConfig();
std::atomic<bool> interruptDummy(false);
auto banman = std::make_unique<BanMan>(GetDataDir() / "banlist.dat",
config.GetChainParams(), nullptr,
DEFAULT_MISBEHAVING_BANTIME);
auto connman = std::make_unique<CConnman>(config, 0x1337, 0x1337);
auto peerLogic = std::make_unique<PeerLogicValidation>(
- connman.get(), banman.get(), *m_node.scheduler);
+ connman.get(), banman.get(), *m_node.scheduler, *m_node.chainman);
banman->ClearBanned();
int64_t nStartTime = GetTime();
// Overrides future calls to GetTime()
SetMockTime(nStartTime);
CAddress addr(ip(0xa0b0c001), NODE_NONE);
CNode dummyNode(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr, 4, 4,
CAddress(), "", true);
dummyNode.SetSendVersion(PROTOCOL_VERSION);
peerLogic->InitializeNode(config, &dummyNode);
dummyNode.nVersion = 1;
dummyNode.fSuccessfullyConnected = true;
{
LOCK(cs_main);
Misbehaving(dummyNode.GetId(), 100, "");
}
{
LOCK2(cs_main, dummyNode.cs_sendProcessing);
BOOST_CHECK(
peerLogic->SendMessages(config, &dummyNode, interruptDummy));
}
BOOST_CHECK(banman->IsDiscouraged(addr));
bool dummy;
peerLogic->FinalizeNode(config, dummyNode.GetId(), dummy);
}
static CTransactionRef RandomOrphan() {
std::map<uint256, COrphanTx>::iterator it;
LOCK2(cs_main, g_cs_orphans);
it = mapOrphanTransactions.lower_bound(InsecureRand256());
if (it == mapOrphanTransactions.end()) {
it = mapOrphanTransactions.begin();
}
return it->second.tx;
}
BOOST_AUTO_TEST_CASE(DoS_mapOrphans) {
CKey key;
key.MakeNewKey(true);
FillableSigningProvider keystore;
BOOST_CHECK(keystore.AddKey(key));
// 50 orphan transactions:
for (int i = 0; i < 50; i++) {
CMutableTransaction tx;
tx.vin.resize(1);
tx.vin[0].prevout = COutPoint(TxId(InsecureRand256()), 0);
tx.vin[0].scriptSig << OP_1;
tx.vout.resize(1);
tx.vout[0].nValue = 1 * CENT;
tx.vout[0].scriptPubKey =
GetScriptForDestination(PKHash(key.GetPubKey()));
AddOrphanTx(MakeTransactionRef(tx), i);
}
// ... and 50 that depend on other orphans:
for (int i = 0; i < 50; i++) {
CTransactionRef txPrev = RandomOrphan();
CMutableTransaction tx;
tx.vin.resize(1);
tx.vin[0].prevout = COutPoint(txPrev->GetId(), 0);
tx.vout.resize(1);
tx.vout[0].nValue = 1 * CENT;
tx.vout[0].scriptPubKey =
GetScriptForDestination(PKHash(key.GetPubKey()));
BOOST_CHECK(SignSignature(keystore, *txPrev, tx, 0,
SigHashType().withForkId()));
AddOrphanTx(MakeTransactionRef(tx), i);
}
// This really-big orphan should be ignored:
for (int i = 0; i < 10; i++) {
CTransactionRef txPrev = RandomOrphan();
CMutableTransaction tx;
tx.vout.resize(1);
tx.vout[0].nValue = 1 * CENT;
tx.vout[0].scriptPubKey =
GetScriptForDestination(PKHash(key.GetPubKey()));
tx.vin.resize(2777);
for (size_t j = 0; j < tx.vin.size(); j++) {
tx.vin[j].prevout = COutPoint(txPrev->GetId(), j);
}
BOOST_CHECK(SignSignature(keystore, *txPrev, tx, 0,
SigHashType().withForkId()));
// Re-use same signature for other inputs
// (they don't have to be valid for this test)
for (unsigned int j = 1; j < tx.vin.size(); j++) {
tx.vin[j].scriptSig = tx.vin[0].scriptSig;
}
BOOST_CHECK(!AddOrphanTx(MakeTransactionRef(tx), i));
}
LOCK2(cs_main, g_cs_orphans);
// Test EraseOrphansFor:
for (NodeId i = 0; i < 3; i++) {
size_t sizeBefore = mapOrphanTransactions.size();
EraseOrphansFor(i);
BOOST_CHECK(mapOrphanTransactions.size() < sizeBefore);
}
// Test LimitOrphanTxSize() function:
LimitOrphanTxSize(40);
BOOST_CHECK(mapOrphanTransactions.size() <= 40);
LimitOrphanTxSize(10);
BOOST_CHECK(mapOrphanTransactions.size() <= 10);
LimitOrphanTxSize(0);
BOOST_CHECK(mapOrphanTransactions.empty());
}
BOOST_AUTO_TEST_SUITE_END()
diff --git a/src/test/fuzz/process_message.cpp b/src/test/fuzz/process_message.cpp
index 9423a87f9..f73b10a3b 100644
--- a/src/test/fuzz/process_message.cpp
+++ b/src/test/fuzz/process_message.cpp
@@ -1,129 +1,130 @@
// Copyright (c) 2020 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 <banman.h>
#include <chainparams.h>
#include <config.h>
#include <consensus/consensus.h>
#include <net.h>
#include <net_processing.h>
#include <protocol.h>
#include <scheduler.h>
#include <script/script.h>
#include <streams.h>
#include <validationinterface.h>
#include <version.h>
#include <test/fuzz/FuzzedDataProvider.h>
#include <test/fuzz/fuzz.h>
#include <test/util/mining.h>
#include <test/util/setup_common.h>
#include <algorithm>
#include <atomic>
#include <cassert>
#include <chrono>
#include <cstdint>
#include <iosfwd>
#include <iostream>
#include <map>
#include <memory>
#include <set>
#include <string>
#include <vector>
namespace {
#ifdef MESSAGE_TYPE
#define TO_STRING_(s) #s
#define TO_STRING(s) TO_STRING_(s)
const std::string LIMIT_TO_MESSAGE_TYPE{TO_STRING(MESSAGE_TYPE)};
#else
const std::string LIMIT_TO_MESSAGE_TYPE;
#endif
const std::map<std::string, std::set<std::string>>
EXPECTED_DESERIALIZATION_EXCEPTIONS = {
{"CDataStream::read(): end of data: iostream error",
{"addr", "block", "blocktxn", "cmpctblock", "feefilter", "filteradd",
"filterload", "getblocks", "getblocktxn", "getdata", "getheaders",
"headers", "inv", "notfound", "ping", "sendcmpct", "tx"}},
{"CompactSize exceeds limit of type: iostream error", {"cmpctblock"}},
{"differential value overflow: iostream error", {"getblocktxn"}},
{"index overflowed 16 bits: iostream error", {"getblocktxn"}},
{"index overflowed 16-bits: iostream error", {"cmpctblock"}},
{"indexes overflowed 16 bits: iostream error", {"getblocktxn"}},
{"non-canonical ReadCompactSize(): iostream error",
{"addr", "block", "blocktxn", "cmpctblock", "filteradd", "filterload",
"getblocks", "getblocktxn", "getdata", "getheaders", "headers", "inv",
"notfound", "tx"}},
{"ReadCompactSize(): size too large: iostream error",
{"addr", "block", "blocktxn", "cmpctblock", "filteradd", "filterload",
"getblocks", "getblocktxn", "getdata", "getheaders", "headers", "inv",
"notfound", "tx"}},
{"Superfluous witness record: iostream error",
{"block", "blocktxn", "cmpctblock", "tx"}},
{"Unknown transaction optional data: iostream error",
{"block", "blocktxn", "cmpctblock", "tx"}},
};
const RegTestingSetup *g_setup;
} // namespace
void initialize() {
static RegTestingSetup setup{};
g_setup = &setup;
for (int i = 0; i < 2 * COINBASE_MATURITY; i++) {
MineBlock(GetConfig(), g_setup->m_node, CScript() << OP_TRUE);
}
SyncWithValidationInterfaceQueue();
}
void test_one_input(const std::vector<uint8_t> &buffer) {
const Config &config = GetConfig();
FuzzedDataProvider fuzzed_data_provider(buffer.data(), buffer.size());
const std::string random_message_type{
fuzzed_data_provider.ConsumeBytesAsString(CMessageHeader::COMMAND_SIZE)
.c_str()};
if (!LIMIT_TO_MESSAGE_TYPE.empty() &&
random_message_type != LIMIT_TO_MESSAGE_TYPE) {
return;
}
CDataStream random_bytes_data_stream{
fuzzed_data_provider.ConsumeRemainingBytes<uint8_t>(), SER_NETWORK,
PROTOCOL_VERSION};
CNode p2p_node{0,
ServiceFlags(NODE_NETWORK | NODE_BLOOM),
0,
INVALID_SOCKET,
CAddress{CService{in_addr{0x0100007f}, 7777}, NODE_NETWORK},
0,
0,
CAddress{},
std::string{},
false};
p2p_node.fSuccessfullyConnected = true;
p2p_node.nVersion = PROTOCOL_VERSION;
p2p_node.SetSendVersion(PROTOCOL_VERSION);
g_setup->m_node.peer_logic->InitializeNode(config, &p2p_node);
try {
(void)ProcessMessage(
config, p2p_node, random_message_type, random_bytes_data_stream,
- GetTimeMillis(), *g_setup->m_node.connman.get(),
- g_setup->m_node.banman.get(), std::atomic<bool>{false});
+ GetTimeMillis(), *g_setup->m_node.chainman,
+ *g_setup->m_node.connman.get(), g_setup->m_node.banman.get(),
+ std::atomic<bool>{false});
} catch (const std::ios_base::failure &e) {
const std::string exception_message{e.what()};
const auto p =
EXPECTED_DESERIALIZATION_EXCEPTIONS.find(exception_message);
if (p == EXPECTED_DESERIALIZATION_EXCEPTIONS.cend() ||
p->second.count(random_message_type) == 0) {
std::cout << "Unexpected exception when processing message type \""
<< random_message_type << "\": " << exception_message
<< std::endl;
assert(false);
}
}
SyncWithValidationInterfaceQueue();
}
diff --git a/src/test/util/setup_common.cpp b/src/test/util/setup_common.cpp
index 5d015859c..40cc6b8c1 100644
--- a/src/test/util/setup_common.cpp
+++ b/src/test/util/setup_common.cpp
@@ -1,376 +1,377 @@
// Copyright (c) 2011-2019 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
#include <test/util/setup_common.h>
#include <banman.h>
#include <chainparams.h>
#include <config.h>
#include <consensus/consensus.h>
#include <consensus/validation.h>
#include <crypto/sha256.h>
#include <init.h>
#include <interfaces/chain.h>
#include <logging.h>
#include <miner.h>
#include <net.h>
#include <net_processing.h>
#include <noui.h>
#include <pow/pow.h>
#include <rpc/blockchain.h>
#include <rpc/register.h>
#include <rpc/server.h>
#include <script/script_error.h>
#include <script/scriptcache.h>
#include <script/sigcache.h>
#include <streams.h>
#include <txdb.h>
#include <txmempool.h>
#include <util/strencodings.h>
#include <util/time.h>
#include <util/translation.h>
#include <validation.h>
#include <validationinterface.h>
#include <walletinitinterface.h>
#include <functional>
#include <memory>
const std::function<std::string(const char *)> G_TRANSLATION_FUN = nullptr;
FastRandomContext g_insecure_rand_ctx;
/**
* Random context to get unique temp data dirs. Separate from
* g_insecure_rand_ctx, which can be seeded from a const env var
*/
static FastRandomContext g_insecure_rand_ctx_temp_path;
/**
* Return the unsigned from the environment var if available,
* otherwise 0
*/
static uint256 GetUintFromEnv(const std::string &env_name) {
const char *num = std::getenv(env_name.c_str());
if (!num) {
return {};
}
return uint256S(num);
}
void Seed(FastRandomContext &ctx) {
// Should be enough to get the seed once for the process
static uint256 seed{};
static const std::string RANDOM_CTX_SEED{"RANDOM_CTX_SEED"};
if (seed.IsNull()) {
seed = GetUintFromEnv(RANDOM_CTX_SEED);
}
if (seed.IsNull()) {
seed = GetRandHash();
}
LogPrintf("%s: Setting random seed for current tests to %s=%s\n", __func__,
RANDOM_CTX_SEED, seed.GetHex());
ctx = FastRandomContext(seed);
}
std::ostream &operator<<(std::ostream &os, const uint256 &num) {
os << num.ToString();
return os;
}
std::ostream &operator<<(std::ostream &os, const ScriptError &err) {
os << ScriptErrorString(err);
return os;
}
BasicTestingSetup::BasicTestingSetup(const std::string &chainName)
: m_path_root{fs::temp_directory_path() / "test_common_" PACKAGE_NAME /
g_insecure_rand_ctx_temp_path.rand256().ToString()} {
SetMockTime(0);
fs::create_directories(m_path_root);
gArgs.ForceSetArg("-datadir", m_path_root.string());
ClearDatadirCache();
SelectParams(chainName);
SeedInsecureRand();
gArgs.ForceSetArg("-printtoconsole", "0");
InitLogging();
LogInstance().StartLogging();
SHA256AutoDetect();
ECC_Start();
SetupEnvironment();
SetupNetworking();
InitSignatureCache();
InitScriptExecutionCache();
m_node.chain = interfaces::MakeChain(m_node, Params());
g_wallet_init_interface.Construct(m_node);
fCheckBlockIndex = true;
static bool noui_connected = false;
if (!noui_connected) {
noui_connect();
noui_connected = true;
}
}
BasicTestingSetup::~BasicTestingSetup() {
LogInstance().DisconnectTestLogger();
fs::remove_all(m_path_root);
ECC_Stop();
}
TestingSetup::TestingSetup(const std::string &chainName)
: BasicTestingSetup(chainName) {
const Config &config = GetConfig();
const CChainParams &chainparams = config.GetChainParams();
// Ideally we'd move all the RPC tests to the functional testing framework
// instead of unit tests, but for now we need these here.
RPCServer rpcServer;
RegisterAllRPCCommands(config, rpcServer, tableRPC);
/**
* RPC does not come out of the warmup state on its own. Normally, this is
* handled in bitcoind's init path, but unit tests do not trigger this
* codepath, so we call it explicitly as part of setup.
*/
std::string rpcWarmupStatus;
if (RPCIsInWarmup(&rpcWarmupStatus)) {
SetRPCWarmupFinished();
}
m_node.scheduler = std::make_unique<CScheduler>();
// We have to run a scheduler thread to prevent ActivateBestChain
// from blocking due to queue overrun.
threadGroup.create_thread([&] { m_node.scheduler->serviceQueue(); });
GetMainSignals().RegisterBackgroundSignalScheduler(*m_node.scheduler);
pblocktree.reset(new CBlockTreeDB(1 << 20, true));
m_node.chainman = &::g_chainman;
m_node.chainman->InitializeChainstate();
::ChainstateActive().InitCoinsDB(
/* cache_size_bytes */ 1 << 23, /* in_memory */ true,
/* should_wipe */ false);
assert(!::ChainstateActive().CanFlushToDisk());
::ChainstateActive().InitCoinsCache();
assert(::ChainstateActive().CanFlushToDisk());
if (!LoadGenesisBlock(chainparams)) {
throw std::runtime_error("LoadGenesisBlock failed.");
}
{
BlockValidationState state;
if (!ActivateBestChain(config, state)) {
throw std::runtime_error(
strprintf("ActivateBestChain failed. (%s)", state.ToString()));
}
}
constexpr int script_check_threads = 2;
for (int i = 0; i < script_check_threads; ++i) {
threadGroup.create_thread([i]() { return ThreadScriptCheck(i); });
}
m_node.mempool = &::g_mempool;
m_node.mempool->setSanityCheck(1.0);
m_node.banman =
std::make_unique<BanMan>(GetDataDir() / "banlist.dat", chainparams,
nullptr, DEFAULT_MISBEHAVING_BANTIME);
// Deterministic randomness for tests.
m_node.connman = std::make_unique<CConnman>(config, 0x1337, 0x1337);
m_node.peer_logic = std::make_unique<PeerLogicValidation>(
- m_node.connman.get(), m_node.banman.get(), *m_node.scheduler);
+ m_node.connman.get(), m_node.banman.get(), *m_node.scheduler,
+ *m_node.chainman);
}
TestingSetup::~TestingSetup() {
if (m_node.scheduler) {
m_node.scheduler->stop();
}
threadGroup.interrupt_all();
threadGroup.join_all();
GetMainSignals().FlushBackgroundCallbacks();
GetMainSignals().UnregisterBackgroundSignalScheduler();
m_node.connman.reset();
m_node.banman.reset();
m_node.mempool = nullptr;
m_node.scheduler.reset();
UnloadBlockIndex();
m_node.chainman->Reset();
m_node.chainman = nullptr;
pblocktree.reset();
}
TestChain100Setup::TestChain100Setup() {
// Generate a 100-block chain:
coinbaseKey.MakeNewKey(true);
CScript scriptPubKey = CScript() << ToByteVector(coinbaseKey.GetPubKey())
<< OP_CHECKSIG;
for (int i = 0; i < COINBASE_MATURITY; i++) {
std::vector<CMutableTransaction> noTxns;
CBlock b = CreateAndProcessBlock(noTxns, scriptPubKey);
m_coinbase_txns.push_back(b.vtx[0]);
}
}
//
// Create a new block with just given transactions, coinbase paying to
// scriptPubKey, and try to add it to the current chain.
//
CBlock TestChain100Setup::CreateAndProcessBlock(
const std::vector<CMutableTransaction> &txns, const CScript &scriptPubKey) {
const Config &config = GetConfig();
std::unique_ptr<CBlockTemplate> pblocktemplate =
BlockAssembler(config, *m_node.mempool).CreateNewBlock(scriptPubKey);
CBlock &block = pblocktemplate->block;
// Replace mempool-selected txns with just coinbase plus passed-in txns:
block.vtx.resize(1);
for (const CMutableTransaction &tx : txns) {
block.vtx.push_back(MakeTransactionRef(tx));
}
// Order transactions by canonical order
std::sort(std::begin(block.vtx) + 1, std::end(block.vtx),
[](const std::shared_ptr<const CTransaction> &txa,
const std::shared_ptr<const CTransaction> &txb) -> bool {
return txa->GetId() < txb->GetId();
});
// IncrementExtraNonce creates a valid coinbase and merkleRoot
{
LOCK(cs_main);
unsigned int extraNonce = 0;
IncrementExtraNonce(&block, ::ChainActive().Tip(),
config.GetMaxBlockSize(), extraNonce);
}
const Consensus::Params &params = config.GetChainParams().GetConsensus();
while (!CheckProofOfWork(block.GetHash(), block.nBits, params)) {
++block.nNonce;
}
std::shared_ptr<const CBlock> shared_pblock =
std::make_shared<const CBlock>(block);
ProcessNewBlock(config, shared_pblock, true, nullptr);
CBlock result = block;
return result;
}
TestChain100Setup::~TestChain100Setup() {}
CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CMutableTransaction &tx) {
return FromTx(MakeTransactionRef(tx));
}
CTxMemPoolEntry TestMemPoolEntryHelper::FromTx(const CTransactionRef &tx) {
return CTxMemPoolEntry(tx, nFee, nTime, nHeight, spendsCoinbase,
nSigOpCount, LockPoints());
}
/**
* @returns a real block
* (0000000000013b8ab2cd513b0261a14096412195a72a0c4827d229dcc7e0f7af) with 9
* txs.
*/
CBlock getBlock13b8a() {
CBlock block;
CDataStream stream(
ParseHex(
"0100000090f0a9f110702f808219ebea1173056042a714bad51b916cb680000000"
"0000005275289558f51c9966699404ae2294730c3c9f9bda53523ce50e9b95e558"
"da2fdb261b4d4c86041b1ab1bf9309010000000100000000000000000000000000"
"00000000000000000000000000000000000000ffffffff07044c86041b0146ffff"
"ffff0100f2052a01000000434104e18f7afbe4721580e81e8414fc8c24d7cfacf2"
"54bb5c7b949450c3e997c2dc1242487a8169507b631eb3771f2b425483fb13102c"
"4eb5d858eef260fe70fbfae0ac00000000010000000196608ccbafa16abada9027"
"80da4dc35dafd7af05fa0da08cf833575f8cf9e836000000004a493046022100da"
"b24889213caf43ae6adc41cf1c9396c08240c199f5225acf45416330fd7dbd0221"
"00fe37900e0644bf574493a07fc5edba06dbc07c311b947520c2d514bc5725dcb4"
"01ffffffff0100f2052a010000001976a914f15d1921f52e4007b146dfa60f369e"
"d2fc393ce288ac000000000100000001fb766c1288458c2bafcfec81e48b24d98e"
"c706de6b8af7c4e3c29419bfacb56d000000008c493046022100f268ba165ce0ad"
"2e6d93f089cfcd3785de5c963bb5ea6b8c1b23f1ce3e517b9f022100da7c0f21ad"
"c6c401887f2bfd1922f11d76159cbc597fbd756a23dcbb00f4d7290141042b4e86"
"25a96127826915a5b109852636ad0da753c9e1d5606a50480cd0c40f1f8b8d8982"
"35e571fe9357d9ec842bc4bba1827daaf4de06d71844d0057707966affffffff02"
"80969800000000001976a9146963907531db72d0ed1a0cfb471ccb63923446f388"
"ac80d6e34c000000001976a914f0688ba1c0d1ce182c7af6741e02658c7d4dfcd3"
"88ac000000000100000002c40297f730dd7b5a99567eb8d27b78758f607507c522"
"92d02d4031895b52f2ff010000008b483045022100f7edfd4b0aac404e5bab4fd3"
"889e0c6c41aa8d0e6fa122316f68eddd0a65013902205b09cc8b2d56e1cd1f7f2f"
"afd60a129ed94504c4ac7bdc67b56fe67512658b3e014104732012cb962afa90d3"
"1b25d8fb0e32c94e513ab7a17805c14ca4c3423e18b4fb5d0e676841733cb83aba"
"f975845c9f6f2a8097b7d04f4908b18368d6fc2d68ecffffffffca5065ff9617cb"
"cba45eb23726df6498a9b9cafed4f54cbab9d227b0035ddefb000000008a473044"
"022068010362a13c7f9919fa832b2dee4e788f61f6f5d344a7c2a0da6ae7406056"
"58022006d1af525b9a14a35c003b78b72bd59738cd676f845d1ff3fc25049e0100"
"3614014104732012cb962afa90d31b25d8fb0e32c94e513ab7a17805c14ca4c342"
"3e18b4fb5d0e676841733cb83abaf975845c9f6f2a8097b7d04f4908b18368d6fc"
"2d68ecffffffff01001ec4110200000043410469ab4181eceb28985b9b4e895c13"
"fa5e68d85761b7eee311db5addef76fa8621865134a221bd01f28ec9999ee3e021"
"e60766e9d1f3458c115fb28650605f11c9ac000000000100000001cdaf2f758e91"
"c514655e2dc50633d1e4c84989f8aa90a0dbc883f0d23ed5c2fa010000008b4830"
"4502207ab51be6f12a1962ba0aaaf24a20e0b69b27a94fac5adf45aa7d2d18ffd9"
"236102210086ae728b370e5329eead9accd880d0cb070aea0c96255fae6c4f1ddc"
"ce1fd56e014104462e76fd4067b3a0aa42070082dcb0bf2f388b6495cf33d78990"
"4f07d0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15312ef1c0e8"
"ebbb12dcd4ffffffff02404b4c00000000001976a9142b6ba7c9d796b75eef7942"
"fc9288edd37c32f5c388ac002d3101000000001976a9141befba0cdc1ad5652937"
"1864d9f6cb042faa06b588ac000000000100000001b4a47603e71b61bc3326efd9"
"0111bf02d2f549b067f4c4a8fa183b57a0f800cb010000008a4730440220177c37"
"f9a505c3f1a1f0ce2da777c339bd8339ffa02c7cb41f0a5804f473c9230220585b"
"25a2ee80eb59292e52b987dad92acb0c64eced92ed9ee105ad153cdb12d0014104"
"43bd44f683467e549dae7d20d1d79cbdb6df985c6e9c029c8d0c6cb46cc1a4d3cf"
"7923c5021b27f7a0b562ada113bc85d5fda5a1b41e87fe6e8802817cf69996ffff"
"ffff0280651406000000001976a9145505614859643ab7b547cd7f1f5e7e2a1232"
"2d3788ac00aa0271000000001976a914ea4720a7a52fc166c55ff2298e07baf70a"
"e67e1b88ac00000000010000000586c62cd602d219bb60edb14a3e204de0705176"
"f9022fe49a538054fb14abb49e010000008c493046022100f2bc2aba2534becbdf"
"062eb993853a42bbbc282083d0daf9b4b585bd401aa8c9022100b1d7fd7ee0b956"
"00db8535bbf331b19eed8d961f7a8e54159c53675d5f69df8c014104462e76fd40"
"67b3a0aa42070082dcb0bf2f388b6495cf33d789904f07d0f55c40fbd4b82963c6"
"9b3dc31895d0c772c812b1d5fbcade15312ef1c0e8ebbb12dcd4ffffffff03ad0e"
"58ccdac3df9dc28a218bcf6f1997b0a93306faaa4b3a28ae83447b217901000000"
"8b483045022100be12b2937179da88599e27bb31c3525097a07cdb52422d165b3c"
"a2f2020ffcf702200971b51f853a53d644ebae9ec8f3512e442b1bcb6c315a5b49"
"1d119d10624c83014104462e76fd4067b3a0aa42070082dcb0bf2f388b6495cf33"
"d789904f07d0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15312e"
"f1c0e8ebbb12dcd4ffffffff2acfcab629bbc8685792603762c921580030ba144a"
"f553d271716a95089e107b010000008b483045022100fa579a840ac258871365dd"
"48cd7552f96c8eea69bd00d84f05b283a0dab311e102207e3c0ee9234814cfbb1b"
"659b83671618f45abc1326b9edcc77d552a4f2a805c0014104462e76fd4067b3a0"
"aa42070082dcb0bf2f388b6495cf33d789904f07d0f55c40fbd4b82963c69b3dc3"
"1895d0c772c812b1d5fbcade15312ef1c0e8ebbb12dcd4ffffffffdcdc6023bbc9"
"944a658ddc588e61eacb737ddf0a3cd24f113b5a8634c517fcd2000000008b4830"
"450221008d6df731df5d32267954bd7d2dda2302b74c6c2a6aa5c0ca64ecbabc1a"
"f03c75022010e55c571d65da7701ae2da1956c442df81bbf076cdbac25133f99d9"
"8a9ed34c014104462e76fd4067b3a0aa42070082dcb0bf2f388b6495cf33d78990"
"4f07d0f55c40fbd4b82963c69b3dc31895d0c772c812b1d5fbcade15312ef1c0e8"
"ebbb12dcd4ffffffffe15557cd5ce258f479dfd6dc6514edf6d7ed5b21fcfa4a03"
"8fd69f06b83ac76e010000008b483045022023b3e0ab071eb11de2eb1cc3a67261"
"b866f86bf6867d4558165f7c8c8aca2d86022100dc6e1f53a91de3efe8f6351285"
"0811f26284b62f850c70ca73ed5de8771fb451014104462e76fd4067b3a0aa4207"
"0082dcb0bf2f388b6495cf33d789904f07d0f55c40fbd4b82963c69b3dc31895d0"
"c772c812b1d5fbcade15312ef1c0e8ebbb12dcd4ffffffff01404b4c0000000000"
"1976a9142b6ba7c9d796b75eef7942fc9288edd37c32f5c388ac00000000010000"
"000166d7577163c932b4f9690ca6a80b6e4eb001f0a2fa9023df5595602aae96ed"
"8d000000008a4730440220262b42546302dfb654a229cefc86432b89628ff259dc"
"87edd1154535b16a67e102207b4634c020a97c3e7bbd0d4d19da6aa2269ad9dded"
"4026e896b213d73ca4b63f014104979b82d02226b3a4597523845754d44f13639e"
"3bf2df5e82c6aab2bdc79687368b01b1ab8b19875ae3c90d661a3d0a33161dab29"
"934edeb36aa01976be3baf8affffffff02404b4c00000000001976a9144854e695"
"a02af0aeacb823ccbc272134561e0a1688ac40420f00000000001976a914abee93"
"376d6b37b5c2940655a6fcaf1c8e74237988ac0000000001000000014e3f8ef2e9"
"1349a9059cb4f01e54ab2597c1387161d3da89919f7ea6acdbb371010000008c49"
"304602210081f3183471a5ca22307c0800226f3ef9c353069e0773ac76bb580654"
"d56aa523022100d4c56465bdc069060846f4fbf2f6b20520b2a80b08b168b31e66"
"ddb9c694e240014104976c79848e18251612f8940875b2b08d06e6dc73b9840e88"
"60c066b7e87432c477e9a59a453e71e6d76d5fe34058b800a098fc1740ce3012e8"
"fc8a00c96af966ffffffff02c0e1e400000000001976a9144134e75a6fcb604203"
"4aab5e18570cf1f844f54788ac404b4c00000000001976a9142b6ba7c9d796b75e"
"ef7942fc9288edd37c32f5c388ac00000000"),
SER_NETWORK, PROTOCOL_VERSION);
stream >> block;
return block;
}

File Metadata

Mime Type
text/x-diff
Expires
Thu, May 22, 03:32 (9 h, 17 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
5866381
Default Alt Text
(418 KB)

Event Timeline