Skip to content

Commit 1d29a80

Browse files
authored
Merge pull request #826 from qubic/develop
Release v1.286.0
2 parents 0383fba + 601917a commit 1d29a80

20 files changed

+836
-49
lines changed

src/Qubic.vcxproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@
7575
<ClInclude Include="files\files.h" />
7676
<ClInclude Include="logging\logging.h" />
7777
<ClInclude Include="logging\net_msg_impl.h" />
78+
<ClInclude Include="mining\custom_qubic_mining_storage.h" />
7879
<ClInclude Include="mining\mining.h" />
7980
<ClInclude Include="mining\score_addition.h" />
8081
<ClInclude Include="mining\score_common.h" />

src/Qubic.vcxproj.filters

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,6 +371,9 @@
371371
<ClInclude Include="oracle_interfaces\DogeShareValidation.h">
372372
<Filter>oracle_interfaces</Filter>
373373
</ClInclude>
374+
<ClInclude Include="mining\custom_qubic_mining_storage.h">
375+
<Filter>mining</Filter>
376+
</ClInclude>
374377
</ItemGroup>
375378
<ItemGroup>
376379
<Filter Include="platform">
@@ -421,4 +424,4 @@
421424
<Filter>platform</Filter>
422425
</MASM>
423426
</ItemGroup>
424-
</Project>
427+
</Project>

src/contract_core/qpi_hash_map_impl.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55

66
#pragma once
77

8+
#include "pre_qpi_def.h"
9+
#include "../common_buffers.h"
810
#include "../contracts/qpi.h"
911
#include "../platform/memory.h"
1012
#include "../kangaroo_twelve.h"

src/contract_core/qpi_spectrum_impl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#include "spectrum/spectrum.h"
55

66

7-
bool QPI::QpiContextFunctionCall::getEntity(const m256i& id, QPI::Entity& entity) const
7+
QPI::bit QPI::QpiContextFunctionCall::getEntity(const m256i& id, QPI::Entity& entity) const
88
{
99
int index = spectrumIndex(id);
1010
if (index < 0)

src/contract_core/qpi_trivial_impl.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ unsigned char QPI::QpiContextFunctionCall::dayOfWeek(unsigned char year, unsigne
102102
return dayIndex(year, month, day) % 7;
103103
}
104104

105-
bool QPI::QpiContextFunctionCall::signatureValidity(const m256i& entity, const m256i& digest, const Array<signed char, 64>& signature) const
105+
QPI::bit QPI::QpiContextFunctionCall::signatureValidity(const m256i& entity, const m256i& digest, const Array<signed char, 64>& signature) const
106106
{
107107
return verify(entity.m256i_u8, digest.m256i_u8, reinterpret_cast<const unsigned char*>(&signature));
108108
}

src/contracts/Pulse.h

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -479,6 +479,20 @@ struct PULSE : public ContractBase
479479
sint64 index;
480480
};
481481

482+
struct DepositManagedQHeart_input
483+
{
484+
sint64 amount;
485+
};
486+
struct DepositManagedQHeart_output
487+
{
488+
uint8 returnCode;
489+
};
490+
struct DepositManagedQHeart_locals
491+
{
492+
sint64 transferResult;
493+
sint64 userBalance;
494+
};
495+
482496
struct TransferTokenToQx_input
483497
{
484498
sint64 numberOfShares;
@@ -814,6 +828,7 @@ struct PULSE : public ContractBase
814828
REGISTER_USER_PROCEDURE(SetAutoConfig, 10);
815829
REGISTER_USER_PROCEDURE(SetAutoLimits, 11);
816830
REGISTER_USER_PROCEDURE(TransferTokenToQx, 12);
831+
REGISTER_USER_PROCEDURE(DepositManagedQHeart, 13);
817832
}
818833

819834
INITIALIZE()
@@ -1508,6 +1523,44 @@ struct PULSE : public ContractBase
15081523
output.returnCode = locals.allocateOutput.returnCode;
15091524
}
15101525

