diff --git a/README.md b/README.md index c992410..e871b4d 100644 --- a/README.md +++ b/README.md @@ -109,6 +109,8 @@ Commands: View list of active IPOs in this epoch. valid node ip/port are required. -getsysteminfo View Current System Status. Includes initial tick, random mining seed, epoch info. + -getrevenuedata + Write current (approximate) per-computor revenue score components (tx, oracle, doge) as CSV to OUTPUT_CSV_FILE. Valid node ip/port required. [NODE COMMANDS] -getcurrenttick diff --git a/argparser.h b/argparser.h index 1a0fc26..dc5a66b 100644 --- a/argparser.h +++ b/argparser.h @@ -135,6 +135,8 @@ void print_help() printf("\t\tView list of active IPOs in this epoch. valid node ip/port are required.\n"); printf("\t-getsysteminfo\n"); printf("\t\tView Current System Status. Includes initial tick, random mining seed, epoch info.\n"); + printf("\t-getrevenuedata \n"); + printf("\t\tWrite current (approximate) per-computor revenue score components (tx, oracle, doge) as CSV to OUTPUT_CSV_FILE. Valid node ip/port required.\n"); printf("\n[NODE COMMANDS]\n"); printf("\t-getcurrenttick\n"); @@ -1058,6 +1060,16 @@ void parseArgument(int argc, char** argv) CHECK_OVER_PARAMETERS break; } + + if (strcmp(argv[i], "-getrevenuedata") == 0) + { + CHECK_NUMBER_OF_PARAMETERS(1) + g_cmd = GET_REVENUE_DATA; + g_dumpBinaryFileOutput = argv[i+1]; + i+=2; + CHECK_OVER_PARAMETERS + break; + } if (strcmp(argv[i], "-getcurrenttick") == 0) { g_cmd = GET_CURRENT_TICK; diff --git a/connection.cpp b/connection.cpp index fbcc94f..3096717 100644 --- a/connection.cpp +++ b/connection.cpp @@ -420,3 +420,6 @@ template GetCurrentResult_output QubicConnection::receivePacketWithHeaderAs(); template GetCurrentPollId_output QubicConnection::receivePacketWithHeaderAs(); template GetPollInfo_output QubicConnection::receivePacketWithHeaderAs(); + +// REVENUE +template void QubicConnection::receivePacketWithHeaderAs(RevenueData&); diff --git a/defines.h b/defines.h index 4412354..1864def 100644 --- a/defines.h +++ b/defines.h @@ -41,6 +41,8 @@ #define RESPOND_POSSESSED_ASSETS 41 #define REQUEST_SYSTEM_INFO 46 #define RESPOND_SYSTEM_INFO 47 +#define REQUEST_REVENUE_DATA 70 +#define RESPOND_REVENUE_DATA 71 #define SPECIAL_COMMAND_SET_SOLUTION_THRESHOLD_REQUEST 5ULL #define SPECIAL_COMMAND_SET_SOLUTION_THRESHOLD_RESPONSE 6ULL diff --git a/main.cpp b/main.cpp index 0dfb9be..edcc8be 100644 --- a/main.cpp +++ b/main.cpp @@ -45,6 +45,11 @@ int run(int argc, char* argv[]) sanityCheckNode(g_nodeIp, g_nodePort); printSystemInfoFromNode(g_nodeIp, g_nodePort); break; + case GET_REVENUE_DATA: + sanityCheckNode(g_nodeIp, g_nodePort); + sanityCheckValidString(g_dumpBinaryFileOutput); + dumpRevenueDataFromNode(g_nodeIp, g_nodePort, g_dumpBinaryFileOutput); + break; case GET_BALANCE: sanityCheckIdentity(g_requestedIdentity); sanityCheckNode(g_nodeIp, g_nodePort); diff --git a/node_utils.cpp b/node_utils.cpp index 8bdf4ef..d350c24 100644 --- a/node_utils.cpp +++ b/node_utils.cpp @@ -124,6 +124,173 @@ void printSystemInfoFromNode(const char* nodeIp, int nodePort) } } +// Mirrors core computeMultiDimRevenue in src/revenue.h +// Constants match the core: NUMBER_OF_COMPUTORS = 676 -> QUORUM = 451, REVENUE_SCALE = 1024. +static const unsigned long long REVENUE_SCALE = 1024; +static const unsigned int REVENUE_QUORUM = 451; + +// QUORUM-th largest score (== core getQuorumScore), min 1. +static unsigned long long revQuorumThreshold(const unsigned long long* scores) +{ + std::vector sorted(scores, scores + NUMBER_OF_COMPUTORS); + std::sort(sorted.begin(), sorted.end()); // ascending; index N-QUORUM is the QUORUM-th largest + unsigned long long th = sorted[NUMBER_OF_COMPUTORS - REVENUE_QUORUM]; + return th ? th : 1ULL; +} + +// Per-computor factor in [0, SCALE]. noActivityValve mirrors the oracle/doge valve: if the whole +// network is idle in this dimension, everyone gets the full factor (tx has no valve in core). +static void revComputeFactor(const unsigned long long* scores, bool noActivityValve, unsigned long long* out) +{ + if (noActivityValve) + { + bool any = false; + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + if (scores[i]) + { + any = true; + break; + } + } + if (!any) + { + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + out[i] = REVENUE_SCALE; + } + return; + } + } + unsigned long long th = revQuorumThreshold(scores); + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + if (scores[i] == 0) + { + out[i] = 0; + } + else if (scores[i] >= th) + { + out[i] = REVENUE_SCALE; + } + else + { + out[i] = REVENUE_SCALE * scores[i] / th; + } + } +} + +static unsigned long long revIpow(unsigned long long v, unsigned int e) +{ + unsigned long long r = 1; + for (unsigned int i = 0; i < e; i++) + { + r *= v; + } + return r; +} + +// true if x^k <= n, computed without overflow +static bool revPowLE(unsigned long long x, unsigned int k, unsigned long long n) +{ + unsigned long long r = 1; + for (unsigned int i = 0; i < k; i++) + { + if (x != 0 && r > n / x) + { + return false; + } + r *= x; + } + return r <= n; +} + +// floor(n^(1/k)), matches core math_lib::irootK64 +static unsigned long long revIroot(unsigned long long n, unsigned int k) +{ + if (k <= 1) + { + return n; + } + if (n == 0) + { + return 0; + } + unsigned long long lo = 0, hi = n; + while (lo < hi) + { + unsigned long long mid = lo + (hi - lo + 1) / 2; + if (revPowLE(mid, k, n)) + { + lo = mid; + } + else + { + hi = mid - 1; + } + } + return lo; +} + +void dumpRevenueDataFromNode(const char* nodeIp, int nodePort, const char* outputFile) +{ + auto qc = make_qc(nodeIp, nodePort); + + struct { + RequestResponseHeader header; + } packet; + packet.header.setSize(sizeof(packet)); + packet.header.randomizeDejavu(); + packet.header.setType(REQUEST_REVENUE_DATA); + qc->sendData((uint8_t *) &packet, packet.header.size()); + + // RevenueData is ~16 KB; receive into a heap buffer to avoid a large stack frame. + auto result = std::make_unique(); + try + { + qc->receivePacketWithHeaderAs(*result); + } + catch (std::logic_error) + { + LOG("Error while getting revenue data from %s:%d\n", nodeIp, nodePort); + return; + } + + // Compute the factors and revenue exactly as the node does at end of epoch (approximate mid-epoch). + auto txF = std::make_unique(NUMBER_OF_COMPUTORS); + auto oracleF = std::make_unique(NUMBER_OF_COMPUTORS); + auto dogeF = std::make_unique(NUMBER_OF_COMPUTORS); + revComputeFactor(result->txScore, /*noActivityValve=*/false, txF.get()); + revComputeFactor(result->oracleScore, /*noActivityValve=*/true, oracleF.get()); + revComputeFactor(result->dogeScore, /*noActivityValve=*/true, dogeF.get()); + + const unsigned int K = result->dogeK ? result->dogeK : 1; + const unsigned long long sPowKm1 = revIpow(REVENUE_SCALE, K - 1); + const unsigned long long divisor = REVENUE_SCALE * REVENUE_SCALE * REVENUE_SCALE; + + FILE* f = fopen(outputFile, "w"); + if (!f) + { + LOG("Failed to open %s for writing\n", outputFile); + return; + } + fprintf(f, "Tick,DogeK,IPC,ComputorIndex,txScore,oracleScore,dogeScore,txFactor,oracleFactor,dogeFactor,revenue,revenuePercent\n"); + + for (unsigned int i = 0; i < NUMBER_OF_COMPUTORS; i++) + { + const unsigned long long dogeRoot = revIroot(dogeF[i] * sPowKm1, K); + const unsigned long long revenue = + (unsigned long long)result->ipc * txF[i] * oracleF[i] * dogeRoot / divisor; + const double pct = result->ipc ? (100.0 * (double)revenue / (double)result->ipc) : 0.0; + fprintf(f, "%u,%u,%lld,%u,%llu,%llu,%llu,%llu,%llu,%llu,%llu,%.4f\n", + result->tick, result->dogeK, result->ipc, + i, result->txScore[i], result->oracleScore[i], result->dogeScore[i], + txF[i], oracleF[i], dogeF[i], revenue, pct); + } + + fclose(f); +} + static void getTickTransactions(QCPtr qc, const uint32_t requestedTick, int nTx, std::vector& txs, // out std::vector* hashes, // out diff --git a/node_utils.h b/node_utils.h index 87e059d..34c6d99 100644 --- a/node_utils.h +++ b/node_utils.h @@ -6,6 +6,7 @@ void printTickInfoFromNode(const char* nodeIp, int nodePort); void printSystemInfoFromNode(const char* nodeIp, int nodePort); CurrentSystemInfo getSystemInfoFromNode(QCPtr qc); +void dumpRevenueDataFromNode(const char* nodeIp, int nodePort, const char* outputFile); uint32_t getTickNumberFromNode(QCPtr qc); bool checkTxOnTick(QCPtr qc, const char* txHash, uint32_t requestedTick, bool printTxReceipt = true); bool checkTxOnTick(const char* nodeIp, const int nodePort, const char* txHash, uint32_t requestedTick, bool printTxReceipt = true); diff --git a/structs.h b/structs.h index 8520594..3f9561a 100644 --- a/structs.h +++ b/structs.h @@ -44,6 +44,7 @@ enum COMMAND REISSUE_VOTE, SYNC_TIME, GET_SYSTEM_INFO, + GET_REVENUE_DATA, QX_ORDER, QX_GET_ORDER, GET_MINING_SCORE_RANKING, @@ -419,6 +420,26 @@ struct CurrentSystemInfo }; #pragma pack(pop) +// Mirrors core RespondRevenueData (src/network_messages/revenue_data.h). Wire format must match exactly. +// Approximate, current-tick snapshot of the per-computor revenue score components; the consumer +// computes the final revenue from these (rank-cap + k-th root DOGE softening + multiply). +#pragma pack(push, 1) +struct RevenueData +{ + unsigned int tick; // current tick the scores correspond to + unsigned short dogeK; // REVENUE_DOGE_K (DOGE softening exponent) active on the node + long long ipc; // REVENUE_IPC, the per-computor revenue cap + unsigned long long txScore[NUMBER_OF_COMPUTORS]; + unsigned long long oracleScore[NUMBER_OF_COMPUTORS]; + unsigned long long dogeScore[NUMBER_OF_COMPUTORS]; + + static constexpr unsigned char type() + { + return RESPOND_REVENUE_DATA; + } +}; +#pragma pack(pop) + struct TickData { unsigned short computorIndex;