diff --git a/.github/workflows/cmake-multi-platform.yml b/.github/workflows/cmake-multi-platform.yml index e78e078c..b0363440 100644 --- a/.github/workflows/cmake-multi-platform.yml +++ b/.github/workflows/cmake-multi-platform.yml @@ -36,6 +36,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + submodules: 'true' - name: Set reusable strings id: strings diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ba983e1e..99e1a823 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -32,6 +32,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + submodules: 'true' - name: Configure CMake run: > diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..3e7c04a7 --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "submodules/core"] + path = submodules/core + url = git@github.com:qubic/core.git diff --git a/CMakeLists.txt b/CMakeLists.txt index b6a3bb2e..580b0afa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -11,9 +11,11 @@ SET(FILES ${CMAKE_SOURCE_DIR}/asset_utils.cpp ${CMAKE_SOURCE_DIR}/msvault.cpp ${CMAKE_SOURCE_DIR}/node_utils.cpp ${CMAKE_SOURCE_DIR}/nostromo.cpp + ${CMAKE_SOURCE_DIR}/oracle_utils.cpp ${CMAKE_SOURCE_DIR}/proposal.cpp ${CMAKE_SOURCE_DIR}/qbond.cpp ${CMAKE_SOURCE_DIR}/qearn.cpp + ${CMAKE_SOURCE_DIR}/qpi_adapter.cpp ${CMAKE_SOURCE_DIR}/qswap.cpp ${CMAKE_SOURCE_DIR}/quottery.cpp ${CMAKE_SOURCE_DIR}/qutil.cpp @@ -38,6 +40,8 @@ SET(HEADER_FILES msvault.h node_utils.h nostromo.h + qpi_adapter.h + oracle_utils.h prompt.h proposal.h qbond.h @@ -60,8 +64,16 @@ SET(HEADER_FILES if(MSVC) add_definitions(-D_CRT_SECURE_NO_WARNINGS) endif() + ADD_EXECUTABLE(qubic-cli main.cpp ${FILES} ${HEADER_FILES}) set_property(TARGET qubic-cli PROPERTY COMPILE_WARNING_AS_ERROR ON) +target_include_directories(qubic-cli PUBLIC ${CMAKE_SOURCE_DIR}/submodules) +target_include_directories(qubic-cli PUBLIC ${CMAKE_SOURCE_DIR}/submodules/core) +target_include_directories(qubic-cli PUBLIC ${CMAKE_SOURCE_DIR}/submodules/core/src) +if(CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU") + target_compile_options(${PROJECT_NAME} PRIVATE -mrdrnd -mavx2) +endif() + ADD_LIBRARY(fourq-qubic SHARED fourq_qubic.cpp) set_property(TARGET fourq-qubic PROPERTY SOVERSION 1) target_compile_options(fourq-qubic PRIVATE -DBUILD_4Q_LIB) diff --git a/README.md b/README.md index 103a3a1d..1116e00b 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,12 @@ Commands: -getexecutionfeemultiplier Get the current multiplier for the conversion of raw execution time to contract execution fees, valid seed and node ip/port are required. +[ORACLE COMMANDS] + -getoraclequery <...> + Get information about oracle queries. Skip arguments to get detailed documentation. + -queryoracle [INTERFACE] [QUERY_STRING] [TIMEOUT_IN_SECONDS] + Send user oracle query. Skip arguments to get detailed documentation. + [SMART CONTRACT COMMANDS] -callcontractfunction Call a contract function of contract index and print the output. Valid node ip/port are required. @@ -452,6 +458,11 @@ enabled. ### BUILD +Run the following command to get `qubic/core` as a submodule: +``` +git submodule update --init --recursive +``` + On Linux or MacOS, make sure `cmake` and `make` commands are installed and then run: ``` mkdir build; diff --git a/argparser.h b/argparser.h index 2b395bd9..ee0e9950 100644 --- a/argparser.h +++ b/argparser.h @@ -165,6 +165,11 @@ void print_help() printf("\t-getexecutionfeemultiplier\n"); printf("\t\tGet the current multiplier for the conversion of raw execution time to contract execution fees, valid seed and node ip/port are required.\t\n"); + printf("\n[ORACLE COMMANDS]\n"); + printf("\t-getoraclequery <...>\n"); + printf("\t\tGet information about oracle queries. Skip arguments to get detailed documentation.\n"); + printf("\t-queryoracle [INTERFACE] [QUERY_STRING] [TIMEOUT_IN_SECONDS]\n"); + printf("\t\tSend user oracle query. Skip arguments to get detailed documentation.\n"); printf("\n[SMART CONTRACT COMMANDS]\n"); printf("\t-callcontractfunction \n"); @@ -1163,6 +1168,35 @@ void parseArgument(int argc, char** argv) break; } + /*********************** + *** ORACLE COMMANDS *** + ***********************/ + + if (strcmp(argv[i], "-getoraclequery") == 0) + { + g_cmd = GET_ORACLE_QUERY; + if (i + 1 < argc) + g_paramString1 = argv[i + 1]; + if (i + 2 < argc) + g_paramString2 = argv[i + 2]; + i += 3; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-queryoracle") == 0) + { + g_cmd = SEND_ORACLE_QUERY_TX; + if (i + 1 < argc) + g_paramString1 = argv[i + 1]; + if (i + 2 < argc) + g_paramString2 = argv[i + 2]; + if (i + 3 < argc) + g_paramString3 = argv[i + 3]; + i += 4; + CHECK_OVER_PARAMETERS + break; + } + /*********************** ***** QX COMMANDS ***** ***********************/ diff --git a/connection.cpp b/connection.cpp index f6e34e10..de234620 100644 --- a/connection.cpp +++ b/connection.cpp @@ -29,7 +29,6 @@ #include "qutil.h" #include "qbond.h" -#define DEFAULT_TIMEOUT_MSEC 1000 #ifdef _MSC_VER @@ -113,7 +112,7 @@ static int connect(const char* nodeIp, int nodePort) #endif -QubicConnection::QubicConnection(const char* nodeIp, int nodePort) +QubicConnection::QubicConnection(const char* nodeIp, int nodePort, unsigned long timeoutMillisec) { memset(mNodeIp, 0, 32); memcpy(mNodeIp, nodeIp, strlen(nodeIp)); @@ -136,7 +135,8 @@ QubicConnection::QubicConnection(const char* nodeIp, int nodePort) *((RequestComputors*)data) = receivePacketWithHeaderAs(); } catch(std::logic_error) {} - setTimeout(mSocket, SO_RCVTIMEO, DEFAULT_TIMEOUT_MSEC); + setTimeout(mSocket, SO_RCVTIMEO, timeoutMillisec); + setTimeout(mSocket, SO_SNDTIMEO, timeoutMillisec); } void QubicConnection::getHandshakeData(std::vector& buffer) diff --git a/connection.h b/connection.h index 0dc3cc3c..303785a9 100644 --- a/connection.h +++ b/connection.h @@ -5,11 +5,13 @@ #include #include +#define DEFAULT_TIMEOUT_MSEC 1000 + // Not thread safe class QubicConnection { public: - QubicConnection(const char* nodeIp, int nodePort); + QubicConnection(const char* nodeIp, int nodePort, unsigned long timeoutMillisec = DEFAULT_TIMEOUT_MSEC); ~QubicConnection(); // Establish connection to mNodePort on node mNodeIp. @@ -52,9 +54,9 @@ class QubicConnection typedef std::shared_ptr QCPtr; -static QCPtr make_qc(const char* nodeIp, int nodePort) +static QCPtr make_qc(const char* nodeIp, int nodePort, unsigned long timeoutMsec = DEFAULT_TIMEOUT_MSEC) { - return std::make_shared(nodeIp, nodePort); + return std::make_shared(nodeIp, nodePort, timeoutMsec); } class EndResponseReceived : public std::runtime_error @@ -62,3 +64,9 @@ class EndResponseReceived : public std::runtime_error public: explicit EndResponseReceived(const char* message = "Received end response message") : std::runtime_error(message) {} }; + +class ConnectionTimeout : public std::runtime_error +{ +public: + explicit ConnectionTimeout(const char* message = "Connection timeout") : std::runtime_error(message) {} +}; diff --git a/defines.h b/defines.h index cfef0d6c..53adf69d 100644 --- a/defines.h +++ b/defines.h @@ -5,7 +5,7 @@ #include #include -#define DEFAULT_SCHEDULED_TICK_OFFSET 20 +#define DEFAULT_SCHEDULED_TICK_OFFSET 8 #define DEFAULT_NODE_PORT 21841 #define DEFAULT_NODE_IP "127.0.0.1" #define NUMBER_OF_TRANSACTIONS_PER_TICK 1024 @@ -61,6 +61,4 @@ #define QX_CONTRACT_INDEX 1 #define QX_FEE_FUNCTION_INDEX 1 -constexpr unsigned long long CONTRACT_ACTION_TRACKER_SIZE = 16 * 1024 * 1024; - #define Q_SLEEP(X) std::this_thread::sleep_for (std::chrono::milliseconds(X)); \ No newline at end of file diff --git a/global.h b/global.h index 59ad68d7..8de194fe 100644 --- a/global.h +++ b/global.h @@ -18,6 +18,7 @@ const char* g_proposalString = ""; const char* g_voteValueString = ""; const char* g_paramString1 = ""; const char* g_paramString2 = ""; +const char* g_paramString3 = ""; const char* g_invokeContractProcedureInputFormat = ""; const char* g_callContractFunctionInputFormat = ""; const char* g_callContractFunctionOutputFormat = ""; diff --git a/main.cpp b/main.cpp index b757d239..902b06d0 100644 --- a/main.cpp +++ b/main.cpp @@ -10,6 +10,7 @@ #include "key_utils.h" #include "sanity_check.h" #include "sc_utils.h" +#include "oracle_utils.h" #include "quottery.h" #include "qutil.h" #include "qx.h" @@ -390,6 +391,15 @@ int run(int argc, char* argv[]) sanityCheckSeed(g_seed); getExecutionFeeMultiplier(g_nodeIp, g_nodePort, g_seed); break; + case GET_ORACLE_QUERY: + sanityCheckNode(g_nodeIp, g_nodePort); + processGetOracleQuery(g_nodeIp, g_nodePort, g_paramString1, g_paramString2); + break; + case SEND_ORACLE_QUERY_TX: + sanityCheckNode(g_nodeIp, g_nodePort); + sanityCheckSeed(g_seed); + makeOracleUserQueryTransaction(g_nodeIp, g_nodePort, g_seed, g_paramString1, g_paramString2, g_paramString3, g_offsetScheduledTick); + break; case QUTIL_SEND_TO_MANY_V1: sanityCheckNode(g_nodeIp, g_nodePort); sanityCheckSeed(g_seed); diff --git a/oracle_utils.cpp b/oracle_utils.cpp new file mode 100644 index 00000000..89af197d --- /dev/null +++ b/oracle_utils.cpp @@ -0,0 +1,740 @@ +#include +#include + +#include "core/src/network_messages/common_def.h" +#include "core/src/network_messages/oracles.h" + +#include "qpi_adapter.h" + +#include "oracle_utils.h" +#include "logger.h" +#include "utils.h" +#include "structs.h" +#include "connection.h" +#include "key_utils.h" +#include "wallet_utils.h" + + +void printGetOracleQueryHelpAndExit() +{ + LOG("qubic-cli [...] -getoraclequery [QUERY_ID]\n"); + LOG(" Print the oracle query, metadata, and reply if available.\n"); + LOG("qubic-cli [...] -getoraclequery pending\n"); + LOG(" Print the query IDs for all pending queries.\n"); + LOG("qubic-cli [...] -getoraclequery pending+\n"); + LOG(" Print the oracle query, metadata, and reply if available for each query ID received by pending.\n"); + LOG("qubic-cli [...] -getoraclequery all [TICK]\n"); + LOG(" Print the query IDs of all queries started in the given tick.\n"); + LOG("qubic-cli [...] -getoraclequery all+ [TICK]\n"); + LOG(" Print the oracle query, metadata, and reply if available for each query ID received by all.\n"); + LOG("qubic-cli [...] -getoraclequery user [TICK]\n"); + LOG(" Print the query IDs of user queries started in the given tick.\n"); + LOG("qubic-cli [...] -getoraclequery user+ [TICK]\n"); + LOG(" Print the oracle query, metadata, and reply if available for each query ID received by user.\n"); + LOG("qubic-cli [...] -getoraclequery contract [TICK]\n"); + LOG(" Print the query IDs of contract one-time queries started in the given tick.\n"); + LOG("qubic-cli [...] -getoraclequery contract+ [TICK]\n"); + LOG(" Print the oracle query, metadata, and reply if available for each query ID received by contract.\n"); + LOG("qubic-cli [...] -getoraclequery subscription [TICK]\n"); + LOG(" Print the query IDs of contract subscription queries started in the given tick.\n"); + LOG("qubic-cli [...] -getoraclequery subscription+ [TICK]\n"); + LOG(" Print the oracle query, metadata, and reply if available for each query ID received by subscription.\n"); + LOG("qubic-cli [...] -getoraclequery stats\n"); + LOG(" Print the oracle query statistics of the core node.\n"); + LOG("qubic-cli [...] -getoraclequery revenue\n"); + LOG(" Print the current oracle revenue points of all computors.\n"); + exit(1); +} + +void printMakeOracleUserQueryTransactionHelpAndExit() +{ + LOG("qubic-cli [...] -queryoracle [INTERFACE] [QUERY_STRING] [TIMEOUT_IN_SECONDS]\n\n"); + LOG(" Send an oracle user query transaction. [INTERFACE] and [QUERY_STRING] are mandatory parameters.\n"); + LOG(" [TIMEOUT_IN_SECONDS] is optional (the default value is 60 seconds).\n\n"); + LOG(" As [INTERFACE], you can currently chose one of following:\n"); + for (uint32_t idx = 0; idx < OI::oracleInterfacesCount; ++idx) + LOG(" %s\n", oracleInterfaceToString(idx).c_str()); + LOG("\n"); + LOG(" The [QUERY_STRING] depends on the [INTERFACE]. Run the following to get help:\n"); + LOG(" qubic-cli [...] queryoracle [INTERFACE]\n"); + LOG(" For example:\n"); + LOG(" qubic-cli [...] queryoracle price\n"); + exit(1); +} + +static std::vector receiveQueryIds(QCPtr qc, unsigned int reqType, long long reqTickOrId = 0) +{ + struct { + RequestResponseHeader header; + RequestOracleData req; + } packet; + packet.header.setSize(sizeof(packet)); + packet.header.randomizeDejavu(); + packet.header.setType(RequestOracleData::type()); + memset(&packet.req, 0, sizeof(packet.req)); + packet.req.reqType = reqType; + packet.req.reqTickOrId = reqTickOrId; + + qc->sendData((uint8_t*)&packet, packet.header.size()); + + std::vector queryIds; + + uint8_t headerBuffer[sizeof(RequestResponseHeader)]; + auto header = (const RequestResponseHeader*)headerBuffer; + int recvByte = qc->receiveData(headerBuffer, sizeof(RequestResponseHeader)); + + std::vector payloadBuffer(sizeof(RespondOracleData) + 128 * sizeof(int64_t)); + + while (recvByte == sizeof(RequestResponseHeader)) + { + if (header->dejavu() != packet.header.dejavu()) + { + throw std::runtime_error("Unexpected dejavu!"); + } + if (header->type() == RespondOracleData::type()) + { + unsigned int payloadSize = header->size() - sizeof(RequestResponseHeader); + if (payloadSize > payloadBuffer.size()) + { + payloadBuffer.resize(payloadSize); + } + recvByte = qc->receiveAllDataOrThrowException(payloadBuffer.data(), payloadSize); + auto resp = (RespondOracleData*)(payloadBuffer.data()); + if (resp->resType == RespondOracleData::respondQueryIds) + { + long long idsNumBytes = payloadSize - sizeof(RespondOracleData); + if (idsNumBytes % 8 != 0) + { + throw std::runtime_error("Malformatted RespondOracleData::respondQueryIds message!"); + + } + else if (idsNumBytes > 0) + { + const uint8_t* queryIdBuffer = payloadBuffer.data() + sizeof(RespondOracleData); + queryIds.insert(queryIds.end(), + (int64_t*)queryIdBuffer, (int64_t*)(queryIdBuffer + idsNumBytes)); + } + } + else if (resp->resType == RespondOracleData::respondTickRange) + { + if (payloadSize != sizeof(RespondOracleData) + sizeof(RespondOracleDataValidTickRange)) + { + throw std::runtime_error("Malformatted RespondOracleData::respondTickRange message!"); + } + const auto* tickRange = (RespondOracleDataValidTickRange*)(payloadBuffer.data() + sizeof(RespondOracleData)); + if (reqTickOrId < tickRange->firstTick) + { + throw std::runtime_error("Data is not available, because tick is too old."); + } + else + { + throw std::runtime_error("Data is not available yet. You need to wait. Current tick is " + std::to_string(tickRange->currentTick)); + } + } + } + else if (header->type() == END_RESPOND) + { + return queryIds; + } + else + { + throw std::runtime_error("Unexpected packet type!"); + } + recvByte = qc->receiveData(headerBuffer, sizeof(RequestResponseHeader)); + } + + throw ConnectionTimeout(); +} + +static void receiveQueryInformation(QCPtr qc, int64_t queryId, RespondOracleDataQueryMetadata& metadata, std::vector& query, std::vector& reply) +{ + // send request + struct { + RequestResponseHeader header; + RequestOracleData req; + } request; + request.header.setSize(sizeof(request)); + request.header.randomizeDejavu(); + request.header.setType(RequestOracleData::type()); + memset(&request.req, 0, sizeof(request.req)); + request.req.reqType = RequestOracleData::requestQueryAndResponse; + request.req.reqTickOrId = queryId; + qc->sendData((uint8_t*)&request, request.header.size()); + + // reset output + memset(&metadata, 0, sizeof(RespondOracleDataQueryMetadata)); + query.clear(); + reply.clear(); + + // prepare output buffers + uint8_t headerBuffer[sizeof(RequestResponseHeader)]; + auto responseHeader = (const RequestResponseHeader*)headerBuffer; + std::vector payloadBuffer(2048); + + // receive query data + int recvHeaderBytes = qc->receiveData(headerBuffer, sizeof(RequestResponseHeader)); + while (recvHeaderBytes == sizeof(RequestResponseHeader)) + { + // get remaining part of the response + const unsigned int responsePayloadSize = responseHeader->size() - sizeof(RequestResponseHeader); + if (responsePayloadSize > payloadBuffer.size()) + { + payloadBuffer.resize(responsePayloadSize); + } + qc->receiveAllDataOrThrowException(payloadBuffer.data(), responsePayloadSize); + + // only process if dejavu matches (response is to current request, skip otherwise) + if (responseHeader->dejavu() == request.header.dejavu()) + { + if (responseHeader->type() == RespondOracleData::type()) + { + // Oracle data response + if (responsePayloadSize < sizeof(RespondOracleData)) + { + throw std::runtime_error("Malformatted RespondOracleData reply"); + } + auto respOracleData = (RespondOracleData*)payloadBuffer.data(); + auto responseInnerPayload = payloadBuffer.data() + sizeof(RespondOracleData); + auto responseInnerPayloadSize = responsePayloadSize - sizeof(RespondOracleData); + + if (respOracleData->resType == RespondOracleData::respondQueryMetadata + && responsePayloadSize == sizeof(RespondOracleData) + sizeof(RespondOracleDataQueryMetadata)) + { + // Query metadata + metadata = *(RespondOracleDataQueryMetadata*)(responseInnerPayload); + } + else if (respOracleData->resType == RespondOracleData::respondQueryData) + { + // Oracle query + query.insert(query.end(), + responseInnerPayload, + responseInnerPayload + responseInnerPayloadSize); + } + else if (respOracleData->resType == RespondOracleData::respondReplyData) + { + // Oracle reply + reply.insert(reply.end(), + responseInnerPayload, + responseInnerPayload + responseInnerPayloadSize); + } + else + { + throw std::runtime_error("Unexpected RespondOracleData message sub-type"); + } + } + else if (responseHeader->type() == END_RESPOND) + { + // End of output packages for this request + if (metadata.queryId != queryId) + throw std::logic_error("Unknown query ID!"); + else + break; + } + + // try to get next message header + recvHeaderBytes = qc->receiveData(headerBuffer, sizeof(RequestResponseHeader)); + } + } + if (recvHeaderBytes < sizeof(RequestResponseHeader)) + { + throw std::logic_error("Error receiving message header."); + } +} + +static const char* getOracleQueryTypeStr(uint8_t type) +{ + switch (type) + { + case ORACLE_QUERY_TYPE_CONTRACT_QUERY: + return "contract one-time query"; + case ORACLE_QUERY_TYPE_CONTRACT_SUBSCRIPTION: + return "contract subscription"; + case ORACLE_QUERY_TYPE_USER_QUERY: + return "user query"; + default: + return "unknown"; + } +} + +static const char* getOracleQueryStatusStr(uint8_t type) +{ + switch (type) + { + case ORACLE_QUERY_STATUS_PENDING: + return "pending"; + case ORACLE_QUERY_STATUS_COMMITTED: + return "committed"; + case ORACLE_QUERY_STATUS_SUCCESS: + return "success"; + case ORACLE_QUERY_STATUS_UNRESOLVABLE: + return "unresolvable"; + case ORACLE_QUERY_STATUS_TIMEOUT: + return "timeout"; + default: + return "unknown"; + } +} + +static std::string getOracleQueryStatusFlagsStr(uint16_t flags) +{ + std::string str; + if (flags & ORACLE_FLAG_REPLY_RECEIVED) + str += "-> core node received valid reply from the oracle machine"; + if (flags & ORACLE_FLAG_INVALID_ORACLE) + str += "\n\t- oracle machine reported that oracle (data source) in query was invalid"; + if (flags & ORACLE_FLAG_ORACLE_UNAVAIL) + str += "\n\t- oracle machine reported that oracle (data source) isn't available at the moment"; + if (flags & ORACLE_FLAG_INVALID_TIME) + str += "\n\t- oracle machine reported that time in query was invalid"; + if (flags & ORACLE_FLAG_INVALID_PLACE) + str += "\n\t- oracle machine reported that place in query was invalid"; + if (flags & ORACLE_FLAG_INVALID_ARG) + str += "\n\t- oracle machine reported that an argument in query was invalid"; + if (flags & ORACLE_FLAG_BAD_SIZE_REPLY) + str += "\n\t- core node got reply of wrong size from the oracle machine"; + if (flags & ORACLE_FLAG_OM_DISAGREE) + str += "\n\t- core node got different replies from oracle machine nodes"; + if (flags & ORACLE_FLAG_BAD_SIZE_REVEAL) + str += "\n\t- weren't enough reply commit tx with the same digest before timeout (< 451)"; + + return str; +} + +static void printQueryInformation(const RespondOracleDataQueryMetadata& metadata, const std::vector& query, const std::vector& reply) +{ + LOG("Query ID: %" PRIi64 "\n", metadata.queryId); + LOG("Type: %s (%" PRIu8 ")\n", getOracleQueryTypeStr(metadata.type), metadata.type); + LOG("Status: %s (%" PRIu8 ")\n", getOracleQueryStatusStr(metadata.status), metadata.status); + LOG("Status Flags: %" PRIu16 " %s\n", metadata.statusFlags, getOracleQueryStatusFlagsStr(metadata.statusFlags).c_str()); + LOG("Query Tick: %" PRIu32 "\n", metadata.queryTick); + + char queryingIdentity[128] = { 0 }; + getIdentityFromPublicKey(metadata.queryingEntity.m256i_u8, queryingIdentity, /*isLowerCase=*/false); + LOG("Querying Entity: %s\n", queryingIdentity); + + LOG("Timeout: %s\n", toString(*(QPI::DateAndTime*)&metadata.timeout).c_str()); + LOG("Interface Index: %" PRIu32 "\n", metadata.interfaceIndex); + if (metadata.type == ORACLE_QUERY_TYPE_CONTRACT_SUBSCRIPTION) + LOG("Subscription ID: %" PRIi32 "\n", metadata.subscriptionId); + if (metadata.status == ORACLE_QUERY_STATUS_SUCCESS) + { + LOG("Reveal Tick: %" PRIu32 "\n", metadata.revealTick); + } + else + { + LOG("Total Commits: %" PRIu16 "\n", metadata.totalCommits); + LOG("Agreeing Commits: %" PRIu16 "\n", metadata.agreeingCommits); + } + + std::string queryStr = oracleQueryToString(metadata.interfaceIndex, query); + if (queryStr.find("error") != std::string::npos) + { + std::vector hexQuery(2 * query.size() + 1, 0); + byteToHex(query.data(), hexQuery.data(), static_cast(query.size())); + LOG("Query: %s %s\n", hexQuery.data(), queryStr.c_str()); + } + else + { + LOG("Query: %s\n", queryStr.c_str()); + } + + if (reply.size() > 0) + { + std::string replyStr = oracleReplyToString(metadata.interfaceIndex, reply); + if (replyStr.find("error") != std::string::npos) + { + std::vector hexReply(2 * reply.size() + 1, 0); + byteToHex(reply.data(), hexReply.data(), static_cast(reply.size())); + LOG("Reply: %s %s\n", hexReply.data(), replyStr.c_str()); + } + else + { + LOG("Reply: %s\n", replyStr.c_str()); + } + } +} + +static void receiveQueryStats(QCPtr& qc, RespondOracleDataQueryStatistics& stats) +{ + struct { + RequestResponseHeader header; + RequestOracleData req; + } packet; + packet.header.setSize(sizeof(packet)); + packet.header.randomizeDejavu(); + packet.header.setType(RequestOracleData::type()); + memset(&packet.req, 0, sizeof(packet.req)); + packet.req.reqType = RequestOracleData::requestQueryStatistics; + qc->sendData((uint8_t*)&packet, packet.header.size()); + + constexpr unsigned long long responseSize = sizeof(RequestResponseHeader) + sizeof(RespondOracleData) + sizeof(RespondOracleDataQueryStatistics); + uint8_t buffer[responseSize]; + int recvByte = qc->receiveData(buffer, responseSize); + if (recvByte != responseSize) + { + throw std::logic_error("Connection closed."); + } + const auto* header = (RequestResponseHeader*)buffer; + const auto* header2 = (RespondOracleData*)(buffer + sizeof(RequestResponseHeader)); + if (header->type() != RespondOracleData::type() || header2->resType != RespondOracleData::respondQueryStatistics) + { + throw std::logic_error("Unexpected response message."); + } + const auto* receivedStats = (RespondOracleDataQueryStatistics*)(buffer + sizeof(RequestResponseHeader) + sizeof(RespondOracleData)); + stats = *receivedStats; +} + +static void printQueryStats(const RespondOracleDataQueryStatistics& stats) +{ + const float revealPerSuccess = (stats.successfulCount) ? float(stats.revealTxCount) / stats.successfulCount : 0; + LOG("successful: % " PRIu64 " queries (takes %.3f ticks on average, %.1f reveal tx / success)\n", stats.successfulCount, float(stats.successAvgMilliTicksPerQuery) / 1000.0f, revealPerSuccess); + LOG("timeout: % " PRIu64 " queries in total (average timeout is %.3f ticks)\n", stats.timeoutCount, float(stats.timeoutAvgMilliTicksPerQuery) / 1000.0f); + LOG(" % " PRIu64 " queries before OM reply\n", stats.timeoutNoReplyCount); + LOG(" % " PRIu64 " queries before commit quorum\n", stats.timeoutNoCommitCount); + LOG(" % " PRIu64 " queries before reveal\n", stats.timeoutNoRevealCount); + LOG("unresolvable: % " PRIu64 " queries\n", stats.unresolvableCount); + LOG("pending: % " PRIu64 " queries in total\n", stats.pendingCount); + LOG(" % " PRIu64 " queries before OM reply (takes %.3f ticks on average)\n", stats.pendingOracleMachineCount, float(stats.oracleMachineReplyAvgMilliTicksPerQuery) / 1000.0f); + LOG(" % " PRIu64 " queries before commit quorum (takes %.3f ticks on average)\n", stats.pendingCommitCount, float(stats.commitAvgMilliTicksPerQuery) / 1000.0f); + LOG(" % " PRIu64 " queries before reveal / success\n", stats.pendingRevealCount); + if (stats.oracleMachineRepliesDisagreeCount > 0) + { + LOG("OM issues: % " PRIu64 " queries had OM replies that differ between OMs\n", stats.oracleMachineRepliesDisagreeCount); + } +} + +static void receiveOracleRevenuePoints(QCPtr& qc, std::vector& revenuePoints) +{ + struct { + RequestResponseHeader header; + RequestOracleData req; + } packet; + packet.header.setSize(sizeof(packet)); + packet.header.randomizeDejavu(); + packet.header.setType(RequestOracleData::type()); + memset(&packet.req, 0, sizeof(packet.req)); + packet.req.reqType = RequestOracleData::requestOracleRevenuePoints; + qc->sendData((uint8_t*)&packet, packet.header.size()); + + constexpr unsigned long long responseSize = sizeof(RequestResponseHeader) + sizeof(RespondOracleData) + 8 * 676; + uint8_t buffer[responseSize]; + int recvByte = qc->receiveData(buffer, responseSize); + if (recvByte != responseSize) + { + throw std::logic_error("Connection closed."); + } + const auto* header = (RequestResponseHeader*)buffer; + const auto* header2 = (RespondOracleData*)(buffer + sizeof(RequestResponseHeader)); + if (header->type() != RespondOracleData::type() || header2->resType != RespondOracleData::respondOracleRevenuePoints) + { + throw std::logic_error("Unexpected response message."); + } + revenuePoints.resize(676); + memcpy(revenuePoints.data(), buffer + sizeof(RequestResponseHeader) + sizeof(RespondOracleData), 8 * 676); +} + +static void printOracleRevenuePoints(const std::vector& revenuePoints) +{ + for (int i = 0; i < (int)revenuePoints.size(); ++i) + { + LOG("computor %d -> %" PRIu64 " oracle rev points\n", i, revenuePoints[i]); + } +} + +static bool parseTick(const char* tickStr, long long& tickFrom, long long& tickTo) +{ + char* writableTickStr = STRDUP(tickStr); + std::string part1 = strtok2string(writableTickStr, "-"); + std::string part2 = strtok2string(NULL, "-"); + std::string part3 = strtok2string(NULL, "-"); + free(writableTickStr); + if (!part3.empty()) + { + LOG("Failed to parse tick string \"%s\"! Does not follow syntax N1-N2!", tickStr); + return false; + } + + bool okay = true; + try + { + tickFrom = std::stoll(part1); + if (tickFrom <= 0) + okay = false; + if (!part2.empty()) + { + tickTo = std::stoll(part2); + if (tickTo <= 0) + okay = false; + } + else + tickTo = tickFrom; + } + catch (std::exception e) + { + okay = false; + } + + if (!okay) + { + LOG("Failed to parse tick string \"%s\"! Tick must be a positive number N or range N1-N2!", tickStr); + } + return okay; +} + +void processGetOracleQueryWithTick(const char* nodeIp, const int nodePort, unsigned int reqType, const char* reqParam, bool getAllDetails) +{ + long long tickFrom = 0, tickTo = 0; + if (!parseTick(reqParam, tickFrom, tickTo)) + return; + + const long long tickCount = tickTo - tickFrom + 1; + if (tickCount < 1) + { + LOG("In range N1-N2, N2 should be greater than N2."); + return; + } + if (tickCount > 10000) + { + LOG("Range of ticks is too large. Skipped for node performance reasons."); + return; + } + + // use longer 3 second timeout + unsigned long timeoutMilliseconds = 3000; + + auto qc = make_qc(nodeIp, nodePort, timeoutMilliseconds); + for (long long tick = tickFrom; tick <= tickTo; ++tick) + { + std::vector recQueryIds = receiveQueryIds(qc, reqType, tick); + + if (!getAllDetails) + { + LOG("Query IDs in tick %" PRIi64 ":\n", tick); + for (const int64_t& id : recQueryIds) + { + LOG("- %" PRIi64 "\n", id); + } + } + else + { + LOG("Number of query IDs in tick %" PRIi64 ": %d\n\n", tick, (int)recQueryIds.size()); + + RespondOracleDataQueryMetadata metadata; + std::vector query, reply; + for (const int64_t& id : recQueryIds) + { + receiveQueryInformation(qc, id, metadata, query, reply); + if (metadata.queryId == 0) + { + LOG("Error getting query metadata! Stopping now.\n"); + return; + } + printQueryInformation(metadata, query, reply); + LOG("\n"); + } + } + } +} + +void processGetOracleQuery(const char* nodeIp, const int nodePort, const char* requestType, const char* reqParam) +{ + if (strcasecmp(requestType, "") == 0) + printGetOracleQueryHelpAndExit(); + + if (strcasecmp(requestType, "pending") == 0) + { + auto qc = make_qc(nodeIp, nodePort); + std::vector recQueryIds = receiveQueryIds(qc, RequestOracleData::requestPendingQueryIds); + + LOG("Pending query ids:\n"); + for (const int64_t& id : recQueryIds) + { + LOG("- %" PRIi64 "\n", id); + } + } + else if (strcasecmp(requestType, "pending+") == 0) + { + auto qc = make_qc(nodeIp, nodePort); + std::vector recQueryIds = receiveQueryIds(qc, RequestOracleData::requestPendingQueryIds); + LOG("Number of pending query IDs: %d\n\n", (int)recQueryIds.size()); + + RespondOracleDataQueryMetadata metadata; + std::vector query, reply; + for (const int64_t& id : recQueryIds) + { + receiveQueryInformation(qc, id, metadata, query, reply); + printQueryInformation(metadata, query, reply); + LOG("\n"); + } + } + else if (strcasecmp(requestType, "all") == 0) + { + processGetOracleQueryWithTick(nodeIp, nodePort, + RequestOracleData::requestAllQueryIdsByTick, reqParam, + /*getAllDetails=*/false); + } + else if (strcasecmp(requestType, "all+") == 0) + { + processGetOracleQueryWithTick(nodeIp, nodePort, + RequestOracleData::requestAllQueryIdsByTick, reqParam, + /*getAllDetails=*/true); + } + else if (strcasecmp(requestType, "user") == 0) + { + processGetOracleQueryWithTick(nodeIp, nodePort, + RequestOracleData::requestUserQueryIdsByTick, reqParam, + /*getAllDetails=*/false); + } + else if (strcasecmp(requestType, "user+") == 0) + { + processGetOracleQueryWithTick(nodeIp, nodePort, + RequestOracleData::requestUserQueryIdsByTick, reqParam, + /*getAllDetails=*/true); + } + else if (strcasecmp(requestType, "contract") == 0) + { + processGetOracleQueryWithTick(nodeIp, nodePort, + RequestOracleData::requestContractDirectQueryIdsByTick, reqParam, + /*getAllDetails=*/false); + } + else if (strcasecmp(requestType, "contract+") == 0) + { + processGetOracleQueryWithTick(nodeIp, nodePort, + RequestOracleData::requestContractDirectQueryIdsByTick, reqParam, + /*getAllDetails=*/true); + } + else if (strcasecmp(requestType, "subscription") == 0) + { + processGetOracleQueryWithTick(nodeIp, nodePort, + RequestOracleData::requestContractSubscriptionQueryIdsByTick, reqParam, + /*getAllDetails=*/false); + } + else if (strcasecmp(requestType, "subscription+") == 0) + { + processGetOracleQueryWithTick(nodeIp, nodePort, + RequestOracleData::requestContractSubscriptionQueryIdsByTick, reqParam, + /*getAllDetails=*/true); + } + else if (strcasecmp(requestType, "stats") == 0) + { + auto qc = make_qc(nodeIp, nodePort); + RespondOracleDataQueryStatistics stats; + receiveQueryStats(qc, stats); + printQueryStats(stats); + } + else if (strcasecmp(requestType, "revenue") == 0) + { + auto qc = make_qc(nodeIp, nodePort); + std::vector computorRevenuePoints; + receiveOracleRevenuePoints(qc, computorRevenuePoints); + printOracleRevenuePoints(computorRevenuePoints); + } + else + { + // no known command, try to interpret param string as query id (8-byte int64_t number) + int64_t queryId; + try + { + queryId = std::stoll(std::string(requestType)); + } + catch (std::exception e) + { + LOG("Expected query ID (unsigned integer), found unknown command %s!\n", requestType); + return; + } + if (queryId <= 0) + { + LOG("Invalid query ID. Expected positive integer. Use commands such as \"user\" and \"contract\" to get a valid ID.\n"); + return; + } + + auto qc = make_qc(nodeIp, nodePort); + + RespondOracleDataQueryMetadata metadata; + std::vector query, reply; + receiveQueryInformation(qc, queryId, metadata, query, reply); + printQueryInformation(metadata, query, reply); + } +} + +void makeOracleUserQueryTransaction( + const char* nodeIp, int nodePort, const char* seed, + const char* oracleInterfaceString, + const char* queryString, + const char* timeoutSecondsString, + uint32_t scheduledTickOffset) +{ + if (strcasecmp(oracleInterfaceString, "") == 0) + printMakeOracleUserQueryTransactionHelpAndExit(); + + const uint32_t interfaceIndex = stringToOracleInterface(oracleInterfaceString); + if (interfaceIndex >= OI::oracleInterfacesCount) + { + LOG("Unknown oracle interface \"%s\"!\nSupported options are:\n", oracleInterfaceString); + for (uint32_t idx = 0; idx < OI::oracleInterfacesCount; ++idx) + LOG("\t%s\n", oracleInterfaceToString(idx).c_str()); + return; + } + + if (strcasecmp(queryString, "") == 0) + { + LOG("[QUERY_STRING] in %s oracle:\n", oracleInterfaceToString(interfaceIndex).c_str()); + printOracleQueryParamHelp(interfaceIndex); + return; + } + + std::vector queryData = parseOracleQueryParams(interfaceIndex, queryString); + if (queryData.size() == 0) + { + LOG("Error parsing [QUERY_STRING] \"%s\".\n", queryString); + return; + } + + uint64_t timeoutSeconds = 60; + if (strcasecmp(timeoutSecondsString, "") == 0) + { + LOG("No timeout specified. Using default timeout of 60 seconds.\n"); + } + else + { + try + { + timeoutSeconds = std::stoull(timeoutSecondsString); + } + catch (...) + { + timeoutSeconds = 0; + } + if (timeoutSeconds == 0 || timeoutSeconds > 600) + { + LOG("Error: Invalid timeout! Please use a value between 1 and 600.\n"); + return; + } + } + + // get oracle query fee + OI::initOracleInterfaces(); + int64_t fee = OI::getOracleQueryFeeFunc[interfaceIndex](queryData.data()); + LOG("Fee for oracle query: %" PRIi64 "\n", fee); + + // build tx input data + const int txInputSize = 8 + (int)queryData.size(); + std::vector txInputData(txInputSize); + *(uint32_t*)(txInputData.data()) = interfaceIndex; + *(uint32_t*)(txInputData.data() + 4) = (uint32_t)timeoutSeconds * 1000; // timeout in milliseconds + memcpy(txInputData.data() + 8, queryData.data(), queryData.size()); + + // send transaction + char destinationPublicKey[32] = {0}; + uint32_t scheduledTick = 0; + makeCustomTransaction(nodeIp, nodePort, seed, destinationPublicKey, + 10, // TODO: define tx types as enum in core and use here + fee, txInputSize, txInputData.data(), scheduledTickOffset, &scheduledTick); + + // TODO: print command to check directly + LOG("\n\nYou may run the following command for checking the query:\n"); + LOG("qubic-cli [...] -getoraclequery user+ %u\n", scheduledTick); + + // TODO: add "my/my+ [TICK]" command that uses a future core command for filtering queries based on seed + // (send tick + 4 bytes of public key to core for filtering oracle IDs) +} diff --git a/oracle_utils.h b/oracle_utils.h new file mode 100644 index 00000000..2b7a6b4b --- /dev/null +++ b/oracle_utils.h @@ -0,0 +1,9 @@ +#pragma once + +void processGetOracleQuery(const char* nodeIp, const int nodePort, const char* requestType, const char* reqParam); +void makeOracleUserQueryTransaction( + const char* nodeIp, int nodePort, const char* seed, + const char* oracleInterfaceString, + const char* queryString, + const char* timeoutSecondsString, + uint32_t scheduledTickOffset); diff --git a/proposal.cpp b/proposal.cpp index fab64382..7f45a119 100644 --- a/proposal.cpp +++ b/proposal.cpp @@ -410,11 +410,6 @@ void printSetProposalHelp() std::cout << std::endl; } -#ifdef _MSC_VER -#define STRDUP(x) _strdup(x) -#else -#define STRDUP(x) strdup(x) -#endif bool parseProposalString(const char* proposalString, ProposalDataV1& p, SubscriptionParams* subscriptionParams = nullptr) diff --git a/qpi_adapter.cpp b/qpi_adapter.cpp new file mode 100644 index 00000000..33965bbb --- /dev/null +++ b/qpi_adapter.cpp @@ -0,0 +1,269 @@ +#include "qpi_adapter.h" + +#include "key_utils.h" +#include "utils.h" +#include "logger.h" + +#include + + +std::string toString(const QPI::id& id) +{ + // check if ID is printable as a string + bool printable = true; + if (id.m256i_u8[31] == 0) + { + printable = true; + for (int i = 0; i < 31 && id.m256i_u8[i] != 0; ++i) + { + if (!isprint(id.m256i_u8[i])) + { + printable = false; + break; + } + } + } + + if (printable) + { + return std::string((const char*) id.m256i_u8); + } + else + { + char buffer[64]; + getIdentityFromPublicKey(id.m256i_u8, buffer, true); + return std::string(buffer); + } +} + +std::string toString(const QPI::DateAndTime& dt) +{ + char buffer[100]; + if (dt.getMicrosecDuringMillisec()) + sprintf(buffer, "%04d-%02d-%02d_%02d:%02d:%02d.%03d'%03d", dt.getYear(), dt.getMonth(), dt.getDay(), dt.getHour(), dt.getMinute(), dt.getSecond(), dt.getMillisec(), dt.getMicrosecDuringMillisec()); + else + sprintf(buffer, "%04d-%02d-%02d_%02d:%02d:%02d.%03d", dt.getYear(), dt.getMonth(), dt.getDay(), dt.getHour(), dt.getMinute(), dt.getSecond(), dt.getMillisec()); + std::string str(buffer); + if (!dt.isValid()) + str += " (invalid date)"; + return str; +} + +static std::string oracleQueryToString(const typename OI::Price::OracleQuery& query) +{ + return std::string("Price::OracleQuery(oracle = ") + toString(query.oracle) + + ", currency1 = " + toString(query.currency1) + + ", currency2 = " + toString(query.currency2) + + ", timestamp = " + toString(query.timestamp) + ")"; +} + +static std::string oracleReplyToString(const typename OI::Price::OracleReply& reply) +{ + return std::string("Price::OracleReply(numerator = ") + std::to_string(reply.numerator) + ", denominator = " + std::to_string(reply.denominator) + ")"; +} + +static std::string oracleQueryToString(const typename OI::Mock::OracleQuery& query) +{ + return std::string("Mock::OracleQuery(value = ") + std::to_string(query.value) + ")"; +} + +static std::string oracleReplyToString(const typename OI::Mock::OracleReply& reply) +{ + return std::string("Mock::OracleReply(echoedValue = ") + std::to_string(reply.echoedValue) + ", doubledValue = " + std::to_string(reply.doubledValue) + ")"; +} + +template +static std::string callOracleQueryToString(const std::vector& query) +{ + typedef typename OracleInterface::OracleQuery T; + if (query.size() != sizeof(T)) + return "[error: unexpected query size]"; + return oracleQueryToString(*reinterpret_cast(query.data())); +} + +std::string oracleQueryToString(uint32_t interfaceIndex, const std::vector& query) +{ + switch (interfaceIndex) + { + case OI::Price::oracleInterfaceIndex: + return callOracleQueryToString(query); + case OI::Mock::oracleInterfaceIndex: + return callOracleQueryToString(query); + default: + return "[error: unsupported interface]"; + } +} + +template +static std::string callOracleReplyToString(const std::vector& query) +{ + typedef typename OracleInterface::OracleReply T; + if (query.size() != sizeof(T)) + return "[error: unexpected reply size]"; + return oracleReplyToString(*reinterpret_cast(query.data())); +} + +std::string oracleReplyToString(uint32_t interfaceIndex, const std::vector& reply) +{ + switch (interfaceIndex) + { + case OI::Price::oracleInterfaceIndex: + return callOracleReplyToString(reply); + case OI::Mock::oracleInterfaceIndex: + return callOracleReplyToString(reply); + default: + return "[error: unsupported interface]"; + } +} + +std::string oracleInterfaceToString(uint32_t interfaceIndex) +{ + switch (interfaceIndex) + { + case OI::Price::oracleInterfaceIndex: + return "Price"; + case OI::Mock::oracleInterfaceIndex: + return "Mock"; + default: + return "[error: unsupported interface]"; + } +} + +uint32_t stringToOracleInterface(const char* oracleInterfaceString) +{ + if (strcasecmp(oracleInterfaceString, "Price") == 0) + { + return OI::Price::oracleInterfaceIndex; + } + else if (strcasecmp(oracleInterfaceString, "Mock") == 0) + { + return OI::Mock::oracleInterfaceIndex; + } + else + { + return UINT32_MAX; + } +} + +void printOracleQueryParamHelp(uint32_t interfaceIndex) +{ + switch (interfaceIndex) + { + case OI::Price::oracleInterfaceIndex: + LOG("As [QUERY_PARAMS] pass a oracle, currency1, currency2, and UTC time separated with commas without any spaces.\n"); + LOG("UTC time is optional, default is now.\n"); + LOG("Examples:\n"); + LOG(" binance,btc,usdt,now (current BTC/USDT price from binance)\n"); + LOG(" mexc,ETH,BTC,2026-01-31_15:20:40 (ETH/BTC price from MEXC at the given time)\n"); + LOG(" gate,Qubic,USDT,2026-02-09_0:0:0.0 (QUBIC/USDT price from Gate.io at the given time)\n"); + LOG(" gate_mexc,QUBIC,USDT (current average price from gate.io and mexc)\n"); + break; + case OI::Mock::oracleInterfaceIndex: + LOG("As [QUERY_PARAMS] pass an unsigned integer value.\n"); + break; + } +} + +static bool parseOracleQueryParams(const char* paramString, typename OI::Mock::OracleQuery& query) +{ + try + { + query.value = std::stoull(paramString); + } + catch (...) + { + LOG("Expected unsigned integer, found %s!\n", paramString); + return false; + } + return true; +} + +static bool parseOracleQueryParams(const char* paramString, typename OI::Price::OracleQuery& query) +{ + std::vector parts = splitString(paramString, ","); + if (parts.size() < 3 || parts.size() > 4) + { + LOG("As [QUERY_PARAMS] pass \"[oracle],[currency1],[currency2]\" or \"[oracle],[currency1],[currency2],[utc_time]\".\n"); + return false; + } + + const std::string& oracle = parts[0]; + if (oracle.size() > 31) + { + LOG("The [oracle] must have 31 characters at most.\n"); + return false; + } + memcpy(&query.oracle, oracle.data(), oracle.size()); + + const std::string& currency1 = parts[1]; + const std::string& currency2 = parts[2]; + if (currency1.size() > 31 && currency2.size() > 31) + { + LOG("Both [currency1] and [currency2] must have 31 characters at most.\n"); + return false; + } + memcpy(&query.currency1, currency1.data(), currency1.size()); + memcpy(&query.currency2, currency2.data(), currency2.size()); + + if (parts.size() < 4 || strcasecmp(parts[3].c_str(), "now") == 0) + { + // use current UTC time + using namespace std::chrono; + auto now = system_clock::now(); + auto tt = system_clock::to_time_t(now); + std::tm tm; +#if defined(_WIN32) + gmtime_s(&tm, &tt); // (UTC) +#else + gmtime_r(&tt, &tm); // (UTC) +#endif + query.timestamp.set(tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec); + } + else + { + unsigned int year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0, millisecond = 0; + bool okay; + if (parts[3].find('.') != std::string::npos) + okay = (sscanf(parts[3].c_str(), "%u-%u-%u_%u:%u:%u.%u", &year, &month, &day, &hour, &minute, &second, &millisecond) == 7); + else + okay = (sscanf(parts[3].c_str(), "%u-%u-%u_%u:%u:%u", &year, &month, &day, &hour, &minute, &second) == 6); + if (!okay || !query.timestamp.setIfValid(year, month, day, hour, minute, second, millisecond)) + { + LOG("Unable to parse time string \"%s\" into valid date/time.\n", parts[3].c_str()); + LOG("It should follow the pattern \"YEAR-MONTH-DAY_HOUR:MINUTE:SECOND.MILLISECOND\". \".MILLISECOND\" is optional.\n"); + return false; + } + } + + return true; +} + +template +static std::vector callParseOracleQueryParams(const char* oracleQueryParams) +{ + typedef typename OracleInterface::OracleQuery T; + std::vector query(sizeof(T), 0); + if (!parseOracleQueryParams(oracleQueryParams, *reinterpret_cast(query.data()))) + { + // error + LOG("\nMore help:\n"); + printOracleQueryParamHelp(OracleInterface::oracleInterfaceIndex); + query.clear(); + } + + return query; +} + +std::vector parseOracleQueryParams(uint32_t interfaceIndex, const char* oracleQueryString) +{ + switch (interfaceIndex) + { + case OI::Price::oracleInterfaceIndex: + return callParseOracleQueryParams(oracleQueryString); + case OI::Mock::oracleInterfaceIndex: + return callParseOracleQueryParams(oracleQueryString); + default: + LOG("Error: unsupported interface\n"); + return {}; + } +} diff --git a/qpi_adapter.h b/qpi_adapter.h new file mode 100644 index 00000000..2edd23e6 --- /dev/null +++ b/qpi_adapter.h @@ -0,0 +1,52 @@ +#pragma once + +#if !defined(NDEBUG) +#define NDEBUG +#endif + +// For GCC/Clang, provide implementations of MSVC intrinsics used in qpi.h +#if !defined(_MSC_VER) + #include + + // Signed 64-bit multiply returning low 64 bits and high 64 bits + inline long long int _mul128(long long int a, long long int b, long long int* high) { + __int128 result = static_cast<__int128>(a) * static_cast<__int128>(b); + *high = static_cast(result >> 64); + return static_cast(result); + } + + // Unsigned 64-bit multiply returning low 64 bits and high 64 bits + inline long long unsigned int _umul128(long long unsigned int a, long long unsigned int b, long long unsigned int* high) { + unsigned __int128 result = static_cast(a) * static_cast(b); + *high = static_cast(result >> 64); + return static_cast(result); + } +#endif + +#include "core/src/contract_core/pre_qpi_def.h" +#include "core/src/contracts/qpi.h" +#include "core/src/oracle_core/oracle_interfaces_def.h" + +#include +#include + +// define static functions declared by qpi.h +static void* __acquireScratchpad(unsigned long long size, bool initZero) { return nullptr; } +static void __releaseScratchpad(void* ptr) {} +void QPI::AssetIssuanceIterator::begin(const QPI::AssetIssuanceSelect&) {} +QPI::uint64 QPI::AssetIssuanceIterator::assetName() const { return 0; } +QPI::id QPI::AssetIssuanceIterator::issuer() const { return QPI::id::zero(); } +void QPI::AssetOwnershipIterator::begin(const QPI::Asset&, const QPI::AssetOwnershipSelect&) {} +void QPI::AssetPossessionIterator::begin(const QPI::Asset&, const QPI::AssetOwnershipSelect&, const QPI::AssetPossessionSelect&) {} +template void QPI::copyMemory(T1&, const T2&) {} + + +// define additional functions implemented by qpi:adapter.cpp +std::string toString(const QPI::id& id); +std::string toString(const QPI::DateAndTime& dt); +std::string oracleQueryToString(uint32_t interfaceIndex, const std::vector& query); +std::string oracleReplyToString(uint32_t interfaceIndex, const std::vector& reply); +std::string oracleInterfaceToString(uint32_t interfaceIndex); +uint32_t stringToOracleInterface(const char* oracleInterfaceString); +void printOracleQueryParamHelp(uint32_t interfaceIndex); +std::vector parseOracleQueryParams(uint32_t interfaceIndex, const char* oracleQueryString); diff --git a/qutil.cpp b/qutil.cpp index c994b417..cac08c4b 100644 --- a/qutil.cpp +++ b/qutil.cpp @@ -18,6 +18,9 @@ constexpr int QUTIL_CONTRACT_ID = 4; +// TODO: use value from core source code +constexpr unsigned long long CONTRACT_ACTION_TRACKER_SIZE = 16 * 1024 * 1024; + struct SendToManyV1_input { uint8_t addresses[25][32]; diff --git a/structs.h b/structs.h index 6acbea78..dd0972bd 100644 --- a/structs.h +++ b/structs.h @@ -206,6 +206,8 @@ enum COMMAND SHAREHOLDER_GET_VOTING_RESULTS, SET_EXECUTION_FEE_MULTIPLIER, GET_EXECUTION_FEE_MULTIPLIER, + GET_ORACLE_QUERY, + SEND_ORACLE_QUERY_TX, TOTAL_COMMAND // DO NOT CHANGE THIS }; @@ -217,7 +219,7 @@ struct RequestResponseHeader unsigned int _dejavu; public: - inline unsigned int size() + inline unsigned int size() const { if (((*((unsigned int*)_size)) & 0xFFFFFF)==0) return INT32_MAX; // size is never zero, zero means broken packets return (*((unsigned int*)_size)) & 0xFFFFFF; @@ -230,7 +232,12 @@ struct RequestResponseHeader _size[2] = (uint8_t)(size >> 16); } - inline bool isDejavuZero() + inline unsigned int dejavu() const + { + return _dejavu; + } + + inline bool isDejavuZero() const { return !_dejavu; } @@ -249,7 +256,7 @@ struct RequestResponseHeader } } - inline uint8_t type() + inline uint8_t type() const { return _type; } diff --git a/submodules/core b/submodules/core new file mode 160000 index 00000000..5b8da833 --- /dev/null +++ b/submodules/core @@ -0,0 +1 @@ +Subproject commit 5b8da8339728c487cb63bdd107635e0094bb55f5 diff --git a/utils.h b/utils.h index 6855fd93..5ab3d5a8 100644 --- a/utils.h +++ b/utils.h @@ -45,6 +45,12 @@ static void rand64(uint64_t* r) *r = distribution(generator); } +#ifdef _MSC_VER +#define STRDUP(x) _strdup(x) +#else +#define STRDUP(x) strdup(x) +#endif + static inline std::string strtok2string(char* s, const char* delimiter) { const char* res = strtok(s, delimiter); diff --git a/wallet_utils.cpp b/wallet_utils.cpp index edcc6647..d790e561 100644 --- a/wallet_utils.cpp +++ b/wallet_utils.cpp @@ -263,7 +263,8 @@ void makeCustomTransaction(const char* nodeIp, int nodePort, uint64_t amount, int extraDataSize, const uint8_t* extraData, - uint32_t scheduledTickOffset) + uint32_t scheduledTickOffset, + uint32_t* outputScheduledTick = nullptr) { if (extraDataSize < 0) throw std::invalid_argument("extraDataSize < 0"); @@ -292,7 +293,8 @@ void makeCustomTransaction(const char* nodeIp, int nodePort, memcpy(temp_packet.transaction.destinationPublicKey, destPublicKey, 32); temp_packet.transaction.amount = amount; uint32_t currentTick = getTickNumberFromNode(qc); - temp_packet.transaction.tick = currentTick + scheduledTickOffset; + uint32_t scheduleTick = currentTick + scheduledTickOffset; + temp_packet.transaction.tick = scheduleTick; temp_packet.transaction.inputType = txType; temp_packet.transaction.inputSize = extraDataSize; @@ -319,8 +321,11 @@ void makeCustomTransaction(const char* nodeIp, int nodePort, getTxHashFromDigest(digest, txHash); LOG("Transaction has been sent!\n"); printReceipt(temp_packet.transaction, txHash, extraData); - LOG("run ./qubic-cli [...] -checktxontick %u %s\n", currentTick + scheduledTickOffset, txHash); + LOG("run ./qubic-cli [...] -checktxontick %u %s\n", scheduleTick, txHash); LOG("to check your tx confirmation status\n"); + + if (outputScheduledTick) + *outputScheduledTick = scheduleTick; } void makeContractTransaction(const char* nodeIp, int nodePort, diff --git a/wallet_utils.h b/wallet_utils.h index 2d6f1479..a5609f99 100644 --- a/wallet_utils.h +++ b/wallet_utils.h @@ -20,7 +20,8 @@ void makeCustomTransaction(const char* nodeIp, int nodePort, uint64_t amount, int extraDataSize, const uint8_t* extraData, - uint32_t scheduledTickOffset); + uint32_t scheduledTickOffset, + uint32_t* outputScheduledTick = nullptr); void makeContractTransaction(const char* nodeIp, int nodePort, const char* seed, uint64_t contractIndex,