1526+
/**
1527+
* Deposits QHeart already managed by Pulse into the Pulse wallet.
1528+
* @param amount QHeart amount to transfer from the invocator to the contract.
1529+
* @return Status code describing whether the transfer succeeded.
1530+
* @note This only moves managed QHeart into `SELF`; it does not update auto-participation or other accounting.
1531+
*/
1532+
PUBLIC_PROCEDURE_WITH_LOCALS(DepositManagedQHeart)
1533+
{
1534+
if (qpi.invocationReward() > 0)
1535+
{
1536+
qpi.transfer(qpi.invocator(), qpi.invocationReward());
1537+
}
1538+
1539+
if (input.amount <= 0)
1540+
{
1541+
output.returnCode = toReturnCode(EReturnCode::INVALID_VALUE);
1542+
return;
1543+
}
1544+
1545+
locals.userBalance =
1546+
qpi.numberOfPossessedShares(PULSE_QHEART_ASSET_NAME, state.get().qheartIssuer, qpi.invocator(), qpi.invocator(), SELF_INDEX, SELF_INDEX);
1547+
if (locals.userBalance < input.amount)
1548+
{
1549+
output.returnCode = toReturnCode(EReturnCode::TICKET_INVALID_PRICE);
1550+
return;
1551+
}
1552+
1553+
locals.transferResult = qpi.transferShareOwnershipAndPossession(PULSE_QHEART_ASSET_NAME, state.get().qheartIssuer, qpi.invocator(),
1554+
qpi.invocator(), input.amount, SELF);
1555+
if (locals.transferResult < 0)
1556+
{
1557+
output.returnCode = toReturnCode(EReturnCode::TRANSFER_TO_PULSE_FAILED);
1558+
return;
1559+
}
1560+
1561+
output.returnCode = toReturnCode(EReturnCode::SUCCESS);
1562+
}
1563+
15111564
/**
15121565
* @brief Releases PULSE share management rights back to QX for the invocator.
15131566
* @param input Number of PULSE shares to transfer under QX management.

src/contracts/QBond.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -924,11 +924,11 @@ struct QBOND : public ContractBase
924924

925925
if (input.operation == 0)
926926
{
927-
output.result = state.mut()._commissionFreeAddresses.remove(input.user);
927+
output.result = state.mut()._commissionFreeAddresses.remove(input.user) != NULL_INDEX;
928928
}
929929
else
930930
{
931-
output.result = state.mut()._commissionFreeAddresses.add(input.user);
931+
output.result = state.mut()._commissionFreeAddresses.add(input.user) != NULL_INDEX;
932932
}
933933
}
934934

src/contracts/qpi.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,21 @@ namespace QPI
3939
4040
*/
4141

