Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
391060a
Implement qpi.getOracleQuery() and tests
philippwerner Dec 16, 2025
7ca5294
Implement oracle reply commit transaction
philippwerner Dec 17, 2025
aea387c
OracleEngine: add lock for mutal exclusion
philippwerner Dec 23, 2025
95745e5
Refactor: move global tick storage instance to header
philippwerner Jan 5, 2026
a90d567
Create/process reveal tx + notify contract
philippwerner Jan 5, 2026
38f2be1
Change contract notifications to use registry
philippwerner Jan 6, 2026
defb91b
Implement notifications in oracleEngine
philippwerner Jan 6, 2026
2e58367
Implement timeouts in oracle engine
philippwerner Jan 6, 2026
dac80f7
Integrate oracle engine in qubic.cpp
philippwerner Jan 6, 2026
54fe7cc
Reduce OracleQueryMetadata size from 80 to 72 bytes
philippwerner Jan 6, 2026
f3c85d5
Implement first RequestOracleData features
philippwerner Jan 6, 2026
fcecda7
Merge branch 'develop' into feature/2025-12-15-contract-one-time-orac…
philippwerner Jan 6, 2026
45e7b62
Merge branch 'feature/2025-12-09-oracle-machine' into feature/2025-12…
philippwerner Jan 6, 2026
017c75f
Add oracle interface Mock
philippwerner Jan 7, 2026
674639d
Add char enum to conveniently init id from string
philippwerner Jan 7, 2026
c195c22
Set TESTEXC regular price query to value supported by OM
philippwerner Jan 7, 2026
ea7bef4
TESTEXC: Add query/notification to mack oracle interface
philippwerner Jan 7, 2026
feb6a05
Fix bug in processRequestOracleData()
philippwerner Jan 12, 2026
2b90e06
Fix compiler compatibility issue
philippwerner Jan 16, 2026
0678ff4
OracleMock: show stats.
cyber-pc Jan 8, 2026
3fe22d5
Add logging of oracle query status changes
philippwerner Jan 9, 2026
030f993
Fix uninitialized padding in log items
philippwerner Jan 9, 2026
851dc98
TESTEXC: Log in oracle notification procedures
philippwerner Jan 9, 2026
8d2ec92
Fix status type in OracleNotificationInpuut
philippwerner Jan 9, 2026
b118bc1
Fix ASSERT in logging
philippwerner Jan 9, 2026
3a955c3
Fix loop in processOracleReplyCommitTransaction()
philippwerner Jan 13, 2026
6138676
Fix bug in processRequestOracleData()
philippwerner Jan 13, 2026
79464ba
Fix deadlock in processRequestOracleData()
philippwerner Jan 13, 2026
7f1770f
Add important reminder about debug logging
philippwerner Jan 16, 2026
0a3182a
Implement more oracle query QPI functions
philippwerner Jan 16, 2026
b2b03bb
Log avg tick count until oracle reply tx get executed
philippwerner Jan 16, 2026
cebab8f
Set oracle reply tx publication offsets
philippwerner Jan 13, 2026
9c47cc2
Add debug output to oracle query pipeline
philippwerner Jan 9, 2026
f902702
More oracle query stats and request/response for those
philippwerner Jan 19, 2026
565af82
Fix typo in oracle query stats struct
philippwerner Jan 19, 2026
211f36a
Extend and fix stats
philippwerner Jan 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Qubic.vcxproj
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
<ClInclude Include="oracle_core\oracle_engine.h" />
<ClInclude Include="oracle_core\oracle_interfaces_def.h" />
<ClInclude Include="oracle_core\oracle_transactions.h" />
<ClInclude Include="oracle_interfaces\Mock.h" />
<ClInclude Include="oracle_interfaces\Price.h" />
<ClInclude Include="platform\assert.h" />
<ClInclude Include="platform\concurrency.h" />
Expand Down
5 changes: 4 additions & 1 deletion src/Qubic.vcxproj.filters
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,9 @@
<ClInclude Include="contract_core\execution_time_accumulator.h">
<Filter>contract_core</Filter>
</ClInclude>
<ClInclude Include="oracle_interfaces\Mock.h">
<Filter>oracle_interfaces</Filter>
</ClInclude>
</ItemGroup>
<ItemGroup>
<Filter Include="platform">
Expand Down Expand Up @@ -382,4 +385,4 @@
<Filter>platform</Filter>
</MASM>
</ItemGroup>
</Project>
</Project>
45 changes: 45 additions & 0 deletions src/contract_core/contract_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -441,3 +441,48 @@ static void initializeContracts()
#endif
}

