From ffe15395f0cea088d8d12d64d3c514114f15fc0c Mon Sep 17 00:00:00 2001 From: KavataK Date: Mon, 13 Apr 2026 15:51:01 +0300 Subject: [PATCH 1/2] add transfersManagementRights and transferShareToManyV1 --- argparser.h | 29 +++++++++- global.h | 1 + main.cpp | 18 ++++++ qutil.cpp | 160 ++++++++++++++++++++++++++++++++++++++++++++++++++-- qutil.h | 7 +++ structs.h | 2 + 6 files changed, 212 insertions(+), 5 deletions(-) diff --git a/argparser.h b/argparser.h index b39ec668..247ce3ef 100644 --- a/argparser.h +++ b/argparser.h @@ -92,6 +92,10 @@ void print_help() printf("\t\tCancel a poll by its ID. Only the poll creator can cancel it. Requires seed and node ip/port.\n"); printf("\t-qutilgetfee\n"); printf("\t\tShow current QUTIL fees.\n"); + printf("\t-qutiltransfersharetomanyv1 \n"); + printf("\t\tTransfer shares of an asset to up to 24 recipients in one tick. is like -qutilsendtomanyv1 (identity and amount per line).Valid seed and node ip/port are required.\n"); + printf("\t-qutiltransfersharemanagementrights \n"); + printf("\t\tTransfer asset management rights of shares from QUTIL to another contract. can be given as name or index. You need to own/possess the shares to do this (seed required).\n"); printf("\n[BLOCKCHAIN/PROTOCOL COMMANDS]\n"); printf("\t-gettickdata \n"); @@ -1626,7 +1630,30 @@ void parseArgument(int argc, char** argv) i += 1; CHECK_OVER_PARAMETERS break; - } + } + if (strcmp(argv[i], "-qutiltransfersharetomanyv1") == 0) + { + CHECK_NUMBER_OF_PARAMETERS(3) + g_cmd = QUTIL_TRANSFER_SHARE_TO_MANY_V1; + g_paramString1 = argv[i + 1]; + g_paramString2 = argv[i + 2]; + g_qutil_transferSharePayoutListFile = argv[i + 3]; + i += 4; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qutiltransfersharemanagementrights") == 0) + { + CHECK_NUMBER_OF_PARAMETERS(4) + g_cmd = QUTIL_TRANSFER_SHARE_MANAGEMENT_RIGHTS; + g_qx_assetName = argv[i + 1]; + g_qx_issuer = argv[i + 2]; + g_contractIndex = getContractIndex(argv[i + 3], g_enableTestContracts); + g_qx_numberOfShare = charToNumber(argv[i + 4]); + i += 5; + CHECK_OVER_PARAMETERS + break; + } /**************************** ***** GQMPROP COMMANDS ***** diff --git a/global.h b/global.h index 84acbacd..243baa1c 100644 --- a/global.h +++ b/global.h @@ -92,6 +92,7 @@ uint32_t g_quottery_pickedOption = 0; // qutil char* g_qutil_sendToManyV1PayoutListFile = nullptr; +char* g_qutil_transferSharePayoutListFile = nullptr; int64_t g_qutil_sendToManyBenchmarkDestinationCount = 0; int64_t g_qutil_sendToManyBenchmarkNumTransfersEach = 0; diff --git a/main.cpp b/main.cpp index 2e17372f..01ce02bc 100644 --- a/main.cpp +++ b/main.cpp @@ -482,6 +482,24 @@ int run(int argc, char* argv[]) sanityCheckNode(g_nodeIp, g_nodePort); qutilPrintFees(g_nodeIp, g_nodePort); break; + case QUTIL_TRANSFER_SHARE_TO_MANY_V1: + sanityCheckNode(g_nodeIp, g_nodePort); + sanityCheckSeed(g_seed); + sanityCheckIdentity(g_paramString1); + sanityCheckValidAssetName(g_paramString2); + sanityFileExist(g_qutil_transferSharePayoutListFile); + qutilTransferShareToManyV1(g_nodeIp, g_nodePort, g_seed, g_paramString1, g_paramString2, + g_qutil_transferSharePayoutListFile, g_offsetScheduledTick); + break; + case QUTIL_TRANSFER_SHARE_MANAGEMENT_RIGHTS: + sanityCheckNode(g_nodeIp, g_nodePort); + sanityCheckSeed(g_seed); + sanityCheckValidAssetName(g_qx_assetName); + sanityCheckIdentity(g_qx_issuer); + sanityCheckNumberOfUnit(g_qx_numberOfShare); + qutilTransferShareManagementRights(g_nodeIp, g_nodePort, g_seed, g_qx_assetName, g_qx_issuer, + g_contractIndex, g_qx_numberOfShare, g_offsetScheduledTick); + break; case GQMPROP_SET_PROPOSAL: sanityCheckNode(g_nodeIp, g_nodePort); sanityCheckSeed(g_seed); diff --git a/qutil.cpp b/qutil.cpp index cac08c4b..8721356e 100644 --- a/qutil.cpp +++ b/qutil.cpp @@ -17,6 +17,8 @@ #include "sanity_check.h" constexpr int QUTIL_CONTRACT_ID = 4; +// TODO: QU attached to tx and offeredTransferFee in input +constexpr int64_t QUTIL_TRANSFER_SHARE_MANAGEMENT_TX_FEE = 100; // TODO: use value from core source code constexpr unsigned long long CONTRACT_ACTION_TRACKER_SIZE = 16 * 1024 * 1024; @@ -52,21 +54,60 @@ struct SendToManyBenchmark_input int64_t numTransfersEach; }; -void readPayoutList(const char* payoutListFile, std::vector& addresses, std::vector& amounts) +struct TransferShareToManyV1_input +{ + uint8_t issuer[32]; + uint64_t assetName; + uint8_t addresses[24][32]; + int64_t amounts[24]; +}; +static_assert(sizeof(TransferShareToManyV1_input) == 1000); + +struct TransferShareManagementRights_input +{ + qpi::Asset asset; + int64_t numberOfShares; + uint32_t newManagingContractIndex; + int64_t offeredTransferFee; +}; +static_assert(sizeof(TransferShareManagementRights_input) == 64); + +bool readPayoutList(const char* payoutListFile, std::vector& addresses, std::vector& amounts, std::string& error) { addresses.resize(0); amounts.resize(0); std::ifstream infile(payoutListFile); + if (!infile.is_open()) + { + error = "failed to open payout file"; + return false; + } + std::string line; + uint64_t lineNo = 0; while (std::getline(infile, line)) { + ++lineNo; + if (line.empty()) + continue; + std::istringstream iss(line); - std::string a; + std::string a, extra; int64_t b; - if (!(iss >> a >> b)) { break; } // error + if (!(iss >> a >> b) || (iss >> extra)) + { + error = "invalid format at line " + std::to_string(lineNo) + ", expected: "; + return false; + } + if (a.size() != 60) + { + error = "invalid identity length at line " + std::to_string(lineNo) + ", expected 60 chars"; + return false; + } addresses.push_back(a); amounts.push_back(b); } + return true; } static std::string assetNameFromInt64(unsigned long long assetName) @@ -159,7 +200,12 @@ void qutilSendToManyV1(const char* nodeIp, int nodePort, const char* seed, const std::vector addresses; std::vector amounts; - readPayoutList(payoutListFile, addresses, amounts); + std::string parseError; + if (!readPayoutList(payoutListFile, addresses, amounts, parseError)) + { + LOG("ERROR: %s\n", parseError.c_str()); + return; + } if (addresses.size() > 25) { LOG("WARNING: payout list has more than 25 addresses, only the first 25 addresses will be paid\n"); @@ -229,6 +275,112 @@ void qutilSendToManyV1(const char* nodeIp, int nodePort, const char* seed, const LOG("to check your tx confirmation status\n"); } +void qutilTransferShareToManyV1(const char* nodeIp, int nodePort, const char* seed, + const char* issuerIdentity, const char* assetName, const char* payoutListFile, uint32_t scheduledTickOffset) +{ + auto qc = make_qc(nodeIp, nodePort); + + std::vector addresses; + std::vector amounts; + std::string parseError; + if (!readPayoutList(payoutListFile, addresses, amounts, parseError)) + { + LOG("ERROR: %s\n", parseError.c_str()); + return; + } + if (addresses.empty()) + { + LOG("ERROR: payout file is empty or has no valid \" \" lines.\n"); + return; + } + if (addresses.size() > 24) + { + LOG("WARNING: payout list has more than 24 addresses, only the first 24 entries will be used\n"); + } + + uint8_t privateKey[32] = {0}; + uint8_t sourcePublicKey[32] = {0}; + uint8_t destPublicKey[32] = {0}; + uint8_t subseed[32] = {0}; + uint8_t digest[32] = {0}; + uint8_t signature[64] = {0}; + char txHash[128] = {0}; + getSubseedFromSeed((uint8_t*)seed, subseed); + getPrivateKeyFromSubSeed(subseed, privateKey); + getPublicKeyFromPrivateKey(privateKey, sourcePublicKey); + ((uint64_t*)destPublicKey)[0] = QUTIL_CONTRACT_ID; + ((uint64_t*)destPublicKey)[1] = 0; + ((uint64_t*)destPublicKey)[2] = 0; + ((uint64_t*)destPublicKey)[3] = 0; + + struct { + RequestResponseHeader header; + Transaction transaction; + TransferShareToManyV1_input tsm; + unsigned char signature[64]; + } packet; + memset(&packet.tsm, 0, sizeof(TransferShareToManyV1_input)); + getPublicKeyFromIdentity(issuerIdentity, packet.tsm.issuer); + packet.tsm.assetName = 0; + memcpy(&packet.tsm.assetName, assetName, std::min(size_t(7), strlen(assetName))); + + for (int i = 0; i < std::min(24, int(addresses.size())); i++) + { + getPublicKeyFromIdentity(addresses[i].data(), packet.tsm.addresses[i]); + packet.tsm.amounts[i] = amounts[i]; + } + + long long fee = getSendToManyV1Fee(qc); + if (fee == -1) + return; + LOG("TransferShareToManyV1 invocation fee: %lld\n", fee); + packet.transaction.amount = fee; + memcpy(packet.transaction.sourcePublicKey, sourcePublicKey, 32); + memcpy(packet.transaction.destinationPublicKey, destPublicKey, 32); + uint32_t currentTick = getTickNumberFromNode(qc); + packet.transaction.tick = currentTick + scheduledTickOffset; + packet.transaction.inputType = qutilProcedureId::TransferShareToManyV1; + packet.transaction.inputSize = sizeof(TransferShareToManyV1_input); + KangarooTwelve((unsigned char*)&packet.transaction, + sizeof(packet.transaction) + sizeof(TransferShareToManyV1_input), + digest, + 32); + sign(subseed, sourcePublicKey, digest, signature); + memcpy(packet.signature, signature, 64); + packet.header.setSize(sizeof(packet)); + packet.header.zeroDejavu(); + packet.header.setType(BROADCAST_TRANSACTION); + + qc->sendData((uint8_t *) &packet, packet.header.size()); + KangarooTwelve((unsigned char*)&packet.transaction, + sizeof(packet.transaction) + sizeof(TransferShareToManyV1_input) + SIGNATURE_SIZE, + digest, + 32); + getTxHashFromDigest(digest, txHash); + LOG("TransferShareToManyV1 tx has been sent!\n"); + printReceipt(packet.transaction, txHash, nullptr); + LOG("run ./qubic-cli [...] -checktxontick %u %s\n", currentTick + scheduledTickOffset, txHash); + LOG("to check your tx confirmation status\n"); +} + +void qutilTransferShareManagementRights(const char* nodeIp, int nodePort, const char* seed, + const char* assetName, const char* issuerIdentity, uint32_t newManagingContractIndex, + int64_t numberOfShares, uint32_t scheduledTickOffset) +{ + TransferShareManagementRights_input input{}; + input.asset.assetName = 0; + memcpy(&input.asset.assetName, assetName, std::min(size_t(7), strlen(assetName))); + getPublicKeyFromIdentity(issuerIdentity, input.asset.issuer); + input.numberOfShares = numberOfShares; + input.newManagingContractIndex = newManagingContractIndex; + input.offeredTransferFee = QUTIL_TRANSFER_SHARE_MANAGEMENT_TX_FEE; + + LOG("\nSending TransferShareManagementRights transaction (fee %" PRIi64 " QU) ...\n", + QUTIL_TRANSFER_SHARE_MANAGEMENT_TX_FEE); + makeContractTransaction(nodeIp, nodePort, seed, QUTIL_CONTRACT_ID, qutilProcedureId::TransferShareManagementRights, + static_cast(QUTIL_TRANSFER_SHARE_MANAGEMENT_TX_FEE), sizeof(input), &input, scheduledTickOffset); +} + void qutilBurnQubic(const char* nodeIp, int nodePort, const char* seed, long long amount, uint32_t scheduledTickOffset) { auto qc = make_qc(nodeIp, nodePort); diff --git a/qutil.h b/qutil.h index 73895bf4..6ba8b5b9 100644 --- a/qutil.h +++ b/qutil.h @@ -107,6 +107,8 @@ enum qutilProcedureId CancelPoll = 6, DistributeQuToShareholders = 7, BurnQubicForContract = 8, + TransferShareToManyV1 = 9, + TransferShareManagementRights = 10, }; enum qutilFunctionId @@ -132,6 +134,11 @@ struct GetSendToManyV1Fee_output void qutilSendToManyV1(const char* nodeIp, int nodePort, const char* seed, const char* payoutListFile, uint32_t scheduledTickOffset); +void qutilTransferShareToManyV1(const char* nodeIp, int nodePort, const char* seed, + const char* issuerIdentity, const char* assetName, const char* payoutListFile, uint32_t scheduledTickOffset); +void qutilTransferShareManagementRights(const char* nodeIp, int nodePort, const char* seed, + const char* assetName, const char* issuerIdentity, uint32_t newManagingContractIndex, + int64_t numberOfShares, uint32_t scheduledTickOffset); void qutilBurnQubic(const char* nodeIp, int nodePort, const char* seed, long long amount, uint32_t scheduledTickOffset); void qutilBurnQubicForContract(const char* nodeIp, int nodePort, const char* seed, long long amount, uint32_t contractIndex, uint32_t scheduledTickOffset); void qutilQueryFeeReserve(const char* nodeIp, int nodePort, uint32_t contractIndex); diff --git a/structs.h b/structs.h index a4f8d7ac..7bcfa378 100644 --- a/structs.h +++ b/structs.h @@ -134,6 +134,8 @@ enum COMMAND QUTIL_PRINT_FEE, QUTIL_BURN_QUBIC_FOR_CONTRACT, QUTIL_QUERY_FEE_RESERVE, + QUTIL_TRANSFER_SHARE_TO_MANY_V1, + QUTIL_TRANSFER_SHARE_MANAGEMENT_RIGHTS, PRINT_QSWAP_FEE, QSWAP_ISSUE_ASSET, QSWAP_TRANSFER_ASSET, From e7862e99a5e70208b6b4612de4979dbae92a2f17 Mon Sep 17 00:00:00 2001 From: KavataK Date: Mon, 20 Apr 2026 04:02:19 +0300 Subject: [PATCH 2/2] feat(qutil): update share transfer commands, add getbalancesmany --- argparser.h | 31 ++++++--- global.h | 1 + main.cpp | 13 ++-- qutil.cpp | 182 ++++++++++++++++++++++++++++++++++++++++++++-------- qutil.h | 11 ++-- structs.h | 5 +- 6 files changed, 197 insertions(+), 46 deletions(-) diff --git a/argparser.h b/argparser.h index 247ce3ef..8dbadd2e 100644 --- a/argparser.h +++ b/argparser.h @@ -92,11 +92,12 @@ void print_help() printf("\t\tCancel a poll by its ID. Only the poll creator can cancel it. Requires seed and node ip/port.\n"); printf("\t-qutilgetfee\n"); printf("\t\tShow current QUTIL fees.\n"); - printf("\t-qutiltransfersharetomanyv1 \n"); - printf("\t\tTransfer shares of an asset to up to 24 recipients in one tick. is like -qutilsendtomanyv1 (identity and amount per line).Valid seed and node ip/port are required.\n"); - printf("\t-qutiltransfersharemanagementrights \n"); - printf("\t\tTransfer asset management rights of shares from QUTIL to another contract. can be given as name or index. You need to own/possess the shares to do this (seed required).\n"); - + printf("\t-getbalancesmany \n"); + printf("\t\tQuery Qubic balances via QUTIL GetBalances16 in batches of 16 (one 60-char identity per line; any count).\n"); + printf("\t-qutiltransfersharestomanyv1 \n"); + printf("\t\tTransfer shares of an asset to up to 24 recipients in one tick. Fee: 2500 QU (fixed; does not depend on how many lines are in the file). is like -qutilsendtomanyv1 (identity and amount per line). Valid seed and node ip/port are required.\n"); + printf("\t-qutiltransferrights \n"); + printf("\t\tTransfer asset management rights of shares from Qutil to another contract. can be given as name or index. You need to own/possess the shares to do this (seed required).\n"); printf("\n[BLOCKCHAIN/PROTOCOL COMMANDS]\n"); printf("\t-gettickdata \n"); printf("\t\tGet tick data and write it to a file. Use -readtickdata to examine the file. valid node ip/port are required.\n"); @@ -1631,10 +1632,20 @@ void parseArgument(int argc, char** argv) CHECK_OVER_PARAMETERS break; } - if (strcmp(argv[i], "-qutiltransfersharetomanyv1") == 0) + if (strcmp(argv[i], "-getbalancesmany") == 0) + { + CHECK_NUMBER_OF_PARAMETERS(1) + g_cmd = QUTIL_GET_BALANCES_MANY; + g_qutil_getBalancesManyFile = argv[i + 1]; + i += 2; + CHECK_OVER_PARAMETERS + break; + } + if (strcmp(argv[i], "-qutiltransfersharestomanyv1") == 0 + || strcmp(argv[i], "-qutiltransfersharetomanyv1") == 0) { CHECK_NUMBER_OF_PARAMETERS(3) - g_cmd = QUTIL_TRANSFER_SHARE_TO_MANY_V1; + g_cmd = QUTIL_TRANSFER_SHARES_TO_MANY_V1; g_paramString1 = argv[i + 1]; g_paramString2 = argv[i + 2]; g_qutil_transferSharePayoutListFile = argv[i + 3]; @@ -1642,10 +1653,12 @@ void parseArgument(int argc, char** argv) CHECK_OVER_PARAMETERS break; } - if (strcmp(argv[i], "-qutiltransfersharemanagementrights") == 0) + if (strcmp(argv[i], "-qutiltransferrights") == 0 + || strcmp(argv[i], "-qutiltransfersharesmanagementrights") == 0 + || strcmp(argv[i], "-qutiltransfersharemanagementrights") == 0) { CHECK_NUMBER_OF_PARAMETERS(4) - g_cmd = QUTIL_TRANSFER_SHARE_MANAGEMENT_RIGHTS; + g_cmd = QUTIL_TRANSFER_SHARES_MANAGEMENT_RIGHTS; g_qx_assetName = argv[i + 1]; g_qx_issuer = argv[i + 2]; g_contractIndex = getContractIndex(argv[i + 3], g_enableTestContracts); diff --git a/global.h b/global.h index 243baa1c..ab97f603 100644 --- a/global.h +++ b/global.h @@ -93,6 +93,7 @@ uint32_t g_quottery_pickedOption = 0; // qutil char* g_qutil_sendToManyV1PayoutListFile = nullptr; char* g_qutil_transferSharePayoutListFile = nullptr; +char* g_qutil_getBalancesManyFile = nullptr; int64_t g_qutil_sendToManyBenchmarkDestinationCount = 0; int64_t g_qutil_sendToManyBenchmarkNumTransfersEach = 0; diff --git a/main.cpp b/main.cpp index 01ce02bc..a8061e26 100644 --- a/main.cpp +++ b/main.cpp @@ -482,22 +482,27 @@ int run(int argc, char* argv[]) sanityCheckNode(g_nodeIp, g_nodePort); qutilPrintFees(g_nodeIp, g_nodePort); break; - case QUTIL_TRANSFER_SHARE_TO_MANY_V1: + case QUTIL_GET_BALANCES_MANY: + sanityCheckNode(g_nodeIp, g_nodePort); + sanityFileExist(g_qutil_getBalancesManyFile); + qutilGetBalancesMany(g_nodeIp, g_nodePort, g_qutil_getBalancesManyFile); + break; + case QUTIL_TRANSFER_SHARES_TO_MANY_V1: sanityCheckNode(g_nodeIp, g_nodePort); sanityCheckSeed(g_seed); sanityCheckIdentity(g_paramString1); sanityCheckValidAssetName(g_paramString2); sanityFileExist(g_qutil_transferSharePayoutListFile); - qutilTransferShareToManyV1(g_nodeIp, g_nodePort, g_seed, g_paramString1, g_paramString2, + qutilTransferSharesToManyV1(g_nodeIp, g_nodePort, g_seed, g_paramString1, g_paramString2, g_qutil_transferSharePayoutListFile, g_offsetScheduledTick); break; - case QUTIL_TRANSFER_SHARE_MANAGEMENT_RIGHTS: + case QUTIL_TRANSFER_SHARES_MANAGEMENT_RIGHTS: sanityCheckNode(g_nodeIp, g_nodePort); sanityCheckSeed(g_seed); sanityCheckValidAssetName(g_qx_assetName); sanityCheckIdentity(g_qx_issuer); sanityCheckNumberOfUnit(g_qx_numberOfShare); - qutilTransferShareManagementRights(g_nodeIp, g_nodePort, g_seed, g_qx_assetName, g_qx_issuer, + qutilTransferSharesManagementRights(g_nodeIp, g_nodePort, g_seed, g_qx_assetName, g_qx_issuer, g_contractIndex, g_qx_numberOfShare, g_offsetScheduledTick); break; case GQMPROP_SET_PROPOSAL: diff --git a/qutil.cpp b/qutil.cpp index 8721356e..535a3ae6 100644 --- a/qutil.cpp +++ b/qutil.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -17,8 +18,8 @@ #include "sanity_check.h" constexpr int QUTIL_CONTRACT_ID = 4; -// TODO: QU attached to tx and offeredTransferFee in input -constexpr int64_t QUTIL_TRANSFER_SHARE_MANAGEMENT_TX_FEE = 100; +constexpr int64_t QUTIL_TRANSFER_SHARES_TO_MANY_FEE_QU = 2500; +constexpr int64_t QUTIL_TRANSFER_SHARES_MANAGEMENT_FEE_QU = 100; // TODO: use value from core source code constexpr unsigned long long CONTRACT_ACTION_TRACKER_SIZE = 16 * 1024 * 1024; @@ -54,23 +55,109 @@ struct SendToManyBenchmark_input int64_t numTransfersEach; }; -struct TransferShareToManyV1_input +struct TransferSharesToManyV1_input { uint8_t issuer[32]; uint64_t assetName; uint8_t addresses[24][32]; int64_t amounts[24]; }; -static_assert(sizeof(TransferShareToManyV1_input) == 1000); +static_assert(sizeof(TransferSharesToManyV1_input) == 1000); -struct TransferShareManagementRights_input +struct TransferSharesManagementRights_input { qpi::Asset asset; int64_t numberOfShares; uint32_t newManagingContractIndex; - int64_t offeredTransferFee; }; -static_assert(sizeof(TransferShareManagementRights_input) == 64); +static_assert(sizeof(TransferSharesManagementRights_input) == 56); + +struct GetBalances16_input +{ + uint8_t publicKeys[16][32]; +}; +struct GetBalances16_output +{ + int64_t balances[16]; +}; +static_assert(sizeof(GetBalances16_input) == 512); +static_assert(sizeof(GetBalances16_output) == 128); + +static constexpr int kGetBalances16MaxAttempts = 6; + +static bool tryGetBalances16Batch(const char* nodeIp, int nodePort, + GetBalances16_input* input, GetBalances16_output* output, + size_t batchIndex1Based, size_t numBatches) +{ + for (int attempt = 1; attempt <= kGetBalances16MaxAttempts; ++attempt) + { + bool ok = false; + try + { + ok = runContractFunction(nodeIp, nodePort, QUTIL_CONTRACT_ID, qutilFunctionId::GetBalances16, + input, sizeof(GetBalances16_input), output, sizeof(GetBalances16_output)); + } + catch (...) + { +#ifdef _DEBUG + LOG("WARNING: GetBalances16 batch %zu/%zu: connection or response error.\n", batchIndex1Based, numBatches); +#endif + ok = false; + } + if (ok) + return true; + if (attempt < kGetBalances16MaxAttempts) + { +#ifdef _DEBUG + LOG("WARNING: GetBalances16 batch %zu/%zu: attempt %d/%d failed; retrying...\n", + batchIndex1Based, numBatches, attempt, kGetBalances16MaxAttempts); +#endif + } + } + return false; +} + +static bool readIdentityList(const char* path, std::vector& identities, std::string& error) +{ + identities.clear(); + error.clear(); + std::ifstream infile(path); + if (!infile.is_open()) + { + error = "failed to open identity list file"; + return false; + } + + std::string line; + uint64_t lineNo = 0; + while (std::getline(infile, line)) + { + ++lineNo; + if (!line.empty() && line.back() == '\r') + line.pop_back(); + if (line.empty()) + continue; + + std::istringstream iss(line); + std::string id; + std::string extra; + if (!(iss >> id) || (iss >> extra)) + { + error = "invalid format at line " + std::to_string(lineNo) + ", expected a single identity token"; + return false; + } + if (id.size() != 60) + { + error = "invalid identity length at line " + std::to_string(lineNo) + ", expected 60 chars"; + return false; + } + identities.push_back(id); + } + + if (identities.empty()) + error = "no identities in file"; + return !identities.empty(); +} bool readPayoutList(const char* payoutListFile, std::vector& addresses, std::vector& amounts, std::string& error) { @@ -192,6 +279,8 @@ void qutilPrintFees(const char* nodeIp, int nodePort) LOG("Poll vote fee (var 2): %" PRIi64 "\n", fees.pollVoteFee); LOG("DistributeQuToShareholders fee (var 3): %" PRIi64 " per shareholder\n", fees.distributeQuToShareholderFeePerShareholder); LOG("Shareholder proposal fee (var 4): %" PRIi64 "\n", fees.shareholderProposalFee); + LOG("TransferSharesToManyV1 (fixed): %" PRIi64 "\n", (int64_t)QUTIL_TRANSFER_SHARES_TO_MANY_FEE_QU); + LOG("TransferSharesManagementRights (fixed): %" PRIi64 "\n", (int64_t)QUTIL_TRANSFER_SHARES_MANAGEMENT_FEE_QU); } void qutilSendToManyV1(const char* nodeIp, int nodePort, const char* seed, const char* payoutListFile, uint32_t scheduledTickOffset) @@ -275,7 +364,7 @@ void qutilSendToManyV1(const char* nodeIp, int nodePort, const char* seed, const LOG("to check your tx confirmation status\n"); } -void qutilTransferShareToManyV1(const char* nodeIp, int nodePort, const char* seed, +void qutilTransferSharesToManyV1(const char* nodeIp, int nodePort, const char* seed, const char* issuerIdentity, const char* assetName, const char* payoutListFile, uint32_t scheduledTickOffset) { auto qc = make_qc(nodeIp, nodePort); @@ -316,10 +405,10 @@ void qutilTransferShareToManyV1(const char* nodeIp, int nodePort, const char* se struct { RequestResponseHeader header; Transaction transaction; - TransferShareToManyV1_input tsm; + TransferSharesToManyV1_input tsm; unsigned char signature[64]; } packet; - memset(&packet.tsm, 0, sizeof(TransferShareToManyV1_input)); + memset(&packet.tsm, 0, sizeof(TransferSharesToManyV1_input)); getPublicKeyFromIdentity(issuerIdentity, packet.tsm.issuer); packet.tsm.assetName = 0; memcpy(&packet.tsm.assetName, assetName, std::min(size_t(7), strlen(assetName))); @@ -330,19 +419,17 @@ void qutilTransferShareToManyV1(const char* nodeIp, int nodePort, const char* se packet.tsm.amounts[i] = amounts[i]; } - long long fee = getSendToManyV1Fee(qc); - if (fee == -1) - return; - LOG("TransferShareToManyV1 invocation fee: %lld\n", fee); - packet.transaction.amount = fee; + const int64_t fee = QUTIL_TRANSFER_SHARES_TO_MANY_FEE_QU; + LOG("TransferSharesToManyV1 fee: %lld QU\n", (long long)fee); + packet.transaction.amount = (uint64_t)fee; memcpy(packet.transaction.sourcePublicKey, sourcePublicKey, 32); memcpy(packet.transaction.destinationPublicKey, destPublicKey, 32); uint32_t currentTick = getTickNumberFromNode(qc); packet.transaction.tick = currentTick + scheduledTickOffset; - packet.transaction.inputType = qutilProcedureId::TransferShareToManyV1; - packet.transaction.inputSize = sizeof(TransferShareToManyV1_input); + packet.transaction.inputType = qutilProcedureId::TransferSharesToManyV1; + packet.transaction.inputSize = sizeof(TransferSharesToManyV1_input); KangarooTwelve((unsigned char*)&packet.transaction, - sizeof(packet.transaction) + sizeof(TransferShareToManyV1_input), + sizeof(packet.transaction) + sizeof(TransferSharesToManyV1_input), digest, 32); sign(subseed, sourcePublicKey, digest, signature); @@ -353,32 +440,73 @@ void qutilTransferShareToManyV1(const char* nodeIp, int nodePort, const char* se qc->sendData((uint8_t *) &packet, packet.header.size()); KangarooTwelve((unsigned char*)&packet.transaction, - sizeof(packet.transaction) + sizeof(TransferShareToManyV1_input) + SIGNATURE_SIZE, + sizeof(packet.transaction) + sizeof(TransferSharesToManyV1_input) + SIGNATURE_SIZE, digest, 32); getTxHashFromDigest(digest, txHash); - LOG("TransferShareToManyV1 tx has been sent!\n"); + LOG("TransferSharesToManyV1 tx has been sent!\n"); printReceipt(packet.transaction, txHash, nullptr); LOG("run ./qubic-cli [...] -checktxontick %u %s\n", currentTick + scheduledTickOffset, txHash); LOG("to check your tx confirmation status\n"); } -void qutilTransferShareManagementRights(const char* nodeIp, int nodePort, const char* seed, +void qutilTransferSharesManagementRights(const char* nodeIp, int nodePort, const char* seed, const char* assetName, const char* issuerIdentity, uint32_t newManagingContractIndex, int64_t numberOfShares, uint32_t scheduledTickOffset) { - TransferShareManagementRights_input input{}; + TransferSharesManagementRights_input input{}; input.asset.assetName = 0; memcpy(&input.asset.assetName, assetName, std::min(size_t(7), strlen(assetName))); getPublicKeyFromIdentity(issuerIdentity, input.asset.issuer); input.numberOfShares = numberOfShares; input.newManagingContractIndex = newManagingContractIndex; - input.offeredTransferFee = QUTIL_TRANSFER_SHARE_MANAGEMENT_TX_FEE; - LOG("\nSending TransferShareManagementRights transaction (fee %" PRIi64 " QU) ...\n", - QUTIL_TRANSFER_SHARE_MANAGEMENT_TX_FEE); - makeContractTransaction(nodeIp, nodePort, seed, QUTIL_CONTRACT_ID, qutilProcedureId::TransferShareManagementRights, - static_cast(QUTIL_TRANSFER_SHARE_MANAGEMENT_TX_FEE), sizeof(input), &input, scheduledTickOffset); + const uint64_t invocationQu = (uint64_t)QUTIL_TRANSFER_SHARES_MANAGEMENT_FEE_QU; + + LOG("\nSending TransferSharesManagementRights transaction (fee %" PRIu64 " QU) ...\n", + invocationQu); + makeContractTransaction(nodeIp, nodePort, seed, QUTIL_CONTRACT_ID, qutilProcedureId::TransferSharesManagementRights, + invocationQu, sizeof(input), &input, scheduledTickOffset); +} + +void qutilGetBalancesMany(const char* nodeIp, int nodePort, const char* identitiesFile) +{ + std::vector identities; + std::string err; + if (!readIdentityList(identitiesFile, identities, err)) + { + LOG("ERROR: %s\n", err.c_str()); + return; + } + + constexpr size_t kBatch = 16; + const size_t numBatches = (identities.size() + kBatch - 1) / kBatch; + if (numBatches > 1) + LOG("%zu identities, %zu GetBalances16 request(s).\n", identities.size(), numBatches); + + for (size_t b = 0; b < numBatches; b++) + { + const size_t offset = b * kBatch; + const size_t count = std::min(kBatch, identities.size() - offset); + + GetBalances16_input input{}; + memset(&input, 0, sizeof(input)); + for (size_t i = 0; i < count; i++) + getPublicKeyFromIdentity(identities[offset + i].c_str(), input.publicKeys[i]); + + GetBalances16_output output{}; + memset(&output, 0, sizeof(output)); + + if (!tryGetBalances16Batch(nodeIp, nodePort, &input, &output, b + 1, numBatches)) + { + LOG("ERROR: GetBalances16 batch %zu / %zu failed after %d attempts.\n", + b + 1, numBatches, kGetBalances16MaxAttempts); + return; + } + + for (size_t i = 0; i < count; i++) + LOG("%s %" PRIi64 "\n", identities[offset + i].c_str(), output.balances[i]); + } } void qutilBurnQubic(const char* nodeIp, int nodePort, const char* seed, long long amount, uint32_t scheduledTickOffset) diff --git a/qutil.h b/qutil.h index 6ba8b5b9..b0f11666 100644 --- a/qutil.h +++ b/qutil.h @@ -107,8 +107,8 @@ enum qutilProcedureId CancelPoll = 6, DistributeQuToShareholders = 7, BurnQubicForContract = 8, - TransferShareToManyV1 = 9, - TransferShareManagementRights = 10, + TransferSharesToManyV1 = 9, + TransferSharesManagementRights = 10, }; enum qutilFunctionId @@ -121,6 +121,7 @@ enum qutilFunctionId GetPollInfo = 6, GetFees = 7, QueryFeeReserve = 8, + GetBalances16 = 9, }; struct GetSendToManyV1Fee_output @@ -134,9 +135,9 @@ struct GetSendToManyV1Fee_output void qutilSendToManyV1(const char* nodeIp, int nodePort, const char* seed, const char* payoutListFile, uint32_t scheduledTickOffset); -void qutilTransferShareToManyV1(const char* nodeIp, int nodePort, const char* seed, +void qutilTransferSharesToManyV1(const char* nodeIp, int nodePort, const char* seed, const char* issuerIdentity, const char* assetName, const char* payoutListFile, uint32_t scheduledTickOffset); -void qutilTransferShareManagementRights(const char* nodeIp, int nodePort, const char* seed, +void qutilTransferSharesManagementRights(const char* nodeIp, int nodePort, const char* seed, const char* assetName, const char* issuerIdentity, uint32_t newManagingContractIndex, int64_t numberOfShares, uint32_t scheduledTickOffset); void qutilBurnQubic(const char* nodeIp, int nodePort, const char* seed, long long amount, uint32_t scheduledTickOffset); @@ -168,3 +169,5 @@ void qutilGetPollInfo(const char* nodeIp, int nodePort, uint64_t poll_id); void qutilCancelPoll(const char* nodeIp, int nodePort, const char* seed, uint64_t poll_id, uint32_t scheduledTickOffset); void qutilPrintFees(const char* nodeIp, int nodePort); + +void qutilGetBalancesMany(const char* nodeIp, int nodePort, const char* identitiesFile); diff --git a/structs.h b/structs.h index 7bcfa378..90083586 100644 --- a/structs.h +++ b/structs.h @@ -132,10 +132,11 @@ enum COMMAND QUTIL_CANCEL_POLL, QUTIL_DISTRIBUTE_QU_TO_SHAREHOLDERS, QUTIL_PRINT_FEE, + QUTIL_GET_BALANCES_MANY, QUTIL_BURN_QUBIC_FOR_CONTRACT, QUTIL_QUERY_FEE_RESERVE, - QUTIL_TRANSFER_SHARE_TO_MANY_V1, - QUTIL_TRANSFER_SHARE_MANAGEMENT_RIGHTS, + QUTIL_TRANSFER_SHARES_TO_MANY_V1, + QUTIL_TRANSFER_SHARES_MANAGEMENT_RIGHTS, PRINT_QSWAP_FEE, QSWAP_ISSUE_ASSET, QSWAP_TRANSFER_ASSET,