Browse Source
Add z_getoperationstatus RPC command. Add z_sendmany RPC command (dummy implementation, does not send actual coins).pull/145/head
Simon
8 years ago
11 changed files with 843 additions and 0 deletions
@ -0,0 +1,147 @@ |
|||
// Copyright (c) 2016 The Zcash developers
|
|||
// Distributed under the MIT software license, see the accompanying
|
|||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|||
|
|||
#include "asyncrpcoperation.h" |
|||
|
|||
#include <boost/uuid/uuid.hpp> |
|||
#include <boost/uuid/uuid_generators.hpp> |
|||
#include <boost/uuid/uuid_io.hpp> |
|||
|
|||
#include <string> |
|||
#include <ctime> |
|||
#include <chrono> |
|||
|
|||
using namespace std; |
|||
using namespace json_spirit; |
|||
|
|||
static boost::uuids::random_generator uuidgen; |
|||
|
|||
std::map<OperationStatus, std::string> OperationStatusMap = { |
|||
{OperationStatus::READY, "queued"}, |
|||
{OperationStatus::EXECUTING, "executing"}, |
|||
{OperationStatus::CANCELLED, "cancelled"}, |
|||
{OperationStatus::FAILED, "failed"}, |
|||
{OperationStatus::SUCCESS, "success"} |
|||
}; |
|||
|
|||
AsyncRPCOperation::AsyncRPCOperation() : errorCode(0), errorMessage() { |
|||
// Set a unique reference for each operation
|
|||
boost::uuids::uuid uuid = uuidgen(); |
|||
std::string s = boost::uuids::to_string(uuid); |
|||
setId(s); |
|||
|
|||
setState(OperationStatus::READY); |
|||
creationTime = (int64_t)time(NULL); |
|||
} |
|||
|
|||
AsyncRPCOperation::AsyncRPCOperation(const AsyncRPCOperation& o) : id(o.id), creationTime(o.creationTime), state(o.state.load()) |
|||
{ |
|||
} |
|||
|
|||
|
|||
AsyncRPCOperation& AsyncRPCOperation::operator=( const AsyncRPCOperation& other ) { |
|||
this->id = other.getId(); |
|||
this->creationTime = other.creationTime; |
|||
this->state.store(other.state.load()); |
|||
return *this; |
|||
} |
|||
|
|||
|
|||
AsyncRPCOperation::~AsyncRPCOperation() { |
|||
} |
|||
|
|||
void AsyncRPCOperation::cancel() { |
|||
if (isReady()) |
|||
setState(OperationStatus::CANCELLED); |
|||
} |
|||
|
|||
|
|||
void AsyncRPCOperation::startExecutionClock() { |
|||
startTime = std::chrono::system_clock::now(); |
|||
} |
|||
|
|||
void AsyncRPCOperation::stopExecutionClock() { |
|||
endTime = std::chrono::system_clock::now(); |
|||
} |
|||
|
|||
|
|||
// Implement this method in any subclass.
|
|||
// This is just an example implementation.
|
|||
|
|||
|
|||
void AsyncRPCOperation::main() { |
|||
if (isCancelled()) |
|||
return; |
|||
|
|||
setState(OperationStatus::EXECUTING); |
|||
|
|||
//
|
|||
// Do some work here...
|
|||
//
|
|||
|
|||
startExecutionClock(); |
|||
|
|||
//std::this_thread::sleep_for(std::chrono::milliseconds(10000));
|
|||
|
|||
stopExecutionClock(); |
|||
|
|||
|
|||
// If there was an error...
|
|||
// setErrorCode(123);
|
|||
// setErrorMessage("Murphy's law");
|
|||
// setState(OperationStatus::FAILED);
|
|||
|
|||
|
|||
// Otherwise
|
|||
Value v("We have a result!"); |
|||
setResult(v); |
|||
setState(OperationStatus::SUCCESS); |
|||
} |
|||
|
|||
Value AsyncRPCOperation::getError() const { |
|||
if (!isFailed()) |
|||
return Value::null; |
|||
|
|||
Object error; |
|||
error.push_back(Pair("code", this->errorCode)); |
|||
error.push_back(Pair("message", this->errorMessage)); |
|||
return Value(error); |
|||
} |
|||
|
|||
Value AsyncRPCOperation::getResult() const { |
|||
if (!isSuccess()) |
|||
return Value::null; |
|||
|
|||
return this->resultValue; |
|||
} |
|||
|
|||
|
|||
/*
|
|||
* Returns a status Value object. |
|||
* If the operation has failed, it will include an error object. |
|||
* If the operation has succeeded, it will include the result value. |
|||
*/ |
|||
Value AsyncRPCOperation::getStatus() const { |
|||
OperationStatus status = this->getState(); |
|||
Object obj; |
|||
obj.push_back(Pair("id", this->getId())); |
|||
obj.push_back(Pair("status", OperationStatusMap[status])); |
|||
obj.push_back(Pair("creation_time", this->creationTime)); |
|||
// creation, exec time, duration, exec end, etc.
|
|||
Value err = this->getError(); |
|||
if (!err.is_null()) { |
|||
obj.push_back(Pair("error", err.get_obj())); |
|||
} |
|||
Value result = this->getResult(); |
|||
if (!result.is_null()) { |
|||
obj.push_back(Pair("result", result)); |
|||
|
|||
// Include execution time for successful operation
|
|||
std::chrono::duration<double> elapsed_seconds = endTime - startTime; |
|||
obj.push_back(Pair("execution_secs", elapsed_seconds.count())); |
|||
|
|||
} |
|||
return Value(obj); |
|||
} |
|||
|
@ -0,0 +1,148 @@ |
|||
// Copyright (c) 2016 The Zcash developers
|
|||
// Distributed under the MIT software license, see the accompanying
|
|||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|||
|
|||
|
|||
#ifndef ASYNCRPCOPERATION_H |
|||
#define ASYNCRPCOPERATION_H |
|||
|
|||
#include <string> |
|||
#include <atomic> |
|||
#include <map> |
|||
#include <chrono> |
|||
|
|||
#include "json/json_spirit_value.h" |
|||
#include "json/json_spirit_utils.h" |
|||
#include "json/json_spirit_reader_template.h" |
|||
#include "json/json_spirit_writer_template.h" |
|||
|
|||
using namespace std; |
|||
using namespace json_spirit; |
|||
|
|||
/**
|
|||
* AsyncRPCOperations are given to the AsyncRPCQueue for processing. |
|||
* |
|||
* How to subclass: |
|||
* Implement the main() method, this is where work is performed. |
|||
* Update the operation status as work is underway and completes. |
|||
*/ |
|||
|
|||
typedef std::string AsyncRPCOperationId; |
|||
|
|||
typedef enum class operationStateEnum { |
|||
READY = 0, |
|||
EXECUTING, |
|||
CANCELLED, |
|||
FAILED, |
|||
SUCCESS |
|||
} OperationStatus; |
|||
|
|||
class AsyncRPCOperation { |
|||
public: |
|||
AsyncRPCOperation(); |
|||
|
|||
// Todo: keep or delete copy constructors and assignment?
|
|||
AsyncRPCOperation(const AsyncRPCOperation& orig); |
|||
AsyncRPCOperation& operator=( const AsyncRPCOperation& other ); |
|||
|
|||
virtual ~AsyncRPCOperation(); |
|||
|
|||
// Implement this method in your subclass.
|
|||
virtual void main(); |
|||
|
|||
void cancel(); |
|||
|
|||
// Getters and setters
|
|||
|
|||
OperationStatus getState() const { |
|||
return state.load(); |
|||
} |
|||
|
|||
AsyncRPCOperationId getId() const { |
|||
return id; |
|||
} |
|||
|
|||
int64_t getCreationTime() const { |
|||
return creationTime; |
|||
} |
|||
|
|||
Value getStatus() const; |
|||
|
|||
Value getError() const; |
|||
|
|||
Value getResult() const; |
|||
|
|||
int getErrorCode() const { |
|||
return errorCode; |
|||
} |
|||
|
|||
std::string getErrorMessage() const { |
|||
return errorMessage; |
|||
} |
|||
|
|||
bool isCancelled() const { |
|||
return OperationStatus::CANCELLED==getState(); |
|||
} |
|||
|
|||
bool isExecuting() const { |
|||
return OperationStatus::EXECUTING==getState(); |
|||
} |
|||
|
|||
bool isReady() const { |
|||
return OperationStatus::READY==getState(); |
|||
} |
|||
|
|||
bool isFailed() const { |
|||
return OperationStatus::FAILED==getState(); |
|||
} |
|||
|
|||
bool isSuccess() const { |
|||
return OperationStatus::SUCCESS==getState(); |
|||
} |
|||
|
|||
protected: |
|||
|
|||
Value resultValue; |
|||
int errorCode; |
|||
std::string errorMessage; |
|||
std::atomic<OperationStatus> state; |
|||
std::chrono::time_point<std::chrono::system_clock> startTime, endTime; |
|||
|
|||
void startExecutionClock(); |
|||
void stopExecutionClock(); |
|||
|
|||
void setState(OperationStatus state) { |
|||
this->state.store(state); |
|||
} |
|||
|
|||
void setErrorCode(int errorCode) { |
|||
this->errorCode = errorCode; |
|||
} |
|||
|
|||
void setErrorMessage(std::string errorMessage) { |
|||
this->errorMessage = errorMessage; |
|||
} |
|||
|
|||
void setResult(Value v) { |
|||
this->resultValue = v; |
|||
} |
|||
|
|||
private: |
|||
|
|||
// Todo: Private for now. If copying an operation is possible, should it
|
|||
// receive a new id and a new creation time?
|
|||
void setId(AsyncRPCOperationId id) { |
|||
this->id = id; |
|||
} |
|||
|
|||
// Todo: Ditto above.
|
|||
void setCreationTime(int64_t creationTime) { |
|||
this->creationTime = creationTime; |
|||
} |
|||
|
|||
AsyncRPCOperationId id; |
|||
int64_t creationTime; |
|||
}; |
|||
|
|||
#endif /* ASYNCRPCOPERATION_H */ |
|||
|
@ -0,0 +1,147 @@ |
|||
// Copyright (c) 2016 The Zcash developers
|
|||
// Distributed under the MIT software license, see the accompanying
|
|||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|||
|
|||
#include "asyncrpcqueue.h" |
|||
|
|||
static std::atomic<int> workerCounter(0); |
|||
|
|||
AsyncRPCQueue::AsyncRPCQueue() : closed(false) { |
|||
} |
|||
|
|||
/*
|
|||
* Calling thread will join on all the worker threads |
|||
*/ |
|||
AsyncRPCQueue::~AsyncRPCQueue() { |
|||
this->closed = true; // set this in case close() was not invoked
|
|||
for (std::thread & t : this->workers) { |
|||
t.join(); |
|||
} |
|||
} |
|||
|
|||
/*
|
|||
* A worker will execute this method on a new thread |
|||
*/ |
|||
void AsyncRPCQueue::run(int workerId) { |
|||
// std::cout << "Launched queue worker " << workerId << std::endl;
|
|||
|
|||
while (!isClosed()) { |
|||
AsyncRPCOperationId key; |
|||
std::shared_ptr<AsyncRPCOperation> operation; |
|||
{ |
|||
std::unique_lock< std::mutex > guard(cs_lock); |
|||
while (operationIdQueue.empty() && !isClosed()) { |
|||
this->cs_condition.wait(guard); |
|||
} |
|||
|
|||
// Exit if the queue is closing.
|
|||
if (isClosed()) |
|||
break; |
|||
|
|||
// Get operation id
|
|||
key = operationIdQueue.front(); |
|||
operationIdQueue.pop(); |
|||
|
|||
// Search operation map
|
|||
AsyncRPCOperationMap::const_iterator iter = operationMap.find(key); |
|||
if (iter != operationMap.end()) { |
|||
operation = iter->second; |
|||
} |
|||
} |
|||
|
|||
if (!operation) { |
|||
// cannot find operation in map, may have been removed
|
|||
} else if (operation->isCancelled()) { |
|||
// skip cancelled operation
|
|||
} else { |
|||
operation->main(); |
|||
} |
|||
} |
|||
// std::cout << "Terminating queue worker " << workerId << std::endl;
|
|||
} |
|||
|
|||
|
|||
/*
|
|||
* Add shared_ptr to operation. |
|||
* |
|||
* To retain polymorphic behaviour, i.e. main() method of derived classes is invoked, |
|||
* caller should create the shared_ptr like thi: |
|||
* |
|||
* std::shared_ptr<AsyncRPCOperation> ptr(new MyCustomAsyncRPCOperation(params)); |
|||
* |
|||
* Don't use std::make_shared<AsyncRPCOperation>(). |
|||
*/ |
|||
void AsyncRPCQueue::addOperation(const std::shared_ptr<AsyncRPCOperation> &ptrOperation) { |
|||
|
|||
// Don't add if queue is closed
|
|||
if (isClosed()) |
|||
return; |
|||
|
|||
AsyncRPCOperationId id = ptrOperation->getId(); |
|||
{ |
|||
std::lock_guard< std::mutex > guard(cs_lock); |
|||
operationMap.emplace(id, ptrOperation); |
|||
operationIdQueue.push(id); |
|||
this->cs_condition.notify_one(); |
|||
} |
|||
} |
|||
|
|||
|
|||
std::shared_ptr<AsyncRPCOperation> AsyncRPCQueue::getOperationForId(AsyncRPCOperationId id) { |
|||
std::shared_ptr<AsyncRPCOperation> ptr; |
|||
|
|||
std::lock_guard< std::mutex > guard(cs_lock); |
|||
AsyncRPCOperationMap::const_iterator iter = operationMap.find(id); |
|||
if (iter != operationMap.end()) { |
|||
ptr = iter->second; |
|||
} |
|||
return ptr; |
|||
} |
|||
|
|||
std::shared_ptr<AsyncRPCOperation> AsyncRPCQueue::popOperationForId(AsyncRPCOperationId id) { |
|||
std::shared_ptr<AsyncRPCOperation> ptr = getOperationForId(id); |
|||
if (ptr) { |
|||
std::lock_guard< std::mutex > guard(cs_lock); |
|||
// Note: if the id still exists in the operationIdQueue, when it gets processed by a worker
|
|||
// there will no operation in the map to execute, so nothing will happen.
|
|||
operationMap.erase(id); |
|||
} |
|||
return ptr; |
|||
} |
|||
|
|||
bool AsyncRPCQueue::isClosed() { |
|||
return closed; |
|||
} |
|||
|
|||
void AsyncRPCQueue::close() { |
|||
this->closed = true; |
|||
cancelAllOperations(); |
|||
} |
|||
|
|||
/*
|
|||
* Call cancel() on all operations |
|||
*/ |
|||
void AsyncRPCQueue::cancelAllOperations() { |
|||
std::unique_lock< std::mutex > guard(cs_lock); |
|||
for (auto key : operationMap) { |
|||
key.second->cancel(); |
|||
} |
|||
this->cs_condition.notify_all(); |
|||
} |
|||
|
|||
int AsyncRPCQueue::getOperationCount() { |
|||
std::unique_lock< std::mutex > guard(cs_lock); |
|||
return operationIdQueue.size(); |
|||
} |
|||
|
|||
/*
|
|||
* Spawn a worker thread |
|||
*/ |
|||
void AsyncRPCQueue::addWorker() { |
|||
std::unique_lock< std::mutex > guard(cs_lock); // Todo: could just have a lock on the vector
|
|||
workers.emplace_back( std::thread(&AsyncRPCQueue::run, this, ++workerCounter) ); |
|||
} |
|||
|
|||
int AsyncRPCQueue::getNumberOfWorkers() { |
|||
return workers.size(); |
|||
} |
@ -0,0 +1,61 @@ |
|||
// Copyright (c) 2014 The Zcash developers
|
|||
// Distributed under the MIT software license, see the accompanying
|
|||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|||
|
|||
#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: |
|||
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(); |
|||
int getNumberOfWorkers(); |
|||
bool isClosed(); |
|||
void close(); |
|||
void cancelAllOperations(); |
|||
int getOperationCount(); |
|||
std::shared_ptr<AsyncRPCOperation> getOperationForId(AsyncRPCOperationId); |
|||
std::shared_ptr<AsyncRPCOperation> popOperationForId(AsyncRPCOperationId); |
|||
void addOperation(const std::shared_ptr<AsyncRPCOperation> &ptrOperation); |
|||
|
|||
private: |
|||
// addWorker() will spawn a new thread on this method
|
|||
void run(int workerId); |
|||
|
|||
std::mutex cs_lock; |
|||
std::condition_variable cs_condition; |
|||
bool closed; |
|||
AsyncRPCOperationMap operationMap; |
|||
std::queue <AsyncRPCOperationId> operationIdQueue; |
|||
std::vector<std::thread> workers; |
|||
}; |
|||
|
|||
#endif |
|||
|
|||
|
@ -0,0 +1,77 @@ |
|||
// Copyright (c) 2016 The Zcash developers
|
|||
// Distributed under the MIT software license, see the accompanying
|
|||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|||
|
|||
|
|||
#include "asyncrpcoperation_sendmany.h" |
|||
|
|||
#include <iostream> |
|||
#include <chrono> |
|||
#include <thread> |
|||
|
|||
AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(std::string fromAddress, std::vector<SendManyRecipient> outputs, int minconf) : fromAddress(fromAddress), outputs(outputs), minconf(minconf) |
|||
{ |
|||
} |
|||
|
|||
AsyncRPCOperation_sendmany::AsyncRPCOperation_sendmany(const AsyncRPCOperation_sendmany& orig) { |
|||
} |
|||
|
|||
AsyncRPCOperation_sendmany::~AsyncRPCOperation_sendmany() { |
|||
} |
|||
|
|||
void AsyncRPCOperation_sendmany::main() { |
|||
if (isCancelled()) |
|||
return; |
|||
|
|||
setState(OperationStatus::EXECUTING); |
|||
startExecutionClock(); |
|||
|
|||
/**
|
|||
* Dummy run of a sendmany operation |
|||
*/ |
|||
std::this_thread::sleep_for(std::chrono::milliseconds(2000)); |
|||
|
|||
std::cout << std::endl << "z_sendmany: **************** DUMMY RUN *****************" << std::endl; |
|||
std::cout << "z_sendmany: source of funds: " << fromAddress << std::endl; |
|||
std::cout << "z_sendmany: minconf: " << minconf << std::endl; |
|||
|
|||
for (SendManyRecipient & t : outputs) { |
|||
std::cout << "z_sendmany: send " << std::get<1>(t) << " to " << std::get<0>(t) << std::endl; |
|||
std::string memo = std::get<2>(t); |
|||
if (memo.size()>0) { |
|||
std::cout << " : memo = " << memo << std::endl; |
|||
} |
|||
} |
|||
|
|||
std::cout << "z_sendmany: checking balances and selecting coins and notes..." << std::endl; |
|||
std::this_thread::sleep_for(std::chrono::milliseconds(1000)); |
|||
|
|||
std::cout << "z_sendmany: performing a joinsplit..." << std::endl; |
|||
std::this_thread::sleep_for(std::chrono::milliseconds(3000)); |
|||
|
|||
std::cout << "z_sendmany: attempting to broadcast to network..." << std::endl; |
|||
std::this_thread::sleep_for(std::chrono::milliseconds(1000)); |
|||
|
|||
std::cout << "z_sendmany: operation complete!" << std::endl; |
|||
std::cout << "z_sendmany: ********************************************" << std::endl; |
|||
|
|||
stopExecutionClock(); |
|||
|
|||
|
|||
// dummy run will say that even number of outputs is success
|
|||
bool isEven = outputs.size() % 2 == 0; |
|||
//std::cout<< "here is r: " << r << std::endl;
|
|||
if (isEven) { |
|||
setState(OperationStatus::SUCCESS); |
|||
Object obj; |
|||
obj.push_back(Pair("dummy_txid", "4a1298544a1298544a1298544a1298544a129854")); |
|||
obj.push_back(Pair("dummy_fee", 0.0001)); // dummy fee
|
|||
setResult(Value(obj)); |
|||
} else { |
|||
setState(OperationStatus::FAILED); |
|||
errorCode = std::rand(); |
|||
errorMessage = "Dummy run tests error handling by not liking an odd number number of outputs."; |
|||
} |
|||
|
|||
} |
|||
|
@ -0,0 +1,32 @@ |
|||
// Copyright (c) 2016 The Zcash developers
|
|||
// Distributed under the MIT software license, see the accompanying
|
|||
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
|
|||
|
|||
#ifndef ASYNCRPCOPERATION_SENDMANY_H |
|||
#define ASYNCRPCOPERATION_SENDMANY_H |
|||
|
|||
#include "../asyncrpcoperation.h" |
|||
|
|||
#include "amount.h" |
|||
|
|||
#include <tuple> |
|||
|
|||
// A recipient is a tuple of address, amount, memo (optional if zaddr)
|
|||
typedef std::tuple<std::string, CAmount, std::string> SendManyRecipient; |
|||
|
|||
class AsyncRPCOperation_sendmany : public AsyncRPCOperation { |
|||
public: |
|||
AsyncRPCOperation_sendmany(std::string fromAddress, std::vector<SendManyRecipient> outputs, int minconf); |
|||
AsyncRPCOperation_sendmany(const AsyncRPCOperation_sendmany& orig); |
|||
virtual ~AsyncRPCOperation_sendmany(); |
|||
|
|||
virtual void main(); |
|||
|
|||
private: |
|||
std::string fromAddress; |
|||
std::vector<SendManyRecipient> outputs; |
|||
int minconf; |
|||
}; |
|||
|
|||
#endif /* ASYNCRPCOPERATION_SENDMANY_H */ |
|||
|
Loading…
Reference in new issue