// Class for registering and looking up user procedures independently of input type, for example for notifications
class UserProcedureRegistry
{
public:
struct UserProcedureData
{
USER_PROCEDURE procedure;
unsigned int contractIndex;
unsigned int localsSize;
unsigned short inputSize;
unsigned short outputSize;
};

void init()
{
setMemory(*this, 0);
}

bool add(unsigned int procedureId, const UserProcedureData& data)
{
const unsigned int cnt = (unsigned int)idToIndex.population();
if (cnt >= idToIndex.capacity())
return false;

copyMemory(userProcData[cnt], data);
idToIndex.set(procedureId, cnt);

return true;
}

const UserProcedureData* get(unsigned int procedureId) const
{
unsigned int idx;
if (!idToIndex.get(procedureId, idx))
return nullptr;
return userProcData + idx;
}

protected:
UserProcedureData userProcData[MAX_CONTRACT_PROCEDURES_REGISTERED];
QPI::HashMap<unsigned int, unsigned int, MAX_CONTRACT_PROCEDURES_REGISTERED> idToIndex;
};

// For registering and looking up user procedures independently of input type (for notifications), initialized by initContractExec()
GLOBAL_VAR_DECL UserProcedureRegistry* userProcedureRegistry GLOBAL_VAR_INIT(nullptr);
38 changes: 24 additions & 14 deletions src/contract_core/contract_exec.h
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,12 @@ static bool initContractExec()
if (!contractActionTracker.allocBuffer())
return false;

if (!allocPoolWithErrorLog(L"userProcedureRegistry", sizeof(*userProcedureRegistry), (void**)&userProcedureRegistry, __LINE__))
{
return false;
}
userProcedureRegistry->init();

return true;
}

Expand All @@ -218,6 +224,9 @@ static void deinitContractExec()
freePool(contractStateChangeFlags);
}

if (userProcedureRegistry)
freePool(userProcedureRegistry);

contractActionTracker.freeBuffer();
}

Expand Down Expand Up @@ -917,6 +926,16 @@ void QPI::QpiContextForInit::__registerUserProcedure(USER_PROCEDURE userProcedur
contractUserProcedureLocalsSizes[_currentContractIndex][inputType] = localsSize;
}

void QPI::QpiContextForInit::__registerUserProcedureNotification(USER_PROCEDURE userProcedure, unsigned int procedureId, unsigned short inputSize, unsigned short outputSize, unsigned int localsSize) const
{
ASSERT(userProcedureRegistry);
if (!userProcedureRegistry->add(procedureId, { userProcedure, _currentContractIndex, localsSize, inputSize, outputSize }))
{
#if !defined(NDEBUG)
addDebugMessage(L"__registerUserProcedureNotification() failed. You should increase MAX_CONTRACT_PROCEDURES_REGISTERED.");
#endif
}
}


// QPI context used to call contract system procedure from qubic core (contract processor)
Expand Down Expand Up @@ -1266,15 +1285,6 @@ struct QpiContextUserFunctionCall : public QPI::QpiContextFunctionCall
};


struct UserProcedureNotification
{
unsigned int contractIndex;
USER_PROCEDURE procedure;
const void* inputPtr;
unsigned short inputSize;
unsigned int localsSize;
};

