From c5413a530e9d1c0101a6671ea82b5feeec638fca Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Wed, 17 Apr 2024 17:32:25 +0200 Subject: [PATCH 01/31] Introduce non-persistent locals in contracts, removing need to copy state for function calls --- src/Qubic.vcxproj | 1 + src/Qubic.vcxproj.filters | 3 + src/contract_core/contract_def.h | 58 +++++-- src/contract_core/contract_exec.h | 256 ++++++++++++++++++++++++++++++ src/contract_core/stack_buffer.h | 11 +- src/contracts/QUtil.h | 4 +- src/contracts/Qx.h | 251 +++++++++++++++++------------ src/contracts/Random.h | 2 +- src/contracts/qpi.h | 250 +++++++++++++++++++++-------- src/platform/concurrency.h | 6 + src/platform/m256.h | 1 + src/public_settings.h | 1 + src/qubic.cpp | 168 ++++++-------------- test/contract_core.cpp | 5 +- 14 files changed, 707 insertions(+), 310 deletions(-) create mode 100644 src/contract_core/contract_exec.h diff --git a/src/Qubic.vcxproj b/src/Qubic.vcxproj index aa1ef7b4..532c38a8 100644 --- a/src/Qubic.vcxproj +++ b/src/Qubic.vcxproj @@ -26,6 +26,7 @@ + diff --git a/src/Qubic.vcxproj.filters b/src/Qubic.vcxproj.filters index d23fa6ef..2bc1f513 100644 --- a/src/Qubic.vcxproj.filters +++ b/src/Qubic.vcxproj.filters @@ -120,6 +120,9 @@ contract_core + + contract_core + diff --git a/src/contract_core/contract_def.h b/src/contract_core/contract_def.h index ab18065f..f311c9e2 100644 --- a/src/contract_core/contract_def.h +++ b/src/contract_core/contract_def.h @@ -6,18 +6,31 @@ ////////// Smart contracts \\\\\\\\\\ +// The order in this file is very important, because it restricts what is available to the contracts. +// For example, a contract may only call a contract with lower index, which is enforced by order of +// include / availability of definition. + namespace QPI { - struct QpiContext; + struct QpiContextProcedureCall; + struct QpiContextFunctionCall; } -typedef void (*SYSTEM_PROCEDURE)(const QPI::QpiContext&, void*); -typedef void (*EXPAND_PROCEDURE)(const QPI::QpiContext&, void*, void*); -typedef void (*USER_FUNCTION)(const QPI::QpiContext&, void*, void*, void*); -typedef void (*USER_PROCEDURE)(const QPI::QpiContext&, void*, void*, void*); +// TODO: add option for having locals to SYSTEM and EXPAND procedures +typedef void (*SYSTEM_PROCEDURE)(const QPI::QpiContextProcedureCall&, void*); +typedef void (*EXPAND_PROCEDURE)(const QPI::QpiContextProcedureCall&, void*, void*); +typedef void (*USER_FUNCTION)(const QPI::QpiContextFunctionCall&, void* state, void* input, void* output, void* locals); +typedef void (*USER_PROCEDURE)(const QPI::QpiContextProcedureCall&, void* state, void* input, void* output, void* locals); + +// Maximum size of local variables that may be used by a contract function or procedure +// If increased, the size of contractLocalsStack should be increased as well. +constexpr unsigned int MAX_SIZE_OF_CONTRACT_LOCALS = 128 * 1024; +// TODO: make sure the limit of nested calls is not violated +constexpr unsigned short MAX_NESTED_CONTRACT_CALLS = 10; +// TODO: rename these to __qpiX (forbidding to call them outside macros) static void __beginFunctionOrProcedure(const unsigned int); static void __endFunctionOrProcedure(const unsigned int); template static m256i __K12(T); @@ -26,6 +39,8 @@ template static void __logContractErrorMessage(unsigned int, const template static void __logContractInfoMessage(unsigned int, const T&); template static void __logContractWarningMessage(unsigned int, const T&); static void* __scratchpad(); // TODO: concurrency support (n buffers for n allowed concurrent contract executions) +// static void* __tryAcquireScratchpad(unsigned int size); // Thread-safe, may return nullptr if no appropriate buffer is available +// static void __ReleaseScratchpad(void*); #include "contracts/qpi.h" @@ -34,6 +49,7 @@ static void* __scratchpad(); // TODO: concurrency support (n buffers for n al #define CONTRACT_STATE_TYPE QX #define CONTRACT_STATE2_TYPE QX2 #include "contracts/Qx.h" +// TODO: remove state variables to prevent manipulation and make sure contracts can only call other contracts through the API static CONTRACT_STATE_TYPE* _QX; #undef CONTRACT_INDEX @@ -71,6 +87,13 @@ static CONTRACT_STATE_TYPE* _QUTIL; #define MAX_CONTRACT_ITERATION_DURATION 1000 // In milliseconds, must be above 0 +#undef INITIALIZE +#undef BEGIN_EPOCH +#undef END_EPOCH +#undef BEGIN_TICK +#undef END_TICK + + struct Contract0State { long long contractFeeReserves[MAX_NUMBER_OF_CONTRACTS]; @@ -103,21 +126,30 @@ constexpr unsigned int contractCount = sizeof(contractDescriptions) / sizeof(con static SYSTEM_PROCEDURE contractSystemProcedures[contractCount][5]; static EXPAND_PROCEDURE contractExpandProcedures[contractCount]; + +// TODO: all below are filled very sparsely, so a better data structure could save almost all the memory static USER_FUNCTION contractUserFunctions[contractCount][65536]; static unsigned short contractUserFunctionInputSizes[contractCount][65536]; static unsigned short contractUserFunctionOutputSizes[contractCount][65536]; +static unsigned int contractUserFunctionLocalsSizes[contractCount][65536]; static USER_PROCEDURE contractUserProcedures[contractCount][65536]; static unsigned short contractUserProcedureInputSizes[contractCount][65536]; static unsigned short contractUserProcedureOutputSizes[contractCount][65536]; +static unsigned int contractUserProcedureLocalsSizes[contractCount][65536]; + +// TODO: allow parallel reading with distinguishing read/write lock +static volatile char contractStateLock[contractCount]; +static unsigned char* contractStates[contractCount]; +static unsigned long long contractTotalExecutionTicks[contractCount]; -#pragma warning(push) -#pragma warning(disable: 4005) -#define INITIALIZE 0 -#define BEGIN_EPOCH 1 -#define END_EPOCH 2 -#define BEGIN_TICK 3 -#define END_TICK 4 -#pragma warning(pop) +enum SystemProcedureID +{ + INITIALIZE = 0, + BEGIN_EPOCH = 1, + END_EPOCH = 2, + BEGIN_TICK = 3, + END_TICK = 4, +}; #define REGISTER_CONTRACT_FUNCTIONS_AND_PROCEDURES(contractName)\ _##contractName = (contractName*)contractState;\ diff --git a/src/contract_core/contract_exec.h b/src/contract_core/contract_exec.h new file mode 100644 index 00000000..becefce9 --- /dev/null +++ b/src/contract_core/contract_exec.h @@ -0,0 +1,256 @@ +#pragma once + +#include "platform/concurrency.h" +#include "platform/debugging.h" +#include "platform/memory.h" + +#include "contract_core/contract_def.h" +#include "contract_core/stack_buffer.h" + +#include "public_settings.h" + +// Used to store: locals and for first invocation level also input and output +typedef StackBuffer ContractLocalsStack; +ContractLocalsStack contractLocalsStack[NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS]; +static volatile char contractLocalsStackLock[NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS]; + +bool initContractExec() +{ + for (ContractLocalsStack::SizeType i = 0; i < NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS; ++i) + contractLocalsStack[i].init(); + setMem((void*)contractLocalsStackLock, sizeof(contractLocalsStackLock), 0); + return true; +} + +// Acquire lock of an currently unused stack (may block if all in use) +void acquireContractLocalsStack(int& stackIdx) +{ + ASSERT(stackIdx < 0); + + int i = 0; + while (TRY_ACQUIRE(contractLocalsStackLock[i]) == false) + { + _mm_pause(); + ++i; + if (i == NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS) + i = 0; + } + + stackIdx = i; +} + +void releaseContractLocalsStack(int& stackIdx) +{ + ASSERT(stackIdx > 0); + ASSERT(stackIdx < NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS); + ASSERT(contractLocalsStackLock[stackIdx]); + RELEASE(contractLocalsStackLock[stackIdx]); + stackIdx = -1; +} + +void* QPI::QpiContextFunctionCall::__qpiAllocLocals(unsigned int sizeOfLocals) const +{ + ASSERT(_stackIndex >= 0 && _stackIndex < NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS); + if (_stackIndex < 0 || _stackIndex >= NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS) + return 0; // TODO: log problem / restart processor here instead of returning + void* p = contractLocalsStack[_stackIndex].allocate(sizeOfLocals); + if (p) + setMem(p, sizeOfLocals, 0); + return p; +} + +void QPI::QpiContextFunctionCall::__qpiFreeLocals() const +{ + ASSERT(_stackIndex >= 0 && _stackIndex < NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS); + if (_stackIndex < 0 || _stackIndex >= NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS) + return; + contractLocalsStack[_stackIndex].free(); +} + +const QpiContextFunctionCall& QPI::QpiContextFunctionCall::__qpiConstructContextOtherContractFunctionCall(unsigned int otherContractIndex) const +{ + ASSERT(_stackIndex >= 0 && _stackIndex < NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS); + char * buffer = contractLocalsStack[_stackIndex].allocate(sizeof(QpiContextFunctionCall)); + QpiContextFunctionCall& newContext = *reinterpret_cast(buffer); + newContext.init(otherContractIndex, _originator, _currentContractId, _invocationReward); + return newContext; +} + +const QpiContextProcedureCall& QPI::QpiContextProcedureCall::__qpiConstructContextOtherContractProcedureCall(unsigned int otherContractIndex) const +{ + ASSERT(_stackIndex >= 0 && _stackIndex < NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS); + char* buffer = contractLocalsStack[_stackIndex].allocate(sizeof(QpiContextProcedureCall)); + QpiContextProcedureCall& newContext = *reinterpret_cast(buffer); + newContext.init(otherContractIndex, _originator, _currentContractId, _invocationReward); + return newContext; +} + + +void QPI::QpiContextFunctionCall::__qpiFreeContextOtherContract() const +{ + ASSERT(_stackIndex >= 0 && _stackIndex < NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS); + contractLocalsStack[_stackIndex].free(); +} + +void* QPI::QpiContextFunctionCall::__qpiAquireStateForReading(unsigned int contractIndex) const +{ + ASSERT(contractIndex < contractCount); + ACQUIRE(contractStateLock[contractIndex]); + return contractStates[contractIndex]; +} + +void QPI::QpiContextFunctionCall::__qpiReleaseStateForReading(unsigned int contractIndex) const +{ + ASSERT(contractIndex < contractCount); + ASSERT(contractStateLock[contractIndex]); + RELEASE(contractStateLock[contractIndex]); +} + +void* QPI::QpiContextProcedureCall::__qpiAquireStateForWriting(unsigned int contractIndex) const +{ + return QpiContextFunctionCall::__qpiAquireStateForReading(contractIndex); +} + +void QPI::QpiContextProcedureCall::__qpiReleaseStateForWriting(unsigned int contractIndex) const +{ + QpiContextFunctionCall::__qpiReleaseStateForReading(contractIndex); +} + +struct QpiContextSystemProcedureCall : public QPI::QpiContextProcedureCall +{ + QpiContextSystemProcedureCall(unsigned int contractIndex) : QPI::QpiContextProcedureCall(contractIndex, NULL_ID, 0) + { + } + + void call(SystemProcedureID systemProcId) + { + ASSERT(_currentContractIndex < contractCount); + + // reserve resources for this processor (may block) + ACQUIRE(contractStateLock[_currentContractIndex]); + + const unsigned long long startTick = __rdtsc(); + contractSystemProcedures[_currentContractIndex][systemProcId](*this, contractStates[_currentContractIndex]); + contractTotalExecutionTicks[_currentContractIndex] += __rdtsc() - startTick; + + RELEASE(contractStateLock[_currentContractIndex]); + } +}; + +struct QpiContextUserProcedureCall : public QPI::QpiContextProcedureCall +{ + QpiContextUserProcedureCall(unsigned int contractIndex, const m256i& originator, long long invocationReward) : QPI::QpiContextProcedureCall(contractIndex, originator, invocationReward) + { + } + + void call(unsigned short inputType, const void* inputPtr, unsigned short inputSize) + { + ASSERT(_currentContractIndex < contractCount); + ASSERT(contractUserProcedures[_currentContractIndex][inputType]); + + // reserve stack for this processor (may block) + acquireContractLocalsStack(_stackIndex); + ASSERT(contractLocalsStack[_stackIndex].size() == 0); + + // allocate input, output, and locals buffer from stack and init them + unsigned short fullInputSize = contractUserProcedureInputSizes[_currentContractIndex][inputType]; + unsigned short outputSize = contractUserProcedureOutputSizes[_currentContractIndex][inputType]; + unsigned int localsSize = contractUserProcedureLocalsSizes[_currentContractIndex][inputType]; + char* inputBuffer = contractLocalsStack[_stackIndex].allocate(fullInputSize + outputSize + localsSize); + ASSERT(inputBuffer); // TODO: error handling + char* outputBuffer = inputBuffer + fullInputSize; + char* localsBuffer = outputBuffer + outputSize; + if (fullInputSize > inputSize) + setMem(inputBuffer + inputSize, fullInputSize - inputSize, 0); + copyMem(inputBuffer, inputPtr, inputSize); + setMem(outputBuffer, outputSize + localsSize, 0); + + // aquire lock of contract state for writing (may block) + ACQUIRE(contractStateLock[_currentContractIndex]); + + // run procedure + const unsigned long long startTick = __rdtsc(); + contractUserProcedures[_currentContractIndex][inputType](*this, contractStates[_currentContractIndex], inputBuffer, outputBuffer, localsBuffer); + contractTotalExecutionTicks[_currentContractIndex] += __rdtsc() - startTick; + + // release lock of contract state + RELEASE(contractStateLock[_currentContractIndex]); + + // free data on stack (output is unused) + contractLocalsStack[_stackIndex].free(); + ASSERT(contractLocalsStack[_stackIndex].size() == 0); + + // release stack lock + releaseContractLocalsStack(_stackIndex); + } +}; + + +struct QpiContextUserFunctionCall : public QPI::QpiContextFunctionCall +{ + char* outputBuffer; + unsigned short outputSize; + + QpiContextUserFunctionCall(unsigned int contractIndex) : QPI::QpiContextFunctionCall(contractIndex, NULL_ID, 0) + { + outputBuffer = nullptr; + outputSize = 0; + } + + ~QpiContextUserFunctionCall() + { + freeBuffer(); + } + + // call function + void call(unsigned short inputType, const void* inputPtr, unsigned short inputSize) + { + ASSERT(_currentContractIndex < contractCount); + ASSERT(contractUserFunctions[_currentContractIndex][inputType]); + + // reserve stack for this processor (may block) + acquireContractLocalsStack(_stackIndex); + ASSERT(contractLocalsStack[_stackIndex].size() == 0); + + // allocate input, output, and locals buffer from stack and init them + unsigned short fullInputSize = contractUserFunctionInputSizes[_currentContractIndex][inputType]; + outputSize = contractUserFunctionOutputSizes[_currentContractIndex][inputType]; + unsigned int localsSize = contractUserFunctionLocalsSizes[_currentContractIndex][inputType]; + char* inputBuffer = contractLocalsStack[_stackIndex].allocate(fullInputSize + outputSize + localsSize); + ASSERT(inputBuffer); // TODO: error handling + outputBuffer = inputBuffer + fullInputSize; + char* localsBuffer = outputBuffer + outputSize; + if (fullInputSize > inputSize) + setMem(inputBuffer + inputSize, fullInputSize - inputSize, 0); + copyMem(inputBuffer, inputPtr, inputSize); + setMem(outputBuffer, outputSize + localsSize, 0); + + // aquire lock of contract state for writing (may block) + ACQUIRE(contractStateLock[_currentContractIndex]); + + // run function + const unsigned long long startTick = __rdtsc(); + contractUserFunctions[_currentContractIndex][inputType](*this, contractStates[_currentContractIndex], inputBuffer, outputBuffer, localsBuffer); + contractTotalExecutionTicks[_currentContractIndex] += __rdtsc() - startTick; + + // release lock of contract state + RELEASE(contractStateLock[_currentContractIndex]); + } + + // free buffer after output has been copied + void freeBuffer() + { + if (_stackIndex < 0) + return; + + // free data on stack + contractLocalsStack[_stackIndex].free(); + ASSERT(contractLocalsStack[_stackIndex].size() == 0); + + // release locks + releaseContractLocalsStack(_stackIndex); + } +}; + + + diff --git a/src/contract_core/stack_buffer.h b/src/contract_core/stack_buffer.h index a270a685..be86970f 100644 --- a/src/contract_core/stack_buffer.h +++ b/src/contract_core/stack_buffer.h @@ -12,7 +12,12 @@ struct StackBuffer typedef StackBufferSizeType SizeType; static_assert(SizeType(-1) > 0, "Signed StackBufferSizeType is not supported!"); - // Initialize as empty stack (momory not zeroed) + StackBuffer() + { + init(); + } + + // Initialize as empty stack (memory not zeroed) void init() { _allocatedSize = 0; @@ -41,7 +46,7 @@ struct StackBuffer #endif // Allocate storage in buffer. - void* allocate(SizeType size) + char* allocate(SizeType size) { // allocate fails of size after allocating overflows buffer size or the used size type StackBufferSizeType newSize = _allocatedSize + size + sizeof(SizeType); @@ -61,6 +66,8 @@ struct StackBuffer if (_allocatedSize > _maxAllocatedSize) _maxAllocatedSize = _allocatedSize; #endif + + return allocatedBuffer; } // Free storage allocated by last call to allocate(). diff --git a/src/contracts/QUtil.h b/src/contracts/QUtil.h index 5892eed0..3db977c7 100644 --- a/src/contracts/QUtil.h +++ b/src/contracts/QUtil.h @@ -71,7 +71,7 @@ struct QUTIL /* * @return return SendToManyV1 fee per invocation */ - PUBLIC(GetSendToManyV1Fee) + PUBLIC_FUNCTION(GetSendToManyV1Fee) output.fee = STM1_INVOCATION_FEE; _ @@ -81,7 +81,7 @@ struct QUTIL * @param list of 25 amounts (200 bytes): 8 bytes(long long) for each amount, leave empty(zeroes) for unused memory space * @return returnCode (0 means success) */ - PUBLIC(SendToManyV1) + PUBLIC_PROCEDURE(SendToManyV1) state.logger = QUtilLogger{ 0, 0, qpi.invocator(), SELF, qpi.invocationReward(), STM1_TRIGGERED }; LOG_INFO(state.logger); state.total = input.amt0 + input.amt1 + input.amt2 + input.amt3 + input.amt4 + input.amt5 + input.amt6 + input.amt7 + input.amt8 + input.amt9 + input.amt10 + input.amt11 + input.amt12 + input.amt13 + input.amt14 + input.amt15 + input.amt16 + input.amt17 + input.amt18 + input.amt19 + input.amt20 + input.amt21 + input.amt22 + input.amt23 + input.amt24 + STM1_INVOCATION_FEE; diff --git a/src/contracts/Qx.h b/src/contracts/Qx.h index ed97f955..a436d9ce 100644 --- a/src/contracts/Qx.h +++ b/src/contracts/Qx.h @@ -185,6 +185,7 @@ struct QX }; collection<_EntityOrder, 2097152 * X_MULTIPLIER> _entityOrders; + // TODO: change to "locals" variables -> every func/proc can define struct of "locals" that are provided to function (stored on stack structure per processor) sint64 _elementIndex, _elementIndex2; id _issuerAndAssetName; _AssetOrder _assetOrder; @@ -219,40 +220,56 @@ struct QX sint64 numberOfShares; } _numberOfReservedShares_output; - PRIVATE(_NumberOfReservedShares) + struct _NumberOfReservedShares_locals + { + sint64 _elementIndex; + _EntityOrder _entityOrder; + }; + + PRIVATE_FUNCTION_WITH_LOCALS(_NumberOfReservedShares) output.numberOfShares = 0; - state._elementIndex = state._entityOrders.headIndex(qpi.invocator(), 0); - while (state._elementIndex != NULL_INDEX) + locals._elementIndex = state._entityOrders.headIndex(qpi.invocator(), 0); + while (locals._elementIndex != NULL_INDEX) { - state._entityOrder = state._entityOrders.element(state._elementIndex); - if (state._entityOrder.assetName == input.assetName - && state._entityOrder.issuer == input.issuer) + locals._entityOrder = state._entityOrders.element(locals._elementIndex); + if (locals._entityOrder.assetName == input.assetName + && locals._entityOrder.issuer == input.issuer) { - output.numberOfShares += state._entityOrder.numberOfShares; + output.numberOfShares += locals._entityOrder.numberOfShares; } - state._elementIndex = state._entityOrders.nextElementIndex(state._elementIndex); + locals._elementIndex = state._entityOrders.nextElementIndex(locals._elementIndex); } _ - PUBLIC(Fees) + + PUBLIC_FUNCTION(Fees) output.assetIssuanceFee = state._assetIssuanceFee; output.transferFee = state._transferFee; output.tradeFee = state._tradeFee; - _ + _ + - PUBLIC(AssetAskOrders) + struct AssetAskOrders_locals + { + sint64 _elementIndex, _elementIndex2; + id _issuerAndAssetName; + _AssetOrder _assetOrder; + AssetAskOrders_output::Order _assetAskOrder; + }; + + PUBLIC_FUNCTION_WITH_LOCALS(AssetAskOrders) - state._issuerAndAssetName = input.issuer; - state._issuerAndAssetName.u64._3 = input.assetName; + locals._issuerAndAssetName = input.issuer; + locals._issuerAndAssetName.u64._3 = input.assetName; - state._elementIndex = state._assetOrders.headIndex(state._issuerAndAssetName, 0); - state._elementIndex2 = 0; - while (state._elementIndex != NULL_INDEX - && state._elementIndex2 < 256) + locals._elementIndex = state._assetOrders.headIndex(locals._issuerAndAssetName, 0); + locals._elementIndex2 = 0; + while (locals._elementIndex != NULL_INDEX + && locals._elementIndex2 < 256) { if (input.offset > 0) { @@ -260,43 +277,52 @@ struct QX } else { - state._assetAskOrder.price = -state._assetOrders.priority(state._elementIndex); - state._assetOrder = state._assetOrders.element(state._elementIndex); - state._assetAskOrder.entity = state._assetOrder.entity; - state._assetAskOrder.numberOfShares = state._assetOrder.numberOfShares; - output.orders.set(state._elementIndex2, state._assetAskOrder); - state._elementIndex2++; + locals._assetAskOrder.price = -state._assetOrders.priority(locals._elementIndex); + locals._assetOrder = state._assetOrders.element(locals._elementIndex); + locals._assetAskOrder.entity = locals._assetOrder.entity; + locals._assetAskOrder.numberOfShares = locals._assetOrder.numberOfShares; + output.orders.set(locals._elementIndex2, locals._assetAskOrder); + locals._elementIndex2++; } - state._elementIndex = state._assetOrders.nextElementIndex(state._elementIndex); + locals._elementIndex = state._assetOrders.nextElementIndex(locals._elementIndex); } - if (state._elementIndex2 < 256) + if (locals._elementIndex2 < 256) { - state._assetAskOrder.entity = NULL_ID; - state._assetAskOrder.price = 0; - state._assetAskOrder.numberOfShares = 0; - while (state._elementIndex2 < 256) + locals._assetAskOrder.entity = NULL_ID; + locals._assetAskOrder.price = 0; + locals._assetAskOrder.numberOfShares = 0; + while (locals._elementIndex2 < 256) { - output.orders.set(state._elementIndex2, state._assetAskOrder); - state._elementIndex2++; + output.orders.set(locals._elementIndex2, locals._assetAskOrder); + locals._elementIndex2++; } } _ - PUBLIC(AssetBidOrders) - state._issuerAndAssetName = input.issuer; - state._issuerAndAssetName.u64._3 = input.assetName; + struct AssetBidOrders_locals + { + sint64 _elementIndex, _elementIndex2; + id _issuerAndAssetName; + _AssetOrder _assetOrder; + AssetBidOrders_output::Order _assetBidOrder; + }; + + PUBLIC_FUNCTION_WITH_LOCALS(AssetBidOrders) - state._elementIndex = state._assetOrders.headIndex(state._issuerAndAssetName); - state._elementIndex2 = 0; - while (state._elementIndex != NULL_INDEX - && state._elementIndex2 < 256) + locals._issuerAndAssetName = input.issuer; + locals._issuerAndAssetName.u64._3 = input.assetName; + + locals._elementIndex = state._assetOrders.headIndex(locals._issuerAndAssetName); + locals._elementIndex2 = 0; + while (locals._elementIndex != NULL_INDEX + && locals._elementIndex2 < 256) { - state._assetBidOrder.price = state._assetOrders.priority(state._elementIndex); + locals._assetBidOrder.price = state._assetOrders.priority(locals._elementIndex); - if (state._assetBidOrder.price <= 0) + if (locals._assetBidOrder.price <= 0) { break; } @@ -307,35 +333,43 @@ struct QX } else { - state._assetOrder = state._assetOrders.element(state._elementIndex); - state._assetBidOrder.entity = state._assetOrder.entity; - state._assetBidOrder.numberOfShares = state._assetOrder.numberOfShares; - output.orders.set(state._elementIndex2, state._assetBidOrder); - state._elementIndex2++; + locals._assetOrder = state._assetOrders.element(locals._elementIndex); + locals._assetBidOrder.entity = locals._assetOrder.entity; + locals._assetBidOrder.numberOfShares = locals._assetOrder.numberOfShares; + output.orders.set(locals._elementIndex2, locals._assetBidOrder); + locals._elementIndex2++; } - state._elementIndex = state._assetOrders.nextElementIndex(state._elementIndex); + locals._elementIndex = state._assetOrders.nextElementIndex(locals._elementIndex); } - if (state._elementIndex2 < 256) + if (locals._elementIndex2 < 256) { - state._assetBidOrder.entity = NULL_ID; - state._assetBidOrder.price = 0; - state._assetBidOrder.numberOfShares = 0; - while (state._elementIndex2 < 256) + locals._assetBidOrder.entity = NULL_ID; + locals._assetBidOrder.price = 0; + locals._assetBidOrder.numberOfShares = 0; + while (locals._elementIndex2 < 256) { - output.orders.set(state._elementIndex2, state._assetBidOrder); - state._elementIndex2++; + output.orders.set(locals._elementIndex2, locals._assetBidOrder); + locals._elementIndex2++; } } _ - PUBLIC(EntityAskOrders) - state._elementIndex = state._entityOrders.headIndex(input.entity, 0); - state._elementIndex2 = 0; - while (state._elementIndex != NULL_INDEX - && state._elementIndex2 < 256) + struct EntityAskOrders_locals + { + sint64 _elementIndex, _elementIndex2; + _EntityOrder _entityOrder; + EntityAskOrders_output::Order _entityAskOrder; + }; + + PUBLIC_FUNCTION_WITH_LOCALS(EntityAskOrders) + + locals._elementIndex = state._entityOrders.headIndex(input.entity, 0); + locals._elementIndex2 = 0; + while (locals._elementIndex != NULL_INDEX + && locals._elementIndex2 < 256) { if (input.offset > 0) { @@ -343,42 +377,50 @@ struct QX } else { - state._entityAskOrder.price = -state._entityOrders.priority(state._elementIndex); - state._entityOrder = state._entityOrders.element(state._elementIndex); - state._entityAskOrder.issuer = state._entityOrder.issuer; - state._entityAskOrder.assetName = state._entityOrder.assetName; - state._entityAskOrder.numberOfShares = state._entityOrder.numberOfShares; - output.orders.set(state._elementIndex2, state._entityAskOrder); - state._elementIndex2++; + locals._entityAskOrder.price = -state._entityOrders.priority(locals._elementIndex); + locals._entityOrder = state._entityOrders.element(locals._elementIndex); + locals._entityAskOrder.issuer = locals._entityOrder.issuer; + locals._entityAskOrder.assetName = locals._entityOrder.assetName; + locals._entityAskOrder.numberOfShares = locals._entityOrder.numberOfShares; + output.orders.set(locals._elementIndex2, locals._entityAskOrder); + locals._elementIndex2++; } - state._elementIndex = state._entityOrders.nextElementIndex(state._elementIndex); + locals._elementIndex = state._entityOrders.nextElementIndex(locals._elementIndex); } - if (state._elementIndex2 < 256) + if (locals._elementIndex2 < 256) { - state._entityAskOrder.issuer = NULL_ID; - state._entityAskOrder.assetName = 0; - state._entityAskOrder.price = 0; - state._entityAskOrder.numberOfShares = 0; - while (state._elementIndex2 < 256) + locals._entityAskOrder.issuer = NULL_ID; + locals._entityAskOrder.assetName = 0; + locals._entityAskOrder.price = 0; + locals._entityAskOrder.numberOfShares = 0; + while (locals._elementIndex2 < 256) { - output.orders.set(state._elementIndex2, state._entityAskOrder); - state._elementIndex2++; + output.orders.set(locals._elementIndex2, locals._entityAskOrder); + locals._elementIndex2++; } } _ - PUBLIC(EntityBidOrders) + + struct EntityBidOrders_locals + { + sint64 _elementIndex, _elementIndex2; + _EntityOrder _entityOrder; + EntityBidOrders_output::Order _entityBidOrder; + }; + + PUBLIC_FUNCTION_WITH_LOCALS(EntityBidOrders) - state._elementIndex = state._entityOrders.headIndex(input.entity); - state._elementIndex2 = 0; - while (state._elementIndex != NULL_INDEX - && state._elementIndex2 < 256) + locals._elementIndex = state._entityOrders.headIndex(input.entity); + locals._elementIndex2 = 0; + while (locals._elementIndex != NULL_INDEX + && locals._elementIndex2 < 256) { - state._entityBidOrder.price = state._entityOrders.priority(state._elementIndex); + locals._entityBidOrder.price = state._entityOrders.priority(locals._elementIndex); - if (state._entityBidOrder.price <= 0) + if (locals._entityBidOrder.price <= 0) { break; } @@ -389,32 +431,33 @@ struct QX } else { - state._entityOrder = state._entityOrders.element(state._elementIndex); - state._entityBidOrder.issuer = state._entityOrder.issuer; - state._entityBidOrder.assetName = state._entityOrder.assetName; - state._entityBidOrder.numberOfShares = state._entityOrder.numberOfShares; - output.orders.set(state._elementIndex2, state._entityBidOrder); - state._elementIndex2++; + locals._entityOrder = state._entityOrders.element(locals._elementIndex); + locals._entityBidOrder.issuer = locals._entityOrder.issuer; + locals._entityBidOrder.assetName = locals._entityOrder.assetName; + locals._entityBidOrder.numberOfShares = locals._entityOrder.numberOfShares; + output.orders.set(locals._elementIndex2, locals._entityBidOrder); + locals._elementIndex2++; } - state._elementIndex = state._entityOrders.nextElementIndex(state._elementIndex); + locals._elementIndex = state._entityOrders.nextElementIndex(state._elementIndex); } - if (state._elementIndex2 < 256) + if (locals._elementIndex2 < 256) { - state._entityBidOrder.issuer = NULL_ID; - state._entityBidOrder.assetName = 0; - state._entityBidOrder.price = 0; - state._entityBidOrder.numberOfShares = 0; - while (state._elementIndex2 < 256) + locals._entityBidOrder.issuer = NULL_ID; + locals._entityBidOrder.assetName = 0; + locals._entityBidOrder.price = 0; + locals._entityBidOrder.numberOfShares = 0; + while (locals._elementIndex2 < 256) { - output.orders.set(state._elementIndex2, state._entityBidOrder); - state._elementIndex2++; + output.orders.set(locals._elementIndex2, locals._entityBidOrder); + locals._elementIndex2++; } } _ - PUBLIC(IssueAsset) + + PUBLIC_PROCEDURE(IssueAsset) if (qpi.invocationReward() < state._assetIssuanceFee) { @@ -437,7 +480,7 @@ struct QX } _ - PUBLIC(TransferShareOwnershipAndPossession) + PUBLIC_PROCEDURE(TransferShareOwnershipAndPossession) if (qpi.invocationReward() < state._transferFee) { @@ -458,7 +501,7 @@ struct QX state._numberOfReservedShares_input.issuer = input.issuer; state._numberOfReservedShares_input.assetName = input.assetName; - qpi.call(_NumberOfReservedShares, state, state._numberOfReservedShares_input, state._numberOfReservedShares_output); + CALL(_NumberOfReservedShares, state._numberOfReservedShares_input, state._numberOfReservedShares_output); if (qpi.numberOfPossessedShares(input.assetName, input.issuer, qpi.invocator(), qpi.invocator(), SELF_INDEX, SELF_INDEX) - state._numberOfReservedShares_output.numberOfShares < input.numberOfShares) { output.transferredNumberOfShares = 0; @@ -470,7 +513,7 @@ struct QX } _ - PUBLIC(AddToAskOrder) + PUBLIC_PROCEDURE(AddToAskOrder) if (qpi.invocationReward() > 0) { @@ -486,7 +529,7 @@ struct QX { state._numberOfReservedShares_input.issuer = input.issuer; state._numberOfReservedShares_input.assetName = input.assetName; - qpi.call(_NumberOfReservedShares, state, state._numberOfReservedShares_input, state._numberOfReservedShares_output); + CALL(_NumberOfReservedShares, state._numberOfReservedShares_input, state._numberOfReservedShares_output); if (qpi.numberOfPossessedShares(input.assetName, input.issuer, qpi.invocator(), qpi.invocator(), SELF_INDEX, SELF_INDEX) - state._numberOfReservedShares_output.numberOfShares < input.numberOfShares) { output.addedNumberOfShares = 0; @@ -636,7 +679,7 @@ struct QX } _ - PUBLIC(AddToBidOrder) + PUBLIC_PROCEDURE(AddToBidOrder) if (input.price <= 0 || input.numberOfShares <= 0 @@ -798,7 +841,7 @@ struct QX } _ - PUBLIC(RemoveFromAskOrder) + PUBLIC_PROCEDURE(RemoveFromAskOrder) if (qpi.invocationReward() > 0) { @@ -885,7 +928,7 @@ struct QX } _ - PUBLIC(RemoveFromBidOrder) + PUBLIC_PROCEDURE(RemoveFromBidOrder) if (qpi.invocationReward() > 0) { diff --git a/src/contracts/Random.h b/src/contracts/Random.h index 5df2f19a..74c8bc3a 100644 --- a/src/contracts/Random.h +++ b/src/contracts/Random.h @@ -23,7 +23,7 @@ struct RANDOM uint32 _bitFee; // Amount of qus - PUBLIC(RevealAndCommit) + PUBLIC_PROCEDURE(RevealAndCommit) qpi.transfer(qpi.invocator(), qpi.invocationReward()); _ diff --git a/src/contracts/qpi.h b/src/contracts/qpi.h index 7845391d..d71e26ab 100644 --- a/src/contracts/qpi.h +++ b/src/contracts/qpi.h @@ -30,7 +30,9 @@ namespace QPI const_cast QpiContext - __registerUser + __qpi + TODO: prevent other casts trying to cast const away from state + TODO: #undef and other methods trying to redefine QPI (maybe completely disallow #define?) */ @@ -5965,27 +5967,53 @@ namespace QPI ////////// #if !defined(NO_UEFI) + + // QPI context base class (common data, by default has no stack for locals) struct QpiContext { - id arbitrator( - ) const; - - sint64 burn( - sint64 amount - ) const; + protected: + // Construction is done in core, not allowed in contracts + QpiContext( + unsigned int contractIndex, + const m256i& originator, + const m256i& invocator, + long long invocationReward + ) { + init(contractIndex, originator, invocator, invocationReward); + } - // TODO: needs to be macro to access private members - // TODO: remove need to pass state explicitly - // call function or procedure of contract - template - void call(Callable& callable, State& state, Input& input, Output& output) const - { - callable(*this, state, input, output); + void init( + unsigned int contractIndex, + const m256i& originator, + const m256i& invocator, + long long invocationReward + ) { + _currentContractIndex = contractIndex; + _currentContractId = m256i(contractIndex, 0, 0, 0); + _originator = originator; + _invocator = invocator; + _invocationReward = invocationReward; + _stackIndex = -1; } - // TODO: calling of other contracts, make sure to avoid race conditions - // write note about use of stack and risk of stack overlow with many -> we may have a separate per-processor stack to store the context (or changing variables of the context) + unsigned int _currentContractIndex; + m256i _currentContractId, _originator, _invocator; + long long _invocationReward; + int _stackIndex; + private: + // Disabling copy and move + QpiContext(const QpiContext&) = delete; + QpiContext(QpiContext&&) = delete; + QpiContext& operator=(const QpiContext&) = delete; + QpiContext& operator=(QpiContext&&) = delete; + }; + + // QPI function available to contract functions and procedures + struct QpiContextFunctionCall : public QpiContext + { + id arbitrator( + ) const; id computor( uint16 computorIndex // [0..675] @@ -6017,14 +6045,6 @@ namespace QPI id invocator( ) const; // Returns the id of the user/contract who has triggered this contract; returns NULL_ID if there has been no user/contract - sint64 issueAsset( - uint64 name, - const id& issuer, - sint8 numberOfDecimalPlaces, - sint64 numberOfShares, - uint64 unitOfMeasurement - ) const; // Returns number of shares or 0 on error - template id K12( const T& data @@ -6061,6 +6081,38 @@ namespace QPI uint32 tick( ) const; // [0..999'999'999] + uint8 year( + ) const; // [0..99] (0 = 2000, 1 = 2001, ..., 99 = 2099) + + + // Internal functions, calling not allowed in contracts + void* __qpiAllocLocals(unsigned int sizeOfLocals) const; + void __qpiFreeLocals() const; + const QpiContextFunctionCall& __qpiConstructContextOtherContractFunctionCall(unsigned int otherContractIndex) const; + void __qpiFreeContextOtherContract() const; + void * __qpiAquireStateForReading(unsigned int contractIndex) const; + void __qpiReleaseStateForReading(unsigned int contractIndex) const; + + protected: + // Construction is done in core, not allowed in contracts + QpiContextFunctionCall(unsigned int contractIndex, const m256i& originator, long long invocationReward) : QpiContext(contractIndex, originator, originator, invocationReward) {} + }; + + // QPI procedures available to contract procedures (not to contract functions) + struct QpiContextProcedureCall : public QPI::QpiContextFunctionCall + { + sint64 burn( + sint64 amount + ) const; + + sint64 issueAsset( + uint64 name, + const id& issuer, + sint8 numberOfDecimalPlaces, + sint64 numberOfShares, + uint64 unitOfMeasurement + ) const; // Returns number of shares or 0 on error + sint64 transfer( // Attempts to transfer energy from this qubic const id& destination, // Destination to transfer to, use NULL_ID to destroy the transferred energy sint64 amount // Energy amount to transfer, must be in [0..1'000'000'000'000'000] range @@ -6075,57 +6127,34 @@ namespace QPI const id& newOwnerAndPossessor ) const; // Returns remaining number of possessed shares satisfying all the conditions; if the value is less than 0 then the attempt has failed, in this case the absolute value equals to the insufficient number - uint8 year( - ) const; // [0..99] (0 = 2000, 1 = 2001, ..., 99 = 2099) + + // Internal functions, calling not allowed in contracts + const QpiContextProcedureCall& __qpiConstructContextOtherContractProcedureCall(unsigned int otherContractIndex) const; + void* __qpiAquireStateForWriting(unsigned int contractIndex) const; + void __qpiReleaseStateForWriting(unsigned int contractIndex) const; protected: // Construction is done in core, not allowed in contracts - QpiContext( - unsigned int contractIndex, - const m256i& originator, - const m256i& invocator, - long long invocationReward - ) : - _currentContractIndex(contractIndex), - _currentContractId(contractIndex, 0, 0, 0), - _originator(originator), - _invocator(invocator), - _invocationReward(invocationReward) - {} - - unsigned int _currentContractIndex; - m256i _currentContractId, _originator, _invocator; - long long _invocationReward; - - private: - // Disabling copy and move - QpiContext(const QpiContext&) = delete; - QpiContext(QpiContext&&) = delete; - QpiContext& operator=(const QpiContext&) = delete; - QpiContext& operator=(QpiContext&&) = delete; - }; - - struct QpiContextProcedureCall : public QPI::QpiContext - { - QpiContextProcedureCall(unsigned int contractIndex, const m256i& originator, long long invocationReward) : QpiContext(contractIndex, originator, originator, invocationReward) {} + QpiContextProcedureCall(unsigned int contractIndex, const m256i& originator, long long invocationReward) : QpiContextFunctionCall(contractIndex, originator, invocationReward) {} }; + // QPI available in REGISTER_USER_FUNCTIONS_AND_PROCEDURES struct QpiContextForInit : public QPI::QpiContext { + void __registerUserFunction(USER_FUNCTION, unsigned short, unsigned short, unsigned short, unsigned int) const; + void __registerUserProcedure(USER_PROCEDURE, unsigned short, unsigned short, unsigned short, unsigned int) const; + + // Construction is done in core, not allowed in contracts QpiContextForInit(unsigned int contractIndex) : QpiContext(contractIndex, NULL_ID, NULL_ID, 0) {} - void __registerUserFunction(USER_FUNCTION, unsigned short, unsigned short, unsigned short) const; - void __registerUserProcedure(USER_PROCEDURE, unsigned short, unsigned short, unsigned short) const; }; - struct QpiContextNoOriginatorAndReward : public QPI::QpiContext - { - QpiContextNoOriginatorAndReward(unsigned int contractIndex) : QpiContext(contractIndex, NULL_ID, NULL_ID, 0) {} - }; + struct NoLocalVariables {}; #endif ////////// + // TODO: make sure these cannot be called from contract body #define INITIALIZE public: static void __initialize(const QPI::QpiContext& qpi, CONTRACT_STATE_TYPE& state) { constexpr unsigned int __functionOrProcedureId = (CONTRACT_INDEX << 22) | __LINE__; ::__beginFunctionOrProcedure(__functionOrProcedureId); #define BEGIN_EPOCH public: static void __beginEpoch(const QPI::QpiContext& qpi, CONTRACT_STATE_TYPE& state) { constexpr unsigned int __functionOrProcedureId = (CONTRACT_INDEX << 22) | __LINE__; ::__beginFunctionOrProcedure(__functionOrProcedureId); @@ -6138,6 +6167,7 @@ namespace QPI #define EXPAND public: static void __expand(const QPI::QpiContext& qpi, CONTRACT_STATE_TYPE& state, CONTRACT_STATE2_TYPE& state2) { constexpr unsigned int __functionOrProcedureId = (CONTRACT_INDEX << 22) | __LINE__; ::__beginFunctionOrProcedure(__functionOrProcedureId); + // TODO: move to QPI to prevent from spamming log of other contract (by calling local function) #define LOG_DEBUG(message) __logContractDebugMessage(CONTRACT_INDEX, message); #define LOG_ERROR(message) __logContractErrorMessage(CONTRACT_INDEX, message); @@ -6146,17 +6176,101 @@ namespace QPI #define LOG_WARNING(message) __logContractWarningMessage(CONTRACT_INDEX, message); - #define PRIVATE(functionOrProcedure) private: static void functionOrProcedure(const QPI::QpiContext& qpi, CONTRACT_STATE_TYPE& state, functionOrProcedure##_input& input, functionOrProcedure##_output& output) { constexpr unsigned int __functionOrProcedureId = (CONTRACT_INDEX << 22) | __LINE__; ::__beginFunctionOrProcedure(__functionOrProcedureId); - - #define PUBLIC(functionOrProcedure) public: static void functionOrProcedure(const QPI::QpiContext& qpi, CONTRACT_STATE_TYPE& state, functionOrProcedure##_input& input, functionOrProcedure##_output& output) { constexpr unsigned int __functionOrProcedureId = (CONTRACT_INDEX << 22) | __LINE__; ::__beginFunctionOrProcedure(__functionOrProcedureId); - - #define REGISTER_USER_FUNCTIONS_AND_PROCEDURES public: static void __registerUserFunctionsAndProcedures(const QPI::QpiContextForInit& qpi) { constexpr unsigned int __functionOrProcedureId = (CONTRACT_INDEX << 22) | __LINE__; ::__beginFunctionOrProcedure(__functionOrProcedureId); + #define PRIVATE_FUNCTION(function) \ + private: \ + typedef QPI::NoLocalVariables function##_locals; \ + PRIVATE_FUNCTION_WITH_LOCALS(function) + + #define PRIVATE_FUNCTION_WITH_LOCALS(function) \ + private: \ + enum { __is_function_##function = true }; \ + static void function(const QPI::QpiContextFunctionCall& qpi, const CONTRACT_STATE_TYPE& state, function##_input& input, function##_output& output, function##_locals& locals) { constexpr unsigned int __functionOrProcedureId = (CONTRACT_INDEX << 22) | __LINE__; ::__beginFunctionOrProcedure(__functionOrProcedureId); + + #define PRIVATE_PROCEDURE(procedure) \ + private: \ + typedef QPI::NoLocalVariables procedure##_locals; \ + PRIVATE_PROCEDURE_WITH_LOCALS(procedure); + + #define PRIVATE_PROCEDURE_WITH_LOCALS(procedure) \ + private: \ + enum { __is_function_##procedure = false }; \ + static void procedure(const QPI::QpiContextProcedureCall& qpi, CONTRACT_STATE_TYPE& state, procedure##_input& input, procedure##_output& output, procedure##_locals& locals) { constexpr unsigned int __functionOrProcedureId = (CONTRACT_INDEX << 22) | __LINE__; ::__beginFunctionOrProcedure(__functionOrProcedureId); + + #define PUBLIC_FUNCTION(function) \ + public: \ + typedef QPI::NoLocalVariables function##_locals; \ + PUBLIC_FUNCTION_WITH_LOCALS(function); + + #define PUBLIC_FUNCTION_WITH_LOCALS(function) \ + public: \ + enum { __is_function_##function = true }; \ + static void function(const QPI::QpiContextFunctionCall& qpi, const CONTRACT_STATE_TYPE& state, function##_input& input, function##_output& output, function##_locals& locals) { constexpr unsigned int __functionOrProcedureId = (CONTRACT_INDEX << 22) | __LINE__; ::__beginFunctionOrProcedure(__functionOrProcedureId); + + #define PUBLIC_PROCEDURE(procedure) \ + public: \ + typedef QPI::NoLocalVariables procedure##_locals; \ + PUBLIC_PROCEDURE_WITH_LOCALS(procedure); + + #define PUBLIC_PROCEDURE_WITH_LOCALS(procedure) \ + public: \ + enum { __is_function_##procedure = false }; \ + static void procedure(const QPI::QpiContextProcedureCall& qpi, CONTRACT_STATE_TYPE& state, procedure##_input& input, procedure##_output& output, procedure##_locals& locals) { constexpr unsigned int __functionOrProcedureId = (CONTRACT_INDEX << 22) | __LINE__; ::__beginFunctionOrProcedure(__functionOrProcedureId); + + #define REGISTER_USER_FUNCTIONS_AND_PROCEDURES \ + public: \ + enum { __contract_index = CONTRACT_INDEX }; \ + static void __registerUserFunctionsAndProcedures(const QPI::QpiContextForInit& qpi) { constexpr unsigned int __functionOrProcedureId = (CONTRACT_INDEX << 22) | __LINE__; ::__beginFunctionOrProcedure(__functionOrProcedureId); #define _ ::__endFunctionOrProcedure(__functionOrProcedureId); } - #define REGISTER_USER_FUNCTION(userFunction, inputType) qpi.__registerUserFunction((USER_FUNCTION)userFunction, inputType, sizeof(userFunction##_input), sizeof(userFunction##_output)); - - #define REGISTER_USER_PROCEDURE(userProcedure, inputType) qpi.__registerUserProcedure((USER_PROCEDURE)userProcedure, inputType, sizeof(userProcedure##_input), sizeof(userProcedure##_output)); + #define REGISTER_USER_FUNCTION(userFunction, inputType) \ + static_assert(__is_function_##userFunction, #userFunction " is procedure"); \ + static_assert(sizeof(userFunction##_output) <= 65536, #userFunction "_output size too large"); \ + static_assert(sizeof(userFunction##_input) <= 65536, #userFunction "_input size too large"); \ + static_assert(sizeof(userFunction##_locals) <= MAX_SIZE_OF_CONTRACT_LOCALS, #userFunction "_locals size too large"); \ + qpi.__registerUserFunction((USER_FUNCTION)userFunction, inputType, sizeof(userFunction##_input), sizeof(userFunction##_output), sizeof(userFunction##_locals)); + + #define REGISTER_USER_PROCEDURE(userProcedure, inputType) \ + static_assert(!__is_function_##userProcedure, #userProcedure " is function"); \ + static_assert(sizeof(userProcedure##_output) <= 65536, #userProcedure "_output size too large"); \ + static_assert(sizeof(userProcedure##_input) <= 65536, #userProcedure "_input size too large"); \ + static_assert(sizeof(userProcedure##_locals) <= MAX_SIZE_OF_CONTRACT_LOCALS, #userProcedure "_locals size too large"); \ + qpi.__registerUserProcedure((USER_PROCEDURE)userProcedure, inputType, sizeof(userProcedure##_input), sizeof(userProcedure##_output), sizeof(userProcedure##_locals)); + + // Call function or procedure of current contract + #define CALL(functionOrProcedure, input, output) \ + static_assert(sizeof(CONTRACT_STATE_TYPE::functionOrProcedure##_locals) <= MAX_SIZE_OF_CONTRACT_LOCALS, #functionOrProcedure "_locals size too large"); \ + functionOrProcedure(qpi, state, input, output, *(functionOrProcedure##_locals*)qpi.__qpiAllocLocals(sizeof(CONTRACT_STATE_TYPE::functionOrProcedure##_locals))); \ + qpi.__qpiFreeLocals() + + // Call function or procedure of other contract + // TODO: static_assert(contractStateType != CONTRACT_STATE_TYPE, "Use CALL to call a function/procedure of this contract"); + #define CALL_OTHER_CONTRACT(contractStateType, functionOrProcedure, input, output) \ + static_assert(sizeof(contractStateType::functionOrProcedure##_locals) <= MAX_SIZE_OF_CONTRACT_LOCALS, #functionOrProcedure "_locals size too large"); \ + if (contractStateType::__is_function_##functionOrProcedure) { \ + contractStateType::functionOrProcedure( \ + qpi.__qpiConstructContextOtherContractFunctionCall(contractStateType::__contract_index), \ + *(contractStateType*)qpi.__qpiAquireStateForReading(contractStateType::__contract_index), \ + input, output, \ + *(contractStateType::functionOrProcedure##_locals*)qpi.__qpiAllocLocals(sizeof(contractStateType::functionOrProcedure##_locals))); \ + qpi.__qpiReleaseStateForReading(contractStateType::__contract_index); \ + } else { \ + contractStateType::functionOrProcedure( \ + qpi.__qpiConstructContextOtherContractProcedureCall(contractStateType::__contract_index), \ + *(contractStateType*)qpi.__qpiAquireStateForWriting(contractStateType::__contract_index), \ + input, output, \ + *(contractStateType::functionOrProcedure##_locals*)qpi.__qpiAllocLocals(sizeof(contractStateType::functionOrProcedure##_locals))); \ + qpi.__qpiReleaseStateForWriting(contractStateType::__contract_index); \ + } \ + qpi.__qpiFreeLocals() + + // call function or procedure of contract (test case function -> function, procedure -> function, procedure -> proc, forbidden func -> proc) + + + // TODO: calling of other contracts, make sure to avoid race conditions, prevent deadlocks (A -> A not allowed) + // check that private cannot be called from other + // only allow to call contracts with smaller ID + // write note about use of stack and risk of stack overlow with many -> we may have a separate per-processor stack to store the context (or changing variables of the context) #define SELF id(CONTRACT_INDEX, 0, 0, 0) diff --git a/src/platform/concurrency.h b/src/platform/concurrency.h index 5fbcd052..039e5c4e 100644 --- a/src/platform/concurrency.h +++ b/src/platform/concurrency.h @@ -2,5 +2,11 @@ #include +// Acquire lock, may block #define ACQUIRE(lock) while (_InterlockedCompareExchange8(&lock, 1, 0)) _mm_pause() + +// Try to acquire lock and return if successful (without blocking) +#define TRY_ACQUIRE(lock) (_InterlockedCompareExchange8(&lock, 1, 0) == 0) + +// Release lock #define RELEASE(lock) lock = 0 diff --git a/src/platform/m256.h b/src/platform/m256.h index c3ad24a6..434c6c40 100644 --- a/src/platform/m256.h +++ b/src/platform/m256.h @@ -56,6 +56,7 @@ union m256i m256i(unsigned long long ull0, unsigned long long ull1, unsigned long long ull2, unsigned long long ull3) { + // TODO: set with _mm256_set_epi64x m256i_u64[0] = ull0; m256i_u64[1] = ull1; m256i_u64[2] = ull2; diff --git a/src/public_settings.h b/src/public_settings.h index 29f12ce3..ccafa437 100644 --- a/src/public_settings.h +++ b/src/public_settings.h @@ -10,6 +10,7 @@ #define MAX_NUMBER_OF_PROCESSORS 32 #define NUMBER_OF_SOLUTION_PROCESSORS 6 // do not increase this for this epoch, because there may be issues due too fast ticking +#define NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS 4 // number of processors that can execute contract functions in parallel #define USE_SCORE_CACHE 1 #define SCORE_CACHE_SIZE 2000000 // the larger the better diff --git a/src/qubic.cpp b/src/qubic.cpp index ccae3ec8..e362c2e3 100644 --- a/src/qubic.cpp +++ b/src/qubic.cpp @@ -2,7 +2,9 @@ #include "network_messages/all.h" +// needs to be included early to make sure that contracts have minimal access #include "contract_core/contract_def.h" +#include "contract_core/contract_exec.h" #include "private_settings.h" #include "public_settings.h" @@ -125,17 +127,8 @@ static unsigned long long mainLoopNumerator = 0, mainLoopDenominator = 0; static unsigned char contractProcessorState = 0; static unsigned int contractProcessorPhase; static EFI_EVENT contractProcessorEvent; -static volatile char contractStateLock[contractCount]; -static unsigned char* contractStates[contractCount]; static m256i contractStateDigests[MAX_NUMBER_OF_CONTRACTS * 2 - 1]; static unsigned long long* contractStateChangeFlags = NULL; -static unsigned long long contractTotalExecutionTicks[contractCount] = { 0 }; -static volatile char contractStateCopyLock = 0; -static char* contractStateCopy = NULL; -static char contractFunctionInputs[MAX_NUMBER_OF_PROCESSORS][65536]; -static char* contractFunctionOutputs[MAX_NUMBER_OF_PROCESSORS]; -static char executedContractInput[65536]; -static char executedContractOutput[RequestResponseHeader::max_size + 1]; static bool targetNextTickDataDigestIsKnown = false; static m256i targetNextTickDataDigest; @@ -1056,30 +1049,19 @@ static void processRequestContractFunction(Peer* peer, const unsigned long long // TODO: Invoked function may enter endless loop, so a timeout (and restart) is required for request processing threads // TODO: Enable parallel execution of contract functions - RespondContractFunction* response = (RespondContractFunction*)contractFunctionOutputs[processorNumber]; - RequestContractFunction* request = header->getPayload(); if (header->size() != sizeof(RequestResponseHeader) + sizeof(RequestContractFunction) + request->inputSize || !request->contractIndex || request->contractIndex >= contractCount || system.epoch < contractDescriptions[request->contractIndex].constructionEpoch || !contractUserFunctions[request->contractIndex][request->inputType]) { - enqueueResponse(peer, 0, response->type, header->dejavu(), NULL); + enqueueResponse(peer, 0, RespondContractFunction::type, header->dejavu(), NULL); } else { - QPI::QpiContextNoOriginatorAndReward qpiContext(request->contractIndex); - - bs->SetMem(&contractFunctionInputs[processorNumber], sizeof(contractFunctionInputs[processorNumber]), 0); - bs->CopyMem(&contractFunctionInputs[processorNumber], (((unsigned char*)request) + sizeof(RequestContractFunction)), request->inputSize); - ACQUIRE(contractStateLock[request->contractIndex]); - ACQUIRE(contractStateCopyLock); // A single contract state buffer is used because of lack of memory, if we could afford we would use a buffer per request processing thread - bs->CopyMem(contractStateCopy, contractStates[request->contractIndex], contractDescriptions[request->contractIndex].stateSize); - contractUserFunctions[request->contractIndex][request->inputType](qpiContext, contractStateCopy, &contractFunctionInputs[processorNumber], response); - RELEASE(contractStateCopyLock); - RELEASE(contractStateLock[request->contractIndex]); - - enqueueResponse(peer, contractUserFunctionOutputSizes[request->contractIndex][request->inputType], response->type, header->dejavu(), response); + QpiContextUserFunctionCall qpiContext(request->contractIndex); + qpiContext.call(request->inputType, (((unsigned char*)request) + sizeof(RequestContractFunction)), request->inputSize); + enqueueResponse(peer, qpiContext.outputSize, RespondContractFunction::type, header->dejavu(), qpiContext.outputBuffer); } } @@ -1453,29 +1435,32 @@ static void __beginFunctionOrProcedure(const unsigned int functionOrProcedureId) static void __endFunctionOrProcedure(const unsigned int functionOrProcedureId) { + // TODO: move this to procedure call cleanup (so it is not set by function calls) contractStateChangeFlags[functionOrProcedureId >> (22 + 6)] |= (1ULL << ((functionOrProcedureId >> 22) & 63)); } -void QPI::QpiContextForInit::__registerUserFunction(USER_FUNCTION userFunction, unsigned short inputType, unsigned short inputSize, unsigned short outputSize) const +void QPI::QpiContextForInit::__registerUserFunction(USER_FUNCTION userFunction, unsigned short inputType, unsigned short inputSize, unsigned short outputSize, unsigned int localsSize) const { contractUserFunctions[_currentContractIndex][inputType] = userFunction; contractUserFunctionInputSizes[_currentContractIndex][inputType] = inputSize; contractUserFunctionOutputSizes[_currentContractIndex][inputType] = outputSize; + contractUserFunctionLocalsSizes[_currentContractIndex][inputType] = localsSize; } -void QPI::QpiContextForInit::__registerUserProcedure(USER_PROCEDURE userProcedure, unsigned short inputType, unsigned short inputSize, unsigned short outputSize) const +void QPI::QpiContextForInit::__registerUserProcedure(USER_PROCEDURE userProcedure, unsigned short inputType, unsigned short inputSize, unsigned short outputSize, unsigned int localsSize) const { contractUserProcedures[_currentContractIndex][inputType] = userProcedure; contractUserProcedureInputSizes[_currentContractIndex][inputType] = inputSize; contractUserProcedureOutputSizes[_currentContractIndex][inputType] = outputSize; + contractUserProcedureLocalsSizes[_currentContractIndex][inputType] = localsSize; } -QPI::id QPI::QpiContext::arbitrator() const +QPI::id QPI::QpiContextFunctionCall::arbitrator() const { return arbitratorPublicKey; } -long long QPI::QpiContext::burn(long long amount) const +long long QPI::QpiContextProcedureCall::burn(long long amount) const { if (amount < 0 || amount > MAX_AMOUNT) { @@ -1509,27 +1494,27 @@ long long QPI::QpiContext::burn(long long amount) const return remainingAmount; } -QPI::id QPI::QpiContext::computor(unsigned short computorIndex) const +QPI::id QPI::QpiContextFunctionCall::computor(unsigned short computorIndex) const { return broadcastedComputors.computors.publicKeys[computorIndex % NUMBER_OF_COMPUTORS]; } -unsigned char QPI::QpiContext::day() const +unsigned char QPI::QpiContextFunctionCall::day() const { return etalonTick.day; } -unsigned char QPI::QpiContext::dayOfWeek(unsigned char year, unsigned char month, unsigned char day) const +unsigned char QPI::QpiContextFunctionCall::dayOfWeek(unsigned char year, unsigned char month, unsigned char day) const { return dayIndex(year, month, day) % 7; } -unsigned short QPI::QpiContext::epoch() const +unsigned short QPI::QpiContextFunctionCall::epoch() const { return system.epoch; } -bool QPI::QpiContext::getEntity(const m256i& id, ::Entity& entity) const +bool QPI::QpiContextFunctionCall::getEntity(const m256i& id, ::Entity& entity) const { int index = spectrumIndex(id); if (index < 0) @@ -1558,22 +1543,22 @@ bool QPI::QpiContext::getEntity(const m256i& id, ::Entity& entity) const } } -unsigned char QPI::QpiContext::hour() const +unsigned char QPI::QpiContextFunctionCall::hour() const { return etalonTick.hour; } -long long QPI::QpiContext::invocationReward() const +long long QPI::QpiContextFunctionCall::invocationReward() const { return _invocationReward; } -QPI::id QPI::QpiContext::invocator() const +QPI::id QPI::QpiContextFunctionCall::invocator() const { return _invocator; } -long long QPI::QpiContext::issueAsset(unsigned long long name, const QPI::id& issuer, signed char numberOfDecimalPlaces, long long numberOfShares, unsigned long long unitOfMeasurement) const +long long QPI::QpiContextProcedureCall::issueAsset(unsigned long long name, const QPI::id& issuer, signed char numberOfDecimalPlaces, long long numberOfShares, unsigned long long unitOfMeasurement) const { if (((unsigned char)name) < 'A' || ((unsigned char)name) > 'Z' || name > 0xFFFFFFFFFFFFFF) @@ -1632,22 +1617,22 @@ long long QPI::QpiContext::issueAsset(unsigned long long name, const QPI::id& is return numberOfShares; } -unsigned short QPI::QpiContext::millisecond() const +unsigned short QPI::QpiContextFunctionCall::millisecond() const { return etalonTick.millisecond; } -unsigned char QPI::QpiContext::minute() const +unsigned char QPI::QpiContextFunctionCall::minute() const { return etalonTick.minute; } -unsigned char QPI::QpiContext::month() const +unsigned char QPI::QpiContextFunctionCall::month() const { return etalonTick.month; } -m256i QPI::QpiContext::nextId(const m256i& currentId) const +m256i QPI::QpiContextFunctionCall::nextId(const m256i& currentId) const { int index = spectrumIndex(currentId); while (++index < SPECTRUM_CAPACITY) @@ -1662,7 +1647,7 @@ m256i QPI::QpiContext::nextId(const m256i& currentId) const return _mm256_setzero_si256(); } -long long QPI::QpiContext::numberOfPossessedShares(unsigned long long assetName, const m256i& issuer, const m256i& owner, const m256i& possessor, unsigned short ownershipManagingContractIndex, unsigned short possessionManagingContractIndex) const +long long QPI::QpiContextFunctionCall::numberOfPossessedShares(unsigned long long assetName, const m256i& issuer, const m256i& owner, const m256i& possessor, unsigned short ownershipManagingContractIndex, unsigned short possessionManagingContractIndex) const { ACQUIRE(universeLock); @@ -1741,12 +1726,12 @@ long long QPI::QpiContext::numberOfPossessedShares(unsigned long long assetName, } } -m256i QPI::QpiContext::originator() const +m256i QPI::QpiContextFunctionCall::originator() const { return _originator; } -unsigned char QPI::QpiContext::second() const +unsigned char QPI::QpiContextFunctionCall::second() const { return etalonTick.second; } @@ -1756,12 +1741,12 @@ static void* __scratchpad() return reorgBuffer; } -unsigned int QPI::QpiContext::tick() const +unsigned int QPI::QpiContextFunctionCall::tick() const { return system.tick; } -long long QPI::QpiContext::transfer(const m256i& destination, long long amount) const +long long QPI::QpiContextProcedureCall::transfer(const m256i& destination, long long amount) const { if (amount < 0 || amount > MAX_AMOUNT) { @@ -1793,7 +1778,7 @@ long long QPI::QpiContext::transfer(const m256i& destination, long long amount) return remainingAmount; } -long long QPI::QpiContext::transferShareOwnershipAndPossession(unsigned long long assetName, const m256i& issuer, const m256i& owner, const m256i& possessor, long long numberOfShares, const m256i& newOwnerAndPossessor) const +long long QPI::QpiContextProcedureCall::transferShareOwnershipAndPossession(unsigned long long assetName, const m256i& issuer, const m256i& owner, const m256i& possessor, long long numberOfShares, const m256i& newOwnerAndPossessor) const { if (numberOfShares <= 0 || numberOfShares > MAX_AMOUNT) { @@ -1895,13 +1880,13 @@ long long QPI::QpiContext::transferShareOwnershipAndPossession(unsigned long lon } } -unsigned char QPI::QpiContext::year() const +unsigned char QPI::QpiContextFunctionCall::year() const { return etalonTick.year; } template -m256i QPI::QpiContext::K12(const T& data) const +m256i QPI::QpiContextFunctionCall::K12(const T& data) const { m256i digest; @@ -1927,13 +1912,8 @@ static void contractProcessor(void*) if (system.epoch == contractDescriptions[executedContractIndex].constructionEpoch && system.epoch < contractDescriptions[executedContractIndex].destructionEpoch) { - QPI::QpiContextNoOriginatorAndReward qpiContext(executedContractIndex); - - ACQUIRE(contractStateLock[executedContractIndex]); - const unsigned long long startTick = __rdtsc(); - contractSystemProcedures[executedContractIndex][INITIALIZE](qpiContext, contractStates[executedContractIndex]); - contractTotalExecutionTicks[executedContractIndex] += __rdtsc() - startTick; - RELEASE(contractStateLock[executedContractIndex]); + QpiContextSystemProcedureCall qpiContext(executedContractIndex); + qpiContext.call(INITIALIZE); } } } @@ -1946,13 +1926,8 @@ static void contractProcessor(void*) if (system.epoch >= contractDescriptions[executedContractIndex].constructionEpoch && system.epoch < contractDescriptions[executedContractIndex].destructionEpoch) { - QPI::QpiContextNoOriginatorAndReward qpiContext(executedContractIndex); - - ACQUIRE(contractStateLock[executedContractIndex]); - const unsigned long long startTick = __rdtsc(); - contractSystemProcedures[executedContractIndex][BEGIN_EPOCH](qpiContext, contractStates[executedContractIndex]); - contractTotalExecutionTicks[executedContractIndex] += __rdtsc() - startTick; - RELEASE(contractStateLock[executedContractIndex]); + QpiContextSystemProcedureCall qpiContext(executedContractIndex); + qpiContext.call(BEGIN_EPOCH); } } } @@ -1965,13 +1940,8 @@ static void contractProcessor(void*) if (system.epoch >= contractDescriptions[executedContractIndex].constructionEpoch && system.epoch < contractDescriptions[executedContractIndex].destructionEpoch) { - QPI::QpiContextNoOriginatorAndReward qpiContext(executedContractIndex); - - ACQUIRE(contractStateLock[executedContractIndex]); - const unsigned long long startTick = __rdtsc(); - contractSystemProcedures[executedContractIndex][BEGIN_TICK](qpiContext, contractStates[executedContractIndex]); - contractTotalExecutionTicks[executedContractIndex] += __rdtsc() - startTick; - RELEASE(contractStateLock[executedContractIndex]); + QpiContextSystemProcedureCall qpiContext(executedContractIndex); + qpiContext.call(BEGIN_TICK); } } } @@ -1984,13 +1954,8 @@ static void contractProcessor(void*) if (system.epoch >= contractDescriptions[executedContractIndex].constructionEpoch && system.epoch < contractDescriptions[executedContractIndex].destructionEpoch) { - QPI::QpiContextNoOriginatorAndReward qpiContext(executedContractIndex); - - ACQUIRE(contractStateLock[executedContractIndex]); - const unsigned long long startTick = __rdtsc(); - contractSystemProcedures[executedContractIndex][END_TICK](qpiContext, contractStates[executedContractIndex]); - contractTotalExecutionTicks[executedContractIndex] += __rdtsc() - startTick; - RELEASE(contractStateLock[executedContractIndex]); + QpiContextSystemProcedureCall qpiContext(executedContractIndex); + qpiContext.call(END_TICK); } } } @@ -2003,13 +1968,8 @@ static void contractProcessor(void*) if (system.epoch >= contractDescriptions[executedContractIndex].constructionEpoch && system.epoch < contractDescriptions[executedContractIndex].destructionEpoch) { - QPI::QpiContextNoOriginatorAndReward qpiContext(executedContractIndex); - - ACQUIRE(contractStateLock[executedContractIndex]); - const unsigned long long startTick = __rdtsc(); - contractSystemProcedures[executedContractIndex][END_EPOCH](qpiContext, contractStates[executedContractIndex]); - contractTotalExecutionTicks[executedContractIndex] += __rdtsc() - startTick; - RELEASE(contractStateLock[executedContractIndex]); + QpiContextSystemProcedureCall qpiContext(executedContractIndex); + qpiContext.call(END_EPOCH); } } } @@ -2266,15 +2226,8 @@ static void processTick(unsigned long long processorNumber) { if (contractUserProcedures[contractIndex][transaction->inputType]) { - QpiContextProcedureCall qpiContext(contractIndex, transaction->sourcePublicKey, transaction->amount); - - bs->SetMem(&executedContractInput, sizeof(executedContractInput), 0); - bs->CopyMem(&executedContractInput, transaction->inputPtr(), transaction->inputSize); - ACQUIRE(contractStateLock[contractIndex]); - const unsigned long long startTick = __rdtsc(); - contractUserProcedures[contractIndex][transaction->inputType](qpiContext, contractStates[contractIndex], &executedContractInput, &executedContractOutput); - contractTotalExecutionTicks[contractIndex] += __rdtsc() - startTick; - RELEASE(contractStateLock[contractIndex]); + QpiContextUserProcedureCall qpiContext(contractIndex, transaction->sourcePublicKey, transaction->amount); + qpiContext.call(transaction->inputType, transaction->inputPtr(), transaction->inputSize); } } } @@ -3859,10 +3812,6 @@ static bool initialize() bs->SetMem(contractSystemProcedures, sizeof(contractSystemProcedures), 0); bs->SetMem(contractUserFunctions, sizeof(contractUserFunctions), 0); bs->SetMem(contractUserProcedures, sizeof(contractUserProcedures), 0); - for (unsigned int processorIndex = 0; processorIndex < MAX_NUMBER_OF_PROCESSORS; processorIndex++) - { - contractFunctionOutputs[processorIndex] = NULL; - } getPublicKeyFromIdentity((const unsigned char*)OPERATOR, operatorPublicKey.m256i_u8); if (isZero(operatorPublicKey)) @@ -3935,6 +3884,8 @@ static bool initialize() return false; bs->SetMem((void*)contractStateLock, sizeof(contractStateLock), 0); + bs->SetMem(contractTotalExecutionTicks, sizeof(contractTotalExecutionTicks), 0); + initContractExec(); for (unsigned int contractIndex = 0; contractIndex < contractCount; contractIndex++) { unsigned long long size = contractDescriptions[contractIndex].stateSize; @@ -3945,23 +3896,13 @@ static bool initialize() return false; } } - if ((status = bs->AllocatePool(EfiRuntimeServicesData, MAX_NUMBER_OF_CONTRACTS / 8, (void**)&contractStateChangeFlags)) - || (status = bs->AllocatePool(EfiRuntimeServicesData, MAX_CONTRACT_STATE_SIZE, (void**)&contractStateCopy))) + if ((status = bs->AllocatePool(EfiRuntimeServicesData, MAX_NUMBER_OF_CONTRACTS / 8, (void**)&contractStateChangeFlags))) { logStatusToConsole(L"EFI_BOOT_SERVICES.AllocatePool() fails", status, __LINE__); return false; } bs->SetMem(contractStateChangeFlags, MAX_NUMBER_OF_CONTRACTS / 8, 0xFF); - for (unsigned int processorIndex = 0; processorIndex < MAX_NUMBER_OF_PROCESSORS; processorIndex++) - { - if (status = bs->AllocatePool(EfiRuntimeServicesData, RequestResponseHeader::max_size - sizeof(RequestResponseHeader), (void**)&contractFunctionOutputs[processorIndex])) - { - logStatusToConsole(L"EFI_BOOT_SERVICES.AllocatePool() fails", status, __LINE__); - - return false; - } - } if (status = bs->AllocatePool(EfiRuntimeServicesData, sizeof(*score), (void**)&score)) { @@ -4204,17 +4145,6 @@ static void deinitialize() deinitLogging(); - for (unsigned int processorIndex = 0; processorIndex < MAX_NUMBER_OF_PROCESSORS; processorIndex++) - { - if (contractFunctionOutputs[processorIndex]) - { - bs->FreePool(contractFunctionOutputs[processorIndex]); - } - } - if (contractStateCopy) - { - bs->FreePool(contractStateCopy); - } if (contractStateChangeFlags) { bs->FreePool(contractStateChangeFlags); diff --git a/test/contract_core.cpp b/test/contract_core.cpp index f403e22d..63eb14b3 100644 --- a/test/contract_core.cpp +++ b/test/contract_core.cpp @@ -43,5 +43,8 @@ TEST(TestCoreContractCore, StackBuffer) s1.free(); EXPECT_EQ(s1.size(), 0); - + char* p = s1.allocate(70); + *p = 100; + EXPECT_EQ(*p, 100); + s1.free(); } From 9c7d8037e7b1a98086725cf56e4be7505e8550a7 Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Wed, 24 Apr 2024 16:35:22 +0200 Subject: [PATCH 02/31] Fix invocation reward in cross-contract invocation --- src/contract_core/contract_exec.h | 6 ++-- src/contracts/qpi.h | 54 +++++++++++++++---------------- 2 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/contract_core/contract_exec.h b/src/contract_core/contract_exec.h index becefce9..c67c1bc7 100644 --- a/src/contract_core/contract_exec.h +++ b/src/contract_core/contract_exec.h @@ -76,12 +76,14 @@ const QpiContextFunctionCall& QPI::QpiContextFunctionCall::__qpiConstructContext return newContext; } -const QpiContextProcedureCall& QPI::QpiContextProcedureCall::__qpiConstructContextOtherContractProcedureCall(unsigned int otherContractIndex) const +const QpiContextProcedureCall& QPI::QpiContextProcedureCall::__qpiConstructContextOtherContractProcedureCall(unsigned int otherContractIndex, QPI::sint64 invocationReward) const { ASSERT(_stackIndex >= 0 && _stackIndex < NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS); char* buffer = contractLocalsStack[_stackIndex].allocate(sizeof(QpiContextProcedureCall)); QpiContextProcedureCall& newContext = *reinterpret_cast(buffer); - newContext.init(otherContractIndex, _originator, _currentContractId, _invocationReward); + if (transfer(QPI::id(otherContractIndex, 0, 0, 0), invocationReward) < 0) + invocationReward = 0; + newContext.init(otherContractIndex, _originator, _currentContractId, invocationReward); return newContext; } diff --git a/src/contracts/qpi.h b/src/contracts/qpi.h index d71e26ab..3786b564 100644 --- a/src/contracts/qpi.h +++ b/src/contracts/qpi.h @@ -6129,7 +6129,7 @@ namespace QPI // Internal functions, calling not allowed in contracts - const QpiContextProcedureCall& __qpiConstructContextOtherContractProcedureCall(unsigned int otherContractIndex) const; + const QpiContextProcedureCall& __qpiConstructContextOtherContractProcedureCall(unsigned int otherContractIndex, sint64 invocationReward) const; void* __qpiAquireStateForWriting(unsigned int contractIndex) const; void __qpiReleaseStateForWriting(unsigned int contractIndex) const; @@ -6243,34 +6243,34 @@ namespace QPI functionOrProcedure(qpi, state, input, output, *(functionOrProcedure##_locals*)qpi.__qpiAllocLocals(sizeof(CONTRACT_STATE_TYPE::functionOrProcedure##_locals))); \ qpi.__qpiFreeLocals() - // Call function or procedure of other contract - // TODO: static_assert(contractStateType != CONTRACT_STATE_TYPE, "Use CALL to call a function/procedure of this contract"); - #define CALL_OTHER_CONTRACT(contractStateType, functionOrProcedure, input, output) \ - static_assert(sizeof(contractStateType::functionOrProcedure##_locals) <= MAX_SIZE_OF_CONTRACT_LOCALS, #functionOrProcedure "_locals size too large"); \ - if (contractStateType::__is_function_##functionOrProcedure) { \ - contractStateType::functionOrProcedure( \ - qpi.__qpiConstructContextOtherContractFunctionCall(contractStateType::__contract_index), \ - *(contractStateType*)qpi.__qpiAquireStateForReading(contractStateType::__contract_index), \ - input, output, \ - *(contractStateType::functionOrProcedure##_locals*)qpi.__qpiAllocLocals(sizeof(contractStateType::functionOrProcedure##_locals))); \ - qpi.__qpiReleaseStateForReading(contractStateType::__contract_index); \ - } else { \ - contractStateType::functionOrProcedure( \ - qpi.__qpiConstructContextOtherContractProcedureCall(contractStateType::__contract_index), \ - *(contractStateType*)qpi.__qpiAquireStateForWriting(contractStateType::__contract_index), \ - input, output, \ - *(contractStateType::functionOrProcedure##_locals*)qpi.__qpiAllocLocals(sizeof(contractStateType::functionOrProcedure##_locals))); \ - qpi.__qpiReleaseStateForWriting(contractStateType::__contract_index); \ - } \ + // Call function of other contract + #define CALL_OTHER_CONTRACT_FUNCTION(contractStateType, function, input, output) \ + static_assert(sizeof(contractStateType::function##_locals) <= MAX_SIZE_OF_CONTRACT_LOCALS, #function "_locals size too large"); \ + static_assert(contractStateType::__is_function_##function, "CALL_OTHER_CONTRACT_FUNCTION() cannot be used to invoke procedures."); \ + static_assert(!(contractStateType::__contract_index == CONTRACT_STATE_TYPE::__contract_index), "Use CALL() to call a function of this contract."); \ + static_assert(contractStateType::__contract_index < CONTRACT_STATE_TYPE::__contract_index, "You can only call contracts with lower index."); \ + contractStateType::function( \ + qpi.__qpiConstructContextOtherContractFunctionCall(contractStateType::__contract_index), \ + *(contractStateType*)qpi.__qpiAquireStateForReading(contractStateType::__contract_index), \ + input, output, \ + *(contractStateType::function##_locals*)qpi.__qpiAllocLocals(sizeof(contractStateType::function##_locals))); \ + qpi.__qpiReleaseStateForReading(contractStateType::__contract_index); \ qpi.__qpiFreeLocals() - // call function or procedure of contract (test case function -> function, procedure -> function, procedure -> proc, forbidden func -> proc) - - - // TODO: calling of other contracts, make sure to avoid race conditions, prevent deadlocks (A -> A not allowed) - // check that private cannot be called from other - // only allow to call contracts with smaller ID - // write note about use of stack and risk of stack overlow with many -> we may have a separate per-processor stack to store the context (or changing variables of the context) + // Transfer invocation reward and invoke of other contract (procedure only) + #define INVOKE_OTHER_CONTRACT_PROCEDURE(contractStateType, procedure, input, output, invocationReward) \ + static_assert(sizeof(contractStateType::procedure##_locals) <= MAX_SIZE_OF_CONTRACT_LOCALS, #procedure "_locals size too large"); \ + static_assert(!contractStateType::__is_function_##procedure, "INVOKE_OTHER_CONTRACT_PROCEDURE() cannot be used to call functions."); \ + static_assert(!(contractStateType::__contract_index == CONTRACT_STATE_TYPE::__contract_index), "Use CALL() to call a function/procedure of this contract."); \ + static_assert(contractStateType::__contract_index < CONTRACT_STATE_TYPE::__contract_index, "You can only call contracts with lower index."); \ + static_assert(invocationReward >= 0, "The invocationReward cannot be negative!"); \ + contractStateType::procedure( \ + qpi.__qpiConstructContextOtherContractProcedureCall(contractStateType::__contract_index, invocationReward), \ + *(contractStateType*)qpi.__qpiAquireStateForWriting(contractStateType::__contract_index), \ + input, output, \ + *(contractStateType::procedure##_locals*)qpi.__qpiAllocLocals(sizeof(contractStateType::procedure##_locals))); \ + qpi.__qpiReleaseStateForWriting(contractStateType::__contract_index); \ + qpi.__qpiFreeLocals() #define SELF id(CONTRACT_INDEX, 0, 0, 0) From d73aeb25f807ab9faab111ce70329047d31e2659 Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Wed, 24 Apr 2024 16:41:15 +0200 Subject: [PATCH 03/31] Add more details to docs --- src/contracts/README.md | 34 +++++++++++++++++++++++++++++----- 1 file changed, 29 insertions(+), 5 deletions(-) diff --git a/src/contracts/README.md b/src/contracts/README.md index 67ead9d1..4c908088 100644 --- a/src/contracts/README.md +++ b/src/contracts/README.md @@ -9,16 +9,40 @@ Other header files starting with a lowercase letter, such as `math_lib.h`, provi This document outlines the guidelines for developing secure and efficient Qubic contracts. Adherence to these guidelines is crucial for ensuring the proper functionality and security of your contracts within the Qubic environment. +### Concepts: + +The state is the persistent memory of the contract that is kept aligned in all nodes. + +A contract can have member functions and procedures. + +Functions cannot change the state of the contract. They can be called via a `RequestContractFunction` network message. + +Procedures can change the state of the contract. They are invoked by a transaction and run when the tick containing the transaction is processed. + +There are some special procedures that are called by the system at the beginning of the tick etc. + +A call of a user procedure usually goes along with a transfer of an invocation reward from the invoking user to the contract. + +Procedures can call procedures and functions of the same contract and of contracts with lower contract index. + +Functions can call functions of the same contract and of contracts with lower contract ID. + +Private functions and procedures cannot be called from other contracts. + +In order to be available for invocation by transaction and network message, procedures and functions need to be registered in the special member function `REGISTER_USER_FUNCTIONS_AND_PROCEDURES`. + + + ### Syntax and Formatting: - Qubic contracts generally inherit the syntax and format of C/C++. - However, due to security reasons, certain things are prohibited: - - Declaring and accessing arrays using the C/C++ notation (`[` `]`). Utilize pre-defined array structures within qpi.h such as `collection`, `uint32_64`, `uint32_128`,... - - Any pointer-related techniques such as casting, accessing,... - - Native data types like `int`, `long`, `char`,... Use their corresponding predefined datatypes in `qpi.h` (`int8`, `int16`, `int32`, `int64`,...) + - Declaring and accessing arrays using the C/C++ notation (`[` `]`). Utilize pre-defined array structures within qpi.h such as `collection`, `uint32_64`, `uint32_128`, ... + - Any pointer-related techniques such as casting, accessing, ... + - Native data types like `int`, `long`, `char`, ... Use their corresponding predefined data types in `qpi.h` (`uint8`, `sint8`, `uint16`, `sint32`, `uint64`, ...) - Inclusion of other files via `#include`. All functions must reside within a single file. - Math operators `%` and `/`. Use `mod` and `div` from `qpi.h` instead. `+`, `-`, `*`(multiplication), and bit-wise operators are accepted. - - Local variable declaration, even for for-loop. You need to define all necessary variables in the contract state and utilize them. + - Local variable declaration, even for for-loop. You need to define all necessary variables in either in the contract state or in a "locals" struct similar to the input and output struct of a function or procedure. - The `typedef`, `union` keyword. - - Floating points datatypes (half, float, double) + - Floating point data types (half, float, double) Currently, the maximum contract state size is capped at 1 GiB (03/02/2024). This value is subject to change based on hardware upgrades of computors. From b8b37aa480d76d508071cd9a34cdad448e0a31ec Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Wed, 24 Apr 2024 17:05:33 +0200 Subject: [PATCH 04/31] Remove contract state variables to prevent manipulation and make sure contracts can only call other contracts through the QPI --- src/contract_core/contract_def.h | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/contract_core/contract_def.h b/src/contract_core/contract_def.h index f311c9e2..0597b00e 100644 --- a/src/contract_core/contract_def.h +++ b/src/contract_core/contract_def.h @@ -49,8 +49,6 @@ static void* __scratchpad(); // TODO: concurrency support (n buffers for n al #define CONTRACT_STATE_TYPE QX #define CONTRACT_STATE2_TYPE QX2 #include "contracts/Qx.h" -// TODO: remove state variables to prevent manipulation and make sure contracts can only call other contracts through the API -static CONTRACT_STATE_TYPE* _QX; #undef CONTRACT_INDEX #undef CONTRACT_STATE_TYPE @@ -61,7 +59,6 @@ static CONTRACT_STATE_TYPE* _QX; #define CONTRACT_STATE_TYPE QUOTTERY #define CONTRACT_STATE2_TYPE QUOTTERY2 #include "contracts/Quottery.h" -static CONTRACT_STATE_TYPE* _QUOTTERY; #undef CONTRACT_INDEX #undef CONTRACT_STATE_TYPE @@ -72,7 +69,6 @@ static CONTRACT_STATE_TYPE* _QUOTTERY; #define CONTRACT_STATE_TYPE RANDOM #define CONTRACT_STATE2_TYPE RANDOM2 #include "contracts/Random.h" -static CONTRACT_STATE_TYPE* _RANDOM; #undef CONTRACT_INDEX #undef CONTRACT_STATE_TYPE @@ -83,7 +79,6 @@ static CONTRACT_STATE_TYPE* _RANDOM; #define CONTRACT_STATE_TYPE QUTIL #define CONTRACT_STATE2_TYPE QUTIL2 #include "contracts/QUtil.h" -static CONTRACT_STATE_TYPE* _QUTIL; #define MAX_CONTRACT_ITERATION_DURATION 1000 // In milliseconds, must be above 0 @@ -152,14 +147,13 @@ enum SystemProcedureID }; #define REGISTER_CONTRACT_FUNCTIONS_AND_PROCEDURES(contractName)\ -_##contractName = (contractName*)contractState;\ contractSystemProcedures[contractIndex][INITIALIZE] = (SYSTEM_PROCEDURE)contractName::__initialize;\ contractSystemProcedures[contractIndex][BEGIN_EPOCH] = (SYSTEM_PROCEDURE)contractName::__beginEpoch;\ contractSystemProcedures[contractIndex][END_EPOCH] = (SYSTEM_PROCEDURE)contractName::__endEpoch;\ contractSystemProcedures[contractIndex][BEGIN_TICK] = (SYSTEM_PROCEDURE)contractName::__beginTick;\ contractSystemProcedures[contractIndex][END_TICK] = (SYSTEM_PROCEDURE)contractName::__endTick;\ contractExpandProcedures[contractIndex] = (EXPAND_PROCEDURE)contractName::__expand;\ -_##contractName->__registerUserFunctionsAndProcedures(qpi); +((contractName*)contractState)->__registerUserFunctionsAndProcedures(qpi); static void initializeContract(const unsigned int contractIndex, void* contractState) From 822d8aa75486ba5df32f799684950a2ac9c79033 Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Sat, 27 Apr 2024 17:14:54 +0200 Subject: [PATCH 05/31] Fix bug in collection --- src/contracts/qpi.h | 4 ++-- test/qpi.cpp | 20 ++++++++++++++------ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/src/contracts/qpi.h b/src/contracts/qpi.h index 5c00610f..76ebc470 100644 --- a/src/contracts/qpi.h +++ b/src/contracts/qpi.h @@ -5249,11 +5249,11 @@ namespace QPI pov.population++; - if (_elements[pov.headIndex].priority < priority) + if (_elements[pov.headIndex].priority <= priority) { pov.headIndex = newElementIdx; } - else if (_elements[pov.tailIndex].priority >= priority) + else if (_elements[pov.tailIndex].priority > priority) { pov.tailIndex = newElementIdx; } diff --git a/test/qpi.cpp b/test/qpi.cpp index 97cd1a05..aabb9d88 100644 --- a/test/qpi.cpp +++ b/test/qpi.cpp @@ -599,10 +599,9 @@ TEST(TestCoreQPI, CollectionMultiPovMultiElements) { EXPECT_EQ(coll.priority(0), 0); } - -TEST(TestCoreQPI, CollectionOnePovMultiElements) { - constexpr unsigned long long capacity = 128; - +template +void testCollectionOnePovMultiElements(int prioAmpFactor, int prioFreqDiv) +{ // for valid init you either need to call reset or load the data from a file (in SC, state is zeroed before INITIALIZE is called) QPI::collection coll; coll.reset(); @@ -615,7 +614,7 @@ TEST(TestCoreQPI, CollectionOnePovMultiElements) { QPI::id pov(1, 2, 3, 4); for (int i = 0; i < capacity; ++i) { - QPI::sint64 prio = QPI::sint64(i * 10 * sin(i / 3)); + QPI::sint64 prio = QPI::sint64(i * prioAmpFactor * sin(i / prioFreqDiv)); int value = i * 4; EXPECT_EQ(coll.capacity(), capacity); @@ -624,6 +623,7 @@ TEST(TestCoreQPI, CollectionOnePovMultiElements) { QPI::sint64 elementIndex = coll.add(pov, value, prio); elementIndices.push_back(elementIndex); + checkPriorityQueue(coll, pov); EXPECT_TRUE(elementIndex != QPI::NULL_INDEX); EXPECT_EQ(coll.population(pov), i + 1); @@ -640,7 +640,7 @@ TEST(TestCoreQPI, CollectionOnePovMultiElements) { checkPriorityQueue(coll, pov); for (int i = 0; i < capacity; ++i) { - QPI::sint64 prio = QPI::sint64(i * 10 * sin(i / 3)); + QPI::sint64 prio = QPI::sint64(i * prioAmpFactor * sin(i / prioFreqDiv)); int value = i * 4; QPI::sint64 elementIndex = elementIndices[i]; @@ -807,6 +807,14 @@ TEST(TestCoreQPI, CollectionOnePovMultiElements) { EXPECT_TRUE(isCompletelySame(resetColl, coll)); } +TEST(TestCoreQPI, CollectionOnePovMultiElements) +{ + testCollectionOnePovMultiElements<128>(10, 3); + testCollectionOnePovMultiElements<128>(10, 10); + testCollectionOnePovMultiElements<128>(1, 10); + testCollectionOnePovMultiElements<128>(1, 1); +} + template void testCollectionMultiPovOneElement(bool cleanupAfterEachRemove) { From 129157bc0e54e8a8651414e0fb6cc6363d553900 Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Wed, 3 Apr 2024 12:33:17 +0200 Subject: [PATCH 06/31] Exchange connect original code --- src/exchange_connect.h | 112 +++++++++++++++++++++++++++++++++++++++++ src/qubic.cpp | 23 ++++++++- 2 files changed, 134 insertions(+), 1 deletion(-) create mode 100644 src/exchange_connect.h diff --git a/src/exchange_connect.h b/src/exchange_connect.h new file mode 100644 index 00000000..ab42fb36 --- /dev/null +++ b/src/exchange_connect.h @@ -0,0 +1,112 @@ +#pragma once + +typedef struct +{ + unsigned int tick; + unsigned char moneyFlew; + unsigned char _padding[3]; + m256i digest; +} ConfirmedTx; + +// Memory to store confiremd TX's +static ConfirmedTx *confirmedTx = NULL; +static int tickTxCounter[MAX_NUMBER_OF_TICKS_PER_EPOCH]; // store the amount of tx per tick +static long tickTxIndexStart[MAX_NUMBER_OF_TICKS_PER_EPOCH]; // store the index position per tick +static volatile char confirmedTxLock = 0; + +#define REQUEST_TX_STATUS 201 + +// Use "#pragma pack" keep the binary struct compatibility after changing from unsigned char [32] to m256i +#pragma pack(push,1) +typedef struct +{ + unsigned int tick; + m256i digest; + // char digest[32]; + unsigned char signature[64]; +} RequestTxStatus; +#pragma pack(pop) + +static_assert(sizeof(m256i) == 32, ""); +static_assert(sizeof(RequestTxStatus) == 100, "unexpected size"); + +#define RESPOND_TX_STATUS 202 + +typedef struct +{ + unsigned int currentTickOfNode; + unsigned int tickOfTx; + unsigned char moneyFlew; + unsigned char executed; + unsigned char notfound; + unsigned char _padding[5]; + m256i digest; +} RespondTxStatus; + +static bool initExchangeConnect() +{ + // Allocate pool to store confirmed TX's + if (bs->AllocatePool(EfiRuntimeServicesData, FIRST_TICK_TRANSACTION_OFFSET + (((unsigned long long)MAX_NUMBER_OF_TICKS_PER_EPOCH) * NUMBER_OF_TRANSACTIONS_PER_TICK / TRANSACTION_SPARSENESS) * sizeof(ConfirmedTx), (void**)&confirmedTx) != 0) + return false; + // Init pool with 0 + bs->SetMem(confirmedTx, FIRST_TICK_TRANSACTION_OFFSET + (((unsigned long long)MAX_NUMBER_OF_TICKS_PER_EPOCH) * NUMBER_OF_TRANSACTIONS_PER_TICK / TRANSACTION_SPARSENESS) * sizeof(ConfirmedTx), 0); + return true; +} + +// adds a tx to the confirmed tx store +// txNumberMinusOne: the current tx number -1 +// moneyFlew: if money has been flow +// tick: tick in which this tx was +// digest: digest of tx +static void saveConfirmedTx(unsigned int txNumberMinusOne, unsigned char moneyFlew, unsigned int tick, m256i digest) +{ + ACQUIRE(confirmedTxLock); + int tickNumber = tick - system.initialTick; // get current tick number in epoch + ConfirmedTx & txConfirmation = confirmedTx[txNumberMinusOne]; + txConfirmation.tick = tick; + txConfirmation.moneyFlew = moneyFlew; + txConfirmation.digest = digest; + // keep track of tx number in tick to find it later easier + tickTxCounter[tickNumber]++; + RELEASE(confirmedTxLock); +} + +static void processRequestConfirmedTx(Peer *peer, RequestResponseHeader *header) +{ + + RequestTxStatus *request = header->getPayload(); + + RespondTxStatus currentTxStatus = {}; + + int tickNumber = request->tick - system.initialTick; + // get index where confirmedtx are starting to be stored in memory + int index = tickTxIndexStart[tickNumber]; + + // only send a response if the node is in higher tick than the requested tx + if (request->tick < system.tick) + { + currentTxStatus.currentTickOfNode = system.tick; + currentTxStatus.tickOfTx = request->tick; + currentTxStatus.executed = 0; + currentTxStatus.moneyFlew = 0; + currentTxStatus.notfound = 1; + currentTxStatus.digest = request->digest; + + // loop over the number of tx which were stored for the given tick + for (int i = 0; i < tickTxCounter[tickNumber]; i++) + { + ConfirmedTx *localConfirmedTx = &confirmedTx[index + i]; + + // if requested tx digest match stored digest tx has been found and is confirmed + if (request->digest == localConfirmedTx->digest) + { + currentTxStatus.executed = 1; + currentTxStatus.notfound = 0; + currentTxStatus.moneyFlew = localConfirmedTx->moneyFlew; + break; + } + } + + enqueueResponse(peer, sizeof(currentTxStatus), RESPOND_TX_STATUS, header->dejavu(), ¤tTxStatus); + } +} \ No newline at end of file diff --git a/src/qubic.cpp b/src/qubic.cpp index a54f7b4c..9e67ca3c 100644 --- a/src/qubic.cpp +++ b/src/qubic.cpp @@ -57,6 +57,7 @@ #define MIN_MINING_SOLUTIONS_PUBLICATION_OFFSET 3 // Must be 3+ #define TIME_ACCURACY 5000 +#include "exchange_connect.h" typedef struct @@ -1429,6 +1430,14 @@ static void requestProcessor(void* ProcedureArgument) processSpecialCommand(peer, header); } break; + + /* qli: Exchange Connect Start */ + case REQUEST_TX_STATUS: + { + processRequestConfirmedTx(peer, header); + } + break; + } queueProcessingNumerator += __rdtsc() - beginningTick; @@ -2089,7 +2098,7 @@ static void processTick(unsigned long long processorNumber) if (nextTickData.epoch == system.epoch) { auto* tsCurrentTickTransactionOffsets = ts.tickTransactionOffsets.getByTickIndex(tickIndex); - + tickTxIndexStart[system.tick - system.initialTick] = numberOfTransactions; // qli: exchange add-on bs->SetMem(entityPendingTransactionIndices, sizeof(entityPendingTransactionIndices), 0); // reset solution task queue score->resetTaskQueue(); @@ -2157,9 +2166,11 @@ static void processTick(unsigned long long processorNumber) entityPendingTransactionIndices[spectrumIndex] = 1; numberOfTransactions++; + tickTxIndexStart[system.tick - system.initialTick + 1] = numberOfTransactions; // qli: exchange add-on if (decreaseEnergy(spectrumIndex, transaction->amount)) { increaseEnergy(transaction->destinationPublicKey, transaction->amount); + saveConfirmedTx(numberOfTransactions - 1, 1, system.tick, nextTickData.transactionDigests[transactionIndex]); // qli: save tx if (transaction->amount) { const QuTransfer quTransfer = { transaction->sourcePublicKey , transaction->destinationPublicKey , transaction->amount }; @@ -2472,6 +2483,10 @@ static void processTick(unsigned long long processorNumber) } } } + else + { + saveConfirmedTx(numberOfTransactions - 1, 0, system.tick, nextTickData.transactionDigests[transactionIndex]); // qli: save tx + } } } else @@ -4191,6 +4206,12 @@ static bool initialize() if (!initTcp4(PORT)) return false; + if (!initExchangeConnect()) + { + logToConsole(L"AllocatePool() failed!"); + return false; + } + beginEpoch2of2(); return true; From b156d35c5721a99a108d67386eccc181a520cb8c Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Wed, 3 Apr 2024 13:16:01 +0200 Subject: [PATCH 07/31] Add support seamless epoch transition --- src/Qubic.vcxproj | 1 + src/Qubic.vcxproj.filters | 1 + src/exchange_connect.h | 204 ++++++++++++++++++++++++++++------- src/qubic.cpp | 15 +-- test/exchange_connect.cpp | 219 ++++++++++++++++++++++++++++++++++++++ test/test.vcxproj | 1 + 6 files changed, 397 insertions(+), 44 deletions(-) create mode 100644 test/exchange_connect.cpp diff --git a/src/Qubic.vcxproj b/src/Qubic.vcxproj index aa1ef7b4..36a5db9d 100644 --- a/src/Qubic.vcxproj +++ b/src/Qubic.vcxproj @@ -27,6 +27,7 @@ + diff --git a/src/Qubic.vcxproj.filters b/src/Qubic.vcxproj.filters index d23fa6ef..358a1a3c 100644 --- a/src/Qubic.vcxproj.filters +++ b/src/Qubic.vcxproj.filters @@ -120,6 +120,7 @@ contract_core + diff --git a/src/exchange_connect.h b/src/exchange_connect.h index ab42fb36..e2c24f33 100644 --- a/src/exchange_connect.h +++ b/src/exchange_connect.h @@ -1,5 +1,15 @@ #pragma once +#include "platform/m256.h" +#include "platform/debugging.h" +#include "platform/memory.h" + +#include "network_messages/header.h" + +#include "public_settings.h" +#include "system.h" + + typedef struct { unsigned int tick; @@ -8,11 +18,17 @@ typedef struct m256i digest; } ConfirmedTx; -// Memory to store confiremd TX's +constexpr unsigned long long confirmedTxCurrentEpochLength = (((unsigned long long)MAX_NUMBER_OF_TICKS_PER_EPOCH) * NUMBER_OF_TRANSACTIONS_PER_TICK / TRANSACTION_SPARSENESS); +constexpr unsigned long long confirmedTxPreviousEpochLength = (((unsigned long long)TICKS_TO_KEEP_FROM_PRIOR_EPOCH) * NUMBER_OF_TRANSACTIONS_PER_TICK / TRANSACTION_SPARSENESS); +constexpr unsigned long long confirmedTxLength = confirmedTxCurrentEpochLength + confirmedTxPreviousEpochLength; + +// Memory to store confirmed TX's (arrays store data of current epoch, followed by last ticks of previous epoch) static ConfirmedTx *confirmedTx = NULL; -static int tickTxCounter[MAX_NUMBER_OF_TICKS_PER_EPOCH]; // store the amount of tx per tick -static long tickTxIndexStart[MAX_NUMBER_OF_TICKS_PER_EPOCH]; // store the index position per tick +static unsigned int tickTxCounter[MAX_NUMBER_OF_TICKS_PER_EPOCH + TICKS_TO_KEEP_FROM_PRIOR_EPOCH]; // store the amount of tx per tick +static unsigned int tickTxIndexStart[MAX_NUMBER_OF_TICKS_PER_EPOCH + TICKS_TO_KEEP_FROM_PRIOR_EPOCH]; // store the index position per tick static volatile char confirmedTxLock = 0; +static unsigned int confirmedTxPreviousEpochBeginTick = 0; // first tick kept from previous epoch (or 0 if no tick from prev. epoch available) +static unsigned int confirmedTxCurrentEpochBeginTick = 0; // first tick of current epoch stored #define REQUEST_TX_STATUS 201 @@ -43,70 +59,182 @@ typedef struct m256i digest; } RespondTxStatus; + +// Allocate buffers static bool initExchangeConnect() { // Allocate pool to store confirmed TX's - if (bs->AllocatePool(EfiRuntimeServicesData, FIRST_TICK_TRANSACTION_OFFSET + (((unsigned long long)MAX_NUMBER_OF_TICKS_PER_EPOCH) * NUMBER_OF_TRANSACTIONS_PER_TICK / TRANSACTION_SPARSENESS) * sizeof(ConfirmedTx), (void**)&confirmedTx) != 0) + if (!allocatePool(confirmedTxLength * sizeof(ConfirmedTx), (void**)&confirmedTx)) return false; - // Init pool with 0 - bs->SetMem(confirmedTx, FIRST_TICK_TRANSACTION_OFFSET + (((unsigned long long)MAX_NUMBER_OF_TICKS_PER_EPOCH) * NUMBER_OF_TRANSACTIONS_PER_TICK / TRANSACTION_SPARSENESS) * sizeof(ConfirmedTx), 0); + confirmedTxPreviousEpochBeginTick = 0; + confirmedTxCurrentEpochBeginTick = 0; return true; } + +// Free buffers +static void deinitExchangeConnect() +{ + if (confirmedTx) + freePool(confirmedTx); +} + + +// Begin new epoch. If not called the first time (seamless transition), assume that the ticks to keep +// are ticks in [newInitialTick-TICKS_TO_KEEP_FROM_PRIOR_EPOCH, newInitialTick-1]. +static void beginEpochExchangeConnect(unsigned int newInitialTick) +{ + unsigned int& tickBegin = confirmedTxCurrentEpochBeginTick; + unsigned int& oldTickBegin = confirmedTxPreviousEpochBeginTick; + + bool keepTicks = tickBegin && newInitialTick > tickBegin && newInitialTick < tickBegin + MAX_NUMBER_OF_TICKS_PER_EPOCH; + if (keepTicks) + { + // Seamless epoch transition: keep some ticks of prior epoch + // The number of ticks to keep is limited by: + // - the length of the previous epoch tick buffer (tickCount < TICKS_TO_KEEP_FROM_PRIOR_EPOCH) + // - the length of the previous epoch confirmedTx array (confirmedTxPreviousEpochLength) + // - the number of ticks available from in ended epoch (tickIndex >= 0) + unsigned int tickCount = 0; + unsigned int txCount = 0; + for (int tickIndex = (newInitialTick - 1) - tickBegin; tickIndex >= 0; --tickIndex) + { + unsigned int newTxCount = txCount + tickTxCounter[tickIndex]; + if (newTxCount <= confirmedTxPreviousEpochLength && tickCount < TICKS_TO_KEEP_FROM_PRIOR_EPOCH) + { + txCount = newTxCount; + ++tickCount; + } + else + { + break; + } + } + + oldTickBegin = newInitialTick - tickCount; + const unsigned int oldTickEnd = newInitialTick; + const unsigned int tickIndex = oldTickBegin - tickBegin; + + // copy confirmedTx and tickTxCounter from recently ended epoch into storage of previous epoch + copyMem(tickTxCounter + MAX_NUMBER_OF_TICKS_PER_EPOCH, tickTxCounter + tickIndex, tickCount * sizeof(tickTxCounter[0])); + copyMem(confirmedTx + confirmedTxCurrentEpochLength, confirmedTx + tickTxIndexStart[tickIndex], txCount * sizeof(confirmedTx[0])); + + // copy adjusted tickTxIndexStart + const unsigned int indexStartDelta = confirmedTxCurrentEpochLength - tickTxIndexStart[tickIndex]; + for (unsigned int tickOffset = 0; tickOffset < tickCount; ++tickOffset) + { + tickTxIndexStart[MAX_NUMBER_OF_TICKS_PER_EPOCH + tickOffset] = tickTxIndexStart[tickIndex + tickOffset] + indexStartDelta; + } + + // init pool of current epoch with 0 + setMem(confirmedTx, confirmedTxCurrentEpochLength * sizeof(ConfirmedTx), 0); + setMem(tickTxCounter, MAX_NUMBER_OF_TICKS_PER_EPOCH * sizeof(tickTxCounter[0]), 0); + setMem(tickTxIndexStart, MAX_NUMBER_OF_TICKS_PER_EPOCH * sizeof(tickTxIndexStart[0]), 0); + } + else + { + // node startup with no data of prior epoch or no ticks to keep + oldTickBegin = 0; + + // init pool with 0 + setMem(confirmedTx, confirmedTxLength * sizeof(ConfirmedTx), 0); + setMem(tickTxCounter, sizeof(tickTxCounter), 0); + setMem(tickTxIndexStart, sizeof(tickTxIndexStart), 0); + } + + tickBegin = newInitialTick; +} + + // adds a tx to the confirmed tx store // txNumberMinusOne: the current tx number -1 // moneyFlew: if money has been flow -// tick: tick in which this tx was +// tick: tick of tx (needs to be in range of current epoch) // digest: digest of tx -static void saveConfirmedTx(unsigned int txNumberMinusOne, unsigned char moneyFlew, unsigned int tick, m256i digest) +static bool saveConfirmedTx(unsigned int txNumberMinusOne, unsigned char moneyFlew, unsigned int tick, const m256i& digest) { + ASSERT(confirmedTxCurrentEpochBeginTick == system.initialTick); + ASSERT(tick >= system.initialTick && tick < system.initialTick + MAX_NUMBER_OF_TICKS_PER_EPOCH); + + // skip if confirmedTx storage is full + if (txNumberMinusOne >= confirmedTxCurrentEpochLength) + return false; + ACQUIRE(confirmedTxLock); - int tickNumber = tick - system.initialTick; // get current tick number in epoch + + // set confirmedTx data ConfirmedTx & txConfirmation = confirmedTx[txNumberMinusOne]; txConfirmation.tick = tick; txConfirmation.moneyFlew = moneyFlew; txConfirmation.digest = digest; + + // get current tick number in epoch + int tickIndex = tick - system.initialTick; // keep track of tx number in tick to find it later easier - tickTxCounter[tickNumber]++; + tickTxCounter[tickIndex]++; + RELEASE(confirmedTxLock); + + return true; } + static void processRequestConfirmedTx(Peer *peer, RequestResponseHeader *header) { + ASSERT(confirmedTxCurrentEpochBeginTick == system.initialTick); + ASSERT(system.tick >= system.initialTick); RequestTxStatus *request = header->getPayload(); - RespondTxStatus currentTxStatus = {}; + // only send a response if the node is in higher tick than the requested tx + if (request->tick >= system.tick) + return; + + int tickIndex; + if (request->tick >= confirmedTxCurrentEpochBeginTick && request->tick < confirmedTxCurrentEpochBeginTick + MAX_NUMBER_OF_TICKS_PER_EPOCH) + { + // current epoch + tickIndex = request->tick - confirmedTxCurrentEpochBeginTick; + } + else if (confirmedTxPreviousEpochBeginTick != 0 && request->tick >= confirmedTxPreviousEpochBeginTick && request->tick < confirmedTxCurrentEpochBeginTick) + { + // available tick of previous epoch (stored behind current epoch data) + tickIndex = request->tick - confirmedTxPreviousEpochBeginTick + MAX_NUMBER_OF_TICKS_PER_EPOCH; + } + else + { + // tick not available + return; + } - int tickNumber = request->tick - system.initialTick; - // get index where confirmedtx are starting to be stored in memory - int index = tickTxIndexStart[tickNumber]; + // get index where confirmedTx are starting to be stored in memory + int index = tickTxIndexStart[tickIndex]; - // only send a response if the node is in higher tick than the requested tx - if (request->tick < system.tick) + RespondTxStatus currentTxStatus = {}; + currentTxStatus.currentTickOfNode = system.tick; + currentTxStatus.tickOfTx = request->tick; + currentTxStatus.executed = 0; + currentTxStatus.moneyFlew = 0; + currentTxStatus.notfound = 1; + currentTxStatus.digest = request->digest; + + // loop over the number of tx which were stored for the given tick + for (unsigned int i = 0; i < tickTxCounter[tickIndex]; i++) { - currentTxStatus.currentTickOfNode = system.tick; - currentTxStatus.tickOfTx = request->tick; - currentTxStatus.executed = 0; - currentTxStatus.moneyFlew = 0; - currentTxStatus.notfound = 1; - currentTxStatus.digest = request->digest; - - // loop over the number of tx which were stored for the given tick - for (int i = 0; i < tickTxCounter[tickNumber]; i++) - { - ConfirmedTx *localConfirmedTx = &confirmedTx[index + i]; + ConfirmedTx& localConfirmedTx = confirmedTx[index + i]; - // if requested tx digest match stored digest tx has been found and is confirmed - if (request->digest == localConfirmedTx->digest) - { - currentTxStatus.executed = 1; - currentTxStatus.notfound = 0; - currentTxStatus.moneyFlew = localConfirmedTx->moneyFlew; - break; - } - } + ASSERT(index + i < confirmedTxLength); + ASSERT(localConfirmedTx.tick == request->tick); - enqueueResponse(peer, sizeof(currentTxStatus), RESPOND_TX_STATUS, header->dejavu(), ¤tTxStatus); + // if requested tx digest match stored digest tx has been found and is confirmed + if (request->digest == localConfirmedTx.digest) + { + currentTxStatus.executed = 1; + currentTxStatus.notfound = 0; + currentTxStatus.moneyFlew = localConfirmedTx.moneyFlew; + break; + } } -} \ No newline at end of file + + enqueueResponse(peer, sizeof(currentTxStatus), RESPOND_TX_STATUS, header->dejavu(), ¤tTxStatus); +} diff --git a/src/qubic.cpp b/src/qubic.cpp index 9e67ca3c..64eee54c 100644 --- a/src/qubic.cpp +++ b/src/qubic.cpp @@ -2712,6 +2712,7 @@ static void beginEpoch1of2() #ifndef NDEBUG ts.checkStateConsistencyWithAssert(); #endif + beginEpochExchangeConnect(system.initialTick); for (unsigned int i = 0; i < SPECTRUM_CAPACITY; i++) { @@ -4003,6 +4004,12 @@ static bool initialize() if (!initLogging()) return false; + if (!initExchangeConnect()) + { + logToConsole(L"initExchangeConnect() failed!"); + return false; + } + logToConsole(L"Loading system file ..."); bs->SetMem(&system, sizeof(system), 0); load(SYSTEM_FILE_NAME, sizeof(system), (unsigned char*)&system); @@ -4206,12 +4213,6 @@ static bool initialize() if (!initTcp4(PORT)) return false; - if (!initExchangeConnect()) - { - logToConsole(L"AllocatePool() failed!"); - return false; - } - beginEpoch2of2(); return true; @@ -4249,6 +4250,8 @@ static void deinitialize() deinitLogging(); + deinitExchangeConnect(); + for (unsigned int processorIndex = 0; processorIndex < MAX_NUMBER_OF_PROCESSORS; processorIndex++) { if (contractFunctionOutputs[processorIndex]) diff --git a/test/exchange_connect.cpp b/test/exchange_connect.cpp new file mode 100644 index 00000000..9c6c97ec --- /dev/null +++ b/test/exchange_connect.cpp @@ -0,0 +1,219 @@ +#define NO_UEFI + +#include "gtest/gtest.h" + +#define system systemStructInstance +#define Peer void +void enqueueResponse(Peer* peer, unsigned int dataSize, unsigned char type, unsigned int dejavu, const void* data); + +#include "../src/public_settings.h" +#undef MAX_NUMBER_OF_TICKS_PER_EPOCH +#define MAX_NUMBER_OF_TICKS_PER_EPOCH 50 +#undef TICKS_TO_KEEP_FROM_PRIOR_EPOCH +#define TICKS_TO_KEEP_FROM_PRIOR_EPOCH 32 +#include "../src/exchange_connect.h" + +#include + +unsigned int numberOfTransactions = 0; + + +// Add tick with random transactions, return whether all transactions could be stored +static bool addTick(unsigned int tick, unsigned long long seed, unsigned short maxTransactions) +{ + system.tick = tick; + + // set start index of tick transactions + tickTxIndexStart[system.tick - system.initialTick] = numberOfTransactions; + + // use pseudo-random sequence + std::mt19937_64 gen64(seed); + + // add transactions of tick + unsigned int transactionNum = gen64() % (maxTransactions + 1); + for (unsigned int transaction = 0; transaction < transactionNum; ++transaction) + { + ++numberOfTransactions; + m256i digest(gen64(), gen64(), gen64(), gen64()); + if (!saveConfirmedTx(numberOfTransactions - 1, gen64() % 2, system.tick, digest)) + return false; + } + + return true; +} + + +struct { + RequestResponseHeader header; + RequestTxStatus payload; +} requestMessage; + +int expectedMoneyFlew = 0; +bool expectedToBeFound = true; + + +static void enqueueResponse(Peer* peer, unsigned int dataSize, unsigned char type, unsigned int dejavu, const void* data) +{ + EXPECT_EQ(type, RESPOND_TX_STATUS); + EXPECT_EQ(dejavu, requestMessage.header.dejavu()); + EXPECT_EQ(dataSize, sizeof(RespondTxStatus)); + + const RespondTxStatus* txStatus = (const RespondTxStatus*)data; + EXPECT_EQ(txStatus->digest, requestMessage.payload.digest); + EXPECT_EQ(txStatus->tickOfTx, requestMessage.payload.tick); + if (expectedToBeFound) + { + EXPECT_EQ(txStatus->moneyFlew, expectedMoneyFlew); + EXPECT_EQ(txStatus->notfound, 0); + EXPECT_EQ(txStatus->executed, 1); + } +} + +static void checkTick(unsigned int tick, unsigned long long seed, unsigned short maxTransactions, bool fullyStoredTick, bool previousEpoch) +{ + // Ensure that we do not skip processRequestConfirmedTx() + if (system.tick <= tick) + system.tick = tick + 1; + + // use pseudo-random sequence + std::mt19937_64 gen64(seed); + + requestMessage.header.checkAndSetSize(sizeof(requestMessage)); + requestMessage.header.setType(REQUEST_TX_STATUS); + requestMessage.header.setDejavu(seed % UINT_MAX); + requestMessage.payload.tick = tick; + + expectedToBeFound = fullyStoredTick; + + // check transactions of tick + unsigned int transactionNum = gen64() % (maxTransactions + 1); + for (unsigned int transaction = 0; transaction < transactionNum; ++transaction) + { + requestMessage.payload.digest = m256i(gen64(), gen64(), gen64(), gen64()); + expectedMoneyFlew = gen64() % 2; + processRequestConfirmedTx(nullptr, &requestMessage.header); + } + + unsigned int tickIndex; + if (previousEpoch) + { + ASSERT(confirmedTxPreviousEpochBeginTick != 0); + EXPECT_LT(tick, confirmedTxCurrentEpochBeginTick); + if (tick < confirmedTxPreviousEpochBeginTick) + { + // tick not available -> okay + return; + } + tickIndex = tick - confirmedTxPreviousEpochBeginTick + MAX_NUMBER_OF_TICKS_PER_EPOCH; + } + else + { + ASSERT(tick >= confirmedTxCurrentEpochBeginTick && tick < confirmedTxCurrentEpochBeginTick + MAX_NUMBER_OF_TICKS_PER_EPOCH); + tickIndex = tick - confirmedTxCurrentEpochBeginTick; + } + if (fullyStoredTick) + { + EXPECT_EQ(tickTxCounter[tickIndex], transactionNum); + } + else + { + EXPECT_LE(tickTxCounter[tickIndex], transactionNum); + } +} + + +TEST(TestCoreExchangeConnect, EpochTransition) +{ + unsigned long long seed = 42; + + // use pseudo-random sequence + std::mt19937_64 gen64(seed); + + // 5x test with running 2 epoch transitions + for (int testIdx = 0; testIdx < 6; ++testIdx) + { + // first, test case of having no transactions, then of having few transaction, later of having many transactions + unsigned short maxTransactions = 0; + if (testIdx == 1) + maxTransactions = NUMBER_OF_TRANSACTIONS_PER_TICK / 4; + else if (testIdx > 1) + maxTransactions = NUMBER_OF_TRANSACTIONS_PER_TICK; + + initExchangeConnect(); + + const int firstEpochTicks = gen64() % (MAX_NUMBER_OF_TICKS_PER_EPOCH + 1); + const int secondEpochTicks = gen64() % (MAX_NUMBER_OF_TICKS_PER_EPOCH + 1); + const int thirdEpochTicks = gen64() % (MAX_NUMBER_OF_TICKS_PER_EPOCH + 1); + const unsigned int firstEpochTick0 = gen64() % 10000000; + const unsigned int secondEpochTick0 = firstEpochTick0 + firstEpochTicks; + const unsigned int thirdEpochTick0 = secondEpochTick0 + secondEpochTicks; + unsigned long long firstEpochSeeds[MAX_NUMBER_OF_TICKS_PER_EPOCH]; + unsigned long long secondEpochSeeds[MAX_NUMBER_OF_TICKS_PER_EPOCH]; + unsigned long long thirdEpochSeeds[MAX_NUMBER_OF_TICKS_PER_EPOCH]; + for (int i = 0; i < firstEpochTicks; ++i) + firstEpochSeeds[i] = gen64(); + for (int i = 0; i < secondEpochTicks; ++i) + secondEpochSeeds[i] = gen64(); + for (int i = 0; i < thirdEpochTicks; ++i) + thirdEpochSeeds[i] = gen64(); + + // first epoch + numberOfTransactions = 0; + system.initialTick = firstEpochTick0; + beginEpochExchangeConnect(firstEpochTick0); + + // add ticks + int firstEpochLastFullyStoredTick = -1; + for (int i = 0; i < firstEpochTicks; ++i) + { + if (addTick(firstEpochTick0 + i, firstEpochSeeds[i], maxTransactions)) + firstEpochLastFullyStoredTick = i; + } + + // check ticks + bool previousEpoch = true; + for (int i = 0; i < firstEpochTicks; ++i) + checkTick(firstEpochTick0 + i, firstEpochSeeds[i], maxTransactions, i < firstEpochLastFullyStoredTick, !previousEpoch); + + // Epoch transistion + numberOfTransactions = 0; + system.initialTick = secondEpochTick0; + beginEpochExchangeConnect(secondEpochTick0); + + // add ticks + int secondEpochLastFullyStoredTick = -1; + for (int i = 0; i < secondEpochTicks; ++i) + { + if (addTick(secondEpochTick0 + i, secondEpochSeeds[i], maxTransactions)) + secondEpochLastFullyStoredTick = i; + } + + // check ticks + for (int i = 0; i < secondEpochTicks; ++i) + checkTick(secondEpochTick0 + i, secondEpochSeeds[i], maxTransactions, i < secondEpochLastFullyStoredTick, !previousEpoch); + for (int i = 0; i < firstEpochTicks; ++i) + checkTick(firstEpochTick0 + i, firstEpochSeeds[i], maxTransactions, i < firstEpochLastFullyStoredTick, previousEpoch); + + // Epoch transistion + numberOfTransactions = 0; + system.initialTick = thirdEpochTick0; + beginEpochExchangeConnect(thirdEpochTick0); + + // add ticks + int thirdEpochLastFullyStoredTick = -1; + for (int i = 0; i < thirdEpochTicks; ++i) + { + if (addTick(thirdEpochTick0 + i, thirdEpochSeeds[i], maxTransactions)) + thirdEpochLastFullyStoredTick = i; + } + + // check ticks + for (int i = 0; i < thirdEpochTicks; ++i) + checkTick(thirdEpochTick0 + i, thirdEpochSeeds[i], maxTransactions, i < thirdEpochLastFullyStoredTick, !previousEpoch); + for (int i = 0; i < secondEpochTicks; ++i) + checkTick(secondEpochTick0 + i, secondEpochSeeds[i], maxTransactions, i < secondEpochLastFullyStoredTick, previousEpoch); + + deinitExchangeConnect(); + } +} + diff --git a/test/test.vcxproj b/test/test.vcxproj index fff24db6..201d5057 100644 --- a/test/test.vcxproj +++ b/test/test.vcxproj @@ -61,6 +61,7 @@ + From 95ece26620aee9d59237e3cd55b9100102c4f694 Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Thu, 4 Apr 2024 14:13:41 +0200 Subject: [PATCH 08/31] Remove signature from RequestTxStatus --- src/exchange_connect.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/exchange_connect.h b/src/exchange_connect.h index e2c24f33..30cab71d 100644 --- a/src/exchange_connect.h +++ b/src/exchange_connect.h @@ -38,13 +38,11 @@ typedef struct { unsigned int tick; m256i digest; - // char digest[32]; - unsigned char signature[64]; } RequestTxStatus; #pragma pack(pop) static_assert(sizeof(m256i) == 32, ""); -static_assert(sizeof(RequestTxStatus) == 100, "unexpected size"); +static_assert(sizeof(RequestTxStatus) == 36, "unexpected size"); #define RESPOND_TX_STATUS 202 From 5824dae91bdec6239e044357a74712b603c8bbe9 Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Thu, 4 Apr 2024 14:09:38 +0200 Subject: [PATCH 09/31] Rename files to tx_status_request --- src/Qubic.vcxproj | 2 +- src/Qubic.vcxproj.filters | 2 +- src/qubic.cpp | 19 ++++++++++--------- ...exchange_connect.h => tx_status_request.h} | 8 +++++--- test/test.vcxproj | 2 +- ...ange_connect.cpp => tx_status_request.cpp} | 14 +++++++------- 6 files changed, 25 insertions(+), 22 deletions(-) rename src/{exchange_connect.h => tx_status_request.h} (94%) rename test/{exchange_connect.cpp => tx_status_request.cpp} (93%) diff --git a/src/Qubic.vcxproj b/src/Qubic.vcxproj index 36a5db9d..b91c32af 100644 --- a/src/Qubic.vcxproj +++ b/src/Qubic.vcxproj @@ -27,7 +27,7 @@ - + diff --git a/src/Qubic.vcxproj.filters b/src/Qubic.vcxproj.filters index 358a1a3c..8fea4b5b 100644 --- a/src/Qubic.vcxproj.filters +++ b/src/Qubic.vcxproj.filters @@ -120,7 +120,7 @@ contract_core - + diff --git a/src/qubic.cpp b/src/qubic.cpp index 64eee54c..5872d94c 100644 --- a/src/qubic.cpp +++ b/src/qubic.cpp @@ -36,6 +36,8 @@ #include "tick_storage.h" +#include "tx_status_request.h" + ////////// Qubic \\\\\\\\\\ @@ -57,8 +59,6 @@ #define MIN_MINING_SOLUTIONS_PUBLICATION_OFFSET 3 // Must be 3+ #define TIME_ACCURACY 5000 -#include "exchange_connect.h" - typedef struct { @@ -1431,7 +1431,7 @@ static void requestProcessor(void* ProcedureArgument) } break; - /* qli: Exchange Connect Start */ + /* qli: process RequestTxStatus message */ case REQUEST_TX_STATUS: { processRequestConfirmedTx(peer, header); @@ -2098,7 +2098,7 @@ static void processTick(unsigned long long processorNumber) if (nextTickData.epoch == system.epoch) { auto* tsCurrentTickTransactionOffsets = ts.tickTransactionOffsets.getByTickIndex(tickIndex); - tickTxIndexStart[system.tick - system.initialTick] = numberOfTransactions; // qli: exchange add-on + tickTxIndexStart[system.tick - system.initialTick] = numberOfTransactions; // qli: part of tx_status_request add-on bs->SetMem(entityPendingTransactionIndices, sizeof(entityPendingTransactionIndices), 0); // reset solution task queue score->resetTaskQueue(); @@ -2166,7 +2166,7 @@ static void processTick(unsigned long long processorNumber) entityPendingTransactionIndices[spectrumIndex] = 1; numberOfTransactions++; - tickTxIndexStart[system.tick - system.initialTick + 1] = numberOfTransactions; // qli: exchange add-on + tickTxIndexStart[system.tick - system.initialTick + 1] = numberOfTransactions; // qli: part of tx_status_request add-on if (decreaseEnergy(spectrumIndex, transaction->amount)) { increaseEnergy(transaction->destinationPublicKey, transaction->amount); @@ -2712,7 +2712,7 @@ static void beginEpoch1of2() #ifndef NDEBUG ts.checkStateConsistencyWithAssert(); #endif - beginEpochExchangeConnect(system.initialTick); + beginEpochTxStatusRequestAddOn(system.initialTick); for (unsigned int i = 0; i < SPECTRUM_CAPACITY; i++) { @@ -4004,9 +4004,10 @@ static bool initialize() if (!initLogging()) return false; - if (!initExchangeConnect()) + // qli: init tx_status_request + if (!initTxStatusRequestAddOn()) { - logToConsole(L"initExchangeConnect() failed!"); + logToConsole(L"initTxStatusRequestAddOn() failed!"); return false; } @@ -4250,7 +4251,7 @@ static void deinitialize() deinitLogging(); - deinitExchangeConnect(); + deinitTxStatusRequestAddOn(); for (unsigned int processorIndex = 0; processorIndex < MAX_NUMBER_OF_PROCESSORS; processorIndex++) { diff --git a/src/exchange_connect.h b/src/tx_status_request.h similarity index 94% rename from src/exchange_connect.h rename to src/tx_status_request.h index 30cab71d..52fc2942 100644 --- a/src/exchange_connect.h +++ b/src/tx_status_request.h @@ -1,3 +1,5 @@ +// This is an extension by qli allowing to query transaction status with RequestTxStatus message + #pragma once #include "platform/m256.h" @@ -59,7 +61,7 @@ typedef struct // Allocate buffers -static bool initExchangeConnect() +static bool initTxStatusRequestAddOn() { // Allocate pool to store confirmed TX's if (!allocatePool(confirmedTxLength * sizeof(ConfirmedTx), (void**)&confirmedTx)) @@ -71,7 +73,7 @@ static bool initExchangeConnect() // Free buffers -static void deinitExchangeConnect() +static void deinitTxStatusRequestAddOn() { if (confirmedTx) freePool(confirmedTx); @@ -80,7 +82,7 @@ static void deinitExchangeConnect() // Begin new epoch. If not called the first time (seamless transition), assume that the ticks to keep // are ticks in [newInitialTick-TICKS_TO_KEEP_FROM_PRIOR_EPOCH, newInitialTick-1]. -static void beginEpochExchangeConnect(unsigned int newInitialTick) +static void beginEpochTxStatusRequestAddOn(unsigned int newInitialTick) { unsigned int& tickBegin = confirmedTxCurrentEpochBeginTick; unsigned int& oldTickBegin = confirmedTxPreviousEpochBeginTick; diff --git a/test/test.vcxproj b/test/test.vcxproj index 201d5057..184f90e9 100644 --- a/test/test.vcxproj +++ b/test/test.vcxproj @@ -61,7 +61,7 @@ - + diff --git a/test/exchange_connect.cpp b/test/tx_status_request.cpp similarity index 93% rename from test/exchange_connect.cpp rename to test/tx_status_request.cpp index 9c6c97ec..a883874a 100644 --- a/test/exchange_connect.cpp +++ b/test/tx_status_request.cpp @@ -11,7 +11,7 @@ void enqueueResponse(Peer* peer, unsigned int dataSize, unsigned char type, unsi #define MAX_NUMBER_OF_TICKS_PER_EPOCH 50 #undef TICKS_TO_KEEP_FROM_PRIOR_EPOCH #define TICKS_TO_KEEP_FROM_PRIOR_EPOCH 32 -#include "../src/exchange_connect.h" +#include "../src/tx_status_request.h" #include @@ -122,7 +122,7 @@ static void checkTick(unsigned int tick, unsigned long long seed, unsigned short } -TEST(TestCoreExchangeConnect, EpochTransition) +TEST(TestCoreTxStatusRequestAddOn, EpochTransition) { unsigned long long seed = 42; @@ -139,7 +139,7 @@ TEST(TestCoreExchangeConnect, EpochTransition) else if (testIdx > 1) maxTransactions = NUMBER_OF_TRANSACTIONS_PER_TICK; - initExchangeConnect(); + initTxStatusRequestAddOn(); const int firstEpochTicks = gen64() % (MAX_NUMBER_OF_TICKS_PER_EPOCH + 1); const int secondEpochTicks = gen64() % (MAX_NUMBER_OF_TICKS_PER_EPOCH + 1); @@ -160,7 +160,7 @@ TEST(TestCoreExchangeConnect, EpochTransition) // first epoch numberOfTransactions = 0; system.initialTick = firstEpochTick0; - beginEpochExchangeConnect(firstEpochTick0); + beginEpochTxStatusRequestAddOn(firstEpochTick0); // add ticks int firstEpochLastFullyStoredTick = -1; @@ -178,7 +178,7 @@ TEST(TestCoreExchangeConnect, EpochTransition) // Epoch transistion numberOfTransactions = 0; system.initialTick = secondEpochTick0; - beginEpochExchangeConnect(secondEpochTick0); + beginEpochTxStatusRequestAddOn(secondEpochTick0); // add ticks int secondEpochLastFullyStoredTick = -1; @@ -197,7 +197,7 @@ TEST(TestCoreExchangeConnect, EpochTransition) // Epoch transistion numberOfTransactions = 0; system.initialTick = thirdEpochTick0; - beginEpochExchangeConnect(thirdEpochTick0); + beginEpochTxStatusRequestAddOn(thirdEpochTick0); // add ticks int thirdEpochLastFullyStoredTick = -1; @@ -213,7 +213,7 @@ TEST(TestCoreExchangeConnect, EpochTransition) for (int i = 0; i < secondEpochTicks; ++i) checkTick(secondEpochTick0 + i, secondEpochSeeds[i], maxTransactions, i < secondEpochLastFullyStoredTick, previousEpoch); - deinitExchangeConnect(); + deinitTxStatusRequestAddOn(); } } From 94342d6963a5d0653f8b0d71b56062d9fc257fda Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Thu, 4 Apr 2024 19:57:36 +0200 Subject: [PATCH 10/31] Change tx status request to tick level instead of transaction level --- src/tx_status_request.h | 67 ++++++++++++++++---------------- test/tx_status_request.cpp | 79 ++++++++++++++++++++------------------ 2 files changed, 74 insertions(+), 72 deletions(-) diff --git a/src/tx_status_request.h b/src/tx_status_request.h index 52fc2942..e343b9c8 100644 --- a/src/tx_status_request.h +++ b/src/tx_status_request.h @@ -34,30 +34,33 @@ static unsigned int confirmedTxCurrentEpochBeginTick = 0; // first tick of cur #define REQUEST_TX_STATUS 201 -// Use "#pragma pack" keep the binary struct compatibility after changing from unsigned char [32] to m256i -#pragma pack(push,1) -typedef struct +struct RequestTxStatus { unsigned int tick; - m256i digest; -} RequestTxStatus; -#pragma pack(pop) +}; -static_assert(sizeof(m256i) == 32, ""); -static_assert(sizeof(RequestTxStatus) == 36, "unexpected size"); +static_assert(sizeof(RequestTxStatus) == 4, "unexpected size"); #define RESPOND_TX_STATUS 202 -typedef struct +#pragma pack(push, 1) +struct RespondTxStatus { unsigned int currentTickOfNode; - unsigned int tickOfTx; - unsigned char moneyFlew; - unsigned char executed; - unsigned char notfound; - unsigned char _padding[5]; - m256i digest; -} RespondTxStatus; + unsigned int tick; + unsigned int txCount; + unsigned char moneyFlew[(NUMBER_OF_TRANSACTIONS_PER_TICK + 7) / 8]; + + // only txCount digests are sent with this message, so only read the first txCount digests when using this as a view to the received data + m256i txDigests[NUMBER_OF_TRANSACTIONS_PER_TICK]; + + // return size of this struct to be sent (last txDigests are 0 and do not need to be sent) + unsigned int size() const + { + return offsetof(RespondTxStatus, txDigests) + txCount * sizeof(m256i); + } +}; +#pragma pack(pop) // Allocate buffers @@ -87,7 +90,7 @@ static void beginEpochTxStatusRequestAddOn(unsigned int newInitialTick) unsigned int& tickBegin = confirmedTxCurrentEpochBeginTick; unsigned int& oldTickBegin = confirmedTxPreviousEpochBeginTick; - bool keepTicks = tickBegin && newInitialTick > tickBegin && newInitialTick < tickBegin + MAX_NUMBER_OF_TICKS_PER_EPOCH; + bool keepTicks = tickBegin && newInitialTick > tickBegin && newInitialTick <= tickBegin + MAX_NUMBER_OF_TICKS_PER_EPOCH; if (keepTicks) { // Seamless epoch transition: keep some ticks of prior epoch @@ -210,31 +213,27 @@ static void processRequestConfirmedTx(Peer *peer, RequestResponseHeader *header) // get index where confirmedTx are starting to be stored in memory int index = tickTxIndexStart[tickIndex]; - RespondTxStatus currentTxStatus = {}; - currentTxStatus.currentTickOfNode = system.tick; - currentTxStatus.tickOfTx = request->tick; - currentTxStatus.executed = 0; - currentTxStatus.moneyFlew = 0; - currentTxStatus.notfound = 1; - currentTxStatus.digest = request->digest; + // init response message data + RespondTxStatus tickTxStatus; + tickTxStatus.currentTickOfNode = system.tick; + tickTxStatus.tick = request->tick; + tickTxStatus.txCount = tickTxCounter[tickIndex]; + setMem(&tickTxStatus.moneyFlew, sizeof(tickTxStatus.moneyFlew), 0); // loop over the number of tx which were stored for the given tick - for (unsigned int i = 0; i < tickTxCounter[tickIndex]; i++) + ASSERT(tickTxStatus.txCount <= NUMBER_OF_TRANSACTIONS_PER_TICK); + for (unsigned int i = 0; i < tickTxStatus.txCount; i++) { ConfirmedTx& localConfirmedTx = confirmedTx[index + i]; ASSERT(index + i < confirmedTxLength); ASSERT(localConfirmedTx.tick == request->tick); + ASSERT(localConfirmedTx.moneyFlew == 1 || localConfirmedTx.moneyFlew == 0); - // if requested tx digest match stored digest tx has been found and is confirmed - if (request->digest == localConfirmedTx.digest) - { - currentTxStatus.executed = 1; - currentTxStatus.notfound = 0; - currentTxStatus.moneyFlew = localConfirmedTx.moneyFlew; - break; - } + tickTxStatus.txDigests[i] = localConfirmedTx.digest; + tickTxStatus.moneyFlew[i >> 3] |= (localConfirmedTx.moneyFlew << (i & 7)); } - enqueueResponse(peer, sizeof(currentTxStatus), RESPOND_TX_STATUS, header->dejavu(), ¤tTxStatus); + ASSERT(tickTxStatus.size() <= sizeof(tickTxStatus)); + enqueueResponse(peer, tickTxStatus.size(), RESPOND_TX_STATUS, header->dejavu(), &tickTxStatus); } diff --git a/test/tx_status_request.cpp b/test/tx_status_request.cpp index a883874a..4bea67ce 100644 --- a/test/tx_status_request.cpp +++ b/test/tx_status_request.cpp @@ -26,7 +26,7 @@ static bool addTick(unsigned int tick, unsigned long long seed, unsigned short m // set start index of tick transactions tickTxIndexStart[system.tick - system.initialTick] = numberOfTransactions; - // use pseudo-random sequence + // use pseudo-random sequence for generating test data std::mt19937_64 gen64(seed); // add transactions of tick @@ -48,25 +48,18 @@ struct { RequestTxStatus payload; } requestMessage; -int expectedMoneyFlew = 0; -bool expectedToBeFound = true; +RespondTxStatus responseMessage; static void enqueueResponse(Peer* peer, unsigned int dataSize, unsigned char type, unsigned int dejavu, const void* data) { + const RespondTxStatus* txStatus = (const RespondTxStatus*)data; + EXPECT_EQ(type, RESPOND_TX_STATUS); EXPECT_EQ(dejavu, requestMessage.header.dejavu()); - EXPECT_EQ(dataSize, sizeof(RespondTxStatus)); + EXPECT_EQ(dataSize, txStatus->size()); - const RespondTxStatus* txStatus = (const RespondTxStatus*)data; - EXPECT_EQ(txStatus->digest, requestMessage.payload.digest); - EXPECT_EQ(txStatus->tickOfTx, requestMessage.payload.tick); - if (expectedToBeFound) - { - EXPECT_EQ(txStatus->moneyFlew, expectedMoneyFlew); - EXPECT_EQ(txStatus->notfound, 0); - EXPECT_EQ(txStatus->executed, 1); - } + copyMem(&responseMessage, txStatus, txStatus->size()); } static void checkTick(unsigned int tick, unsigned long long seed, unsigned short maxTransactions, bool fullyStoredTick, bool previousEpoch) @@ -75,26 +68,7 @@ static void checkTick(unsigned int tick, unsigned long long seed, unsigned short if (system.tick <= tick) system.tick = tick + 1; - // use pseudo-random sequence - std::mt19937_64 gen64(seed); - - requestMessage.header.checkAndSetSize(sizeof(requestMessage)); - requestMessage.header.setType(REQUEST_TX_STATUS); - requestMessage.header.setDejavu(seed % UINT_MAX); - requestMessage.payload.tick = tick; - - expectedToBeFound = fullyStoredTick; - - // check transactions of tick - unsigned int transactionNum = gen64() % (maxTransactions + 1); - for (unsigned int transaction = 0; transaction < transactionNum; ++transaction) - { - requestMessage.payload.digest = m256i(gen64(), gen64(), gen64(), gen64()); - expectedMoneyFlew = gen64() % 2; - processRequestConfirmedTx(nullptr, &requestMessage.header); - } - - unsigned int tickIndex; + // check tick number dependent on if it is previous epoch if (previousEpoch) { ASSERT(confirmedTxPreviousEpochBeginTick != 0); @@ -104,20 +78,49 @@ static void checkTick(unsigned int tick, unsigned long long seed, unsigned short // tick not available -> okay return; } - tickIndex = tick - confirmedTxPreviousEpochBeginTick + MAX_NUMBER_OF_TICKS_PER_EPOCH; } else { ASSERT(tick >= confirmedTxCurrentEpochBeginTick && tick < confirmedTxCurrentEpochBeginTick + MAX_NUMBER_OF_TICKS_PER_EPOCH); - tickIndex = tick - confirmedTxCurrentEpochBeginTick; } + + // prepare request message + requestMessage.header.checkAndSetSize(sizeof(requestMessage)); + requestMessage.header.setType(REQUEST_TX_STATUS); + requestMessage.header.setDejavu(seed % UINT_MAX); + requestMessage.payload.tick = tick; + + // get status of tick transactions + responseMessage.tick = responseMessage.currentTickOfNode = 0; + processRequestConfirmedTx(nullptr, &requestMessage.header); + + // check that we received right data + EXPECT_EQ(responseMessage.tick, tick); + EXPECT_EQ(responseMessage.currentTickOfNode, system.tick); + + // use pseudo-random sequence for generating test data + std::mt19937_64 gen64(seed); + + // check number of transactions + unsigned int transactionNum = gen64() % (maxTransactions + 1); if (fullyStoredTick) { - EXPECT_EQ(tickTxCounter[tickIndex], transactionNum); + EXPECT_EQ(responseMessage.txCount, transactionNum); } else { - EXPECT_LE(tickTxCounter[tickIndex], transactionNum); + EXPECT_LE(responseMessage.txCount, transactionNum); + } + + // check tick's transaction digests and money flows + for (unsigned int transaction = 0; transaction < responseMessage.txCount; ++transaction) + { + m256i digest(gen64(), gen64(), gen64(), gen64()); + EXPECT_EQ(responseMessage.txDigests[transaction], digest); // CAUTION: responseMessage.txDigests only available up to responseMessage.txCount + + unsigned char receivedMoneyFlow = (responseMessage.moneyFlew[transaction / 8] >> (transaction % 8)) & 1; + unsigned char expectedMoneyFlew = gen64() % 2; + EXPECT_EQ(receivedMoneyFlow, expectedMoneyFlew); } } @@ -130,7 +133,7 @@ TEST(TestCoreTxStatusRequestAddOn, EpochTransition) std::mt19937_64 gen64(seed); // 5x test with running 2 epoch transitions - for (int testIdx = 0; testIdx < 6; ++testIdx) + for (int testIdx = 0; testIdx < 20; ++testIdx) { // first, test case of having no transactions, then of having few transaction, later of having many transactions unsigned short maxTransactions = 0; From 56dc437f4a209d80f4db897f54a985987beface4 Mon Sep 17 00:00:00 2001 From: J0ET0M <107187448+J0ET0M@users.noreply.github.com> Date: Thu, 4 Apr 2024 22:23:14 +0200 Subject: [PATCH 11/31] set moneyflew correct if amount <= 0 --- src/qubic.cpp | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/qubic.cpp b/src/qubic.cpp index 5872d94c..2487ed1c 100644 --- a/src/qubic.cpp +++ b/src/qubic.cpp @@ -2170,11 +2170,14 @@ static void processTick(unsigned long long processorNumber) if (decreaseEnergy(spectrumIndex, transaction->amount)) { increaseEnergy(transaction->destinationPublicKey, transaction->amount); - saveConfirmedTx(numberOfTransactions - 1, 1, system.tick, nextTickData.transactionDigests[transactionIndex]); // qli: save tx + if (transaction->amount) { + saveConfirmedTx(numberOfTransactions - 1, 1, system.tick, nextTickData.transactionDigests[transactionIndex]); // qli: save tx const QuTransfer quTransfer = { transaction->sourcePublicKey , transaction->destinationPublicKey , transaction->amount }; logQuTransfer(quTransfer); + }else{ + saveConfirmedTx(numberOfTransactions - 1, 0, system.tick, nextTickData.transactionDigests[transactionIndex]); // qli: save tx } if (isZero(transaction->destinationPublicKey)) From ab3f74e98f058d0cbb6b9ff92720f14b6466acf9 Mon Sep 17 00:00:00 2001 From: krypdkat Date: Sat, 27 Apr 2024 00:26:46 +0700 Subject: [PATCH 12/31] allocate tickTxStatus to heap mem --- src/qubic.cpp | 2 +- src/tx_status_request.h | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/src/qubic.cpp b/src/qubic.cpp index 2487ed1c..58429c13 100644 --- a/src/qubic.cpp +++ b/src/qubic.cpp @@ -1434,7 +1434,7 @@ static void requestProcessor(void* ProcedureArgument) /* qli: process RequestTxStatus message */ case REQUEST_TX_STATUS: { - processRequestConfirmedTx(peer, header); + processRequestConfirmedTx(processorNumber, peer, header); } break; diff --git a/src/tx_status_request.h b/src/tx_status_request.h index e343b9c8..7cc3c228 100644 --- a/src/tx_status_request.h +++ b/src/tx_status_request.h @@ -61,7 +61,7 @@ struct RespondTxStatus } }; #pragma pack(pop) - +static RespondTxStatus* tickTxStatusStorage = NULL; // Allocate buffers static bool initTxStatusRequestAddOn() @@ -69,6 +69,9 @@ static bool initTxStatusRequestAddOn() // Allocate pool to store confirmed TX's if (!allocatePool(confirmedTxLength * sizeof(ConfirmedTx), (void**)&confirmedTx)) return false; + // allocate tickTxStatus responses storage + if (!allocatePool(MAX_NUMBER_OF_PROCESSORS * sizeof(RespondTxStatus), (void**)&tickTxStatusStorage)) + return false; confirmedTxPreviousEpochBeginTick = 0; confirmedTxCurrentEpochBeginTick = 0; return true; @@ -182,7 +185,7 @@ static bool saveConfirmedTx(unsigned int txNumberMinusOne, unsigned char moneyFl } -static void processRequestConfirmedTx(Peer *peer, RequestResponseHeader *header) +static void processRequestConfirmedTx(long long processorNumber, Peer *peer, RequestResponseHeader *header) { ASSERT(confirmedTxCurrentEpochBeginTick == system.initialTick); ASSERT(system.tick >= system.initialTick); @@ -213,8 +216,8 @@ static void processRequestConfirmedTx(Peer *peer, RequestResponseHeader *header) // get index where confirmedTx are starting to be stored in memory int index = tickTxIndexStart[tickIndex]; - // init response message data - RespondTxStatus tickTxStatus; + // init response message data, get it from the storage to avoid increasing stack mem + RespondTxStatus& tickTxStatus = tickTxStatusStorage[processorNumber]; tickTxStatus.currentTickOfNode = system.tick; tickTxStatus.tick = request->tick; tickTxStatus.txCount = tickTxCounter[tickIndex]; From 73176f13672df3f458e37e8d8b13948cb4caeb13 Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Mon, 29 Apr 2024 09:54:01 +0200 Subject: [PATCH 13/31] Fix test --- test/tx_status_request.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/tx_status_request.cpp b/test/tx_status_request.cpp index 4bea67ce..2f8af561 100644 --- a/test/tx_status_request.cpp +++ b/test/tx_status_request.cpp @@ -92,7 +92,7 @@ static void checkTick(unsigned int tick, unsigned long long seed, unsigned short // get status of tick transactions responseMessage.tick = responseMessage.currentTickOfNode = 0; - processRequestConfirmedTx(nullptr, &requestMessage.header); + processRequestConfirmedTx(0, nullptr, &requestMessage.header); // check that we received right data EXPECT_EQ(responseMessage.tick, tick); From 2ceaacfdf191251a855848ee358c919027edf146 Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Wed, 1 May 2024 11:44:03 +0200 Subject: [PATCH 14/31] Refactor tx-status-request to addon with switch in public_settings.h --- src/Qubic.vcxproj | 2 +- src/Qubic.vcxproj.filters | 7 ++++++- src/{ => addons}/tx_status_request.h | 16 ++++++++++------ src/public_settings.h | 3 +++ src/qubic.cpp | 26 ++++++++++++++++++++++---- test/tx_status_request.cpp | 4 +++- 6 files changed, 45 insertions(+), 13 deletions(-) rename src/{ => addons}/tx_status_request.h (95%) diff --git a/src/Qubic.vcxproj b/src/Qubic.vcxproj index b91c32af..02e4c8b6 100644 --- a/src/Qubic.vcxproj +++ b/src/Qubic.vcxproj @@ -18,6 +18,7 @@ + @@ -27,7 +28,6 @@ - diff --git a/src/Qubic.vcxproj.filters b/src/Qubic.vcxproj.filters index 8fea4b5b..7576d754 100644 --- a/src/Qubic.vcxproj.filters +++ b/src/Qubic.vcxproj.filters @@ -120,7 +120,9 @@ contract_core - + + addons + @@ -138,5 +140,8 @@ {e18dc5b0-6ba9-4b8a-9221-71165f0335b9} + + {d409258b-a63c-443f-8c7a-918c14f192a6} + \ No newline at end of file diff --git a/src/tx_status_request.h b/src/addons/tx_status_request.h similarity index 95% rename from src/tx_status_request.h rename to src/addons/tx_status_request.h index 7cc3c228..b42682d9 100644 --- a/src/tx_status_request.h +++ b/src/addons/tx_status_request.h @@ -2,14 +2,16 @@ #pragma once -#include "platform/m256.h" -#include "platform/debugging.h" -#include "platform/memory.h" +#if ADDON_TX_STATUS_REQUEST -#include "network_messages/header.h" +#include "../platform/m256.h" +#include "../platform/debugging.h" +#include "../platform/memory.h" -#include "public_settings.h" -#include "system.h" +#include "../network_messages/header.h" + +#include "../public_settings.h" +#include "../system.h" typedef struct @@ -240,3 +242,5 @@ static void processRequestConfirmedTx(long long processorNumber, Peer *peer, Req ASSERT(tickTxStatus.size() <= sizeof(tickTxStatus)); enqueueResponse(peer, tickTxStatus.size(), RESPOND_TX_STATUS, header->dejavu(), &tickTxStatus); } + +#endif diff --git a/src/public_settings.h b/src/public_settings.h index 3c9d7f01..b5f2c1d5 100644 --- a/src/public_settings.h +++ b/src/public_settings.h @@ -27,6 +27,9 @@ // If you restart your node after seamless epoch transition, make sure EPOCH and TICK are set correctly for the currently running epoch. #define START_NETWORK_FROM_SCRATCH 1 +// Addons: If you don't know it, leave it 0. +#define ADDON_TX_STATUS_REQUEST 0 + ////////////////////////////////////////////////////////////////////////// // Config options that should NOT be changed by operators diff --git a/src/qubic.cpp b/src/qubic.cpp index 58429c13..f0be305b 100644 --- a/src/qubic.cpp +++ b/src/qubic.cpp @@ -36,8 +36,7 @@ #include "tick_storage.h" -#include "tx_status_request.h" - +#include "addons/tx_status_request.h" ////////// Qubic \\\\\\\\\\ @@ -1431,12 +1430,14 @@ static void requestProcessor(void* ProcedureArgument) } break; +#if ADDON_TX_STATUS_REQUEST /* qli: process RequestTxStatus message */ case REQUEST_TX_STATUS: { processRequestConfirmedTx(processorNumber, peer, header); } break; +#endif } @@ -2098,7 +2099,9 @@ static void processTick(unsigned long long processorNumber) if (nextTickData.epoch == system.epoch) { auto* tsCurrentTickTransactionOffsets = ts.tickTransactionOffsets.getByTickIndex(tickIndex); +#if ADDON_TX_STATUS_REQUEST tickTxIndexStart[system.tick - system.initialTick] = numberOfTransactions; // qli: part of tx_status_request add-on +#endif bs->SetMem(entityPendingTransactionIndices, sizeof(entityPendingTransactionIndices), 0); // reset solution task queue score->resetTaskQueue(); @@ -2166,18 +2169,26 @@ static void processTick(unsigned long long processorNumber) entityPendingTransactionIndices[spectrumIndex] = 1; numberOfTransactions++; +#if ADDON_TX_STATUS_REQUEST tickTxIndexStart[system.tick - system.initialTick + 1] = numberOfTransactions; // qli: part of tx_status_request add-on +#endif if (decreaseEnergy(spectrumIndex, transaction->amount)) { increaseEnergy(transaction->destinationPublicKey, transaction->amount); if (transaction->amount) { +#if ADDON_TX_STATUS_REQUEST saveConfirmedTx(numberOfTransactions - 1, 1, system.tick, nextTickData.transactionDigests[transactionIndex]); // qli: save tx +#endif const QuTransfer quTransfer = { transaction->sourcePublicKey , transaction->destinationPublicKey , transaction->amount }; logQuTransfer(quTransfer); - }else{ + } + else + { +#if ADDON_TX_STATUS_REQUEST saveConfirmedTx(numberOfTransactions - 1, 0, system.tick, nextTickData.transactionDigests[transactionIndex]); // qli: save tx +#endif } if (isZero(transaction->destinationPublicKey)) @@ -2488,7 +2499,9 @@ static void processTick(unsigned long long processorNumber) } else { +#if ADDON_TX_STATUS_REQUEST saveConfirmedTx(numberOfTransactions - 1, 0, system.tick, nextTickData.transactionDigests[transactionIndex]); // qli: save tx +#endif } } } @@ -2715,7 +2728,9 @@ static void beginEpoch1of2() #ifndef NDEBUG ts.checkStateConsistencyWithAssert(); #endif +#if ADDON_TX_STATUS_REQUEST beginEpochTxStatusRequestAddOn(system.initialTick); +#endif for (unsigned int i = 0; i < SPECTRUM_CAPACITY; i++) { @@ -4007,12 +4022,13 @@ static bool initialize() if (!initLogging()) return false; - // qli: init tx_status_request +#if ADDON_TX_STATUS_REQUEST if (!initTxStatusRequestAddOn()) { logToConsole(L"initTxStatusRequestAddOn() failed!"); return false; } +#endif logToConsole(L"Loading system file ..."); bs->SetMem(&system, sizeof(system), 0); @@ -4254,7 +4270,9 @@ static void deinitialize() deinitLogging(); +#if ADDON_TX_STATUS_REQUEST deinitTxStatusRequestAddOn(); +#endif for (unsigned int processorIndex = 0; processorIndex < MAX_NUMBER_OF_PROCESSORS; processorIndex++) { diff --git a/test/tx_status_request.cpp b/test/tx_status_request.cpp index 2f8af561..cdf54af1 100644 --- a/test/tx_status_request.cpp +++ b/test/tx_status_request.cpp @@ -11,7 +11,9 @@ void enqueueResponse(Peer* peer, unsigned int dataSize, unsigned char type, unsi #define MAX_NUMBER_OF_TICKS_PER_EPOCH 50 #undef TICKS_TO_KEEP_FROM_PRIOR_EPOCH #define TICKS_TO_KEEP_FROM_PRIOR_EPOCH 32 -#include "../src/tx_status_request.h" +#undef ADDON_TX_STATUS_REQUEST +#define ADDON_TX_STATUS_REQUEST 1 +#include "../src/addons/tx_status_request.h" #include From 5138c585664504da8c02ce31eb9f762854a7ce17 Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Wed, 1 May 2024 12:25:19 +0200 Subject: [PATCH 15/31] Some ASSERTs in queuing of received requests --- src/network_core/peers.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/network_core/peers.h b/src/network_core/peers.h index 570dc751..c030423e 100644 --- a/src/network_core/peers.h +++ b/src/network_core/peers.h @@ -513,6 +513,10 @@ static void peerReceiveAndTransmit(unsigned int i, unsigned int salt) { dejavu0[saltedId >> 6] |= (1ULL << (saltedId & 63)); + ASSERT(requestQueueElementHead < REQUEST_QUEUE_LENGTH); + ASSERT(requestQueueBufferHead < REQUEST_QUEUE_BUFFER_SIZE); + ASSERT(requestQueueBufferHead + requestResponseHeader->size() < REQUEST_QUEUE_BUFFER_SIZE); + requestQueueElements[requestQueueElementHead].offset = requestQueueBufferHead; bs->CopyMem(&requestQueueBuffer[requestQueueBufferHead], peers[i].receiveBuffer, requestResponseHeader->size()); requestQueueBufferHead += requestResponseHeader->size(); From a6eaf853cf4842851f180eb6ea514153acc78862 Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Wed, 1 May 2024 12:35:35 +0200 Subject: [PATCH 16/31] Fix typos --- src/contract_core/contract_exec.h | 10 +++++----- src/contracts/qpi.h | 12 ++++++------ src/network_core/peers.h | 2 +- src/network_messages/transactions.h | 2 +- src/platform/m256.h | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/src/contract_core/contract_exec.h b/src/contract_core/contract_exec.h index c67c1bc7..90788ee6 100644 --- a/src/contract_core/contract_exec.h +++ b/src/contract_core/contract_exec.h @@ -94,7 +94,7 @@ void QPI::QpiContextFunctionCall::__qpiFreeContextOtherContract() const contractLocalsStack[_stackIndex].free(); } -void* QPI::QpiContextFunctionCall::__qpiAquireStateForReading(unsigned int contractIndex) const +void* QPI::QpiContextFunctionCall::__qpiAcquireStateForReading(unsigned int contractIndex) const { ASSERT(contractIndex < contractCount); ACQUIRE(contractStateLock[contractIndex]); @@ -108,9 +108,9 @@ void QPI::QpiContextFunctionCall::__qpiReleaseStateForReading(unsigned int contr RELEASE(contractStateLock[contractIndex]); } -void* QPI::QpiContextProcedureCall::__qpiAquireStateForWriting(unsigned int contractIndex) const +void* QPI::QpiContextProcedureCall::__qpiAcquireStateForWriting(unsigned int contractIndex) const { - return QpiContextFunctionCall::__qpiAquireStateForReading(contractIndex); + return QpiContextFunctionCall::__qpiAcquireStateForReading(contractIndex); } void QPI::QpiContextProcedureCall::__qpiReleaseStateForWriting(unsigned int contractIndex) const @@ -167,7 +167,7 @@ struct QpiContextUserProcedureCall : public QPI::QpiContextProcedureCall copyMem(inputBuffer, inputPtr, inputSize); setMem(outputBuffer, outputSize + localsSize, 0); - // aquire lock of contract state for writing (may block) + // acquire lock of contract state for writing (may block) ACQUIRE(contractStateLock[_currentContractIndex]); // run procedure @@ -227,7 +227,7 @@ struct QpiContextUserFunctionCall : public QPI::QpiContextFunctionCall copyMem(inputBuffer, inputPtr, inputSize); setMem(outputBuffer, outputSize + localsSize, 0); - // aquire lock of contract state for writing (may block) + // acquire lock of contract state for writing (may block) ACQUIRE(contractStateLock[_currentContractIndex]); // run function diff --git a/src/contracts/qpi.h b/src/contracts/qpi.h index 76ebc470..d259fba9 100644 --- a/src/contracts/qpi.h +++ b/src/contracts/qpi.h @@ -5785,7 +5785,7 @@ namespace QPI // Remove element and mark its pov for removal, if the last element. // Returns element index of next element in priority queue (the one following elementIdx). - // Element indices obatined before this call are invalidated, because at least one element is moved. + // Element indices obtained before this call are invalidated, because at least one element is moved. sint64 remove(sint64 elementIdx) { sint64 nextElementIdxOfRemoved = NULL_INDEX; @@ -5850,7 +5850,7 @@ namespace QPI } else if (curElement.bstLeftIndex != NULL_INDEX) { - // contains lonly left child + // contains only left child if (elementIdx == pov.tailIndex) { pov.tailIndex = _previousElementIndex(elementIdx); @@ -6096,7 +6096,7 @@ namespace QPI void __qpiFreeLocals() const; const QpiContextFunctionCall& __qpiConstructContextOtherContractFunctionCall(unsigned int otherContractIndex) const; void __qpiFreeContextOtherContract() const; - void * __qpiAquireStateForReading(unsigned int contractIndex) const; + void * __qpiAcquireStateForReading(unsigned int contractIndex) const; void __qpiReleaseStateForReading(unsigned int contractIndex) const; protected: @@ -6136,7 +6136,7 @@ namespace QPI // Internal functions, calling not allowed in contracts const QpiContextProcedureCall& __qpiConstructContextOtherContractProcedureCall(unsigned int otherContractIndex, sint64 invocationReward) const; - void* __qpiAquireStateForWriting(unsigned int contractIndex) const; + void* __qpiAcquireStateForWriting(unsigned int contractIndex) const; void __qpiReleaseStateForWriting(unsigned int contractIndex) const; protected: @@ -6257,7 +6257,7 @@ namespace QPI static_assert(contractStateType::__contract_index < CONTRACT_STATE_TYPE::__contract_index, "You can only call contracts with lower index."); \ contractStateType::function( \ qpi.__qpiConstructContextOtherContractFunctionCall(contractStateType::__contract_index), \ - *(contractStateType*)qpi.__qpiAquireStateForReading(contractStateType::__contract_index), \ + *(contractStateType*)qpi.__qpiAcquireStateForReading(contractStateType::__contract_index), \ input, output, \ *(contractStateType::function##_locals*)qpi.__qpiAllocLocals(sizeof(contractStateType::function##_locals))); \ qpi.__qpiReleaseStateForReading(contractStateType::__contract_index); \ @@ -6272,7 +6272,7 @@ namespace QPI static_assert(invocationReward >= 0, "The invocationReward cannot be negative!"); \ contractStateType::procedure( \ qpi.__qpiConstructContextOtherContractProcedureCall(contractStateType::__contract_index, invocationReward), \ - *(contractStateType*)qpi.__qpiAquireStateForWriting(contractStateType::__contract_index), \ + *(contractStateType*)qpi.__qpiAcquireStateForWriting(contractStateType::__contract_index), \ input, output, \ *(contractStateType::procedure##_locals*)qpi.__qpiAllocLocals(sizeof(contractStateType::procedure##_locals))); \ qpi.__qpiReleaseStateForWriting(contractStateType::__contract_index); \ diff --git a/src/network_core/peers.h b/src/network_core/peers.h index c030423e..23289dad 100644 --- a/src/network_core/peers.h +++ b/src/network_core/peers.h @@ -245,7 +245,7 @@ static void enqueueResponse(Peer* peer, unsigned int dataSize, unsigned char typ /** * checks if a given address is a bogon address -* a bogon address is an ip address which should not be used pubicly (e.g. private networks) +* a bogon address is an ip address which should not be used publicly (e.g. private networks) * * @param address the ip address to be checked * @return true if address is bogon or false if not diff --git a/src/network_messages/transactions.h b/src/network_messages/transactions.h index 75a7609e..0cb0cfdb 100644 --- a/src/network_messages/transactions.h +++ b/src/network_messages/transactions.h @@ -22,7 +22,7 @@ struct Transaction unsigned short inputType; unsigned short inputSize; - // Return total transaction datat size with payload data and signature + // Return total transaction data size with payload data and signature unsigned int totalSize() const { return sizeof(Transaction) + inputSize + SIGNATURE_SIZE; diff --git a/src/platform/m256.h b/src/platform/m256.h index 434c6c40..4ac68e21 100644 --- a/src/platform/m256.h +++ b/src/platform/m256.h @@ -6,7 +6,7 @@ // Existing interface and behavior should never be changed! (However, it may be extended.) union m256i { - // access for loops and compatability with __m256i + // access for loops and compatibility with __m256i __int8 m256i_i8[32]; __int16 m256i_i16[16]; __int32 m256i_i32[8]; From 977d42896ee1f9ba1b97b5a226153b1beb65647d Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Wed, 1 May 2024 13:58:42 +0200 Subject: [PATCH 17/31] Extend testing of QPI::collection --- test/qpi.cpp | 61 ++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 57 insertions(+), 4 deletions(-) diff --git a/test/qpi.cpp b/test/qpi.cpp index aabb9d88..d43e7913 100644 --- a/test/qpi.cpp +++ b/test/qpi.cpp @@ -626,6 +626,8 @@ void testCollectionOnePovMultiElements(int prioAmpFactor, int prioFreqDiv) checkPriorityQueue(coll, pov); EXPECT_TRUE(elementIndex != QPI::NULL_INDEX); + EXPECT_EQ(coll.priority(elementIndex), prio); + EXPECT_EQ(coll.element(elementIndex), value); EXPECT_EQ(coll.population(pov), i + 1); EXPECT_EQ(coll.population(), i + 1); } @@ -745,10 +747,11 @@ void testCollectionOnePovMultiElements(int prioAmpFactor, int prioFreqDiv) EXPECT_EQ(followingRemovedPrio, coll.priority(followingRemovedIndex)); checkPriorityQueue(coll, pov); - // remove another - followingRemovedValue = coll.element(coll.nextElementIndex(1)); - followingRemovedPrio = coll.priority(coll.nextElementIndex(1)); - followingRemovedIndex = coll.remove(1); + // remove another (not head or tail!) + QPI::sint64 removeIdx = followingRemovedIndex; + followingRemovedValue = coll.element(coll.nextElementIndex(removeIdx)); + followingRemovedPrio = coll.priority(coll.nextElementIndex(removeIdx)); + followingRemovedIndex = coll.remove(removeIdx); EXPECT_EQ(followingRemovedValue, coll.element(followingRemovedIndex)); EXPECT_EQ(followingRemovedPrio, coll.priority(followingRemovedIndex)); checkPriorityQueue(coll, pov); @@ -809,12 +812,62 @@ void testCollectionOnePovMultiElements(int prioAmpFactor, int prioFreqDiv) TEST(TestCoreQPI, CollectionOnePovMultiElements) { + testCollectionOnePovMultiElements<16>(10, 3); testCollectionOnePovMultiElements<128>(10, 3); testCollectionOnePovMultiElements<128>(10, 10); testCollectionOnePovMultiElements<128>(1, 10); testCollectionOnePovMultiElements<128>(1, 1); } +TEST(TestCoreQPI, CollectionOnePovMultiElementsSamePrioOrder) +{ + constexpr unsigned long long capacity = 16; + + // for valid init you either need to call reset or load the data from a file (in SC, state is zeroed before INITIALIZE is called) + QPI::collection coll; + coll.reset(); + + // these tests support changing the implementation of the element array filling to non-sequential + // by saving element indices in order + std::vector elementIndices; + + // fill completely with same priority + QPI::id pov(1, 2, 3, 4); + constexpr QPI::sint64 prio = 100; + for (int i = 0; i < capacity; ++i) + { + int value = i * 3; + + EXPECT_EQ(coll.capacity(), capacity); + EXPECT_EQ(coll.population(), i); + EXPECT_EQ(coll.population(pov), i); + + QPI::sint64 elementIndex = coll.add(pov, value, prio); + elementIndices.push_back(elementIndex); + checkPriorityQueue(coll, pov); + + EXPECT_TRUE(elementIndex != QPI::NULL_INDEX); + EXPECT_EQ(coll.element(elementIndex), value); + EXPECT_EQ(coll.priority(elementIndex), prio); + EXPECT_EQ(coll.population(pov), i + 1); + EXPECT_EQ(coll.population(), i + 1); + } + + checkPriorityQueue(coll, pov, true); + + // check that priority queue order of same priorty items matches the order of insertion + QPI::sint64 elementIndex = coll.headIndex(pov); + for (int i = 0; i < capacity; ++i) + { + int value = i * 3; + EXPECT_NE(elementIndex, QPI::NULL_INDEX); + EXPECT_EQ(coll.element(elementIndex), value); + EXPECT_EQ(coll.priority(elementIndex), prio); + elementIndex = coll.nextElementIndex(elementIndex); + } + EXPECT_EQ(elementIndex, QPI::NULL_INDEX); +} + template void testCollectionMultiPovOneElement(bool cleanupAfterEachRemove) { From 5a0081ae1dcd566dbc3d8818448dd739aac16eeb Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Wed, 1 May 2024 15:47:58 +0200 Subject: [PATCH 18/31] Fix bug in QPI::collection Insert element behind other elements with same priority (not before them). --- src/contracts/qpi.h | 8 ++++---- test/qpi.cpp | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/contracts/qpi.h b/src/contracts/qpi.h index d259fba9..0bb5f4ac 100644 --- a/src/contracts/qpi.h +++ b/src/contracts/qpi.h @@ -5193,7 +5193,7 @@ namespace QPI *pIterationsCount += 1; } auto& curElement = _elements[idx]; - if (curElement.priority > priority) + if (curElement.priority >= priority) { if (curElement.bstRightIndex != NULL_INDEX) { @@ -5237,7 +5237,7 @@ namespace QPI { int iterations_count = 0; sint64 parentIdx = _searchElement(pov.bstRootIndex, priority, &iterations_count); - if (_elements[parentIdx].priority > priority) + if (_elements[parentIdx].priority >= priority) { _elements[parentIdx].bstRightIndex = newElementIdx; } @@ -5249,11 +5249,11 @@ namespace QPI pov.population++; - if (_elements[pov.headIndex].priority <= priority) + if (_elements[pov.headIndex].priority < priority) { pov.headIndex = newElementIdx; } - else if (_elements[pov.tailIndex].priority > priority) + else if (_elements[pov.tailIndex].priority >= priority) { pov.tailIndex = newElementIdx; } diff --git a/test/qpi.cpp b/test/qpi.cpp index d43e7913..40e2264a 100644 --- a/test/qpi.cpp +++ b/test/qpi.cpp @@ -853,8 +853,6 @@ TEST(TestCoreQPI, CollectionOnePovMultiElementsSamePrioOrder) EXPECT_EQ(coll.population(), i + 1); } - checkPriorityQueue(coll, pov, true); - // check that priority queue order of same priorty items matches the order of insertion QPI::sint64 elementIndex = coll.headIndex(pov); for (int i = 0; i < capacity; ++i) From 3fe02b5dea5d6adcdbeb438ef8758ebc1d59faff Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Wed, 1 May 2024 16:22:02 +0200 Subject: [PATCH 19/31] Fix definitions of contract system procedures in QPI --- src/contracts/qpi.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/contracts/qpi.h b/src/contracts/qpi.h index 0bb5f4ac..ae19e441 100644 --- a/src/contracts/qpi.h +++ b/src/contracts/qpi.h @@ -6161,17 +6161,17 @@ namespace QPI ////////// // TODO: make sure these cannot be called from contract body - #define INITIALIZE public: static void __initialize(const QPI::QpiContext& qpi, CONTRACT_STATE_TYPE& state) { constexpr unsigned int __functionOrProcedureId = (CONTRACT_INDEX << 22) | __LINE__; ::__beginFunctionOrProcedure(__functionOrProcedureId); + #define INITIALIZE public: static void __initialize(const QPI::QpiContextProcedureCall& qpi, CONTRACT_STATE_TYPE& state) { constexpr unsigned int __functionOrProcedureId = (CONTRACT_INDEX << 22) | __LINE__; ::__beginFunctionOrProcedure(__functionOrProcedureId); - #define BEGIN_EPOCH public: static void __beginEpoch(const QPI::QpiContext& qpi, CONTRACT_STATE_TYPE& state) { constexpr unsigned int __functionOrProcedureId = (CONTRACT_INDEX << 22) | __LINE__; ::__beginFunctionOrProcedure(__functionOrProcedureId); + #define BEGIN_EPOCH public: static void __beginEpoch(const QPI::QpiContextProcedureCall& qpi, CONTRACT_STATE_TYPE& state) { constexpr unsigned int __functionOrProcedureId = (CONTRACT_INDEX << 22) | __LINE__; ::__beginFunctionOrProcedure(__functionOrProcedureId); - #define END_EPOCH public: static void __endEpoch(const QPI::QpiContext& qpi, CONTRACT_STATE_TYPE& state) { constexpr unsigned int __functionOrProcedureId = (CONTRACT_INDEX << 22) | __LINE__; ::__beginFunctionOrProcedure(__functionOrProcedureId); + #define END_EPOCH public: static void __endEpoch(const QPI::QpiContextProcedureCall& qpi, CONTRACT_STATE_TYPE& state) { constexpr unsigned int __functionOrProcedureId = (CONTRACT_INDEX << 22) | __LINE__; ::__beginFunctionOrProcedure(__functionOrProcedureId); - #define BEGIN_TICK public: static void __beginTick(const QPI::QpiContext& qpi, CONTRACT_STATE_TYPE& state) { constexpr unsigned int __functionOrProcedureId = (CONTRACT_INDEX << 22) | __LINE__; ::__beginFunctionOrProcedure(__functionOrProcedureId); + #define BEGIN_TICK public: static void __beginTick(const QPI::QpiContextProcedureCall& qpi, CONTRACT_STATE_TYPE& state) { constexpr unsigned int __functionOrProcedureId = (CONTRACT_INDEX << 22) | __LINE__; ::__beginFunctionOrProcedure(__functionOrProcedureId); - #define END_TICK public: static void __endTick(const QPI::QpiContext& qpi, CONTRACT_STATE_TYPE& state) { constexpr unsigned int __functionOrProcedureId = (CONTRACT_INDEX << 22) | __LINE__; ::__beginFunctionOrProcedure(__functionOrProcedureId); + #define END_TICK public: static void __endTick(const QPI::QpiContextProcedureCall& qpi, CONTRACT_STATE_TYPE& state) { constexpr unsigned int __functionOrProcedureId = (CONTRACT_INDEX << 22) | __LINE__; ::__beginFunctionOrProcedure(__functionOrProcedureId); - #define EXPAND public: static void __expand(const QPI::QpiContext& qpi, CONTRACT_STATE_TYPE& state, CONTRACT_STATE2_TYPE& state2) { constexpr unsigned int __functionOrProcedureId = (CONTRACT_INDEX << 22) | __LINE__; ::__beginFunctionOrProcedure(__functionOrProcedureId); + #define EXPAND public: static void __expand(const QPI::QpiContextProcedureCall& qpi, CONTRACT_STATE_TYPE& state, CONTRACT_STATE2_TYPE& state2) { constexpr unsigned int __functionOrProcedureId = (CONTRACT_INDEX << 22) | __LINE__; ::__beginFunctionOrProcedure(__functionOrProcedureId); // TODO: move to QPI to prevent from spamming log of other contract (by calling local function) #define LOG_DEBUG(message) __logContractDebugMessage(CONTRACT_INDEX, message); From 12bee8a0f63cb74b53ed7030d1c0ffecc664a877 Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Wed, 1 May 2024 18:34:24 +0200 Subject: [PATCH 20/31] Fix contract state misalignment between nodes with logging and without logging --- src/contract_core/contract_def.h | 8 ++++---- src/logging.h | 24 ++++++++++++++++-------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/src/contract_core/contract_def.h b/src/contract_core/contract_def.h index 0597b00e..3c9de788 100644 --- a/src/contract_core/contract_def.h +++ b/src/contract_core/contract_def.h @@ -34,10 +34,10 @@ constexpr unsigned short MAX_NESTED_CONTRACT_CALLS = 10; static void __beginFunctionOrProcedure(const unsigned int); static void __endFunctionOrProcedure(const unsigned int); template static m256i __K12(T); -template static void __logContractDebugMessage(unsigned int, const T&); -template static void __logContractErrorMessage(unsigned int, const T&); -template static void __logContractInfoMessage(unsigned int, const T&); -template static void __logContractWarningMessage(unsigned int, const T&); +template static void __logContractDebugMessage(unsigned int, T&); +template static void __logContractErrorMessage(unsigned int, T&); +template static void __logContractInfoMessage(unsigned int, T&); +template static void __logContractWarningMessage(unsigned int, T&); static void* __scratchpad(); // TODO: concurrency support (n buffers for n allowed concurrent contract executions) // static void* __tryAcquireScratchpad(unsigned int size); // Thread-safe, may return nullptr if no appropriate buffer is available // static void __ReleaseScratchpad(void*); diff --git a/src/logging.h b/src/logging.h index 6ba30ae3..c5ffdb32 100644 --- a/src/logging.h +++ b/src/logging.h @@ -210,12 +210,14 @@ struct DummyContractErrorMessage }; template -static void __logContractErrorMessage(unsigned int contractIndex, const T& message) +static void __logContractErrorMessage(unsigned int contractIndex, T& message) { static_assert(offsetof(T, _terminator) >= 8, "Invalid contract error message structure"); + // In order to keep state changes consistent, this needs to be set independently of whether logging is enabled + *((unsigned int*)&message) = contractIndex; + #if LOG_CONTRACT_ERROR_MESSAGES - * ((unsigned int*)&message) = contractIndex; logMessage(offsetof(T, _terminator), CONTRACT_ERROR_MESSAGE, &message); #endif } @@ -231,12 +233,14 @@ struct DummyContractWarningMessage }; template -static void __logContractWarningMessage(unsigned int contractIndex, const T& message) +static void __logContractWarningMessage(unsigned int contractIndex, T& message) { static_assert(offsetof(T, _terminator) >= 8, "Invalid contract warning message structure"); + // In order to keep state changes consistent, this needs to be set independently of whether logging is enabled + *((unsigned int*)&message) = contractIndex; + #if LOG_CONTRACT_WARNING_MESSAGES - * ((unsigned int*)&message) = contractIndex; logMessage(offsetof(T, _terminator), CONTRACT_WARNING_MESSAGE, &message); #endif } @@ -252,12 +256,14 @@ struct DummyContractInfoMessage }; template -static void __logContractInfoMessage(unsigned int contractIndex, const T& message) +static void __logContractInfoMessage(unsigned int contractIndex, T& message) { static_assert(offsetof(T, _terminator) >= 8, "Invalid contract info message structure"); + // In order to keep state changes consistent, this needs to be set independently of whether logging is enabled + *((unsigned int*)&message) = contractIndex; + #if LOG_CONTRACT_INFO_MESSAGES - * ((unsigned int*)&message) = contractIndex; logMessage(offsetof(T, _terminator), CONTRACT_INFORMATION_MESSAGE, &message); #endif } @@ -273,12 +279,14 @@ struct DummyContractDebugMessage }; template -static void __logContractDebugMessage(unsigned int contractIndex, const T& message) +static void __logContractDebugMessage(unsigned int contractIndex, T& message) { static_assert(offsetof(T, _terminator) >= 8, "Invalid contract debug message structure"); + // In order to keep state changes consistent, this needs to be set independently of whether logging is enabled + *((unsigned int*)&message) = contractIndex; + #if LOG_CONTRACT_DEBUG_MESSAGES - * ((unsigned int*)&message) = contractIndex; logMessage(offsetof(T, _terminator), CONTRACT_DEBUG_MESSAGE, &message); #endif } From 576bd2d27bbef8fcd32e323635030d295f439859 Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Wed, 1 May 2024 18:57:27 +0200 Subject: [PATCH 21/31] Change fix of previous commit to variant that is safer in regards to compiler optimizations --- src/logging.h | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/src/logging.h b/src/logging.h index c5ffdb32..3b32b8d6 100644 --- a/src/logging.h +++ b/src/logging.h @@ -214,12 +214,14 @@ static void __logContractErrorMessage(unsigned int contractIndex, T& message) { static_assert(offsetof(T, _terminator) >= 8, "Invalid contract error message structure"); - // In order to keep state changes consistent, this needs to be set independently of whether logging is enabled - *((unsigned int*)&message) = contractIndex; - #if LOG_CONTRACT_ERROR_MESSAGES + * ((unsigned int*)&message) = contractIndex; logMessage(offsetof(T, _terminator), CONTRACT_ERROR_MESSAGE, &message); #endif + + // In order to keep state changes consistent independently of (a) whether logging is enabled and + // (b) potential compiler optimizes, set contractIndex to 0 after logging + * ((unsigned int*)&message) = 0; } struct DummyContractWarningMessage @@ -237,12 +239,14 @@ static void __logContractWarningMessage(unsigned int contractIndex, T& message) { static_assert(offsetof(T, _terminator) >= 8, "Invalid contract warning message structure"); - // In order to keep state changes consistent, this needs to be set independently of whether logging is enabled - *((unsigned int*)&message) = contractIndex; - #if LOG_CONTRACT_WARNING_MESSAGES + * ((unsigned int*)&message) = contractIndex; logMessage(offsetof(T, _terminator), CONTRACT_WARNING_MESSAGE, &message); #endif + + // In order to keep state changes consistent independently of (a) whether logging is enabled and + // (b) potential compiler optimizes, set contractIndex to 0 after logging + * ((unsigned int*)&message) = 0; } struct DummyContractInfoMessage @@ -260,12 +264,14 @@ static void __logContractInfoMessage(unsigned int contractIndex, T& message) { static_assert(offsetof(T, _terminator) >= 8, "Invalid contract info message structure"); - // In order to keep state changes consistent, this needs to be set independently of whether logging is enabled - *((unsigned int*)&message) = contractIndex; - #if LOG_CONTRACT_INFO_MESSAGES + * ((unsigned int*)&message) = contractIndex; logMessage(offsetof(T, _terminator), CONTRACT_INFORMATION_MESSAGE, &message); #endif + + // In order to keep state changes consistent independently of (a) whether logging is enabled and + // (b) potential compiler optimizes, set contractIndex to 0 after logging + * ((unsigned int*)&message) = 0; } struct DummyContractDebugMessage @@ -283,12 +289,14 @@ static void __logContractDebugMessage(unsigned int contractIndex, T& message) { static_assert(offsetof(T, _terminator) >= 8, "Invalid contract debug message structure"); - // In order to keep state changes consistent, this needs to be set independently of whether logging is enabled - *((unsigned int*)&message) = contractIndex; - #if LOG_CONTRACT_DEBUG_MESSAGES + * ((unsigned int*)&message) = contractIndex; logMessage(offsetof(T, _terminator), CONTRACT_DEBUG_MESSAGE, &message); #endif + + // In order to keep state changes consistent independently of (a) whether logging is enabled and + // (b) potential compiler optimizes, set contractIndex to 0 after logging + *((unsigned int*)&message) = 0; } struct DummyCustomMessage From b1a82be429c1ec00b731d441397e1f472fb5f62e Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Fri, 3 May 2024 10:24:13 +0200 Subject: [PATCH 22/31] =?UTF-8?q?=C3=81dd=20read/write=20lock=20for=20cont?= =?UTF-8?q?ract=20states=20that=20allows=20multiple=20readers=20but=20prio?= =?UTF-8?q?rizes=20writers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Qubic.vcxproj | 1 + src/Qubic.vcxproj.filters | 3 + src/contract_core/contract_def.h | 5 -- src/contract_core/contract_exec.h | 34 +++++--- src/platform/read_write_lock.h | 135 ++++++++++++++++++++++++++++++ src/qubic.cpp | 29 +++---- test/platform.cpp | 37 ++++++++ test/test.vcxproj | 1 + 8 files changed, 213 insertions(+), 32 deletions(-) create mode 100644 src/platform/read_write_lock.h create mode 100644 test/platform.cpp diff --git a/src/Qubic.vcxproj b/src/Qubic.vcxproj index 532c38a8..e6d49421 100644 --- a/src/Qubic.vcxproj +++ b/src/Qubic.vcxproj @@ -53,6 +53,7 @@ + diff --git a/src/Qubic.vcxproj.filters b/src/Qubic.vcxproj.filters index 2bc1f513..37ab30a5 100644 --- a/src/Qubic.vcxproj.filters +++ b/src/Qubic.vcxproj.filters @@ -123,6 +123,9 @@ contract_core + + platform + diff --git a/src/contract_core/contract_def.h b/src/contract_core/contract_def.h index 3c9de788..a3891ca4 100644 --- a/src/contract_core/contract_def.h +++ b/src/contract_core/contract_def.h @@ -132,11 +132,6 @@ static unsigned short contractUserProcedureInputSizes[contractCount][65536]; static unsigned short contractUserProcedureOutputSizes[contractCount][65536]; static unsigned int contractUserProcedureLocalsSizes[contractCount][65536]; -// TODO: allow parallel reading with distinguishing read/write lock -static volatile char contractStateLock[contractCount]; -static unsigned char* contractStates[contractCount]; -static unsigned long long contractTotalExecutionTicks[contractCount]; - enum SystemProcedureID { INITIALIZE = 0, diff --git a/src/contract_core/contract_exec.h b/src/contract_core/contract_exec.h index 90788ee6..8f36e25b 100644 --- a/src/contract_core/contract_exec.h +++ b/src/contract_core/contract_exec.h @@ -1,6 +1,6 @@ #pragma once -#include "platform/concurrency.h" +#include "platform/read_write_lock.h" #include "platform/debugging.h" #include "platform/memory.h" @@ -14,6 +14,12 @@ typedef StackBuffer ContractLocalsStack; ContractLocalsStack contractLocalsStack[NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS]; static volatile char contractLocalsStackLock[NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS]; + +static ReadWriteLock contractStateLock[contractCount]; +static unsigned char* contractStates[contractCount]; +static unsigned long long contractTotalExecutionTicks[contractCount]; + + bool initContractExec() { for (ContractLocalsStack::SizeType i = 0; i < NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS; ++i) @@ -97,25 +103,27 @@ void QPI::QpiContextFunctionCall::__qpiFreeContextOtherContract() const void* QPI::QpiContextFunctionCall::__qpiAcquireStateForReading(unsigned int contractIndex) const { ASSERT(contractIndex < contractCount); - ACQUIRE(contractStateLock[contractIndex]); + contractStateLock[contractIndex].acquireRead(); return contractStates[contractIndex]; } void QPI::QpiContextFunctionCall::__qpiReleaseStateForReading(unsigned int contractIndex) const { ASSERT(contractIndex < contractCount); - ASSERT(contractStateLock[contractIndex]); - RELEASE(contractStateLock[contractIndex]); + contractStateLock[contractIndex].releaseRead(); } void* QPI::QpiContextProcedureCall::__qpiAcquireStateForWriting(unsigned int contractIndex) const { - return QpiContextFunctionCall::__qpiAcquireStateForReading(contractIndex); + ASSERT(contractIndex < contractCount); + contractStateLock[contractIndex].acquireWrite(); + return contractStates[contractIndex]; } void QPI::QpiContextProcedureCall::__qpiReleaseStateForWriting(unsigned int contractIndex) const { - QpiContextFunctionCall::__qpiReleaseStateForReading(contractIndex); + ASSERT(contractIndex < contractCount); + contractStateLock[contractIndex].releaseWrite(); } struct QpiContextSystemProcedureCall : public QPI::QpiContextProcedureCall @@ -129,13 +137,13 @@ struct QpiContextSystemProcedureCall : public QPI::QpiContextProcedureCall ASSERT(_currentContractIndex < contractCount); // reserve resources for this processor (may block) - ACQUIRE(contractStateLock[_currentContractIndex]); + contractStateLock[_currentContractIndex].acquireWrite(); const unsigned long long startTick = __rdtsc(); contractSystemProcedures[_currentContractIndex][systemProcId](*this, contractStates[_currentContractIndex]); contractTotalExecutionTicks[_currentContractIndex] += __rdtsc() - startTick; - RELEASE(contractStateLock[_currentContractIndex]); + contractStateLock[_currentContractIndex].releaseWrite(); } }; @@ -168,7 +176,7 @@ struct QpiContextUserProcedureCall : public QPI::QpiContextProcedureCall setMem(outputBuffer, outputSize + localsSize, 0); // acquire lock of contract state for writing (may block) - ACQUIRE(contractStateLock[_currentContractIndex]); + contractStateLock[_currentContractIndex].acquireWrite(); // run procedure const unsigned long long startTick = __rdtsc(); @@ -176,7 +184,7 @@ struct QpiContextUserProcedureCall : public QPI::QpiContextProcedureCall contractTotalExecutionTicks[_currentContractIndex] += __rdtsc() - startTick; // release lock of contract state - RELEASE(contractStateLock[_currentContractIndex]); + contractStateLock[_currentContractIndex].releaseWrite(); // free data on stack (output is unused) contractLocalsStack[_stackIndex].free(); @@ -227,8 +235,8 @@ struct QpiContextUserFunctionCall : public QPI::QpiContextFunctionCall copyMem(inputBuffer, inputPtr, inputSize); setMem(outputBuffer, outputSize + localsSize, 0); - // acquire lock of contract state for writing (may block) - ACQUIRE(contractStateLock[_currentContractIndex]); + // acquire lock of contract state for reading (may block) + contractStateLock[_currentContractIndex].acquireRead(); // run function const unsigned long long startTick = __rdtsc(); @@ -236,7 +244,7 @@ struct QpiContextUserFunctionCall : public QPI::QpiContextFunctionCall contractTotalExecutionTicks[_currentContractIndex] += __rdtsc() - startTick; // release lock of contract state - RELEASE(contractStateLock[_currentContractIndex]); + contractStateLock[_currentContractIndex].releaseRead(); } // free buffer after output has been copied diff --git a/src/platform/read_write_lock.h b/src/platform/read_write_lock.h new file mode 100644 index 00000000..0ac214ae --- /dev/null +++ b/src/platform/read_write_lock.h @@ -0,0 +1,135 @@ +#pragma once + +#include +#include "debugging.h" + +// Lock that allows multiple readers but only one writer. +// Priorizes writers, that is, additional readers can only gain access if no writer is waiting. +class ReadWriteLock +{ +public: + // Constructor + ReadWriteLock() + { + reset(); + } + + // Set lock fully unlocked state. + void reset() + { + readers = 0; + writersWaiting = 0; + } + + // Aquire lock for reading (wait if needed). + void acquireRead() + { + ASSERT(readers >= -1); + ASSERT(writersWaiting >= 0); + + // Wait until getting read access + while (!tryAcquireRead()) + _mm_pause(); + } + + // Try to aquire lock for reading without waiting. Return true if lock has been acquired. + bool tryAcquireRead() + { + ASSERT(readers >= -1); + ASSERT(writersWaiting >= 0); + + // Prioritize writers over readers (do not grant access for new reader if writer is waiting) + if (writersWaiting) + return false; + + // It may happen that writersWaiting changes before the read lock is acquired below. But this should + // be rare and does not justify the overhead of additional locking. + + // If not acquired by writer (would be -1 readers), acquire read access by incrementing number of readers. + // Below is the atomic version of the following: + // + // if (readers >= 0) { + // ++readers; + // return true; + // } else { + // return false; + // } + long currentReaders, newReaders; + while (1) + { + // Get number of current readers and return false if locked by writer + currentReaders = readers; + if (currentReaders < 0) + return false; + + // Increment readers counter. + // If readers is changed after setting currentReaders, the _InterlockedCompareExchange will fail + // and we retry in the next iteration of the while loop. + newReaders = currentReaders + 1; + if (_InterlockedCompareExchange(&readers, newReaders, currentReaders) == currentReaders) + return true; + } + } + + // Release read lock. Needs to follow corresponding acquireRead() or successful tryAcquireRead(). + void releaseRead() + { + ASSERT(readers > 0); + ASSERT(writersWaiting >= 0); + + _InterlockedDecrement(&readers); + } + + // Aquire lock for writing (wait if needed). + void acquireWrite() + { + ASSERT(readers >= -1); + ASSERT(writersWaiting >= 0); + + // Indicate that one more writer is waiting for access + // -> don't grant access to additional readers in tryAcquireRead() in concurrent threads + _InterlockedIncrement(&writersWaiting); + + // Wait until getting write access + while (!tryAcquireWrite()) + _mm_pause(); + + // Writer got access -> decrement counter of writers waiting for access + _InterlockedDecrement(&writersWaiting); + } + + // Try to aquire lock for writing without waiting. Return true if lock has been acquired. + bool tryAcquireWrite() + { + ASSERT(readers >= -1); + ASSERT(writersWaiting >= 0); + + // If no readers or writers, lock for writing. + // Below is the atomic version of the following: + // + // if (readers == 0) + // { + // readers = -1; + // return true; + // } + if (_InterlockedCompareExchange(&readers, -1, 0) == 0) + return true; + + return false; + } + + // Release write lock. Needs to follow corresponding acquireWrite() or successful tryAcquireWrite(). + void releaseWrite() + { + ASSERT(readers == -1); + ASSERT(writersWaiting >= 0); + readers = 0; + } + +private: + // Positive values means number of readers using having acquired a read lock; -1 means writer has acquired lock; 0 mean no lock is active + volatile long readers; + + // Number of writers waiting for lock (>= 0) + volatile long writersWaiting; +}; diff --git a/src/qubic.cpp b/src/qubic.cpp index 89818e87..d0a07a47 100644 --- a/src/qubic.cpp +++ b/src/qubic.cpp @@ -389,9 +389,10 @@ static void getComputerDigest(m256i& digest) // + contractStateChangeFlags set afterwards by thread B and contractStateChangeFlags cleared below below // by thread A. We then have a changed state but a cleared contractStateChangeFlags flag leading to wrong // digest. - ACQUIRE(contractStateLock[digestIndex]); + // This is currently avoided by calling getComputerDigest() from tick processor only (and in non-concurrent init) + contractStateLock[digestIndex].acquireRead(); KangarooTwelve(contractStates[digestIndex], (unsigned int)size, &contractStateDigests[digestIndex], 32); - RELEASE(contractStateLock[digestIndex]); + contractStateLock[digestIndex].releaseRead(); } } } @@ -1035,11 +1036,11 @@ static void processRequestContractIPO(Peer* peer, RequestResponseHeader* header) } else { - ACQUIRE(contractStateLock[request->contractIndex]); + contractStateLock[request->contractIndex].acquireRead(); IPO* ipo = (IPO*)contractStates[request->contractIndex]; bs->CopyMem(respondContractIPO.publicKeys, ipo->publicKeys, sizeof(respondContractIPO.publicKeys)); bs->CopyMem(respondContractIPO.prices, ipo->prices, sizeof(respondContractIPO.prices)); - RELEASE(contractStateLock[request->contractIndex]); + contractStateLock[request->contractIndex].releaseRead(); } enqueueResponse(peer, sizeof(respondContractIPO), RespondContractIPO::type, header->dejavu(), &respondContractIPO); @@ -1484,9 +1485,9 @@ long long QPI::QpiContextProcedureCall::burn(long long amount) const if (decreaseEnergy(index, amount)) { - ACQUIRE(contractStateLock[0]); + contractStateLock[0].acquireWrite(); contractFeeReserve(_currentContractIndex) += amount; - RELEASE(contractStateLock[0]); + contractStateLock[0].releaseWrite(); const Burning burning = { _currentContractId , amount }; logBurning(burning); @@ -2157,7 +2158,7 @@ static void processTick(unsigned long long processorNumber) logQuTransfer(quTransfer); numberOfReleasedEntities = 0; - ACQUIRE(contractStateLock[contractIndex]); + contractStateLock[contractIndex].acquireWrite(); IPO* ipo = (IPO*)contractStates[contractIndex]; for (unsigned int i = 0; i < contractIPOBid->quantity; i++) { @@ -2218,7 +2219,7 @@ static void processTick(unsigned long long processorNumber) contractStateChangeFlags[contractIndex >> 6] |= (1ULL << (contractIndex & 63)); } } - RELEASE(contractStateLock[contractIndex]); + contractStateLock[contractIndex].releaseWrite(); for (unsigned int i = 0; i < numberOfReleasedEntities; i++) { @@ -2732,7 +2733,7 @@ static void endEpoch() { if (system.epoch < contractDescriptions[contractIndex].constructionEpoch) { - ACQUIRE(contractStateLock[contractIndex]); + contractStateLock[contractIndex].acquireRead(); IPO* ipo = (IPO*)contractStates[contractIndex]; long long finalPrice = ipo->prices[NUMBER_OF_COMPUTORS - 1]; int issuanceIndex, ownershipIndex, possessionIndex; @@ -2778,11 +2779,11 @@ static void endEpoch() const QuTransfer quTransfer = { _mm256_setzero_si256() , releasedPublicKeys[i] , releasedAmounts[i] }; logQuTransfer(quTransfer); } - RELEASE(contractStateLock[contractIndex]); + contractStateLock[contractIndex].releaseRead(); - ACQUIRE(contractStateLock[0]); + contractStateLock[0].acquireWrite(); contractFeeReserve(contractIndex) = finalPrice * NUMBER_OF_COMPUTORS; - RELEASE(contractStateLock[0]); + contractStateLock[0].releaseWrite(); } } @@ -3765,9 +3766,9 @@ static void saveComputer() CONTRACT_FILE_NAME[sizeof(CONTRACT_FILE_NAME) / sizeof(CONTRACT_FILE_NAME[0]) - 8] = (contractIndex % 1000) / 100 + L'0'; CONTRACT_FILE_NAME[sizeof(CONTRACT_FILE_NAME) / sizeof(CONTRACT_FILE_NAME[0]) - 7] = (contractIndex % 100) / 10 + L'0'; CONTRACT_FILE_NAME[sizeof(CONTRACT_FILE_NAME) / sizeof(CONTRACT_FILE_NAME[0]) - 6] = contractIndex % 10 + L'0'; - ACQUIRE(contractStateLock[contractIndex]); + contractStateLock[contractIndex].acquireRead(); long long savedSize = save(CONTRACT_FILE_NAME, contractDescriptions[contractIndex].stateSize, contractStates[contractIndex]); - RELEASE(contractStateLock[contractIndex]); + contractStateLock[contractIndex].releaseRead(); totalSize += savedSize; if (savedSize != contractDescriptions[contractIndex].stateSize) { diff --git a/test/platform.cpp b/test/platform.cpp new file mode 100644 index 00000000..b2f86a2e --- /dev/null +++ b/test/platform.cpp @@ -0,0 +1,37 @@ +#define NO_UEFI + +#include "gtest/gtest.h" +#include "../src/platform/read_write_lock.h" + +TEST(TestCoreReadWriteLock, SimpleSingleThread) +{ + ReadWriteLock l; + + // Aquire lock for multiple readers + constexpr int numReaders = 10; + for (int i = 0; i < numReaders; ++i) + { + EXPECT_TRUE(l.tryAcquireRead()); + EXPECT_FALSE(l.tryAcquireWrite()); + } + + // Release read locks + for (int i = 0; i < numReaders; ++i) + { + l.releaseRead(); + } + + // Aquire lock for writer + EXPECT_TRUE(l.tryAcquireWrite()); + EXPECT_FALSE(l.tryAcquireWrite()); + EXPECT_FALSE(l.tryAcquireRead()); + + // Release write lock + l.releaseWrite(); + + l.acquireRead(); + l.releaseRead(); + + l.acquireWrite(); + l.releaseWrite(); +} diff --git a/test/test.vcxproj b/test/test.vcxproj index fff24db6..0b54b013 100644 --- a/test/test.vcxproj +++ b/test/test.vcxproj @@ -64,6 +64,7 @@ + From 098525983ab2af2f8e1235ef42c30c08e0529880 Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Fri, 3 May 2024 11:13:20 +0200 Subject: [PATCH 23/31] Fix warning that constructor is not called in UEFI --- src/contract_core/contract_exec.h | 7 +++++++ src/platform/read_write_lock.h | 10 +++++----- src/qubic.cpp | 2 -- test/platform.cpp | 1 + 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/src/contract_core/contract_exec.h b/src/contract_core/contract_exec.h index 8f36e25b..f8c4e810 100644 --- a/src/contract_core/contract_exec.h +++ b/src/contract_core/contract_exec.h @@ -25,6 +25,13 @@ bool initContractExec() for (ContractLocalsStack::SizeType i = 0; i < NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS; ++i) contractLocalsStack[i].init(); setMem((void*)contractLocalsStackLock, sizeof(contractLocalsStackLock), 0); + + setMem(contractTotalExecutionTicks, sizeof(contractTotalExecutionTicks), 0); + for (int i = 0; i < contractCount; ++i) + { + contractStateLock[i].reset(); + } + return true; } diff --git a/src/platform/read_write_lock.h b/src/platform/read_write_lock.h index 0ac214ae..4676664a 100644 --- a/src/platform/read_write_lock.h +++ b/src/platform/read_write_lock.h @@ -8,11 +8,11 @@ class ReadWriteLock { public: - // Constructor - ReadWriteLock() - { - reset(); - } + // Constructor (disabled because not called without MS CRT, you need to call reset() to init) + //ReadWriteLock() + //{ + // reset(); + //} // Set lock fully unlocked state. void reset() diff --git a/src/qubic.cpp b/src/qubic.cpp index d0a07a47..61fd4929 100644 --- a/src/qubic.cpp +++ b/src/qubic.cpp @@ -3892,8 +3892,6 @@ static bool initialize() if (!initAssets()) return false; - bs->SetMem((void*)contractStateLock, sizeof(contractStateLock), 0); - bs->SetMem(contractTotalExecutionTicks, sizeof(contractTotalExecutionTicks), 0); initContractExec(); for (unsigned int contractIndex = 0; contractIndex < contractCount; contractIndex++) { diff --git a/test/platform.cpp b/test/platform.cpp index b2f86a2e..ef49d9fe 100644 --- a/test/platform.cpp +++ b/test/platform.cpp @@ -6,6 +6,7 @@ TEST(TestCoreReadWriteLock, SimpleSingleThread) { ReadWriteLock l; + l.reset(); // Aquire lock for multiple readers constexpr int numReaders = 10; From 871fcf829a92611851a7d3de7673812cd9ae47a8 Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Fri, 3 May 2024 11:17:21 +0200 Subject: [PATCH 24/31] Reserve one contract stack for procedures (not used by functions) --- src/contract_core/contract_exec.h | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/contract_core/contract_exec.h b/src/contract_core/contract_exec.h index f8c4e810..012e73ff 100644 --- a/src/contract_core/contract_exec.h +++ b/src/contract_core/contract_exec.h @@ -36,17 +36,20 @@ bool initContractExec() } // Acquire lock of an currently unused stack (may block if all in use) -void acquireContractLocalsStack(int& stackIdx) +// stacksToIgnore > 0 can be passed by low priority tasks to keep some stacks reserved for high prio purposes. +void acquireContractLocalsStack(int& stackIdx, unsigned int stacksToIgnore = 0) { + static_assert(NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS >= 2, "NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS should be at least 2."); ASSERT(stackIdx < 0); + ASSERT(stacksToIgnore < NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS); - int i = 0; + int i = stacksToIgnore; while (TRY_ACQUIRE(contractLocalsStackLock[i]) == false) { _mm_pause(); ++i; if (i == NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS) - i = 0; + i = stacksToIgnore; } stackIdx = i; @@ -226,7 +229,8 @@ struct QpiContextUserFunctionCall : public QPI::QpiContextFunctionCall ASSERT(contractUserFunctions[_currentContractIndex][inputType]); // reserve stack for this processor (may block) - acquireContractLocalsStack(_stackIndex); + constexpr unsigned int stacksNotUsedToReserveThemForStateWriter = 1; + acquireContractLocalsStack(_stackIndex, stacksNotUsedToReserveThemForStateWriter); ASSERT(contractLocalsStack[_stackIndex].size() == 0); // allocate input, output, and locals buffer from stack and init them From 731e8ca45141d7c68cc826281f74377a88820856 Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Fri, 3 May 2024 14:33:23 +0200 Subject: [PATCH 25/31] Fix bug in ASSERT --- src/contract_core/contract_exec.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/contract_core/contract_exec.h b/src/contract_core/contract_exec.h index 012e73ff..ff1bf8b0 100644 --- a/src/contract_core/contract_exec.h +++ b/src/contract_core/contract_exec.h @@ -57,7 +57,7 @@ void acquireContractLocalsStack(int& stackIdx, unsigned int stacksToIgnore = 0) void releaseContractLocalsStack(int& stackIdx) { - ASSERT(stackIdx > 0); + ASSERT(stackIdx >= 0); ASSERT(stackIdx < NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS); ASSERT(contractLocalsStackLock[stackIdx]); RELEASE(contractLocalsStackLock[stackIdx]); From 3105d86054fbba9a35e40b4a7a99e4019ea452f3 Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Fri, 3 May 2024 14:58:42 +0200 Subject: [PATCH 26/31] Fix potential race condition with contractTotalExecutionTicks Without this fix, the contractTotalExecutionTicks may be inaccurately updated in parallel contract function calls. --- src/contract_core/contract_exec.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/contract_core/contract_exec.h b/src/contract_core/contract_exec.h index ff1bf8b0..78e456af 100644 --- a/src/contract_core/contract_exec.h +++ b/src/contract_core/contract_exec.h @@ -17,7 +17,7 @@ static volatile char contractLocalsStackLock[NUMBER_OF_CONTRACT_EXECUTION_PROCES static ReadWriteLock contractStateLock[contractCount]; static unsigned char* contractStates[contractCount]; -static unsigned long long contractTotalExecutionTicks[contractCount]; +static volatile long long contractTotalExecutionTicks[contractCount]; bool initContractExec() @@ -26,7 +26,7 @@ bool initContractExec() contractLocalsStack[i].init(); setMem((void*)contractLocalsStackLock, sizeof(contractLocalsStackLock), 0); - setMem(contractTotalExecutionTicks, sizeof(contractTotalExecutionTicks), 0); + setMem((void*)contractTotalExecutionTicks, sizeof(contractTotalExecutionTicks), 0); for (int i = 0; i < contractCount; ++i) { contractStateLock[i].reset(); @@ -151,8 +151,8 @@ struct QpiContextSystemProcedureCall : public QPI::QpiContextProcedureCall const unsigned long long startTick = __rdtsc(); contractSystemProcedures[_currentContractIndex][systemProcId](*this, contractStates[_currentContractIndex]); - contractTotalExecutionTicks[_currentContractIndex] += __rdtsc() - startTick; - + _interlockedadd64(&contractTotalExecutionTicks[_currentContractIndex], __rdtsc() - startTick); + contractStateLock[_currentContractIndex].releaseWrite(); } }; @@ -191,7 +191,7 @@ struct QpiContextUserProcedureCall : public QPI::QpiContextProcedureCall // run procedure const unsigned long long startTick = __rdtsc(); contractUserProcedures[_currentContractIndex][inputType](*this, contractStates[_currentContractIndex], inputBuffer, outputBuffer, localsBuffer); - contractTotalExecutionTicks[_currentContractIndex] += __rdtsc() - startTick; + _interlockedadd64(&contractTotalExecutionTicks[_currentContractIndex], __rdtsc() - startTick); // release lock of contract state contractStateLock[_currentContractIndex].releaseWrite(); @@ -252,7 +252,7 @@ struct QpiContextUserFunctionCall : public QPI::QpiContextFunctionCall // run function const unsigned long long startTick = __rdtsc(); contractUserFunctions[_currentContractIndex][inputType](*this, contractStates[_currentContractIndex], inputBuffer, outputBuffer, localsBuffer); - contractTotalExecutionTicks[_currentContractIndex] += __rdtsc() - startTick; + _interlockedadd64(&contractTotalExecutionTicks[_currentContractIndex], __rdtsc() - startTick); // release lock of contract state contractStateLock[_currentContractIndex].releaseRead(); From d79512ab1583f58515b32915273126521a539851 Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Fri, 3 May 2024 15:15:53 +0200 Subject: [PATCH 27/31] Only set contract state changed flag for procedure calls (not function calls) --- src/contract_core/contract_exec.h | 10 +++++++++- src/qubic.cpp | 4 +--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/contract_core/contract_exec.h b/src/contract_core/contract_exec.h index 78e456af..ef37a6e9 100644 --- a/src/contract_core/contract_exec.h +++ b/src/contract_core/contract_exec.h @@ -19,6 +19,10 @@ static ReadWriteLock contractStateLock[contractCount]; static unsigned char* contractStates[contractCount]; static volatile long long contractTotalExecutionTicks[contractCount]; +// TODO: If we ever have parallel procedure calls (of different contracts), we need to make +// access to contractStateChangeFlags thread-safe +static unsigned long long* contractStateChangeFlags = NULL; + bool initContractExec() { @@ -134,6 +138,7 @@ void QPI::QpiContextProcedureCall::__qpiReleaseStateForWriting(unsigned int cont { ASSERT(contractIndex < contractCount); contractStateLock[contractIndex].releaseWrite(); + contractStateChangeFlags[_currentContractIndex >> 6] |= (1ULL << (_currentContractIndex & 63)); } struct QpiContextSystemProcedureCall : public QPI::QpiContextProcedureCall @@ -153,7 +158,9 @@ struct QpiContextSystemProcedureCall : public QPI::QpiContextProcedureCall contractSystemProcedures[_currentContractIndex][systemProcId](*this, contractStates[_currentContractIndex]); _interlockedadd64(&contractTotalExecutionTicks[_currentContractIndex], __rdtsc() - startTick); + // release lock of contract state and set state to changed contractStateLock[_currentContractIndex].releaseWrite(); + contractStateChangeFlags[_currentContractIndex >> 6] |= (1ULL << (_currentContractIndex & 63)); } }; @@ -193,8 +200,9 @@ struct QpiContextUserProcedureCall : public QPI::QpiContextProcedureCall contractUserProcedures[_currentContractIndex][inputType](*this, contractStates[_currentContractIndex], inputBuffer, outputBuffer, localsBuffer); _interlockedadd64(&contractTotalExecutionTicks[_currentContractIndex], __rdtsc() - startTick); - // release lock of contract state + // release lock of contract state and set state to changed contractStateLock[_currentContractIndex].releaseWrite(); + contractStateChangeFlags[_currentContractIndex >> 6] |= (1ULL << (_currentContractIndex & 63)); // free data on stack (output is unused) contractLocalsStack[_stackIndex].free(); diff --git a/src/qubic.cpp b/src/qubic.cpp index 61fd4929..4ff9ad6c 100644 --- a/src/qubic.cpp +++ b/src/qubic.cpp @@ -128,7 +128,6 @@ static unsigned char contractProcessorState = 0; static unsigned int contractProcessorPhase; static EFI_EVENT contractProcessorEvent; static m256i contractStateDigests[MAX_NUMBER_OF_CONTRACTS * 2 - 1]; -static unsigned long long* contractStateChangeFlags = NULL; static bool targetNextTickDataDigestIsKnown = false; static m256i targetNextTickDataDigest; @@ -1437,8 +1436,7 @@ static void __beginFunctionOrProcedure(const unsigned int functionOrProcedureId) static void __endFunctionOrProcedure(const unsigned int functionOrProcedureId) { - // TODO: move this to procedure call cleanup (so it is not set by function calls) - contractStateChangeFlags[functionOrProcedureId >> (22 + 6)] |= (1ULL << ((functionOrProcedureId >> 22) & 63)); + // TODO } void QPI::QpiContextForInit::__registerUserFunction(USER_FUNCTION userFunction, unsigned short inputType, unsigned short inputSize, unsigned short outputSize, unsigned int localsSize) const From 3d221767fdace96349af614a2ac6bafcf18d948f Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Mon, 6 May 2024 10:29:31 +0200 Subject: [PATCH 28/31] Add some comments --- src/contract_core/contract_exec.h | 16 ++++++++++++++-- src/contracts/Qx.h | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/src/contract_core/contract_exec.h b/src/contract_core/contract_exec.h index ef37a6e9..eb7aad03 100644 --- a/src/contract_core/contract_exec.h +++ b/src/contract_core/contract_exec.h @@ -59,6 +59,7 @@ void acquireContractLocalsStack(int& stackIdx, unsigned int stacksToIgnore = 0) stackIdx = i; } +// Release locked stack (and reset stackIdx) void releaseContractLocalsStack(int& stackIdx) { ASSERT(stackIdx >= 0); @@ -68,6 +69,7 @@ void releaseContractLocalsStack(int& stackIdx) stackIdx = -1; } +// Allocate storage on ContractLocalsStack of QPI execution context void* QPI::QpiContextFunctionCall::__qpiAllocLocals(unsigned int sizeOfLocals) const { ASSERT(_stackIndex >= 0 && _stackIndex < NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS); @@ -79,6 +81,7 @@ void* QPI::QpiContextFunctionCall::__qpiAllocLocals(unsigned int sizeOfLocals) c return p; } +// Free last allocated storage on ContractLocalsStack of QPI execution context void QPI::QpiContextFunctionCall::__qpiFreeLocals() const { ASSERT(_stackIndex >= 0 && _stackIndex < NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS); @@ -87,6 +90,7 @@ void QPI::QpiContextFunctionCall::__qpiFreeLocals() const contractLocalsStack[_stackIndex].free(); } +// Called before one contract calls a function of a different contract const QpiContextFunctionCall& QPI::QpiContextFunctionCall::__qpiConstructContextOtherContractFunctionCall(unsigned int otherContractIndex) const { ASSERT(_stackIndex >= 0 && _stackIndex < NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS); @@ -96,6 +100,7 @@ const QpiContextFunctionCall& QPI::QpiContextFunctionCall::__qpiConstructContext return newContext; } +// Called before one contract calls a procedure of a different contract const QpiContextProcedureCall& QPI::QpiContextProcedureCall::__qpiConstructContextOtherContractProcedureCall(unsigned int otherContractIndex, QPI::sint64 invocationReward) const { ASSERT(_stackIndex >= 0 && _stackIndex < NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS); @@ -107,13 +112,14 @@ const QpiContextProcedureCall& QPI::QpiContextProcedureCall::__qpiConstructConte return newContext; } - +// Called before one contract calls a function or procedure of a different contract void QPI::QpiContextFunctionCall::__qpiFreeContextOtherContract() const { ASSERT(_stackIndex >= 0 && _stackIndex < NUMBER_OF_CONTRACT_EXECUTION_PROCESSORS); contractLocalsStack[_stackIndex].free(); } +// Used to acquire a contract state lock when one contract calls a function of a different contract void* QPI::QpiContextFunctionCall::__qpiAcquireStateForReading(unsigned int contractIndex) const { ASSERT(contractIndex < contractCount); @@ -121,12 +127,14 @@ void* QPI::QpiContextFunctionCall::__qpiAcquireStateForReading(unsigned int cont return contractStates[contractIndex]; } +// Used to release a contract state lock when one contract calls a function of a different contract void QPI::QpiContextFunctionCall::__qpiReleaseStateForReading(unsigned int contractIndex) const { ASSERT(contractIndex < contractCount); contractStateLock[contractIndex].releaseRead(); } +// Used to acquire a contract state lock when one contract calls a procedure of a different contract void* QPI::QpiContextProcedureCall::__qpiAcquireStateForWriting(unsigned int contractIndex) const { ASSERT(contractIndex < contractCount); @@ -134,6 +142,7 @@ void* QPI::QpiContextProcedureCall::__qpiAcquireStateForWriting(unsigned int con return contractStates[contractIndex]; } +// Used to release a contract state lock when one contract calls a procedure of a different contract void QPI::QpiContextProcedureCall::__qpiReleaseStateForWriting(unsigned int contractIndex) const { ASSERT(contractIndex < contractCount); @@ -141,6 +150,8 @@ void QPI::QpiContextProcedureCall::__qpiReleaseStateForWriting(unsigned int cont contractStateChangeFlags[_currentContractIndex >> 6] |= (1ULL << (_currentContractIndex & 63)); } + +// QPI context used to call contract system procedure from qubic core (contract processor) struct QpiContextSystemProcedureCall : public QPI::QpiContextProcedureCall { QpiContextSystemProcedureCall(unsigned int contractIndex) : QPI::QpiContextProcedureCall(contractIndex, NULL_ID, 0) @@ -164,6 +175,7 @@ struct QpiContextSystemProcedureCall : public QPI::QpiContextProcedureCall } }; +// QPI context used to call contract user procedure from qubic core (tick processor) struct QpiContextUserProcedureCall : public QPI::QpiContextProcedureCall { QpiContextUserProcedureCall(unsigned int contractIndex, const m256i& originator, long long invocationReward) : QPI::QpiContextProcedureCall(contractIndex, originator, invocationReward) @@ -213,7 +225,7 @@ struct QpiContextUserProcedureCall : public QPI::QpiContextProcedureCall } }; - +// QPI context used to call contract user function from qubic core (request processor) struct QpiContextUserFunctionCall : public QPI::QpiContextFunctionCall { char* outputBuffer; diff --git a/src/contracts/Qx.h b/src/contracts/Qx.h index 8514b997..bd6ebd43 100644 --- a/src/contracts/Qx.h +++ b/src/contracts/Qx.h @@ -185,7 +185,7 @@ struct QX }; collection<_EntityOrder, 2097152 * X_MULTIPLIER> _entityOrders; - // TODO: change to "locals" variables -> every func/proc can define struct of "locals" that are provided to function (stored on stack structure per processor) + // TODO: change to "locals" variables and remove from state? -> every func/proc can define struct of "locals" that is passed as an argument (stored on stack structure per processor) sint64 _elementIndex, _elementIndex2; id _issuerAndAssetName; _AssetOrder _assetOrder; From eebeaecb0e8bce9467f8048c1bb3672c0d53d6a4 Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Tue, 7 May 2024 10:53:23 +0200 Subject: [PATCH 29/31] Fix Qubic.vcxproj.filters (merge error) --- src/Qubic.vcxproj.filters | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Qubic.vcxproj.filters b/src/Qubic.vcxproj.filters index 709036e0..f06f023d 100644 --- a/src/Qubic.vcxproj.filters +++ b/src/Qubic.vcxproj.filters @@ -122,6 +122,7 @@ addons + contract_core From 2cb65e196c4211b6c1789fcdb0407d51523fd545 Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Tue, 7 May 2024 11:13:00 +0200 Subject: [PATCH 30/31] Docs --- src/contracts/qpi.h | 3 +++ src/network_messages/entity.h | 3 +++ 2 files changed, 6 insertions(+) diff --git a/src/contracts/qpi.h b/src/contracts/qpi.h index ae19e441..a9683b45 100644 --- a/src/contracts/qpi.h +++ b/src/contracts/qpi.h @@ -5960,7 +5960,10 @@ namespace QPI { id id; sint64 incomingAmount, outgoingAmount; + + // Numbers of transfers. These may overflow for entities with high traffic, such as Qx. uint32 numberOfIncomingTransfers, numberOfOutgoingTransfers; + uint32 latestIncomingTransferTick, latestOutgoingTransferTick; }; diff --git a/src/network_messages/entity.h b/src/network_messages/entity.h index 35b328f4..0cf19da7 100644 --- a/src/network_messages/entity.h +++ b/src/network_messages/entity.h @@ -6,7 +6,10 @@ struct Entity { m256i publicKey; long long incomingAmount, outgoingAmount; + + // Numbers of transfers. These may overflow for entities with high traffic, such as Qx. unsigned int numberOfIncomingTransfers, numberOfOutgoingTransfers; + unsigned int latestIncomingTransferTick, latestOutgoingTransferTick; }; From 547c9ce7ab7e9fd3ee1376adf5e424fffc8bd287 Mon Sep 17 00:00:00 2001 From: Philipp Werner <22914157+philippwerner@users.noreply.github.com> Date: Tue, 7 May 2024 15:24:22 +0200 Subject: [PATCH 31/31] Update params for epoch 108 (v1.202.0) --- src/public_settings.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/public_settings.h b/src/public_settings.h index ce9d0fd9..fde81059 100644 --- a/src/public_settings.h +++ b/src/public_settings.h @@ -35,12 +35,12 @@ // Config options that should NOT be changed by operators #define VERSION_A 1 -#define VERSION_B 201 +#define VERSION_B 202 #define VERSION_C 0 // Epoch and initial tick for node startup -#define EPOCH 107 -#define TICK 13680000 +#define EPOCH 108 +#define TICK 13820000 #define ARBITRATOR "AFZPUAIYVPNUYGJRQVLUKOPPVLHAZQTGLYAAUUNBXFTVTAMSBKQBLEIEPCVJ"