42-
typedef bool bit;
42+
// Boolean type ensuring that input values > 1 are mapped to 1.
43+
struct bit
44+
{
45+
bit(bool v = false) : charValue(v)
46+
{
47+
}
48+
49+
operator bool() const
50+
{
51+
return !!charValue;
52+
}
53+
54+
char charValue;
55+
};
56+
4357
typedef signed char sint8;
4458
typedef unsigned char uint8;
4559
typedef signed short sint16;
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
#pragma once
2+
3+
#include "lib/platform_common/qstdint.h"
4+
5+
#include "platform/memory.h"
6+
#include "platform/memory_util.h"
7+
#include "platform/concurrency.h"
8+
#include "network_messages/custom_mining.h"
9+
#include "contract_core/qpi_hash_map_impl.h"
10+
#include "kangaroo_twelve.h"
11+
12+
13+
class CustomQubicMiningStorage
14+
{
15+
public:
16+
static constexpr unsigned int maxNumTasks = 32;
17+
18+
// A struct for storing an active doge mining task on the node.
19+
struct StoredDogeMiningTask
20+
{
21+
uint8_t dispatcherTarget[32]; // dispatcher target, usually easier than pool and network difficulty, full 32-byte representation
22+
23+
// Full header can be constructed via concatenating version + prevHash + merkleRoot + miner's nTime + nBits + miner's nonce.
24+
uint8_t version[4]; // 4 bytes version
25+
uint8_t prevHash[32]; // 32 bytes prevBlockHash
26+
uint8_t nBits[4]; // 4 bytes network difficulty (nBits)
27+
};
28+
29+
private:
30+
static constexpr unsigned int maxNumSolutionsPerTask = 256;
31+
32+
uint64_t activeTasks[CustomMiningType::TOTAL_NUM_TYPES][maxNumTasks];
33+
unsigned int nextTaskIndex[CustomMiningType::TOTAL_NUM_TYPES];
34+
35+
// For each task, we store a set of received solution hashes to prevent duplicate solutions.
36+
// Two-dimensional array [CustomMiningType::TOTAL_NUM_TYPES][maxNumTasks] indexed by mining type and type-specific task index.
37+
QPI::HashSet<m256i, maxNumSolutionsPerTask>* receivedSolutions;
38+
static constexpr unsigned long long receivedSolutionsSize = CustomMiningType::TOTAL_NUM_TYPES * maxNumTasks * sizeof(QPI::HashSet<m256i, maxNumSolutionsPerTask>);
39+
40+
// Storage for type-specific task descriptions.
41+
StoredDogeMiningTask dogeTasks[maxNumTasks];
42+
43+
inline static volatile char lock = 0;
44+
45+
// Return the mining-type-specific index of the task with the given jobId and customMiningType, or -1 if not found.
46+
// The returned index can then be used to access the type-specific task descriptions and the solution hash set for this task.
47+
int findTask(uint8_t customMiningType, uint64_t jobId)
48+
{
49+
for (unsigned int i = 0; i < maxNumTasks; ++i)
50+
{
51+
if (activeTasks[customMiningType][i] == jobId)
52+
return i;
53+
}
54+
return -1;
55+
}
56+
57+
// Converts the compact representation of the target (4 bytes) to the full 32-byte representation, and writes it to the provided output pointer.
58+
// Expected input byte order: bytes 0 - 2 mantissa in little endian, byte 3 exponent.
59+
// Output byte order: little endian, i.e. least-significant byte in index 0.
60+
void convertTargetCompactToFull(const uint8_t* compact, uint8_t* full)
61+
{
62+
setMem(full, 32, 0);
63+
64+
uint8_t exponent = compact[3];
65+
66+
// The target is mantissa * 256^(exponent - 3).
67+
// This means the mantissa starts at byte index (exponent - 3).
68+
int start_index = exponent - 3;
69+
70+
for (int i = 0; i < 3; ++i)
71+
{
72+
int target_idx = start_index + i;
73+
if (target_idx >= 0 && target_idx < 32)
74+
{
75+
full[target_idx] = compact[i];
76+
}
77+
}
78+
}
79+
80+
public:
81+
82+
// Initialize the storage. Return true if successful, false if initialization failed (e.g. due to memory allocation failure).
83+
bool init()
84+
{
85+
ASSERT(lock == 0);
86+
87+
if (!allocPoolWithErrorLog(L"CustomQubicMiningStorage::receivedSolutions ", receivedSolutionsSize, (void**)&receivedSolutions, __LINE__))
88+
return false;
89+
90+
setMem(activeTasks, sizeof(activeTasks), 0);
91+
setMem(nextTaskIndex, sizeof(nextTaskIndex), 0);
92+
setMem(dogeTasks, sizeof(dogeTasks), 0);
93+
94+
return true;
95+
}
96+
97+
// Deinitialize the storage and free any allocated memory.
98+
void deinit()
99+
{
100+
if (receivedSolutions)
101+
freePool(receivedSolutions);
102+
}
103+
104+
// Add a new mining task. Return false if a task with the same jobId and customMiningType already exists, true if added successfully.
105+
// The task description is expected to be behind the CustomQubicMiningTask struct in memory. The provided size should specify the
106+
// full size of the CustomQubicMiningTask struct and the task description.
107+
// For DOGE mining: Adding a task with cleanJobQueue = true will clear all existing tasks and solutions before adding the new task.
108+
// If maxNumTasks is reached and a job with cleanJobQueue = false is added, it will override the oldest task and its solutions.
109+
bool addTask(const CustomQubicMiningTask* task, unsigned int size)
110+
{
111+
LockGuard guard(lock);
112+
113+
if (findTask(task->customMiningType, task->jobId) < 0)
114+
{
115+
unsigned int typeSpecificTaskIndex = 0;
116+
if (task->customMiningType == CustomMiningType::DOGE)
117+
{
118+
// Type-specific task info is stored behind general CustomQubicMiningTask struct.
119+
if (size < sizeof(CustomQubicMiningTask) + sizeof(QubicDogeMiningTask))
120+
return false;
121+
unsigned int& nextDogeTaskId = nextTaskIndex[CustomMiningType::DOGE];
122+
const QubicDogeMiningTask* dogeTask = reinterpret_cast<const QubicDogeMiningTask*>(reinterpret_cast<const char*>(task) + sizeof(CustomQubicMiningTask));
123+
if (dogeTask->cleanJobQueue)
124+
{
125+
setMem(activeTasks[CustomMiningType::DOGE], maxNumTasks * sizeof(uint64_t), 0);
126+
for (int t = 0; t < maxNumTasks; ++t)
127+
receivedSolutions[CustomMiningType::DOGE * maxNumTasks + t].reset();
128+
setMem(dogeTasks, sizeof(dogeTasks), 0);
129+
nextDogeTaskId = 0;
130+
}
131+
else
132+
{
133+
// If not cleaning job queue, we will override the oldest task. Clean the corresponding solution hash set.
134+
receivedSolutions[CustomMiningType::DOGE * maxNumTasks + nextDogeTaskId].reset();
135+
}
136+
convertTargetCompactToFull(dogeTask->dispatcherDifficulty, dogeTasks[nextDogeTaskId].dispatcherTarget);
137+
copyMem(dogeTasks[nextDogeTaskId].nBits, dogeTask->nBits, 4);
138+
copyMem(dogeTasks[nextDogeTaskId].version, dogeTask->version, 4);
139+
copyMem(dogeTasks[nextDogeTaskId].prevHash, dogeTask->prevHash, 32);
140+
typeSpecificTaskIndex = nextDogeTaskId;
141+
nextDogeTaskId = (nextDogeTaskId + 1) % maxNumTasks;
142+
}
143+
else
144+
{
145+
return false;
146+
}
147+
activeTasks[task->customMiningType][typeSpecificTaskIndex] = task->jobId;
148+
return true;
149+
}
150+
151+
return false;
152+
}
153+
154+
// Return -1 if the solution is invalid (stale or duplicate), 0 if the solution is valid but not added due to storage limit,
155+
// and 1 if the solution is valid and added successfully. If taskDescription is not nullptr and the solution corresponds to an active task,
156+
// the task description is written into the provided pointer.
157+
int addSolution(const CustomQubicMiningSolution* solution, unsigned int size, unsigned char* taskDescription = nullptr)
158+
{
159+
if (size <= sizeof(CustomQubicMiningSolution))
160+
return -1;
161+
162+
LockGuard guard(lock);
163+
164+
// Check if the solution corresponds to an active task.
165+
int typeSpecificTaskIndex = findTask(solution->customMiningType, solution->jobId);
166+
if (typeSpecificTaskIndex < 0)
167+
return -1;
168+
169+
if (taskDescription)
170+
{
171+
if (solution->customMiningType == CustomMiningType::DOGE)
172+
{
173+
StoredDogeMiningTask* taskOut = reinterpret_cast<StoredDogeMiningTask*>(taskDescription);
174+
*taskOut = dogeTasks[typeSpecificTaskIndex];
175+
}
176+
}
177+
178+
// Check if the solution is duplicate.
179+
m256i digest;
180+
KangarooTwelve(reinterpret_cast<const char*>(solution) + sizeof(CustomQubicMiningSolution),
181+
size - sizeof(CustomQubicMiningSolution), &digest, sizeof(digest));
182+
183+
if (receivedSolutions[solution->customMiningType * maxNumTasks + typeSpecificTaskIndex].contains(digest))
184+
return -1;
185+
186+
// Try to add the solution hash to the set of received solutions for this task. May return NULL_INDEX if the set is full.
187+
QPI::sint64 indexAdded = receivedSolutions[solution->customMiningType * maxNumTasks + typeSpecificTaskIndex].add(digest);
188+
189+
return (indexAdded == QPI::NULL_INDEX) ? 0 : 1;
190+
}
191+
192+
// Return true if there is an active task with the given jobId and customMiningType, false otherwise.
193+
bool containsTask(uint8_t customMiningType, uint64_t jobId)
194+
{
195+
LockGuard guard(lock);
196+
return findTask(customMiningType, jobId) >= 0;
197+
}
198+
};

0 commit comments

Comments
 (0)