diff options
Diffstat (limited to 'src/node')
| -rw-r--r-- | src/node/coin.cpp | 11 | ||||
| -rw-r--r-- | src/node/coin.h | 4 | ||||
| -rw-r--r-- | src/node/coinstats.cpp | 73 | ||||
| -rw-r--r-- | src/node/coinstats.h | 34 | ||||
| -rw-r--r-- | src/node/context.cpp | 14 | ||||
| -rw-r--r-- | src/node/context.h | 49 | ||||
| -rw-r--r-- | src/node/psbt.cpp | 55 | ||||
| -rw-r--r-- | src/node/psbt.h | 11 | ||||
| -rw-r--r-- | src/node/transaction.cpp | 39 | ||||
| -rw-r--r-- | src/node/transaction.h | 20 | ||||
| -rw-r--r-- | src/node/utxo_snapshot.h | 50 |
11 files changed, 311 insertions, 49 deletions
diff --git a/src/node/coin.cpp b/src/node/coin.cpp index bb98e63f3..f4f86cdbe 100644 --- a/src/node/coin.cpp +++ b/src/node/coin.cpp @@ -4,15 +4,16 @@ #include <node/coin.h> +#include <node/context.h> #include <txmempool.h> #include <validation.h> -void FindCoins(std::map<COutPoint, Coin>& coins) +void FindCoins(const NodeContext& node, std::map<COutPoint, Coin>& coins) { - LOCK2(cs_main, ::mempool.cs); - assert(pcoinsTip); - CCoinsViewCache& chain_view = *::pcoinsTip; - CCoinsViewMemPool mempool_view(&chain_view, ::mempool); + assert(node.mempool); + LOCK2(cs_main, node.mempool->cs); + CCoinsViewCache& chain_view = ::ChainstateActive().CoinsTip(); + CCoinsViewMemPool mempool_view(&chain_view, *node.mempool); for (auto& coin : coins) { if (!mempool_view.GetCoin(coin.first, coin.second)) { // Either the coin is not in the CCoinsViewCache or is spent. Clear it. diff --git a/src/node/coin.h b/src/node/coin.h index eb95b75cf..908850e2a 100644 --- a/src/node/coin.h +++ b/src/node/coin.h @@ -9,14 +9,16 @@ class COutPoint; class Coin; +struct NodeContext; /** * Look up unspent output information. Returns coins in the mempool and in the * current chain UTXO set. Iterates through all the keys in the map and * populates the values. * + * @param[in] node The node context to use for lookup * @param[in,out] coins map to fill */ -void FindCoins(std::map<COutPoint, Coin>& coins); +void FindCoins(const NodeContext& node, std::map<COutPoint, Coin>& coins); #endif // BITCOIN_NODE_COIN_H diff --git a/src/node/coinstats.cpp b/src/node/coinstats.cpp new file mode 100644 index 000000000..ec52a08ac --- /dev/null +++ b/src/node/coinstats.cpp @@ -0,0 +1,73 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// 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. + +#include <node/coinstats.h> + +#include <coins.h> +#include <hash.h> +#include <serialize.h> +#include <validation.h> +#include <uint256.h> +#include <util/system.h> + +#include <map> + +static void ApplyStats(CCoinsStats &stats, CHashWriter& ss, const uint256& hash, const std::map<uint32_t, Coin>& outputs) +{ + 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 += 32 /* txid */ + 4 /* vout index */ + 4 /* height + coinbase */ + 8 /* amount */ + + 2 /* scriptPubKey len */ + output.second.out.scriptPubKey.size() /* scriptPubKey */; + } + ss << VARINT(0u); +} + +//! Calculate statistics about the unspent transaction output set +bool GetUTXOStats(CCoinsView *view, CCoinsStats &stats) +{ + stats = CCoinsStats(); + std::unique_ptr<CCoinsViewCursor> pcursor(view->Cursor()); + assert(pcursor); + + CHashWriter ss(SER_GETHASH, PROTOCOL_VERSION); + stats.hashBlock = pcursor->GetBestBlock(); + { + LOCK(cs_main); + stats.nHeight = LookupBlockIndex(stats.hashBlock)->nHeight; + } + ss << stats.hashBlock; + uint256 prevkey; + std::map<uint32_t, Coin> outputs; + while (pcursor->Valid()) { + COutPoint key; + Coin coin; + if (pcursor->GetKey(key) && pcursor->GetValue(coin)) { + if (!outputs.empty() && key.hash != prevkey) { + ApplyStats(stats, ss, prevkey, outputs); + outputs.clear(); + } + prevkey = key.hash; + outputs[key.n] = std::move(coin); + stats.coins_count++; + } else { + return error("%s: unable to read value", __func__); + } + pcursor->Next(); + } + if (!outputs.empty()) { + ApplyStats(stats, ss, prevkey, outputs); + } + stats.hashSerialized = ss.GetHash(); + stats.nDiskSize = view->EstimateSize(); + return true; +} diff --git a/src/node/coinstats.h b/src/node/coinstats.h new file mode 100644 index 000000000..a19af0fd1 --- /dev/null +++ b/src/node/coinstats.h @@ -0,0 +1,34 @@ +// Copyright (c) 2010 Satoshi Nakamoto +// Copyright (c) 2009-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_NODE_COINSTATS_H +#define BITCOIN_NODE_COINSTATS_H + +#include <amount.h> +#include <uint256.h> + +#include <cstdint> + +class CCoinsView; + +struct CCoinsStats +{ + int nHeight{0}; + uint256 hashBlock{}; + uint64_t nTransactions{0}; + uint64_t nTransactionOutputs{0}; + uint64_t nBogoSize{0}; + uint256 hashSerialized{}; + uint64_t nDiskSize{0}; + CAmount nTotalAmount{0}; + + //! The number of coins contained. + uint64_t coins_count{0}; +}; + +//! Calculate statistics about the unspent transaction output set +bool GetUTXOStats(CCoinsView* view, CCoinsStats& stats); + +#endif // BITCOIN_NODE_COINSTATS_H diff --git a/src/node/context.cpp b/src/node/context.cpp new file mode 100644 index 000000000..0238aab0d --- /dev/null +++ b/src/node/context.cpp @@ -0,0 +1,14 @@ +// 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 <node/context.h> + +#include <banman.h> +#include <interfaces/chain.h> +#include <net.h> +#include <net_processing.h> +#include <scheduler.h> + +NodeContext::NodeContext() {} +NodeContext::~NodeContext() {} diff --git a/src/node/context.h b/src/node/context.h new file mode 100644 index 000000000..566ff170b --- /dev/null +++ b/src/node/context.h @@ -0,0 +1,49 @@ +// 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_NODE_CONTEXT_H +#define BITCOIN_NODE_CONTEXT_H + +#include <memory> +#include <vector> + +class ArgsManager; +class BanMan; +class CConnman; +class CScheduler; +class CTxMemPool; +class PeerLogicValidation; +namespace interfaces { +class Chain; +class ChainClient; +} // namespace interfaces + +//! NodeContext struct containing references to chain state and connection +//! state. +//! +//! This is used by init, rpc, and test code to pass object references around +//! without needing to declare the same variables and parameters repeatedly, or +//! to use globals. More variables could be added to this struct (particularly +//! references to validation objects) to eliminate use of globals +//! and make code more modular and testable. The struct isn't intended to have +//! any member functions. It should just be a collection of references that can +//! 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<BanMan> banman; + ArgsManager* args{nullptr}; // Currently a raw pointer because the memory is not managed by this struct + std::unique_ptr<interfaces::Chain> chain; + std::vector<std::unique_ptr<interfaces::ChainClient>> chain_clients; + std::unique_ptr<CScheduler> scheduler; + + //! Declare default constructor and destructor that are not inline, so code + //! instantiating the NodeContext struct doesn't need to #include class + //! definitions for all the unique_ptr members. + NodeContext(); + ~NodeContext(); +}; + +#endif // BITCOIN_NODE_CONTEXT_H diff --git a/src/node/psbt.cpp b/src/node/psbt.cpp index 12559c5a5..c18901826 100644 --- a/src/node/psbt.cpp +++ b/src/node/psbt.cpp @@ -1,12 +1,14 @@ -// Copyright (c) 2009-2018 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. +#include <amount.h> #include <coins.h> #include <consensus/tx_verify.h> #include <node/psbt.h> #include <policy/policy.h> #include <policy/settings.h> +#include <tinyformat.h> #include <numeric> @@ -16,9 +18,7 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) PSBTAnalysis result; bool calc_fee = true; - bool all_final = true; - bool only_missing_sigs = true; - bool only_missing_final = false; + CAmount in_amt = 0; result.inputs.resize(psbtx.tx->vin.size()); @@ -27,22 +27,37 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) PSBTInput& input = psbtx.inputs[i]; PSBTInputAnalysis& input_analysis = result.inputs[i]; + // We set next role here and ratchet backwards as required + input_analysis.next = PSBTRole::EXTRACTOR; + // Check for a UTXO CTxOut utxo; if (psbtx.GetInputUTXO(utxo, i)) { + if (!MoneyRange(utxo.nValue) || !MoneyRange(in_amt + utxo.nValue)) { + result.SetInvalid(strprintf("PSBT is not valid. Input %u has invalid value", i)); + return result; + } in_amt += utxo.nValue; input_analysis.has_utxo = true; } else { + if (input.non_witness_utxo && psbtx.tx->vin[i].prevout.n >= input.non_witness_utxo->vout.size()) { + result.SetInvalid(strprintf("PSBT is not valid. Input %u specifies invalid prevout", i)); + return result; + } input_analysis.has_utxo = false; input_analysis.is_final = false; input_analysis.next = PSBTRole::UPDATER; calc_fee = false; } + if (!utxo.IsNull() && utxo.scriptPubKey.IsUnspendable()) { + result.SetInvalid(strprintf("PSBT is not valid. Input %u spends unspendable output", i)); + return result; + } + // Check if it is final if (!utxo.IsNull() && !PSBTInputSigned(input)) { input_analysis.is_final = false; - all_final = false; // Figure out what is missing SignatureData outdata; @@ -59,11 +74,9 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) if (outdata.missing_pubkeys.empty() && outdata.missing_redeem_script.IsNull() && outdata.missing_witness_script.IsNull() && !outdata.missing_sigs.empty()) { input_analysis.next = PSBTRole::SIGNER; } else { - only_missing_sigs = false; input_analysis.next = PSBTRole::UPDATER; } } else { - only_missing_final = true; input_analysis.next = PSBTRole::FINALIZER; } } else if (!utxo.IsNull()){ @@ -71,17 +84,28 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) } } - if (all_final) { - only_missing_sigs = false; - result.next = PSBTRole::EXTRACTOR; + // Calculate next role for PSBT by grabbing "minimum" PSBTInput next role + result.next = PSBTRole::EXTRACTOR; + for (unsigned int i = 0; i < psbtx.tx->vin.size(); ++i) { + PSBTInputAnalysis& input_analysis = result.inputs[i]; + result.next = std::min(result.next, input_analysis.next); } + assert(result.next > PSBTRole::CREATOR); + if (calc_fee) { // Get the output amount CAmount out_amt = std::accumulate(psbtx.tx->vout.begin(), psbtx.tx->vout.end(), CAmount(0), [](CAmount a, const CTxOut& b) { + if (!MoneyRange(a) || !MoneyRange(b.nValue) || !MoneyRange(a + b.nValue)) { + return CAmount(-1); + } return a += b.nValue; } ); + if (!MoneyRange(out_amt)) { + result.SetInvalid(strprintf("PSBT is not valid. Output amount invalid")); + return result; + } // Get the fee CAmount fee = in_amt - out_amt; @@ -117,17 +141,6 @@ PSBTAnalysis AnalyzePSBT(PartiallySignedTransaction psbtx) result.estimated_feerate = feerate; } - if (only_missing_sigs) { - result.next = PSBTRole::SIGNER; - } else if (only_missing_final) { - result.next = PSBTRole::FINALIZER; - } else if (all_final) { - result.next = PSBTRole::EXTRACTOR; - } else { - result.next = PSBTRole::UPDATER; - } - } else { - result.next = PSBTRole::UPDATER; } return result; diff --git a/src/node/psbt.h b/src/node/psbt.h index e04366a20..7384dc415 100644 --- a/src/node/psbt.h +++ b/src/node/psbt.h @@ -30,6 +30,17 @@ struct PSBTAnalysis { 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; + inputs.clear(); + next = PSBTRole::CREATOR; + error = err_msg; + } }; /** diff --git a/src/node/transaction.cpp b/src/node/transaction.cpp index 8e5649635..3841d8687 100644 --- a/src/node/transaction.cpp +++ b/src/node/transaction.cpp @@ -1,22 +1,25 @@ // Copyright (c) 2010 Satoshi Nakamoto -// Copyright (c) 2009-2018 The Bitcoin Core developers +// Copyright (c) 2009-2019 The Bitcoin Core developers // Distributed under the MIT software license, see the accompanying // file COPYING or http://www.opensource.org/licenses/mit-license.php. #include <consensus/validation.h> #include <net.h> #include <net_processing.h> -#include <txmempool.h> -#include <util/validation.h> +#include <node/context.h> #include <validation.h> #include <validationinterface.h> #include <node/transaction.h> #include <future> -TransactionError BroadcastTransaction(const CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback) +TransactionError BroadcastTransaction(NodeContext& node, const CTransactionRef tx, std::string& err_string, const CAmount& max_tx_fee, bool relay, bool wait_callback) { - assert(g_connman); + // 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); + assert(node.mempool); std::promise<void> promise; uint256 hashTx = tx->GetHash(); bool callback_set = false; @@ -25,27 +28,25 @@ TransactionError BroadcastTransaction(const CTransactionRef tx, std::string& err LOCK(cs_main); // If the transaction is already confirmed in the chain, don't do anything // and return early. - CCoinsViewCache &view = *pcoinsTip; + CCoinsViewCache &view = ::ChainstateActive().CoinsTip(); for (size_t o = 0; o < tx->vout.size(); o++) { const Coin& existingCoin = view.AccessCoin(COutPoint(hashTx, o)); - // IsSpent doesnt mean the coin is spent, it means the output doesnt' exist. + // IsSpent doesn't mean the coin is spent, it means the output doesn't exist. // So if the output does exist, then this transaction exists in the chain. if (!existingCoin.IsSpent()) return TransactionError::ALREADY_IN_CHAIN; } - if (!mempool.exists(hashTx)) { + if (!node.mempool->exists(hashTx)) { // Transaction is not already in the mempool. Submit it. - CValidationState state; - bool fMissingInputs; - if (!AcceptToMemoryPool(mempool, state, std::move(tx), &fMissingInputs, + 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()) { - err_string = FormatStateMessage(state); - return TransactionError::MEMPOOL_REJECTED; - } else { - if (fMissingInputs) { + if (state.GetResult() == TxValidationResult::TX_MISSING_INPUTS) { return TransactionError::MISSING_INPUTS; } - err_string = FormatStateMessage(state); + return TransactionError::MEMPOOL_REJECTED; + } else { return TransactionError::MEMPOOL_ERROR; } } @@ -77,7 +78,11 @@ TransactionError BroadcastTransaction(const CTransactionRef tx, std::string& err } if (relay) { - RelayTransaction(hashTx, *g_connman); + // the mempool tracks locally submitted transactions to make a + // best-effort of initial broadcast + node.mempool->AddUnbroadcastTx(hashTx); + + RelayTransaction(hashTx, *node.connman); } return TransactionError::OK; diff --git a/src/node/transaction.h b/src/node/transaction.h index cf64fc28d..6491700d4 100644 --- a/src/node/transaction.h +++ b/src/node/transaction.h @@ -1,4 +1,4 @@ -// Copyright (c) 2017-2019 The Bitcoin Core developers +// Copyright (c) 2017-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. @@ -6,10 +6,19 @@ #define BITCOIN_NODE_TRANSACTION_H #include <attributes.h> +#include <policy/feerate.h> #include <primitives/transaction.h> -#include <uint256.h> #include <util/error.h> +struct NodeContext; + +/** Maximum fee rate for sendrawtransaction and testmempoolaccept RPC calls. + * Also used by the GUI when broadcasting a completed PSBT. + * By default, a transaction with a fee rate higher than this will be rejected + * by these RPCs and the GUI. This can be overridden with the maxfeerate argument. + */ +static const CFeeRate DEFAULT_MAX_RAW_TX_FEE_RATE{COIN / 10}; + /** * Submit a transaction to the mempool and (optionally) relay it to all P2P peers. * @@ -19,13 +28,14 @@ * NOT be set while cs_main, cs_mempool or cs_wallet are held to avoid * deadlock. * + * @param[in] node reference to node context * @param[in] tx the transaction to broadcast - * @param[out] &err_string reference to std::string to fill with error string if available + * @param[out] err_string reference to std::string to fill with error string if available * @param[in] max_tx_fee reject txs with fees higher than this (if 0, accept any fee) * @param[in] relay flag if both mempool insertion and p2p relay are requested - * @param[in] wait_callback, wait until callbacks have been processed to avoid stale result due to a sequentially RPC. + * @param[in] wait_callback wait until callbacks have been processed to avoid stale result due to a sequentially RPC. * return error */ -NODISCARD TransactionError BroadcastTransaction(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 new file mode 100644 index 000000000..702a0cbe5 --- /dev/null +++ b/src/node/utxo_snapshot.h @@ -0,0 +1,50 @@ +// Copyright (c) 2009-2010 Satoshi Nakamoto +// Copyright (c) 2009-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_NODE_UTXO_SNAPSHOT_H +#define BITCOIN_NODE_UTXO_SNAPSHOT_H + +#include <uint256.h> +#include <serialize.h> + +//! Metadata describing a serialized version of a UTXO set from which an +//! assumeutxo CChainState can be constructed. +class SnapshotMetadata +{ +public: + //! The hash of the block that reflects the tip of the chain for the + //! UTXO set contained in this snapshot. + uint256 m_base_blockhash; + + //! The number of coins in the UTXO set contained in this snapshot. Used + //! during snapshot load to estimate progress of UTXO set reconstruction. + uint64_t m_coins_count = 0; + + //! Necessary to "fake" the base nChainTx so that we can estimate progress during + //! initial block download for the assumeutxo chainstate. + unsigned int m_nchaintx = 0; + + SnapshotMetadata() { } + SnapshotMetadata( + const uint256& base_blockhash, + uint64_t coins_count, + unsigned int nchaintx) : + m_base_blockhash(base_blockhash), + m_coins_count(coins_count), + m_nchaintx(nchaintx) { } + + ADD_SERIALIZE_METHODS; + + template <typename Stream, typename Operation> + inline void SerializationOp(Stream& s, Operation ser_action) + { + READWRITE(m_base_blockhash); + READWRITE(m_coins_count); + READWRITE(m_nchaintx); + } + +}; + +#endif // BITCOIN_NODE_UTXO_SNAPSHOT_H |