// QPI context used to call contract user procedure as a notification from qubic core (contract processor).
// This means, it isn't triggered by a transaction, but following an event after having setup the notification
// callback in the contract code.
Expand All @@ -1283,16 +1293,16 @@ struct UserProcedureNotification
// The procedure pointer, the expected inputSize, and the expected localsSize, which are passed via
// UserProcedureNotification, must be consistent. The code using notifications is responible for ensuring that.
// Use cases:
// - oracle notifications (managed by oracleEngine)
// - oracle notifications (managed by oracleEngine and userProcedureRegistry)
struct QpiContextUserProcedureNotificationCall : public QPI::QpiContextProcedureCall
{
QpiContextUserProcedureNotificationCall(const UserProcedureNotification& notification) : QPI::QpiContextProcedureCall(notif.contractIndex, NULL_ID, 0, USER_PROCEDURE_NOTIFICATION_CALL), notif(notification)
QpiContextUserProcedureNotificationCall(const UserProcedureRegistry::UserProcedureData& notification) : QPI::QpiContextProcedureCall(notification.contractIndex, NULL_ID, 0, USER_PROCEDURE_NOTIFICATION_CALL), notif(notification)
{
contractActionTracker.init();
}

// Run user procedure notification
void call()
void call(const void* inputPtr)
{
ASSERT(_currentContractIndex < contractCount);

Expand Down Expand Up @@ -1330,7 +1340,7 @@ struct QpiContextUserProcedureNotificationCall : public QPI::QpiContextProcedure
__qpiAbort(ContractErrorAllocInputOutputFailed);
}
char* locals = input + notif.inputSize;
copyMem(input, notif.inputPtr, notif.inputSize);
copyMem(input, inputPtr, notif.inputSize);
setMem(locals, notif.localsSize, 0);

// call user procedure
Expand All @@ -1353,5 +1363,5 @@ struct QpiContextUserProcedureNotificationCall : public QPI::QpiContextProcedure
}

private:
const UserProcedureNotification& notif;
const UserProcedureRegistry::UserProcedureData& notif;
};
2 changes: 1 addition & 1 deletion src/contract_core/execution_time_accumulator.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ class ExecutionTimeAccumulator
math_lib::sadd(contractExecutionTimePerPhase[contractExecutionTimeActiveArrayIndex][contractIndex], timeMicroSeconds);
RELEASE(lock);

#if !defined(NDEBUG) && !defined(NO_UEFI)
#if !defined(NDEBUG) && !defined(NO_UEFI) && 0
CHAR16 dbgMsgBuf[128];
setText(dbgMsgBuf, L"Execution time added for contract ");
appendNumber(dbgMsgBuf, contractIndex, FALSE);
Expand Down
3 changes: 3 additions & 0 deletions src/contract_core/pre_qpi_def.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@ constexpr unsigned short MAX_NESTED_CONTRACT_CALLS = 10;
// Size of the contract action tracker, limits the number of transfers that one contract call can execute.
constexpr unsigned long long CONTRACT_ACTION_TRACKER_SIZE = 16 * 1024 * 1024;

// Maximum number of contract procedures that may be registered, e.g. for user procedure notifications
constexpr unsigned int MAX_CONTRACT_PROCEDURES_REGISTERED = 16 * 1024;


static void __beginFunctionOrProcedure(const unsigned int); // TODO: more human-readable form of function ID?
static void __endFunctionOrProcedure(const unsigned int);
Expand Down
34 changes: 19 additions & 15 deletions src/contract_core/qpi_oracle_impl.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@


