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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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;
};
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
27 changes: 17 additions & 10 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,8 +93,7 @@ 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>
Expand Down
57 changes: 52 additions & 5 deletions src/contracts/TestExampleC.h
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ struct TESTEXC : public ContractBase

PUBLIC_PROCEDURE(QueryPriceOracle)
{
output.oracleQueryId = qpi.queryOracle<OI::Price>(input.priceOracleQuery, NotifyPriceOracleReply, input.timeoutMilliseconds);
output.oracleQueryId = QUERY_ORACLE(OI::Price, input.priceOracleQuery, NotifyPriceOracleReply, input.timeoutMilliseconds);
if (output.oracleQueryId < 0)
{
// error
Expand All @@ -234,7 +234,7 @@ struct TESTEXC : public ContractBase

PUBLIC_PROCEDURE(SubscribePriceOracle)
{
output.oracleSubscriptionId = qpi.subscribeOracle<OI::Price>(input.priceOracleQuery, NotifyPriceOracleReply, input.subscriptionIntervalMinutes);
output.oracleSubscriptionId = SUBSCRIBE_ORACLE(OI::Price, input.priceOracleQuery, NotifyPriceOracleReply, input.subscriptionIntervalMinutes, true);
if (output.oracleSubscriptionId < 0)
{
// error
Expand Down Expand Up @@ -271,18 +271,62 @@ struct TESTEXC : public ContractBase
}
}

typedef OracleNotificationInput<OI::Mock> NotifyMockOracleReply_input;
typedef NoData NotifyMockOracleReply_output;
struct NotifyMockOracleReply_locals
{
OI::Mock::OracleQuery query;
uint32 queryExtraData;
};

PRIVATE_PROCEDURE_WITH_LOCALS(NotifyMockOracleReply)
{
if (input.status == ORACLE_QUERY_STATUS_SUCCESS)
{
// get and use query info if needed
if (!qpi.getOracleQuery<OI::Mock>(input.queryId, locals.query))
return;

ASSERT(locals.query.value == input.reply.echoedValue);
ASSERT(locals.query.value == input.reply.doubledValue / 2);

// TODO: log
}
else
{
// handle failure ...

// TODO: log
}
}

struct END_TICK_locals
{
OI::Price::OracleQuery priceOracleQuery;
OI::Mock::OracleQuery mockOracleQuery;
sint64 oracleQueryId;
};

END_TICK_WITH_LOCALS()
{
// Query oracle
if (qpi.tick() % 2 == 0)
// Query oracles
if (qpi.tick() % 10 == 0)
{
locals.oracleQueryId = qpi.queryOracle<OI::Price>(locals.priceOracleQuery, NotifyPriceOracleReply, 20000);
// Setup query (in extra scope limit scope of using namespace Ch
{
using namespace Ch;
locals.priceOracleQuery.oracle = OI::Price::getMockOracleId();
locals.priceOracleQuery.currency1 = id(B, T, C, null, null);
locals.priceOracleQuery.currency2 = id(U, S, D, null, null);
locals.priceOracleQuery.timestamp = qpi.now();
}

locals.oracleQueryId = QUERY_ORACLE(OI::Price, locals.priceOracleQuery, NotifyPriceOracleReply, 20000);
}
if (qpi.tick() % 2 == 1)
{
locals.mockOracleQuery.value = qpi.tick();
QUERY_ORACLE(OI::Mock, locals.mockOracleQuery, NotifyMockOracleReply, 8000);
}
}

Expand All @@ -300,5 +344,8 @@ struct TESTEXC : public ContractBase
REGISTER_USER_PROCEDURE(QpiBidInIpo, 30);

REGISTER_USER_PROCEDURE(QueryPriceOracle, 100);

REGISTER_USER_PROCEDURE_NOTIFICATION(NotifyPriceOracleReply);
REGISTER_USER_PROCEDURE_NOTIFICATION(NotifyMockOracleReply);
}
};
Loading