diff options
Diffstat (limited to 'src')
56 files changed, 1176 insertions, 511 deletions
diff --git a/src/Makefile.am b/src/Makefile.am index 6c52d6e4b0..aa63b5f516 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -202,6 +202,7 @@ BITCOIN_CORE_H = \ script/signingprovider.h \ script/standard.h \ shutdown.h \ + signet.h \ streams.h \ support/allocators/secure.h \ support/allocators/zeroafterfree.h \ @@ -263,10 +264,10 @@ BITCOIN_CORE_H = \ walletinitinterface.h \ warnings.h \ zmq/zmqabstractnotifier.h \ - zmq/zmqconfig.h\ zmq/zmqnotificationinterface.h \ zmq/zmqpublishnotifier.h \ - zmq/zmqrpc.h + zmq/zmqrpc.h \ + zmq/zmqutil.h obj/build.h: FORCE @@ -322,6 +323,7 @@ libbitcoin_server_a_SOURCES = \ rpc/server.cpp \ script/sigcache.cpp \ shutdown.cpp \ + signet.cpp \ timedata.cpp \ torcontrol.cpp \ txdb.cpp \ @@ -345,7 +347,8 @@ libbitcoin_zmq_a_SOURCES = \ zmq/zmqabstractnotifier.cpp \ zmq/zmqnotificationinterface.cpp \ zmq/zmqpublishnotifier.cpp \ - zmq/zmqrpc.cpp + zmq/zmqrpc.cpp \ + zmq/zmqutil.cpp endif diff --git a/src/Makefile.test.include b/src/Makefile.test.include index 77e50cf22c..06dde87ddd 100644 --- a/src/Makefile.test.include +++ b/src/Makefile.test.include @@ -137,6 +137,7 @@ FUZZ_TARGETS = \ test/fuzz/secp256k1_ecdsa_signature_parse_der_lax \ test/fuzz/service_deserialize \ test/fuzz/signature_checker \ + test/fuzz/signet \ test/fuzz/snapshotmetadata_deserialize \ test/fuzz/span \ test/fuzz/spanparsing \ @@ -1129,6 +1130,12 @@ test_fuzz_signature_checker_LDADD = $(FUZZ_SUITE_LD_COMMON) test_fuzz_signature_checker_LDFLAGS = $(FUZZ_SUITE_LDFLAGS_COMMON) test_fuzz_signature_checker_SOURCES = test/fuzz/signature_checker.cpp +test_fuzz_signet_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) +test_fuzz_signet_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) +test_fuzz_signet_LDADD = $(FUZZ_SUITE_LD_COMMON) +test_fuzz_signet_LDFLAGS = $(RELDFLAGS) $(AM_LDFLAGS) $(LIBTOOL_APP_LDFLAGS) +test_fuzz_signet_SOURCES = test/fuzz/signet.cpp + test_fuzz_snapshotmetadata_deserialize_CPPFLAGS = $(AM_CPPFLAGS) $(BITCOIN_INCLUDES) -DSNAPSHOTMETADATA_DESERIALIZE=1 test_fuzz_snapshotmetadata_deserialize_CXXFLAGS = $(AM_CXXFLAGS) $(PIE_FLAGS) test_fuzz_snapshotmetadata_deserialize_LDADD = $(FUZZ_SUITE_LD_COMMON) diff --git a/src/bitcoin-cli.cpp b/src/bitcoin-cli.cpp index 198cd5eb52..e94d4dff49 100644 --- a/src/bitcoin-cli.cpp +++ b/src/bitcoin-cli.cpp @@ -87,11 +87,6 @@ static void libevent_log_cb(int severity, const char *msg) } } -////////////////////////////////////////////////////////////////////////////// -// -// Start -// - // // Exception thrown on connection error. This error is used to determine // when to wait if -rpcwait is given. @@ -112,9 +107,6 @@ public: // static int AppInitRPC(int argc, char* argv[]) { - // - // Parameters - // SetupCliArgs(gArgs); std::string error; if (!gArgs.ParseParameters(argc, argv, error)) { @@ -147,7 +139,7 @@ static int AppInitRPC(int argc, char* argv[]) tfm::format(std::cerr, "Error reading configuration file: %s\n", error); return EXIT_FAILURE; } - // Check for -chain, -testnet or -regtest parameter (BaseParams() calls are only valid after this clause) + // Check for chain settings (BaseParams() calls are only valid after this clause) try { SelectBaseParams(gArgs.GetChainName()); } catch (const std::exception& e) { diff --git a/src/bitcoin-tx.cpp b/src/bitcoin-tx.cpp index a9119d5144..085f1ecfda 100644 --- a/src/bitcoin-tx.cpp +++ b/src/bitcoin-tx.cpp @@ -78,9 +78,6 @@ static void SetupBitcoinTxArgs(ArgsManager &argsman) // static int AppInitRawTx(int argc, char* argv[]) { - // - // Parameters - // SetupBitcoinTxArgs(gArgs); std::string error; if (!gArgs.ParseParameters(argc, argv, error)) { @@ -88,7 +85,7 @@ static int AppInitRawTx(int argc, char* argv[]) return EXIT_FAILURE; } - // Check for -chain, -testnet or -regtest parameter (Params() calls are only valid after this clause) + // Check for chain settings (Params() calls are only valid after this clause) try { SelectParams(gArgs.GetChainName()); } catch (const std::exception& e) { diff --git a/src/bitcoin-wallet.cpp b/src/bitcoin-wallet.cpp index 06b0c86476..8fdf1bae0f 100644 --- a/src/bitcoin-wallet.cpp +++ b/src/bitcoin-wallet.cpp @@ -62,7 +62,7 @@ static bool WalletAppInit(int argc, char* argv[]) tfm::format(std::cerr, "Error: Specified data directory \"%s\" does not exist.\n", gArgs.GetArg("-datadir", "")); return false; } - // Check for -testnet or -regtest parameter (Params() calls are only valid after this clause) + // Check for chain settings (Params() calls are only valid after this clause) SelectParams(gArgs.GetChainName()); return true; diff --git a/src/bitcoind.cpp b/src/bitcoind.cpp index 02074f820a..455a82e390 100644 --- a/src/bitcoind.cpp +++ b/src/bitcoind.cpp @@ -37,10 +37,6 @@ static void WaitForShutdown(NodeContext& node) Interrupt(node); } -////////////////////////////////////////////////////////////////////////////// -// -// Start -// static bool AppInit(int argc, char* argv[]) { NodeContext node; @@ -81,7 +77,7 @@ static bool AppInit(int argc, char* argv[]) if (!args.ReadConfigFiles(error, true)) { return InitError(Untranslated(strprintf("Error reading configuration file: %s\n", error))); } - // Check for -chain, -testnet or -regtest parameter (Params() calls are only valid after this clause) + // Check for chain settings (Params() calls are only valid after this clause) try { SelectParams(args.GetChainName()); } catch (const std::exception& e) { diff --git a/src/chainparams.cpp b/src/chainparams.cpp index ffd2076c9a..d7f7888ef3 100644 --- a/src/chainparams.cpp +++ b/src/chainparams.cpp @@ -7,6 +7,7 @@ #include <chainparamsseeds.h> #include <consensus/merkle.h> +#include <hash.h> // for signet block challenge hash #include <tinyformat.h> #include <util/system.h> #include <util/strencodings.h> @@ -63,6 +64,8 @@ class CMainParams : public CChainParams { public: CMainParams() { strNetworkID = CBaseChainParams::MAIN; + consensus.signet_blocks = false; + consensus.signet_challenge.clear(); consensus.nSubsidyHalvingInterval = 210000; consensus.BIP16Exception = uint256S("0x00000000000002dc756eebf4f49723ed8d30cc28a5f108eb94b1ba88ac4f9c22"); consensus.BIP34Height = 227931; @@ -172,6 +175,8 @@ class CTestNetParams : public CChainParams { public: CTestNetParams() { strNetworkID = CBaseChainParams::TESTNET; + consensus.signet_blocks = false; + consensus.signet_challenge.clear(); consensus.nSubsidyHalvingInterval = 210000; consensus.BIP16Exception = uint256S("0x00000000dd30457c001f4095d208cc1296b0eed002427aa599874af7a432b105"); consensus.BIP34Height = 21111; @@ -251,12 +256,103 @@ public: }; /** + * Signet + */ +class SigNetParams : public CChainParams { +public: + explicit SigNetParams(const ArgsManager& args) { + std::vector<uint8_t> bin; + vSeeds.clear(); + + if (!args.IsArgSet("-signetchallenge")) { + LogPrintf("Using default signet network\n"); + bin = ParseHex("512103ad5e0edad18cb1f0fc0d28a3d4f1f3e445640337489abb10404f2d1e086be430210359ef5021964fe22d6f8e05b2463c9540ce96883fe3b278760f048f5189f2e6c452ae"); + vSeeds.emplace_back("178.128.221.177"); + vSeeds.emplace_back("2a01:7c8:d005:390::5"); + vSeeds.emplace_back("ntv3mtqw5wt63red.onion:38333"); + } else { + const auto signet_challenge = args.GetArgs("-signetchallenge"); + if (signet_challenge.size() != 1) { + throw std::runtime_error(strprintf("%s: -signetchallenge cannot be multiple values.", __func__)); + } + bin = ParseHex(signet_challenge[0]); + + LogPrintf("Signet with challenge %s\n", signet_challenge[0]); + } + + if (args.IsArgSet("-signetseednode")) { + vSeeds = args.GetArgs("-signetseednode"); + } + + strNetworkID = CBaseChainParams::SIGNET; + consensus.signet_blocks = true; + consensus.signet_challenge.assign(bin.begin(), bin.end()); + consensus.nSubsidyHalvingInterval = 210000; + consensus.BIP34Height = 1; + consensus.BIP65Height = 1; + consensus.BIP66Height = 1; + consensus.CSVHeight = 1; + consensus.SegwitHeight = 1; + consensus.nPowTargetTimespan = 14 * 24 * 60 * 60; // two weeks + consensus.nPowTargetSpacing = 10 * 60; + consensus.fPowAllowMinDifficultyBlocks = false; + consensus.fPowNoRetargeting = false; + consensus.nRuleChangeActivationThreshold = 1916; + consensus.nMinerConfirmationWindow = 2016; + consensus.powLimit = uint256S("00000377ae000000000000000000000000000000000000000000000000000000"); + consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].bit = 28; + consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nStartTime = 1199145601; // January 1, 2008 + consensus.vDeployments[Consensus::DEPLOYMENT_TESTDUMMY].nTimeout = 1230767999; // December 31, 2008 + + // message start is defined as the first 4 bytes of the sha256d of the block script + CHashWriter h(SER_DISK, 0); + h << consensus.signet_challenge; + uint256 hash = h.GetHash(); + memcpy(pchMessageStart, hash.begin(), 4); + LogPrintf("Signet derived magic (message start): %s\n", HexStr({pchMessageStart, pchMessageStart + 4})); + + nDefaultPort = 38333; + nPruneAfterHeight = 1000; + m_assumed_blockchain_size = 0; + m_assumed_chain_state_size = 0; + + genesis = CreateGenesisBlock(1598918400, 52613770, 0x1e0377ae, 1, 50 * COIN); + consensus.hashGenesisBlock = genesis.GetHash(); + assert(consensus.hashGenesisBlock == uint256S("0x00000008819873e925422c1ff0f99f7cc9bbb232af63a077a480a3633bee1ef6")); + assert(genesis.hashMerkleRoot == uint256S("0x4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b")); + + vFixedSeeds.clear(); + + base58Prefixes[PUBKEY_ADDRESS] = std::vector<unsigned char>(1,111); + base58Prefixes[SCRIPT_ADDRESS] = std::vector<unsigned char>(1,196); + base58Prefixes[SECRET_KEY] = std::vector<unsigned char>(1,239); + base58Prefixes[EXT_PUBLIC_KEY] = {0x04, 0x35, 0x87, 0xCF}; + base58Prefixes[EXT_SECRET_KEY] = {0x04, 0x35, 0x83, 0x94}; + + bech32_hrp = "tb"; + + fDefaultConsistencyChecks = false; + fRequireStandard = true; + m_is_test_chain = true; + m_is_mockable_chain = false; + + chainTxData = ChainTxData{ + 0, + 0, + 0 + }; + } +}; + +/** * Regression test */ class CRegTestParams : public CChainParams { public: explicit CRegTestParams(const ArgsManager& args) { strNetworkID = CBaseChainParams::REGTEST; + consensus.signet_blocks = false; + consensus.signet_challenge.clear(); consensus.nSubsidyHalvingInterval = 150; consensus.BIP16Exception = uint256(); consensus.BIP34Height = 500; // BIP34 activated on regtest (Used in functional tests) @@ -391,12 +487,15 @@ const CChainParams &Params() { std::unique_ptr<const CChainParams> CreateChainParams(const std::string& chain) { - if (chain == CBaseChainParams::MAIN) + if (chain == CBaseChainParams::MAIN) { return std::unique_ptr<CChainParams>(new CMainParams()); - else if (chain == CBaseChainParams::TESTNET) + } else if (chain == CBaseChainParams::TESTNET) { return std::unique_ptr<CChainParams>(new CTestNetParams()); - else if (chain == CBaseChainParams::REGTEST) + } else if (chain == CBaseChainParams::SIGNET) { + return std::unique_ptr<CChainParams>(new SigNetParams(gArgs)); + } else if (chain == CBaseChainParams::REGTEST) { return std::unique_ptr<CChainParams>(new CRegTestParams(gArgs)); + } throw std::runtime_error(strprintf("%s: Unknown chain %s.", __func__, chain)); } diff --git a/src/chainparamsbase.cpp b/src/chainparamsbase.cpp index 1825ced640..034e897ca6 100644 --- a/src/chainparamsbase.cpp +++ b/src/chainparamsbase.cpp @@ -13,6 +13,7 @@ const std::string CBaseChainParams::MAIN = "main"; const std::string CBaseChainParams::TESTNET = "test"; +const std::string CBaseChainParams::SIGNET = "signet"; const std::string CBaseChainParams::REGTEST = "regtest"; void SetupChainParamsBaseOptions(ArgsManager& argsman) @@ -23,6 +24,9 @@ void SetupChainParamsBaseOptions(ArgsManager& argsman) argsman.AddArg("-segwitheight=<n>", "Set the activation height of segwit. -1 to disable. (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::DEBUG_TEST); argsman.AddArg("-testnet", "Use the test chain. Equivalent to -chain=test.", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); argsman.AddArg("-vbparams=deployment:start:end", "Use given start/end times for specified version bits deployment (regtest-only)", ArgsManager::ALLOW_ANY | ArgsManager::DEBUG_ONLY, OptionsCategory::CHAINPARAMS); + argsman.AddArg("-signet", "Use the signet chain. Note that the network is defined by the -signetchallenge parameter", ArgsManager::ALLOW_ANY, OptionsCategory::CHAINPARAMS); + argsman.AddArg("-signetchallenge", "Blocks must satisfy the given script to be considered valid (only for signet networks; defaults to the global default signet test network challenge)", ArgsManager::ALLOW_STRING, OptionsCategory::CHAINPARAMS); + argsman.AddArg("-signetseednode", "Specify a seed node for the signet network, in the hostname[:port] format, e.g. sig.net:1234 (may be used multiple times to specify multiple seed nodes; defaults to the global default signet test network seed node(s))", ArgsManager::ALLOW_STRING, OptionsCategory::CHAINPARAMS); } static std::unique_ptr<CBaseChainParams> globalChainBaseParams; @@ -35,14 +39,16 @@ const CBaseChainParams& BaseParams() std::unique_ptr<CBaseChainParams> CreateBaseChainParams(const std::string& chain) { - if (chain == CBaseChainParams::MAIN) + if (chain == CBaseChainParams::MAIN) { return MakeUnique<CBaseChainParams>("", 8332); - else if (chain == CBaseChainParams::TESTNET) + } else if (chain == CBaseChainParams::TESTNET) { return MakeUnique<CBaseChainParams>("testnet3", 18332); - else if (chain == CBaseChainParams::REGTEST) + } else if (chain == CBaseChainParams::SIGNET) { + return MakeUnique<CBaseChainParams>("signet", 38332); + } else if (chain == CBaseChainParams::REGTEST) { return MakeUnique<CBaseChainParams>("regtest", 18443); - else - throw std::runtime_error(strprintf("%s: Unknown chain %s.", __func__, chain)); + } + throw std::runtime_error(strprintf("%s: Unknown chain %s.", __func__, chain)); } void SelectBaseParams(const std::string& chain) diff --git a/src/chainparamsbase.h b/src/chainparamsbase.h index 1c52d0ea97..9852446b3c 100644 --- a/src/chainparamsbase.h +++ b/src/chainparamsbase.h @@ -21,6 +21,7 @@ public: /** Chain name strings */ static const std::string MAIN; static const std::string TESTNET; + static const std::string SIGNET; static const std::string REGTEST; ///@} diff --git a/src/consensus/params.h b/src/consensus/params.h index 61b1fbc2e5..85ab3f61ef 100644 --- a/src/consensus/params.h +++ b/src/consensus/params.h @@ -80,6 +80,13 @@ struct Params { int64_t DifficultyAdjustmentInterval() const { return nPowTargetTimespan / nPowTargetSpacing; } uint256 nMinimumChainWork; uint256 defaultAssumeValid; + + /** + * If true, witness commitments contain a payload equal to a Bitcoin Script solution + * to the signet challenge. See BIP325. + */ + bool signet_blocks{false}; + std::vector<uint8_t> signet_challenge; }; } // namespace Consensus diff --git a/src/consensus/validation.h b/src/consensus/validation.h index 2a93a090d6..e007c481df 100644 --- a/src/consensus/validation.h +++ b/src/consensus/validation.h @@ -12,6 +12,12 @@ #include <primitives/transaction.h> #include <primitives/block.h> +/** Index marker for when no witness commitment is present in a coinbase transaction. */ +static constexpr int NO_WITNESS_COMMITMENT{-1}; + +/** Minimum size of a witness commitment structure. Defined in BIP 141. **/ +static constexpr size_t MINIMUM_WITNESS_COMMITMENT{38}; + /** A "reason" why a transaction was invalid, suitable for determining whether the * provider of the transaction should be banned/ignored/disconnected/etc. */ @@ -151,4 +157,25 @@ static inline int64_t GetTransactionInputWeight(const CTxIn& txin) return ::GetSerializeSize(txin, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS) * (WITNESS_SCALE_FACTOR - 1) + ::GetSerializeSize(txin, PROTOCOL_VERSION) + ::GetSerializeSize(txin.scriptWitness.stack, PROTOCOL_VERSION); } +/** Compute at which vout of the block's coinbase transaction the witness commitment occurs, or -1 if not found */ +inline int GetWitnessCommitmentIndex(const CBlock& block) +{ + int commitpos = NO_WITNESS_COMMITMENT; + if (!block.vtx.empty()) { + for (size_t o = 0; o < block.vtx[0]->vout.size(); o++) { + const CTxOut& vout = block.vtx[0]->vout[o]; + if (vout.scriptPubKey.size() >= MINIMUM_WITNESS_COMMITMENT && + vout.scriptPubKey[0] == OP_RETURN && + vout.scriptPubKey[1] == 0x24 && + vout.scriptPubKey[2] == 0xaa && + vout.scriptPubKey[3] == 0x21 && + vout.scriptPubKey[4] == 0xa9 && + vout.scriptPubKey[5] == 0xed) { + commitpos = o; + } + } + } + return commitpos; +} + #endif // BITCOIN_CONSENSUS_VALIDATION_H diff --git a/src/init.cpp b/src/init.cpp index 023aa9aba5..132acbfc81 100644 --- a/src/init.cpp +++ b/src/init.cpp @@ -488,19 +488,23 @@ void SetupServerArgs(NodeContext& node) argsman.AddArg("-zmqpubhashtx=<address>", "Enable publish hash transaction in <address>", ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); argsman.AddArg("-zmqpubrawblock=<address>", "Enable publish raw block in <address>", ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); argsman.AddArg("-zmqpubrawtx=<address>", "Enable publish raw transaction in <address>", ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); + argsman.AddArg("-zmqpubsequence=<address>", "Enable publish hash block and tx sequence in <address>", ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); argsman.AddArg("-zmqpubhashblockhwm=<n>", strprintf("Set publish hash block outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); argsman.AddArg("-zmqpubhashtxhwm=<n>", strprintf("Set publish hash transaction outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); argsman.AddArg("-zmqpubrawblockhwm=<n>", strprintf("Set publish raw block outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); argsman.AddArg("-zmqpubrawtxhwm=<n>", strprintf("Set publish raw transaction outbound message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), ArgsManager::ALLOW_ANY, OptionsCategory::ZMQ); + argsman.AddArg("-zmqpubsequencehwm=<n>", strprintf("Set publish hash sequence message high water mark (default: %d)", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM), ArgsManager::ALLOW_ANY, 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("-zmqpubsequence=<n>"); hidden_args.emplace_back("-zmqpubhashblockhwm=<n>"); hidden_args.emplace_back("-zmqpubhashtxhwm=<n>"); hidden_args.emplace_back("-zmqpubrawblockhwm=<n>"); hidden_args.emplace_back("-zmqpubrawtxhwm=<n>"); + hidden_args.emplace_back("-zmqpubsequencehwm=<n>"); #endif argsman.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); diff --git a/src/interfaces/chain.cpp b/src/interfaces/chain.cpp index 13d885a20d..4c5ebe66fc 100644 --- a/src/interfaces/chain.cpp +++ b/src/interfaces/chain.cpp @@ -59,13 +59,13 @@ public: explicit NotificationsProxy(std::shared_ptr<Chain::Notifications> notifications) : m_notifications(std::move(notifications)) {} virtual ~NotificationsProxy() = default; - void TransactionAddedToMempool(const CTransactionRef& tx) override + void TransactionAddedToMempool(const CTransactionRef& tx, uint64_t mempool_sequence) override { - m_notifications->transactionAddedToMempool(tx); + m_notifications->transactionAddedToMempool(tx, mempool_sequence); } - void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason) override + void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) override { - m_notifications->transactionRemovedFromMempool(tx, reason); + m_notifications->transactionRemovedFromMempool(tx, reason, mempool_sequence); } void BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* index) override { @@ -405,7 +405,7 @@ public: if (!m_node.mempool) return; LOCK2(::cs_main, m_node.mempool->cs); for (const CTxMemPoolEntry& entry : m_node.mempool->mapTx) { - notifications.transactionAddedToMempool(entry.GetSharedTx()); + notifications.transactionAddedToMempool(entry.GetSharedTx(), 0 /* mempool_sequence */); } } NodeContext& m_node; diff --git a/src/interfaces/chain.h b/src/interfaces/chain.h index 6e50ccb27a..85d09be0f3 100644 --- a/src/interfaces/chain.h +++ b/src/interfaces/chain.h @@ -242,8 +242,8 @@ public: { public: virtual ~Notifications() {} - virtual void transactionAddedToMempool(const CTransactionRef& tx) {} - virtual void transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason) {} + virtual void transactionAddedToMempool(const CTransactionRef& tx, uint64_t mempool_sequence) {} + virtual void transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) {} virtual void blockConnected(const CBlock& block, int height) {} virtual void blockDisconnected(const CBlock& block, int height) {} virtual void updatedBlockTip() {} diff --git a/src/net.cpp b/src/net.cpp index e35d05cec0..e7d3a146ff 100644 --- a/src/net.cpp +++ b/src/net.cpp @@ -94,6 +94,7 @@ const std::string NET_MESSAGE_COMMAND_OTHER = "*other*"; static const uint64_t RANDOMIZER_ID_NETGROUP = 0x6c0edd8036ef4036ULL; // SHA256("netgroup")[0:8] static const uint64_t RANDOMIZER_ID_LOCALHOSTNONCE = 0xd93e69e2bbfa5735ULL; // SHA256("localhostnonce")[0:8] +static const uint64_t RANDOMIZER_ID_ADDRCACHE = 0x1cf2e4ddd306dda9ULL; // SHA256("addrcache")[0:8] // // Global state variables // @@ -621,32 +622,6 @@ bool CNode::ReceiveMsgBytes(const char *pch, unsigned int nBytes, bool& complete return true; } -void CNode::SetSendVersion(int nVersionIn) -{ - // Send version may only be changed in the version message, and - // only one version message is allowed per session. We can therefore - // treat this value as const and even atomic as long as it's only used - // once a version message has been successfully processed. Any attempt to - // set this twice is an error. - if (nSendVersion != 0) { - error("Send version already set for node: %i. Refusing to change from %i to %i", id, nSendVersion, nVersionIn); - } else { - nSendVersion = nVersionIn; - } -} - -int CNode::GetSendVersion() const -{ - // The send version should always be explicitly set to - // INIT_PROTO_VERSION rather than using this value until SetSendVersion - // has been called. - if (nSendVersion == 0) { - error("Requesting unset send version for node: %i. Using %i", id, INIT_PROTO_VERSION); - return INIT_PROTO_VERSION; - } - return nSendVersion; -} - int V1TransportDeserializer::readHeader(const char *pch, unsigned int nBytes) { // copy data to temporary parsing buffer @@ -1193,7 +1168,7 @@ void CConnman::InactivityCheck(CNode *pnode) LogPrintf("socket sending timeout: %is\n", nTime - pnode->nLastSend); pnode->fDisconnect = true; } - else if (nTime - pnode->nLastRecv > (pnode->nVersion > BIP0031_VERSION ? TIMEOUT_INTERVAL : 90*60)) + else if (nTime - pnode->nLastRecv > (pnode->GetCommonVersion() > BIP0031_VERSION ? TIMEOUT_INTERVAL : 90*60)) { LogPrintf("socket receive timeout: %is\n", nTime - pnode->nLastRecv); pnode->fDisconnect = true; @@ -2560,15 +2535,47 @@ std::vector<CAddress> CConnman::GetAddresses(size_t max_addresses, size_t max_pc return addresses; } -std::vector<CAddress> CConnman::GetAddresses(Network requestor_network, size_t max_addresses, size_t max_pct) +std::vector<CAddress> CConnman::GetAddresses(CNode& requestor, size_t max_addresses, size_t max_pct) { + SOCKET socket; + WITH_LOCK(requestor.cs_hSocket, socket = requestor.hSocket); + auto local_socket_bytes = GetBindAddress(socket).GetAddrBytes(); + uint64_t cache_id = GetDeterministicRandomizer(RANDOMIZER_ID_ADDRCACHE) + .Write(requestor.addr.GetNetwork()) + .Write(local_socket_bytes.data(), local_socket_bytes.size()) + .Finalize(); const auto current_time = GetTime<std::chrono::microseconds>(); - if (m_addr_response_caches.find(requestor_network) == m_addr_response_caches.end() || - m_addr_response_caches[requestor_network].m_update_addr_response < current_time) { - m_addr_response_caches[requestor_network].m_addrs_response_cache = GetAddresses(max_addresses, max_pct); - m_addr_response_caches[requestor_network].m_update_addr_response = current_time + std::chrono::hours(21) + GetRandMillis(std::chrono::hours(6)); + auto r = m_addr_response_caches.emplace(cache_id, CachedAddrResponse{}); + CachedAddrResponse& cache_entry = r.first->second; + if (cache_entry.m_cache_entry_expiration < current_time) { // If emplace() added new one it has expiration 0. + cache_entry.m_addrs_response_cache = GetAddresses(max_addresses, max_pct); + // Choosing a proper cache lifetime is a trade-off between the privacy leak minimization + // and the usefulness of ADDR responses to honest users. + // + // Longer cache lifetime makes it more difficult for an attacker to scrape + // enough AddrMan data to maliciously infer something useful. + // By the time an attacker scraped enough AddrMan records, most of + // the records should be old enough to not leak topology info by + // e.g. analyzing real-time changes in timestamps. + // + // It takes only several hundred requests to scrape everything from an AddrMan containing 100,000 nodes, + // so ~24 hours of cache lifetime indeed makes the data less inferable by the time + // most of it could be scraped (considering that timestamps are updated via + // ADDR self-announcements and when nodes communicate). + // We also should be robust to those attacks which may not require scraping *full* victim's AddrMan + // (because even several timestamps of the same handful of nodes may leak privacy). + // + // On the other hand, longer cache lifetime makes ADDR responses + // outdated and less useful for an honest requestor, e.g. if most nodes + // in the ADDR response are no longer active. + // + // However, the churn in the network is known to be rather low. Since we consider + // nodes to be "terrible" (see IsTerrible()) if the timestamps are older than 30 days, + // max. 24 hours of "penalty" due to cache shouldn't make any meaningful difference + // in terms of the freshness of the response. + cache_entry.m_cache_entry_expiration = current_time + std::chrono::hours(21) + GetRandMillis(std::chrono::hours(6)); } - return m_addr_response_caches[requestor_network].m_addrs_response_cache; + return cache_entry.m_addrs_response_cache; } bool CConnman::AddNode(const std::string& strNode) @@ -310,7 +310,7 @@ public: * A non-malicious call (from RPC or a peer with addr permission) should * call the function without a parameter to avoid using the cache. */ - std::vector<CAddress> GetAddresses(Network requestor_network, size_t max_addresses, size_t max_pct); + std::vector<CAddress> GetAddresses(CNode& requestor, size_t max_addresses, size_t max_pct); // This allows temporarily exceeding m_max_outbound_full_relay, with the goal of finding // a peer that is better than all our current peers. @@ -483,20 +483,24 @@ private: */ struct CachedAddrResponse { std::vector<CAddress> m_addrs_response_cache; - std::chrono::microseconds m_update_addr_response{0}; + std::chrono::microseconds m_cache_entry_expiration{0}; }; /** * Addr responses stored in different caches - * per network prevent cross-network node identification. + * per (network, local socket) prevent cross-network node identification. * If a node for example is multi-homed under Tor and IPv6, * a single cache (or no cache at all) would let an attacker * to easily detect that it is the same node by comparing responses. - * The used memory equals to 1000 CAddress records (or around 32 bytes) per + * Indexing by local socket prevents leakage when a node has multiple + * listening addresses on the same network. + * + * The used memory equals to 1000 CAddress records (or around 40 bytes) per * distinct Network (up to 5) we have/had an inbound peer from, - * resulting in at most ~160 KB. + * resulting in at most ~196 KB. Every separate local socket may + * add up to ~196 KB extra. */ - std::map<Network, CachedAddrResponse> m_addr_response_caches; + std::map<uint64_t, CachedAddrResponse> m_addr_response_caches; /** * Services this instance offers. @@ -826,7 +830,6 @@ public: std::deque<CInv> vRecvGetData; uint64_t nRecvBytes GUARDED_BY(cs_vRecv){0}; - std::atomic<int> nRecvVersion{INIT_PROTO_VERSION}; std::atomic<int64_t> nLastSend{0}; std::atomic<int64_t> nLastRecv{0}; @@ -1013,6 +1016,7 @@ private: const NodeId id; const uint64_t nLocalHostNonce; const ConnectionType m_conn_type; + std::atomic<int> m_greatest_common_version{INIT_PROTO_VERSION}; //! Services offered to this peer. //! @@ -1032,7 +1036,6 @@ private: const ServiceFlags nLocalServices; const int nMyStartingHeight; - int nSendVersion{0}; NetPermissionFlags m_permissionFlags{ PF_NONE }; std::list<CNetMessage> vRecvMsg; // Used only by SocketHandler thread @@ -1064,16 +1067,14 @@ public: bool ReceiveMsgBytes(const char *pch, unsigned int nBytes, bool& complete); - void SetRecvVersion(int nVersionIn) + void SetCommonVersion(int greatest_common_version) { - nRecvVersion = nVersionIn; + m_greatest_common_version = greatest_common_version; } - int GetRecvVersion() const + int GetCommonVersion() const { - return nRecvVersion; + return m_greatest_common_version; } - void SetSendVersion(int nVersionIn); - int GetSendVersion() const; CService GetAddrLocal() const; //! May not be called more than once diff --git a/src/net_processing.cpp b/src/net_processing.cpp index 7cf6da270e..f3f30285d8 100644 --- a/src/net_processing.cpp +++ b/src/net_processing.cpp @@ -669,12 +669,12 @@ static void MaybeSetPeerAsAnnouncingHeaderAndIDs(NodeId nodeid, CConnman& connma // As per BIP152, we only get 3 of our peers to announce // blocks using compact encodings. connman.ForNode(lNodesAnnouncingHeaderAndIDs.front(), [&connman, nCMPCTBLOCKVersion](CNode* pnodeStop){ - connman.PushMessage(pnodeStop, CNetMsgMaker(pnodeStop->GetSendVersion()).Make(NetMsgType::SENDCMPCT, /*fAnnounceUsingCMPCTBLOCK=*/false, nCMPCTBLOCKVersion)); + connman.PushMessage(pnodeStop, CNetMsgMaker(pnodeStop->GetCommonVersion()).Make(NetMsgType::SENDCMPCT, /*fAnnounceUsingCMPCTBLOCK=*/false, nCMPCTBLOCKVersion)); return true; }); lNodesAnnouncingHeaderAndIDs.pop_front(); } - connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetSendVersion()).Make(NetMsgType::SENDCMPCT, /*fAnnounceUsingCMPCTBLOCK=*/true, nCMPCTBLOCKVersion)); + connman.PushMessage(pfrom, CNetMsgMaker(pfrom->GetCommonVersion()).Make(NetMsgType::SENDCMPCT, /*fAnnounceUsingCMPCTBLOCK=*/true, nCMPCTBLOCKVersion)); lNodesAnnouncingHeaderAndIDs.push_back(pfrom->GetId()); return true; }); @@ -1359,7 +1359,7 @@ void PeerManager::NewPoWValidBlock(const CBlockIndex *pindex, const std::shared_ AssertLockHeld(::cs_main); // TODO: Avoid the repeated-serialization here - if (pnode->nVersion < INVALID_CB_NO_BAN_VERSION || pnode->fDisconnect) + if (pnode->GetCommonVersion() < INVALID_CB_NO_BAN_VERSION || pnode->fDisconnect) return; ProcessBlockAvailability(pnode->GetId()); CNodeState &state = *State(pnode->GetId()); @@ -1585,7 +1585,7 @@ void static ProcessGetBlockData(CNode& pfrom, const CChainParams& chainparams, c 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()); + const CNetMsgMaker msgMaker(pfrom.GetCommonVersion()); // disconnect node in case we have reached the outbound limit for serving historical blocks if (send && connman.OutboundTargetReached(true) && @@ -1728,7 +1728,7 @@ void static ProcessGetData(CNode& pfrom, const CChainParams& chainparams, CConnm std::deque<CInv>::iterator it = pfrom.vRecvGetData.begin(); std::vector<CInv> vNotFound; - const CNetMsgMaker msgMaker(pfrom.GetSendVersion()); + const CNetMsgMaker msgMaker(pfrom.GetCommonVersion()); const std::chrono::seconds now = GetTime<std::chrono::seconds>(); // Get last mempool request time @@ -1834,14 +1834,14 @@ void PeerManager::SendBlockTransactions(CNode& pfrom, const CBlock& block, const resp.txn[i] = block.vtx[req.indexes[i]]; } LOCK(cs_main); - const CNetMsgMaker msgMaker(pfrom.GetSendVersion()); + const CNetMsgMaker msgMaker(pfrom.GetCommonVersion()); int nSendFlags = State(pfrom.GetId())->fWantsCmpctWitness ? 0 : SERIALIZE_TRANSACTION_NO_WITNESS; m_connman.PushMessage(&pfrom, msgMaker.Make(nSendFlags, NetMsgType::BLOCKTXN, resp)); } void PeerManager::ProcessHeadersMessage(CNode& pfrom, const std::vector<CBlockHeader>& headers, bool via_compact_block) { - const CNetMsgMaker msgMaker(pfrom.GetSendVersion()); + const CNetMsgMaker msgMaker(pfrom.GetCommonVersion()); size_t nCount = headers.size(); if (nCount == 0) { @@ -2211,7 +2211,7 @@ static void ProcessGetCFilters(CNode& peer, CDataStream& vRecv, const CChainPara } for (const auto& filter : filters) { - CSerializedNetMsg msg = CNetMsgMaker(peer.GetSendVersion()) + CSerializedNetMsg msg = CNetMsgMaker(peer.GetCommonVersion()) .Make(NetMsgType::CFILTER, filter); connman.PushMessage(&peer, std::move(msg)); } @@ -2263,7 +2263,7 @@ static void ProcessGetCFHeaders(CNode& peer, CDataStream& vRecv, const CChainPar return; } - CSerializedNetMsg msg = CNetMsgMaker(peer.GetSendVersion()) + CSerializedNetMsg msg = CNetMsgMaker(peer.GetCommonVersion()) .Make(NetMsgType::CFHEADERS, filter_type_ser, stop_index->GetBlockHash(), @@ -2315,7 +2315,7 @@ static void ProcessGetCFCheckPt(CNode& peer, CDataStream& vRecv, const CChainPar } } - CSerializedNetMsg msg = CNetMsgMaker(peer.GetSendVersion()) + CSerializedNetMsg msg = CNetMsgMaker(peer.GetCommonVersion()) .Make(NetMsgType::CFCHECKPT, filter_type_ser, stop_index->GetBlockHash(), @@ -2350,13 +2350,11 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat 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.IsInboundConn()) { @@ -2405,11 +2403,16 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat if (pfrom.IsInboundConn()) PushNodeVersion(pfrom, m_connman, GetAdjustedTime()); - if (nVersion >= WTXID_RELAY_VERSION) { - m_connman.PushMessage(&pfrom, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::WTXIDRELAY)); + // Change version + const int greatest_common_version = std::min(nVersion, PROTOCOL_VERSION); + pfrom.SetCommonVersion(greatest_common_version); + pfrom.nVersion = nVersion; + + if (greatest_common_version >= WTXID_RELAY_VERSION) { + m_connman.PushMessage(&pfrom, CNetMsgMaker(greatest_common_version).Make(NetMsgType::WTXIDRELAY)); } - m_connman.PushMessage(&pfrom, CNetMsgMaker(INIT_PROTO_VERSION).Make(NetMsgType::VERACK)); + m_connman.PushMessage(&pfrom, CNetMsgMaker(greatest_common_version).Make(NetMsgType::VERACK)); pfrom.nServices = nServices; pfrom.SetAddrLocal(addrMe); @@ -2430,10 +2433,6 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat pfrom.m_tx_relay->fRelayTxes = fRelay; // set to true after we get the first filter* message } - // Change version - pfrom.SetSendVersion(nSendVersion); - pfrom.nVersion = nVersion; - if((nServices & NODE_WITNESS)) { LOCK(cs_main); @@ -2479,7 +2478,7 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat } // Get recent addresses - m_connman.PushMessage(&pfrom, CNetMsgMaker(nSendVersion).Make(NetMsgType::GETADDR)); + m_connman.PushMessage(&pfrom, CNetMsgMaker(greatest_common_version).Make(NetMsgType::GETADDR)); pfrom.fGetAddr = true; // Moves address from New to Tried table in Addrman, resolves @@ -2501,9 +2500,9 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat AddTimeData(pfrom.addr, nTimeOffset); // If the peer is old enough to have the old alert system, send it the final alert. - if (pfrom.nVersion <= 70012) { + if (greatest_common_version <= 70012) { CDataStream finalAlert(ParseHex("60010000000000000000000000ffffff7f00000000ffffff7ffeffff7f01ffffff7f00000000ffffff7f00ffffff7f002f555247454e543a20416c657274206b657920636f6d70726f6d697365642c2075706772616465207265717569726564004630440220653febd6410f470f6bae11cad19c48413becb1ac2c17f908fd0fd53bdc3abd5202206d0e9c96fe88d4a0f01ed9dedae2b6f9e00da94cad0fecaae66ecf689bf71b50"), SER_NETWORK, PROTOCOL_VERSION); - m_connman.PushMessage(&pfrom, CNetMsgMaker(nSendVersion).Make("alert", finalAlert)); + m_connman.PushMessage(&pfrom, CNetMsgMaker(greatest_common_version).Make("alert", finalAlert)); } // Feeler connections exist only to verify if address is online. @@ -2520,12 +2519,10 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat } // At this point, the outgoing message serialization version can't change. - const CNetMsgMaker msgMaker(pfrom.GetSendVersion()); + const CNetMsgMaker msgMaker(pfrom.GetCommonVersion()); if (msg_type == NetMsgType::VERACK) { - pfrom.SetRecvVersion(std::min(pfrom.nVersion.load(), PROTOCOL_VERSION)); - if (!pfrom.IsInboundConn()) { // Mark this node as currently connected, so we update its timestamp later. LOCK(cs_main); @@ -2536,14 +2533,14 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat pfrom.m_tx_relay == nullptr ? "block-relay" : "full-relay"); } - if (pfrom.nVersion >= SENDHEADERS_VERSION) { + if (pfrom.GetCommonVersion() >= 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) m_connman.PushMessage(&pfrom, msgMaker.Make(NetMsgType::SENDHEADERS)); } - if (pfrom.nVersion >= SHORT_IDS_BLOCKS_VERSION) { + if (pfrom.GetCommonVersion() >= 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. @@ -2569,7 +2566,7 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat pfrom.fDisconnect = true; return; } - if (pfrom.nVersion >= WTXID_RELAY_VERSION) { + if (pfrom.GetCommonVersion() >= WTXID_RELAY_VERSION) { LOCK(cs_main); if (!State(pfrom.GetId())->m_wtxid_relay) { State(pfrom.GetId())->m_wtxid_relay = true; @@ -3544,7 +3541,7 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat if (pfrom.HasPermission(PF_ADDR)) { vAddr = m_connman.GetAddresses(MAX_ADDR_TO_SEND, MAX_PCT_ADDR_TO_SEND); } else { - vAddr = m_connman.GetAddresses(pfrom.addr.GetNetwork(), MAX_ADDR_TO_SEND, MAX_PCT_ADDR_TO_SEND); + vAddr = m_connman.GetAddresses(pfrom, MAX_ADDR_TO_SEND, MAX_PCT_ADDR_TO_SEND); } FastRandomContext insecure_rand; for (const CAddress &addr : vAddr) { @@ -3582,8 +3579,7 @@ void PeerManager::ProcessMessage(CNode& pfrom, const std::string& msg_type, CDat } if (msg_type == NetMsgType::PING) { - if (pfrom.nVersion > BIP0031_VERSION) - { + if (pfrom.GetCommonVersion() > BIP0031_VERSION) { uint64_t nonce = 0; vRecv >> nonce; // Echo the message back with the nonce. This allows for two useful features: @@ -3870,7 +3866,7 @@ bool PeerManager::ProcessMessages(CNode* pfrom, std::atomic<bool>& interruptMsgP } CNetMessage& msg(msgs.front()); - msg.SetVersion(pfrom->GetRecvVersion()); + msg.SetVersion(pfrom->GetCommonVersion()); // Check network magic if (!msg.m_valid_netmagic) { LogPrint(BCLog::NET, "PROCESSMESSAGE: INVALID MESSAGESTART %s peer=%d\n", SanitizeString(msg.m_command), pfrom->GetId()); @@ -3918,7 +3914,7 @@ void PeerManager::ConsiderEviction(CNode& pto, int64_t time_in_seconds) AssertLockHeld(cs_main); CNodeState &state = *State(pto.GetId()); - const CNetMsgMaker msgMaker(pto.GetSendVersion()); + const CNetMsgMaker msgMaker(pto.GetCommonVersion()); if (!state.m_chain_sync.m_protect && pto.IsOutboundOrBlockRelayConn() && state.fSyncStarted) { // This is an outbound peer subject to disconnection if they don't @@ -4080,7 +4076,7 @@ bool PeerManager::SendMessages(CNode* pto) return true; // If we get here, the outgoing message serialization version is set and can't change. - const CNetMsgMaker msgMaker(pto->GetSendVersion()); + const CNetMsgMaker msgMaker(pto->GetCommonVersion()); // // Message: ping @@ -4101,7 +4097,7 @@ bool PeerManager::SendMessages(CNode* pto) } pto->fPingQueued = false; pto->m_ping_start = GetTime<std::chrono::microseconds>(); - if (pto->nVersion > BIP0031_VERSION) { + if (pto->GetCommonVersion() > BIP0031_VERSION) { pto->nPingNonceSent = nonce; m_connman.PushMessage(pto, msgMaker.Make(NetMsgType::PING, nonce)); } else { @@ -4640,7 +4636,7 @@ bool PeerManager::SendMessages(CNode* pto) // // Message: feefilter // - if (pto->m_tx_relay != nullptr && pto->nVersion >= FEEFILTER_VERSION && gArgs.GetBoolArg("-feefilter", DEFAULT_FEEFILTER) && + if (pto->m_tx_relay != nullptr && pto->GetCommonVersion() >= FEEFILTER_VERSION && gArgs.GetBoolArg("-feefilter", DEFAULT_FEEFILTER) && !pto->HasPermission(PF_FORCERELAY) // peers with the forcerelay permission should not filter txs to us ) { CAmount currentFilter = m_mempool.GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000).GetFeePerK(); diff --git a/src/qt/bitcoin.cpp b/src/qt/bitcoin.cpp index eaaaaeb904..3055cd7da6 100644 --- a/src/qt/bitcoin.cpp +++ b/src/qt/bitcoin.cpp @@ -166,8 +166,8 @@ void BitcoinCore::initialize() { try { - qDebug() << __func__ << ": Running initialization in thread"; util::ThreadRename("qt-init"); + qDebug() << __func__ << ": Running initialization in thread"; interfaces::BlockAndHeaderTipInfo tip_info; bool rv = m_node.appInitMain(&tip_info); Q_EMIT initializeResult(rv, tip_info); @@ -533,7 +533,7 @@ int GuiMain(int argc, char* argv[]) // - QSettings() will use the new application name after this, resulting in network-specific settings // - Needs to be done before createOptionsModel - // Check for -chain, -testnet or -regtest parameter (Params() calls are only valid after this clause) + // Check for chain settings (Params() calls are only valid after this clause) try { SelectParams(gArgs.GetChainName()); } catch(std::exception &e) { diff --git a/src/qt/clientmodel.cpp b/src/qt/clientmodel.cpp index 7822d4c5f3..a2f46c339b 100644 --- a/src/qt/clientmodel.cpp +++ b/src/qt/clientmodel.cpp @@ -15,6 +15,7 @@ #include <net.h> #include <netbase.h> #include <util/system.h> +#include <util/threadnames.h> #include <validation.h> #include <stdint.h> @@ -52,6 +53,9 @@ ClientModel::ClientModel(interfaces::Node& node, OptionsModel *_optionsModel, QO // move timer to thread so that polling doesn't disturb main event loop timer->moveToThread(m_thread); m_thread->start(); + QTimer::singleShot(0, timer, []() { + util::ThreadRename("qt-clientmodl"); + }); subscribeToCoreSignals(); } diff --git a/src/qt/guiconstants.h b/src/qt/guiconstants.h index 9457ea37d6..882d2c8f52 100644 --- a/src/qt/guiconstants.h +++ b/src/qt/guiconstants.h @@ -46,6 +46,7 @@ static const int TOOLTIP_WRAP_THRESHOLD = 80; #define QAPP_ORG_DOMAIN "bitcoin.org" #define QAPP_APP_NAME_DEFAULT "Bitcoin-Qt" #define QAPP_APP_NAME_TESTNET "Bitcoin-Qt-testnet" +#define QAPP_APP_NAME_SIGNET "Bitcoin-Qt-signet" #define QAPP_APP_NAME_REGTEST "Bitcoin-Qt-regtest" /* One gigabyte (GB) in bytes */ diff --git a/src/qt/networkstyle.cpp b/src/qt/networkstyle.cpp index 3a251e0573..b1081f6aee 100644 --- a/src/qt/networkstyle.cpp +++ b/src/qt/networkstyle.cpp @@ -19,7 +19,8 @@ static const struct { } network_styles[] = { {"main", QAPP_APP_NAME_DEFAULT, 0, 0}, {"test", QAPP_APP_NAME_TESTNET, 70, 30}, - {"regtest", QAPP_APP_NAME_REGTEST, 160, 30} + {"signet", QAPP_APP_NAME_SIGNET, 35, 15}, + {"regtest", QAPP_APP_NAME_REGTEST, 160, 30}, }; static const unsigned network_styles_count = sizeof(network_styles)/sizeof(*network_styles); diff --git a/src/qt/rpcconsole.cpp b/src/qt/rpcconsole.cpp index a14fae6460..4c5601242e 100644 --- a/src/qt/rpcconsole.cpp +++ b/src/qt/rpcconsole.cpp @@ -20,6 +20,7 @@ #include <rpc/client.h> #include <util/strencodings.h> #include <util/system.h> +#include <util/threadnames.h> #include <univalue.h> @@ -978,6 +979,9 @@ void RPCConsole::startExecutor() // Default implementation of QThread::run() simply spins up an event loop in the thread, // which is what we want. thread.start(); + QTimer::singleShot(0, executor, []() { + util::ThreadRename("qt-rpcconsole"); + }); } void RPCConsole::on_tabWidget_currentChanged(int index) diff --git a/src/qt/walletcontroller.cpp b/src/qt/walletcontroller.cpp index bee17abf11..d9e0274d01 100644 --- a/src/qt/walletcontroller.cpp +++ b/src/qt/walletcontroller.cpp @@ -14,6 +14,7 @@ #include <interfaces/handler.h> #include <interfaces/node.h> #include <util/string.h> +#include <util/threadnames.h> #include <util/translation.h> #include <wallet/wallet.h> @@ -45,6 +46,9 @@ WalletController::WalletController(ClientModel& client_model, const PlatformStyl m_activity_worker->moveToThread(m_activity_thread); m_activity_thread->start(); + QTimer::singleShot(0, m_activity_worker, []() { + util::ThreadRename("qt-walletctrl"); + }); } // Not using the default destructor because not all member types definitions are diff --git a/src/rest.cpp b/src/rest.cpp index f0bcbe55f9..949cc9d84a 100644 --- a/src/rest.cpp +++ b/src/rest.cpp @@ -303,7 +303,7 @@ static bool rest_block_notxdetails(const util::Ref& context, HTTPRequest* req, c } // A bit of a hack - dependency on a function defined in rpc/blockchain.cpp -UniValue getblockchaininfo(const JSONRPCRequest& request); +RPCHelpMan getblockchaininfo(); static bool rest_chaininfo(const util::Ref& context, HTTPRequest* req, const std::string& strURIPart) { @@ -316,7 +316,7 @@ static bool rest_chaininfo(const util::Ref& context, HTTPRequest* req, const std case RetFormat::JSON: { JSONRPCRequest jsonRequest(context); jsonRequest.params = UniValue(UniValue::VARR); - UniValue chainInfoObject = getblockchaininfo(jsonRequest); + UniValue chainInfoObject = getblockchaininfo().HandleRequest(jsonRequest); std::string strJSON = chainInfoObject.write() + "\n"; req->WriteHeader("Content-Type", "application/json"); req->WriteReply(HTTP_OK, strJSON); diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 04b9c6b1cc..0bb7342db0 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -189,9 +189,9 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIn return result; } -static UniValue getblockcount(const JSONRPCRequest& request) +static RPCHelpMan getblockcount() { - RPCHelpMan{"getblockcount", + return RPCHelpMan{"getblockcount", "\nReturns the height of the most-work fully-validated chain.\n" "The genesis block has height 0.\n", {}, @@ -201,15 +201,17 @@ static UniValue getblockcount(const JSONRPCRequest& request) HelpExampleCli("getblockcount", "") + HelpExampleRpc("getblockcount", "") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ LOCK(cs_main); return ::ChainActive().Height(); +}, + }; } -static UniValue getbestblockhash(const JSONRPCRequest& request) +static RPCHelpMan getbestblockhash() { - RPCHelpMan{"getbestblockhash", + return RPCHelpMan{"getbestblockhash", "\nReturns the hash of the best (tip) block in the most-work fully-validated chain.\n", {}, RPCResult{ @@ -218,10 +220,12 @@ static UniValue getbestblockhash(const JSONRPCRequest& request) HelpExampleCli("getbestblockhash", "") + HelpExampleRpc("getbestblockhash", "") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ LOCK(cs_main); return ::ChainActive().Tip()->GetBlockHash().GetHex(); +}, + }; } void RPCNotifyBlockChange(const CBlockIndex* pindex) @@ -234,9 +238,9 @@ void RPCNotifyBlockChange(const CBlockIndex* pindex) cond_blockchange.notify_all(); } -static UniValue waitfornewblock(const JSONRPCRequest& request) +static RPCHelpMan waitfornewblock() { - RPCHelpMan{"waitfornewblock", + return RPCHelpMan{"waitfornewblock", "\nWaits for a specific new block and returns useful info about it.\n" "\nReturns the current block on timeout or exit.\n", { @@ -252,7 +256,8 @@ static UniValue waitfornewblock(const JSONRPCRequest& request) HelpExampleCli("waitfornewblock", "1000") + HelpExampleRpc("waitfornewblock", "1000") }, - }.Check(request); + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ int timeout = 0; if (!request.params[0].isNull()) timeout = request.params[0].get_int(); @@ -271,11 +276,13 @@ static UniValue waitfornewblock(const JSONRPCRequest& request) ret.pushKV("hash", block.hash.GetHex()); ret.pushKV("height", block.height); return ret; +}, + }; } -static UniValue waitforblock(const JSONRPCRequest& request) +static RPCHelpMan waitforblock() { - RPCHelpMan{"waitforblock", + return RPCHelpMan{"waitforblock", "\nWaits for a specific new block and returns useful info about it.\n" "\nReturns the current block on timeout or exit.\n", { @@ -292,7 +299,8 @@ static UniValue waitforblock(const JSONRPCRequest& request) HelpExampleCli("waitforblock", "\"0000000000079f8ef3d2c688c244eb7a4570b24c9ed7b4a8c619eb02596f8862\" 1000") + HelpExampleRpc("waitforblock", "\"0000000000079f8ef3d2c688c244eb7a4570b24c9ed7b4a8c619eb02596f8862\", 1000") }, - }.Check(request); + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ int timeout = 0; uint256 hash(ParseHashV(request.params[0], "blockhash")); @@ -314,11 +322,13 @@ static UniValue waitforblock(const JSONRPCRequest& request) ret.pushKV("hash", block.hash.GetHex()); ret.pushKV("height", block.height); return ret; +}, + }; } -static UniValue waitforblockheight(const JSONRPCRequest& request) +static RPCHelpMan waitforblockheight() { - RPCHelpMan{"waitforblockheight", + return RPCHelpMan{"waitforblockheight", "\nWaits for (at least) block height and returns the height and hash\n" "of the current tip.\n" "\nReturns the current block on timeout or exit.\n", @@ -336,7 +346,8 @@ static UniValue waitforblockheight(const JSONRPCRequest& request) HelpExampleCli("waitforblockheight", "100 1000") + HelpExampleRpc("waitforblockheight", "100, 1000") }, - }.Check(request); + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ int timeout = 0; int height = request.params[0].get_int(); @@ -357,11 +368,13 @@ static UniValue waitforblockheight(const JSONRPCRequest& request) ret.pushKV("hash", block.hash.GetHex()); ret.pushKV("height", block.height); return ret; +}, + }; } -static UniValue syncwithvalidationinterfacequeue(const JSONRPCRequest& request) +static RPCHelpMan syncwithvalidationinterfacequeue() { - RPCHelpMan{"syncwithvalidationinterfacequeue", + return RPCHelpMan{"syncwithvalidationinterfacequeue", "\nWaits for the validation interface queue to catch up on everything that was there when we entered this function.\n", {}, RPCResult{RPCResult::Type::NONE, "", ""}, @@ -369,15 +382,17 @@ static UniValue syncwithvalidationinterfacequeue(const JSONRPCRequest& request) HelpExampleCli("syncwithvalidationinterfacequeue","") + HelpExampleRpc("syncwithvalidationinterfacequeue","") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ SyncWithValidationInterfaceQueue(); return NullUniValue; +}, + }; } -static UniValue getdifficulty(const JSONRPCRequest& request) +static RPCHelpMan getdifficulty() { - RPCHelpMan{"getdifficulty", + return RPCHelpMan{"getdifficulty", "\nReturns the proof-of-work difficulty as a multiple of the minimum difficulty.\n", {}, RPCResult{ @@ -386,10 +401,12 @@ static UniValue getdifficulty(const JSONRPCRequest& request) HelpExampleCli("getdifficulty", "") + HelpExampleRpc("getdifficulty", "") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ LOCK(cs_main); return GetDifficulty(::ChainActive().Tip()); +}, + }; } static std::vector<RPCResult> MempoolEntryDescription() { return { @@ -483,9 +500,12 @@ static void entryToJSON(const CTxMemPool& pool, UniValue& info, const CTxMemPool info.pushKV("unbroadcast", pool.IsUnbroadcastTx(tx.GetHash())); } -UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose) +UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose, bool include_mempool_sequence) { if (verbose) { + if (include_mempool_sequence) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Verbose results cannot contain mempool sequence values."); + } LOCK(pool.cs); UniValue o(UniValue::VOBJ); for (const CTxMemPoolEntry& e : pool.mapTx) { @@ -499,24 +519,36 @@ UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose) } return o; } else { + uint64_t mempool_sequence; std::vector<uint256> vtxid; - pool.queryHashes(vtxid); - + { + LOCK(pool.cs); + pool.queryHashes(vtxid); + mempool_sequence = pool.GetSequence(); + } UniValue a(UniValue::VARR); for (const uint256& hash : vtxid) a.push_back(hash.ToString()); - return a; + if (!include_mempool_sequence) { + return a; + } else { + UniValue o(UniValue::VOBJ); + o.pushKV("txids", a); + o.pushKV("mempool_sequence", mempool_sequence); + return o; + } } } -static UniValue getrawmempool(const JSONRPCRequest& request) +static RPCHelpMan getrawmempool() { - RPCHelpMan{"getrawmempool", + return RPCHelpMan{"getrawmempool", "\nReturns all transaction ids in memory pool as a json array of string transaction ids.\n" "\nHint: use getmempoolentry to fetch a specific transaction from the mempool.\n", { {"verbose", RPCArg::Type::BOOL, /* default */ "false", "True for a json object, false for array of transaction ids"}, + {"mempool_sequence", RPCArg::Type::BOOL, /* default */ "false", "If verbose=false, returns a json object with transaction list and mempool sequence number attached."}, }, { RPCResult{"for verbose = false", @@ -529,23 +561,39 @@ static UniValue getrawmempool(const JSONRPCRequest& request) { {RPCResult::Type::OBJ, "transactionid", "", MempoolEntryDescription()}, }}, + RPCResult{"for verbose = false and mempool_sequence = true", + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::ARR, "txids", "", + { + {RPCResult::Type::STR_HEX, "", "The transaction id"}, + }}, + {RPCResult::Type::NUM, "mempool_sequence", "The mempool sequence value."}, + }}, }, RPCExamples{ HelpExampleCli("getrawmempool", "true") + HelpExampleRpc("getrawmempool", "true") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ bool fVerbose = false; if (!request.params[0].isNull()) fVerbose = request.params[0].get_bool(); - return MempoolToJSON(EnsureMemPool(request.context), fVerbose); + bool include_mempool_sequence = false; + if (!request.params[1].isNull()) { + include_mempool_sequence = request.params[1].get_bool(); + } + + return MempoolToJSON(EnsureMemPool(request.context), fVerbose, include_mempool_sequence); +}, + }; } -static UniValue getmempoolancestors(const JSONRPCRequest& request) +static RPCHelpMan getmempoolancestors() { - RPCHelpMan{"getmempoolancestors", + return RPCHelpMan{"getmempoolancestors", "\nIf txid is in the mempool, returns all in-mempool ancestors.\n", { {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"}, @@ -565,8 +613,8 @@ static UniValue getmempoolancestors(const JSONRPCRequest& request) HelpExampleCli("getmempoolancestors", "\"mytxid\"") + HelpExampleRpc("getmempoolancestors", "\"mytxid\"") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ bool fVerbose = false; if (!request.params[1].isNull()) fVerbose = request.params[1].get_bool(); @@ -603,11 +651,13 @@ static UniValue getmempoolancestors(const JSONRPCRequest& request) } return o; } +}, + }; } -static UniValue getmempooldescendants(const JSONRPCRequest& request) +static RPCHelpMan getmempooldescendants() { - RPCHelpMan{"getmempooldescendants", + return RPCHelpMan{"getmempooldescendants", "\nIf txid is in the mempool, returns all in-mempool descendants.\n", { {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"}, @@ -627,8 +677,8 @@ static UniValue getmempooldescendants(const JSONRPCRequest& request) HelpExampleCli("getmempooldescendants", "\"mytxid\"") + HelpExampleRpc("getmempooldescendants", "\"mytxid\"") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ bool fVerbose = false; if (!request.params[1].isNull()) fVerbose = request.params[1].get_bool(); @@ -666,11 +716,13 @@ static UniValue getmempooldescendants(const JSONRPCRequest& request) } return o; } +}, + }; } -static UniValue getmempoolentry(const JSONRPCRequest& request) +static RPCHelpMan getmempoolentry() { - RPCHelpMan{"getmempoolentry", + return RPCHelpMan{"getmempoolentry", "\nReturns mempool data for given transaction\n", { {"txid", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction id (must be in mempool)"}, @@ -681,8 +733,8 @@ static UniValue getmempoolentry(const JSONRPCRequest& request) HelpExampleCli("getmempoolentry", "\"mytxid\"") + HelpExampleRpc("getmempoolentry", "\"mytxid\"") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ uint256 hash = ParseHashV(request.params[0], "parameter 1"); const CTxMemPool& mempool = EnsureMemPool(request.context); @@ -697,11 +749,13 @@ static UniValue getmempoolentry(const JSONRPCRequest& request) UniValue info(UniValue::VOBJ); entryToJSON(mempool, info, e); return info; +}, + }; } -static UniValue getblockhash(const JSONRPCRequest& request) +static RPCHelpMan getblockhash() { - RPCHelpMan{"getblockhash", + return RPCHelpMan{"getblockhash", "\nReturns hash of block in best-block-chain at height provided.\n", { {"height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The height index"}, @@ -712,8 +766,8 @@ static UniValue getblockhash(const JSONRPCRequest& request) HelpExampleCli("getblockhash", "1000") + HelpExampleRpc("getblockhash", "1000") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ LOCK(cs_main); int nHeight = request.params[0].get_int(); @@ -722,11 +776,13 @@ static UniValue getblockhash(const JSONRPCRequest& request) CBlockIndex* pblockindex = ::ChainActive()[nHeight]; return pblockindex->GetBlockHash().GetHex(); +}, + }; } -static UniValue getblockheader(const JSONRPCRequest& request) +static RPCHelpMan getblockheader() { - RPCHelpMan{"getblockheader", + return RPCHelpMan{"getblockheader", "\nIf verbose is false, returns a string that is serialized, hex-encoded data for blockheader 'hash'.\n" "If verbose is true, returns an Object with information about blockheader <hash>.\n", { @@ -760,8 +816,8 @@ static UniValue getblockheader(const JSONRPCRequest& request) HelpExampleCli("getblockheader", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"") + HelpExampleRpc("getblockheader", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ uint256 hash(ParseHashV(request.params[0], "hash")); bool fVerbose = true; @@ -789,6 +845,8 @@ static UniValue getblockheader(const JSONRPCRequest& request) } return blockheaderToJSON(tip, pblockindex); +}, + }; } static CBlock GetBlockChecked(const CBlockIndex* pblockindex) @@ -822,9 +880,9 @@ static CBlockUndo GetUndoChecked(const CBlockIndex* pblockindex) return blockUndo; } -static UniValue getblock(const JSONRPCRequest& request) +static RPCHelpMan getblock() { - RPCHelpMan{"getblock", + return RPCHelpMan{"getblock", "\nIf verbosity is 0, returns a string that is serialized, hex-encoded data for block 'hash'.\n" "If verbosity is 1, returns an Object with information about block <hash>.\n" "If verbosity is 2, returns an Object with information about block <hash> and information about each transaction. \n", @@ -877,8 +935,8 @@ static UniValue getblock(const JSONRPCRequest& request) HelpExampleCli("getblock", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"") + HelpExampleRpc("getblock", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\"") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ uint256 hash(ParseHashV(request.params[0], "blockhash")); int verbosity = 1; @@ -913,11 +971,13 @@ static UniValue getblock(const JSONRPCRequest& request) } return blockToJSON(block, tip, pblockindex, verbosity >= 2); +}, + }; } -static UniValue pruneblockchain(const JSONRPCRequest& request) +static RPCHelpMan pruneblockchain() { - RPCHelpMan{"pruneblockchain", "", + return RPCHelpMan{"pruneblockchain", "", { {"height", RPCArg::Type::NUM, RPCArg::Optional::NO, "The block height to prune up to. May be set to a discrete height, or to a " + UNIX_EPOCH_TIME + "\n" " to prune blocks whose block time is at least 2 hours older than the provided timestamp."}, @@ -928,8 +988,8 @@ static UniValue pruneblockchain(const JSONRPCRequest& request) HelpExampleCli("pruneblockchain", "1000") + HelpExampleRpc("pruneblockchain", "1000") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ if (!fPruneMode) throw JSONRPCError(RPC_MISC_ERROR, "Cannot prune blocks because node is not in prune mode."); @@ -968,11 +1028,13 @@ static UniValue pruneblockchain(const JSONRPCRequest& request) block = block->pprev; } return uint64_t(block->nHeight); +}, + }; } -static UniValue gettxoutsetinfo(const JSONRPCRequest& request) +static RPCHelpMan gettxoutsetinfo() { - RPCHelpMan{"gettxoutsetinfo", + return RPCHelpMan{"gettxoutsetinfo", "\nReturns statistics about the unspent transaction output set.\n" "Note this call may take some time.\n", { @@ -994,8 +1056,8 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request) HelpExampleCli("gettxoutsetinfo", "") + HelpExampleRpc("gettxoutsetinfo", "") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ UniValue ret(UniValue::VOBJ); CCoinsStats stats; @@ -1020,11 +1082,13 @@ static UniValue gettxoutsetinfo(const JSONRPCRequest& request) throw JSONRPCError(RPC_INTERNAL_ERROR, "Unable to read UTXO set"); } return ret; +}, + }; } -UniValue gettxout(const JSONRPCRequest& request) +static RPCHelpMan gettxout() { - RPCHelpMan{"gettxout", + return RPCHelpMan{"gettxout", "\nReturns details about an unspent transaction output.\n", { {"txid", RPCArg::Type::STR, RPCArg::Optional::NO, "The transaction id"}, @@ -1056,8 +1120,8 @@ UniValue gettxout(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("gettxout", "\"txid\", 1") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ LOCK(cs_main); UniValue ret(UniValue::VOBJ); @@ -1099,11 +1163,13 @@ UniValue gettxout(const JSONRPCRequest& request) ret.pushKV("coinbase", (bool)coin.fCoinBase); return ret; +}, + }; } -static UniValue verifychain(const JSONRPCRequest& request) +static RPCHelpMan verifychain() { - RPCHelpMan{"verifychain", + return RPCHelpMan{"verifychain", "\nVerifies blockchain database.\n", { {"checklevel", RPCArg::Type::NUM, /* default */ strprintf("%d, range=0-4", DEFAULT_CHECKLEVEL), @@ -1116,14 +1182,16 @@ static UniValue verifychain(const JSONRPCRequest& request) HelpExampleCli("verifychain", "") + HelpExampleRpc("verifychain", "") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ const int check_level(request.params[0].isNull() ? DEFAULT_CHECKLEVEL : request.params[0].get_int()); const int check_depth{request.params[1].isNull() ? DEFAULT_CHECKBLOCKS : request.params[1].get_int()}; LOCK(cs_main); return CVerifyDB().VerifyDB(Params(), &::ChainstateActive().CoinsTip(), check_level, check_depth); +}, + }; } static void BuriedForkDescPushBack(UniValue& softforks, const std::string &name, int height) EXCLUSIVE_LOCKS_REQUIRED(cs_main) @@ -1192,9 +1260,9 @@ static void BIP9SoftForkDescPushBack(UniValue& softforks, const std::string &nam softforks.pushKV(name, rv); } -UniValue getblockchaininfo(const JSONRPCRequest& request) +RPCHelpMan getblockchaininfo() { - RPCHelpMan{"getblockchaininfo", + return RPCHelpMan{"getblockchaininfo", "Returns an object containing various state info regarding blockchain processing.\n", {}, RPCResult{ @@ -1245,8 +1313,8 @@ UniValue getblockchaininfo(const JSONRPCRequest& request) HelpExampleCli("getblockchaininfo", "") + HelpExampleRpc("getblockchaininfo", "") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ LOCK(cs_main); const CBlockIndex* tip = ::ChainActive().Tip(); @@ -1291,6 +1359,8 @@ UniValue getblockchaininfo(const JSONRPCRequest& request) obj.pushKV("warnings", GetWarnings(false).original); return obj; +}, + }; } /** Comparison function for sorting the getchaintips heads. */ @@ -1308,9 +1378,9 @@ struct CompareBlocksByHeight } }; -static UniValue getchaintips(const JSONRPCRequest& request) +static RPCHelpMan getchaintips() { - RPCHelpMan{"getchaintips", + return RPCHelpMan{"getchaintips", "Return information about all known tips in the block tree," " including the main chain as well as orphaned branches.\n", {}, @@ -1333,8 +1403,8 @@ static UniValue getchaintips(const JSONRPCRequest& request) HelpExampleCli("getchaintips", "") + HelpExampleRpc("getchaintips", "") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ ChainstateManager& chainman = EnsureChainman(request.context); LOCK(cs_main); @@ -1401,6 +1471,8 @@ static UniValue getchaintips(const JSONRPCRequest& request) } return res; +}, + }; } UniValue MempoolInfoToJSON(const CTxMemPool& pool) @@ -1420,9 +1492,9 @@ UniValue MempoolInfoToJSON(const CTxMemPool& pool) return ret; } -static UniValue getmempoolinfo(const JSONRPCRequest& request) +static RPCHelpMan getmempoolinfo() { - RPCHelpMan{"getmempoolinfo", + return RPCHelpMan{"getmempoolinfo", "\nReturns details on the active state of the TX memory pool.\n", {}, RPCResult{ @@ -1441,14 +1513,16 @@ static UniValue getmempoolinfo(const JSONRPCRequest& request) HelpExampleCli("getmempoolinfo", "") + HelpExampleRpc("getmempoolinfo", "") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ return MempoolInfoToJSON(EnsureMemPool(request.context)); +}, + }; } -static UniValue preciousblock(const JSONRPCRequest& request) +static RPCHelpMan preciousblock() { - RPCHelpMan{"preciousblock", + return RPCHelpMan{"preciousblock", "\nTreats a block as if it were received before others with the same work.\n" "\nA later preciousblock call can override the effect of an earlier one.\n" "\nThe effects of preciousblock are not retained across restarts.\n", @@ -1460,8 +1534,8 @@ static UniValue preciousblock(const JSONRPCRequest& request) HelpExampleCli("preciousblock", "\"blockhash\"") + HelpExampleRpc("preciousblock", "\"blockhash\"") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ uint256 hash(ParseHashV(request.params[0], "blockhash")); CBlockIndex* pblockindex; @@ -1481,11 +1555,13 @@ static UniValue preciousblock(const JSONRPCRequest& request) } return NullUniValue; +}, + }; } -static UniValue invalidateblock(const JSONRPCRequest& request) +static RPCHelpMan invalidateblock() { - RPCHelpMan{"invalidateblock", + return RPCHelpMan{"invalidateblock", "\nPermanently marks a block as invalid, as if it violated a consensus rule.\n", { {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hash of the block to mark as invalid"}, @@ -1495,8 +1571,8 @@ static UniValue invalidateblock(const JSONRPCRequest& request) HelpExampleCli("invalidateblock", "\"blockhash\"") + HelpExampleRpc("invalidateblock", "\"blockhash\"") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ uint256 hash(ParseHashV(request.params[0], "blockhash")); BlockValidationState state; @@ -1519,11 +1595,13 @@ static UniValue invalidateblock(const JSONRPCRequest& request) } return NullUniValue; +}, + }; } -static UniValue reconsiderblock(const JSONRPCRequest& request) +static RPCHelpMan reconsiderblock() { - RPCHelpMan{"reconsiderblock", + return RPCHelpMan{"reconsiderblock", "\nRemoves invalidity status of a block, its ancestors and its descendants, reconsider them for activation.\n" "This can be used to undo the effects of invalidateblock.\n", { @@ -1534,8 +1612,8 @@ static UniValue reconsiderblock(const JSONRPCRequest& request) HelpExampleCli("reconsiderblock", "\"blockhash\"") + HelpExampleRpc("reconsiderblock", "\"blockhash\"") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ uint256 hash(ParseHashV(request.params[0], "blockhash")); { @@ -1556,11 +1634,13 @@ static UniValue reconsiderblock(const JSONRPCRequest& request) } return NullUniValue; +}, + }; } -static UniValue getchaintxstats(const JSONRPCRequest& request) +static RPCHelpMan getchaintxstats() { - RPCHelpMan{"getchaintxstats", + return RPCHelpMan{"getchaintxstats", "\nCompute statistics about the total number and rate of transactions in the chain.\n", { {"nblocks", RPCArg::Type::NUM, /* default */ "one month", "Size of the window in number of blocks"}, @@ -1582,8 +1662,8 @@ static UniValue getchaintxstats(const JSONRPCRequest& request) HelpExampleCli("getchaintxstats", "") + HelpExampleRpc("getchaintxstats", "2016") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ const CBlockIndex* pindex; int blockcount = 30 * 24 * 60 * 60 / Params().GetConsensus().nPowTargetSpacing; // By default: 1 month @@ -1633,6 +1713,8 @@ static UniValue getchaintxstats(const JSONRPCRequest& request) } return ret; +}, + }; } template<typename T> @@ -1691,9 +1773,9 @@ static inline bool SetHasKeys(const std::set<T>& set, const Tk& key, const Args& // outpoint (needed for the utxo index) + nHeight + fCoinBase static constexpr size_t PER_UTXO_OVERHEAD = sizeof(COutPoint) + sizeof(uint32_t) + sizeof(bool); -static UniValue getblockstats(const JSONRPCRequest& request) +static RPCHelpMan getblockstats() { - RPCHelpMan{"getblockstats", + return RPCHelpMan{"getblockstats", "\nCompute per block statistics for a given window. All amounts are in satoshis.\n" "It won't work for some heights with pruning.\n", { @@ -1751,8 +1833,8 @@ static UniValue getblockstats(const JSONRPCRequest& request) HelpExampleRpc("getblockstats", R"("00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09", ["minfeerate","avgfeerate"])") + HelpExampleRpc("getblockstats", R"(1000, ["minfeerate","avgfeerate"])") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ LOCK(cs_main); CBlockIndex* pindex; @@ -1948,11 +2030,13 @@ static UniValue getblockstats(const JSONRPCRequest& request) ret.pushKV(stat, value); } return ret; +}, + }; } -static UniValue savemempool(const JSONRPCRequest& request) +static RPCHelpMan savemempool() { - RPCHelpMan{"savemempool", + return RPCHelpMan{"savemempool", "\nDumps the mempool to disk. It will fail until the previous dump is fully loaded.\n", {}, RPCResult{RPCResult::Type::NONE, "", ""}, @@ -1960,8 +2044,8 @@ static UniValue savemempool(const JSONRPCRequest& request) HelpExampleCli("savemempool", "") + HelpExampleRpc("savemempool", "") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ const CTxMemPool& mempool = EnsureMemPool(request.context); if (!mempool.IsLoaded()) { @@ -1973,6 +2057,8 @@ static UniValue savemempool(const JSONRPCRequest& request) } return NullUniValue; +}, + }; } namespace { @@ -2034,9 +2120,9 @@ public: } }; -UniValue scantxoutset(const JSONRPCRequest& request) +static RPCHelpMan scantxoutset() { - RPCHelpMan{"scantxoutset", + return RPCHelpMan{"scantxoutset", "\nEXPERIMENTAL warning: this call may be removed or changed in future releases.\n" "\nScans the unspent transaction output set for entries that match certain output descriptors.\n" "Examples of output descriptors are:\n" @@ -2090,8 +2176,8 @@ UniValue scantxoutset(const JSONRPCRequest& request) {RPCResult::Type::STR_AMOUNT, "total_amount", "The total amount of all found unspent outputs in " + CURRENCY_UNIT}, }}, RPCExamples{""}, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}); UniValue result(UniValue::VOBJ); @@ -2184,11 +2270,13 @@ UniValue scantxoutset(const JSONRPCRequest& request) throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid command"); } return result; +}, + }; } -static UniValue getblockfilter(const JSONRPCRequest& request) +static RPCHelpMan getblockfilter() { - RPCHelpMan{"getblockfilter", + return RPCHelpMan{"getblockfilter", "\nRetrieve a BIP 157 content filter for a particular block.\n", { {"blockhash", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The hash of the block"}, @@ -2203,9 +2291,9 @@ static UniValue getblockfilter(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("getblockfilter", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\" \"basic\"") + HelpExampleRpc("getblockfilter", "\"00000000c937983704a73af28acdec37b049d214adbda81d7e2a3dd146f6ed09\", \"basic\"") - } - }.Check(request); - + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ uint256 block_hash = ParseHashV(request.params[0], "blockhash"); std::string filtertype_name = "basic"; if (!request.params[1].isNull()) { @@ -2260,6 +2348,8 @@ static UniValue getblockfilter(const JSONRPCRequest& request) ret.pushKV("filter", HexStr(filter.GetEncodedFilter())); ret.pushKV("header", filter_header.GetHex()); return ret; +}, + }; } /** @@ -2267,9 +2357,9 @@ static UniValue getblockfilter(const JSONRPCRequest& request) * * @see SnapshotMetadata */ -UniValue dumptxoutset(const JSONRPCRequest& request) +static RPCHelpMan dumptxoutset() { - RPCHelpMan{ + return RPCHelpMan{ "dumptxoutset", "\nWrite the serialized UTXO set to disk.\n", { @@ -2290,9 +2380,9 @@ UniValue dumptxoutset(const JSONRPCRequest& request) }, RPCExamples{ HelpExampleCli("dumptxoutset", "utxo.dat") - } - }.Check(request); - + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ fs::path path = fs::absolute(request.params[0].get_str(), GetDataDir()); // Write to a temporary path and then move into `path` on completion // to avoid confusion due to an interruption. @@ -2366,6 +2456,8 @@ UniValue dumptxoutset(const JSONRPCRequest& request) result.pushKV("base_height", tip->nHeight); result.pushKV("path", path.string()); return result; +}, + }; } void RegisterBlockchainRPCCommands(CRPCTable &t) @@ -2388,7 +2480,7 @@ static const CRPCCommand commands[] = { "blockchain", "getmempooldescendants", &getmempooldescendants, {"txid","verbose"} }, { "blockchain", "getmempoolentry", &getmempoolentry, {"txid"} }, { "blockchain", "getmempoolinfo", &getmempoolinfo, {} }, - { "blockchain", "getrawmempool", &getrawmempool, {"verbose"} }, + { "blockchain", "getrawmempool", &getrawmempool, {"verbose", "mempool_sequence"} }, { "blockchain", "gettxout", &gettxout, {"txid","n","include_mempool"} }, { "blockchain", "gettxoutsetinfo", &gettxoutsetinfo, {"hash_type"} }, { "blockchain", "pruneblockchain", &pruneblockchain, {"height"} }, diff --git a/src/rpc/blockchain.h b/src/rpc/blockchain.h index 5c9a43b13e..5b362bf211 100644 --- a/src/rpc/blockchain.h +++ b/src/rpc/blockchain.h @@ -43,7 +43,7 @@ UniValue blockToJSON(const CBlock& block, const CBlockIndex* tip, const CBlockIn UniValue MempoolInfoToJSON(const CTxMemPool& pool); /** Mempool to JSON */ -UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose = false); +UniValue MempoolToJSON(const CTxMemPool& pool, bool verbose = false, bool include_mempool_sequence = false); /** Block header to JSON */ UniValue blockheaderToJSON(const CBlockIndex* tip, const CBlockIndex* blockindex) LOCKS_EXCLUDED(cs_main); diff --git a/src/rpc/client.cpp b/src/rpc/client.cpp index 6ef3294132..3c432464f2 100644 --- a/src/rpc/client.cpp +++ b/src/rpc/client.cpp @@ -142,6 +142,7 @@ static const CRPCConvertParam vRPCConvertParams[] = { "pruneblockchain", 0, "height" }, { "keypoolrefill", 0, "newsize" }, { "getrawmempool", 0, "verbose" }, + { "getrawmempool", 1, "mempool_sequence" }, { "estimatesmartfee", 0, "conf_target" }, { "estimaterawfee", 0, "conf_target" }, { "estimaterawfee", 1, "threshold" }, diff --git a/src/rpc/rawtransaction.cpp b/src/rpc/rawtransaction.cpp index 1a43ffcc53..e60e0a2d90 100644 --- a/src/rpc/rawtransaction.cpp +++ b/src/rpc/rawtransaction.cpp @@ -67,9 +67,9 @@ static void TxToJSON(const CTransaction& tx, const uint256 hashBlock, UniValue& } } -static UniValue getrawtransaction(const JSONRPCRequest& request) +static RPCHelpMan getrawtransaction() { - RPCHelpMan{ + return RPCHelpMan{ "getrawtransaction", "\nReturn the raw transaction data.\n" @@ -155,8 +155,8 @@ static UniValue getrawtransaction(const JSONRPCRequest& request) + HelpExampleCli("getrawtransaction", "\"mytxid\" false \"myblockhash\"") + HelpExampleCli("getrawtransaction", "\"mytxid\" true \"myblockhash\"") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ const NodeContext& node = EnsureNodeContext(request.context); bool in_active_chain = true; @@ -217,11 +217,13 @@ static UniValue getrawtransaction(const JSONRPCRequest& request) if (blockindex) result.pushKV("in_active_chain", in_active_chain); TxToJSON(*tx, hash_block, result); return result; +}, + }; } -static UniValue gettxoutproof(const JSONRPCRequest& request) +static RPCHelpMan gettxoutproof() { - RPCHelpMan{"gettxoutproof", + return RPCHelpMan{"gettxoutproof", "\nReturns a hex-encoded proof that \"txid\" was included in a block.\n" "\nNOTE: By default this function only works sometimes. This is when there is an\n" "unspent output in the utxo for this transaction. To make it always work,\n" @@ -239,8 +241,8 @@ static UniValue gettxoutproof(const JSONRPCRequest& request) RPCResult::Type::STR, "data", "A string that is a serialized, hex-encoded data for the proof." }, RPCExamples{""}, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ std::set<uint256> setTxids; uint256 oneTxid; UniValue txids = request.params[0].get_array(); @@ -315,11 +317,13 @@ static UniValue gettxoutproof(const JSONRPCRequest& request) ssMB << mb; std::string strHex = HexStr(ssMB); return strHex; +}, + }; } -static UniValue verifytxoutproof(const JSONRPCRequest& request) +static RPCHelpMan verifytxoutproof() { - RPCHelpMan{"verifytxoutproof", + return RPCHelpMan{"verifytxoutproof", "\nVerifies that a proof points to a transaction in a block, returning the transaction it commits to\n" "and throwing an RPC error if the block is not in our best chain\n", { @@ -332,8 +336,8 @@ static UniValue verifytxoutproof(const JSONRPCRequest& request) } }, RPCExamples{""}, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ CDataStream ssMB(ParseHexV(request.params[0], "proof"), SER_NETWORK, PROTOCOL_VERSION | SERIALIZE_TRANSACTION_NO_WITNESS); CMerkleBlock merkleBlock; ssMB >> merkleBlock; @@ -360,11 +364,13 @@ static UniValue verifytxoutproof(const JSONRPCRequest& request) } return res; +}, + }; } -static UniValue createrawtransaction(const JSONRPCRequest& request) +static RPCHelpMan createrawtransaction() { - RPCHelpMan{"createrawtransaction", + return RPCHelpMan{"createrawtransaction", "\nCreate a transaction spending the given inputs and creating new outputs.\n" "Outputs can be addresses or data.\n" "Returns hex-encoded raw transaction.\n" @@ -412,8 +418,8 @@ static UniValue createrawtransaction(const JSONRPCRequest& request) + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"address\\\":0.01}]\"") + HelpExampleRpc("createrawtransaction", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\", \"[{\\\"data\\\":\\\"00010203\\\"}]\"") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ RPCTypeCheck(request.params, { UniValue::VARR, UniValueType(), // ARR or OBJ, checked later @@ -429,11 +435,13 @@ static UniValue createrawtransaction(const JSONRPCRequest& request) CMutableTransaction rawTx = ConstructTransaction(request.params[0], request.params[1], request.params[2], rbf); return EncodeHexTx(CTransaction(rawTx)); +}, + }; } -static UniValue decoderawtransaction(const JSONRPCRequest& request) +static RPCHelpMan decoderawtransaction() { - RPCHelpMan{"decoderawtransaction", + return RPCHelpMan{"decoderawtransaction", "\nReturn a JSON object representing the serialized, hex-encoded transaction.\n", { {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The transaction hex string"}, @@ -498,8 +506,8 @@ static UniValue decoderawtransaction(const JSONRPCRequest& request) HelpExampleCli("decoderawtransaction", "\"hexstring\"") + HelpExampleRpc("decoderawtransaction", "\"hexstring\"") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}); CMutableTransaction mtx; @@ -515,6 +523,8 @@ static UniValue decoderawtransaction(const JSONRPCRequest& request) TxToUniv(CTransaction(std::move(mtx)), uint256(), result, false); return result; +}, + }; } static std::string GetAllOutputTypes() @@ -527,9 +537,9 @@ static std::string GetAllOutputTypes() return Join(ret, ", "); } -static UniValue decodescript(const JSONRPCRequest& request) +static RPCHelpMan decodescript() { - RPCHelpMan{"decodescript", + return RPCHelpMan{"decodescript", "\nDecode a hex-encoded script.\n", { {"hexstring", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "the hex-encoded script"}, @@ -563,8 +573,8 @@ static UniValue decodescript(const JSONRPCRequest& request) HelpExampleCli("decodescript", "\"hexstring\"") + HelpExampleRpc("decodescript", "\"hexstring\"") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ RPCTypeCheck(request.params, {UniValue::VSTR}); UniValue r(UniValue::VOBJ); @@ -616,11 +626,13 @@ static UniValue decodescript(const JSONRPCRequest& request) } return r; +}, + }; } -static UniValue combinerawtransaction(const JSONRPCRequest& request) +static RPCHelpMan combinerawtransaction() { - RPCHelpMan{"combinerawtransaction", + return RPCHelpMan{"combinerawtransaction", "\nCombine multiple partially signed transactions into one transaction.\n" "The combined transaction may be another partially signed transaction or a \n" "fully signed transaction.", @@ -637,8 +649,8 @@ static UniValue combinerawtransaction(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("combinerawtransaction", R"('["myhex1", "myhex2", "myhex3"]')") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ UniValue txs = request.params[0].get_array(); std::vector<CMutableTransaction> txVariants(txs.size()); @@ -699,11 +711,13 @@ static UniValue combinerawtransaction(const JSONRPCRequest& request) } return EncodeHexTx(CTransaction(mergedTx)); +}, + }; } -static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) +static RPCHelpMan signrawtransactionwithkey() { - RPCHelpMan{"signrawtransactionwithkey", + return RPCHelpMan{"signrawtransactionwithkey", "\nSign inputs for raw transaction (serialized, hex-encoded).\n" "The second argument is an array of base58-encoded private\n" "keys that will be the only keys used to sign the transaction.\n" @@ -761,8 +775,8 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) HelpExampleCli("signrawtransactionwithkey", "\"myhex\" \"[\\\"key1\\\",\\\"key2\\\"]\"") + HelpExampleRpc("signrawtransactionwithkey", "\"myhex\", \"[\\\"key1\\\",\\\"key2\\\"]\"") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR, UniValue::VARR, UniValue::VSTR}, true); CMutableTransaction mtx; @@ -795,11 +809,13 @@ static UniValue signrawtransactionwithkey(const JSONRPCRequest& request) UniValue result(UniValue::VOBJ); SignTransaction(mtx, &keystore, coins, request.params[3], result); return result; +}, + }; } -static UniValue sendrawtransaction(const JSONRPCRequest& request) +static RPCHelpMan sendrawtransaction() { - RPCHelpMan{"sendrawtransaction", + return RPCHelpMan{"sendrawtransaction", "\nSubmit a raw transaction (serialized, hex-encoded) to local node and network.\n" "\nNote that the transaction will be sent unconditionally to all peers, so using this\n" "for manual rebroadcast may degrade privacy by leaking the transaction's origin, as\n" @@ -824,8 +840,8 @@ static UniValue sendrawtransaction(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("sendrawtransaction", "\"signedhex\"") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ RPCTypeCheck(request.params, { UniValue::VSTR, UniValueType(), // VNUM or VSTR, checked inside AmountFromValue() @@ -853,11 +869,13 @@ static UniValue sendrawtransaction(const JSONRPCRequest& request) } return tx->GetHash().GetHex(); +}, + }; } -static UniValue testmempoolaccept(const JSONRPCRequest& request) +static RPCHelpMan testmempoolaccept() { - RPCHelpMan{"testmempoolaccept", + return RPCHelpMan{"testmempoolaccept", "\nReturns result of mempool acceptance tests indicating if raw transaction (serialized, hex-encoded) would be accepted by mempool.\n" "\nThis checks if the transaction violates the consensus or policy rules.\n" "\nSee sendrawtransaction call.\n", @@ -897,8 +915,8 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request) "\nAs a JSON-RPC call\n" + HelpExampleRpc("testmempoolaccept", "[\"signedhex\"]") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ RPCTypeCheck(request.params, { UniValue::VARR, UniValueType(), // VNUM or VSTR, checked inside AmountFromValue() @@ -958,11 +976,13 @@ static UniValue testmempoolaccept(const JSONRPCRequest& request) result.push_back(std::move(result_0)); return result; +}, + }; } -UniValue decodepsbt(const JSONRPCRequest& request) +static RPCHelpMan decodepsbt() { - RPCHelpMan{"decodepsbt", + return RPCHelpMan{"decodepsbt", "\nReturn a JSON object representing the serialized, base64-encoded partially signed Bitcoin transaction.\n", { {"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "The PSBT base64 string"}, @@ -1074,8 +1094,8 @@ UniValue decodepsbt(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("decodepsbt", "\"psbt\"") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ RPCTypeCheck(request.params, {UniValue::VSTR}); // Unserialize the transactions @@ -1267,11 +1287,13 @@ UniValue decodepsbt(const JSONRPCRequest& request) } return result; +}, + }; } -UniValue combinepsbt(const JSONRPCRequest& request) +static RPCHelpMan combinepsbt() { - RPCHelpMan{"combinepsbt", + return RPCHelpMan{"combinepsbt", "\nCombine multiple partially signed Bitcoin transactions into one transaction.\n" "Implements the Combiner role.\n", { @@ -1287,8 +1309,8 @@ UniValue combinepsbt(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("combinepsbt", R"('["mybase64_1", "mybase64_2", "mybase64_3"]')") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ RPCTypeCheck(request.params, {UniValue::VARR}, true); // Unserialize the transactions @@ -1315,11 +1337,13 @@ UniValue combinepsbt(const JSONRPCRequest& request) CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << merged_psbt; return EncodeBase64(MakeUCharSpan(ssTx)); +}, + }; } -UniValue finalizepsbt(const JSONRPCRequest& request) +static RPCHelpMan finalizepsbt() { - RPCHelpMan{"finalizepsbt", + return RPCHelpMan{"finalizepsbt", "Finalize the inputs of a PSBT. If the transaction is fully signed, it will produce a\n" "network serialized transaction which can be broadcast with sendrawtransaction. Otherwise a PSBT will be\n" "created which has the final_scriptSig and final_scriptWitness fields filled for inputs that are complete.\n" @@ -1340,8 +1364,8 @@ UniValue finalizepsbt(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("finalizepsbt", "\"psbt\"") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL}, true); // Unserialize the transactions @@ -1372,11 +1396,13 @@ UniValue finalizepsbt(const JSONRPCRequest& request) result.pushKV("complete", complete); return result; +}, + }; } -UniValue createpsbt(const JSONRPCRequest& request) +static RPCHelpMan createpsbt() { - RPCHelpMan{"createpsbt", + return RPCHelpMan{"createpsbt", "\nCreates a transaction in the Partially Signed Transaction format.\n" "Implements the Creator role.\n", { @@ -1418,8 +1444,8 @@ UniValue createpsbt(const JSONRPCRequest& request) RPCExamples{ HelpExampleCli("createpsbt", "\"[{\\\"txid\\\":\\\"myid\\\",\\\"vout\\\":0}]\" \"[{\\\"data\\\":\\\"00010203\\\"}]\"") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ RPCTypeCheck(request.params, { UniValue::VARR, @@ -1450,11 +1476,13 @@ UniValue createpsbt(const JSONRPCRequest& request) ssTx << psbtx; return EncodeBase64(MakeUCharSpan(ssTx)); +}, + }; } -UniValue converttopsbt(const JSONRPCRequest& request) +static RPCHelpMan converttopsbt() { - RPCHelpMan{"converttopsbt", + return RPCHelpMan{"converttopsbt", "\nConverts a network serialized transaction to a PSBT. This should be used only with createrawtransaction and fundrawtransaction\n" "createpsbt and walletcreatefundedpsbt should be used for new applications.\n", { @@ -1478,8 +1506,8 @@ UniValue converttopsbt(const JSONRPCRequest& request) "\nConvert the transaction to a PSBT\n" + HelpExampleCli("converttopsbt", "\"rawtransaction\"") }, - }.Check(request); - + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VBOOL, UniValue::VBOOL}, true); // parse hex string from parameter @@ -1517,11 +1545,13 @@ UniValue converttopsbt(const JSONRPCRequest& request) ssTx << psbtx; return EncodeBase64(MakeUCharSpan(ssTx)); +}, + }; } -UniValue utxoupdatepsbt(const JSONRPCRequest& request) +static RPCHelpMan utxoupdatepsbt() { - RPCHelpMan{"utxoupdatepsbt", + return RPCHelpMan{"utxoupdatepsbt", "\nUpdates all segwit inputs and outputs in a PSBT with data from output descriptors, the UTXO set or the mempool.\n", { {"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"}, @@ -1538,8 +1568,9 @@ UniValue utxoupdatepsbt(const JSONRPCRequest& request) }, RPCExamples { HelpExampleCli("utxoupdatepsbt", "\"psbt\"") - }}.Check(request); - + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ RPCTypeCheck(request.params, {UniValue::VSTR, UniValue::VARR}, true); // Unserialize the transactions @@ -1605,11 +1636,13 @@ UniValue utxoupdatepsbt(const JSONRPCRequest& request) CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << psbtx; return EncodeBase64(MakeUCharSpan(ssTx)); +}, + }; } -UniValue joinpsbts(const JSONRPCRequest& request) +static RPCHelpMan joinpsbts() { - RPCHelpMan{"joinpsbts", + return RPCHelpMan{"joinpsbts", "\nJoins multiple distinct PSBTs with different inputs and outputs into one PSBT with inputs and outputs from all of the PSBTs\n" "No input in any of the PSBTs can be in more than one of the PSBTs.\n", { @@ -1623,8 +1656,9 @@ UniValue joinpsbts(const JSONRPCRequest& request) }, RPCExamples { HelpExampleCli("joinpsbts", "\"psbt\"") - }}.Check(request); - + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ RPCTypeCheck(request.params, {UniValue::VARR}, true); // Unserialize the transactions @@ -1698,11 +1732,13 @@ UniValue joinpsbts(const JSONRPCRequest& request) CDataStream ssTx(SER_NETWORK, PROTOCOL_VERSION); ssTx << shuffled_psbt; return EncodeBase64(MakeUCharSpan(ssTx)); +}, + }; } -UniValue analyzepsbt(const JSONRPCRequest& request) +static RPCHelpMan analyzepsbt() { - RPCHelpMan{"analyzepsbt", + return RPCHelpMan{"analyzepsbt", "\nAnalyzes and provides information about the current status of a PSBT and its inputs\n", { {"psbt", RPCArg::Type::STR, RPCArg::Optional::NO, "A base64 string of a PSBT"} @@ -1741,8 +1777,9 @@ UniValue analyzepsbt(const JSONRPCRequest& request) }, RPCExamples { HelpExampleCli("analyzepsbt", "\"psbt\"") - }}.Check(request); - + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ RPCTypeCheck(request.params, {UniValue::VSTR}); // Unserialize the transaction @@ -1806,6 +1843,8 @@ UniValue analyzepsbt(const JSONRPCRequest& request) } return result; +}, + }; } void RegisterRawTransactionRPCCommands(CRPCTable &t) diff --git a/src/signet.cpp b/src/signet.cpp new file mode 100644 index 0000000000..e68f031aa4 --- /dev/null +++ b/src/signet.cpp @@ -0,0 +1,149 @@ +// Copyright (c) 2019-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 <signet.h> + +#include <array> +#include <cstdint> +#include <vector> + +#include <consensus/merkle.h> +#include <consensus/params.h> +#include <consensus/validation.h> +#include <core_io.h> +#include <hash.h> +#include <primitives/block.h> +#include <primitives/transaction.h> +#include <span.h> +#include <script/interpreter.h> +#include <script/standard.h> +#include <streams.h> +#include <util/strencodings.h> +#include <util/system.h> +#include <uint256.h> + +static constexpr uint8_t SIGNET_HEADER[4] = {0xec, 0xc7, 0xda, 0xa2}; + +static constexpr unsigned int BLOCK_SCRIPT_VERIFY_FLAGS = SCRIPT_VERIFY_P2SH | SCRIPT_VERIFY_WITNESS | SCRIPT_VERIFY_DERSIG | SCRIPT_VERIFY_NULLDUMMY; + +static bool FetchAndClearCommitmentSection(const Span<const uint8_t> header, CScript& witness_commitment, std::vector<uint8_t>& result) +{ + CScript replacement; + bool found_header = false; + result.clear(); + + opcodetype opcode; + CScript::const_iterator pc = witness_commitment.begin(); + std::vector<uint8_t> pushdata; + while (witness_commitment.GetOp(pc, opcode, pushdata)) { + if (pushdata.size() > 0) { + if (!found_header && pushdata.size() > (size_t) header.size() && Span<const uint8_t>(pushdata.data(), header.size()) == header) { + // pushdata only counts if it has the header _and_ some data + result.insert(result.end(), pushdata.begin() + header.size(), pushdata.end()); + pushdata.erase(pushdata.begin() + header.size(), pushdata.end()); + found_header = true; + } + replacement << pushdata; + } else { + replacement << opcode; + } + } + + if (found_header) witness_commitment = replacement; + return found_header; +} + +static uint256 ComputeModifiedMerkleRoot(const CMutableTransaction& cb, const CBlock& block) +{ + std::vector<uint256> leaves; + leaves.resize(block.vtx.size()); + leaves[0] = cb.GetHash(); + for (size_t s = 1; s < block.vtx.size(); ++s) { + leaves[s] = block.vtx[s]->GetHash(); + } + return ComputeMerkleRoot(std::move(leaves)); +} + +Optional<SignetTxs> SignetTxs::Create(const CBlock& block, const CScript& challenge) +{ + CMutableTransaction tx_to_spend; + tx_to_spend.nVersion = 0; + tx_to_spend.nLockTime = 0; + tx_to_spend.vin.emplace_back(COutPoint(), CScript(OP_0), 0); + tx_to_spend.vout.emplace_back(0, challenge); + + CMutableTransaction tx_spending; + tx_spending.nVersion = 0; + tx_spending.nLockTime = 0; + tx_spending.vin.emplace_back(COutPoint(), CScript(), 0); + tx_spending.vout.emplace_back(0, CScript(OP_RETURN)); + + // can't fill any other fields before extracting signet + // responses from block coinbase tx + + // find and delete signet signature + if (block.vtx.empty()) return nullopt; // no coinbase tx in block; invalid + CMutableTransaction modified_cb(*block.vtx.at(0)); + + const int cidx = GetWitnessCommitmentIndex(block); + if (cidx == NO_WITNESS_COMMITMENT) { + return nullopt; // require a witness commitment + } + + CScript& witness_commitment = modified_cb.vout.at(cidx).scriptPubKey; + + std::vector<uint8_t> signet_solution; + if (!FetchAndClearCommitmentSection(SIGNET_HEADER, witness_commitment, signet_solution)) { + // no signet solution -- allow this to support OP_TRUE as trivial block challenge + } else { + try { + VectorReader v(SER_NETWORK, INIT_PROTO_VERSION, signet_solution, 0); + v >> tx_spending.vin[0].scriptSig; + v >> tx_spending.vin[0].scriptWitness.stack; + if (!v.empty()) return nullopt; // extraneous data encountered + } catch (const std::exception&) { + return nullopt; // parsing error + } + } + uint256 signet_merkle = ComputeModifiedMerkleRoot(modified_cb, block); + + std::vector<uint8_t> block_data; + CVectorWriter writer(SER_NETWORK, INIT_PROTO_VERSION, block_data, 0); + writer << block.nVersion; + writer << block.hashPrevBlock; + writer << signet_merkle; + writer << block.nTime; + tx_to_spend.vin[0].scriptSig << block_data; + tx_spending.vin[0].prevout = COutPoint(tx_to_spend.GetHash(), 0); + + return SignetTxs{tx_to_spend, tx_spending}; +} + +// Signet block solution checker +bool CheckSignetBlockSolution(const CBlock& block, const Consensus::Params& consensusParams) +{ + if (block.GetHash() == consensusParams.hashGenesisBlock) { + // genesis block solution is always valid + return true; + } + + const CScript challenge(consensusParams.signet_challenge.begin(), consensusParams.signet_challenge.end()); + const Optional<SignetTxs> signet_txs = SignetTxs::Create(block, challenge); + + if (!signet_txs) { + LogPrint(BCLog::VALIDATION, "CheckSignetBlockSolution: Errors in block (block solution parse failure)\n"); + return false; + } + + const CScript& scriptSig = signet_txs->m_to_sign.vin[0].scriptSig; + const CScriptWitness& witness = signet_txs->m_to_sign.vin[0].scriptWitness; + + TransactionSignatureChecker sigcheck(&signet_txs->m_to_sign, /*nIn=*/ 0, /*amount=*/ signet_txs->m_to_spend.vout[0].nValue); + + if (!VerifyScript(scriptSig, signet_txs->m_to_spend.vout[0].scriptPubKey, &witness, BLOCK_SCRIPT_VERIFY_FLAGS, sigcheck)) { + LogPrint(BCLog::VALIDATION, "CheckSignetBlockSolution: Errors in block (block solution invalid)\n"); + return false; + } + return true; +} diff --git a/src/signet.h b/src/signet.h new file mode 100644 index 0000000000..23563a83c4 --- /dev/null +++ b/src/signet.h @@ -0,0 +1,37 @@ +// Copyright (c) 2019-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. + +#ifndef BITCOIN_SIGNET_H +#define BITCOIN_SIGNET_H + +#include <consensus/params.h> +#include <primitives/block.h> +#include <primitives/transaction.h> + +#include <optional.h> + +/** + * Extract signature and check whether a block has a valid solution + */ +bool CheckSignetBlockSolution(const CBlock& block, const Consensus::Params& consensusParams); + +/** + * Generate the signet tx corresponding to the given block + * + * The signet tx commits to everything in the block except: + * 1. It hashes a modified merkle root with the signet signature removed. + * 2. It skips the nonce. + */ +class SignetTxs { + template<class T1, class T2> + SignetTxs(const T1& to_spend, const T2& to_sign) : m_to_spend{to_spend}, m_to_sign{to_sign} { } + +public: + static Optional<SignetTxs> Create(const CBlock& block, const CScript& challenge); + + const CTransaction m_to_spend; + const CTransaction m_to_sign; +}; + +#endif // BITCOIN_SIGNET_H diff --git a/src/test/denialofservice_tests.cpp b/src/test/denialofservice_tests.cpp index e4ee08db61..712567ac0d 100644 --- a/src/test/denialofservice_tests.cpp +++ b/src/test/denialofservice_tests.cpp @@ -85,7 +85,7 @@ BOOST_AUTO_TEST_CASE(outbound_slow_chain_eviction) // Mock an outbound peer CAddress addr1(ip(0xa0b0c001), NODE_NONE); CNode dummyNode1(id++, ServiceFlags(NODE_NETWORK | NODE_WITNESS), 0, INVALID_SOCKET, addr1, 0, 0, CAddress(), "", ConnectionType::OUTBOUND_FULL_RELAY); - dummyNode1.SetSendVersion(PROTOCOL_VERSION); + dummyNode1.SetCommonVersion(PROTOCOL_VERSION); peerLogic->InitializeNode(&dummyNode1); dummyNode1.nVersion = 1; @@ -138,7 +138,7 @@ static void AddRandomOutboundPeer(std::vector<CNode *> &vNodes, PeerManager &pee CAddress addr(ip(g_insecure_rand_ctx.randbits(32)), NODE_NONE); vNodes.emplace_back(new CNode(id++, ServiceFlags(NODE_NETWORK | NODE_WITNESS), 0, INVALID_SOCKET, addr, 0, 0, CAddress(), "", ConnectionType::OUTBOUND_FULL_RELAY)); CNode &node = *vNodes.back(); - node.SetSendVersion(PROTOCOL_VERSION); + node.SetCommonVersion(PROTOCOL_VERSION); peerLogic.InitializeNode(&node); node.nVersion = 1; @@ -229,7 +229,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) banman->ClearBanned(); CAddress addr1(ip(0xa0b0c001), NODE_NONE); CNode dummyNode1(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr1, 0, 0, CAddress(), "", ConnectionType::INBOUND); - dummyNode1.SetSendVersion(PROTOCOL_VERSION); + dummyNode1.SetCommonVersion(PROTOCOL_VERSION); peerLogic->InitializeNode(&dummyNode1); dummyNode1.nVersion = 1; dummyNode1.fSuccessfullyConnected = true; @@ -243,7 +243,7 @@ BOOST_AUTO_TEST_CASE(peer_discouragement) CAddress addr2(ip(0xa0b0c002), NODE_NONE); CNode dummyNode2(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr2, 1, 1, CAddress(), "", ConnectionType::INBOUND); - dummyNode2.SetSendVersion(PROTOCOL_VERSION); + dummyNode2.SetCommonVersion(PROTOCOL_VERSION); peerLogic->InitializeNode(&dummyNode2); dummyNode2.nVersion = 1; dummyNode2.fSuccessfullyConnected = true; @@ -280,7 +280,7 @@ BOOST_AUTO_TEST_CASE(DoS_bantime) CAddress addr(ip(0xa0b0c001), NODE_NONE); CNode dummyNode(id++, NODE_NETWORK, 0, INVALID_SOCKET, addr, 4, 4, CAddress(), "", ConnectionType::INBOUND); - dummyNode.SetSendVersion(PROTOCOL_VERSION); + dummyNode.SetCommonVersion(PROTOCOL_VERSION); peerLogic->InitializeNode(&dummyNode); dummyNode.nVersion = 1; dummyNode.fSuccessfullyConnected = true; diff --git a/src/test/fuzz/net.cpp b/src/test/fuzz/net.cpp index cd0c93b8d0..a85c353243 100644 --- a/src/test/fuzz/net.cpp +++ b/src/test/fuzz/net.cpp @@ -48,7 +48,7 @@ void test_one_input(const std::vector<uint8_t>& buffer) fuzzed_data_provider.ConsumeRandomLengthString(32), fuzzed_data_provider.PickValueInArray({ConnectionType::INBOUND, ConnectionType::OUTBOUND_FULL_RELAY, ConnectionType::MANUAL, ConnectionType::FEELER, ConnectionType::BLOCK_RELAY, ConnectionType::ADDR_FETCH})}; while (fuzzed_data_provider.ConsumeBool()) { - switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 12)) { + switch (fuzzed_data_provider.ConsumeIntegralInRange<int>(0, 11)) { case 0: { node.CloseSocketDisconnect(); break; @@ -58,7 +58,7 @@ void test_one_input(const std::vector<uint8_t>& buffer) break; } case 2: { - node.SetSendVersion(fuzzed_data_provider.ConsumeIntegral<int>()); + node.SetCommonVersion(fuzzed_data_provider.ConsumeIntegral<int>()); break; } case 3: { @@ -71,21 +71,17 @@ void test_one_input(const std::vector<uint8_t>& buffer) break; } case 4: { - node.SetRecvVersion(fuzzed_data_provider.ConsumeIntegral<int>()); - break; - } - case 5: { const CNode* add_ref_node = node.AddRef(); assert(add_ref_node == &node); break; } - case 6: { + case 5: { if (node.GetRefCount() > 0) { node.Release(); } break; } - case 7: { + case 6: { if (node.m_addr_known == nullptr) { break; } @@ -96,7 +92,7 @@ void test_one_input(const std::vector<uint8_t>& buffer) node.AddAddressKnown(*addr_opt); break; } - case 8: { + case 7: { if (node.m_addr_known == nullptr) { break; } @@ -108,7 +104,7 @@ void test_one_input(const std::vector<uint8_t>& buffer) node.PushAddress(*addr_opt, fast_random_context); break; } - case 9: { + case 8: { const std::optional<CInv> inv_opt = ConsumeDeserializable<CInv>(fuzzed_data_provider); if (!inv_opt) { break; @@ -116,11 +112,11 @@ void test_one_input(const std::vector<uint8_t>& buffer) node.AddKnownTx(inv_opt->hash); break; } - case 10: { + case 9: { node.PushTxInventory(ConsumeUInt256(fuzzed_data_provider)); break; } - case 11: { + case 10: { const std::optional<CService> service_opt = ConsumeDeserializable<CService>(fuzzed_data_provider); if (!service_opt) { break; @@ -128,7 +124,7 @@ void test_one_input(const std::vector<uint8_t>& buffer) node.SetAddrLocal(*service_opt); break; } - case 12: { + case 11: { const std::vector<uint8_t> b = ConsumeRandomLengthByteVector(fuzzed_data_provider); bool complete; node.ReceiveMsgBytes((const char*)b.data(), b.size(), complete); @@ -143,10 +139,9 @@ void test_one_input(const std::vector<uint8_t>& buffer) (void)node.GetLocalNonce(); (void)node.GetLocalServices(); (void)node.GetMyStartingHeight(); - (void)node.GetRecvVersion(); const int ref_count = node.GetRefCount(); assert(ref_count >= 0); - (void)node.GetSendVersion(); + (void)node.GetCommonVersion(); (void)node.RelayAddrsWithConn(); const NetPermissionFlags net_permission_flags = fuzzed_data_provider.ConsumeBool() ? diff --git a/src/test/fuzz/process_message.cpp b/src/test/fuzz/process_message.cpp index 3d6947ca92..3ef03137ec 100644 --- a/src/test/fuzz/process_message.cpp +++ b/src/test/fuzz/process_message.cpp @@ -71,7 +71,7 @@ void test_one_input(const std::vector<uint8_t>& buffer) CNode& p2p_node = *MakeUnique<CNode>(0, ServiceFlags(NODE_NETWORK | NODE_WITNESS | NODE_BLOOM), 0, INVALID_SOCKET, CAddress{CService{in_addr{0x0100007f}, 7777}, NODE_NETWORK}, 0, 0, CAddress{}, std::string{}, ConnectionType::OUTBOUND_FULL_RELAY).release(); p2p_node.fSuccessfullyConnected = true; p2p_node.nVersion = PROTOCOL_VERSION; - p2p_node.SetSendVersion(PROTOCOL_VERSION); + p2p_node.SetCommonVersion(PROTOCOL_VERSION); connman.AddTestNode(p2p_node); g_setup->m_node.peerman->InitializeNode(&p2p_node); try { diff --git a/src/test/fuzz/process_messages.cpp b/src/test/fuzz/process_messages.cpp index c9433d325a..f722eeac3a 100644 --- a/src/test/fuzz/process_messages.cpp +++ b/src/test/fuzz/process_messages.cpp @@ -51,7 +51,7 @@ void test_one_input(const std::vector<uint8_t>& buffer) p2p_node.fSuccessfullyConnected = true; p2p_node.fPauseSend = false; p2p_node.nVersion = PROTOCOL_VERSION; - p2p_node.SetSendVersion(PROTOCOL_VERSION); + p2p_node.SetCommonVersion(PROTOCOL_VERSION); g_setup->m_node.peerman->InitializeNode(&p2p_node); connman.AddTestNode(p2p_node); diff --git a/src/test/fuzz/signet.cpp b/src/test/fuzz/signet.cpp new file mode 100644 index 0000000000..786f1a83fe --- /dev/null +++ b/src/test/fuzz/signet.cpp @@ -0,0 +1,32 @@ +// 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 <chainparams.h> +#include <consensus/validation.h> +#include <primitives/block.h> +#include <signet.h> +#include <streams.h> +#include <test/fuzz/FuzzedDataProvider.h> +#include <test/fuzz/fuzz.h> +#include <test/fuzz/util.h> + +#include <cstdint> +#include <optional> +#include <vector> + +void initialize() +{ + InitializeFuzzingContext(CBaseChainParams::SIGNET); +} + +void test_one_input(const std::vector<uint8_t>& buffer) +{ + FuzzedDataProvider fuzzed_data_provider{buffer.data(), buffer.size()}; + const std::optional<CBlock> block = ConsumeDeserializable<CBlock>(fuzzed_data_provider); + if (!block) { + return; + } + (void)CheckSignetBlockSolution(*block, Params().GetConsensus()); + (void)SignetTxs::Create(*block, ConsumeScript(fuzzed_data_provider)); +} diff --git a/src/test/key_io_tests.cpp b/src/test/key_io_tests.cpp index d465ee6759..611e9f2623 100644 --- a/src/test/key_io_tests.cpp +++ b/src/test/key_io_tests.cpp @@ -136,7 +136,7 @@ BOOST_AUTO_TEST_CASE(key_io_invalid) std::string exp_base58string = test[0].get_str(); // must be invalid as public and as private key - for (const auto& chain : { CBaseChainParams::MAIN, CBaseChainParams::TESTNET, CBaseChainParams::REGTEST }) { + for (const auto& chain : { CBaseChainParams::MAIN, CBaseChainParams::TESTNET, CBaseChainParams::SIGNET, CBaseChainParams::REGTEST }) { SelectParams(chain); destination = DecodeDestination(exp_base58string); BOOST_CHECK_MESSAGE(!IsValidDestination(destination), "IsValid pubkey in mainnet:" + strTest); diff --git a/src/test/pow_tests.cpp b/src/test/pow_tests.cpp index 0f9872f434..ca49b89ad8 100644 --- a/src/test/pow_tests.cpp +++ b/src/test/pow_tests.cpp @@ -135,4 +135,51 @@ BOOST_AUTO_TEST_CASE(GetBlockProofEquivalentTime_test) } } +void sanity_check_chainparams(std::string chainName) +{ + const auto chainParams = CreateChainParams(chainName); + const auto consensus = chainParams->GetConsensus(); + + // hash genesis is correct + BOOST_CHECK_EQUAL(consensus.hashGenesisBlock, chainParams->GenesisBlock().GetHash()); + + // target timespan is an even multiple of spacing + BOOST_CHECK_EQUAL(consensus.nPowTargetTimespan % consensus.nPowTargetSpacing, 0); + + // genesis nBits is positive, doesn't overflow and is lower than powLimit + arith_uint256 pow_compact; + bool neg, over; + pow_compact.SetCompact(chainParams->GenesisBlock().nBits, &neg, &over); + BOOST_CHECK(!neg && pow_compact != 0); + BOOST_CHECK(!over); + BOOST_CHECK(UintToArith256(consensus.powLimit) >= pow_compact); + + // check max target * 4*nPowTargetTimespan doesn't overflow -- see pow.cpp:CalculateNextWorkRequired() + if (!consensus.fPowNoRetargeting) { + arith_uint256 targ_max("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF"); + targ_max /= consensus.nPowTargetTimespan*4; + BOOST_CHECK(UintToArith256(consensus.powLimit) < targ_max); + } +} + +BOOST_AUTO_TEST_CASE(ChainParams_MAIN_sanity) +{ + sanity_check_chainparams(CBaseChainParams::MAIN); +} + +BOOST_AUTO_TEST_CASE(ChainParams_REGTEST_sanity) +{ + sanity_check_chainparams(CBaseChainParams::REGTEST); +} + +BOOST_AUTO_TEST_CASE(ChainParams_TESTNET_sanity) +{ + sanity_check_chainparams(CBaseChainParams::TESTNET); +} + +BOOST_AUTO_TEST_CASE(ChainParams_SIGNET_sanity) +{ + sanity_check_chainparams(CBaseChainParams::SIGNET); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/test/util_tests.cpp b/src/test/util_tests.cpp index bf7c6c3e3e..241c56934e 100644 --- a/src/test/util_tests.cpp +++ b/src/test/util_tests.cpp @@ -848,8 +848,8 @@ struct ArgsMergeTestingSetup : public BasicTestingSetup { ForEachNoDup(conf_actions, SET, SECTION_NEGATE, [&] { for (bool soft_set : {false, true}) { for (bool force_set : {false, true}) { - for (const std::string& section : {CBaseChainParams::MAIN, CBaseChainParams::TESTNET}) { - for (const std::string& network : {CBaseChainParams::MAIN, CBaseChainParams::TESTNET}) { + for (const std::string& section : {CBaseChainParams::MAIN, CBaseChainParams::TESTNET, CBaseChainParams::SIGNET}) { + for (const std::string& network : {CBaseChainParams::MAIN, CBaseChainParams::TESTNET, CBaseChainParams::SIGNET}) { for (bool net_specific : {false, true}) { fn(arg_actions, conf_actions, soft_set, force_set, section, network, net_specific); } @@ -1003,7 +1003,7 @@ BOOST_FIXTURE_TEST_CASE(util_ArgsMerge, ArgsMergeTestingSetup) // Results file is formatted like: // // <input> || <IsArgSet/IsArgNegated/GetArg output> | <GetArgs output> | <GetUnsuitable output> - BOOST_CHECK_EQUAL(out_sha_hex, "8fd4877bb8bf337badca950ede6c917441901962f160e52514e06a60dea46cde"); + BOOST_CHECK_EQUAL(out_sha_hex, "d1e436c1cd510d0ec44d5205d4b4e3bee6387d316e0075c58206cb16603f3d82"); } // Similar test as above, but for ArgsManager::GetChainName function. @@ -1106,7 +1106,7 @@ BOOST_FIXTURE_TEST_CASE(util_ChainMerge, ChainMergeTestingSetup) // Results file is formatted like: // // <input> || <output> - BOOST_CHECK_EQUAL(out_sha_hex, "f0b3a3c29869edc765d579c928f7f1690a71fbb673b49ccf39cbc4de18156a0d"); + BOOST_CHECK_EQUAL(out_sha_hex, "f263493e300023b6509963887444c41386f44b63bc30047eb8402e8c1144854c"); } BOOST_AUTO_TEST_CASE(util_ReadWriteSettings) diff --git a/src/txmempool.cpp b/src/txmempool.cpp index 6a525c97db..0c2b731967 100644 --- a/src/txmempool.cpp +++ b/src/txmempool.cpp @@ -409,12 +409,16 @@ void CTxMemPool::addUnchecked(const CTxMemPoolEntry &entry, setEntries &setAnces void CTxMemPool::removeUnchecked(txiter it, MemPoolRemovalReason reason) { + // We increment mempool sequence value no matter removal reason + // even if not directly reported below. + uint64_t mempool_sequence = GetAndIncrementSequence(); + if (reason != MemPoolRemovalReason::BLOCK) { // Notify clients that a transaction has been removed from the mempool // for any reason except being included in a block. Clients interested // in transactions included in blocks can subscribe to the BlockConnected // notification. - GetMainSignals().TransactionRemovedFromMempool(it->GetSharedTx(), reason); + GetMainSignals().TransactionRemovedFromMempool(it->GetSharedTx(), reason, mempool_sequence); } const uint256 hash = it->GetTx().GetHash(); diff --git a/src/txmempool.h b/src/txmempool.h index 664fb5986a..f513f14af6 100644 --- a/src/txmempool.h +++ b/src/txmempool.h @@ -501,6 +501,11 @@ private: mutable uint64_t m_epoch; mutable bool m_has_epoch_guard; + // In-memory counter for external mempool tracking purposes. + // This number is incremented once every time a transaction + // is added or removed from the mempool for any reason. + mutable uint64_t m_sequence_number{1}; + void trackPackageRemoved(const CFeeRate& rate) EXCLUSIVE_LOCKS_REQUIRED(cs); bool m_is_loaded GUARDED_BY(cs){false}; @@ -776,6 +781,15 @@ public: return m_unbroadcast_txids.count(txid) != 0; } + /** Guards this internal counter for external reporting */ + uint64_t GetAndIncrementSequence() const EXCLUSIVE_LOCKS_REQUIRED(cs) { + return m_sequence_number++; + } + + uint64_t GetSequence() const EXCLUSIVE_LOCKS_REQUIRED(cs) { + return m_sequence_number; + } + private: /** UpdateForDescendants is used by UpdateTransactionsFromBlock to update * the descendants for a single transaction that has been added to the diff --git a/src/util/system.cpp b/src/util/system.cpp index 999937d906..41715aac1a 100644 --- a/src/util/system.cpp +++ b/src/util/system.cpp @@ -263,6 +263,7 @@ const std::list<SectionInfo> ArgsManager::GetUnrecognizedSections() const // Section names to be recognized in the config file. static const std::set<std::string> available_sections{ CBaseChainParams::REGTEST, + CBaseChainParams::SIGNET, CBaseChainParams::TESTNET, CBaseChainParams::MAIN }; @@ -916,16 +917,21 @@ std::string ArgsManager::GetChainName() const }; const bool fRegTest = get_net("-regtest"); + const bool fSigNet = get_net("-signet"); const bool fTestNet = get_net("-testnet"); const bool is_chain_arg_set = IsArgSet("-chain"); - if ((int)is_chain_arg_set + (int)fRegTest + (int)fTestNet > 1) { - throw std::runtime_error("Invalid combination of -regtest, -testnet and -chain. Can use at most one."); + if ((int)is_chain_arg_set + (int)fRegTest + (int)fSigNet + (int)fTestNet > 1) { + throw std::runtime_error("Invalid combination of -regtest, -signet, -testnet and -chain. Can use at most one."); } if (fRegTest) return CBaseChainParams::REGTEST; + if (fSigNet) { + return CBaseChainParams::SIGNET; + } if (fTestNet) return CBaseChainParams::TESTNET; + return GetArg("-chain", CBaseChainParams::MAIN); } diff --git a/src/validation.cpp b/src/validation.cpp index 0823c6b124..7020b59cb8 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -33,6 +33,7 @@ #include <script/script.h> #include <script/sigcache.h> #include <shutdown.h> +#include <signet.h> #include <timedata.h> #include <tinyformat.h> #include <txdb.h> @@ -1057,7 +1058,7 @@ bool MemPoolAccept::AcceptSingleTransaction(const CTransactionRef& ptx, ATMPArgs if (!Finalize(args, workspace)) return false; - GetMainSignals().TransactionAddedToMempool(ptx); + GetMainSignals().TransactionAddedToMempool(ptx, m_pool.GetAndIncrementSequence()); return true; } @@ -1169,6 +1170,11 @@ bool ReadBlockFromDisk(CBlock& block, const FlatFilePos& pos, const Consensus::P if (!CheckProofOfWork(block.GetHash(), block.nBits, consensusParams)) return error("ReadBlockFromDisk: Errors in block header at %s", pos.ToString()); + // Signet only: check block solution + if (consensusParams.signet_blocks && !CheckSignetBlockSolution(block, consensusParams)) { + return error("ReadBlockFromDisk: Errors in block solution at %s", pos.ToString()); + } + return true; } @@ -3346,6 +3352,11 @@ bool CheckBlock(const CBlock& block, BlockValidationState& state, const Consensu if (!CheckBlockHeader(block, state, consensusParams, fCheckPOW)) return false; + // Signet only: check block solution + if (consensusParams.signet_blocks && fCheckPOW && !CheckSignetBlockSolution(block, consensusParams)) { + return state.Invalid(BlockValidationResult::BLOCK_CONSENSUS, "bad-signet-blksig", "signet block signature validation failure"); + } + // Check the merkle root. if (fCheckMerkleRoot) { bool mutated; @@ -3409,31 +3420,11 @@ bool IsWitnessEnabled(const CBlockIndex* pindexPrev, const Consensus::Params& pa return (height >= params.SegwitHeight); } -int GetWitnessCommitmentIndex(const CBlock& block) -{ - int commitpos = -1; - if (!block.vtx.empty()) { - for (size_t o = 0; o < block.vtx[0]->vout.size(); o++) { - const CTxOut& vout = block.vtx[0]->vout[o]; - if (vout.scriptPubKey.size() >= MINIMUM_WITNESS_COMMITMENT && - vout.scriptPubKey[0] == OP_RETURN && - vout.scriptPubKey[1] == 0x24 && - vout.scriptPubKey[2] == 0xaa && - vout.scriptPubKey[3] == 0x21 && - vout.scriptPubKey[4] == 0xa9 && - vout.scriptPubKey[5] == 0xed) { - commitpos = o; - } - } - } - return commitpos; -} - void UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPrev, const Consensus::Params& consensusParams) { int commitpos = GetWitnessCommitmentIndex(block); static const std::vector<unsigned char> nonce(32, 0x00); - if (commitpos != -1 && IsWitnessEnabled(pindexPrev, consensusParams) && !block.vtx[0]->HasWitness()) { + if (commitpos != NO_WITNESS_COMMITMENT && IsWitnessEnabled(pindexPrev, consensusParams) && !block.vtx[0]->HasWitness()) { CMutableTransaction tx(*block.vtx[0]); tx.vin[0].scriptWitness.stack.resize(1); tx.vin[0].scriptWitness.stack[0] = nonce; @@ -3447,7 +3438,7 @@ std::vector<unsigned char> GenerateCoinbaseCommitment(CBlock& block, const CBloc int commitpos = GetWitnessCommitmentIndex(block); std::vector<unsigned char> ret(32, 0x00); if (consensusParams.SegwitHeight != std::numeric_limits<int>::max()) { - if (commitpos == -1) { + if (commitpos == NO_WITNESS_COMMITMENT) { uint256 witnessroot = BlockWitnessMerkleRoot(block, nullptr); CHash256().Write(witnessroot).Write(ret).Finalize(witnessroot); CTxOut out; @@ -3585,7 +3576,7 @@ static bool ContextualCheckBlock(const CBlock& block, BlockValidationState& stat bool fHaveWitness = false; if (nHeight >= consensusParams.SegwitHeight) { int commitpos = GetWitnessCommitmentIndex(block); - if (commitpos != -1) { + if (commitpos != NO_WITNESS_COMMITMENT) { bool malleated = false; uint256 hashWitness = BlockWitnessMerkleRoot(block, &malleated); // The malleation check is ignored; as the transaction tree itself diff --git a/src/validation.h b/src/validation.h index 0bc80e1cee..0da62093a3 100644 --- a/src/validation.h +++ b/src/validation.h @@ -93,8 +93,6 @@ static const unsigned int DEFAULT_CHECKLEVEL = 3; // one 128MB block file + added 15% undo data = 147MB greater for a total of 545MB // Setting the target to >= 550 MiB will make it likely we can respect the target. static const uint64_t MIN_DISK_SPACE_FOR_BLOCK_FILES = 550 * 1024 * 1024; -/** Minimum size of a witness commitment structure. Defined in BIP 141. **/ -static constexpr size_t MINIMUM_WITNESS_COMMITMENT{38}; struct BlockHasher { @@ -306,9 +304,6 @@ bool TestBlockValidity(BlockValidationState& state, const CChainParams& chainpar * Note that transaction witness validation rules are always enforced when P2SH is enforced. */ bool IsWitnessEnabled(const CBlockIndex* pindexPrev, const Consensus::Params& params); -/** Compute at which vout of the block's coinbase transaction the witness commitment occurs, or -1 if not found */ -int GetWitnessCommitmentIndex(const CBlock& block); - /** Update uncommitted block structures (currently: only the witness reserved value). This is safe for submitted blocks. */ void UpdateUncommittedBlockStructures(CBlock& block, const CBlockIndex* pindexPrev, const Consensus::Params& consensusParams); diff --git a/src/validationinterface.cpp b/src/validationinterface.cpp index 3dfbcc581c..1e07ff23ae 100644 --- a/src/validationinterface.cpp +++ b/src/validationinterface.cpp @@ -199,18 +199,18 @@ void CMainSignals::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockInd fInitialDownload); } -void CMainSignals::TransactionAddedToMempool(const CTransactionRef& tx) { - auto event = [tx, this] { - m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.TransactionAddedToMempool(tx); }); +void CMainSignals::TransactionAddedToMempool(const CTransactionRef& tx, uint64_t mempool_sequence) { + auto event = [tx, mempool_sequence, this] { + m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.TransactionAddedToMempool(tx, mempool_sequence); }); }; ENQUEUE_AND_LOG_EVENT(event, "%s: txid=%s wtxid=%s", __func__, tx->GetHash().ToString(), tx->GetWitnessHash().ToString()); } -void CMainSignals::TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason) { - auto event = [tx, reason, this] { - m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.TransactionRemovedFromMempool(tx, reason); }); +void CMainSignals::TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) { + auto event = [tx, reason, mempool_sequence, this] { + m_internals->Iterate([&](CValidationInterface& callbacks) { callbacks.TransactionRemovedFromMempool(tx, reason, mempool_sequence); }); }; ENQUEUE_AND_LOG_EVENT(event, "%s: txid=%s wtxid=%s", __func__, tx->GetHash().ToString(), diff --git a/src/validationinterface.h b/src/validationinterface.h index e96f2883fc..7c3ce00fbc 100644 --- a/src/validationinterface.h +++ b/src/validationinterface.h @@ -97,7 +97,8 @@ protected: * * Called on a background thread. */ - virtual void TransactionAddedToMempool(const CTransactionRef& tx) {} + virtual void TransactionAddedToMempool(const CTransactionRef& tx, uint64_t mempool_sequence) {} + /** * Notifies listeners of a transaction leaving mempool. * @@ -130,7 +131,7 @@ protected: * * Called on a background thread. */ - virtual void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason) {} + virtual void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) {} /** * Notifies listeners of a block being connected. * Provides a vector of transactions evicted from the mempool as a result. @@ -197,8 +198,8 @@ public: void UpdatedBlockTip(const CBlockIndex *, const CBlockIndex *, bool fInitialDownload); - void TransactionAddedToMempool(const CTransactionRef&); - void TransactionRemovedFromMempool(const CTransactionRef&, MemPoolRemovalReason); + void TransactionAddedToMempool(const CTransactionRef&, uint64_t mempool_sequence); + void TransactionRemovedFromMempool(const CTransactionRef&, MemPoolRemovalReason, uint64_t mempool_sequence); void BlockConnected(const std::shared_ptr<const CBlock> &, const CBlockIndex *pindex); void BlockDisconnected(const std::shared_ptr<const CBlock> &, const CBlockIndex* pindex); void ChainStateFlushed(const CBlockLocator &); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index 73e11a5b52..66857dbb39 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1177,7 +1177,7 @@ void CWallet::SyncTransaction(const CTransactionRef& ptx, CWalletTx::Confirmatio MarkInputsDirty(ptx); } -void CWallet::transactionAddedToMempool(const CTransactionRef& tx) { +void CWallet::transactionAddedToMempool(const CTransactionRef& tx, uint64_t mempool_sequence) { LOCK(cs_wallet); SyncTransaction(tx, {CWalletTx::Status::UNCONFIRMED, /* block height */ 0, /* block hash */ {}, /* index */ 0}); @@ -1187,7 +1187,7 @@ void CWallet::transactionAddedToMempool(const CTransactionRef& tx) { } } -void CWallet::transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason) { +void CWallet::transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) { LOCK(cs_wallet); auto it = mapWallet.find(tx->GetHash()); if (it != mapWallet.end()) { @@ -1234,7 +1234,7 @@ void CWallet::blockConnected(const CBlock& block, int height) m_last_block_processed = block_hash; for (size_t index = 0; index < block.vtx.size(); index++) { SyncTransaction(block.vtx[index], {CWalletTx::Status::CONFIRMED, height, block_hash, (int)index}); - transactionRemovedFromMempool(block.vtx[index], MemPoolRemovalReason::BLOCK); + transactionRemovedFromMempool(block.vtx[index], MemPoolRemovalReason::BLOCK, 0 /* mempool_sequence */); } } diff --git a/src/wallet/wallet.h b/src/wallet/wallet.h index c54480612a..f15712dd0e 100644 --- a/src/wallet/wallet.h +++ b/src/wallet/wallet.h @@ -900,7 +900,7 @@ public: CWalletTx* AddToWallet(CTransactionRef tx, const CWalletTx::Confirmation& confirm, const UpdateWalletTxFn& update_wtx=nullptr, bool fFlushOnClose=true); bool LoadToWallet(const uint256& hash, const UpdateWalletTxFn& fill_wtx) EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); - void transactionAddedToMempool(const CTransactionRef& tx) override; + void transactionAddedToMempool(const CTransactionRef& tx, uint64_t mempool_sequence) override; void blockConnected(const CBlock& block, int height) override; void blockDisconnected(const CBlock& block, int height) override; void updatedBlockTip() override; @@ -922,7 +922,7 @@ public: uint256 last_failed_block; }; ScanResult ScanForWalletTransactions(const uint256& start_block, int start_height, Optional<int> max_height, const WalletRescanReserver& reserver, bool fUpdate); - void transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason) override; + void transactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) override; void ReacceptWalletTransactions() EXCLUSIVE_LOCKS_REQUIRED(cs_wallet); void ResendWalletTransactions(); struct Balance { diff --git a/src/zmq/zmqabstractnotifier.cpp b/src/zmq/zmqabstractnotifier.cpp index aae760adde..3938f6fd2c 100644 --- a/src/zmq/zmqabstractnotifier.cpp +++ b/src/zmq/zmqabstractnotifier.cpp @@ -4,6 +4,8 @@ #include <zmq/zmqabstractnotifier.h> +#include <cassert> + const int CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM; CZMQAbstractNotifier::~CZMQAbstractNotifier() @@ -20,3 +22,23 @@ bool CZMQAbstractNotifier::NotifyTransaction(const CTransaction &/*transaction*/ { return true; } + +bool CZMQAbstractNotifier::NotifyBlockConnect(const CBlockIndex * /*CBlockIndex*/) +{ + return true; +} + +bool CZMQAbstractNotifier::NotifyBlockDisconnect(const CBlockIndex * /*CBlockIndex*/) +{ + return true; +} + +bool CZMQAbstractNotifier::NotifyTransactionAcceptance(const CTransaction &/*transaction*/, uint64_t mempool_sequence) +{ + return true; +} + +bool CZMQAbstractNotifier::NotifyTransactionRemoval(const CTransaction &/*transaction*/, uint64_t mempool_sequence) +{ + return true; +} diff --git a/src/zmq/zmqabstractnotifier.h b/src/zmq/zmqabstractnotifier.h index 887dde7b27..dddba8d6b6 100644 --- a/src/zmq/zmqabstractnotifier.h +++ b/src/zmq/zmqabstractnotifier.h @@ -5,12 +5,16 @@ #ifndef BITCOIN_ZMQ_ZMQABSTRACTNOTIFIER_H #define BITCOIN_ZMQ_ZMQABSTRACTNOTIFIER_H -#include <zmq/zmqconfig.h> +#include <util/memory.h> + +#include <memory> +#include <string> class CBlockIndex; +class CTransaction; class CZMQAbstractNotifier; -typedef CZMQAbstractNotifier* (*CZMQNotifierFactory)(); +using CZMQNotifierFactory = std::unique_ptr<CZMQAbstractNotifier> (*)(); class CZMQAbstractNotifier { @@ -21,9 +25,9 @@ public: virtual ~CZMQAbstractNotifier(); template <typename T> - static CZMQAbstractNotifier* Create() + static std::unique_ptr<CZMQAbstractNotifier> Create() { - return new T(); + return MakeUnique<T>(); } std::string GetType() const { return type; } @@ -40,7 +44,17 @@ public: virtual bool Initialize(void *pcontext) = 0; virtual void Shutdown() = 0; + // Notifies of ConnectTip result, i.e., new active tip only virtual bool NotifyBlock(const CBlockIndex *pindex); + // Notifies of every block connection + virtual bool NotifyBlockConnect(const CBlockIndex *pindex); + // Notifies of every block disconnection + virtual bool NotifyBlockDisconnect(const CBlockIndex *pindex); + // Notifies of every mempool acceptance + virtual bool NotifyTransactionAcceptance(const CTransaction &transaction, uint64_t mempool_sequence); + // Notifies of every mempool removal, except inclusion in blocks + virtual bool NotifyTransactionRemoval(const CTransaction &transaction, uint64_t mempool_sequence); + // Notifies of transactions added to mempool or appearing in blocks virtual bool NotifyTransaction(const CTransaction &transaction); protected: diff --git a/src/zmq/zmqconfig.h b/src/zmq/zmqconfig.h deleted file mode 100644 index 5f0036206d..0000000000 --- a/src/zmq/zmqconfig.h +++ /dev/null @@ -1,22 +0,0 @@ -// Copyright (c) 2014-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. - -#ifndef BITCOIN_ZMQ_ZMQCONFIG_H -#define BITCOIN_ZMQ_ZMQCONFIG_H - -#if defined(HAVE_CONFIG_H) -#include <config/bitcoin-config.h> -#endif - -#include <stdarg.h> - -#if ENABLE_ZMQ -#include <zmq.h> -#endif - -#include <primitives/transaction.h> - -void zmqError(const char *str); - -#endif // BITCOIN_ZMQ_ZMQCONFIG_H diff --git a/src/zmq/zmqnotificationinterface.cpp b/src/zmq/zmqnotificationinterface.cpp index d55b106e04..a7e9a34269 100644 --- a/src/zmq/zmqnotificationinterface.cpp +++ b/src/zmq/zmqnotificationinterface.cpp @@ -4,15 +4,13 @@ #include <zmq/zmqnotificationinterface.h> #include <zmq/zmqpublishnotifier.h> +#include <zmq/zmqutil.h> + +#include <zmq.h> #include <validation.h> #include <util/system.h> -void zmqError(const char *str) -{ - LogPrint(BCLog::ZMQ, "zmq: Error: %s, errno=%s\n", str, zmq_strerror(errno)); -} - CZMQNotificationInterface::CZMQNotificationInterface() : pcontext(nullptr) { } @@ -20,61 +18,53 @@ CZMQNotificationInterface::CZMQNotificationInterface() : pcontext(nullptr) CZMQNotificationInterface::~CZMQNotificationInterface() { Shutdown(); - - for (std::list<CZMQAbstractNotifier*>::iterator i=notifiers.begin(); i!=notifiers.end(); ++i) - { - delete *i; - } } std::list<const CZMQAbstractNotifier*> CZMQNotificationInterface::GetActiveNotifiers() const { std::list<const CZMQAbstractNotifier*> result; - for (const auto* n : notifiers) { - result.push_back(n); + for (const auto& n : notifiers) { + result.push_back(n.get()); } return result; } CZMQNotificationInterface* CZMQNotificationInterface::Create() { - CZMQNotificationInterface* notificationInterface = nullptr; std::map<std::string, CZMQNotifierFactory> factories; - std::list<CZMQAbstractNotifier*> notifiers; - factories["pubhashblock"] = CZMQAbstractNotifier::Create<CZMQPublishHashBlockNotifier>; factories["pubhashtx"] = CZMQAbstractNotifier::Create<CZMQPublishHashTransactionNotifier>; factories["pubrawblock"] = CZMQAbstractNotifier::Create<CZMQPublishRawBlockNotifier>; factories["pubrawtx"] = CZMQAbstractNotifier::Create<CZMQPublishRawTransactionNotifier>; + factories["pubsequence"] = CZMQAbstractNotifier::Create<CZMQPublishSequenceNotifier>; + std::list<std::unique_ptr<CZMQAbstractNotifier>> notifiers; for (const auto& entry : factories) { std::string arg("-zmq" + entry.first); if (gArgs.IsArgSet(arg)) { - CZMQNotifierFactory factory = entry.second; - std::string address = gArgs.GetArg(arg, ""); - CZMQAbstractNotifier *notifier = factory(); + const auto& factory = entry.second; + const std::string address = gArgs.GetArg(arg, ""); + std::unique_ptr<CZMQAbstractNotifier> notifier = factory(); notifier->SetType(entry.first); notifier->SetAddress(address); notifier->SetOutboundMessageHighWaterMark(static_cast<int>(gArgs.GetArg(arg + "hwm", CZMQAbstractNotifier::DEFAULT_ZMQ_SNDHWM))); - notifiers.push_back(notifier); + notifiers.push_back(std::move(notifier)); } } if (!notifiers.empty()) { - notificationInterface = new CZMQNotificationInterface(); - notificationInterface->notifiers = notifiers; + std::unique_ptr<CZMQNotificationInterface> notificationInterface(new CZMQNotificationInterface()); + notificationInterface->notifiers = std::move(notifiers); - if (!notificationInterface->Initialize()) - { - delete notificationInterface; - notificationInterface = nullptr; + if (notificationInterface->Initialize()) { + return notificationInterface.release(); } } - return notificationInterface; + return nullptr; } // Called at startup to conditionally set up ZMQ socket(s) @@ -95,26 +85,15 @@ bool CZMQNotificationInterface::Initialize() return false; } - std::list<CZMQAbstractNotifier*>::iterator i=notifiers.begin(); - for (; i!=notifiers.end(); ++i) - { - CZMQAbstractNotifier *notifier = *i; - if (notifier->Initialize(pcontext)) - { + for (auto& notifier : notifiers) { + if (notifier->Initialize(pcontext)) { LogPrint(BCLog::ZMQ, "zmq: Notifier %s ready (address = %s)\n", notifier->GetType(), notifier->GetAddress()); - } - else - { + } else { LogPrint(BCLog::ZMQ, "zmq: Notifier %s failed (address = %s)\n", notifier->GetType(), notifier->GetAddress()); - break; + return false; } } - if (i!=notifiers.end()) - { - return false; - } - return true; } @@ -124,9 +103,7 @@ void CZMQNotificationInterface::Shutdown() LogPrint(BCLog::ZMQ, "zmq: Shutdown notification interface\n"); if (pcontext) { - for (std::list<CZMQAbstractNotifier*>::iterator i=notifiers.begin(); i!=notifiers.end(); ++i) - { - CZMQAbstractNotifier *notifier = *i; + for (auto& notifier : notifiers) { LogPrint(BCLog::ZMQ, "zmq: Shutdown notifier %s at %s\n", notifier->GetType(), notifier->GetAddress()); notifier->Shutdown(); } @@ -136,61 +113,81 @@ void CZMQNotificationInterface::Shutdown() } } -void CZMQNotificationInterface::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) -{ - if (fInitialDownload || pindexNew == pindexFork) // In IBD or blocks were disconnected without any new ones - return; +namespace { - for (std::list<CZMQAbstractNotifier*>::iterator i = notifiers.begin(); i!=notifiers.end(); ) - { - CZMQAbstractNotifier *notifier = *i; - if (notifier->NotifyBlock(pindexNew)) - { - i++; - } - else - { +template <typename Function> +void TryForEachAndRemoveFailed(std::list<std::unique_ptr<CZMQAbstractNotifier>>& notifiers, const Function& func) +{ + for (auto i = notifiers.begin(); i != notifiers.end(); ) { + CZMQAbstractNotifier* notifier = i->get(); + if (func(notifier)) { + ++i; + } else { notifier->Shutdown(); i = notifiers.erase(i); } } } -void CZMQNotificationInterface::TransactionAddedToMempool(const CTransactionRef& ptx) +} // anonymous namespace + +void CZMQNotificationInterface::UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) +{ + if (fInitialDownload || pindexNew == pindexFork) // In IBD or blocks were disconnected without any new ones + return; + + TryForEachAndRemoveFailed(notifiers, [pindexNew](CZMQAbstractNotifier* notifier) { + return notifier->NotifyBlock(pindexNew); + }); +} + +void CZMQNotificationInterface::TransactionAddedToMempool(const CTransactionRef& ptx, uint64_t mempool_sequence) { - // Used by BlockConnected and BlockDisconnected as well, because they're - // all the same external callback. const CTransaction& tx = *ptx; - for (std::list<CZMQAbstractNotifier*>::iterator i = notifiers.begin(); i!=notifiers.end(); ) - { - CZMQAbstractNotifier *notifier = *i; - if (notifier->NotifyTransaction(tx)) - { - i++; - } - else - { - notifier->Shutdown(); - i = notifiers.erase(i); - } - } + TryForEachAndRemoveFailed(notifiers, [&tx, mempool_sequence](CZMQAbstractNotifier* notifier) { + return notifier->NotifyTransaction(tx) && notifier->NotifyTransactionAcceptance(tx, mempool_sequence); + }); +} + +void CZMQNotificationInterface::TransactionRemovedFromMempool(const CTransactionRef& ptx, MemPoolRemovalReason reason, uint64_t mempool_sequence) +{ + // Called for all non-block inclusion reasons + const CTransaction& tx = *ptx; + + TryForEachAndRemoveFailed(notifiers, [&tx, mempool_sequence](CZMQAbstractNotifier* notifier) { + return notifier->NotifyTransactionRemoval(tx, mempool_sequence); + }); } void CZMQNotificationInterface::BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexConnected) { for (const CTransactionRef& ptx : pblock->vtx) { - // Do a normal notify for each transaction added in the block - TransactionAddedToMempool(ptx); + const CTransaction& tx = *ptx; + TryForEachAndRemoveFailed(notifiers, [&tx](CZMQAbstractNotifier* notifier) { + return notifier->NotifyTransaction(tx); + }); } + + // Next we notify BlockConnect listeners for *all* blocks + TryForEachAndRemoveFailed(notifiers, [pindexConnected](CZMQAbstractNotifier* notifier) { + return notifier->NotifyBlockConnect(pindexConnected); + }); } void CZMQNotificationInterface::BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexDisconnected) { for (const CTransactionRef& ptx : pblock->vtx) { - // Do a normal notify for each transaction removed in block disconnection - TransactionAddedToMempool(ptx); + const CTransaction& tx = *ptx; + TryForEachAndRemoveFailed(notifiers, [&tx](CZMQAbstractNotifier* notifier) { + return notifier->NotifyTransaction(tx); + }); } + + // Next we notify BlockDisconnect listeners for *all* blocks + TryForEachAndRemoveFailed(notifiers, [pindexDisconnected](CZMQAbstractNotifier* notifier) { + return notifier->NotifyBlockDisconnect(pindexDisconnected); + }); } CZMQNotificationInterface* g_zmq_notification_interface = nullptr; diff --git a/src/zmq/zmqnotificationinterface.h b/src/zmq/zmqnotificationinterface.h index 60f3b6148a..788a383517 100644 --- a/src/zmq/zmqnotificationinterface.h +++ b/src/zmq/zmqnotificationinterface.h @@ -7,6 +7,7 @@ #include <validationinterface.h> #include <list> +#include <memory> class CBlockIndex; class CZMQAbstractNotifier; @@ -25,7 +26,8 @@ protected: void Shutdown(); // CValidationInterface - void TransactionAddedToMempool(const CTransactionRef& tx) override; + void TransactionAddedToMempool(const CTransactionRef& tx, uint64_t mempool_sequence) override; + void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) override; void BlockConnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexConnected) override; void BlockDisconnected(const std::shared_ptr<const CBlock>& pblock, const CBlockIndex* pindexDisconnected) override; void UpdatedBlockTip(const CBlockIndex *pindexNew, const CBlockIndex *pindexFork, bool fInitialDownload) override; @@ -34,7 +36,7 @@ private: CZMQNotificationInterface(); void *pcontext; - std::list<CZMQAbstractNotifier*> notifiers; + std::list<std::unique_ptr<CZMQAbstractNotifier>> notifiers; }; extern CZMQNotificationInterface* g_zmq_notification_interface; diff --git a/src/zmq/zmqpublishnotifier.cpp b/src/zmq/zmqpublishnotifier.cpp index e2431cbbb7..a0e7a0a600 100644 --- a/src/zmq/zmqpublishnotifier.cpp +++ b/src/zmq/zmqpublishnotifier.cpp @@ -2,13 +2,23 @@ // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. +#include <zmq/zmqpublishnotifier.h> + #include <chain.h> #include <chainparams.h> +#include <rpc/server.h> #include <streams.h> -#include <zmq/zmqpublishnotifier.h> -#include <validation.h> #include <util/system.h> -#include <rpc/server.h> +#include <validation.h> +#include <zmq/zmqutil.h> + +#include <zmq.h> + +#include <cstdarg> +#include <cstddef> +#include <map> +#include <string> +#include <utility> static std::multimap<std::string, CZMQAbstractPublishNotifier*> mapPublishNotifiers; @@ -16,6 +26,7 @@ static const char *MSG_HASHBLOCK = "hashblock"; static const char *MSG_HASHTX = "hashtx"; static const char *MSG_RAWBLOCK = "rawblock"; static const char *MSG_RAWTX = "rawtx"; +static const char *MSG_SEQUENCE = "sequence"; // Internal function to send multipart message static int zmq_send_multipart(void *sock, const void* data, size_t size, ...) @@ -149,7 +160,7 @@ void CZMQAbstractPublishNotifier::Shutdown() psocket = nullptr; } -bool CZMQAbstractPublishNotifier::SendMessage(const char *command, const void* data, size_t size) +bool CZMQAbstractPublishNotifier::SendZmqMessage(const char *command, const void* data, size_t size) { assert(psocket); @@ -173,7 +184,7 @@ bool CZMQPublishHashBlockNotifier::NotifyBlock(const CBlockIndex *pindex) char data[32]; for (unsigned int i = 0; i < 32; i++) data[31 - i] = hash.begin()[i]; - return SendMessage(MSG_HASHBLOCK, data, 32); + return SendZmqMessage(MSG_HASHBLOCK, data, 32); } bool CZMQPublishHashTransactionNotifier::NotifyTransaction(const CTransaction &transaction) @@ -183,7 +194,7 @@ bool CZMQPublishHashTransactionNotifier::NotifyTransaction(const CTransaction &t char data[32]; for (unsigned int i = 0; i < 32; i++) data[31 - i] = hash.begin()[i]; - return SendMessage(MSG_HASHTX, data, 32); + return SendZmqMessage(MSG_HASHTX, data, 32); } bool CZMQPublishRawBlockNotifier::NotifyBlock(const CBlockIndex *pindex) @@ -204,7 +215,7 @@ bool CZMQPublishRawBlockNotifier::NotifyBlock(const CBlockIndex *pindex) ss << block; } - return SendMessage(MSG_RAWBLOCK, &(*ss.begin()), ss.size()); + return SendZmqMessage(MSG_RAWBLOCK, &(*ss.begin()), ss.size()); } bool CZMQPublishRawTransactionNotifier::NotifyTransaction(const CTransaction &transaction) @@ -213,5 +224,53 @@ bool CZMQPublishRawTransactionNotifier::NotifyTransaction(const CTransaction &tr LogPrint(BCLog::ZMQ, "zmq: Publish rawtx %s\n", hash.GetHex()); CDataStream ss(SER_NETWORK, PROTOCOL_VERSION | RPCSerializationFlags()); ss << transaction; - return SendMessage(MSG_RAWTX, &(*ss.begin()), ss.size()); + return SendZmqMessage(MSG_RAWTX, &(*ss.begin()), ss.size()); +} + + +// TODO: Dedup this code to take label char, log string +bool CZMQPublishSequenceNotifier::NotifyBlockConnect(const CBlockIndex *pindex) +{ + uint256 hash = pindex->GetBlockHash(); + LogPrint(BCLog::ZMQ, "zmq: Publish sequence block connect %s\n", hash.GetHex()); + char data[sizeof(uint256)+1]; + for (unsigned int i = 0; i < sizeof(uint256); i++) + data[sizeof(uint256) - 1 - i] = hash.begin()[i]; + data[sizeof(data) - 1] = 'C'; // Block (C)onnect + return SendZmqMessage(MSG_SEQUENCE, data, sizeof(data)); +} + +bool CZMQPublishSequenceNotifier::NotifyBlockDisconnect(const CBlockIndex *pindex) +{ + uint256 hash = pindex->GetBlockHash(); + LogPrint(BCLog::ZMQ, "zmq: Publish sequence block disconnect %s\n", hash.GetHex()); + char data[sizeof(uint256)+1]; + for (unsigned int i = 0; i < sizeof(uint256); i++) + data[sizeof(uint256) - 1 - i] = hash.begin()[i]; + data[sizeof(data) - 1] = 'D'; // Block (D)isconnect + return SendZmqMessage(MSG_SEQUENCE, data, sizeof(data)); +} + +bool CZMQPublishSequenceNotifier::NotifyTransactionAcceptance(const CTransaction &transaction, uint64_t mempool_sequence) +{ + uint256 hash = transaction.GetHash(); + LogPrint(BCLog::ZMQ, "zmq: Publish hashtx mempool acceptance %s\n", hash.GetHex()); + unsigned char data[sizeof(uint256)+sizeof(mempool_sequence)+1]; + for (unsigned int i = 0; i < sizeof(uint256); i++) + data[sizeof(uint256) - 1 - i] = hash.begin()[i]; + data[sizeof(uint256)] = 'A'; // Mempool (A)cceptance + WriteLE64(data+sizeof(uint256)+1, mempool_sequence); + return SendZmqMessage(MSG_SEQUENCE, data, sizeof(data)); +} + +bool CZMQPublishSequenceNotifier::NotifyTransactionRemoval(const CTransaction &transaction, uint64_t mempool_sequence) +{ + uint256 hash = transaction.GetHash(); + LogPrint(BCLog::ZMQ, "zmq: Publish hashtx mempool removal %s\n", hash.GetHex()); + unsigned char data[sizeof(uint256)+sizeof(mempool_sequence)+1]; + for (unsigned int i = 0; i < sizeof(uint256); i++) + data[sizeof(uint256) - 1 - i] = hash.begin()[i]; + data[sizeof(uint256)] = 'R'; // Mempool (R)emoval + WriteLE64(data+sizeof(uint256)+1, mempool_sequence); + return SendZmqMessage(MSG_SEQUENCE, data, sizeof(data)); } diff --git a/src/zmq/zmqpublishnotifier.h b/src/zmq/zmqpublishnotifier.h index 278fdb94d2..f13ed6f537 100644 --- a/src/zmq/zmqpublishnotifier.h +++ b/src/zmq/zmqpublishnotifier.h @@ -22,7 +22,7 @@ public: * data * message sequence number */ - bool SendMessage(const char *command, const void* data, size_t size); + bool SendZmqMessage(const char *command, const void* data, size_t size); bool Initialize(void *pcontext) override; void Shutdown() override; @@ -52,4 +52,13 @@ public: bool NotifyTransaction(const CTransaction &transaction) override; }; +class CZMQPublishSequenceNotifier : public CZMQAbstractPublishNotifier +{ +public: + bool NotifyBlockConnect(const CBlockIndex *pindex) override; + bool NotifyBlockDisconnect(const CBlockIndex *pindex) override; + bool NotifyTransactionAcceptance(const CTransaction &transaction, uint64_t mempool_sequence) override; + bool NotifyTransactionRemoval(const CTransaction &transaction, uint64_t mempool_sequence) override; +}; + #endif // BITCOIN_ZMQ_ZMQPUBLISHNOTIFIER_H diff --git a/src/zmq/zmqutil.cpp b/src/zmq/zmqutil.cpp new file mode 100644 index 0000000000..f07a4ae9fd --- /dev/null +++ b/src/zmq/zmqutil.cpp @@ -0,0 +1,14 @@ +// Copyright (c) 2014-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. + +#include <zmq/zmqutil.h> + +#include <logging.h> + +#include <zmq.h> + +void zmqError(const char* str) +{ + LogPrint(BCLog::ZMQ, "zmq: Error: %s, errno=%s\n", str, zmq_strerror(errno)); +} diff --git a/src/zmq/zmqutil.h b/src/zmq/zmqutil.h new file mode 100644 index 0000000000..4c1df5d6db --- /dev/null +++ b/src/zmq/zmqutil.h @@ -0,0 +1,10 @@ +// Copyright (c) 2014-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. + +#ifndef BITCOIN_ZMQ_ZMQUTIL_H +#define BITCOIN_ZMQ_ZMQUTIL_H + +void zmqError(const char* str); + +#endif // BITCOIN_ZMQ_ZMQUTIL_H |