Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <OUTPUT_CSV_FILE>
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
Expand Down
12 changes: 12 additions & 0 deletions argparser.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <OUTPUT_CSV_FILE>\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");
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 3 additions & 0 deletions connection.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -420,3 +420,6 @@ template GetCurrentResult_output QubicConnection::receivePacketWithHeaderAs<GetC
template GetPollsByCreator_output QubicConnection::receivePacketWithHeaderAs<GetPollsByCreator_output>();
template GetCurrentPollId_output QubicConnection::receivePacketWithHeaderAs<GetCurrentPollId_output>();
template GetPollInfo_output QubicConnection::receivePacketWithHeaderAs<GetPollInfo_output>();

// REVENUE
template void QubicConnection::receivePacketWithHeaderAs<RevenueData>(RevenueData&);
2 changes: 2 additions & 0 deletions defines.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
5 changes: 5 additions & 0 deletions main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
167 changes: 167 additions & 0 deletions node_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<unsigned long long> 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<k>
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<RevenueData>();
try
{
qc->receivePacketWithHeaderAs<RevenueData>(*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<unsigned long long[]>(NUMBER_OF_COMPUTORS);
auto oracleF = std::make_unique<unsigned long long[]>(NUMBER_OF_COMPUTORS);
auto dogeF = std::make_unique<unsigned long long[]>(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<Transaction>& txs, // out
std::vector<TxhashStruct>* hashes, // out
Expand Down
1 change: 1 addition & 0 deletions node_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
21 changes: 21 additions & 0 deletions structs.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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;
Expand Down
Loading