From 004a84528d5d970f5129bfa9c34d418600014c4a Mon Sep 17 00:00:00 2001 From: timemarkovqtum Date: Thu, 4 Jul 2024 12:02:12 +0200 Subject: [PATCH] Port http server --- src/Makefile.am | 4 +- src/httprpc.cpp | 19 ++- src/httpserver.cpp | 121 +++++++++++++- src/httpserver.h | 32 +++- src/rpc/blockchain.cpp | 274 ++++++++++++++++++++++++++++++-- src/rpc/mining.cpp | 2 +- src/rpc/mining.h | 5 + src/rpc/rawtransaction_util.cpp | 56 ++++++- src/rpc/rawtransaction_util.h | 14 +- src/rpc/request.cpp | 10 ++ src/rpc/request.h | 27 ++++ src/rpc/server.cpp | 55 ++++++- src/rpc/server.h | 48 +++++- src/rpc/signmessage.cpp | 2 +- src/rpc/util.cpp | 16 +- src/rpc/util.h | 14 ++ src/validation.cpp | 29 ++++ src/validation.h | 4 + src/wallet/rpc/mining.cpp | 6 +- 19 files changed, 694 insertions(+), 44 deletions(-) diff --git a/src/Makefile.am b/src/Makefile.am index baaeab24c5..faf8cc7f20 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -508,7 +508,8 @@ libbitcoin_node_a_SOURCES = \ if ENABLE_WALLET libbitcoin_node_a_SOURCES += wallet/init.cpp \ wallet/stake.cpp \ - wallet/rpc/contract.cpp + wallet/rpc/contract.cpp \ + wallet/rpc/mining.cpp libbitcoin_node_a_CPPFLAGS += $(BDB_CPPFLAGS) endif if !ENABLE_WALLET @@ -742,6 +743,7 @@ libbitcoin_common_a_SOURCES = \ rpc/rawtransaction_util.cpp \ rpc/request.cpp \ rpc/util.cpp \ + rpc/contract_util.cpp \ scheduler.cpp \ script/descriptor.cpp \ script/miniscript.cpp \ diff --git a/src/httprpc.cpp b/src/httprpc.cpp index c72dbf10bc..3332c05cb6 100644 --- a/src/httprpc.cpp +++ b/src/httprpc.cpp @@ -33,7 +33,7 @@ class HTTPRPCTimer : public RPCTimerBase { public: HTTPRPCTimer(struct event_base* eventBase, std::function& func, int64_t millis) : - ev(eventBase, false, func) + ev(eventBase, false, nullptr, func) { struct timeval tv; tv.tv_sec = millis/1000; @@ -86,8 +86,14 @@ static void JSONErrorReply(HTTPRequest* req, const UniValue& objError, const Uni std::string strReply = JSONRPCReply(NullUniValue, objError, id); - req->WriteHeader("Content-Type", "application/json"); - req->WriteReply(nStatus, strReply); + if (req->isChunkMode()) { + // in chunk mode, we assume that the handler had already set the response content-type + req->Chunk(strReply); + req->ChunkEnd(); + } else { + req->WriteHeader("Content-Type", "application/json"); + req->WriteReply(nStatus, strReply); + } } //This function checks username and password against -rpcauth @@ -160,7 +166,7 @@ static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req) return false; } - JSONRPCRequest jreq; + JSONRPCRequestLong jreq(req); jreq.context = context; jreq.peerAddr = req->GetPeer().ToStringAddrPort(); if (!RPCAuthorized(authHeader.second, jreq.authUser)) { @@ -202,6 +208,11 @@ static bool HTTPReq_JSONRPC(const std::any& context, HTTPRequest* req) } UniValue result = tableRPC.execute(jreq); + if (jreq.isLongPolling) { + jreq.PollReply(result); + return true; + } + // Send reply strReply = JSONRPCReply(result, NullUniValue, jreq.id); diff --git a/src/httpserver.cpp b/src/httpserver.cpp index 71134d442f..837fc3679f 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -15,6 +15,7 @@ #include #include #include // For HTTP status codes +#include // For HTTP status codes #include #include #include @@ -561,14 +562,17 @@ static void httpevent_callback_fn(evutil_socket_t, short, void* data) delete self; } -HTTPEvent::HTTPEvent(struct event_base* base, bool _deleteWhenTriggered, const std::function& _handler): - deleteWhenTriggered(_deleteWhenTriggered), handler(_handler) +HTTPEvent::HTTPEvent(struct event_base* base, bool _deleteWhenTriggered, struct evbuffer *_databuf, const std::function& _handler): + deleteWhenTriggered(_deleteWhenTriggered), handler(_handler), databuf(_databuf) { ev = event_new(base, -1, 0, httpevent_callback_fn, this); assert(ev); } HTTPEvent::~HTTPEvent() { + if (databuf != NULL) { + evbuffer_free(databuf); + } event_free(ev); } void HTTPEvent::trigger(struct timeval* tv) @@ -579,13 +583,13 @@ void HTTPEvent::trigger(struct timeval* tv) evtimer_add(ev, tv); // trigger after timeval passed } HTTPRequest::HTTPRequest(struct evhttp_request* _req, const util::SignalInterrupt& interrupt, bool _replySent) - : req(_req), m_interrupt(interrupt), replySent(_replySent) + : req(_req), m_interrupt(interrupt), replySent(_replySent), startedChunkTransfer(false), connClosed(false) { } HTTPRequest::~HTTPRequest() { - if (!replySent) { + if (!replySent && !startedChunkTransfer) { // Keep track of whether reply was sent to avoid request leaks LogPrintf("%s: Unhandled request\n", __func__); WriteReply(HTTP_INTERNAL_SERVER_ERROR, "Unhandled request"); @@ -593,6 +597,66 @@ HTTPRequest::~HTTPRequest() // evhttpd cleans up the request, as long as a reply was sent. } +void HTTPRequest::waitClientClose() { + LogPrint(BCLog::HTTPPOLL, "wait for connection close\n"); + + // wait at most 5 seconds for client to close + for (int i = 0; i < 10 && IsRPCRunning() && !isConnClosed(); i++) { + std::unique_lock lock(cs); + closeCv.wait_for(lock, std::chrono::milliseconds(500)); + } + + if (isConnClosed()) { + LogPrint(BCLog::HTTPPOLL, "wait for connection close, ok\n"); + } else if (!IsRPCRunning()) { + LogPrint(BCLog::HTTPPOLL, "wait for connection close, RPC stopped\n"); + } else { + LogPrint(BCLog::HTTPPOLL, "wait for connection close, timeout after 5 seconds\n"); + } +} + +void HTTPRequest::startDetectClientClose() { + LogPrint(BCLog::HTTPPOLL, "start detect http connection close\n"); + // will need to call evhttp_send_reply_end to clean this up + auto conn = evhttp_request_get_connection(req); + + // evhttp_connection_set_closecb does not reliably detect client connection close unless we write to it. + // + // This problem is supposedly resolved in 2.1.8. See: https://github.com/libevent/libevent/issues/78 + // + // But we should just write to the socket to test liveness. This is useful for long-poll RPC calls to see + // if they should terminate the request early. + // + // More weirdness: if process received SIGTERM, the http event loop (in HTTPThread) returns prematurely with 1. + // In which case evhttp_send_reply_end doesn't seem to get called, and evhttp_connection_set_closecb is + // not called. BUT when the event base is freed, this callback IS called, and HTTPRequest is already freed. + // + // So, waitClientClose and startDetectClientClose should just not do anything if RPC is shutting down. + evhttp_connection_set_closecb(conn, [](struct evhttp_connection *conn, void *data) { + LogPrint(BCLog::HTTPPOLL, "http connection close detected\n"); + + if (IsRPCRunning()) { + auto req = (HTTPRequest*) data; + req->setConnClosed(); + } + }, (void *) this); +} + +void HTTPRequest::setConnClosed() { + std::lock_guard lock(cs); + connClosed = true; + closeCv.notify_all(); +} + +bool HTTPRequest::isConnClosed() { + std::lock_guard lock(cs); + return connClosed; +} + +bool HTTPRequest::isChunkMode() { + return startedChunkTransfer; +} + std::pair HTTPRequest::GetHeader(const std::string& hdr) const { const struct evkeyvalq* headers = evhttp_request_get_input_headers(req); @@ -624,6 +688,10 @@ std::string HTTPRequest::ReadBody() return rv; } +bool HTTPRequest::ReplySent() { + return replySent; +} + void HTTPRequest::WriteHeader(const std::string& hdr, const std::string& value) { struct evkeyvalq* headers = evhttp_request_get_output_headers(req); @@ -631,6 +699,49 @@ void HTTPRequest::WriteHeader(const std::string& hdr, const std::string& value) evhttp_add_header(headers, hdr.c_str(), value.c_str()); } +void HTTPRequest::ChunkEnd() { + assert(startedChunkTransfer && !replySent); + + HTTPEvent* ev = new HTTPEvent(eventBase, true, NULL, + std::bind(evhttp_send_reply_end, req)); + + ev->trigger(0); + + // If HTTPRequest is destroyed before connection is closed, evhttp seems to get messed up. + // We wait here for connection close before returning back to the handler, where HTTPRequest will be reclaimed. + waitClientClose(); + + replySent = true; + // `WriteReply` sets req to 0 to prevent req from being freed. But this is not enough in the case of long-polling. + // Something is still freed to early. + // req = 0; +} + +void HTTPRequest::Chunk(const std::string& chunk) { + assert(!replySent); + + int status = 200; + + if (!startedChunkTransfer) { + HTTPEvent* ev = new HTTPEvent(eventBase, true, NULL, + std::bind(evhttp_send_reply_start, req, status, + (const char*) NULL)); + ev->trigger(0); + + startDetectClientClose(); + startedChunkTransfer = true; + } + + + if (chunk.size() > 0) { + auto databuf = evbuffer_new(); // HTTPEvent will free this buffer + evbuffer_add(databuf, chunk.data(), chunk.size()); + HTTPEvent* ev = new HTTPEvent(eventBase, true, databuf, + std::bind(evhttp_send_reply_chunk, req, databuf)); + ev->trigger(0); + } +} + /** Closure sent to main thread to request a reply to be sent to * a HTTP request. * Replies must be sent in the main loop in the main http thread, @@ -647,7 +758,7 @@ void HTTPRequest::WriteReply(int nStatus, const std::string& strReply) assert(evb); evbuffer_add(evb, strReply.data(), strReply.size()); auto req_copy = req; - HTTPEvent* ev = new HTTPEvent(eventBase, true, [req_copy, nStatus]{ + HTTPEvent* ev = new HTTPEvent(eventBase, true, nullptr, [req_copy, nStatus]{ evhttp_send_reply(req_copy, nStatus, nullptr, nullptr); // Re-enable reading from the socket. This is the second part of the libevent // workaround above. diff --git a/src/httpserver.h b/src/httpserver.h index 9a49877f09..b41b42a9a1 100644 --- a/src/httpserver.h +++ b/src/httpserver.h @@ -8,6 +8,8 @@ #include #include #include +#include +#include namespace util { class SignalInterrupt; @@ -63,6 +65,14 @@ class HTTPRequest struct evhttp_request* req; const util::SignalInterrupt& m_interrupt; bool replySent; + bool startedChunkTransfer; + bool connClosed; + + std::mutex cs; + std::condition_variable closeCv; + + void startDetectClientClose(); + void waitClientClose(); public: explicit HTTPRequest(struct evhttp_request* req, const util::SignalInterrupt& interrupt, bool replySent = false); @@ -76,6 +86,10 @@ class HTTPRequest PUT }; + void setConnClosed(); + bool isConnClosed(); + bool isChunkMode(); + /** Get requested URI. */ std::string GetURI() const; @@ -129,6 +143,21 @@ class HTTPRequest * main thread, do not call any other HTTPRequest methods after calling this. */ void WriteReply(int nStatus, const std::string& strReply = ""); + + /** + * Start chunk transfer. Assume to be 200. + */ + void Chunk(const std::string& chunk); + + /** + * End chunk transfer. + */ + void ChunkEnd(); + + /** + * Is reply sent? + */ + bool ReplySent(); }; /** Get the query parameter value from request uri for a specified key, or std::nullopt if the key @@ -163,7 +192,7 @@ class HTTPEvent * deleteWhenTriggered deletes this event object after the event is triggered (and the handler called) * handler is the handler to call when the event is triggered. */ - HTTPEvent(struct event_base* base, bool deleteWhenTriggered, const std::function& handler); + HTTPEvent(struct event_base* base, bool deleteWhenTriggered, struct evbuffer *_databuf, const std::function& handler); ~HTTPEvent(); /** Trigger the event. If tv is 0, trigger it immediately. Otherwise trigger it after @@ -174,6 +203,7 @@ class HTTPEvent bool deleteWhenTriggered; std::function handler; private: + struct evbuffer *databuf; struct event* ev; }; diff --git a/src/rpc/blockchain.cpp b/src/rpc/blockchain.cpp index 50908e9f96..8ba974a8ed 100644 --- a/src/rpc/blockchain.cpp +++ b/src/rpc/blockchain.cpp @@ -61,16 +61,6 @@ using node::BlockManager; using node::NodeContext; using node::SnapshotMetadata; -struct CUpdatedBlock -{ - uint256 hash; - int height; -}; - -static GlobalMutex cs_blockchange; -static std::condition_variable cond_blockchange; -static CUpdatedBlock latestblock GUARDED_BY(cs_blockchange); - /* Calculate the difficulty for a given block index. */ double GetDifficulty(const CBlockIndex& blockindex) @@ -93,6 +83,105 @@ double GetDifficulty(const CBlockIndex& blockindex) return dDiff; } +double GetPoWMHashPS(ChainstateManager& chainman) +{ + if (chainman.m_best_header && chainman.m_best_header->nHeight >= Params().GetConsensus().nLastPOWBlock) + return 0; + + int nPoWInterval = 72; + int64_t nTargetSpacingWorkMin = 30, nTargetSpacingWork = 30; + + CChain& active_chain = chainman.ActiveChain(); + CBlockIndex* pindexGenesisBlock = active_chain.Genesis(); + CBlockIndex* pindex = pindexGenesisBlock; + CBlockIndex* pindexPrevWork = pindexGenesisBlock; + + while (pindex) + { + if (pindex->IsProofOfWork()) + { + int64_t nActualSpacingWork = pindex->GetBlockTime() - pindexPrevWork->GetBlockTime(); + nTargetSpacingWork = ((nPoWInterval - 1) * nTargetSpacingWork + nActualSpacingWork + nActualSpacingWork) / (nPoWInterval + 1); + nTargetSpacingWork = std::max(nTargetSpacingWork, nTargetSpacingWorkMin); + pindexPrevWork = pindex; + } + + pindex = pindex->pnext; + } + + return GetDifficulty(*CHECK_NONFATAL(active_chain.Tip())) * 4294.967296 / nTargetSpacingWork; +} + +double GetPoSKernelPS(ChainstateManager& chainman) +{ + int nPoSInterval = 72; + double dStakeKernelsTriedAvg = 0; + int nStakesHandled = 0, nStakesTime = 0; + + CBlockIndex* pindex = chainman.m_best_header; + CBlockIndex* pindexPrevStake = NULL; + + const Consensus::Params& consensusParams = Params().GetConsensus(); + bool dynamicStakeSpacing = true; + uint32_t stakeTimestampMask=consensusParams.StakeTimestampMask(0); + if(pindex) + { + dynamicStakeSpacing = pindex->nHeight < consensusParams.QIP9Height; + stakeTimestampMask=consensusParams.StakeTimestampMask(pindex->nHeight); + } + + while (pindex && nStakesHandled < nPoSInterval) + { + if (pindex->IsProofOfStake()) + { + if (pindexPrevStake) + { + dStakeKernelsTriedAvg += GetDifficulty(*CHECK_NONFATAL(pindexPrevStake)) * 4294967296.0; + if(dynamicStakeSpacing) + nStakesTime += pindexPrevStake->nTime - pindex->nTime; + nStakesHandled++; + } + pindexPrevStake = pindex; + } + + pindex = pindex->pprev; + } + + if(!dynamicStakeSpacing) + { + // Using a fixed denominator reduces the variation spikes + nStakesTime = consensusParams.TargetSpacing(chainman.m_best_header->nHeight) * nStakesHandled; + } + + double result = 0; + + if (nStakesTime) + result = dStakeKernelsTriedAvg / nStakesTime; + + result *= stakeTimestampMask + 1; + + return result; +} + +double GetEstimatedAnnualROI(ChainstateManager& chainman) +{ + double result = 0; + double networkWeight = GetPoSKernelPS(chainman); + CChain& active_chain = chainman.ActiveChain(); + CBlockIndex* pindex = chainman.m_best_header == 0 ? active_chain.Tip() : chainman.m_best_header; + int nHeight = pindex ? pindex->nHeight : 0; + const Consensus::Params& consensusParams = Params().GetConsensus(); + double subsidy = GetBlockSubsidy(nHeight, consensusParams); + int nBlocktimeDownscaleFactor = consensusParams.BlocktimeDownscaleFactor(nHeight); + if(networkWeight > 0) + { + // Formula: 100 * 675 blocks/day * 365 days * subsidy) / Network Weight + result = nBlocktimeDownscaleFactor * 24637500 * subsidy / networkWeight; + } + + return result; +} + static int ComputeNextBlockAndDepth(const CBlockIndex& tip, const CBlockIndex& blockindex, const CBlockIndex*& next) { next = tip.GetAncestor(blockindex.nHeight + 1); @@ -504,6 +593,171 @@ static RPCHelpMan getblockhash() }; } +static RPCHelpMan getaccountinfo() +{ + return RPCHelpMan{"getaccountinfo", + "\nGet contract details including balance, storage data and code.\n", + { + {"address", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The contract address"}, + }, + RPCResult{ + RPCResult::Type::OBJ, "", "", + { + {RPCResult::Type::STR, "address", "The address of the contract"}, + {RPCResult::Type::NUM, "balance", "The balance of the contract"}, + {RPCResult::Type::STR_HEX, "code", "The bytecode of the contract"}, + {RPCResult::Type::OBJ_DYN, "storage", "The storage data of the contract", + { + {RPCResult::Type::OBJ_DYN, "data", "The storage data entry", + { + {RPCResult::Type::STR_HEX, "hex", "The hex data"}, + }}, + }}, + {RPCResult::Type::OBJ, "vin", /*optional=*/true, "", + { + {RPCResult::Type::STR_HEX, "hash", "The data hash"}, + {RPCResult::Type::NUM, "nVout", "The vout index"}, + {RPCResult::Type::NUM, "value", "The vout value"}, + }}, + }}, + RPCExamples{ + HelpExampleCli("getaccountinfo", "eb23c0b3e6042821da281a2e2364feb22dd543e3") + + HelpExampleRpc("getaccountinfo", "eb23c0b3e6042821da281a2e2364feb22dd543e3") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + + LOCK(cs_main); + + std::string strAddr = request.params[0].get_str(); + if(strAddr.size() != 40 || !CheckHex(strAddr)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Incorrect address"); + + dev::Address addrAccount(strAddr); + if(!globalState->addressInUse(addrAccount)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Address does not exist"); + + UniValue result(UniValue::VOBJ); + + result.pushKV("address", strAddr); + result.pushKV("balance", CAmount(globalState->balance(addrAccount))); + std::vector code(globalState->code(addrAccount)); + auto storage(globalState->storage(addrAccount)); + + UniValue storageUV(UniValue::VOBJ); + for (auto j: storage) + { + UniValue e(UniValue::VOBJ); + e.pushKV(dev::toHex(dev::h256(j.second.first)), dev::toHex(dev::h256(j.second.second))); + storageUV.pushKV(j.first.hex(), e); + } + + result.pushKV("storage", storageUV); + + result.pushKV("code", HexStr(code)); + + std::unordered_map vins = globalState->vins(); + if(vins.count(addrAccount)){ + UniValue vin(UniValue::VOBJ); + valtype vchHash(vins[addrAccount].hash.asBytes()); + std::reverse(vchHash.begin(), vchHash.end()); + vin.pushKV("hash", HexStr(vchHash)); + vin.pushKV("nVout", uint64_t(vins[addrAccount].nVout)); + vin.pushKV("value", uint64_t(vins[addrAccount].value)); + result.pushKV("vin", vin); + } + return result; +}, + }; +} + +static RPCHelpMan getstorage() +{ + return RPCHelpMan{"getstorage", + "\nGet contract storage data.\n", + { + {"address", RPCArg::Type::STR_HEX, RPCArg::Optional::NO, "The contract address"}, + {"blocknum", RPCArg::Type::NUM, RPCArg::Default{-1}, "Number of block to get state from."}, + {"index", RPCArg::Type::NUM, RPCArg::Optional::OMITTED, "Zero-based index position of the storage"}, + }, + RPCResult{ + RPCResult::Type::OBJ_DYN, "", "The storage data of the contract", + { + {RPCResult::Type::OBJ_DYN, "data", "The storage data entry", + { + {RPCResult::Type::STR_HEX, "hex", "The hex data"}, + }}, + } + }, + RPCExamples{ + HelpExampleCli("getstorage", "eb23c0b3e6042821da281a2e2364feb22dd543e3") + + HelpExampleRpc("getstorage", "eb23c0b3e6042821da281a2e2364feb22dd543e3") + }, + [&](const RPCHelpMan& self, const JSONRPCRequest& request) -> UniValue +{ + + ChainstateManager& chainman = EnsureAnyChainman(request.context); + LOCK(cs_main); + + CChain& active_chain = chainman.ActiveChain(); + std::string strAddr = request.params[0].get_str(); + if(strAddr.size() != 40 || !CheckHex(strAddr)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Incorrect address"); + + TemporaryState ts(globalState); + if (!request.params[1].isNull()) + { + if (request.params[1].isNum()) + { + auto blockNum = request.params[1].getInt(); + if((blockNum < 0 && blockNum != -1) || blockNum > active_chain.Height()) + throw JSONRPCError(RPC_INVALID_PARAMS, "Incorrect block number"); + + if(blockNum != -1) + ts.SetRoot(uintToh256(active_chain[blockNum]->hashStateRoot), uintToh256(active_chain[blockNum]->hashUTXORoot)); + + } else { + throw JSONRPCError(RPC_INVALID_PARAMS, "Incorrect block number"); + } + } + + dev::Address addrAccount(strAddr); + if(!globalState->addressInUse(addrAccount)) + throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Address does not exist"); + + UniValue result(UniValue::VOBJ); + + bool onlyIndex = !request.params[2].isNull(); + unsigned index = 0; + if (onlyIndex) + index = request.params[2].getInt(); + + auto storage(globalState->storage(addrAccount)); + + if (onlyIndex) + { + if (index >= storage.size()) + { + std::ostringstream stringStream; + stringStream << "Storage size: " << storage.size() << " got index: " << index; + throw JSONRPCError(RPC_INVALID_PARAMS, stringStream.str()); + } + auto elem = std::next(storage.begin(), index); + UniValue e(UniValue::VOBJ); + + storage = {{elem->first, {elem->second.first, elem->second.second}}}; + } + for (const auto& j: storage) + { + UniValue e(UniValue::VOBJ); + e.pushKV(dev::toHex(dev::h256(j.second.first)), dev::toHex(dev::h256(j.second.second))); + result.pushKV(j.first.hex(), e); + } + return result; +}, + }; +} + static RPCHelpMan getblockheader() { return RPCHelpMan{"getblockheader", diff --git a/src/rpc/mining.cpp b/src/rpc/mining.cpp index ff39a31a43..aa2b62c332 100644 --- a/src/rpc/mining.cpp +++ b/src/rpc/mining.cpp @@ -56,7 +56,7 @@ using node::UpdateTime; * If 'height' is -1, compute the estimate from current chain tip. * If 'height' is a valid block height, compute the estimate at the time when a given block was found. */ -static UniValue GetNetworkHashPS(int lookup, int height, const CChain& active_chain) { +UniValue GetNetworkHashPS(int lookup, int height, const CChain& active_chain) { if (lookup < -1 || lookup == 0) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid nblocks. Must be a positive number or -1."); } diff --git a/src/rpc/mining.h b/src/rpc/mining.h index acc74e1dcc..97c0d18ada 100644 --- a/src/rpc/mining.h +++ b/src/rpc/mining.h @@ -5,7 +5,12 @@ #ifndef BITCOIN_RPC_MINING_H #define BITCOIN_RPC_MINING_H +class UniValue; +class CChain; + /** Default max iterations to try in RPC generatetodescriptor, generatetoaddress, and generateblock. */ static const uint64_t DEFAULT_MAX_TRIES{1000000}; +UniValue GetNetworkHashPS(int lookup, int height, const CChain& active_chain); + #endif // BITCOIN_RPC_MINING_H diff --git a/src/rpc/rawtransaction_util.cpp b/src/rpc/rawtransaction_util.cpp index c0f505fdf6..9ce9981a9d 100644 --- a/src/rpc/rawtransaction_util.cpp +++ b/src/rpc/rawtransaction_util.cpp @@ -129,7 +129,7 @@ std::vector> ParseOutputs(const UniValue& out return parsed_outputs; } -void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in) +void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in, IRawContract* rawContract) { UniValue outputs(UniValue::VOBJ); outputs = NormalizeOutputs(outputs_in); @@ -143,7 +143,7 @@ void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in) } } -CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional rbf) +CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional rbf, IRawContract* rawContract) { CMutableTransaction rawTx; @@ -155,7 +155,7 @@ CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniVal } AddInputs(rawTx, inputs_in, rbf); - AddOutputs(rawTx, outputs_in); + AddOutputs(rawTx, outputs_in, rawContract); if (rbf.has_value() && rbf.value() && rawTx.vin.size() > 0 && !SignalsOptInRBF(CTransaction(rawTx))) { throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter combination: Sequence number(s) contradict replaceable option"); @@ -304,6 +304,22 @@ void ParsePrevouts(const UniValue& prevTxsUnival, FillableSigningProvider* keyst void CheckSenderSignatures(CMutableTransaction& mtx) { + // Check the sender signatures are inside the outputs, before signing the inputs + if(mtx.HasOpSender()) + { + int nOut = 0; + for (const auto& output : mtx.vout) + { + if(output.scriptPubKey.HasOpSender()) + { + CScript senderPubKey, senderSig; + if(!ExtractSenderData(output.scriptPubKey, &senderPubKey, &senderSig)) + throw JSONRPCError(RPC_INVALID_PARAMETER, "Missing contract sender signature," + "use signrawsendertransactionwithwallet or signrawsendertransactionwithkey to sign the outputs"); + } + nOut++; + } + } } void SignTransaction(CMutableTransaction& mtx, const SigningProvider* keystore, const std::map& coins, const UniValue& hashType, UniValue& result) @@ -339,6 +355,40 @@ void SignTransactionResultToJSON(CMutableTransaction& mtx, bool complete, const } } +static void TxOutErrorToJSON(const CTxOut& output, UniValue& vErrorsRet, const std::string& strMessage) +{ + UniValue entry(UniValue::VOBJ); + entry.pushKV("amount", ValueFromAmount(output.nValue)); + entry.pushKV("scriptPubKey", HexStr(MakeUCharSpan(output.scriptPubKey))); + entry.pushKV("error", strMessage); + vErrorsRet.push_back(entry); +} + +void SignTransactionOutput(CMutableTransaction &mtx, FillableSigningProvider *keystore, const UniValue &hashType, UniValue &result) +{ + int nHashType = ParseSighashString(hashType); + + // Script verification errors + std::map output_errors; + + bool complete = SignTransactionOutput(mtx, keystore, nHashType, output_errors); + SignTransactionOutputResultToJSON(mtx, complete, output_errors, result); +} + void SignTransactionOutputResultToJSON(CMutableTransaction &mtx, bool complete, std::map &output_errors, UniValue &result) { + // Make errors UniValue + UniValue vErrors(UniValue::VARR); + for (const auto& err_pair : output_errors) { + TxOutErrorToJSON(mtx.vout.at(err_pair.first), vErrors, err_pair.second); + } + + result.pushKV("hex", EncodeHexTx(CTransaction(mtx))); + result.pushKV("complete", complete); + if (!vErrors.empty()) { + if (result.exists("errors")) { + vErrors.push_backV(result["errors"].getValues()); + } + result.pushKV("errors", vErrors); + } } diff --git a/src/rpc/rawtransaction_util.h b/src/rpc/rawtransaction_util.h index 950365b4ee..6ff61d6320 100644 --- a/src/rpc/rawtransaction_util.h +++ b/src/rpc/rawtransaction_util.h @@ -19,6 +19,15 @@ class Coin; class COutPoint; class SigningProvider; +/** + * @brief The IRawContract class Parse the contract output for raw transaction + */ +class IRawContract +{ +public: + virtual void addContract(CMutableTransaction& rawTx, const UniValue& contract) = 0; +}; + /** * Sign a transaction with the given keystore and previous transactions * @@ -50,11 +59,12 @@ UniValue NormalizeOutputs(const UniValue& outputs_in); std::vector> ParseOutputs(const UniValue& outputs); /** Normalize, parse, and add outputs to the transaction */ -void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in); +void AddOutputs(CMutableTransaction& rawTx, const UniValue& outputs_in, IRawContract* rawContract = nullptr); /** Create a transaction from univalue parameters */ -CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional rbf); +CMutableTransaction ConstructTransaction(const UniValue& inputs_in, const UniValue& outputs_in, const UniValue& locktime, std::optional rbf, IRawContract* rawContract = nullptr); +void SignTransactionOutput(CMutableTransaction& mtx, FillableSigningProvider *keystore, const UniValue& hashType, UniValue& result); void SignTransactionOutputResultToJSON(CMutableTransaction& mtx, bool complete, std::map& output_errors, UniValue& result); void CheckSenderSignatures(CMutableTransaction& mtx); diff --git a/src/rpc/request.cpp b/src/rpc/request.cpp index b7acd62ee3..8f061766c6 100644 --- a/src/rpc/request.cpp +++ b/src/rpc/request.cpp @@ -195,3 +195,13 @@ void JSONRPCRequest::parse(const UniValue& valRequest) else throw JSONRPCError(RPC_INVALID_REQUEST, "Params must be an array or object"); } + +bool JSONRPCRequest::PollAlive() { return false;} + +void JSONRPCRequest::PollStart() {} + +void JSONRPCRequest::PollPing() {} + +void JSONRPCRequest::PollCancel() {} + +void JSONRPCRequest::PollReply(const UniValue& result) {} diff --git a/src/rpc/request.h b/src/rpc/request.h index a682c58d96..6e749ce843 100644 --- a/src/rpc/request.h +++ b/src/rpc/request.h @@ -36,8 +36,35 @@ class JSONRPCRequest std::string authUser; std::string peerAddr; std::any context; + bool isLongPolling = false; + void *httpreq = nullptr; void parse(const UniValue& valRequest); + + /** + * Start long-polling + */ + virtual void PollStart(); + + /** + * Ping long-poll connection with an empty character to make sure it's still alive. + */ + virtual void PollPing(); + + /** + * Returns whether the underlying long-poll connection is still alive. + */ + virtual bool PollAlive(); + + /** + * End a long poll request. + */ + virtual void PollCancel(); + + /** + * Return the JSON result of a long poll request + */ + virtual void PollReply(const UniValue& result); }; #endif // BITCOIN_RPC_REQUEST_H diff --git a/src/rpc/server.cpp b/src/rpc/server.cpp index e7d1e3db4e..bafb0c7fc3 100644 --- a/src/rpc/server.cpp +++ b/src/rpc/server.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #include @@ -30,7 +31,6 @@ #include static GlobalMutex g_rpc_warmup_mutex; -static std::atomic g_rpc_running{false}; static bool fRPCInWarmup GUARDED_BY(g_rpc_warmup_mutex) = true; static std::string rpcWarmupStatus GUARDED_BY(g_rpc_warmup_mutex) = "RPC server started"; /* Timer-creating functions */ @@ -324,11 +324,6 @@ void StopRPC() }); } -bool IsRPCRunning() -{ - return g_rpc_running; -} - void RpcInterruptionPoint() { if (!IsRPCRunning()) throw JSONRPCError(RPC_CLIENT_NOT_CONNECTED, "Shutting down"); @@ -355,6 +350,49 @@ bool RPCIsInWarmup(std::string *outStatus) return fRPCInWarmup; } +JSONRPCRequestLong::JSONRPCRequestLong(HTTPRequest *_req) { + httpreq = _req; +} + +bool JSONRPCRequestLong::PollAlive() { + return !req()->isConnClosed(); +} + +void JSONRPCRequestLong::PollStart() { + // send an empty space to the client to ensure that it's still alive. + assert(!isLongPolling); + req()->WriteHeader("Content-Type", "application/json"); + req()->WriteHeader("Connection", "close"); + req()->Chunk(std::string(" ")); + isLongPolling = true; +} + +void JSONRPCRequestLong::PollPing() { + assert(isLongPolling); + // send an empty space to the client to ensure that it's still alive. + req()->Chunk(std::string(" ")); +} + +void JSONRPCRequestLong::PollCancel() { + assert(isLongPolling); + req()->ChunkEnd(); +} + +void JSONRPCRequestLong::PollReply(const UniValue& result) { + assert(isLongPolling); + UniValue reply(UniValue::VOBJ); + reply.pushKV("result", result); + reply.pushKV("error", NullUniValue); + reply.pushKV("id", id); + + req()->Chunk(reply.write() + "\n"); + req()->ChunkEnd(); +} + +HTTPRequest* JSONRPCRequestLong::req() { + return (HTTPRequest*)httpreq; +} + bool IsDeprecatedRPCEnabled(const std::string& method) { const std::vector enabled_methods = gArgs.GetArgs("-deprecatedrpc"); @@ -398,9 +436,10 @@ std::string JSONRPCExecBatch(const JSONRPCRequest& jreq, const UniValue& vReq) * Process named arguments into a vector of positional arguments, based on the * passed-in specification for the RPC call's arguments. */ -static inline JSONRPCRequest transformNamedArguments(const JSONRPCRequest& in, const std::vector>& argNames) +static inline JSONRPCRequest& transformNamedArguments(const JSONRPCRequest& _in, const std::vector>& argNames) { - JSONRPCRequest out = in; + JSONRPCRequest in = _in; + JSONRPCRequest& out = (JSONRPCRequest&)_in; out.params = UniValue(UniValue::VARR); // Build a map of parameters, and remove ones that have been processed, so that we can throw a focused error if // there is an unknown one. diff --git a/src/rpc/server.h b/src/rpc/server.h index b8348e4aa6..6564151f09 100644 --- a/src/rpc/server.h +++ b/src/rpc/server.h @@ -8,15 +8,22 @@ #include #include +#include #include #include #include #include +#include +#include +#include #include +#include class CRPCCommand; +class ChainstateManager; +class HTTPRequest; namespace RPCServer { @@ -24,8 +31,41 @@ namespace RPCServer void OnStopped(std::function slot); } -/** Query whether RPC is running */ -bool IsRPCRunning(); +class JSONRPCRequestLong : public JSONRPCRequest +{ +public: + JSONRPCRequestLong(HTTPRequest *_req); + + /** + * Start long-polling + */ + void PollStart() override; + + /** + * Ping long-poll connection with an empty character to make sure it's still alive. + */ + void PollPing() override; + + /** + * Returns whether the underlying long-poll connection is still alive. + */ + bool PollAlive() override; + + /** + * End a long poll request. + */ + void PollCancel() override; + + /** + * Return the JSON result of a long poll request + */ + void PollReply(const UniValue& result) override; + + /** + * Return the http request + */ + HTTPRequest* req(); +}; /** Throw JSONRPCError if RPC is not running */ void RpcInterruptionPoint(); @@ -176,6 +216,10 @@ bool IsDeprecatedRPCEnabled(const std::string& method); extern CRPCTable tableRPC; +extern double GetPoWMHashPS(ChainstateManager& chainman); +extern double GetPoSKernelPS(ChainstateManager& chainman); +extern double GetEstimatedAnnualROI(ChainstateManager& chainman); + void StartRPC(); void InterruptRPC(); void StopRPC(); diff --git a/src/rpc/signmessage.cpp b/src/rpc/signmessage.cpp index 8c752ba1fd..0f986dfdd0 100644 --- a/src/rpc/signmessage.cpp +++ b/src/rpc/signmessage.cpp @@ -19,7 +19,7 @@ static RPCHelpMan verifymessage() return RPCHelpMan{"verifymessage", "Verify a signed message.", { - {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The bitcoin address to use for the signature."}, + {"address", RPCArg::Type::STR, RPCArg::Optional::NO, "The qtum address to use for the signature."}, {"signature", RPCArg::Type::STR, RPCArg::Optional::NO, "The signature provided by the signer in base 64 encoding (see signmessage)."}, {"message", RPCArg::Type::STR, RPCArg::Optional::NO, "The message that was signed."}, }, diff --git a/src/rpc/util.cpp b/src/rpc/util.cpp index 51c88cc1ba..5b24b06f80 100644 --- a/src/rpc/util.cpp +++ b/src/rpc/util.cpp @@ -28,7 +28,17 @@ #include const std::string UNIX_EPOCH_TIME = "UNIX epoch time"; -const std::string EXAMPLE_ADDRESS[2] = {"bc1q09vm5lfy0j5reeulh4x5752q25uqqvz34hufdl", "bc1q02ad21edsxd23d32dfgqqsz4vv4nmtfzuklhy3"}; +const std::string EXAMPLE_ADDRESS[2] = {"QM72Sfpbz1BPpXFHz9m3CdqATR44Jvaydd", "QX1GkJdye9WoUnrE2v6ZQhQ72EUVDtGXQX"}; + +GlobalMutex cs_blockchange; +std::condition_variable cond_blockchange; +CUpdatedBlock latestblock GUARDED_BY(cs_blockchange); +std::atomic g_rpc_running{false}; + +bool IsRPCRunning() +{ + return g_rpc_running; +} std::string GetAllOutputTypes() { @@ -154,12 +164,12 @@ std::string ShellQuoteIfNeeded(const std::string& s) std::string HelpExampleCli(const std::string& methodname, const std::string& args) { - return "> bitcoin-cli " + methodname + " " + args + "\n"; + return "> qtum-cli " + methodname + " " + args + "\n"; } std::string HelpExampleCliNamed(const std::string& methodname, const RPCArgList& args) { - std::string result = "> bitcoin-cli -named " + methodname; + std::string result = "> qtum-cli -named " + methodname; for (const auto& argpair: args) { const auto& value = argpair.second.isStr() ? argpair.second.get_str() diff --git a/src/rpc/util.h b/src/rpc/util.h index ad3ed97b2e..dcd1d7cc9b 100644 --- a/src/rpc/util.h +++ b/src/rpc/util.h @@ -46,6 +46,20 @@ static constexpr bool DEFAULT_RPC_DOC_CHECK{ #endif }; +struct CUpdatedBlock +{ + uint256 hash; + int height; +}; + +extern GlobalMutex cs_blockchange; +extern std::condition_variable cond_blockchange; +extern CUpdatedBlock latestblock GUARDED_BY(cs_blockchange); +extern std::atomic g_rpc_running; + +/** Query whether RPC is running */ +bool IsRPCRunning(); + /** * String used to describe UNIX epoch time in documentation, factored out to a * constant for consistency. diff --git a/src/validation.cpp b/src/validation.cpp index 3dfa98a735..2e6ff52f1b 100644 --- a/src/validation.cpp +++ b/src/validation.cpp @@ -65,6 +65,8 @@ #include #include +#include + #include #include #include @@ -2207,6 +2209,9 @@ std::vector CallContract(const dev::Address& addrContract, std::v return {}; } +void writeVMlog(const std::vector& res, CChain& chain, const CTransaction& tx, const CBlock& block){ +} + /** Apply the effects of this block (with given index) on the UTXO set represented by coins. * Validity checks that depend on the UTXO set are also done; ConnectBlock() * can fail if those validity checks fail (among other reasons). */ @@ -5263,6 +5268,30 @@ double GuessVerificationProgress(const ChainTxData& data, const CBlockIndex *pin return std::min(pindex->nChainTx / fTxTotal, 1.0); } +std::string exceptedMessage(const dev::eth::TransactionException& excepted, const dev::bytes& output) +{ + std::string message; + try + { + // Process the revert message from the output + if(excepted == dev::eth::TransactionException::RevertInstruction) + { + // Get function: Error(string) + dev::bytesConstRef oRawData(&output); + dev::bytes errorFunc = oRawData.cropped(0, 4).toBytes(); + if(dev::toHex(errorFunc) == "08c379a0") + { + dev::bytesConstRef oData = oRawData.cropped(4); + message = dev::eth::ABIDeserialiser::deserialise(oData); + } + } + } + catch(...) + {} + + return message; +} + std::optional ChainstateManager::SnapshotBlockhash() const { LOCK(::cs_main); diff --git a/src/validation.h b/src/validation.h index 0461f42c06..c8420bc3bf 100644 --- a/src/validation.h +++ b/src/validation.h @@ -466,6 +466,10 @@ bool GetSpentCoinFromMainChain(const CBlockIndex* pforkPrev, COutPoint prevoutSt std::vector CallContract(const dev::Address& addrContract, std::vector opcode, Chainstate& chainstate, const dev::Address& sender = dev::Address(), uint64_t gasLimit=0, CAmount nAmount=0); +void writeVMlog(const std::vector& res, CChain& chain, const CTransaction& tx = CTransaction(), const CBlock& block = CBlock()); + +std::string exceptedMessage(const dev::eth::TransactionException& excepted, const dev::bytes& output); + enum DisconnectResult { DISCONNECT_OK, // All good. diff --git a/src/wallet/rpc/mining.cpp b/src/wallet/rpc/mining.cpp index 481cf6917d..3bbd71ae35 100644 --- a/src/wallet/rpc/mining.cpp +++ b/src/wallet/rpc/mining.cpp @@ -86,8 +86,8 @@ RPCHelpMan getmininginfo() if (BlockAssembler::m_last_block_weight) obj.pushKV("currentblockweight", *BlockAssembler::m_last_block_weight); if (BlockAssembler::m_last_block_num_txs) obj.pushKV("currentblocktx", *BlockAssembler::m_last_block_num_txs); - diff.pushKV("proof-of-work", (double)GetDifficulty(GetLastBlockIndex(chainman.m_best_header, false))); - diff.pushKV("proof-of-stake", (double)GetDifficulty(GetLastBlockIndex(chainman.m_best_header, true))); + diff.pushKV("proof-of-work", (double)GetDifficulty(*CHECK_NONFATAL(GetLastBlockIndex(chainman.m_best_header, false)))); + diff.pushKV("proof-of-stake", (double)GetDifficulty(*CHECK_NONFATAL(GetLastBlockIndex(chainman.m_best_header, true)))); diff.pushKV("search-interval", (int)lastCoinStakeSearchInterval); obj.pushKV("difficulty", diff); @@ -173,7 +173,7 @@ RPCHelpMan getstakinginfo() if (BlockAssembler::m_last_block_num_txs) obj.pushKV("currentblocktx", *BlockAssembler::m_last_block_num_txs); obj.pushKV("pooledtx", (uint64_t)mempool.size()); - obj.pushKV("difficulty", (double)GetDifficulty(GetLastBlockIndex(chainman.m_best_header, true))); + obj.pushKV("difficulty", (double)GetDifficulty(*CHECK_NONFATAL(GetLastBlockIndex(chainman.m_best_header, true)))); obj.pushKV("search-interval", (int)lastCoinStakeSearchInterval); obj.pushKV("weight", (uint64_t)nStakerWeight);