diff options
Diffstat (limited to 'src/node')
-rw-r--r-- | src/node/coin.cpp | 3 | ||||
-rw-r--r-- | src/node/coinstats.cpp | 88 | ||||
-rw-r--r-- | src/node/coinstats.h | 6 | ||||
-rw-r--r-- | src/node/context.cpp | 2 | ||||
-rw-r--r-- | src/node/context.h | 15 | ||||
-rw-r--r-- | src/node/interfaces.cpp | 696 | ||||
-rw-r--r-- | src/node/psbt.h | 20 | ||||
-rw-r--r-- | src/node/transaction.cpp | 55 | ||||
-rw-r--r-- | src/node/transaction.h | 2 | ||||
-rw-r--r-- | src/node/utxo_snapshot.h | 2 |
10 files changed, 831 insertions, 58 deletions
diff --git a/src/node/coin.cpp b/src/node/coin.cpp index f4f86cdbe9..263dcff657 100644 --- a/src/node/coin.cpp +++ b/src/node/coin.cpp @@ -12,7 +12,8 @@ void FindCoins(const NodeContext& node, std::map<COutPoint, Coin>& coins) { assert(node.mempool); LOCK2(cs_main, node.mempool->cs); - CCoinsViewCache& chain_view = ::ChainstateActive().CoinsTip(); + assert(std::addressof(::ChainstateActive()) == std::addressof(node.chainman->ActiveChainstate())); + CCoinsViewCache& chain_view = node.chainman->ActiveChainstate().CoinsTip(); CCoinsViewMemPool mempool_view(&chain_view, *node.mempool); for (auto& coin : coins) { if (!mempool_view.GetCoin(coin.first, coin.second)) { diff --git a/src/node/coinstats.cpp b/src/node/coinstats.cpp index fb46ea1731..88bdba5953 100644 --- a/src/node/coinstats.cpp +++ b/src/node/coinstats.cpp @@ -6,6 +6,7 @@ #include <node/coinstats.h> #include <coins.h> +#include <crypto/muhash.h> #include <hash.h> #include <serialize.h> #include <uint256.h> @@ -24,37 +25,65 @@ static uint64_t GetBogoSize(const CScript& scriptPubKey) scriptPubKey.size() /* scriptPubKey */; } -static void ApplyStats(CCoinsStats& stats, CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs) +static void ApplyHash(CCoinsStats& stats, CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs, std::map<uint32_t, Coin>::const_iterator it) { - assert(!outputs.empty()); - ss << hash; - ss << VARINT(outputs.begin()->second.nHeight * 2 + outputs.begin()->second.fCoinBase ? 1u : 0u); - stats.nTransactions++; - for (const auto& output : outputs) { - ss << VARINT(output.first + 1); - ss << output.second.out.scriptPubKey; - ss << VARINT_MODE(output.second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED); - stats.nTransactionOutputs++; - stats.nTotalAmount += output.second.out.nValue; - stats.nBogoSize += GetBogoSize(output.second.out.scriptPubKey); + if (it == outputs.begin()) { + ss << hash; + ss << VARINT(it->second.nHeight * 2 + it->second.fCoinBase ? 1u : 0u); + } + + ss << VARINT(it->first + 1); + ss << it->second.out.scriptPubKey; + ss << VARINT_MODE(it->second.out.nValue, VarIntMode::NONNEGATIVE_SIGNED); + + if (it == std::prev(outputs.end())) { + ss << VARINT(0u); } - ss << VARINT(0u); } -static void ApplyStats(CCoinsStats& stats, std::nullptr_t, const uint256& hash, const std::map<uint32_t, Coin>& outputs) +static void ApplyHash(CCoinsStats& stats, std::nullptr_t, const uint256& hash, const std::map<uint32_t, Coin>& outputs, std::map<uint32_t, Coin>::const_iterator it) {} + +static void ApplyHash(CCoinsStats& stats, MuHash3072& muhash, const uint256& hash, const std::map<uint32_t, Coin>& outputs, std::map<uint32_t, Coin>::const_iterator it) +{ + COutPoint outpoint = COutPoint(hash, it->first); + Coin coin = it->second; + + CDataStream ss(SER_DISK, PROTOCOL_VERSION); + ss << outpoint; + ss << static_cast<uint32_t>(coin.nHeight * 2 + coin.fCoinBase); + ss << coin.out; + muhash.Insert(MakeUCharSpan(ss)); +} + +//! Warning: be very careful when changing this! assumeutxo and UTXO snapshot +//! validation commitments are reliant on the hash constructed by this +//! function. +//! +//! If the construction of this hash is changed, it will invalidate +//! existing UTXO snapshots. This will not result in any kind of consensus +//! failure, but it will force clients that were expecting to make use of +//! assumeutxo to do traditional IBD instead. +//! +//! It is also possible, though very unlikely, that a change in this +//! construction could cause a previously invalid (and potentially malicious) +//! UTXO snapshot to be considered valid. +template <typename T> +static void ApplyStats(CCoinsStats& stats, T& hash_obj, const uint256& hash, const std::map<uint32_t, Coin>& outputs) { assert(!outputs.empty()); stats.nTransactions++; - for (const auto& output : outputs) { + for (auto it = outputs.begin(); it != outputs.end(); ++it) { + ApplyHash(stats, hash_obj, hash, outputs, it); + stats.nTransactionOutputs++; - stats.nTotalAmount += output.second.out.nValue; - stats.nBogoSize += GetBogoSize(output.second.out.scriptPubKey); + stats.nTotalAmount += it->second.out.nValue; + stats.nBogoSize += GetBogoSize(it->second.out.scriptPubKey); } } //! Calculate statistics about the unspent transaction output set template <typename T> -static bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point) +static bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, T hash_obj, const std::function<void()>& interruption_point) { stats = CCoinsStats(); std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor()); @@ -63,7 +92,8 @@ static bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, const stats.hashBlock = pcursor->GetBestBlock(); { LOCK(cs_main); - stats.nHeight = LookupBlockIndex(stats.hashBlock)->nHeight; + assert(std::addressof(g_chainman.m_blockman) == std::addressof(blockman)); + stats.nHeight = blockman.LookupBlockIndex(stats.hashBlock)->nHeight; } PrepareHash(hash_obj, stats); @@ -97,29 +127,41 @@ static bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, T hash_obj, const return true; } -bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, CoinStatsHashType hash_type, const std::function<void()>& interruption_point) +bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, CoinStatsHashType hash_type, const std::function<void()>& interruption_point) { switch (hash_type) { case(CoinStatsHashType::HASH_SERIALIZED): { CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); - return GetUTXOStats(view, stats, ss, interruption_point); + return GetUTXOStats(view, blockman, stats, ss, interruption_point); + } + case(CoinStatsHashType::MUHASH): { + MuHash3072 muhash; + return GetUTXOStats(view, blockman, stats, muhash, interruption_point); } case(CoinStatsHashType::NONE): { - return GetUTXOStats(view, stats, nullptr, interruption_point); + return GetUTXOStats(view, blockman, stats, nullptr, interruption_point); } } // no default case, so the compiler can warn about missing cases assert(false); } // The legacy hash serializes the hashBlock -static void PrepareHash(CHashWriter& ss, CCoinsStats& stats) +static void PrepareHash(CHashWriter& ss, const CCoinsStats& stats) { ss << stats.hashBlock; } +// MuHash does not need the prepare step +static void PrepareHash(MuHash3072& muhash, CCoinsStats& stats) {} static void PrepareHash(std::nullptr_t, CCoinsStats& stats) {} static void FinalizeHash(CHashWriter& ss, CCoinsStats& stats) { stats.hashSerialized = ss.GetHash(); } +static void FinalizeHash(MuHash3072& muhash, CCoinsStats& stats) +{ + uint256 out; + muhash.Finalize(out); + stats.hashSerialized = out; +} static void FinalizeHash(std::nullptr_t, CCoinsStats& stats) {} diff --git a/src/node/coinstats.h b/src/node/coinstats.h index 2a7441c10e..83f228aa7e 100644 --- a/src/node/coinstats.h +++ b/src/node/coinstats.h @@ -1,5 +1,5 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2019 The Bitcoin Core developers +// Copyright (c) 2009-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. @@ -8,6 +8,7 @@ #include <amount.h> #include <uint256.h> +#include <validation.h> #include <cstdint> #include <functional> @@ -16,6 +17,7 @@ class CCoinsView; enum class CoinStatsHashType { HASH_SERIALIZED, + MUHASH, NONE, }; @@ -35,6 +37,6 @@ struct CCoinsStats }; //! Calculate statistics about the unspent transaction output set -bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats, const CoinStatsHashType hash_type, const std::function<void()>& interruption_point = {}); +bool GetUTXOStats(CCoinsView* view, BlockManager& blockman, CCoinsStats& stats, const CoinStatsHashType hash_type, const std::function<void()>& interruption_point = {}); #endif // BITCOIN_NODE_COINSTATS_H diff --git a/src/node/context.cpp b/src/node/context.cpp index 0238aab0d9..958221a913 100644 --- a/src/node/context.cpp +++ b/src/node/context.cpp @@ -8,7 +8,9 @@ #include <interfaces/chain.h> #include <net.h> #include <net_processing.h> +#include <policy/fees.h> #include <scheduler.h> +#include <txmempool.h> NodeContext::NodeContext() {} NodeContext::~NodeContext() {} diff --git a/src/node/context.h b/src/node/context.h index c783c39cd6..9b611bf8f5 100644 --- a/src/node/context.h +++ b/src/node/context.h @@ -6,19 +6,22 @@ #define BITCOIN_NODE_CONTEXT_H #include <cassert> +#include <functional> #include <memory> #include <vector> class ArgsManager; class BanMan; +class CBlockPolicyEstimator; class CConnman; class CScheduler; class CTxMemPool; class ChainstateManager; -class PeerLogicValidation; +class PeerManager; namespace interfaces { class Chain; class ChainClient; +class WalletClient; } // namespace interfaces //! NodeContext struct containing references to chain state and connection @@ -33,14 +36,20 @@ class ChainClient; //! be used without pulling in unwanted dependencies or functionality. struct NodeContext { std::unique_ptr<CConnman> connman; - CTxMemPool* mempool{nullptr}; // Currently a raw pointer because the memory is not managed by this struct - std::unique_ptr<PeerLogicValidation> peer_logic; + std::unique_ptr<CTxMemPool> mempool; + std::unique_ptr<CBlockPolicyEstimator> fee_estimator; + std::unique_ptr<PeerManager> peerman; ChainstateManager* chainman{nullptr}; // Currently a raw pointer because the memory is not managed by this struct std::unique_ptr<BanMan> banman; ArgsManager* args{nullptr}; // Currently a raw pointer because the memory is not managed by this struct std::unique_ptr<interfaces::Chain> chain; + //! List of all chain clients (wallet processes or other client) connected to node. std::vector<std::unique_ptr<interfaces::ChainClient>> chain_clients; + //! Reference to chain client that should used to load or create wallets + //! opened by the gui. + interfaces::WalletClient* wallet_client{nullptr}; std::unique_ptr<CScheduler> scheduler; + std::function<void()> rpc_interruption_point = [] {}; //! Declare default constructor and destructor that are not inline, so code //! instantiating the NodeContext struct doesn't need to #include class diff --git a/src/node/interfaces.cpp b/src/node/interfaces.cpp new file mode 100644 index 0000000000..50c8c29175 --- /dev/null +++ b/src/node/interfaces.cpp @@ -0,0 +1,696 @@ +// Copyright (c) 2018-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 <addrdb.h> +#include <banman.h> +#include <boost/signals2/signal.hpp> +#include <chain.h> +#include <chainparams.h> +#include <init.h> +#include <interfaces/chain.h> +#include <interfaces/handler.h> +#include <interfaces/node.h> +#include <interfaces/wallet.h> +#include <mapport.h> +#include <net.h> +#include <net_processing.h> +#include <netaddress.h> +#include <netbase.h> +#include <node/coin.h> +#include <node/context.h> +#include <node/transaction.h> +#include <node/ui_interface.h> +#include <policy/feerate.h> +#include <policy/fees.h> +#include <policy/policy.h> +#include <policy/rbf.h> +#include <policy/settings.h> +#include <primitives/block.h> +#include <primitives/transaction.h> +#include <rpc/protocol.h> +#include <rpc/server.h> +#include <shutdown.h> +#include <support/allocators/secure.h> +#include <sync.h> +#include <timedata.h> +#include <txmempool.h> +#include <uint256.h> +#include <univalue.h> +#include <util/check.h> +#include <util/ref.h> +#include <util/system.h> +#include <util/translation.h> +#include <validation.h> +#include <validationinterface.h> +#include <warnings.h> + +#if defined(HAVE_CONFIG_H) +#include <config/bitcoin-config.h> +#endif + +#include <memory> +#include <optional> +#include <utility> + +using interfaces::BlockTip; +using interfaces::Chain; +using interfaces::FoundBlock; +using interfaces::Handler; +using interfaces::MakeHandler; +using interfaces::Node; +using interfaces::WalletClient; + +namespace node { +namespace { +class NodeImpl : public Node +{ +public: + explicit NodeImpl(NodeContext* context) { setContext(context); } + void initLogging() override { InitLogging(*Assert(m_context->args)); } + void initParameterInteraction() override { InitParameterInteraction(*Assert(m_context->args)); } + bilingual_str getWarnings() override { return GetWarnings(true); } + uint32_t getLogCategories() override { return LogInstance().GetCategoryMask(); } + bool baseInitialize() override + { + return AppInitBasicSetup(gArgs) && AppInitParameterInteraction(gArgs) && AppInitSanityChecks() && + AppInitLockDataDirectory() && AppInitInterfaces(*m_context); + } + bool appInitMain(interfaces::BlockAndHeaderTipInfo* tip_info) override + { + return AppInitMain(m_context_ref, *m_context, tip_info); + } + void appShutdown() override + { + Interrupt(*m_context); + Shutdown(*m_context); + } + void startShutdown() override + { + StartShutdown(); + // Stop RPC for clean shutdown if any of waitfor* commands is executed. + if (gArgs.GetBoolArg("-server", false)) { + InterruptRPC(); + StopRPC(); + } + } + bool shutdownRequested() override { return ShutdownRequested(); } + void mapPort(bool use_upnp, bool use_natpmp) override { StartMapPort(use_upnp, use_natpmp); } + bool getProxy(Network net, proxyType& proxy_info) override { return GetProxy(net, proxy_info); } + size_t getNodeCount(ConnectionDirection flags) override + { + return m_context->connman ? m_context->connman->GetNodeCount(flags) : 0; + } + bool getNodesStats(NodesStats& stats) override + { + stats.clear(); + + if (m_context->connman) { + std::vector<CNodeStats> stats_temp; + m_context->connman->GetNodeStats(stats_temp); + + stats.reserve(stats_temp.size()); + for (auto& node_stats_temp : stats_temp) { + stats.emplace_back(std::move(node_stats_temp), false, CNodeStateStats()); + } + + // Try to retrieve the CNodeStateStats for each node. + if (m_context->peerman) { + TRY_LOCK(::cs_main, lockMain); + if (lockMain) { + for (auto& node_stats : stats) { + std::get<1>(node_stats) = + m_context->peerman->GetNodeStateStats(std::get<0>(node_stats).nodeid, std::get<2>(node_stats)); + } + } + } + return true; + } + return false; + } + bool getBanned(banmap_t& banmap) override + { + if (m_context->banman) { + m_context->banman->GetBanned(banmap); + return true; + } + return false; + } + bool ban(const CNetAddr& net_addr, int64_t ban_time_offset) override + { + if (m_context->banman) { + m_context->banman->Ban(net_addr, ban_time_offset); + return true; + } + return false; + } + bool unban(const CSubNet& ip) override + { + if (m_context->banman) { + m_context->banman->Unban(ip); + return true; + } + return false; + } + bool disconnectByAddress(const CNetAddr& net_addr) override + { + if (m_context->connman) { + return m_context->connman->DisconnectNode(net_addr); + } + return false; + } + bool disconnectById(NodeId id) override + { + if (m_context->connman) { + return m_context->connman->DisconnectNode(id); + } + return false; + } + int64_t getTotalBytesRecv() override { return m_context->connman ? m_context->connman->GetTotalBytesRecv() : 0; } + int64_t getTotalBytesSent() override { return m_context->connman ? m_context->connman->GetTotalBytesSent() : 0; } + size_t getMempoolSize() override { return m_context->mempool ? m_context->mempool->size() : 0; } + size_t getMempoolDynamicUsage() override { return m_context->mempool ? m_context->mempool->DynamicMemoryUsage() : 0; } + bool getHeaderTip(int& height, int64_t& block_time) override + { + LOCK(::cs_main); + if (::pindexBestHeader) { + height = ::pindexBestHeader->nHeight; + block_time = ::pindexBestHeader->GetBlockTime(); + return true; + } + return false; + } + int getNumBlocks() override + { + LOCK(::cs_main); + assert(std::addressof(::ChainActive()) == std::addressof(m_context->chainman->ActiveChain())); + return m_context->chainman->ActiveChain().Height(); + } + uint256 getBestBlockHash() override + { + assert(std::addressof(::ChainActive()) == std::addressof(m_context->chainman->ActiveChain())); + const CBlockIndex* tip = WITH_LOCK(::cs_main, return m_context->chainman->ActiveChain().Tip()); + return tip ? tip->GetBlockHash() : Params().GenesisBlock().GetHash(); + } + int64_t getLastBlockTime() override + { + LOCK(::cs_main); + assert(std::addressof(::ChainActive()) == std::addressof(m_context->chainman->ActiveChain())); + if (m_context->chainman->ActiveChain().Tip()) { + return m_context->chainman->ActiveChain().Tip()->GetBlockTime(); + } + return Params().GenesisBlock().GetBlockTime(); // Genesis block's time of current network + } + double getVerificationProgress() override + { + const CBlockIndex* tip; + { + LOCK(::cs_main); + assert(std::addressof(::ChainActive()) == std::addressof(m_context->chainman->ActiveChain())); + tip = m_context->chainman->ActiveChain().Tip(); + } + return GuessVerificationProgress(Params().TxData(), tip); + } + bool isInitialBlockDownload() override { + assert(std::addressof(::ChainstateActive()) == std::addressof(m_context->chainman->ActiveChainstate())); + return m_context->chainman->ActiveChainstate().IsInitialBlockDownload(); + } + bool getReindex() override { return ::fReindex; } + bool getImporting() override { return ::fImporting; } + void setNetworkActive(bool active) override + { + if (m_context->connman) { + m_context->connman->SetNetworkActive(active); + } + } + bool getNetworkActive() override { return m_context->connman && m_context->connman->GetNetworkActive(); } + CFeeRate getDustRelayFee() override { return ::dustRelayFee; } + UniValue executeRpc(const std::string& command, const UniValue& params, const std::string& uri) override + { + JSONRPCRequest req(m_context_ref); + req.params = params; + req.strMethod = command; + req.URI = uri; + return ::tableRPC.execute(req); + } + std::vector<std::string> listRpcCommands() override { return ::tableRPC.listCommands(); } + void rpcSetTimerInterfaceIfUnset(RPCTimerInterface* iface) override { RPCSetTimerInterfaceIfUnset(iface); } + void rpcUnsetTimerInterface(RPCTimerInterface* iface) override { RPCUnsetTimerInterface(iface); } + bool getUnspentOutput(const COutPoint& output, Coin& coin) override + { + LOCK(::cs_main); + assert(std::addressof(::ChainstateActive()) == std::addressof(m_context->chainman->ActiveChainstate())); + return m_context->chainman->ActiveChainstate().CoinsTip().GetCoin(output, coin); + } + WalletClient& walletClient() override + { + return *Assert(m_context->wallet_client); + } + std::unique_ptr<Handler> handleInitMessage(InitMessageFn fn) override + { + return MakeHandler(::uiInterface.InitMessage_connect(fn)); + } + std::unique_ptr<Handler> handleMessageBox(MessageBoxFn fn) override + { + return MakeHandler(::uiInterface.ThreadSafeMessageBox_connect(fn)); + } + std::unique_ptr<Handler> handleQuestion(QuestionFn fn) override + { + return MakeHandler(::uiInterface.ThreadSafeQuestion_connect(fn)); + } + std::unique_ptr<Handler> handleShowProgress(ShowProgressFn fn) override + { + return MakeHandler(::uiInterface.ShowProgress_connect(fn)); + } + std::unique_ptr<Handler> handleNotifyNumConnectionsChanged(NotifyNumConnectionsChangedFn fn) override + { + return MakeHandler(::uiInterface.NotifyNumConnectionsChanged_connect(fn)); + } + std::unique_ptr<Handler> handleNotifyNetworkActiveChanged(NotifyNetworkActiveChangedFn fn) override + { + return MakeHandler(::uiInterface.NotifyNetworkActiveChanged_connect(fn)); + } + std::unique_ptr<Handler> handleNotifyAlertChanged(NotifyAlertChangedFn fn) override + { + return MakeHandler(::uiInterface.NotifyAlertChanged_connect(fn)); + } + std::unique_ptr<Handler> handleBannedListChanged(BannedListChangedFn fn) override + { + return MakeHandler(::uiInterface.BannedListChanged_connect(fn)); + } + std::unique_ptr<Handler> handleNotifyBlockTip(NotifyBlockTipFn fn) override + { + return MakeHandler(::uiInterface.NotifyBlockTip_connect([fn](SynchronizationState sync_state, const CBlockIndex* block) { + fn(sync_state, BlockTip{block->nHeight, block->GetBlockTime(), block->GetBlockHash()}, + GuessVerificationProgress(Params().TxData(), block)); + })); + } + std::unique_ptr<Handler> handleNotifyHeaderTip(NotifyHeaderTipFn fn) override + { + return MakeHandler( + ::uiInterface.NotifyHeaderTip_connect([fn](SynchronizationState sync_state, const CBlockIndex* block) { + fn(sync_state, BlockTip{block->nHeight, block->GetBlockTime(), block->GetBlockHash()}, + /* verification progress is unused when a header was received */ 0); + })); + } + NodeContext* context() override { return m_context; } + void setContext(NodeContext* context) override + { + m_context = context; + if (context) { + m_context_ref.Set(*context); + } else { + m_context_ref.Clear(); + } + } + NodeContext* m_context{nullptr}; + util::Ref m_context_ref; +}; + +bool FillBlock(const CBlockIndex* index, const FoundBlock& block, UniqueLock<RecursiveMutex>& lock, const CChain& active) +{ + if (!index) return false; + if (block.m_hash) *block.m_hash = index->GetBlockHash(); + if (block.m_height) *block.m_height = index->nHeight; + if (block.m_time) *block.m_time = index->GetBlockTime(); + if (block.m_max_time) *block.m_max_time = index->GetBlockTimeMax(); + if (block.m_mtp_time) *block.m_mtp_time = index->GetMedianTimePast(); + if (block.m_in_active_chain) *block.m_in_active_chain = active[index->nHeight] == index; + if (block.m_next_block) FillBlock(active[index->nHeight] == index ? active[index->nHeight + 1] : nullptr, *block.m_next_block, lock, active); + if (block.m_data) { + REVERSE_LOCK(lock); + if (!ReadBlockFromDisk(*block.m_data, index, Params().GetConsensus())) block.m_data->SetNull(); + } + return true; +} + +class NotificationsProxy : public CValidationInterface +{ +public: + explicit NotificationsProxy(std::shared_ptr<Chain::Notifications> notifications) + : m_notifications(std::move(notifications)) {} + virtual ~NotificationsProxy() = default; + void TransactionAddedToMempool(const CTransactionRef& tx, uint64_t mempool_sequence) override + { + m_notifications->transactionAddedToMempool(tx, mempool_sequence); + } + void TransactionRemovedFromMempool(const CTransactionRef& tx, MemPoolRemovalReason reason, uint64_t mempool_sequence) override + { + m_notifications->transactionRemovedFromMempool(tx, reason, mempool_sequence); + } + void BlockConnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* index) override + { + m_notifications->blockConnected(*block, index->nHeight); + } + void BlockDisconnected(const std::shared_ptr<const CBlock>& block, const CBlockIndex* index) override + { + m_notifications->blockDisconnected(*block, index->nHeight); + } + void UpdatedBlockTip(const CBlockIndex* index, const CBlockIndex* fork_index, bool is_ibd) override + { + m_notifications->updatedBlockTip(); + } + void ChainStateFlushed(const CBlockLocator& locator) override { m_notifications->chainStateFlushed(locator); } + std::shared_ptr<Chain::Notifications> m_notifications; +}; + +class NotificationsHandlerImpl : public Handler +{ +public: + explicit NotificationsHandlerImpl(std::shared_ptr<Chain::Notifications> notifications) + : m_proxy(std::make_shared<NotificationsProxy>(std::move(notifications))) + { + RegisterSharedValidationInterface(m_proxy); + } + ~NotificationsHandlerImpl() override { disconnect(); } + void disconnect() override + { + if (m_proxy) { + UnregisterSharedValidationInterface(m_proxy); + m_proxy.reset(); + } + } + std::shared_ptr<NotificationsProxy> m_proxy; +}; + +class RpcHandlerImpl : public Handler +{ +public: + explicit RpcHandlerImpl(const CRPCCommand& command) : m_command(command), m_wrapped_command(&command) + { + m_command.actor = [this](const JSONRPCRequest& request, UniValue& result, bool last_handler) { + if (!m_wrapped_command) return false; + try { + return m_wrapped_command->actor(request, result, last_handler); + } catch (const UniValue& e) { + // If this is not the last handler and a wallet not found + // exception was thrown, return false so the next handler can + // try to handle the request. Otherwise, reraise the exception. + if (!last_handler) { + const UniValue& code = e["code"]; + if (code.isNum() && code.get_int() == RPC_WALLET_NOT_FOUND) { + return false; + } + } + throw; + } + }; + ::tableRPC.appendCommand(m_command.name, &m_command); + } + + void disconnect() final + { + if (m_wrapped_command) { + m_wrapped_command = nullptr; + ::tableRPC.removeCommand(m_command.name, &m_command); + } + } + + ~RpcHandlerImpl() override { disconnect(); } + + CRPCCommand m_command; + const CRPCCommand* m_wrapped_command; +}; + +class ChainImpl : public Chain +{ +public: + explicit ChainImpl(NodeContext& node) : m_node(node) {} + std::optional<int> getHeight() override + { + LOCK(::cs_main); + const CChain& active = Assert(m_node.chainman)->ActiveChain(); + int height = active.Height(); + if (height >= 0) { + return height; + } + return std::nullopt; + } + uint256 getBlockHash(int height) override + { + LOCK(::cs_main); + const CChain& active = Assert(m_node.chainman)->ActiveChain(); + CBlockIndex* block = active[height]; + assert(block); + return block->GetBlockHash(); + } + bool haveBlockOnDisk(int height) override + { + LOCK(cs_main); + const CChain& active = Assert(m_node.chainman)->ActiveChain(); + CBlockIndex* block = active[height]; + return block && ((block->nStatus & BLOCK_HAVE_DATA) != 0) && block->nTx > 0; + } + CBlockLocator getTipLocator() override + { + LOCK(cs_main); + const CChain& active = Assert(m_node.chainman)->ActiveChain(); + return active.GetLocator(); + } + bool checkFinalTx(const CTransaction& tx) override + { + LOCK(cs_main); + assert(std::addressof(::ChainActive()) == std::addressof(m_node.chainman->ActiveChain())); + return CheckFinalTx(m_node.chainman->ActiveChain().Tip(), tx); + } + std::optional<int> findLocatorFork(const CBlockLocator& locator) override + { + LOCK(cs_main); + const CChain& active = Assert(m_node.chainman)->ActiveChain(); + assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); + if (CBlockIndex* fork = m_node.chainman->m_blockman.FindForkInGlobalIndex(active, locator)) { + return fork->nHeight; + } + return std::nullopt; + } + bool findBlock(const uint256& hash, const FoundBlock& block) override + { + WAIT_LOCK(cs_main, lock); + const CChain& active = Assert(m_node.chainman)->ActiveChain(); + assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); + return FillBlock(m_node.chainman->m_blockman.LookupBlockIndex(hash), block, lock, active); + } + bool findFirstBlockWithTimeAndHeight(int64_t min_time, int min_height, const FoundBlock& block) override + { + WAIT_LOCK(cs_main, lock); + const CChain& active = Assert(m_node.chainman)->ActiveChain(); + return FillBlock(active.FindEarliestAtLeast(min_time, min_height), block, lock, active); + } + bool findAncestorByHeight(const uint256& block_hash, int ancestor_height, const FoundBlock& ancestor_out) override + { + WAIT_LOCK(cs_main, lock); + const CChain& active = Assert(m_node.chainman)->ActiveChain(); + assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); + if (const CBlockIndex* block = m_node.chainman->m_blockman.LookupBlockIndex(block_hash)) { + if (const CBlockIndex* ancestor = block->GetAncestor(ancestor_height)) { + return FillBlock(ancestor, ancestor_out, lock, active); + } + } + return FillBlock(nullptr, ancestor_out, lock, active); + } + bool findAncestorByHash(const uint256& block_hash, const uint256& ancestor_hash, const FoundBlock& ancestor_out) override + { + WAIT_LOCK(cs_main, lock); + const CChain& active = Assert(m_node.chainman)->ActiveChain(); + assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); + const CBlockIndex* block = m_node.chainman->m_blockman.LookupBlockIndex(block_hash); + assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); + const CBlockIndex* ancestor = m_node.chainman->m_blockman.LookupBlockIndex(ancestor_hash); + if (block && ancestor && block->GetAncestor(ancestor->nHeight) != ancestor) ancestor = nullptr; + return FillBlock(ancestor, ancestor_out, lock, active); + } + bool findCommonAncestor(const uint256& block_hash1, const uint256& block_hash2, const FoundBlock& ancestor_out, const FoundBlock& block1_out, const FoundBlock& block2_out) override + { + WAIT_LOCK(cs_main, lock); + const CChain& active = Assert(m_node.chainman)->ActiveChain(); + assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); + const CBlockIndex* block1 = m_node.chainman->m_blockman.LookupBlockIndex(block_hash1); + assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); + const CBlockIndex* block2 = m_node.chainman->m_blockman.LookupBlockIndex(block_hash2); + const CBlockIndex* ancestor = block1 && block2 ? LastCommonAncestor(block1, block2) : nullptr; + // Using & instead of && below to avoid short circuiting and leaving + // output uninitialized. + return FillBlock(ancestor, ancestor_out, lock, active) & FillBlock(block1, block1_out, lock, active) & FillBlock(block2, block2_out, lock, active); + } + void findCoins(std::map<COutPoint, Coin>& coins) override { return FindCoins(m_node, coins); } + double guessVerificationProgress(const uint256& block_hash) override + { + LOCK(cs_main); + assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); + return GuessVerificationProgress(Params().TxData(), m_node.chainman->m_blockman.LookupBlockIndex(block_hash)); + } + bool hasBlocks(const uint256& block_hash, int min_height, std::optional<int> max_height) override + { + // hasBlocks returns true if all ancestors of block_hash in specified + // range have block data (are not pruned), false if any ancestors in + // specified range are missing data. + // + // For simplicity and robustness, min_height and max_height are only + // used to limit the range, and passing min_height that's too low or + // max_height that's too high will not crash or change the result. + LOCK(::cs_main); + assert(std::addressof(g_chainman) == std::addressof(*m_node.chainman)); + if (CBlockIndex* block = m_node.chainman->m_blockman.LookupBlockIndex(block_hash)) { + if (max_height && block->nHeight >= *max_height) block = block->GetAncestor(*max_height); + for (; block->nStatus & BLOCK_HAVE_DATA; block = block->pprev) { + // Check pprev to not segfault if min_height is too low + if (block->nHeight <= min_height || !block->pprev) return true; + } + } + return false; + } + RBFTransactionState isRBFOptIn(const CTransaction& tx) override + { + if (!m_node.mempool) return IsRBFOptInEmptyMempool(tx); + LOCK(m_node.mempool->cs); + return IsRBFOptIn(tx, *m_node.mempool); + } + bool isInMempool(const uint256& txid) override + { + if (!m_node.mempool) return false; + LOCK(m_node.mempool->cs); + return m_node.mempool->exists(txid); + } + bool hasDescendantsInMempool(const uint256& txid) override + { + if (!m_node.mempool) return false; + LOCK(m_node.mempool->cs); + auto it = m_node.mempool->GetIter(txid); + return it && (*it)->GetCountWithDescendants() > 1; + } + bool broadcastTransaction(const CTransactionRef& tx, + const CAmount& max_tx_fee, + bool relay, + std::string& err_string) override + { + const TransactionError err = BroadcastTransaction(m_node, tx, err_string, max_tx_fee, relay, /*wait_callback*/ false); + // Chain clients only care about failures to accept the tx to the mempool. Disregard non-mempool related failures. + // Note: this will need to be updated if BroadcastTransactions() is updated to return other non-mempool failures + // that Chain clients do not need to know about. + return TransactionError::OK == err; + } + void getTransactionAncestry(const uint256& txid, size_t& ancestors, size_t& descendants) override + { + ancestors = descendants = 0; + if (!m_node.mempool) return; + m_node.mempool->GetTransactionAncestry(txid, ancestors, descendants); + } + void getPackageLimits(unsigned int& limit_ancestor_count, unsigned int& limit_descendant_count) override + { + limit_ancestor_count = gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT); + limit_descendant_count = gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT); + } + bool checkChainLimits(const CTransactionRef& tx) override + { + if (!m_node.mempool) return true; + LockPoints lp; + CTxMemPoolEntry entry(tx, 0, 0, 0, false, 0, lp); + CTxMemPool::setEntries ancestors; + auto limit_ancestor_count = gArgs.GetArg("-limitancestorcount", DEFAULT_ANCESTOR_LIMIT); + auto limit_ancestor_size = gArgs.GetArg("-limitancestorsize", DEFAULT_ANCESTOR_SIZE_LIMIT) * 1000; + auto limit_descendant_count = gArgs.GetArg("-limitdescendantcount", DEFAULT_DESCENDANT_LIMIT); + auto limit_descendant_size = gArgs.GetArg("-limitdescendantsize", DEFAULT_DESCENDANT_SIZE_LIMIT) * 1000; + std::string unused_error_string; + LOCK(m_node.mempool->cs); + return m_node.mempool->CalculateMemPoolAncestors( + entry, ancestors, limit_ancestor_count, limit_ancestor_size, + limit_descendant_count, limit_descendant_size, unused_error_string); + } + CFeeRate estimateSmartFee(int num_blocks, bool conservative, FeeCalculation* calc) override + { + if (!m_node.fee_estimator) return {}; + return m_node.fee_estimator->estimateSmartFee(num_blocks, calc, conservative); + } + unsigned int estimateMaxBlocks() override + { + if (!m_node.fee_estimator) return 0; + return m_node.fee_estimator->HighestTargetTracked(FeeEstimateHorizon::LONG_HALFLIFE); + } + CFeeRate mempoolMinFee() override + { + if (!m_node.mempool) return {}; + return m_node.mempool->GetMinFee(gArgs.GetArg("-maxmempool", DEFAULT_MAX_MEMPOOL_SIZE) * 1000000); + } + CFeeRate relayMinFee() override { return ::minRelayTxFee; } + CFeeRate relayIncrementalFee() override { return ::incrementalRelayFee; } + CFeeRate relayDustFee() override { return ::dustRelayFee; } + bool havePruned() override + { + LOCK(cs_main); + return ::fHavePruned; + } + bool isReadyToBroadcast() override { return !::fImporting && !::fReindex && !isInitialBlockDownload(); } + bool isInitialBlockDownload() override { + assert(std::addressof(::ChainstateActive()) == std::addressof(m_node.chainman->ActiveChainstate())); + return m_node.chainman->ActiveChainstate().IsInitialBlockDownload(); + } + bool shutdownRequested() override { return ShutdownRequested(); } + int64_t getAdjustedTime() override { return GetAdjustedTime(); } + void initMessage(const std::string& message) override { ::uiInterface.InitMessage(message); } + void initWarning(const bilingual_str& message) override { InitWarning(message); } + void initError(const bilingual_str& message) override { InitError(message); } + void showProgress(const std::string& title, int progress, bool resume_possible) override + { + ::uiInterface.ShowProgress(title, progress, resume_possible); + } + std::unique_ptr<Handler> handleNotifications(std::shared_ptr<Notifications> notifications) override + { + return std::make_unique<NotificationsHandlerImpl>(std::move(notifications)); + } + void waitForNotificationsIfTipChanged(const uint256& old_tip) override + { + if (!old_tip.IsNull()) { + LOCK(::cs_main); + const CChain& active = Assert(m_node.chainman)->ActiveChain(); + if (old_tip == active.Tip()->GetBlockHash()) return; + } + SyncWithValidationInterfaceQueue(); + } + std::unique_ptr<Handler> handleRpc(const CRPCCommand& command) override + { + return std::make_unique<RpcHandlerImpl>(command); + } + bool rpcEnableDeprecated(const std::string& method) override { return IsDeprecatedRPCEnabled(method); } + void rpcRunLater(const std::string& name, std::function<void()> fn, int64_t seconds) override + { + RPCRunLater(name, std::move(fn), seconds); + } + int rpcSerializationFlags() override { return RPCSerializationFlags(); } + util::SettingsValue getRwSetting(const std::string& name) override + { + util::SettingsValue result; + gArgs.LockSettings([&](const util::Settings& settings) { + if (const util::SettingsValue* value = util::FindKey(settings.rw_settings, name)) { + result = *value; + } + }); + return result; + } + bool updateRwSetting(const std::string& name, const util::SettingsValue& value) override + { + gArgs.LockSettings([&](util::Settings& settings) { + if (value.isNull()) { + settings.rw_settings.erase(name); + } else { + settings.rw_settings[name] = value; + } + }); + return gArgs.WriteSettingsFile(); + } + void requestMempoolTransactions(Notifications& notifications) override + { + if (!m_node.mempool) return; + LOCK2(::cs_main, m_node.mempool->cs); + for (const CTxMemPoolEntry& entry : m_node.mempool->mapTx) { + notifications.transactionAddedToMempool(entry.GetSharedTx(), 0 /* mempool_sequence */); + } + } + NodeContext& m_node; +}; +} // namespace +} // namespace node + +namespace interfaces { +std::unique_ptr<Node> MakeNode(NodeContext* context) { return std::make_unique<node::NodeImpl>(context); } +std::unique_ptr<Chain> MakeChain(NodeContext& context) { return std::make_unique<node::ChainImpl>(context); } +} // namespace interfaces diff --git a/src/node/psbt.h b/src/node/psbt.h index 7384dc415c..def4385c09 100644 --- a/src/node/psbt.h +++ b/src/node/psbt.h @@ -7,6 +7,8 @@ #include <psbt.h> +#include <optional> + /** * Holds an analysis of one input from a PSBT */ @@ -25,18 +27,18 @@ struct PSBTInputAnalysis { * Holds the results of AnalyzePSBT (miscellaneous information about a PSBT) */ struct PSBTAnalysis { - Optional<size_t> estimated_vsize; //!< Estimated weight of the transaction - Optional<CFeeRate> estimated_feerate; //!< Estimated feerate (fee / weight) of the transaction - Optional<CAmount> fee; //!< Amount of fee being paid by the transaction - std::vector<PSBTInputAnalysis> inputs; //!< More information about the individual inputs of the transaction - PSBTRole next; //!< Which of the BIP 174 roles needs to handle the transaction next - std::string error; //!< Error message + std::optional<size_t> estimated_vsize; //!< Estimated weight of the transaction + std::optional<CFeeRate> estimated_feerate; //!< Estimated feerate (fee / weight) of the transaction + std::optional<CAmount> fee; //!< Amount of fee being paid by the transaction + std::vector<PSBTInputAnalysis> inputs; //!< More information about the individual inputs of the transaction + PSBTRole next; //!< Which of the BIP 174 roles needs to handle the transaction next + std::string error; //!< Error message void SetInvalid(std::string err_msg) { - estimated_vsize = nullopt; - estimated_feerate = nullopt; - fee = nullopt; + estimated_vsize = std::nullopt; + estimated_feerate = std::nullopt; + fee = std::nullopt; inputs.clear(); next = PSBTRole::CREATOR; error = err_msg; diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp index 3841d8687d..f47e85aceb 100644 --- a/src/node/transaction.cpp +++ b/src/node/transaction.cpp @@ -1,5 +1,5 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2019 The Bitcoin Core developers +// Copyright (c) 2009-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. @@ -13,12 +13,25 @@ #include <future> +static TransactionError HandleATMPError(const TxValidationState& state, std::string& err_string_out) +{ + err_string_out = state.ToString(); + if (state.IsInvalid()) { + if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) { + return TransactionError::MISSING_INPUTS; + } + return TransactionError::MEMPOOL_REJECTED; + } else { + return TransactionError::MEMPOOL_ERROR; + } +} + TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback) { // BroadcastTransaction can be called by either sendrawtransaction RPC or wallet RPCs. - // node.connman is assigned both before chain clients and before RPC server is accepting calls, - // and reset after chain clients and RPC sever are stopped. node.connman should never be null here. - assert(node.connman); + // node.peerman is assigned both before chain clients and before RPC server is accepting calls, + // and reset after chain clients and RPC sever are stopped. node.peerman should never be null here. + assert(node.peerman); assert(node.mempool); std::promise<void> promise; uint256 hashTx = tx->GetHash(); @@ -26,9 +39,10 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t { // cs_main scope LOCK(cs_main); + assert(std::addressof(::ChainstateActive()) == std::addressof(node.chainman->ActiveChainstate())); // If the transaction is already confirmed in the chain, don't do anything // and return early. - CCoinsViewCache &view = ::ChainstateActive().CoinsTip(); + CCoinsViewCache &view = node.chainman->ActiveChainstate().CoinsTip(); for (size_t o = 0; o < tx->vout.size(); o++) { const Coin& existingCoin = view.AccessCoin(COutPoint(hashTx, o)); // IsSpent doesn't mean the coin is spent, it means the output doesn't exist. @@ -36,20 +50,24 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t if (!existingCoin.IsSpent()) return TransactionError::ALREADY_IN_CHAIN; } if (!node.mempool->exists(hashTx)) { - // Transaction is not already in the mempool. Submit it. - TxValidationState state; - if (!AcceptToMemoryPool(*node.mempool, state, std::move(tx), - nullptr /* plTxnReplaced */, false /* bypass_limits */, max_tx_fee)) { - err_string = state.ToString(); - if (state.IsInvalid()) { - if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) { - return TransactionError::MISSING_INPUTS; - } - return TransactionError::MEMPOOL_REJECTED; - } else { - return TransactionError::MEMPOOL_ERROR; + // Transaction is not already in the mempool. + if (max_tx_fee > 0) { + // First, call ATMP with test_accept and check the fee. If ATMP + // fails here, return error immediately. + const MempoolAcceptResult result = AcceptToMemoryPool(node.chainman->ActiveChainstate(), *node.mempool, tx, false /* bypass_limits */, + true /* test_accept */); + if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) { + return HandleATMPError(result.m_state, err_string); + } else if (result.m_base_fees.value() > max_tx_fee) { + return TransactionError::MAX_FEE_EXCEEDED; } } + // Try to submit the transaction to the mempool. + const MempoolAcceptResult result = AcceptToMemoryPool(node.chainman->ActiveChainstate(), *node.mempool, tx, false /* bypass_limits */, + false /* test_accept */); + if (result.m_result_type != MempoolAcceptResult::ResultType::VALID) { + return HandleATMPError(result.m_state, err_string); + } // Transaction was accepted to the mempool. @@ -82,7 +100,8 @@ TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef t // best-effort of initial broadcast node.mempool->AddUnbroadcastTx(hashTx); - RelayTransaction(hashTx, *node.connman); + LOCK(cs_main); + node.peerman->RelayTransaction(hashTx, tx->GetWitnessHash()); } return TransactionError::OK; diff --git a/src/node/transaction.h b/src/node/transaction.h index 6491700d44..0c016ff04e 100644 --- a/src/node/transaction.h +++ b/src/node/transaction.h @@ -36,6 +36,6 @@ static const CFeeRate DEFAULT_MAX_RAW_TX_FEE_RATE{COIN / 10}; * @param[in] wait_callback wait until callbacks have been processed to avoid stale result due to a sequentially RPC. * return error */ -NODISCARD TransactionError BroadcastTransaction(NodeContext& node, CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback); +[[nodiscard]] TransactionError BroadcastTransaction(NodeContext& node, CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback); #endif // BITCOIN_NODE_TRANSACTION_H diff --git a/src/node/utxo_snapshot.h b/src/node/utxo_snapshot.h index c8b4d60fd0..fe78cb46bd 100644 --- a/src/node/utxo_snapshot.h +++ b/src/node/utxo_snapshot.h @@ -1,5 +1,5 @@ // Copyright (c) 2009-2010 Satoshi Nakamoto -// Copyright (c) 2009-2019 The Bitcoin Core developers +// Copyright (c) 2009-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. |