Six small defects, all found by an audit of the automated-operation path and all
verified present before changing anything.
AsyncRPCQueue::addOperation returned void and silently dropped the operation when
the queue was closed or finishing. Every caller assumed success: the three
schedulers left their running flag set with nothing in flight (which then blocks
every later round), and z_sendmany / z_shieldcoinbase / z_mergetoaddress returned
an opid for work that would never run -- z_shieldcoinbase and z_mergetoaddress
having already locked their selected coins in the constructor. It now returns
bool; the schedulers release the flag and log, and the three RPCs raise an error
instead of handing back an opid. Coin locks are memory-only, so a refusal at
shutdown reclaims them with the process; the lie about success was the defect.
Nothing ever removed finished automated operations from the queue's map.
popOperationForId is reached only from z_getoperationresult, so on a node running
autoshield every 25 blocks the map grew by one entry per round forever. The
schedulers now pop the operation they just cancelled. The worker already handles
a missing id ("cannot find operation in map, may have been removed",
asyncrpcqueue.cpp), and it releases lock_ before calling main(), so popping under
cs_wallet introduces no lock cycle.
The autoshield operation built its transaction against targetHeight_, the
enqueue-time height, while SetExpiryHeight and the network-upgrade straddle guard
both used tipHeight. Since the builder's height selects the consensus branch id,
the guard was checking a height the transaction was not signed against -- it could
not prevent the failure it exists to prevent. Now tipHeight throughout.
cancel() in the sweep, consolidation and autoshield operations set CANCELLED
unconditionally, dropping the base class's guard entirely. The schedulers cancel
the previous operation when they enqueue the next, so a round that had already
SUCCEEDED got its result relabelled as cancelled. Restored a narrower guard: still
cancellable while READY or EXECUTING (the base class refuses the latter, which
would defeat cancellation here), but a terminal state is left alone.
CommitAutomatedTx dumped the whole transaction to stderr on every commit,
duplicating the LogPrintf that CommitTransaction does one call later. ToString()
emits a line per input, so with the 400-input autoshield cap that was tens of KB
of stderr per round. Removed.
Also corrected a comment that credited the immature-coinbase exclusion to
fOnlySpendable (the argument is fOnlyConfirmed; the exclusion is unconditional in
AvailableCoins), and noted that AUTOSHIELD_CTXIN_P2SH_SIZE is a byte size that
merely happens to share the value 400 with the input cap.
Verified on an isolated regtest chain, 8/8: nine consecutive autoshield rounds
succeed after the builder-height change; the operation map stays at 1 entry across
all nine (it grew one per round before); stderr totals 1608 bytes for the whole
run with zero CommitAutomatedTx dumps, while debug.log still records all nine
commits via CommitTransaction; no round is relabelled cancelled; funds shield
correctly and no coin locks leak.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
92 lines
3.9 KiB
C++
92 lines
3.9 KiB
C++
// Copyright (c) 2016 The Zcash developers
|
|
// Copyright (c) 2016-2024 The Hush developers
|
|
// Distributed under the GPLv3 software license, see the accompanying
|
|
// file COPYING or https://www.gnu.org/licenses/gpl-3.0.en.html
|
|
|
|
/******************************************************************************
|
|
* Copyright © 2014-2019 The SuperNET Developers. *
|
|
* *
|
|
* See the AUTHORS, DEVELOPER-AGREEMENT and LICENSE files at *
|
|
* the top-level directory of this distribution for the individual copyright *
|
|
* holder information and the developer policies on copyright and licensing. *
|
|
* *
|
|
* Unless otherwise agreed in a custom licensing agreement, no part of the *
|
|
* SuperNET software, including this file may be copied, modified, propagated *
|
|
* or distributed except according to the terms contained in the LICENSE file *
|
|
* *
|
|
* Removal or modification of this copyright notice is prohibited. *
|
|
* *
|
|
******************************************************************************/
|
|
|
|
#ifndef ASYNCRPCQUEUE_H
|
|
#define ASYNCRPCQUEUE_H
|
|
|
|
#include "asyncrpcoperation.h"
|
|
|
|
#include <iostream>
|
|
#include <string>
|
|
#include <chrono>
|
|
#include <queue>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
#include <future>
|
|
#include <thread>
|
|
#include <utility>
|
|
#include <memory>
|
|
|
|
|
|
typedef std::unordered_map<AsyncRPCOperationId, std::shared_ptr<AsyncRPCOperation> > AsyncRPCOperationMap;
|
|
|
|
|
|
class AsyncRPCQueue {
|
|
public:
|
|
static shared_ptr<AsyncRPCQueue> sharedInstance();
|
|
|
|
AsyncRPCQueue();
|
|
virtual ~AsyncRPCQueue();
|
|
|
|
// We don't want queue to be copied or moved around
|
|
AsyncRPCQueue(AsyncRPCQueue const&) = delete; // Copy construct
|
|
AsyncRPCQueue(AsyncRPCQueue&&) = delete; // Move construct
|
|
AsyncRPCQueue& operator=(AsyncRPCQueue const&) = delete; // Copy assign
|
|
AsyncRPCQueue& operator=(AsyncRPCQueue &&) = delete; // Move assign
|
|
|
|
void addWorker();
|
|
size_t getNumberOfWorkers() const;
|
|
bool isClosed() const;
|
|
bool isFinishing() const;
|
|
void close(); // close queue and cancel all operations
|
|
void finish(); // close queue but finishing existing operations
|
|
void closeAndWait(); // block thread until all threads have terminated.
|
|
void finishAndWait(); // block thread until existing operations have finished, threads terminated
|
|
void cancelAllOperations(); // mark all operations in the queue as cancelled
|
|
size_t getOperationCount() const;
|
|
std::shared_ptr<AsyncRPCOperation> getOperationForId(AsyncRPCOperationId) const;
|
|
std::shared_ptr<AsyncRPCOperation> popOperationForId(AsyncRPCOperationId);
|
|
// Returns false if the queue is closed or finishing, in which case the
|
|
// operation was NOT queued and will never run. Callers must react: a caller
|
|
// that ignores this both reports success for work that will not happen and
|
|
// leaves any state it set for the operation (running flags, coin locks)
|
|
// stranded for the life of the process.
|
|
bool addOperation(const std::shared_ptr<AsyncRPCOperation> &ptrOperation);
|
|
std::vector<AsyncRPCOperationId> getAllOperationIds() const;
|
|
|
|
private:
|
|
// addWorker() will spawn a new thread on run())
|
|
void run(size_t workerId);
|
|
void wait_for_worker_threads();
|
|
|
|
// Why this is not a recursive lock: http://www.zaval.org/resources/library/butenhof1.html
|
|
mutable std::mutex lock_;
|
|
std::condition_variable condition_;
|
|
std::atomic<bool> closed_;
|
|
std::atomic<bool> finish_;
|
|
AsyncRPCOperationMap operation_map_;
|
|
std::queue <AsyncRPCOperationId> operation_id_queue_;
|
|
std::vector<std::thread> workers_;
|
|
};
|
|
|
|
#endif
|
|
|
|
|