diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/bench/block_assemble.cpp | 2 | ||||
-rw-r--r-- | src/bench/gcs_filter.cpp | 4 | ||||
-rw-r--r-- | src/blockfilter.cpp | 62 | ||||
-rw-r--r-- | src/blockfilter.h | 54 | ||||
-rw-r--r-- | src/miner.cpp | 4 | ||||
-rw-r--r-- | src/miner.h | 2 | ||||
-rw-r--r-- | src/policy/fees.cpp | 17 | ||||
-rw-r--r-- | src/policy/fees.h | 40 | ||||
-rw-r--r-- | src/qt/macnotificationhandler.mm | 23 | ||||
-rw-r--r-- | src/rpc/mining.cpp | 25 | ||||
-rw-r--r-- | src/rpc/server.cpp | 8 | ||||
-rw-r--r-- | src/rpc/util.cpp | 16 | ||||
-rw-r--r-- | src/test/blockfilter_tests.cpp | 26 | ||||
-rw-r--r-- | src/test/test_bitcoin.cpp | 4 | ||||
-rw-r--r-- | src/test/test_bitcoin.h | 9 | ||||
-rw-r--r-- | src/test/validation_block_tests.cpp | 13 | ||||
-rw-r--r-- | src/validation.cpp | 26 | ||||
-rw-r--r-- | src/wallet/rpcdump.cpp | 379 | ||||
-rw-r--r-- | src/wallet/rpcwallet.cpp | 39 | ||||
-rw-r--r-- | src/wallet/test/wallet_tests.cpp | 55 | ||||
-rw-r--r-- | src/wallet/wallet.cpp | 1 |
21 files changed, 488 insertions, 321 deletions
diff --git a/src/bench/block_assemble.cpp b/src/bench/block_assemble.cpp index 2def0b23e2..7cf27f9f5b 100644 --- a/src/bench/block_assemble.cpp +++ b/src/bench/block_assemble.cpp @@ -27,7 +27,7 @@ static std::shared_ptr<CBlock> PrepareBlock(const CScript& coinbase_scriptPubKey { auto block = std::make_shared<CBlock>( BlockAssembler{Params()} - .CreateNewBlock(coinbase_scriptPubKey, /* fMineWitnessTx */ true) + .CreateNewBlock(coinbase_scriptPubKey) ->block); block->nTime = ::chainActive.Tip()->GetMedianTimePast() + 1; diff --git a/src/bench/gcs_filter.cpp b/src/bench/gcs_filter.cpp index 6f4e384e3b..535ad35571 100644 --- a/src/bench/gcs_filter.cpp +++ b/src/bench/gcs_filter.cpp @@ -17,7 +17,7 @@ static void ConstructGCSFilter(benchmark::State& state) uint64_t siphash_k0 = 0; while (state.KeepRunning()) { - GCSFilter filter(siphash_k0, 0, 20, 1 << 20, elements); + GCSFilter filter({siphash_k0, 0, 20, 1 << 20}, elements); siphash_k0++; } @@ -32,7 +32,7 @@ static void MatchGCSFilter(benchmark::State& state) element[1] = static_cast<unsigned char>(i >> 8); elements.insert(std::move(element)); } - GCSFilter filter(0, 0, 20, 1 << 20, elements); + GCSFilter filter({0, 0, 20, 1 << 20}, elements); while (state.KeepRunning()) { filter.Match(GCSFilter::Element()); diff --git a/src/blockfilter.cpp b/src/blockfilter.cpp index 163e2a52ef..bcf24047ff 100644 --- a/src/blockfilter.cpp +++ b/src/blockfilter.cpp @@ -79,7 +79,7 @@ static uint64_t MapIntoRange(uint64_t x, uint64_t n) uint64_t GCSFilter::HashToRange(const Element& element) const { - uint64_t hash = CSipHasher(m_siphash_k0, m_siphash_k1) + uint64_t hash = CSipHasher(m_params.m_siphash_k0, m_params.m_siphash_k1) .Write(element.data(), element.size()) .Finalize(); return MapIntoRange(hash, m_F); @@ -96,16 +96,13 @@ std::vector<uint64_t> GCSFilter::BuildHashedSet(const ElementSet& elements) cons return hashed_elements; } -GCSFilter::GCSFilter(uint64_t siphash_k0, uint64_t siphash_k1, uint8_t P, uint32_t M) - : m_siphash_k0(siphash_k0), m_siphash_k1(siphash_k1), m_P(P), m_M(M), m_N(0), m_F(0) +GCSFilter::GCSFilter(const Params& params) + : m_params(params), m_N(0), m_F(0), m_encoded{0} {} -GCSFilter::GCSFilter(uint64_t siphash_k0, uint64_t siphash_k1, uint8_t P, uint32_t M, - std::vector<unsigned char> encoded_filter) - : GCSFilter(siphash_k0, siphash_k1, P, M) +GCSFilter::GCSFilter(const Params& params, std::vector<unsigned char> encoded_filter) + : m_params(params), m_encoded(std::move(encoded_filter)) { - m_encoded = std::move(encoded_filter); - VectorReader stream(GCS_SER_TYPE, GCS_SER_VERSION, m_encoded, 0); uint64_t N = ReadCompactSize(stream); @@ -113,29 +110,28 @@ GCSFilter::GCSFilter(uint64_t siphash_k0, uint64_t siphash_k1, uint8_t P, uint32 if (m_N != N) { throw std::ios_base::failure("N must be <2^32"); } - m_F = static_cast<uint64_t>(m_N) * static_cast<uint64_t>(m_M); + m_F = static_cast<uint64_t>(m_N) * static_cast<uint64_t>(m_params.m_M); // Verify that the encoded filter contains exactly N elements. If it has too much or too little // data, a std::ios_base::failure exception will be raised. BitStreamReader<VectorReader> bitreader(stream); for (uint64_t i = 0; i < m_N; ++i) { - GolombRiceDecode(bitreader, m_P); + GolombRiceDecode(bitreader, m_params.m_P); } if (!stream.empty()) { throw std::ios_base::failure("encoded_filter contains excess data"); } } -GCSFilter::GCSFilter(uint64_t siphash_k0, uint64_t siphash_k1, uint8_t P, uint32_t M, - const ElementSet& elements) - : GCSFilter(siphash_k0, siphash_k1, P, M) +GCSFilter::GCSFilter(const Params& params, const ElementSet& elements) + : m_params(params) { size_t N = elements.size(); m_N = static_cast<uint32_t>(N); if (m_N != N) { throw std::invalid_argument("N must be <2^32"); } - m_F = static_cast<uint64_t>(m_N) * static_cast<uint64_t>(m_M); + m_F = static_cast<uint64_t>(m_N) * static_cast<uint64_t>(m_params.m_M); CVectorWriter stream(GCS_SER_TYPE, GCS_SER_VERSION, m_encoded, 0); @@ -150,7 +146,7 @@ GCSFilter::GCSFilter(uint64_t siphash_k0, uint64_t siphash_k1, uint8_t P, uint32 uint64_t last_value = 0; for (uint64_t value : BuildHashedSet(elements)) { uint64_t delta = value - last_value; - GolombRiceEncode(bitwriter, m_P, delta); + GolombRiceEncode(bitwriter, m_params.m_P, delta); last_value = value; } @@ -170,7 +166,7 @@ bool GCSFilter::MatchInternal(const uint64_t* element_hashes, size_t size) const uint64_t value = 0; size_t hashes_index = 0; for (uint32_t i = 0; i < m_N; ++i) { - uint64_t delta = GolombRiceDecode(bitreader, m_P); + uint64_t delta = GolombRiceDecode(bitreader, m_params.m_P); value += delta; while (true) { @@ -225,19 +221,39 @@ static GCSFilter::ElementSet BasicFilterElements(const CBlock& block, return elements; } +BlockFilter::BlockFilter(BlockFilterType filter_type, const uint256& block_hash, + std::vector<unsigned char> filter) + : m_filter_type(filter_type), m_block_hash(block_hash) +{ + GCSFilter::Params params; + if (!BuildParams(params)) { + throw std::invalid_argument("unknown filter_type"); + } + m_filter = GCSFilter(params, std::move(filter)); +} + BlockFilter::BlockFilter(BlockFilterType filter_type, const CBlock& block, const CBlockUndo& block_undo) : m_filter_type(filter_type), m_block_hash(block.GetHash()) { + GCSFilter::Params params; + if (!BuildParams(params)) { + throw std::invalid_argument("unknown filter_type"); + } + m_filter = GCSFilter(params, BasicFilterElements(block, block_undo)); +} + +bool BlockFilter::BuildParams(GCSFilter::Params& params) const +{ switch (m_filter_type) { case BlockFilterType::BASIC: - m_filter = GCSFilter(m_block_hash.GetUint64(0), m_block_hash.GetUint64(1), - BASIC_FILTER_P, BASIC_FILTER_M, - BasicFilterElements(block, block_undo)); - break; - - default: - throw std::invalid_argument("unknown filter_type"); + params.m_siphash_k0 = m_block_hash.GetUint64(0); + params.m_siphash_k1 = m_block_hash.GetUint64(1); + params.m_P = BASIC_FILTER_P; + params.m_M = BASIC_FILTER_M; + return true; } + + return false; } uint256 BlockFilter::GetHash() const diff --git a/src/blockfilter.h b/src/blockfilter.h index 871be11769..4d1f51dd60 100644 --- a/src/blockfilter.h +++ b/src/blockfilter.h @@ -25,11 +25,20 @@ public: typedef std::vector<unsigned char> Element; typedef std::unordered_set<Element, ByteVectorHash> ElementSet; + struct Params + { + uint64_t m_siphash_k0; + uint64_t m_siphash_k1; + uint8_t m_P; //!< Golomb-Rice coding parameter + uint32_t m_M; //!< Inverse false positive rate + + Params(uint64_t siphash_k0 = 0, uint64_t siphash_k1 = 0, uint8_t P = 0, uint32_t M = 1) + : m_siphash_k0(siphash_k0), m_siphash_k1(siphash_k1), m_P(P), m_M(M) + {} + }; + private: - uint64_t m_siphash_k0; - uint64_t m_siphash_k1; - uint8_t m_P; //!< Golomb-Rice coding parameter - uint32_t m_M; //!< Inverse false positive rate + Params m_params; uint32_t m_N; //!< Number of elements in the filter uint64_t m_F; //!< Range of element hashes, F = N * M std::vector<unsigned char> m_encoded; @@ -45,19 +54,16 @@ private: public: /** Constructs an empty filter. */ - GCSFilter(uint64_t siphash_k0 = 0, uint64_t siphash_k1 = 0, uint8_t P = 0, uint32_t M = 0); + explicit GCSFilter(const Params& params = Params()); /** Reconstructs an already-created filter from an encoding. */ - GCSFilter(uint64_t siphash_k0, uint64_t siphash_k1, uint8_t P, uint32_t M, - std::vector<unsigned char> encoded_filter); + GCSFilter(const Params& params, std::vector<unsigned char> encoded_filter); /** Builds a new filter from the params and set of elements. */ - GCSFilter(uint64_t siphash_k0, uint64_t siphash_k1, uint8_t P, uint32_t M, - const ElementSet& elements); + GCSFilter(const Params& params, const ElementSet& elements); - uint8_t GetP() const { return m_P; } uint32_t GetN() const { return m_N; } - uint32_t GetM() const { return m_M; } + const Params& GetParams() const { return m_params; } const std::vector<unsigned char>& GetEncoded() const { return m_encoded; } /** @@ -93,13 +99,21 @@ private: uint256 m_block_hash; GCSFilter m_filter; + bool BuildParams(GCSFilter::Params& params) const; + public: - // Construct a new BlockFilter of the specified type from a block. + BlockFilter() = default; + + //! Reconstruct a BlockFilter from parts. + BlockFilter(BlockFilterType filter_type, const uint256& block_hash, + std::vector<unsigned char> filter); + + //! Construct a new BlockFilter of the specified type from a block. BlockFilter(BlockFilterType filter_type, const CBlock& block, const CBlockUndo& block_undo); BlockFilterType GetFilterType() const { return m_filter_type; } - + const uint256& GetBlockHash() const { return m_block_hash; } const GCSFilter& GetFilter() const { return m_filter; } const std::vector<unsigned char>& GetEncodedFilter() const @@ -107,10 +121,10 @@ public: return m_filter.GetEncoded(); } - // Compute the filter hash. + //! Compute the filter hash. uint256 GetHash() const; - // Compute the filter header given the previous one. + //! Compute the filter header given the previous one. uint256 ComputeHeader(const uint256& prev_header) const; template <typename Stream> @@ -131,15 +145,11 @@ public: m_filter_type = static_cast<BlockFilterType>(filter_type); - switch (m_filter_type) { - case BlockFilterType::BASIC: - m_filter = GCSFilter(m_block_hash.GetUint64(0), m_block_hash.GetUint64(1), - BASIC_FILTER_P, BASIC_FILTER_M, std::move(encoded_filter)); - break; - - default: + GCSFilter::Params params; + if (!BuildParams(params)) { throw std::ios_base::failure("unknown filter_type"); } + m_filter = GCSFilter(params, std::move(encoded_filter)); } }; diff --git a/src/miner.cpp b/src/miner.cpp index 96c9cd6d2a..ef48a86e32 100644 --- a/src/miner.cpp +++ b/src/miner.cpp @@ -95,7 +95,7 @@ void BlockAssembler::resetBlock() nFees = 0; } -std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn, bool fMineWitnessTx) +std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& scriptPubKeyIn) { int64_t nTimeStart = GetTimeMicros(); @@ -139,7 +139,7 @@ std::unique_ptr<CBlockTemplate> BlockAssembler::CreateNewBlock(const CScript& sc // not activated. // TODO: replace this with a call to main to assess validity of a mempool // transaction (which in most cases can be a no-op). - fIncludeWitness = IsWitnessEnabled(pindexPrev, chainparams.GetConsensus()) && fMineWitnessTx; + fIncludeWitness = IsWitnessEnabled(pindexPrev, chainparams.GetConsensus()); int nPackagesSelected = 0; int nDescendantsUpdated = 0; diff --git a/src/miner.h b/src/miner.h index 8cdcf7133b..44c50b01ad 100644 --- a/src/miner.h +++ b/src/miner.h @@ -157,7 +157,7 @@ public: BlockAssembler(const CChainParams& params, const Options& options); /** Construct a new block template with coinbase to scriptPubKeyIn */ - std::unique_ptr<CBlockTemplate> CreateNewBlock(const CScript& scriptPubKeyIn, bool fMineWitnessTx=true); + std::unique_ptr<CBlockTemplate> CreateNewBlock(const CScript& scriptPubKeyIn); private: // utility functions diff --git a/src/policy/fees.cpp b/src/policy/fees.cpp index 3afe6fe1b7..c49b9fa36b 100644 --- a/src/policy/fees.cpp +++ b/src/policy/fees.cpp @@ -511,7 +511,7 @@ void TxConfirmStats::removeTx(unsigned int entryHeight, unsigned int nBestSeenHe // of no harm to try to remove them again. bool CBlockPolicyEstimator::removeTx(uint256 hash, bool inBlock) { - LOCK(cs_feeEstimator); + LOCK(m_cs_fee_estimator); std::map<uint256, TxStatsInfo>::iterator pos = mapMemPoolTxs.find(hash); if (pos != mapMemPoolTxs.end()) { feeStats->removeTx(pos->second.blockHeight, nBestSeenHeight, pos->second.bucketIndex, inBlock); @@ -548,7 +548,7 @@ CBlockPolicyEstimator::~CBlockPolicyEstimator() void CBlockPolicyEstimator::processTransaction(const CTxMemPoolEntry& entry, bool validFeeEstimate) { - LOCK(cs_feeEstimator); + LOCK(m_cs_fee_estimator); unsigned int txHeight = entry.GetHeight(); uint256 hash = entry.GetTx().GetHash(); if (mapMemPoolTxs.count(hash)) { @@ -615,7 +615,7 @@ bool CBlockPolicyEstimator::processBlockTx(unsigned int nBlockHeight, const CTxM void CBlockPolicyEstimator::processBlock(unsigned int nBlockHeight, std::vector<const CTxMemPoolEntry*>& entries) { - LOCK(cs_feeEstimator); + LOCK(m_cs_fee_estimator); if (nBlockHeight <= nBestSeenHeight) { // Ignore side chains and re-orgs; assuming they are random // they don't affect the estimate. @@ -693,7 +693,7 @@ CFeeRate CBlockPolicyEstimator::estimateRawFee(int confTarget, double successThr } } - LOCK(cs_feeEstimator); + LOCK(m_cs_fee_estimator); // Return failure if trying to analyze a target we're not tracking if (confTarget <= 0 || (unsigned int)confTarget > stats->GetMaxConfirms()) return CFeeRate(0); @@ -710,6 +710,7 @@ CFeeRate CBlockPolicyEstimator::estimateRawFee(int confTarget, double successThr unsigned int CBlockPolicyEstimator::HighestTargetTracked(FeeEstimateHorizon horizon) const { + LOCK(m_cs_fee_estimator); switch (horizon) { case FeeEstimateHorizon::SHORT_HALFLIFE: { return shortStats->GetMaxConfirms(); @@ -819,7 +820,7 @@ double CBlockPolicyEstimator::estimateConservativeFee(unsigned int doubleTarget, */ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, FeeCalculation *feeCalc, bool conservative) const { - LOCK(cs_feeEstimator); + LOCK(m_cs_fee_estimator); if (feeCalc) { feeCalc->desiredTarget = confTarget; @@ -899,7 +900,7 @@ CFeeRate CBlockPolicyEstimator::estimateSmartFee(int confTarget, FeeCalculation bool CBlockPolicyEstimator::Write(CAutoFile& fileout) const { try { - LOCK(cs_feeEstimator); + LOCK(m_cs_fee_estimator); fileout << 149900; // version required to read: 0.14.99 or later fileout << CLIENT_VERSION; // version that wrote the file fileout << nBestSeenHeight; @@ -924,7 +925,7 @@ bool CBlockPolicyEstimator::Write(CAutoFile& fileout) const bool CBlockPolicyEstimator::Read(CAutoFile& filein) { try { - LOCK(cs_feeEstimator); + LOCK(m_cs_fee_estimator); int nVersionRequired, nVersionThatWrote; filein >> nVersionRequired >> nVersionThatWrote; if (nVersionRequired > CLIENT_VERSION) @@ -983,7 +984,7 @@ bool CBlockPolicyEstimator::Read(CAutoFile& filein) void CBlockPolicyEstimator::FlushUnconfirmed() { int64_t startclear = GetTimeMicros(); - LOCK(cs_feeEstimator); + LOCK(m_cs_fee_estimator); size_t num_entries = mapMemPoolTxs.size(); // Remove every entry in mapMemPoolTxs while (!mapMemPoolTxs.empty()) { diff --git a/src/policy/fees.h b/src/policy/fees.h index 90f159b48c..c8472a12f5 100644 --- a/src/policy/fees.h +++ b/src/policy/fees.h @@ -228,10 +228,12 @@ public: unsigned int HighestTargetTracked(FeeEstimateHorizon horizon) const; private: - unsigned int nBestSeenHeight; - unsigned int firstRecordedHeight; - unsigned int historicalFirst; - unsigned int historicalBest; + mutable CCriticalSection m_cs_fee_estimator; + + unsigned int nBestSeenHeight GUARDED_BY(m_cs_fee_estimator); + unsigned int firstRecordedHeight GUARDED_BY(m_cs_fee_estimator); + unsigned int historicalFirst GUARDED_BY(m_cs_fee_estimator); + unsigned int historicalBest GUARDED_BY(m_cs_fee_estimator); struct TxStatsInfo { @@ -241,34 +243,32 @@ private: }; // map of txids to information about that transaction - std::map<uint256, TxStatsInfo> mapMemPoolTxs; + std::map<uint256, TxStatsInfo> mapMemPoolTxs GUARDED_BY(m_cs_fee_estimator); /** Classes to track historical data on transaction confirmations */ - std::unique_ptr<TxConfirmStats> feeStats; - std::unique_ptr<TxConfirmStats> shortStats; - std::unique_ptr<TxConfirmStats> longStats; - - unsigned int trackedTxs; - unsigned int untrackedTxs; + std::unique_ptr<TxConfirmStats> feeStats PT_GUARDED_BY(m_cs_fee_estimator); + std::unique_ptr<TxConfirmStats> shortStats PT_GUARDED_BY(m_cs_fee_estimator); + std::unique_ptr<TxConfirmStats> longStats PT_GUARDED_BY(m_cs_fee_estimator); - std::vector<double> buckets; // The upper-bound of the range for the bucket (inclusive) - std::map<double, unsigned int> bucketMap; // Map of bucket upper-bound to index into all vectors by bucket + unsigned int trackedTxs GUARDED_BY(m_cs_fee_estimator); + unsigned int untrackedTxs GUARDED_BY(m_cs_fee_estimator); - mutable CCriticalSection cs_feeEstimator; + std::vector<double> buckets GUARDED_BY(m_cs_fee_estimator); // The upper-bound of the range for the bucket (inclusive) + std::map<double, unsigned int> bucketMap GUARDED_BY(m_cs_fee_estimator); // Map of bucket upper-bound to index into all vectors by bucket /** Process a transaction confirmed in a block*/ - bool processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry* entry); + bool processBlockTx(unsigned int nBlockHeight, const CTxMemPoolEntry* entry) EXCLUSIVE_LOCKS_REQUIRED(m_cs_fee_estimator); /** Helper for estimateSmartFee */ - double estimateCombinedFee(unsigned int confTarget, double successThreshold, bool checkShorterHorizon, EstimationResult *result) const; + double estimateCombinedFee(unsigned int confTarget, double successThreshold, bool checkShorterHorizon, EstimationResult *result) const EXCLUSIVE_LOCKS_REQUIRED(m_cs_fee_estimator); /** Helper for estimateSmartFee */ - double estimateConservativeFee(unsigned int doubleTarget, EstimationResult *result) const; + double estimateConservativeFee(unsigned int doubleTarget, EstimationResult *result) const EXCLUSIVE_LOCKS_REQUIRED(m_cs_fee_estimator); /** Number of blocks of data recorded while fee estimates have been running */ - unsigned int BlockSpan() const; + unsigned int BlockSpan() const EXCLUSIVE_LOCKS_REQUIRED(m_cs_fee_estimator); /** Number of blocks of recorded fee estimate data represented in saved data file */ - unsigned int HistoricalBlockSpan() const; + unsigned int HistoricalBlockSpan() const EXCLUSIVE_LOCKS_REQUIRED(m_cs_fee_estimator); /** Calculation of highest target that reasonable estimate can be provided for */ - unsigned int MaxUsableEstimate() const; + unsigned int MaxUsableEstimate() const EXCLUSIVE_LOCKS_REQUIRED(m_cs_fee_estimator); }; class FeeFilterRounder diff --git a/src/qt/macnotificationhandler.mm b/src/qt/macnotificationhandler.mm index 0e04d50baa..a07079eece 100644 --- a/src/qt/macnotificationhandler.mm +++ b/src/qt/macnotificationhandler.mm @@ -24,25 +24,10 @@ void MacNotificationHandler::showNotification(const QString &title, const QStrin { // check if users OS has support for NSUserNotification if(this->hasUserNotificationCenterSupport()) { - // okay, seems like 10.8+ - QByteArray utf8 = title.toUtf8(); - char* cString = (char *)utf8.constData(); - NSString *titleMac = [[NSString alloc] initWithUTF8String:cString]; - - utf8 = text.toUtf8(); - cString = (char *)utf8.constData(); - NSString *textMac = [[NSString alloc] initWithUTF8String:cString]; - - // do everything weak linked (because we will keep <10.8 compatibility) - id userNotification = [[NSClassFromString(@"NSUserNotification") alloc] init]; - [userNotification performSelector:@selector(setTitle:) withObject:titleMac]; - [userNotification performSelector:@selector(setInformativeText:) withObject:textMac]; - - id notificationCenterInstance = [NSClassFromString(@"NSUserNotificationCenter") performSelector:@selector(defaultUserNotificationCenter)]; - [notificationCenterInstance performSelector:@selector(deliverNotification:) withObject:userNotification]; - - [titleMac release]; - [textMac release]; + NSUserNotification* userNotification = [[NSUserNotification alloc] init]; + userNotification.title = title.toNSString(); + userNotification.informativeText = text.toNSString(); + [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification: userNotification]; [userNotification release]; } } diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index 93fa3a2728..2fd6f99be5 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -241,7 +241,7 @@ static UniValue prioritisetransaction(const JSONRPCRequest& request) "Accepts the transaction into mined blocks at a higher (or lower) priority\n", { {"txid", RPCArg::Type::STR_HEX, /* opt */ false, /* default_val */ "", "The transaction id."}, - {"dummy", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "API-Compatibility for previous API. Must be zero or null.\n" + {"dummy", RPCArg::Type::NUM, /* opt */ true, /* default_val */ "null", "API-Compatibility for previous API. Must be zero or null.\n" " DEPRECATED. For forward compatibility use named arguments and omit this parameter."}, {"fee_delta", RPCArg::Type::NUM, /* opt */ false, /* default_val */ "", "The fee value (in satoshis) to add (or subtract, if negative).\n" " Note, that this value is not a fee rate. It is a value to modify absolute fee of the TX.\n" @@ -311,7 +311,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) " https://github.com/bitcoin/bips/blob/master/bip-0009.mediawiki#getblocktemplate_changes\n" " https://github.com/bitcoin/bips/blob/master/bip-0145.mediawiki\n", { - {"template_request", RPCArg::Type::OBJ, /* opt */ true, /* default_val */ "", "A json object in the following spec", + {"template_request", RPCArg::Type::OBJ, /* opt */ false, /* default_val */ "", "A json object in the following spec", { {"mode", RPCArg::Type::STR, /* opt */ true, /* default_val */ "", "This must be set to \"template\", \"proposal\" (see BIP 23), or omitted"}, {"capabilities", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "", "A list of strings", @@ -319,7 +319,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) {"support", RPCArg::Type::STR, /* opt */ true, /* default_val */ "", "client side supported feature, 'longpoll', 'coinbasetxn', 'coinbasevalue', 'proposal', 'serverlist', 'workid'"}, }, }, - {"rules", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "", "A list of strings", + {"rules", RPCArg::Type::ARR, /* opt */ false, /* default_val */ "", "A list of strings", { {"support", RPCArg::Type::STR, /* opt */ true, /* default_val */ "", "client side supported softfork deployment"}, }, @@ -503,21 +503,17 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) } const struct VBDeploymentInfo& segwit_info = VersionBitsDeploymentInfo[Consensus::DEPLOYMENT_SEGWIT]; - // If the caller is indicating segwit support, then allow CreateNewBlock() - // to select witness transactions, after segwit activates (otherwise - // don't). - bool fSupportsSegwit = setClientRules.find(segwit_info.name) != setClientRules.end(); + // GBT must be called with 'segwit' set in the rules + if (setClientRules.count(segwit_info.name) != 1) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "getblocktemplate must be called with the segwit rule set (call with {\"rules\": [\"segwit\"]})"); + } // Update block static CBlockIndex* pindexPrev; static int64_t nStart; static std::unique_ptr<CBlockTemplate> pblocktemplate; - // Cache whether the last invocation was with segwit support, to avoid returning - // a segwit-block to a non-segwit caller. - static bool fLastTemplateSupportsSegwit = true; if (pindexPrev != chainActive.Tip() || - (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - nStart > 5) || - fLastTemplateSupportsSegwit != fSupportsSegwit) + (mempool.GetTransactionsUpdated() != nTransactionsUpdatedLast && GetTime() - nStart > 5)) { // Clear pindexPrev so future calls make a new block, despite any failures from here on pindexPrev = nullptr; @@ -526,11 +522,10 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) nTransactionsUpdatedLast = mempool.GetTransactionsUpdated(); CBlockIndex* pindexPrevNew = chainActive.Tip(); nStart = GetTime(); - fLastTemplateSupportsSegwit = fSupportsSegwit; // Create new block CScript scriptDummy = CScript() << OP_TRUE; - pblocktemplate = BlockAssembler(Params()).CreateNewBlock(scriptDummy, fSupportsSegwit); + pblocktemplate = BlockAssembler(Params()).CreateNewBlock(scriptDummy); if (!pblocktemplate) throw JSONRPCError(RPC_OUT_OF_MEMORY, "Out of memory"); @@ -682,7 +677,7 @@ static UniValue getblocktemplate(const JSONRPCRequest& request) result.pushKV("bits", strprintf("%08x", pblock->nBits)); result.pushKV("height", (int64_t)(pindexPrev->nHeight+1)); - if (!pblocktemplate->vchCoinbaseCommitment.empty() && fSupportsSegwit) { + if (!pblocktemplate->vchCoinbaseCommitment.empty()) { result.pushKV("default_witness_commitment", HexStr(pblocktemplate->vchCoinbaseCommitment.begin(), pblocktemplate->vchCoinbaseCommitment.end())); } diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index 733f8601ee..03e1c81132 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -24,7 +24,7 @@ #include <unordered_map> static CCriticalSection cs_rpcWarmup; -static bool fRPCRunning = false; +static std::atomic<bool> g_rpc_running{false}; static bool fRPCInWarmup GUARDED_BY(cs_rpcWarmup) = true; static std::string rpcWarmupStatus GUARDED_BY(cs_rpcWarmup) = "RPC server started"; /* Timer-creating functions */ @@ -303,7 +303,7 @@ bool CRPCTable::appendCommand(const std::string& name, const CRPCCommand* pcmd) void StartRPC() { LogPrint(BCLog::RPC, "Starting RPC\n"); - fRPCRunning = true; + g_rpc_running = true; g_rpcSignals.Started(); } @@ -311,7 +311,7 @@ void InterruptRPC() { LogPrint(BCLog::RPC, "Interrupting RPC\n"); // Interrupt e.g. running longpolls - fRPCRunning = false; + g_rpc_running = false; } void StopRPC() @@ -324,7 +324,7 @@ void StopRPC() bool IsRPCRunning() { - return fRPCRunning; + return g_rpc_running; } void SetRPCWarmupStatus(const std::string& newStatus) diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 740f8351fe..b91baee4ac 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -258,20 +258,19 @@ std::string RPCHelpMan::ToString() const // Oneline summary ret += m_name; - bool is_optional{false}; + bool was_optional{false}; for (const auto& arg : m_args) { ret += " "; if (arg.m_optional) { - if (!is_optional) ret += "( "; - is_optional = true; + if (!was_optional) ret += "( "; + was_optional = true; } else { - // Currently we still support unnamed arguments, so any argument following an optional argument must also be optional - // If support for positional arguments is deprecated in the future, remove this line - assert(!is_optional); + if (was_optional) ret += ") "; + was_optional = false; } ret += arg.ToString(/* oneline */ true); } - if (is_optional) ret += " )"; + if (was_optional) ret += " )"; ret += "\n"; // Description @@ -285,8 +284,7 @@ std::string RPCHelpMan::ToString() const if (i == 0) ret += "\nArguments:\n"; // Push named argument name and description - const auto str_wrapper = (arg.m_type == RPCArg::Type::STR || arg.m_type == RPCArg::Type::STR_HEX) ? "\"" : ""; - sections.m_sections.emplace_back(std::to_string(i + 1) + ". " + str_wrapper + arg.m_name + str_wrapper, arg.ToDescriptionString()); + sections.m_sections.emplace_back(std::to_string(i + 1) + ". " + arg.m_name, arg.ToDescriptionString()); sections.m_max_pad = std::max(sections.m_max_pad, sections.m_sections.back().m_left.size()); // Recursively push nested args diff --git a/src/test/blockfilter_tests.cpp b/src/test/blockfilter_tests.cpp index 2144202b8d..625362f446 100644 --- a/src/test/blockfilter_tests.cpp +++ b/src/test/blockfilter_tests.cpp @@ -29,7 +29,7 @@ BOOST_AUTO_TEST_CASE(gcsfilter_test) excluded_elements.insert(std::move(element2)); } - GCSFilter filter(0, 0, 10, 1 << 10, included_elements); + GCSFilter filter({0, 0, 10, 1 << 10}, included_elements); for (const auto& element : included_elements) { BOOST_CHECK(filter.Match(element)); @@ -39,6 +39,19 @@ BOOST_AUTO_TEST_CASE(gcsfilter_test) } } +BOOST_AUTO_TEST_CASE(gcsfilter_default_constructor) +{ + GCSFilter filter; + BOOST_CHECK_EQUAL(filter.GetN(), 0); + BOOST_CHECK_EQUAL(filter.GetEncoded().size(), 1); + + const GCSFilter::Params& params = filter.GetParams(); + BOOST_CHECK_EQUAL(params.m_siphash_k0, 0); + BOOST_CHECK_EQUAL(params.m_siphash_k1, 0); + BOOST_CHECK_EQUAL(params.m_P, 0); + BOOST_CHECK_EQUAL(params.m_M, 1); +} + BOOST_AUTO_TEST_CASE(blockfilter_basic_test) { CScript included_scripts[5], excluded_scripts[3]; @@ -88,6 +101,17 @@ BOOST_AUTO_TEST_CASE(blockfilter_basic_test) for (const CScript& script : excluded_scripts) { BOOST_CHECK(!filter.Match(GCSFilter::Element(script.begin(), script.end()))); } + + // Test serialization/unserialization. + BlockFilter block_filter2; + + CDataStream stream(SER_NETWORK, PROTOCOL_VERSION); + stream << block_filter; + stream >> block_filter2; + + BOOST_CHECK_EQUAL(block_filter.GetFilterType(), block_filter2.GetFilterType()); + BOOST_CHECK_EQUAL(block_filter.GetBlockHash(), block_filter2.GetBlockHash()); + BOOST_CHECK(block_filter.GetEncodedFilter() == block_filter2.GetEncodedFilter()); } BOOST_AUTO_TEST_CASE(blockfilters_json_test) diff --git a/src/test/test_bitcoin.cpp b/src/test/test_bitcoin.cpp index bb8db9fa8d..a3201de385 100644 --- a/src/test/test_bitcoin.cpp +++ b/src/test/test_bitcoin.cpp @@ -22,6 +22,8 @@ const std::function<std::string(const char*)> G_TRANSLATION_FUN = nullptr; +FastRandomContext g_insecure_rand_ctx; + void CConnmanTest::AddNode(CNode& node) { LOCK(g_connman->cs_vNodes); @@ -37,8 +39,6 @@ void CConnmanTest::ClearNodes() g_connman->vNodes.clear(); } -thread_local FastRandomContext g_insecure_rand_ctx; - std::ostream& operator<<(std::ostream& os, const uint256& num) { os << num.ToString(); diff --git a/src/test/test_bitcoin.h b/src/test/test_bitcoin.h index 71c0379eac..31d90c0151 100644 --- a/src/test/test_bitcoin.h +++ b/src/test/test_bitcoin.h @@ -26,7 +26,14 @@ std::ostream& operator<<(typename std::enable_if<std::is_enum<T>::value, std::os return stream << static_cast<typename std::underlying_type<T>::type>(e); } -thread_local extern FastRandomContext g_insecure_rand_ctx; +/** + * This global and the helpers that use it are not thread-safe. + * + * If thread-safety is needed, the global could be made thread_local (given + * that thread_local is supported on all architectures we support) or a + * per-thread instance could be used in the multi-threaded test. + */ +extern FastRandomContext g_insecure_rand_ctx; static inline void SeedInsecureRand(bool deterministic = false) { diff --git a/src/test/validation_block_tests.cpp b/src/test/validation_block_tests.cpp index 7dd176b25d..44432cd0a1 100644 --- a/src/test/validation_block_tests.cpp +++ b/src/test/validation_block_tests.cpp @@ -54,7 +54,7 @@ std::shared_ptr<CBlock> Block(const uint256& prev_hash) CScript pubKey; pubKey << i++ << OP_TRUE; - auto ptemplate = BlockAssembler(Params()).CreateNewBlock(pubKey, false); + auto ptemplate = BlockAssembler(Params()).CreateNewBlock(pubKey); auto pblock = std::make_shared<CBlock>(ptemplate->block); pblock->hashPrevBlock = prev_hash; pblock->nTime = ++time; @@ -152,12 +152,13 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering) // create a bunch of threads that repeatedly process a block generated above at random // this will create parallelism and randomness inside validation - the ValidationInterface // will subscribe to events generated during block validation and assert on ordering invariance - boost::thread_group threads; + std::vector<std::thread> threads; for (int i = 0; i < 10; i++) { - threads.create_thread([&blocks]() { + threads.emplace_back([&blocks]() { bool ignored; + FastRandomContext insecure; for (int i = 0; i < 1000; i++) { - auto block = blocks[InsecureRandRange(blocks.size() - 1)]; + auto block = blocks[insecure.randrange(blocks.size() - 1)]; ProcessNewBlock(Params(), block, true, &ignored); } @@ -171,7 +172,9 @@ BOOST_AUTO_TEST_CASE(processnewblock_signals_ordering) }); } - threads.join_all(); + for (auto& t : threads) { + t.join(); + } while (GetMainSignals().CallbacksPending() > 0) { MilliSleep(100); } diff --git a/src/validation.cpp b/src/validation.cpp index 5696684ed6..f0c4aba95f 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -3374,10 +3374,30 @@ bool CChainState::AcceptBlockHeader(const CBlockHeader& block, CValidationState& if (!ContextualCheckBlockHeader(block, state, chainparams, pindexPrev, GetAdjustedTime())) return error("%s: Consensus::ContextualCheckBlockHeader: %s, %s", __func__, hash.ToString(), FormatStateMessage(state)); - // If the previous block index isn't valid, determine if it descends from any block which - // has been found invalid (m_failed_blocks), then mark pindexPrev and any blocks - // between them as failed. + /* Determine if this block descends from any block which has been found + * invalid (m_failed_blocks), then mark pindexPrev and any blocks between + * them as failed. For example: + * + * D3 + * / + * B2 - C2 + * / \ + * A D2 - E2 - F2 + * \ + * B1 - C1 - D1 - E1 + * + * In the case that we attempted to reorg from E1 to F2, only to find + * C2 to be invalid, we would mark D2, E2, and F2 as BLOCK_FAILED_CHILD + * but NOT D3 (it was not in any of our candidate sets at the time). + * + * In any case D3 will also be marked as BLOCK_FAILED_CHILD at restart + * in LoadBlockIndex. + */ if (!pindexPrev->IsValid(BLOCK_VALID_SCRIPTS)) { + // The above does not mean "invalid": it checks if the previous block + // hasn't been validated up to BLOCK_VALID_SCRIPTS. This is a performance + // optimization, in the common case of adding a new block to the tip, + // we don't need to iterate over the failed blocks list. for (const CBlockIndex* failedit : m_failed_blocks) { if (pindexPrev->GetAncestor(failedit->nHeight) == failedit) { assert(failedit->nStatus & BLOCK_FAILED_VALID); diff --git a/src/wallet/rpcdump.cpp b/src/wallet/rpcdump.cpp index 647af3eb86..ed1e2d3940 100644 --- a/src/wallet/rpcdump.cpp +++ b/src/wallet/rpcdump.cpp @@ -836,9 +836,98 @@ UniValue dumpwallet(const JSONRPCRequest& request) return reply; } +struct ImportData +{ + // Input data + std::unique_ptr<CScript> redeemscript; //!< Provided redeemScript; will be moved to `import_scripts` if relevant. + std::unique_ptr<CScript> witnessscript; //!< Provided witnessScript; will be moved to `import_scripts` if relevant. + + // Output data + std::set<CScript> import_scripts; + std::map<CKeyID, bool> used_keys; //!< Import these private keys if available (the value indicates whether if the key is required for solvability) +}; + +enum class ScriptContext +{ + TOP, //! Top-level scriptPubKey + P2SH, //! P2SH redeemScript + WITNESS_V0, //! P2WSH witnessScript +}; + +// Analyse the provided scriptPubKey, determining which keys and which redeem scripts from the ImportData struct are needed to spend it, and mark them as used. +// Returns an error string, or the empty string for success. +static std::string RecurseImportData(const CScript& script, ImportData& import_data, const ScriptContext script_ctx) +{ + // Use Solver to obtain script type and parsed pubkeys or hashes: + std::vector<std::vector<unsigned char>> solverdata; + txnouttype script_type = Solver(script, solverdata); + + switch (script_type) { + case TX_PUBKEY: { + CPubKey pubkey(solverdata[0].begin(), solverdata[0].end()); + import_data.used_keys.emplace(pubkey.GetID(), false); + return ""; + } + case TX_PUBKEYHASH: { + CKeyID id = CKeyID(uint160(solverdata[0])); + import_data.used_keys[id] = true; + return ""; + } + case TX_SCRIPTHASH: { + if (script_ctx == ScriptContext::P2SH) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2SH inside another P2SH"); + if (script_ctx == ScriptContext::WITNESS_V0) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2SH inside a P2WSH"); + assert(script_ctx == ScriptContext::TOP); + CScriptID id = CScriptID(uint160(solverdata[0])); + auto subscript = std::move(import_data.redeemscript); // Remove redeemscript from import_data to check for superfluous script later. + if (!subscript) return "missing redeemscript"; + if (CScriptID(*subscript) != id) return "redeemScript does not match the scriptPubKey"; + import_data.import_scripts.emplace(*subscript); + return RecurseImportData(*subscript, import_data, ScriptContext::P2SH); + } + case TX_MULTISIG: { + for (size_t i = 1; i + 1< solverdata.size(); ++i) { + CPubKey pubkey(solverdata[i].begin(), solverdata[i].end()); + import_data.used_keys.emplace(pubkey.GetID(), false); + } + return ""; + } + case TX_WITNESS_V0_SCRIPTHASH: { + if (script_ctx == ScriptContext::WITNESS_V0) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2WSH inside another P2WSH"); + uint256 fullid(solverdata[0]); + CScriptID id; + CRIPEMD160().Write(fullid.begin(), fullid.size()).Finalize(id.begin()); + auto subscript = std::move(import_data.witnessscript); // Remove redeemscript from import_data to check for superfluous script later. + if (!subscript) return "missing witnessscript"; + if (CScriptID(*subscript) != id) return "witnessScript does not match the scriptPubKey or redeemScript"; + if (script_ctx == ScriptContext::TOP) { + import_data.import_scripts.emplace(script); // Special rule for IsMine: native P2WSH requires the TOP script imported (see script/ismine.cpp) + } + import_data.import_scripts.emplace(*subscript); + return RecurseImportData(*subscript, import_data, ScriptContext::WITNESS_V0); + } + case TX_WITNESS_V0_KEYHASH: { + if (script_ctx == ScriptContext::WITNESS_V0) throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Trying to nest P2WPKH inside P2WSH"); + CKeyID id = CKeyID(uint160(solverdata[0])); + import_data.used_keys[id] = true; + if (script_ctx == ScriptContext::TOP) { + import_data.import_scripts.emplace(script); // Special rule for IsMine: native P2WPKH requires the TOP script imported (see script/ismine.cpp) + } + return ""; + } + case TX_NULL_DATA: + return "unspendable script"; + case TX_NONSTANDARD: + case TX_WITNESS_UNKNOWN: + default: + return "unrecognized script"; + } +} static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, const int64_t timestamp) EXCLUSIVE_LOCKS_REQUIRED(pwallet->cs_wallet) { + UniValue warnings(UniValue::VARR); + UniValue result(UniValue::VOBJ); + try { // First ensure scriptPubKey has either a script or JSON with "address" string const UniValue& scriptPubKey = data["scriptPubKey"]; @@ -860,18 +949,16 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con // Generate the script and destination for the scriptPubKey provided CScript script; CTxDestination dest; - if (!isScript) { dest = DecodeDestination(output); if (!IsValidDestination(dest)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid address \"" + output + "\""); } script = GetScriptForDestination(dest); } else { if (!IsHex(output)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid scriptPubKey"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid scriptPubKey \"" + output + "\""); } - std::vector<unsigned char> vData(ParseHex(output)); script = CScript(vData.begin(), vData.end()); if (!ExtractDestination(script, dest) && !internal) { @@ -879,203 +966,161 @@ static UniValue ProcessImport(CWallet * const pwallet, const UniValue& data, con } } - // Watchonly and private keys - if (watchOnly && keys.size()) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Watch-only addresses should not include private keys"); - } - - // Internal addresses should not have a label - if (internal && data.exists("label")) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label"); - } - - // Force users to provide the witness script in its field rather than redeemscript - if (!strRedeemScript.empty() && script.IsPayToWitnessScriptHash()) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "P2WSH addresses have an empty redeemscript. Please provide the witnessscript instead."); - } - - CScript scriptpubkey_script = script; - CTxDestination scriptpubkey_dest = dest; - bool allow_p2wpkh = true; - - // P2SH - if (!strRedeemScript.empty() && script.IsPayToScriptHash()) { - // Check the redeemScript is valid + // Parse all arguments + ImportData import_data; + if (strRedeemScript.size()) { if (!IsHex(strRedeemScript)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script: must be hex string"); - } - - // Import redeem script. - std::vector<unsigned char> vData(ParseHex(strRedeemScript)); - CScript redeemScript = CScript(vData.begin(), vData.end()); - CScriptID redeem_id(redeemScript); - - // Check that the redeemScript and scriptPubKey match - if (GetScriptForDestination(redeem_id) != script) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "The redeemScript does not match the scriptPubKey"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid redeem script \"" + strRedeemScript + "\": must be hex string"); } - - pwallet->MarkDirty(); - - if (!pwallet->AddWatchOnly(redeemScript, timestamp)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); - } - - if (!pwallet->HaveCScript(redeem_id) && !pwallet->AddCScript(redeemScript)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2sh redeemScript to wallet"); - } - - // Now set script to the redeemScript so we parse the inner script as P2WSH or P2WPKH below - script = redeemScript; - ExtractDestination(script, dest); + auto parsed_redeemscript = ParseHex(strRedeemScript); + import_data.redeemscript = MakeUnique<CScript>(parsed_redeemscript.begin(), parsed_redeemscript.end()); } - - // (P2SH-)P2WSH - if (!witness_script_hex.empty() && script.IsPayToWitnessScriptHash()) { + if (witness_script_hex.size()) { if (!IsHex(witness_script_hex)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid witness script: must be hex string"); + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid witness script \"" + witness_script_hex + "\": must be hex string"); } - - // Generate the scripts - std::vector<unsigned char> witness_script_parsed(ParseHex(witness_script_hex)); - CScript witness_script = CScript(witness_script_parsed.begin(), witness_script_parsed.end()); - CScriptID witness_id(witness_script); - - // Check that the witnessScript and scriptPubKey match - if (GetScriptForDestination(WitnessV0ScriptHash(witness_script)) != script) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "The witnessScript does not match the scriptPubKey or redeemScript"); + auto parsed_witnessscript = ParseHex(witness_script_hex); + import_data.witnessscript = MakeUnique<CScript>(parsed_witnessscript.begin(), parsed_witnessscript.end()); + } + std::map<CKeyID, CPubKey> pubkey_map; + for (size_t i = 0; i < pubKeys.size(); ++i) { + const auto& str = pubKeys[i].get_str(); + if (!IsHex(str)) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" must be a hex string"); } - - // Add the witness script as watch only only if it is not for P2SH-P2WSH - if (!scriptpubkey_script.IsPayToScriptHash() && !pwallet->AddWatchOnly(witness_script, timestamp)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + auto parsed_pubkey = ParseHex(str); + CPubKey pubkey(parsed_pubkey.begin(), parsed_pubkey.end()); + if (!pubkey.IsFullyValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey \"" + str + "\" is not a valid public key"); } - - if (!pwallet->HaveCScript(witness_id) && !pwallet->AddCScript(witness_script)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding p2wsh witnessScript to wallet"); + pubkey_map.emplace(pubkey.GetID(), pubkey); + } + std::map<CKeyID, CKey> privkey_map; + for (size_t i = 0; i < keys.size(); ++i) { + const auto& str = keys[i].get_str(); + CKey key = DecodeSecret(str); + if (!key.IsValid()) { + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); } + CPubKey pubkey = key.GetPubKey(); + CKeyID id = pubkey.GetID(); + if (pubkey_map.count(id)) { + pubkey_map.erase(id); + } + privkey_map.emplace(id, key); + } - // Now set script to the witnessScript so we parse the inner script as P2PK or P2PKH below - script = witness_script; - ExtractDestination(script, dest); - allow_p2wpkh = false; // P2WPKH cannot be embedded in P2WSH + // Internal addresses should not have a label + if (internal && data.exists("label")) { + throw JSONRPCError(RPC_INVALID_PARAMETER, "Internal addresses should not have a label"); } - // (P2SH-)P2PK/P2PKH/P2WPKH - if (dest.type() == typeid(CKeyID) || dest.type() == typeid(WitnessV0KeyHash)) { - if (!allow_p2wpkh && dest.type() == typeid(WitnessV0KeyHash)) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "P2WPKH cannot be embedded in P2WSH"); - } - if (keys.size() > 1 || pubKeys.size() > 1) { - throw JSONRPCError(RPC_INVALID_PARAMETER, "More than one key given for one single-key address"); - } - CPubKey pubkey; - if (keys.size()) { - pubkey = DecodeSecret(keys[0].get_str()).GetPubKey(); + // Verify and process input data + bool have_solving_data = import_data.redeemscript || import_data.witnessscript || pubkey_map.size() || privkey_map.size(); + if (have_solving_data) { + // Match up data in import_data with the scriptPubKey in script. + auto error = RecurseImportData(script, import_data, ScriptContext::TOP); + + // Verify whether the watchonly option corresponds to the availability of private keys. + bool spendable = std::all_of(import_data.used_keys.begin(), import_data.used_keys.end(), [&](const std::pair<CKeyID, bool>& used_key){ return privkey_map.count(used_key.first) > 0; }); + if (!watchOnly && !spendable) { + warnings.push_back("Some private keys are missing, outputs will be considered watchonly. If this is intentional, specify the watchonly flag."); } - if (pubKeys.size()) { - const std::string& strPubKey = pubKeys[0].get_str(); - if (!IsHex(strPubKey)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey must be a hex string"); - } - std::vector<unsigned char> vData(ParseHex(pubKeys[0].get_str())); - CPubKey pubkey_temp(vData.begin(), vData.end()); - if (pubkey.size() && pubkey_temp != pubkey) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Private key does not match public key for address"); - } - pubkey = pubkey_temp; + if (watchOnly && spendable) { + warnings.push_back("All private keys are provided, outputs will be considered spendable. If this is intentional, do not specify the watchonly flag."); } - if (pubkey.size() > 0) { - if (!pubkey.IsFullyValid()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Pubkey is not a valid public key"); - } - // Check the key corresponds to the destination given - std::vector<CTxDestination> destinations = GetAllDestinationsForKey(pubkey); - if (std::find(destinations.begin(), destinations.end(), dest) == destinations.end()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Key does not match address destination"); + // Check that all required keys for solvability are provided. + if (error.empty()) { + for (const auto& require_key : import_data.used_keys) { + if (!require_key.second) continue; // Not a required key + if (pubkey_map.count(require_key.first) == 0 && privkey_map.count(require_key.first) == 0) { + error = "some required keys are missing"; + } } + } - // This is necessary to force the wallet to import the pubKey - CScript scriptRawPubKey = GetScriptForRawPubKey(pubkey); - - if (::IsMine(*pwallet, scriptRawPubKey) == ISMINE_SPENDABLE) { - throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); + if (!error.empty()) { + warnings.push_back("Importing as non-solvable: " + error + ". If this is intentional, don't provide any keys, pubkeys, witnessscript, or redeemscript."); + import_data = ImportData(); + pubkey_map.clear(); + privkey_map.clear(); + have_solving_data = false; + } else { + // RecurseImportData() removes any relevant redeemscript/witnessscript from import_data, so we can use that to discover if a superfluous one was provided. + if (import_data.redeemscript) warnings.push_back("Ignoring redeemscript as this is not a P2SH script."); + if (import_data.witnessscript) warnings.push_back("Ignoring witnessscript as this is not a (P2SH-)P2WSH script."); + for (auto it = privkey_map.begin(); it != privkey_map.end(); ) { + auto oldit = it++; + if (import_data.used_keys.count(oldit->first) == 0) { + warnings.push_back("Ignoring irrelevant private key."); + privkey_map.erase(oldit); + } } - - pwallet->MarkDirty(); - - if (!pwallet->AddWatchOnly(scriptRawPubKey, timestamp)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + for (auto it = pubkey_map.begin(); it != pubkey_map.end(); ) { + auto oldit = it++; + auto key_data_it = import_data.used_keys.find(oldit->first); + if (key_data_it == import_data.used_keys.end() || !key_data_it->second) { + warnings.push_back("Ignoring public key \"" + HexStr(oldit->first) + "\" as it doesn't appear inside P2PKH or P2WPKH."); + pubkey_map.erase(oldit); + } } } } - // Import the address - if (::IsMine(*pwallet, scriptpubkey_script) == ISMINE_SPENDABLE) { + // Check whether we have any work to do + if (::IsMine(*pwallet, script) & ISMINE_SPENDABLE) { throw JSONRPCError(RPC_WALLET_ERROR, "The wallet already contains the private key for this address or script"); } + // All good, time to import pwallet->MarkDirty(); - - if (!pwallet->AddWatchOnly(scriptpubkey_script, timestamp)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); - } - - if (!watchOnly && !pwallet->HaveCScript(CScriptID(scriptpubkey_script)) && !pwallet->AddCScript(scriptpubkey_script)) { - throw JSONRPCError(RPC_WALLET_ERROR, "Error adding scriptPubKey script to wallet"); - } - - // if not internal add to address book or update label - if (!internal) { - assert(IsValidDestination(scriptpubkey_dest)); - pwallet->SetAddressBook(scriptpubkey_dest, label, "receive"); - } - - // Import private keys. - for (size_t i = 0; i < keys.size(); i++) { - const std::string& strPrivkey = keys[i].get_str(); - - // Checks. - CKey key = DecodeSecret(strPrivkey); - - if (!key.IsValid()) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Invalid private key encoding"); + for (const auto& entry : import_data.import_scripts) { + if (!pwallet->HaveCScript(CScriptID(entry)) && !pwallet->AddCScript(entry)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding script to wallet"); } - - CPubKey pubKey = key.GetPubKey(); - assert(key.VerifyPubKey(pubKey)); - - CKeyID vchAddress = pubKey.GetID(); - pwallet->MarkDirty(); - - if (pwallet->HaveKey(vchAddress)) { - throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Already have this key"); - } - - pwallet->mapKeyMetadata[vchAddress].nCreateTime = timestamp; - - if (!pwallet->AddKeyPubKey(key, pubKey)) { + } + for (const auto& entry : privkey_map) { + const CKey& key = entry.second; + CPubKey pubkey = key.GetPubKey(); + const CKeyID& id = entry.first; + assert(key.VerifyPubKey(pubkey)); + pwallet->mapKeyMetadata[id].nCreateTime = timestamp; + // If the private key is not present in the wallet, insert it. + if (!pwallet->HaveKey(id) && !pwallet->AddKeyPubKey(key, pubkey)) { throw JSONRPCError(RPC_WALLET_ERROR, "Error adding key to wallet"); } - pwallet->UpdateTimeFirstKey(timestamp); } + for (const auto& entry : pubkey_map) { + const CPubKey& pubkey = entry.second; + const CKeyID& id = entry.first; + CPubKey temp; + if (!pwallet->GetPubKey(id, temp) && !pwallet->AddWatchOnly(GetScriptForRawPubKey(pubkey), timestamp)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + } + } + if (!have_solving_data || !::IsMine(*pwallet, script)) { // Always call AddWatchOnly for non-solvable watch-only, so that watch timestamp gets updated + if (!pwallet->AddWatchOnly(script, timestamp)) { + throw JSONRPCError(RPC_WALLET_ERROR, "Error adding address to wallet"); + } + } + if (!internal) { + assert(IsValidDestination(dest)); + pwallet->SetAddressBook(dest, label, "receive"); + } - UniValue result = UniValue(UniValue::VOBJ); result.pushKV("success", UniValue(true)); - return result; } catch (const UniValue& e) { - UniValue result = UniValue(UniValue::VOBJ); result.pushKV("success", UniValue(false)); result.pushKV("error", e); - return result; } catch (...) { - UniValue result = UniValue(UniValue::VOBJ); result.pushKV("success", UniValue(false)); + result.pushKV("error", JSONRPCError(RPC_MISC_ERROR, "Missing required fields")); - return result; } + if (warnings.size()) result.pushKV("warnings", warnings); + return result; } static int64_t GetImportTimestamp(const UniValue& data, int64_t now) @@ -1122,18 +1167,18 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) }, {"redeemscript", RPCArg::Type::STR, /* opt */ true, /* default_val */ "omitted", "Allowed only if the scriptPubKey is a P2SH or P2SH-P2WSH address/scriptPubKey"}, {"witnessscript", RPCArg::Type::STR, /* opt */ true, /* default_val */ "omitted", "Allowed only if the scriptPubKey is a P2SH-P2WSH or P2WSH address/scriptPubKey"}, - {"pubkeys", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "empty array", "Array of strings giving pubkeys that must occur in the output or redeemscript", + {"pubkeys", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "empty array", "Array of strings giving pubkeys to import. They must occur in P2PKH or P2WPKH scripts. They are not required when the private key is also provided (see the \"keys\" argument).", { {"pubKey", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", ""}, } }, - {"keys", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "empty array", "Array of strings giving private keys whose corresponding public keys must occur in the output or redeemscript", + {"keys", RPCArg::Type::ARR, /* opt */ true, /* default_val */ "empty array", "Array of strings giving private keys to import. The corresponding public keys must occur in the output or redeemscript.", { {"key", RPCArg::Type::STR, /* opt */ false, /* default_val */ "", ""}, } }, - {"internal", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Stating whether matching outputs should be treated as not incoming payments aka change"}, - {"watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Stating whether matching outputs should be considered watched even when they're not spendable, only allowed if keys are empty"}, + {"internal", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Stating whether matching outputs should be treated as not incoming payments (also known as change)"}, + {"watchonly", RPCArg::Type::BOOL, /* opt */ true, /* default_val */ "false", "Stating whether matching outputs should be considered watched even when not all private keys are provided."}, {"label", RPCArg::Type::STR, /* opt */ true, /* default_val */ "''", "Label to assign to the address, only allowed with internal=false"}, }, }, @@ -1154,7 +1199,7 @@ UniValue importmulti(const JSONRPCRequest& mainRequest) HelpExampleCli("importmulti", "'[{ \"scriptPubKey\": { \"address\": \"<my address>\" }, \"timestamp\":1455191478 }]' '{ \"rescan\": false}'") + "\nResponse is an array with the same size as the input that has the execution result :\n" - " [{ \"success\": true } , { \"success\": false, \"error\": { \"code\": -1, \"message\": \"Internal Server Error\"} }, ... ]\n"); + " [{\"success\": true}, {\"success\": true, \"warnings\": [\"Ignoring irrelevant private key\"]}, {\"success\": false, \"error\": {\"code\": -1, \"message\": \"Internal Server Error\"}}, ...]\n"); RPCTypeCheck(mainRequest.params, {UniValue::VARR, UniValue::VOBJ}); diff --git a/src/wallet/rpcwallet.cpp b/src/wallet/rpcwallet.cpp index 1d9ab54857..c1cdd0b2ee 100644 --- a/src/wallet/rpcwallet.cpp +++ b/src/wallet/rpcwallet.cpp @@ -1397,9 +1397,14 @@ UniValue listtransactions(const JSONRPCRequest& request) "[\n" " {\n" " \"address\":\"address\", (string) The bitcoin address of the transaction.\n" - " \"category\":\"send|receive\", (string) The transaction category.\n" + " \"category\": (string) The transaction category.\n" + " \"send\" Transactions sent.\n" + " \"receive\" Non-coinbase transactions received.\n" + " \"generate\" Coinbase transactions received with more than 100 confirmations.\n" + " \"immature\" Coinbase transactions received with 100 or fewer confirmations.\n" + " \"orphan\" Orphaned coinbase transactions received.\n" " \"amount\": x.xxx, (numeric) The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and is positive\n" - " for the 'receive' category,\n" + " for all other categories\n" " \"label\": \"label\", (string) A comment for the address/transaction, if any\n" " \"vout\": n, (numeric) the vout value\n" " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the \n" @@ -1526,20 +1531,25 @@ static UniValue listsinceblock(const JSONRPCRequest& request) "\nResult:\n" "{\n" " \"transactions\": [\n" - " \"address\":\"address\", (string) The bitcoin address of the transaction. Not present for move transactions (category = move).\n" - " \"category\":\"send|receive\", (string) The transaction category. 'send' has negative amounts, 'receive' has positive amounts.\n" - " \"amount\": x.xxx, (numeric) The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and for the 'move' category for moves \n" - " outbound. It is positive for the 'receive' category, and for the 'move' category for inbound funds.\n" + " \"address\":\"address\", (string) The bitcoin address of the transaction.\n" + " \"category\": (string) The transaction category.\n" + " \"send\" Transactions sent.\n" + " \"receive\" Non-coinbase transactions received.\n" + " \"generate\" Coinbase transactions received with more than 100 confirmations.\n" + " \"immature\" Coinbase transactions received with 100 or fewer confirmations.\n" + " \"orphan\" Orphaned coinbase transactions received.\n" + " \"amount\": x.xxx, (numeric) The amount in " + CURRENCY_UNIT + ". This is negative for the 'send' category, and is positive\n" + " for all other categories\n" " \"vout\" : n, (numeric) the vout value\n" " \"fee\": x.xxx, (numeric) The amount of the fee in " + CURRENCY_UNIT + ". This is negative and only available for the 'send' category of transactions.\n" - " \"confirmations\": n, (numeric) The number of confirmations for the transaction. Available for 'send' and 'receive' category of transactions.\n" + " \"confirmations\": n, (numeric) The number of confirmations for the transaction.\n" " When it's < 0, it means the transaction conflicted that many blocks ago.\n" - " \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction. Available for 'send' and 'receive' category of transactions.\n" - " \"blockindex\": n, (numeric) The index of the transaction in the block that includes it. Available for 'send' and 'receive' category of transactions.\n" + " \"blockhash\": \"hashvalue\", (string) The block hash containing the transaction.\n" + " \"blockindex\": n, (numeric) The index of the transaction in the block that includes it.\n" " \"blocktime\": xxx, (numeric) The block time in seconds since epoch (1 Jan 1970 GMT).\n" - " \"txid\": \"transactionid\", (string) The transaction id. Available for 'send' and 'receive' category of transactions.\n" + " \"txid\": \"transactionid\", (string) The transaction id.\n" " \"time\": xxx, (numeric) The transaction time in seconds since epoch (Jan 1 1970 GMT).\n" - " \"timereceived\": xxx, (numeric) The time received in seconds since epoch (Jan 1 1970 GMT). Available for 'send' and 'receive' category of transactions.\n" + " \"timereceived\": xxx, (numeric) The time received in seconds since epoch (Jan 1 1970 GMT).\n" " \"bip125-replaceable\": \"yes|no|unknown\", (string) Whether this transaction could be replaced due to BIP125 (replace-by-fee);\n" " may be unknown for unconfirmed transactions not in the mempool\n" " \"abandoned\": xxx, (bool) 'true' if the transaction has been abandoned (inputs are respendable). Only available for the 'send' category of transactions.\n" @@ -1677,7 +1687,12 @@ static UniValue gettransaction(const JSONRPCRequest& request) " \"details\" : [\n" " {\n" " \"address\" : \"address\", (string) The bitcoin address involved in the transaction\n" - " \"category\" : \"send|receive\", (string) The category, either 'send' or 'receive'\n" + " \"category\" : (string) The transaction category.\n" + " \"send\" Transactions sent.\n" + " \"receive\" Non-coinbase transactions received.\n" + " \"generate\" Coinbase transactions received with more than 100 confirmations.\n" + " \"immature\" Coinbase transactions received with 100 or fewer confirmations.\n" + " \"orphan\" Orphaned coinbase transactions received.\n" " \"amount\" : x.xxx, (numeric) The amount in " + CURRENCY_UNIT + "\n" " \"label\" : \"label\", (string) A comment for the address/transaction, if any\n" " \"vout\" : n, (numeric) the vout value\n" diff --git a/src/wallet/test/wallet_tests.cpp b/src/wallet/test/wallet_tests.cpp index 5731a27e00..1ed1926af2 100644 --- a/src/wallet/test/wallet_tests.cpp +++ b/src/wallet/test/wallet_tests.cpp @@ -34,7 +34,7 @@ static void AddKey(CWallet& wallet, const CKey& key) wallet.AddKeyPubKey(key, key.GetPubKey()); } -BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) +BOOST_FIXTURE_TEST_CASE(scan_for_wallet_transactions, TestChain100Setup) { auto chain = interfaces::MakeChain(); @@ -47,6 +47,19 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) auto locked_chain = chain->lock(); + // Verify ScanForWalletTransactions accomodates a null start block. + { + CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); + AddKey(wallet, coinbaseKey); + WalletRescanReserver reserver(&wallet); + reserver.reserve(); + const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; + BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(nullptr, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS); + BOOST_CHECK_EQUAL(failed_block, null_block); + BOOST_CHECK_EQUAL(stop_block, null_block); + BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 0); + } + // Verify ScanForWalletTransactions picks up transactions in both the old // and new block files. { @@ -54,7 +67,7 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); - const CBlockIndex *stop_block, *failed_block; + const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(oldTip, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS); BOOST_CHECK_EQUAL(failed_block, null_block); BOOST_CHECK_EQUAL(stop_block, newTip); @@ -72,13 +85,47 @@ BOOST_FIXTURE_TEST_CASE(rescan, TestChain100Setup) AddKey(wallet, coinbaseKey); WalletRescanReserver reserver(&wallet); reserver.reserve(); - const CBlockIndex *stop_block, *failed_block; + const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(oldTip, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::FAILURE); BOOST_CHECK_EQUAL(failed_block, oldTip); BOOST_CHECK_EQUAL(stop_block, newTip); BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 50 * COIN); } + // Prune the remaining block file. + PruneOneBlockFile(newTip->GetBlockPos().nFile); + UnlinkPrunedFiles({newTip->GetBlockPos().nFile}); + + // Verify ScanForWalletTransactions scans no blocks. + { + CWallet wallet(*chain, WalletLocation(), WalletDatabase::CreateDummy()); + AddKey(wallet, coinbaseKey); + WalletRescanReserver reserver(&wallet); + reserver.reserve(); + const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; + BOOST_CHECK_EQUAL(wallet.ScanForWalletTransactions(oldTip, nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::FAILURE); + BOOST_CHECK_EQUAL(failed_block, newTip); + BOOST_CHECK_EQUAL(stop_block, null_block); + BOOST_CHECK_EQUAL(wallet.GetImmatureBalance(), 0); + } +} + +BOOST_FIXTURE_TEST_CASE(importmulti_rescan, TestChain100Setup) +{ + auto chain = interfaces::MakeChain(); + + // Cap last block file size, and mine new block in a new block file. + CBlockIndex* oldTip = chainActive.Tip(); + GetBlockFileInfo(oldTip->GetBlockPos().nFile)->nSize = MAX_BLOCKFILE_SIZE; + CreateAndProcessBlock({}, GetScriptForRawPubKey(coinbaseKey.GetPubKey())); + CBlockIndex* newTip = chainActive.Tip(); + + auto locked_chain = chain->lock(); + + // Prune the older block file. + PruneOneBlockFile(oldTip->GetBlockPos().nFile); + UnlinkPrunedFiles({oldTip->GetBlockPos().nFile}); + // Verify importmulti RPC returns failure for a key whose creation time is // before the missing block, and success for a key whose creation time is // after. @@ -294,7 +341,7 @@ public: WalletRescanReserver reserver(wallet.get()); reserver.reserve(); const CBlockIndex* const null_block = nullptr; - const CBlockIndex *stop_block, *failed_block; + const CBlockIndex *stop_block = null_block + 1, *failed_block = null_block + 1; BOOST_CHECK_EQUAL(wallet->ScanForWalletTransactions(chainActive.Genesis(), nullptr, reserver, failed_block, stop_block), CWallet::ScanResult::SUCCESS); BOOST_CHECK_EQUAL(stop_block, chainActive.Tip()); BOOST_CHECK_EQUAL(failed_block, null_block); diff --git a/src/wallet/wallet.cpp b/src/wallet/wallet.cpp index fea0378839..109f8e6da0 100644 --- a/src/wallet/wallet.cpp +++ b/src/wallet/wallet.cpp @@ -1601,6 +1601,7 @@ CWallet::ScanResult CWallet::ScanForWalletTransactions(const CBlockIndex* const const CBlockIndex* pindex = pindexStart; failed_block = nullptr; + stop_block = nullptr; if (pindex) WalletLogPrintf("Rescan started from block %d...\n", pindex->nHeight); |