template <typename OracleInterface, typename ContractStateType, typename LocalsType>
QPI::sint64 QPI::QpiContextProcedureCall::queryOracle(
QPI::sint64 QPI::QpiContextProcedureCall::__qpiQueryOracle(
const OracleInterface::OracleQuery& query,
void (*notificationCallback)(const QPI::QpiContextProcedureCall& qpi, ContractStateType& state, QPI::OracleNotificationInput<OracleInterface>& input, QPI::NoData& output, LocalsType& locals),
void (*notificationProcPtr)(const QPI::QpiContextProcedureCall& qpi, ContractStateType& state, OracleNotificationInput<OracleInterface>& input, NoData& output, LocalsType& locals),
unsigned int notificationProcId,
uint32 timeoutMillisec
) const
{
Expand All @@ -28,9 +29,16 @@ QPI::sint64 QPI::QpiContextProcedureCall::queryOracle(
const QPI::uint16 contractIndex = static_cast<QPI::uint16>(this->_currentContractIndex);

// check callback
if (!notificationCallback || ContractStateType::__contract_index != contractIndex)
if (!notificationProcPtr || ContractStateType::__contract_index != contractIndex)
return -1;

// check vs registry of user procedures for notification
const UserProcedureRegistry::UserProcedureData* procData;
if (!userProcedureRegistry || !(procData = userProcedureRegistry->get(notificationProcId)) || procData->procedure != (USER_PROCEDURE)notificationProcPtr)
return -1;
ASSERT(procData->inputSize == sizeof(OracleNotificationInput<OracleInterface>));
ASSERT(procData->localsSize == sizeof(LocalsType));

// get and destroy fee (not adding to contracts execution fee reserve)
sint64 fee = OracleInterface::getQueryFee(query);
int contractSpectrumIdx = ::spectrumIndex(this->_currentContractId);
Expand All @@ -39,8 +47,7 @@ QPI::sint64 QPI::QpiContextProcedureCall::queryOracle(
// try to start query
QPI::sint64 queryId = oracleEngine.startContractQuery(
contractIndex, OracleInterface::oracleInterfaceIndex,
&query, sizeof(query), timeoutMillisec,
(USER_PROCEDURE)notificationCallback, sizeof(LocalsType));
&query, sizeof(query), timeoutMillisec, notificationProcId);
if (queryId >= 0)
{
// success
Expand All @@ -55,17 +62,18 @@ QPI::sint64 QPI::QpiContextProcedureCall::queryOracle(
input->queryId = -1;
QPI::NoData output;
auto* locals = (LocalsType*)__qpiAllocLocals(sizeof(LocalsType));
notificationCallback(*this, *state, *input, output, *locals);
notificationProcPtr(*this, *state, *input, output, *locals);
__qpiFreeLocals();
__qpiFreeLocals();
return -1;
}

template <typename OracleInterface, typename ContractStateType, typename LocalsType>
inline QPI::sint32 QPI::QpiContextProcedureCall::subscribeOracle(
inline QPI::sint32 QPI::QpiContextProcedureCall::__qpiSubscribeOracle(
const OracleInterface::OracleQuery& query,
void (*notificationCallback)(const QPI::QpiContextProcedureCall& qpi, ContractStateType& state, OracleNotificationInput<OracleInterface>& input, NoData& output, LocalsType& locals),
void (*notificationProcPtr)(const QPI::QpiContextProcedureCall& qpi, ContractStateType& state, OracleNotificationInput<OracleInterface>& input, NoData& output, LocalsType& locals),
QPI::uint32 notificationIntervalInMilliseconds,
unsigned int notificationProcId,
bool notifyWithPreviousReply
) const
{
Expand All @@ -85,20 +93,16 @@ inline bool QPI::QpiContextProcedureCall::unsubscribeOracle(
template <typename OracleInterface>
bool QPI::QpiContextFunctionCall::getOracleQuery(QPI::sint64 queryId, OracleInterface::OracleQuery& query) const
{
// TODO
return false;
return oracleEngine.getOracleQuery(queryId, &query, sizeof(query));
}

template <typename OracleInterface>
bool QPI::QpiContextFunctionCall::getOracleReply(QPI::sint64 queryId, OracleInterface::OracleReply& reply) const
{
// TODO
return false;
return oracleEngine.getOracleReply(queryId, &reply, sizeof(reply));
}

template <typename OracleInterface>
inline QPI::uint8 QPI::QpiContextFunctionCall::getOracleQueryStatus(sint64 queryId) const
{
// TODO
return ORACLE_QUERY_STATUS_UNKNOWN;
return oracleEngine.getOracleReplygetOracleQueryStatus(queryId);
}
Loading