From 02f81660a77341ed86c8a1c4772cd1fc202a7dae Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 26 Apr 2025 13:38:01 +0300 Subject: [PATCH 1/8] add: mutex calls --- .../mutex/calls/constructExecOpsCall.ts | 70 ++++++++ .../mutex/calls/constructGetAccessCall.ts | 26 --- .../onchain/mutex/calls/constructLockCall.ts | 4 +- .../onchain/mutex/calls/constructTryOpCall.ts | 23 --- .../mutex/calls/constructUnlockCall.ts | 4 +- .../cwait/src/onchain/mutex/calls/index.ts | 3 +- .../cwait/src/onchain/mutex/claims/index.ts | 2 +- .../onchain/mutex/claims/mutexBlockAccess.ts | 41 ----- .../src/onchain/mutex/claims/mutexExecOps.ts | 43 +++++ packages/cwait/src/onchain/mutex/index.ts | 13 +- .../src/onchain/mutex/methods/execOps.ts | 117 +++++++++++++ .../src/onchain/mutex/methods/getAccess.ts | 32 ---- .../cwait/src/onchain/mutex/methods/index.ts | 4 +- .../cwait/src/onchain/mutex/methods/names.ts | 5 +- .../mutex/methods/preReadExecTakeOps.ts | 49 ++++++ .../onchain/mutex/methods/saveExecOpResult.ts | 33 ++++ .../cwait/src/onchain/mutex/methods/tryOp.ts | 30 ---- packages/cwait/src/onchain/mutex/types.ts | 29 ++-- .../src/onchain/promisifiedOps/ops/store.ts | 4 +- .../onchain/utils/constructTx/constructTx.ts | 4 +- .../utils/constructTx/prepareInThreadTxs.ts | 155 ++++++++++++------ .../utils/constructTx/prepareOutThreadTxs.ts | 149 ++++++++++------- .../onchain/utils/constructTx/prepareTxs.ts | 38 +++-- .../onchain/utils/constructTx/splitTasks.ts | 64 +++----- .../cwait/src/onchain/utils/typeGuards.ts | 44 +++++ packages/cwait/src/types.ts | 33 +++- 26 files changed, 672 insertions(+), 347 deletions(-) create mode 100644 packages/cwait/src/onchain/mutex/calls/constructExecOpsCall.ts delete mode 100644 packages/cwait/src/onchain/mutex/calls/constructGetAccessCall.ts delete mode 100644 packages/cwait/src/onchain/mutex/calls/constructTryOpCall.ts delete mode 100644 packages/cwait/src/onchain/mutex/claims/mutexBlockAccess.ts create mode 100644 packages/cwait/src/onchain/mutex/claims/mutexExecOps.ts create mode 100644 packages/cwait/src/onchain/mutex/methods/execOps.ts delete mode 100644 packages/cwait/src/onchain/mutex/methods/getAccess.ts create mode 100644 packages/cwait/src/onchain/mutex/methods/preReadExecTakeOps.ts create mode 100644 packages/cwait/src/onchain/mutex/methods/saveExecOpResult.ts delete mode 100644 packages/cwait/src/onchain/mutex/methods/tryOp.ts diff --git a/packages/cwait/src/onchain/mutex/calls/constructExecOpsCall.ts b/packages/cwait/src/onchain/mutex/calls/constructExecOpsCall.ts new file mode 100644 index 0000000..af1d981 --- /dev/null +++ b/packages/cwait/src/onchain/mutex/calls/constructExecOpsCall.ts @@ -0,0 +1,70 @@ +import { constructContractRef, ContractIssuer, CwebStore, GStore, GTake } from '@coinweb/contract-kit'; +import { CwebTake } from '@coinweb/contract-kit/dist/types/operations/take'; + +import { PreparedExtendedStoreOp } from '../../../types'; +import { + constructMutexExecOpsBlock, + constructMutexExecOpsClaimTake, + constructMutexLockClaimRangeRead, +} from '../claims'; +import { execOpsMethodName } from '../methods'; +import { MutexExecOpArgs } from '../types'; + +export const constructExecOpsCall = ({ + issuer, + ops, + processId, + execId, +}: { + issuer: ContractIssuer; + ops: (GTake | PreparedExtendedStoreOp)[]; + processId: string; + execId?: string; +}) => { + const opsFee = BigInt(ops.length) * 100n; + const execCallInnerFee = 700n; + + const takeOpsAdditionalFee = BigInt(ops.filter((op) => 'TakeOp' in op).length) * 100n; + const prepareTakeOpsInnerFee = takeOpsAdditionalFee > 0n ? 700n : 0n; + + const storedCweb = ops + .filter((op) => 'StoreOp' in op) + .reduce((total, op) => { + if (op.providedCweb) { + return total + op.providedCweb; + } + return total + BigInt(op.StoreOp.fees_stored); + }, 0n); + + const callFee = 700n; + + const providedCweb = opsFee + execCallInnerFee + takeOpsAdditionalFee + prepareTakeOpsInnerFee + storedCweb + callFee; + const contractArgsFee = 100n; + + const preparedOps: (GTake | GStore)[] = ops.map((op) => { + if ('StoreOp' in op) { + const preparedStoreOp = { ...op }; + delete preparedStoreOp.providedCweb; + return preparedStoreOp; + } + + return op; + }); + + return { + callInfo: { + ref: constructContractRef(issuer, []), + methodInfo: { + methodName: execOpsMethodName, + methodArgs: [preparedOps, processId, execId] satisfies MutexExecOpArgs, + }, + contractInfo: { + providedCweb, + authenticated: null, + }, + contractArgs: [constructMutexLockClaimRangeRead(issuer)], + }, + fee: providedCweb + contractArgsFee, + ops: execId ? ([constructMutexExecOpsBlock(execId, issuer), constructMutexExecOpsClaimTake(execId)] as const) : [], + }; +}; diff --git a/packages/cwait/src/onchain/mutex/calls/constructGetAccessCall.ts b/packages/cwait/src/onchain/mutex/calls/constructGetAccessCall.ts deleted file mode 100644 index 88eec8b..0000000 --- a/packages/cwait/src/onchain/mutex/calls/constructGetAccessCall.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ContractIssuer, constructContractRef } from '@coinweb/contract-kit'; - -import { constructMutexLockClaimRangeRead } from '../claims'; -import { getAccessMethodName } from '../methods'; -import { MutexGetAccessArgs } from '../types'; - -export const constructGetAccessCall = ( - issuer: ContractIssuer, - ...[claimKey, processId, uniqueId]: MutexGetAccessArgs -) => { - return { - callInfo: { - ref: constructContractRef(issuer, []), - methodInfo: { - methodName: getAccessMethodName, - methodArgs: [claimKey, processId, uniqueId] satisfies MutexGetAccessArgs, - }, - contractInfo: { - providedCweb: 800n, - authenticated: null, - }, - contractArgs: [constructMutexLockClaimRangeRead(issuer)], - }, - fee: 900n, - }; -}; diff --git a/packages/cwait/src/onchain/mutex/calls/constructLockCall.ts b/packages/cwait/src/onchain/mutex/calls/constructLockCall.ts index 4c2a69a..aee89c0 100644 --- a/packages/cwait/src/onchain/mutex/calls/constructLockCall.ts +++ b/packages/cwait/src/onchain/mutex/calls/constructLockCall.ts @@ -1,6 +1,6 @@ import { ContractIssuer, constructContractRef } from '@coinweb/contract-kit'; -import { constructMutexBlockAccessClaimTake, constructMutexLockBlock } from '../claims'; +import { constructMutexBlockLockClaimTake, constructMutexLockBlock } from '../claims'; import { lockMethodName } from '../methods'; import { lockFee } from '../settings'; @@ -18,7 +18,7 @@ export const constructLockCall = (issuer: ContractIssuer, lockId: string) => { }, contractArgs: [], }, - ops: [constructMutexLockBlock(lockId, issuer), constructMutexBlockAccessClaimTake(lockId)], + ops: [constructMutexLockBlock(lockId, issuer), constructMutexBlockLockClaimTake(lockId)] as const, fee: lockFee + 200n, }; }; diff --git a/packages/cwait/src/onchain/mutex/calls/constructTryOpCall.ts b/packages/cwait/src/onchain/mutex/calls/constructTryOpCall.ts deleted file mode 100644 index 04d6078..0000000 --- a/packages/cwait/src/onchain/mutex/calls/constructTryOpCall.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { constructContractRef, ContractIssuer } from '@coinweb/contract-kit'; - -import { constructMutexLockClaimRangeRead } from '../claims'; -import { tryOpMethodName } from '../methods'; -import { MutexTryOpArgs } from '../types'; - -export const constructTryOpCall = (issuer: ContractIssuer, ...[op, processId]: MutexTryOpArgs) => { - return { - callInfo: { - ref: constructContractRef(issuer, []), - methodInfo: { - methodName: tryOpMethodName, - methodArgs: [op, processId] satisfies MutexTryOpArgs, - }, - contractInfo: { - providedCweb: 800n, - authenticated: null, - }, - contractArgs: [constructMutexLockClaimRangeRead(issuer)], - }, - fee: 900n, - }; -}; diff --git a/packages/cwait/src/onchain/mutex/calls/constructUnlockCall.ts b/packages/cwait/src/onchain/mutex/calls/constructUnlockCall.ts index 4ce421d..72b6ec6 100644 --- a/packages/cwait/src/onchain/mutex/calls/constructUnlockCall.ts +++ b/packages/cwait/src/onchain/mutex/calls/constructUnlockCall.ts @@ -18,7 +18,9 @@ export const constructUnlockCall = (issuer: ContractIssuer, ...[lockId, timestam }, contractArgs: [], }, - ops: notify ? [constructMutexUnlockBlock(lockId, issuer), constructMutexBlockUnlockClaimTake(lockId)] : [], + ops: notify + ? ([constructMutexUnlockBlock(lockId, issuer), constructMutexBlockUnlockClaimTake(lockId)] as const) + : [], fee: 1200n, }; }; diff --git a/packages/cwait/src/onchain/mutex/calls/index.ts b/packages/cwait/src/onchain/mutex/calls/index.ts index 357971b..04c5a4f 100644 --- a/packages/cwait/src/onchain/mutex/calls/index.ts +++ b/packages/cwait/src/onchain/mutex/calls/index.ts @@ -1,4 +1,3 @@ -export * from './constructGetAccessCall'; export * from './constructLockCall'; -export * from './constructTryOpCall'; +export * from './constructExecOpsCall'; export * from './constructUnlockCall'; diff --git a/packages/cwait/src/onchain/mutex/claims/index.ts b/packages/cwait/src/onchain/mutex/claims/index.ts index c1f0350..588d5db 100644 --- a/packages/cwait/src/onchain/mutex/claims/index.ts +++ b/packages/cwait/src/onchain/mutex/claims/index.ts @@ -1,4 +1,4 @@ -export * from './mutexBlockAccess'; +export * from './mutexExecOps'; export * from './mutexBlockLock'; export * from './mutexBlockUnlock'; export * from './mutexLock'; diff --git a/packages/cwait/src/onchain/mutex/claims/mutexBlockAccess.ts b/packages/cwait/src/onchain/mutex/claims/mutexBlockAccess.ts deleted file mode 100644 index 8c02e2e..0000000 --- a/packages/cwait/src/onchain/mutex/claims/mutexBlockAccess.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { - BlockFilter, - constructBlock, - constructClaim, - constructClaimKey, - constructStore, - constructTake, - ContractIssuer, - CwebStore, - GStore, -} from '@coinweb/contract-kit'; - -import { MutexAccessResult, MutexAccessStatus } from '../types'; - -export const mutexBlockAccessKey = 'mutex_block_access'; - -export const constructMutexBlockAccessClaimKey = (uniqueId: string) => - constructClaimKey([mutexBlockAccessKey], [uniqueId]); - -export const constructMutexBlockAccessClaim = (uniqueId: string, status: MutexAccessStatus) => - constructClaim(constructMutexBlockAccessClaimKey(uniqueId), { status } satisfies MutexAccessResult, '0x0'); - -export const constructMutexBlockAccessClaimStore = (uniqueId: string, status: MutexAccessStatus): GStore => - constructStore(constructMutexBlockAccessClaim(uniqueId, status)); - -export const constructMutexBlockAccessClaimTake = (uniqueId: string) => - constructTake(constructMutexBlockAccessClaimKey(uniqueId)); - -export const constructMutexBlockAccessBlockFilter = (lockId: string, issuer: ContractIssuer): BlockFilter => { - const { first_part: first, second_part: second } = constructMutexBlockAccessClaimKey(lockId); - - return { - issuer, - first, - second, - }; -}; - -export const constructMutexAccessBlock = (lockId: string, issuer: ContractIssuer) => { - return constructBlock([constructMutexBlockAccessBlockFilter(lockId, issuer)]); -}; diff --git a/packages/cwait/src/onchain/mutex/claims/mutexExecOps.ts b/packages/cwait/src/onchain/mutex/claims/mutexExecOps.ts new file mode 100644 index 0000000..e3b4d47 --- /dev/null +++ b/packages/cwait/src/onchain/mutex/claims/mutexExecOps.ts @@ -0,0 +1,43 @@ +import { + BlockFilter, + constructBlock, + constructClaim, + constructClaimKey, + constructStore, + constructTake, + ContractIssuer, + CwebStore, + GStore, +} from '@coinweb/contract-kit'; +import { toHex } from 'lib/shared'; + +import { MutexExecOpsResult } from '../types'; + +export const mutexExecOpsKey = 'mutex_exec_ops'; + +export const constructMutexExecOpsClaimKey = (execId: string) => constructClaimKey([mutexExecOpsKey], [execId]); + +export const constructMutexExecOpsClaim = (execId: string, result: MutexExecOpsResult, storeCweb: bigint) => + constructClaim(constructMutexExecOpsClaimKey(execId), result, toHex(storeCweb)); + +export const constructMutexExecOpsClaimStore = ( + execId: string, + result: MutexExecOpsResult, + storeCweb: bigint = 0n +): GStore => constructStore(constructMutexExecOpsClaim(execId, result, storeCweb)); + +export const constructMutexExecOpsClaimTake = (execId: string) => constructTake(constructMutexExecOpsClaimKey(execId)); + +export const constructMutexExecOpsFilter = (lockId: string, issuer: ContractIssuer): BlockFilter => { + const { first_part: first, second_part: second } = constructMutexExecOpsClaimKey(lockId); + + return { + issuer, + first, + second, + }; +}; + +export const constructMutexExecOpsBlock = (lockId: string, issuer: ContractIssuer) => { + return constructBlock([constructMutexExecOpsFilter(lockId, issuer)]); +}; diff --git a/packages/cwait/src/onchain/mutex/index.ts b/packages/cwait/src/onchain/mutex/index.ts index c5a0114..004e773 100644 --- a/packages/cwait/src/onchain/mutex/index.ts +++ b/packages/cwait/src/onchain/mutex/index.ts @@ -1,20 +1,27 @@ import { execLockMethodName, - getAccessMethodName, + execOps, + execOpsMethodName, lockMethodName, mutexExecLock, - mutexGetAccess, mutexLock, mutexUnlock, + preReadExecTakeOpsMethodName, + saveExecOpResult, + saveExecOpResultMethodName, unlockMethodName, } from './methods'; +import { preReadExecTakeOps } from './methods/preReadExecTakeOps'; export * from './claims'; export * from './types'; +export * from './calls'; export const mutexMethods = { [execLockMethodName]: mutexExecLock, - [getAccessMethodName]: mutexGetAccess, [lockMethodName]: mutexLock, [unlockMethodName]: mutexUnlock, + [execOpsMethodName]: execOps, + [preReadExecTakeOpsMethodName]: preReadExecTakeOps, + [saveExecOpResultMethodName]: saveExecOpResult, }; diff --git a/packages/cwait/src/onchain/mutex/methods/execOps.ts b/packages/cwait/src/onchain/mutex/methods/execOps.ts new file mode 100644 index 0000000..31e2842 --- /dev/null +++ b/packages/cwait/src/onchain/mutex/methods/execOps.ts @@ -0,0 +1,117 @@ +import { + constructContinueTx, + constructContractRef, + constructRead, + Context, + extractContractArgs, + extractRead, + PreparedOperation, +} from '@coinweb/contract-kit'; +import { getCallParameters, getContractArguments, getContractIssuer } from 'lib/onchain'; +import { TypedClaim } from 'lib/shared'; + +import { constructMutexExecOpsClaimStore } from '../claims'; +import { MutexLockState, MutexExecOpArgs, MutexSaveExecOpResultArgs, MutexPreReadTakeOpsArgs } from '../types'; +import { isMatchLockKeys } from '../utils'; + +import { saveExecOpResultMethodName } from './names'; + +export const execOps = (context: Context) => { + const { availableCweb } = getCallParameters(context); + const issuer = getContractIssuer(context); + + const [ops, processId, execId] = getContractArguments(context); + + const lockQueue = extractRead(extractContractArgs(context.tx)[0])?.map( + ({ content }) => content as TypedClaim + ); + + let availableOps: PreparedOperation[] = []; + const unavailableIndexes: number[] = []; + + if (!lockQueue) { + availableOps = ops; + } else { + ops.forEach((op, i) => { + const claimKey = 'StoreOp' in op ? op.StoreOp.key : op.TakeOp.key; + const isLockedByOtherProcess = lockQueue.some( + ({ body }) => + body.locked && body.processId !== processId && body.keys.some((key) => isMatchLockKeys(key, claimKey)) + ); + + if (isLockedByOtherProcess) { + unavailableIndexes.push(i); + } else { + availableOps.push(op); + } + }); + } + + if (availableOps.length === 0) { + return execId + ? [ + constructContinueTx(context, [ + constructMutexExecOpsClaimStore(execId, new Array(ops.length).fill({ ok: false })), + ]), + ] + : []; + } + + if (!execId) { + return [constructContinueTx(context, availableOps)]; + } + + const preReadTakeOps = availableOps.filter((op) => 'TakeOp' in op).map((op) => constructRead(issuer, op.TakeOp.key)); + + if (preReadTakeOps.length > 0) { + const fee = 700n + BigInt(preReadTakeOps.length) * 100n; + + return [ + constructContinueTx( + context, + [], + [ + { + callInfo: { + ref: constructContractRef(issuer, []), + methodInfo: { + methodName: saveExecOpResultMethodName, + methodArgs: [execId, unavailableIndexes, availableOps] satisfies MutexPreReadTakeOpsArgs, + }, + contractInfo: { + providedCweb: availableCweb - fee, + authenticated: null, + }, + contractArgs: preReadTakeOps, + }, + }, + ] + ), + ]; + } + + const fee = 700n + BigInt(availableOps.length) * 100n; + + return [ + constructContinueTx( + context, + [], + [ + { + callInfo: { + ref: constructContractRef(issuer, []), + methodInfo: { + methodName: saveExecOpResultMethodName, + methodArgs: [execId, unavailableIndexes] satisfies MutexSaveExecOpResultArgs, + }, + contractInfo: { + providedCweb: availableCweb - fee, + authenticated: null, + }, + contractArgs: availableOps, + }, + }, + ] + ), + ]; +}; diff --git a/packages/cwait/src/onchain/mutex/methods/getAccess.ts b/packages/cwait/src/onchain/mutex/methods/getAccess.ts deleted file mode 100644 index a3365ad..0000000 --- a/packages/cwait/src/onchain/mutex/methods/getAccess.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { constructContinueTx, Context, extractContractArgs, extractRead } from '@coinweb/contract-kit'; -import { getContractArguments } from 'lib/onchain'; -import { TypedClaim } from 'lib/shared'; - -import { constructMutexBlockAccessClaimStore } from '../claims'; -import { MutexAccessStatus, MutexGetAccessArgs, MutexLockState } from '../types'; -import { isMatchLockKeys } from '../utils'; - -export const mutexGetAccess = (context: Context) => { - const [claimKey, processId, uniqueId] = getContractArguments(context); - - const lockQueue = extractRead(extractContractArgs(context.tx)[0])?.map( - ({ content }) => content as TypedClaim - ); - - if (!lockQueue) { - throw new Error('No lock queue found'); - } - - const isLockedByOtherProcess = lockQueue.some( - ({ body }) => body.locked && body.processId !== processId && body.keys.some((key) => isMatchLockKeys(key, claimKey)) - ); - - return [ - constructContinueTx(context, [ - constructMutexBlockAccessClaimStore( - uniqueId, - isLockedByOtherProcess ? MutexAccessStatus.DENIED : MutexAccessStatus.GRANTED - ), - ]), - ]; -}; diff --git a/packages/cwait/src/onchain/mutex/methods/index.ts b/packages/cwait/src/onchain/mutex/methods/index.ts index 78f9c05..3cbc6ba 100644 --- a/packages/cwait/src/onchain/mutex/methods/index.ts +++ b/packages/cwait/src/onchain/mutex/methods/index.ts @@ -1,7 +1,7 @@ export * from './execLock'; -export * from './getAccess'; export * from './lock'; export * from './names'; export * from './notifyLock'; -export * from './tryOp'; +export * from './execOps'; export * from './unlock'; +export * from './saveExecOpResult'; diff --git a/packages/cwait/src/onchain/mutex/methods/names.ts b/packages/cwait/src/onchain/mutex/methods/names.ts index b430203..ce0c305 100644 --- a/packages/cwait/src/onchain/mutex/methods/names.ts +++ b/packages/cwait/src/onchain/mutex/methods/names.ts @@ -1,6 +1,7 @@ export const lockMethodName = '_mutex_lock'; export const execLockMethodName = '_mutex_execLock'; -export const getAccessMethodName = '_mutex_get_access'; export const unlockMethodName = '_mutex_unlock'; -export const tryOpMethodName = '_mutex_try_op'; export const notifyLockMethodName = '_mutex_notify_lock'; +export const execOpsMethodName = '_mutex_execOps'; +export const saveExecOpResultMethodName = '_mutex_save_exec_op_result'; +export const preReadExecTakeOpsMethodName = '_mutex_pre_read_exec_take_ops'; diff --git a/packages/cwait/src/onchain/mutex/methods/preReadExecTakeOps.ts b/packages/cwait/src/onchain/mutex/methods/preReadExecTakeOps.ts new file mode 100644 index 0000000..6e4fb0c --- /dev/null +++ b/packages/cwait/src/onchain/mutex/methods/preReadExecTakeOps.ts @@ -0,0 +1,49 @@ +import { + constructContinueTx, + constructContractRef, + Context, + extractContractArgs, + extractRead, + passCwebFrom, +} from '@coinweb/contract-kit'; +import { getCallParameters, getContractArguments, getContractIssuer } from 'lib/onchain'; + +import { MutexPreReadTakeOpsArgs, MutexSaveExecOpResultArgs } from '../types'; + +import { saveExecOpResultMethodName } from './names'; + +export const preReadExecTakeOps = (context: Context) => { + const { availableCweb } = getCallParameters(context); + const issuer = getContractIssuer(context); + + const [execId, unavailableIndexes, ops] = getContractArguments(context); + + const storedCweb = extractContractArgs(context.tx) + .map((op) => BigInt(extractRead(op)?.[0]?.content.fees_stored ?? 0)) + .reduce((total, op) => total + op, 0n); + + const fee = 700n + BigInt(ops.length) * 100n; + + return [ + constructContinueTx( + context, + [passCwebFrom(issuer, availableCweb)], + [ + { + callInfo: { + ref: constructContractRef(issuer, []), + methodInfo: { + methodName: saveExecOpResultMethodName, + methodArgs: [execId, unavailableIndexes] satisfies MutexSaveExecOpResultArgs, + }, + contractInfo: { + providedCweb: availableCweb + storedCweb - fee, + authenticated: null, + }, + contractArgs: ops, + }, + }, + ] + ), + ]; +}; diff --git a/packages/cwait/src/onchain/mutex/methods/saveExecOpResult.ts b/packages/cwait/src/onchain/mutex/methods/saveExecOpResult.ts new file mode 100644 index 0000000..a531c9a --- /dev/null +++ b/packages/cwait/src/onchain/mutex/methods/saveExecOpResult.ts @@ -0,0 +1,33 @@ +import { constructContinueTx, Context, extractContractArgs } from '@coinweb/contract-kit'; +import { getCallParameters, getContractArguments } from 'lib/onchain'; + +import { constructMutexExecOpsClaimStore } from '../claims'; +import { MutexExecOpsResult, MutexSaveExecOpResultArgs } from '../types'; + +export const saveExecOpResult = (context: Context) => { + const { availableCweb } = getCallParameters(context); + + const [execId, unavailableIndexes] = getContractArguments(context); + const resolvedOps = extractContractArgs(context.tx); + + const result = new Array(resolvedOps.length + unavailableIndexes.length) + .fill({ ok: false, error: 'Resource is busy' }) + .map((value: MutexExecOpsResult[number], i) => { + if (unavailableIndexes.includes(i)) { + return value; + } + + const resolvedOp = resolvedOps.shift(); + + if (!resolvedOp) { + throw new Error('An error occurred while saving the exec op result'); + } + + return { + ok: true as const, + resolved: resolvedOp, + }; + }); + + return [constructContinueTx(context, [constructMutexExecOpsClaimStore(execId, result, availableCweb - 200n)])]; +}; diff --git a/packages/cwait/src/onchain/mutex/methods/tryOp.ts b/packages/cwait/src/onchain/mutex/methods/tryOp.ts deleted file mode 100644 index ce986d2..0000000 --- a/packages/cwait/src/onchain/mutex/methods/tryOp.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { constructContinueTx, Context, extractContractArgs, extractRead } from '@coinweb/contract-kit'; -import { getContractArguments } from 'lib/onchain'; -import { TypedClaim } from 'lib/shared'; - -import { MutexLockState, MutexTryOpArgs } from '../types'; -import { isMatchLockKeys } from '../utils'; - -export const tryOp = (context: Context) => { - const [op, processId] = getContractArguments(context); - - const claimKey = 'StoreOp' in op ? op.StoreOp.key : op.TakeOp.key; - - const lockQueue = extractRead(extractContractArgs(context.tx)[0])?.map( - ({ content }) => content as TypedClaim - ); - - if (!lockQueue) { - throw new Error('No lock queue found'); - } - - const isLockedByOtherProcess = lockQueue.some( - ({ body }) => body.locked && body.processId !== processId && body.keys.some((key) => isMatchLockKeys(key, claimKey)) - ); - - if (isLockedByOtherProcess) { - return []; - } - - return [constructContinueTx(context, [op])]; -}; diff --git a/packages/cwait/src/onchain/mutex/types.ts b/packages/cwait/src/onchain/mutex/types.ts index 9a54e83..7116b84 100644 --- a/packages/cwait/src/onchain/mutex/types.ts +++ b/packages/cwait/src/onchain/mutex/types.ts @@ -1,4 +1,5 @@ -import { Claim, ClaimKey, GStore, GTake } from '@coinweb/contract-kit'; +import { ClaimKey, CwebStore, GStore, GTake, PreparedOperation, ResolvedOperation } from '@coinweb/contract-kit'; +import { CwebTake } from '@coinweb/contract-kit/dist/types/operations/take'; export type LockedKey = Omit & Partial>; @@ -8,19 +9,23 @@ export type MutexLockState = { processId: string; }; -export enum MutexAccessStatus { - GRANTED = 'granted', - DENIED = 'denied', -} - -export type MutexAccessResult = { - status: MutexAccessStatus; -}; - -export type MutexGetAccessArgs = [claimKey: ClaimKey, processId: string, uniqueId: string]; +export type MutexExecOpsResult = ( + | { + ok: true; + resolved: ResolvedOperation; + } + | { + ok: false; + error: string; + } +)[]; export type MutexUnlockArgs = [lockId: string, timestamp: number, notify?: boolean]; -export type MutexTryOpArgs = [op: GTake | GStore, processId: string]; +export type MutexExecOpArgs = [ops: (GTake | GStore)[], processId: string, execId?: string]; export type MutexNotifyLockArgs = [lockId: string]; + +export type MutexPreReadTakeOpsArgs = [execId: string, unavailableIndexes: number[], ops: PreparedOperation[]]; + +export type MutexSaveExecOpResultArgs = [execId: string, unavailableIndexes: number[]]; diff --git a/packages/cwait/src/onchain/promisifiedOps/ops/store.ts b/packages/cwait/src/onchain/promisifiedOps/ops/store.ts index 57b35d7..15857ec 100644 --- a/packages/cwait/src/onchain/promisifiedOps/ops/store.ts +++ b/packages/cwait/src/onchain/promisifiedOps/ops/store.ts @@ -6,7 +6,7 @@ import { isResolvedSlotOp, isResolvedStoreOp } from '../../utils'; import { pushAwaitedTask } from '../awaited'; import { shiftResolvedOp } from '../resolved'; -export const storeOp = (claim: Claim) => { +export const storeOp = (claim: Claim, storeCweb?: bigint) => { console.log('storeOp'); let opMarkerValue = false; @@ -15,7 +15,7 @@ export const storeOp = (claim: Claim) => { const { op, isOp } = shiftResolvedOp(); if (!isOp) { - pushAwaitedTask(constructStore(claim)); + pushAwaitedTask({ ...constructStore(claim), providedCweb: storeCweb }); opMarkerValue = true; } else { if (isResolvedSlotOp(op)) { diff --git a/packages/cwait/src/onchain/utils/constructTx/constructTx.ts b/packages/cwait/src/onchain/utils/constructTx/constructTx.ts index bf5ae39..df863c8 100644 --- a/packages/cwait/src/onchain/utils/constructTx/constructTx.ts +++ b/packages/cwait/src/onchain/utils/constructTx/constructTx.ts @@ -1,9 +1,9 @@ import { prepareTx } from './prepareTxs'; export const constructTx = (isFullyExecuted: boolean) => { - const { calls } = prepareTx(isFullyExecuted); + const { calls, txFee } = prepareTx(isFullyExecuted, 0n); - const { txs } = prepareTx(isFullyExecuted, calls); + const { txs } = prepareTx(isFullyExecuted, txFee, calls); return txs; }; diff --git a/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts b/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts index 0458c90..a7633ff 100644 --- a/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts +++ b/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts @@ -3,42 +3,52 @@ import { constructContinueTx, constructContractRef, FullCallInfo, + GTake, NewTx, passCwebFrom, PreparedOperation, sendCwebInterface, } from '@coinweb/contract-kit'; -import { CwebBlock } from '@coinweb/contract-kit/dist/esm/operations/block'; -import { GBlock } from '@coinweb/contract-kit/dist/types/operations/generics'; +import { CwebTake } from '@coinweb/contract-kit/dist/types/operations/take'; -import { ExecutorMethodArgs, ResolvedOp, ResolvedSlotOp, Task } from '../../../types'; +import { ExecutorMethodArgs, PreparedExtendedStoreOp, PreparedOp, ResolvedOp, ResolvedSlotOp } from '../../../types'; import { constructFundsClaimRangRead, constructFundsClaimStore } from '../../claims/funds'; import { constructResultBlockFilter, constructResultClaimTake, resultKey } from '../../claims/result'; import { context, getRawContext } from '../../context'; -import { isPreparedBlockOp, isPreparedExecOp } from '../typeGuards'; +import { constructExecOpsCall, constructLockCall, constructUnlockCall } from '../../mutex'; +import { + isPreparedExecOp, + isPreparedLockOp, + isPreparedStoreOp, + isPreparedTakeOp, + isPreparedUnlockOp, +} from '../typeGuards'; +import { uuid } from '../uuid'; export const prepareInThreadTxs = ({ cwebPerCall, outThreadTasksCount, outThreadFee, - tasks, + ops, }: { - tasks: Task[]; + ops: PreparedOp[]; cwebPerCall: bigint; outThreadTasksCount: number; outThreadFee: bigint; }): { txs: NewTx[]; calls: number; + txFee: bigint; } => { - if (!tasks.length) { + if (!ops.length) { const { constructSendCweb } = sendCwebInterface(); const { availableCweb, takeOps, storedCweb } = context.funds; const { user } = context; const restOfAvailableCweb = availableCweb - outThreadFee; - const restOfCweb = restOfAvailableCweb + storedCweb - BigInt(takeOps.length) * 100n - 3000n; + const fee = BigInt(takeOps.length) * 100n - 3000n; + const restOfCweb = restOfAvailableCweb + storedCweb - fee; const txs = restOfCweb > 0n @@ -51,12 +61,15 @@ export const prepareInThreadTxs = ({ ] : []; - return { txs, calls: 0 }; + return { txs, calls: 0, txFee: fee }; } let txFee = 0n; let callsPrepared = 0; - const resolvedSlotOps = new Array(outThreadTasksCount).fill({ SlotOp: 0 }) satisfies ResolvedSlotOp[]; + const resolvedSlotOps = new Array(outThreadTasksCount).fill({ SlotOp: { ok: true } }) satisfies ResolvedSlotOp[]; + + const preparedExecOps: (PreparedExtendedStoreOp | GTake)[] = []; + const excOpsIndexes: number[] = []; const resolvedChildOps: ResolvedOp[] = [...context.ops, ...resolvedSlotOps]; @@ -66,48 +79,77 @@ export const prepareInThreadTxs = ({ //Info for separate child call const childCalls: FullCallInfo[] = []; - //Block ops for separate child call - const childBlocks = tasks - .filter((task): task is { op: GBlock; batchId: number } => isPreparedBlockOp(task.op)) - .map(({ op }) => op); + ops.forEach((op, i) => { + switch (true) { + case isPreparedExecOp(op): { + console.log('Child call info'); + const id = op.ExecOp.id; - tasks.forEach((task) => { - if (isPreparedExecOp(task.op)) { - console.log('Child call info'); - const id = task.op.ExecOp.id; + callArgs.push(constructBlock([constructResultBlockFilter(id)]), constructResultClaimTake(id)); + txFee += 200n; - callArgs.push(constructBlock([constructResultBlockFilter(id)]), constructResultClaimTake(id)); - txFee += 200n; - - childCalls.push({ - callInfo: { - ref: constructContractRef(context.issuer, []), - methodInfo: { - methodName: context.methodName, - methodArgs: [ - context.initialArgs, - [...resolvedChildOps, { ExecOp: { id } }], - context.user, - id, - context.thisId, - true, - ] satisfies ExecutorMethodArgs, + childCalls.push({ + callInfo: { + ref: constructContractRef(context.issuer, []), + methodInfo: { + methodName: context.methodName, + methodArgs: [ + context.initialArgs, + [...resolvedChildOps, { ExecOp: { id } }], + context.user, + id, + context.thisId, + true, + ] satisfies ExecutorMethodArgs, + }, + contractInfo: { + providedCweb: cwebPerCall, + authenticated: null, + }, + contractArgs: [], }, - contractInfo: { - providedCweb: cwebPerCall - 700n, - authenticated: null, - }, - contractArgs: [], - }, - }); + }); - callsPrepared++; - } else { - callArgs.push(task.op); - txFee += 100n; + callsPrepared++; + txFee += 700n; + + break; + } + case isPreparedStoreOp(op): + case isPreparedTakeOp(op): { + preparedExecOps.push(op); + excOpsIndexes.push(i); + break; + } + case isPreparedLockOp(op): { + const { callInfo, fee, ops } = constructLockCall(context.issuer, op.LockOp.lockId); + + childCalls.push({ callInfo }); + callArgs.push(...ops); + txFee += fee; + + break; + } + case isPreparedUnlockOp(op): { + const { callInfo, fee, ops } = constructUnlockCall( + context.issuer, + op.UnlockOp.lockId, + op.UnlockOp.timestamp, + false + ); + + childCalls.push({ callInfo }); + callArgs.push(...ops); + txFee += fee; + + break; + } + default: + callArgs.push(op); + txFee += 100n; } - resolvedChildOps.push({ SlotOp: 0 }); + resolvedChildOps.push({ SlotOp: { ok: true } }); }); const returnTxs: NewTx[] = []; @@ -168,6 +210,7 @@ export const prepareInThreadTxs = ({ context.parentId, context.needSaveResult, takeOps.map((op) => (op.TakeOp.key.second_part as [string])[0]), + excOpsIndexes, ] satisfies ExecutorMethodArgs, }, contractInfo: { @@ -182,10 +225,26 @@ export const prepareInThreadTxs = ({ ); if (childCalls.length) { - returnTxs.push(constructContinueTx(getRawContext(), childBlocks, childCalls)); + returnTxs.push(constructContinueTx(getRawContext(), [], childCalls)); } } } - return { txs: returnTxs, calls: callsPrepared }; + if (preparedExecOps.length > 0) { + const execId = uuid(); + + const { callInfo, fee, ops } = constructExecOpsCall({ + issuer: context.issuer, + ops: preparedExecOps, + processId: context.thisId, + execId, + }); + + childCalls.push({ callInfo }); + txFee += fee; + + callArgs.push(...ops); + } + + return { txs: returnTxs, calls: callsPrepared, txFee }; }; diff --git a/packages/cwait/src/onchain/utils/constructTx/prepareOutThreadTxs.ts b/packages/cwait/src/onchain/utils/constructTx/prepareOutThreadTxs.ts index 390b5af..d04bd3a 100644 --- a/packages/cwait/src/onchain/utils/constructTx/prepareOutThreadTxs.ts +++ b/packages/cwait/src/onchain/utils/constructTx/prepareOutThreadTxs.ts @@ -2,96 +2,125 @@ import { constructContinueTx, constructContractRef, FullCallInfo, + GTake, NewTx, PreparedOperation, } from '@coinweb/contract-kit'; +import { CwebTake } from '@coinweb/contract-kit/dist/types/operations/take'; -import { ExecutorMethodArgs, Task } from '../../../types'; +import { ExecutorMethodArgs, PreparedExtendedStoreOp, PreparedOp } from '../../../types'; import { context, getRawContext } from '../../context'; -import { isPreparedExecOp } from '../typeGuards'; +import { constructLockCall, constructExecOpsCall, constructUnlockCall } from '../../mutex'; +import { + isPreparedBlockOp, + isPreparedExecOp, + isPreparedLockOp, + isPreparedStoreOp, + isPreparedTakeOp, + isPreparedUnlockOp, +} from '../typeGuards'; export const prepareOutThreadTxs = ({ - tasks, + ops, cwebPerCall, }: { - tasks: Task[]; + ops: PreparedOp[]; cwebPerCall: bigint; }): { txs: NewTx[]; - fee: bigint; + txFee: bigint; calls: number; } => { const siblingCallResolvedOps = [...context.ops]; - const siblingTxInfo: { - [batchId: number]: { - calls: FullCallInfo[]; - ops: PreparedOperation[]; - }; - } = {}; + const preparedCalls: FullCallInfo[] = []; + const preparedOps: PreparedOperation[] = []; + const preparedStoreAndTakes: (PreparedExtendedStoreOp | GTake)[] = []; let txFee = 0n; let callsPrepared = 0; - tasks.forEach((task) => { - if (isPreparedExecOp(task.op)) { - console.log('Sibling call info'); + ops.forEach((op) => { + switch (true) { + case isPreparedExecOp(op): { + console.log('Sibling call info'); - const id = task.op.ExecOp.id; + const id = op.ExecOp.id; - const callInfo = { - callInfo: { - ref: constructContractRef(context.issuer, []), - methodInfo: { - methodName: context.methodName, - methodArgs: [ - context.initialArgs, - [...siblingCallResolvedOps, { ExecOp: { id } }], - context.user, - id, - context.parentId ?? context.thisId, - false, - ] satisfies ExecutorMethodArgs, + const callInfo = { + callInfo: { + ref: constructContractRef(context.issuer, []), + methodInfo: { + methodName: context.methodName, + methodArgs: [ + context.initialArgs, + [...siblingCallResolvedOps, { ExecOp: { id } }], + context.user, + id, + context.parentId ?? context.thisId, + false, + ] satisfies ExecutorMethodArgs, + }, + contractInfo: { + providedCweb: cwebPerCall, + authenticated: null, + }, + contractArgs: [], }, - contractInfo: { - providedCweb: cwebPerCall - 700n, - authenticated: null, - }, - contractArgs: [], - }, - }; - - if (siblingTxInfo[task.batchId]) { - siblingTxInfo[task.batchId].calls.push(callInfo); - } else { - siblingTxInfo[task.batchId] = { - calls: [callInfo], - ops: [], }; - } - callsPrepared++; - } else { - if (siblingTxInfo[task.batchId]) { - siblingTxInfo[task.batchId].ops.push(task.op); - } else { - siblingTxInfo[task.batchId] = { - calls: [], - ops: [task.op], - }; - } + preparedCalls.push(callInfo); + txFee += 700n; - txFee += 100n; + callsPrepared++; + break; + } + case isPreparedStoreOp(op): + case isPreparedTakeOp(op): { + preparedStoreAndTakes.push(op); + break; + } + case isPreparedLockOp(op): { + const { callInfo, fee } = constructLockCall(context.issuer, op.LockOp.lockId); + + preparedCalls.push({ callInfo }); + txFee += fee; + + break; + } + case isPreparedUnlockOp(op): { + const { callInfo, fee } = constructUnlockCall(context.issuer, op.UnlockOp.lockId, op.UnlockOp.timestamp, false); + + preparedCalls.push({ callInfo }); + txFee += fee; + + break; + } + case isPreparedBlockOp(op): { + break; + } + default: + preparedOps.push(op); + txFee += 100n; } - siblingCallResolvedOps.push({ SlotOp: 0 }); + siblingCallResolvedOps.push({ SlotOp: { ok: true } }); }); - const siblingTxs = Object.values(siblingTxInfo).map(({ calls, ops }) => - constructContinueTx(getRawContext(), ops, calls) - ); + if (preparedStoreAndTakes.length > 0) { + const { callInfo, fee } = constructExecOpsCall({ + issuer: context.issuer, + ops: preparedStoreAndTakes, + processId: context.thisId, + }); - txFee += BigInt(siblingTxs.length) * 100n; + preparedCalls.push({ callInfo }); + txFee += fee; + } - return { txs: siblingTxs, fee: txFee, calls: callsPrepared }; + return { + txs: [constructContinueTx(getRawContext(), preparedOps, preparedCalls)], + txFee, + calls: callsPrepared, + }; }; diff --git a/packages/cwait/src/onchain/utils/constructTx/prepareTxs.ts b/packages/cwait/src/onchain/utils/constructTx/prepareTxs.ts index cc0ddf4..7e6ec95 100644 --- a/packages/cwait/src/onchain/utils/constructTx/prepareTxs.ts +++ b/packages/cwait/src/onchain/utils/constructTx/prepareTxs.ts @@ -7,7 +7,11 @@ import { prepareInThreadTxs } from './prepareInThreadTxs'; import { prepareOutThreadTxs } from './prepareOutThreadTxs'; import { splitTasks } from './splitTasks'; -export const prepareTx = (isFullyExecuted: boolean, callsCount?: number): { txs: NewTx[]; calls: number } => { +export const prepareTx = ( + isFullyExecuted: boolean, + txFee: bigint, + callsCount?: number +): { txs: NewTx[]; calls: number; txFee: bigint } => { console.log('Calls Count: ', callsCount); const awaitedTasks = getAwaitedTasks(); @@ -16,14 +20,15 @@ export const prepareTx = (isFullyExecuted: boolean, callsCount?: number): { txs: if (!awaitedTasks.length) { if (context.isChild) { - return { txs: [], calls: 0 }; + return { txs: [], calls: 0, txFee: 0n }; } const { constructSendCweb } = sendCwebInterface(); const { availableCweb, takeOps, storedCweb } = context.funds; const { user } = context; - const restOfCweb = availableCweb + storedCweb - BigInt(takeOps.length) * 100n - 3000n; + const fee = BigInt(takeOps.length) * 100n - 3000n; + const restOfCweb = availableCweb + storedCweb - fee; const txs = restOfCweb > 0n @@ -36,26 +41,35 @@ export const prepareTx = (isFullyExecuted: boolean, callsCount?: number): { txs: ] : []; - return { txs, calls: 0 }; + return { txs, calls: 0, txFee: fee }; } - const { inThreadTasks, outThreadTasks } = splitTasks(awaitedTasks, isFullyExecuted); + const { inThreadOps, outThreadOps } = splitTasks(awaitedTasks, isFullyExecuted); const { availableCweb } = context.funds; - const cwebPerCall = availableCweb / BigInt(callsCount || 1); + const cwebPerCall = (availableCweb - txFee) / BigInt(callsCount || 1); + const { txs: outThreadTxs, - fee: outThreadFee, calls: outThreadCallsPrepared, - } = prepareOutThreadTxs({ tasks: outThreadTasks, cwebPerCall }); + txFee: outThreadFee, + } = prepareOutThreadTxs({ ops: outThreadOps, cwebPerCall }); - const { txs: inThreadTxs, calls: inThreadCallsPrepared } = prepareInThreadTxs({ - tasks: inThreadTasks, + const { + txs: inThreadTxs, + calls: inThreadCallsPrepared, + txFee: inThreadFee, + } = prepareInThreadTxs({ + ops: inThreadOps, cwebPerCall, - outThreadTasksCount: outThreadTasks.length, + outThreadTasksCount: outThreadOps.length, outThreadFee, }); - return { txs: [...inThreadTxs, ...outThreadTxs], calls: inThreadCallsPrepared + outThreadCallsPrepared }; + return { + txs: [...inThreadTxs, ...outThreadTxs], + calls: inThreadCallsPrepared + outThreadCallsPrepared, + txFee: inThreadFee + outThreadFee, + }; }; diff --git a/packages/cwait/src/onchain/utils/constructTx/splitTasks.ts b/packages/cwait/src/onchain/utils/constructTx/splitTasks.ts index ee90bf1..2db8275 100644 --- a/packages/cwait/src/onchain/utils/constructTx/splitTasks.ts +++ b/packages/cwait/src/onchain/utils/constructTx/splitTasks.ts @@ -1,48 +1,26 @@ -import { Task } from '../../../types'; -import { isPreparedBlockOp } from '../typeGuards'; +import { PreparedOp, Task } from '../../../types'; export const splitTasks = (awaitedTasks: Task[], isFullyExecuted: boolean) => { - const awaitedTasksBatched: (Task | Task[])[] = []; - const preparedTasks: (Task | Task[])[] = []; - - awaitedTasks.forEach((task) => { - if (task.batchId === -1) { - awaitedTasksBatched.push(task); - return; - } - - const latestTask = awaitedTasksBatched.at(-1); - - if (Array.isArray(latestTask) && latestTask.at(-1)?.batchId === task.batchId) { - latestTask.push(task); - return; - } - - awaitedTasksBatched.push([task]); - }); - - awaitedTasksBatched.forEach((task, i) => { - if ( - i === awaitedTasksBatched.length - 1 || - !Array.isArray(task) || - (task.length > 1 && task.some(({ op }) => isPreparedBlockOp(op))) - ) { - preparedTasks.push(task); - } else { - preparedTasks.push(...task.map((task) => ({ ...task, batchId: -1 }))); - } - }); - - let inThreadTasks: Task[] = []; - let outThreadTasks: Task[] = []; - - if (!isFullyExecuted) { - const preparedCallTasks = preparedTasks.at(-1)!; - inThreadTasks = [...(Array.isArray(preparedCallTasks) ? preparedCallTasks : [preparedCallTasks])]; - outThreadTasks = preparedTasks.slice(0, -1).flat(); - } else { - outThreadTasks = preparedTasks.flat(); + if (awaitedTasks.length === 0) { + return { inThreadOps: [], outThreadOps: [] }; } - return { inThreadTasks, outThreadTasks }; + const inThreadOps: PreparedOp[] = []; + let outThreadOps: PreparedOp[] = []; + + if (!isFullyExecuted) { + const latestTaskBatchId = awaitedTasks.at(-1)!.batchId ?? -1; + + awaitedTasks.forEach((task) => { + if (task.batchId === latestTaskBatchId) { + inThreadOps.push(task.op); + } else { + outThreadOps.push(task.op); + } + }); + } else { + outThreadOps = awaitedTasks.map((task) => task.op); + } + + return { inThreadOps, outThreadOps }; }; diff --git a/packages/cwait/src/onchain/utils/typeGuards.ts b/packages/cwait/src/onchain/utils/typeGuards.ts index ff95a00..b35ae5b 100644 --- a/packages/cwait/src/onchain/utils/typeGuards.ts +++ b/packages/cwait/src/onchain/utils/typeGuards.ts @@ -12,10 +12,14 @@ import { CwebBlock } from '@coinweb/contract-kit/dist/esm/operations/block'; import { CwebCallRefResolved, isResolvedCall } from '@coinweb/contract-kit/dist/esm/operations/call'; import { ResolvedBlock } from '@coinweb/contract-kit/dist/types/operations/block'; import { GBlock, GCall, GRead, GStore, GTake } from '@coinweb/contract-kit/dist/types/operations/generics'; +import { CwebTake } from '@coinweb/contract-kit/dist/types/operations/take'; import { PreparedExecOp, + PreparedExtendedStoreOp, + PreparedLockOp, PreparedOp, + PreparedUnlockOp, ResolvedChildOp, ResolvedExecOp, ResolvedLockOp, @@ -119,3 +123,43 @@ export const isPreparedBlockOp = (op?: PreparedOp | null): op is GBlock { + if (op && 'LockOp' in op) { + console.log('isPreparedLockOp >>> ', JSON.stringify(op)); + } + + return !!(op && 'LockOp' in op); +}; + +export const isPreparedUnlockOp = (op?: PreparedOp | null): op is PreparedUnlockOp => { + if (op && 'UnlockOp' in op) { + console.log('isPreparedUnlockOp >>> ', JSON.stringify(op)); + } + + return !!(op && 'UnlockOp' in op); +}; + +export const isPreparedStoreOp = (op?: PreparedOp | null): op is GStore => { + if (op && 'StoreOp' in op) { + console.log('isPreparedStoreOp >>> ', JSON.stringify(op)); + } + + return !!(op && 'StoreOp' in op); +}; + +export const isPreparedExtendedStoreOp = (op?: PreparedOp | null): op is PreparedExtendedStoreOp => { + if (op && 'StoreOp' in op) { + console.log('isPreparedExtendedStoreOp >>> ', JSON.stringify(op)); + } + + return !!(op && 'StoreOp' in op); +}; + +export const isPreparedTakeOp = (op?: PreparedOp | null): op is GTake => { + if (op && 'TakeOp' in op) { + console.log('isPreparedTakeOp >>> ', JSON.stringify(op)); + } + + return !!(op && 'TakeOp' in op); +}; diff --git a/packages/cwait/src/types.ts b/packages/cwait/src/types.ts index c0601b9..45fc95c 100644 --- a/packages/cwait/src/types.ts +++ b/packages/cwait/src/types.ts @@ -1,4 +1,13 @@ -import { Claim, ClaimKey, OrdJson, PreparedOperation, ResolvedOperation, User } from '@coinweb/contract-kit'; +import { + Claim, + ClaimKey, + CwebStore, + GStore, + OrdJson, + PreparedOperation, + ResolvedOperation, + User, +} from '@coinweb/contract-kit'; import { LockedKey } from './onchain/mutex'; @@ -11,7 +20,14 @@ export type TypedClaim & { providedCweb?: bigint }; + +export type PreparedOp = + | PreparedOperation + | PreparedExecOp + | PreparedLockOp + | PreparedUnlockOp + | PreparedExtendedStoreOp; export type Task = { op: PreparedOp; @@ -76,4 +102,5 @@ export type ExecutorMethodArgs = [ parentId?: string, saveResult?: boolean, takenFundsIds?: string[], + execOpsIndexes?: number[], ]; From 3f640d90ce2d63f4f155f63280aaa851cb660d8e Mon Sep 17 00:00:00 2001 From: Alex Date: Sat, 26 Apr 2025 17:55:57 +0300 Subject: [PATCH 2/8] feat: handle mutex calls while prepare resolved ops --- packages/cwait/src/onchain/context/context.ts | 154 ++++++++++++------ .../cwait/src/onchain/context/extractFunds.ts | 34 ---- .../cwait/src/onchain/context/extractOps.ts | 146 +++++++++++++++-- packages/cwait/src/onchain/executor.ts | 8 +- .../mutex/calls/constructExecOpsCall.ts | 6 +- .../src/onchain/mutex/claims/mutexExecOps.ts | 4 + packages/cwait/src/onchain/mutex/index.ts | 3 + .../src/onchain/mutex/methods/execLock.ts | 4 +- .../cwait/src/onchain/mutex/methods/names.ts | 1 + .../cwait/src/onchain/mutex/methods/wait.ts | 61 +++++++ packages/cwait/src/onchain/mutex/settings.ts | 2 + packages/cwait/src/onchain/mutex/types.ts | 2 + 12 files changed, 324 insertions(+), 101 deletions(-) delete mode 100644 packages/cwait/src/onchain/context/extractFunds.ts create mode 100644 packages/cwait/src/onchain/mutex/methods/wait.ts diff --git a/packages/cwait/src/onchain/context/context.ts b/packages/cwait/src/onchain/context/context.ts index 273a71a..a056d0e 100644 --- a/packages/cwait/src/onchain/context/context.ts +++ b/packages/cwait/src/onchain/context/context.ts @@ -1,16 +1,39 @@ -import { Context, extractUser, getMethodArguments } from '@coinweb/contract-kit'; +import { + Context, + ContractIssuer, + extractContractArgs, + extractUser, + getMethodArguments, + GTake, + User, +} from '@coinweb/contract-kit'; +import { CwebTake } from '@coinweb/contract-kit/dist/types/operations/take'; import { getCallParameters, getContractIssuer } from 'lib/onchain'; -import { ExecutorMethodArgs } from '../../types'; +import { pushAwaitedOp } from '../../../dist/onchain/ops'; +import { ExecutorMethodArgs, ResolvedOp } from '../../types'; import { uuid } from '../utils'; -import { extractFunds } from './extractFunds'; import { extractOps } from './extractOps'; let rawContext: Context | null = null; -export const setRawContext = (ctx: Context) => { - rawContext = ctx; +const initialContext = { + isChild: false, + thisId: '', + parentId: '' as string | undefined, + ops: [] as ResolvedOp[], + funds: { + availableCweb: 0n, + storedCweb: 0n, + takeOps: [] as GTake[], + }, + methodName: '', + initialArgs: [] as unknown[], + user: null as User | null, + issuer: null as ContractIssuer | null, + parentTxId: '', + needSaveResult: false, }; export const getRawContext = () => { @@ -25,71 +48,106 @@ export const getMethodArgs = () => { return getMethodArguments(getRawContext()) as [methodName: string, ...ExecutorMethodArgs]; }; -let thisId: string | undefined; +export const handleContext = (ctx: Context) => { + rawContext = ctx; + + const contractArgs = extractContractArgs(getRawContext().tx); + const [ + methodName, + initialArgs, + resolvedOps, + caller, + thisId, + parentId, + saveResult, + takenFundsIds = [], + execOpsIndexes = [], + ] = getMethodArgs(); + + initialContext.isChild = !!parentId; + initialContext.thisId = thisId ?? uuid(); + initialContext.parentId = parentId; + initialContext.methodName = methodName; + initialContext.initialArgs = initialArgs ?? []; + initialContext.needSaveResult = saveResult ?? false; + + const { authInfo } = getCallParameters(getRawContext()); + initialContext.user = (authInfo && extractUser(authInfo)) ?? caller ?? null; + + initialContext.issuer = getContractIssuer(getRawContext()); + initialContext.parentTxId = getRawContext().call.txid; + + console.log('Parse ops in context'); + const { extractedOps, executionOpsTakeOp, storedCweb, fundsTakeOps } = extractOps({ + contractArgs, + takenFundsIds, + execOpsIndexes, + }); + + const previousOps = resolvedOps ?? []; + const allResolvedOps = [...previousOps, ...extractedOps]; + console.log('new ops >>>', JSON.stringify(extractedOps)); + initialContext.ops = allResolvedOps; + + const { availableCweb } = getCallParameters(getRawContext()); + + console.log('Available Cweb: ', availableCweb); + console.log('Stored Cweb: ', storedCweb); + console.log('Take Ops: ', fundsTakeOps); + + initialContext.funds = { + availableCweb: availableCweb, + storedCweb: storedCweb, + takeOps: fundsTakeOps, + }; + + if (executionOpsTakeOp) { + pushAwaitedOp(executionOpsTakeOp); + } + + return !!executionOpsTakeOp; //This is a flag to check if the execution should restart / TODO: refactor; +}; export const context = { get isChild() { - return !!getMethodArgs()[5]; + return initialContext.isChild; }, get thisId() { - thisId = thisId ?? getMethodArgs()[4] ?? uuid(); - return thisId; + return initialContext.thisId; }, get parentId() { - return getMethodArgs()[5]; - }, - get ops() { - console.log('Parse ops in context'); - - const previousOps = getMethodArgs()[2] ?? []; - const { extractedOps } = extractOps(); - - const allResolvedOps = [...previousOps, ...extractedOps]; - - console.log('new ops >>>', JSON.stringify(extractedOps)); - - return allResolvedOps; - }, - get funds() { - const { availableCweb } = getCallParameters(getRawContext()); - - const { amount: storedCweb, takeOps } = extractFunds(); - - console.log('Available Cweb: ', availableCweb); - console.log('Stored Cweb: ', storedCweb); - console.log('Take Ops: ', takeOps); - - return { - availableCweb, - storedCweb, - takeOps, - }; + return initialContext.parentId; }, get methodName() { return getMethodArguments(getRawContext())[0] as string; }, get initialArgs() { - return getMethodArgs()[1] ?? []; + return initialContext.initialArgs; }, get user() { - const { authInfo } = getCallParameters(getRawContext()); - const provided = getMethodArgs()[3]; - - const user = (authInfo && extractUser(authInfo)) ?? provided; - - if (!user) { + if (!initialContext.user) { throw new Error('User not found'); } - return user; + return initialContext.user; }, get issuer() { - return getContractIssuer(getRawContext()); + if (!initialContext.issuer) { + throw new Error('Issuer not found'); + } + + return initialContext.issuer; }, get parentTxId() { - return getRawContext().call.txid; + return initialContext.parentTxId; + }, + get ops() { + return initialContext.ops; }, get needSaveResult() { - return getMethodArgs()[6] ?? false; + return initialContext.needSaveResult; + }, + get funds() { + return initialContext.funds; }, }; diff --git a/packages/cwait/src/onchain/context/extractFunds.ts b/packages/cwait/src/onchain/context/extractFunds.ts deleted file mode 100644 index 0f2899d..0000000 --- a/packages/cwait/src/onchain/context/extractFunds.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { constructTake } from '@coinweb/contract-kit'; -import { extractRead } from '@coinweb/contract-kit/dist/esm/operations/read'; - -import { getMethodArgs } from './context'; -import { extractOps } from './extractOps'; - -export const extractFunds = () => { - const { fundsOp } = extractOps(); - - const takenFundsIds = getMethodArgs()[7] ?? []; - - if (!fundsOp) { - return { - takeOps: [], - amount: 0n, - }; - } - - const claims = extractRead(fundsOp); - - const newClaims = claims?.filter(({ content }) => !takenFundsIds.includes((content.key.second_part as [string])[0])); - - if (!newClaims?.length) { - return { - takeOps: [], - amount: 0n, - }; - } - - return { - takeOps: newClaims.map(({ content }) => constructTake(content.key)), - amount: newClaims.reduce((acc, { content }) => acc + BigInt(content.fees_stored), 0n), - }; -}; diff --git a/packages/cwait/src/onchain/context/extractOps.ts b/packages/cwait/src/onchain/context/extractOps.ts index 0b06c0a..d2d3d86 100644 --- a/packages/cwait/src/onchain/context/extractOps.ts +++ b/packages/cwait/src/onchain/context/extractOps.ts @@ -1,18 +1,51 @@ -import { extractContractArgs } from '@coinweb/contract-kit'; +import { constructTake, extractRead, GRead, GTake, ResolvedOperation, ResolvedRead } from '@coinweb/contract-kit'; +import { CwebTake } from '@coinweb/contract-kit/dist/types/operations/take'; import { ResolvedOp, TypedClaim } from '../../types'; import { resultKey } from '../claims/result'; +import { mutexBlockLockKey, mutexBlockUnlockKey, mutexExecOpsKey, MutexExecOpsResult } from '../mutex'; import { isResolvedBlockOp, isResolvedReadOp, isResolvedTakeOp } from '../utils'; -import { getRawContext } from './context'; +const extractFunds = (fundsOp: GRead, takenFundsIds: string[]) => { + if (!fundsOp) { + return { + takeOps: [], + amount: 0n, + }; + } -export const extractOps = () => { - const contractArgs = extractContractArgs(getRawContext().tx); + const claims = extractRead(fundsOp); + const newClaims = claims?.filter(({ content }) => !takenFundsIds.includes((content.key.second_part as [string])[0])); + + if (!newClaims?.length) { + return { + takeOps: [], + amount: 0n, + }; + } + + return { + takeOps: newClaims.map(({ content }) => constructTake(content.key)), + amount: newClaims.reduce((acc, { content }) => acc + BigInt(content.fees_stored), 0n), + }; +}; + +export const extractOps = ({ + contractArgs, + takenFundsIds, + execOpsIndexes, +}: { + contractArgs: ResolvedOperation[]; + takenFundsIds: string[]; + execOpsIndexes: number[]; +}) => { if (!contractArgs.length) { return { extractedOps: [], - fundsOps: [], + fundsTakeOps: [], + storedCweb: 0n, + executionOpsTakeOp: null, }; } @@ -23,6 +56,9 @@ export const extractOps = () => { } const extractedOps: ResolvedOp[] = []; + let executedOps: MutexExecOpsResult = []; + let executionOpsProfit: bigint = 0n; + let executionOpsTakeOp: GTake | null = null; let i = 0; @@ -34,15 +70,69 @@ export const extractOps = () => { //Maybe it is needed to check more conditions here if (Array.isArray(first) && first[0] === resultKey) { - const nextAfterBlock = resolvedOps[i + 1]; + switch (first[0]) { + case resultKey: { + const nextAfterBlock = resolvedOps[i + 1]; - if (!isResolvedTakeOp(nextAfterBlock)) { - throw new Error('Wrong subcall result'); + if (!isResolvedTakeOp(nextAfterBlock)) { + throw new Error('Wrong subcall result'); + } + + extractedOps.push({ ChildOp: 0 }, ...(nextAfterBlock.TakeOp.result as TypedClaim).body); + + i += 2; + continue; + } + case mutexBlockLockKey: { + const nextAfterBlock = resolvedOps[i + 1]; + + if (!isResolvedTakeOp(nextAfterBlock)) { + throw new Error('Wrong mutex lock result'); + } + + extractedOps.push({ SlotOp: { ok: true } }); + + i += 2; + continue; + } + case mutexBlockUnlockKey: { + const nextAfterBlock = resolvedOps[i + 1]; + + if (!isResolvedTakeOp(nextAfterBlock)) { + throw new Error('Wrong mutex unlock result'); + } + + extractedOps.push({ SlotOp: { ok: true } }); + + i += 2; + continue; + } + case mutexExecOpsKey: { + const nextAfterBlock = resolvedOps[i + 1]; + + const execOpResultClaim = extractRead(nextAfterBlock)?.[0]?.content; + + if (!execOpResultClaim) { + throw new Error('Wrong mutex exec result'); + } + + executedOps = execOpResultClaim.body as MutexExecOpsResult; + executionOpsProfit = BigInt(execOpResultClaim.fees_stored); + executionOpsTakeOp = constructTake(execOpResultClaim.key); + i += 2; + continue; + } + + default: + break; } + } + } - extractedOps.push({ ChildOp: 0 }, ...(nextAfterBlock.TakeOp.result as TypedClaim).body); - - i += 2; + if (isResolvedTakeOp(op)) { + const keyFirstPart = op.TakeOp.result.key.first_part; + if (Array.isArray(keyFirstPart) && keyFirstPart[0] === mutexExecOpsKey) { + i++; continue; } } @@ -51,8 +141,38 @@ export const extractOps = () => { i++; } + const allOps: ResolvedOp[] = []; + + for (let i = 0; i < resolvedOps.length + executedOps.length; i++) { + if (execOpsIndexes.includes(i)) { + const op = executedOps.shift(); + + if (!op) { + throw new Error('Wrong mutex exec result'); + } + + if (op.ok) { + allOps.push(op.resolved); + } else { + allOps.push({ SlotOp: { ok: false, error: op.error } }); + } + } else { + const op = resolvedOps.shift(); + + if (!op) { + throw new Error('Contract call args parsing error'); + } + + allOps.push(op); + } + } + + const { takeOps, amount } = extractFunds(fundsOp, takenFundsIds); + return { - fundsOp, - extractedOps, + fundsTakeOps: takeOps, + storedCweb: amount + executionOpsProfit, + extractedOps: allOps, + executionOpsTakeOp, }; }; diff --git a/packages/cwait/src/onchain/executor.ts b/packages/cwait/src/onchain/executor.ts index 9532673..eb8a1dd 100644 --- a/packages/cwait/src/onchain/executor.ts +++ b/packages/cwait/src/onchain/executor.ts @@ -1,7 +1,7 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Context, NewTx, getMethodArguments, isSelfCall } from '@coinweb/contract-kit'; -import { context, getRawContext, setRawContext } from './context'; +import { context, getRawContext, handleContext } from './context'; import { pushResolvedOp } from './promisifiedOps/resolved'; import { constructTx } from './utils'; @@ -10,13 +10,17 @@ let abortExecution: ((result: boolean) => void) | null = null; export const executor = (method: (...args: any[]) => Promise) => { return async (ctx: Context): Promise => { console.log('executor-start >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>'); - setRawContext(ctx); + const shouldRestart = handleContext(ctx); pushResolvedOp(context.ops); if (getMethodArguments(getRawContext()).length > 2 && !isSelfCall(ctx)) { throw new Error('Wrong contract call, check the call arguments'); } + if (shouldRestart) { + return constructTx(false); + } + const execution = new Promise((resolve, reject) => { abortExecution = resolve; diff --git a/packages/cwait/src/onchain/mutex/calls/constructExecOpsCall.ts b/packages/cwait/src/onchain/mutex/calls/constructExecOpsCall.ts index af1d981..7b75ff1 100644 --- a/packages/cwait/src/onchain/mutex/calls/constructExecOpsCall.ts +++ b/packages/cwait/src/onchain/mutex/calls/constructExecOpsCall.ts @@ -4,7 +4,7 @@ import { CwebTake } from '@coinweb/contract-kit/dist/types/operations/take'; import { PreparedExtendedStoreOp } from '../../../types'; import { constructMutexExecOpsBlock, - constructMutexExecOpsClaimTake, + constructMutexExecOpsClaimRead, constructMutexLockClaimRangeRead, } from '../claims'; import { execOpsMethodName } from '../methods'; @@ -65,6 +65,8 @@ export const constructExecOpsCall = ({ contractArgs: [constructMutexLockClaimRangeRead(issuer)], }, fee: providedCweb + contractArgsFee, - ops: execId ? ([constructMutexExecOpsBlock(execId, issuer), constructMutexExecOpsClaimTake(execId)] as const) : [], + ops: execId + ? ([constructMutexExecOpsBlock(execId, issuer), constructMutexExecOpsClaimRead(issuer, execId)] as const) + : [], }; }; diff --git a/packages/cwait/src/onchain/mutex/claims/mutexExecOps.ts b/packages/cwait/src/onchain/mutex/claims/mutexExecOps.ts index e3b4d47..a3cd931 100644 --- a/packages/cwait/src/onchain/mutex/claims/mutexExecOps.ts +++ b/packages/cwait/src/onchain/mutex/claims/mutexExecOps.ts @@ -3,6 +3,7 @@ import { constructBlock, constructClaim, constructClaimKey, + constructRead, constructStore, constructTake, ContractIssuer, @@ -26,6 +27,9 @@ export const constructMutexExecOpsClaimStore = ( storeCweb: bigint = 0n ): GStore => constructStore(constructMutexExecOpsClaim(execId, result, storeCweb)); +export const constructMutexExecOpsClaimRead = (issuer: ContractIssuer, execId: string) => + constructRead(issuer, constructMutexExecOpsClaimKey(execId)); + export const constructMutexExecOpsClaimTake = (execId: string) => constructTake(constructMutexExecOpsClaimKey(execId)); export const constructMutexExecOpsFilter = (lockId: string, issuer: ContractIssuer): BlockFilter => { diff --git a/packages/cwait/src/onchain/mutex/index.ts b/packages/cwait/src/onchain/mutex/index.ts index 004e773..793cef3 100644 --- a/packages/cwait/src/onchain/mutex/index.ts +++ b/packages/cwait/src/onchain/mutex/index.ts @@ -10,8 +10,10 @@ import { saveExecOpResult, saveExecOpResultMethodName, unlockMethodName, + waitMethodName, } from './methods'; import { preReadExecTakeOps } from './methods/preReadExecTakeOps'; +import { wait } from './methods/wait'; export * from './claims'; export * from './types'; @@ -24,4 +26,5 @@ export const mutexMethods = { [execOpsMethodName]: execOps, [preReadExecTakeOpsMethodName]: preReadExecTakeOps, [saveExecOpResultMethodName]: saveExecOpResult, + [waitMethodName]: wait, }; diff --git a/packages/cwait/src/onchain/mutex/methods/execLock.ts b/packages/cwait/src/onchain/mutex/methods/execLock.ts index 3f79a2f..4d8d5ff 100644 --- a/packages/cwait/src/onchain/mutex/methods/execLock.ts +++ b/packages/cwait/src/onchain/mutex/methods/execLock.ts @@ -13,7 +13,7 @@ import { getCallParameters, getContractIssuer } from 'lib/onchain'; import { toHex, TypedClaim } from 'lib/shared'; import { lockFee } from '../settings'; -import { MutexLockState } from '../types'; +import { MutexLockState, MutexWaitNotifyArgs } from '../types'; import { isMatchLockKeys } from '../utils'; import { lockMethodName, notifyLockMethodName } from './names'; @@ -74,7 +74,7 @@ export const mutexExecLock = (context: Context) => { ref: constructContractRef(issuer, []), methodInfo: { methodName: notifyLockMethodName, - methodArgs: [(lockCandidate.key.second_part as [number, string])[1]], + methodArgs: [0, (lockCandidate.key.second_part as [number, string])[1]] satisfies MutexWaitNotifyArgs, }, contractInfo: { providedCweb: 200n, diff --git a/packages/cwait/src/onchain/mutex/methods/names.ts b/packages/cwait/src/onchain/mutex/methods/names.ts index ce0c305..823cf34 100644 --- a/packages/cwait/src/onchain/mutex/methods/names.ts +++ b/packages/cwait/src/onchain/mutex/methods/names.ts @@ -5,3 +5,4 @@ export const notifyLockMethodName = '_mutex_notify_lock'; export const execOpsMethodName = '_mutex_execOps'; export const saveExecOpResultMethodName = '_mutex_save_exec_op_result'; export const preReadExecTakeOpsMethodName = '_mutex_pre_read_exec_take_ops'; +export const waitMethodName = '_mutex_wait'; diff --git a/packages/cwait/src/onchain/mutex/methods/wait.ts b/packages/cwait/src/onchain/mutex/methods/wait.ts new file mode 100644 index 0000000..af8d489 --- /dev/null +++ b/packages/cwait/src/onchain/mutex/methods/wait.ts @@ -0,0 +1,61 @@ +import { constructContinueTx, constructContractRef, Context } from '@coinweb/contract-kit'; +import { getContractArguments, getContractIssuer } from 'lib/onchain'; + +import { waitSteps } from '../settings'; +import { MutexNotifyLockArgs, MutexWaitNotifyArgs } from '../types'; + +import { notifyLockMethodName } from './names'; + +export const wait = (context: Context) => { + const issuer = getContractIssuer(context); + const [step, lockId] = getContractArguments(context); + + //Including the notifyLock method as step + if (step + 1 < waitSteps) { + return [ + constructContinueTx( + context, + [], + [ + { + callInfo: { + ref: constructContractRef(issuer, []), + methodInfo: { + methodName: notifyLockMethodName, + methodArgs: [step + 1, lockId] satisfies MutexWaitNotifyArgs, + }, + contractInfo: { + providedCweb: 200n, + authenticated: null, + }, + contractArgs: [], + }, + }, + ] + ), + ]; + } + + return [ + constructContinueTx( + context, + [], + [ + { + callInfo: { + ref: constructContractRef(issuer, []), + methodInfo: { + methodName: notifyLockMethodName, + methodArgs: [lockId] satisfies MutexNotifyLockArgs, + }, + contractInfo: { + providedCweb: 200n, + authenticated: null, + }, + contractArgs: [], + }, + }, + ] + ), + ]; +}; diff --git a/packages/cwait/src/onchain/mutex/settings.ts b/packages/cwait/src/onchain/mutex/settings.ts index e277112..5f8f9ad 100644 --- a/packages/cwait/src/onchain/mutex/settings.ts +++ b/packages/cwait/src/onchain/mutex/settings.ts @@ -2,3 +2,5 @@ export const lockFee = 5000n; export const unlockFee = 1000n + lockFee; export const rangeReadLimit = 10000; + +export const waitSteps = 2; diff --git a/packages/cwait/src/onchain/mutex/types.ts b/packages/cwait/src/onchain/mutex/types.ts index 7116b84..245e8af 100644 --- a/packages/cwait/src/onchain/mutex/types.ts +++ b/packages/cwait/src/onchain/mutex/types.ts @@ -24,6 +24,8 @@ export type MutexUnlockArgs = [lockId: string, timestamp: number, notify?: boole export type MutexExecOpArgs = [ops: (GTake | GStore)[], processId: string, execId?: string]; +export type MutexWaitNotifyArgs = [step: number, lockId: string]; + export type MutexNotifyLockArgs = [lockId: string]; export type MutexPreReadTakeOpsArgs = [execId: string, unavailableIndexes: number[], ops: PreparedOperation[]]; From 7baec6736b9e460c4d5c895646ffc28e7a445be3 Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 5 May 2025 12:21:50 +0300 Subject: [PATCH 3/8] fix: small fixes and refactor --- .../cwait/src/onchain/constructCwebMain.ts | 4 + packages/cwait/src/onchain/context/context.ts | 4 +- .../cwait/src/onchain/context/extractOps.ts | 2 +- packages/cwait/src/onchain/executor.ts | 2 +- packages/cwait/src/onchain/global.ts | 2 +- packages/cwait/src/onchain/index.ts | 4 +- .../onchain/mutex/claims/mutexBlockLock.ts | 2 +- .../onchain/mutex/claims/mutexBlockUnlock.ts | 2 +- .../src/onchain/mutex/claims/mutexExecOps.ts | 2 +- .../src/onchain/mutex/claims/mutexLock.ts | 4 +- .../features/cwait}/cwait.ts | 17 ++-- .../features/cwait/index.ts | 1 + .../promisifiedFeatures/features/index.ts | 3 + .../features/mutex/index.ts | 1 + .../features/mutex/lock.ts | 78 +++++++++++++++++++ .../features}/ops/block.ts | 7 +- .../features}/ops/index.ts | 0 .../features}/ops/rangeRead.ts | 11 ++- .../features}/ops/read.ts | 11 ++- .../features}/ops/store.ts | 7 +- .../features}/ops/take.ts | 9 +-- .../src/onchain/promisifiedFeatures/index.ts | 2 + .../runtime/awaitedTasks.ts} | 2 +- .../promisifiedFeatures/runtime/index.ts | 2 + .../runtime/resolvedOps.ts} | 2 +- .../cwait/src/onchain/promisifiedOps/index.ts | 4 - .../src/onchain/promisifiedOps/ops/lock.ts | 42 ---------- .../src/onchain/promisifiedOps/ops/unlock.ts | 38 --------- .../utils/constructTx/prepareInThreadTxs.ts | 32 ++++---- .../utils/constructTx/prepareOutThreadTxs.ts | 22 ++++-- .../onchain/utils/constructTx/prepareTxs.ts | 2 +- packages/ui/.env | 2 +- 32 files changed, 166 insertions(+), 157 deletions(-) rename packages/cwait/src/onchain/{promisifiedOps => promisifiedFeatures/features/cwait}/cwait.ts (81%) create mode 100644 packages/cwait/src/onchain/promisifiedFeatures/features/cwait/index.ts create mode 100644 packages/cwait/src/onchain/promisifiedFeatures/features/index.ts create mode 100644 packages/cwait/src/onchain/promisifiedFeatures/features/mutex/index.ts create mode 100644 packages/cwait/src/onchain/promisifiedFeatures/features/mutex/lock.ts rename packages/cwait/src/onchain/{promisifiedOps => promisifiedFeatures/features}/ops/block.ts (82%) rename packages/cwait/src/onchain/{promisifiedOps => promisifiedFeatures/features}/ops/index.ts (100%) rename packages/cwait/src/onchain/{promisifiedOps => promisifiedFeatures/features}/ops/rangeRead.ts (81%) rename packages/cwait/src/onchain/{promisifiedOps => promisifiedFeatures/features}/ops/read.ts (77%) rename packages/cwait/src/onchain/{promisifiedOps => promisifiedFeatures/features}/ops/store.ts (83%) rename packages/cwait/src/onchain/{promisifiedOps => promisifiedFeatures/features}/ops/take.ts (79%) create mode 100644 packages/cwait/src/onchain/promisifiedFeatures/index.ts rename packages/cwait/src/onchain/{promisifiedOps/awaited.ts => promisifiedFeatures/runtime/awaitedTasks.ts} (88%) create mode 100644 packages/cwait/src/onchain/promisifiedFeatures/runtime/index.ts rename packages/cwait/src/onchain/{promisifiedOps/resolved.ts => promisifiedFeatures/runtime/resolvedOps.ts} (94%) delete mode 100644 packages/cwait/src/onchain/promisifiedOps/index.ts delete mode 100644 packages/cwait/src/onchain/promisifiedOps/ops/lock.ts delete mode 100644 packages/cwait/src/onchain/promisifiedOps/ops/unlock.ts diff --git a/packages/cwait/src/onchain/constructCwebMain.ts b/packages/cwait/src/onchain/constructCwebMain.ts index 88e208b..3ac7eaa 100644 --- a/packages/cwait/src/onchain/constructCwebMain.ts +++ b/packages/cwait/src/onchain/constructCwebMain.ts @@ -12,6 +12,10 @@ export const constructCwebMain = (methods: Record Pr const module: ContractHandlers = { handlers: {} }; Object.entries(methods).forEach(([name, handler]) => { + if (name.startsWith('_')) { + throw new Error('Do not start method names with "_" because they are reserved for internal use'); + } + addMethodHandler(module, name, executor(handler)); }); diff --git a/packages/cwait/src/onchain/context/context.ts b/packages/cwait/src/onchain/context/context.ts index a056d0e..cbadb3f 100644 --- a/packages/cwait/src/onchain/context/context.ts +++ b/packages/cwait/src/onchain/context/context.ts @@ -10,8 +10,8 @@ import { import { CwebTake } from '@coinweb/contract-kit/dist/types/operations/take'; import { getCallParameters, getContractIssuer } from 'lib/onchain'; -import { pushAwaitedOp } from '../../../dist/onchain/ops'; import { ExecutorMethodArgs, ResolvedOp } from '../../types'; +import { pushAwaitedTask } from '../promisifiedFeatures'; import { uuid } from '../utils'; import { extractOps } from './extractOps'; @@ -102,7 +102,7 @@ export const handleContext = (ctx: Context) => { }; if (executionOpsTakeOp) { - pushAwaitedOp(executionOpsTakeOp); + pushAwaitedTask(executionOpsTakeOp); } return !!executionOpsTakeOp; //This is a flag to check if the execution should restart / TODO: refactor; diff --git a/packages/cwait/src/onchain/context/extractOps.ts b/packages/cwait/src/onchain/context/extractOps.ts index d2d3d86..6321561 100644 --- a/packages/cwait/src/onchain/context/extractOps.ts +++ b/packages/cwait/src/onchain/context/extractOps.ts @@ -2,7 +2,7 @@ import { constructTake, extractRead, GRead, GTake, ResolvedOperation, ResolvedRe import { CwebTake } from '@coinweb/contract-kit/dist/types/operations/take'; import { ResolvedOp, TypedClaim } from '../../types'; -import { resultKey } from '../claims/result'; +import { resultKey } from '../claims'; import { mutexBlockLockKey, mutexBlockUnlockKey, mutexExecOpsKey, MutexExecOpsResult } from '../mutex'; import { isResolvedBlockOp, isResolvedReadOp, isResolvedTakeOp } from '../utils'; diff --git a/packages/cwait/src/onchain/executor.ts b/packages/cwait/src/onchain/executor.ts index eb8a1dd..635fe4d 100644 --- a/packages/cwait/src/onchain/executor.ts +++ b/packages/cwait/src/onchain/executor.ts @@ -2,7 +2,7 @@ import { Context, NewTx, getMethodArguments, isSelfCall } from '@coinweb/contract-kit'; import { context, getRawContext, handleContext } from './context'; -import { pushResolvedOp } from './promisifiedOps/resolved'; +import { pushResolvedOp } from './promisifiedFeatures/runtime/resolvedOps'; import { constructTx } from './utils'; let abortExecution: ((result: boolean) => void) | null = null; diff --git a/packages/cwait/src/onchain/global.ts b/packages/cwait/src/onchain/global.ts index 484d929..b2c8c6a 100644 --- a/packages/cwait/src/onchain/global.ts +++ b/packages/cwait/src/onchain/global.ts @@ -1,4 +1,4 @@ -import { markTaskBatch } from './promisifiedOps/awaited'; +import { markTaskBatch } from './promisifiedFeatures/runtime/awaitedTasks'; import { getBatchId } from './utils'; export const opMarker = Symbol('opMarker'); diff --git a/packages/cwait/src/onchain/index.ts b/packages/cwait/src/onchain/index.ts index 7be55b9..b31def1 100644 --- a/packages/cwait/src/onchain/index.ts +++ b/packages/cwait/src/onchain/index.ts @@ -1,3 +1,3 @@ export * from './context'; -export * from './executor'; -export * from './promisifiedOps'; +export * from './promisifiedFeatures/features'; +export * from './constructCwebMain'; diff --git a/packages/cwait/src/onchain/mutex/claims/mutexBlockLock.ts b/packages/cwait/src/onchain/mutex/claims/mutexBlockLock.ts index 5985ead..0f6484e 100644 --- a/packages/cwait/src/onchain/mutex/claims/mutexBlockLock.ts +++ b/packages/cwait/src/onchain/mutex/claims/mutexBlockLock.ts @@ -8,7 +8,7 @@ import { ContractIssuer, } from '@coinweb/contract-kit'; -export const mutexBlockLockKey = 'mutex_block_lock'; +export const mutexBlockLockKey = '_mutex_block_lock'; export const constructMutexBlockLockClaimKey = (lockId: string) => constructClaimKey([mutexBlockLockKey], [lockId]); diff --git a/packages/cwait/src/onchain/mutex/claims/mutexBlockUnlock.ts b/packages/cwait/src/onchain/mutex/claims/mutexBlockUnlock.ts index 6f7f7fd..11eab3f 100644 --- a/packages/cwait/src/onchain/mutex/claims/mutexBlockUnlock.ts +++ b/packages/cwait/src/onchain/mutex/claims/mutexBlockUnlock.ts @@ -10,7 +10,7 @@ import { GStore, } from '@coinweb/contract-kit'; -export const mutexBlockUnlockKey = 'mutex_block_unlock'; +export const mutexBlockUnlockKey = '_mutex_block_unlock'; export const constructMutexBlockUnlockClaimKey = (uniqueId: string) => constructClaimKey([mutexBlockUnlockKey], [uniqueId]); diff --git a/packages/cwait/src/onchain/mutex/claims/mutexExecOps.ts b/packages/cwait/src/onchain/mutex/claims/mutexExecOps.ts index a3cd931..0770554 100644 --- a/packages/cwait/src/onchain/mutex/claims/mutexExecOps.ts +++ b/packages/cwait/src/onchain/mutex/claims/mutexExecOps.ts @@ -14,7 +14,7 @@ import { toHex } from 'lib/shared'; import { MutexExecOpsResult } from '../types'; -export const mutexExecOpsKey = 'mutex_exec_ops'; +export const mutexExecOpsKey = '_mutex_exec_ops'; export const constructMutexExecOpsClaimKey = (execId: string) => constructClaimKey([mutexExecOpsKey], [execId]); diff --git a/packages/cwait/src/onchain/mutex/claims/mutexLock.ts b/packages/cwait/src/onchain/mutex/claims/mutexLock.ts index 725d80d..5441d8e 100644 --- a/packages/cwait/src/onchain/mutex/claims/mutexLock.ts +++ b/packages/cwait/src/onchain/mutex/claims/mutexLock.ts @@ -6,13 +6,13 @@ import { ContractIssuer, GRead, } from '@coinweb/contract-kit'; -import { CwebRead } from '@coinweb/contract-kit/dist/esm/operations/read'; +import { CwebRead } from '@coinweb/contract-kit/dist/types/operations/read'; import { toHex } from 'lib/shared'; import { rangeReadLimit } from '../settings'; import { LockedKey, MutexLockState } from '../types'; -export const mutexLockKey = 'mutex_lock'; +export const mutexLockKey = '_mutex_lock'; export const constructMutexLockFirstPart = () => [mutexLockKey]; diff --git a/packages/cwait/src/onchain/promisifiedOps/cwait.ts b/packages/cwait/src/onchain/promisifiedFeatures/features/cwait/cwait.ts similarity index 81% rename from packages/cwait/src/onchain/promisifiedOps/cwait.ts rename to packages/cwait/src/onchain/promisifiedFeatures/features/cwait/cwait.ts index 4af8ba2..cfe97cf 100644 --- a/packages/cwait/src/onchain/promisifiedOps/cwait.ts +++ b/packages/cwait/src/onchain/promisifiedFeatures/features/cwait/cwait.ts @@ -1,14 +1,13 @@ import { constructStore } from '@coinweb/contract-kit/dist/esm/operations/store'; -import { constructResultClaim } from '../claims/result'; -import { context } from '../context'; -import { stopExecution } from '../executor'; -import { opMarker } from '../global'; -import { isResolvedChildOp, isResolvedExecOp, isResolvedSlotOp } from '../utils'; -import { uuid } from '../utils'; - -import { getAwaitedTasks, pushAwaitedTask } from './awaited'; -import { getUsedOps, saveUsedOps, shiftResolvedOp } from './resolved'; +import { constructResultClaim } from '../../../claims/result'; +import { context } from '../../../context'; +import { stopExecution } from '../../../executor'; +import { opMarker } from '../../../global'; +import { isResolvedChildOp, isResolvedExecOp, isResolvedSlotOp } from '../../../utils'; +import { uuid } from '../../../utils'; +import { getAwaitedTasks, pushAwaitedTask } from '../../runtime/awaitedTasks'; +import { getUsedOps, saveUsedOps, shiftResolvedOp } from '../../runtime/resolvedOps'; let isRootDetected = false; diff --git a/packages/cwait/src/onchain/promisifiedFeatures/features/cwait/index.ts b/packages/cwait/src/onchain/promisifiedFeatures/features/cwait/index.ts new file mode 100644 index 0000000..e66ae58 --- /dev/null +++ b/packages/cwait/src/onchain/promisifiedFeatures/features/cwait/index.ts @@ -0,0 +1 @@ +export * from './cwait'; diff --git a/packages/cwait/src/onchain/promisifiedFeatures/features/index.ts b/packages/cwait/src/onchain/promisifiedFeatures/features/index.ts new file mode 100644 index 0000000..e13c3d1 --- /dev/null +++ b/packages/cwait/src/onchain/promisifiedFeatures/features/index.ts @@ -0,0 +1,3 @@ +export * from './cwait'; +export * from './mutex'; +export * from './ops'; diff --git a/packages/cwait/src/onchain/promisifiedFeatures/features/mutex/index.ts b/packages/cwait/src/onchain/promisifiedFeatures/features/mutex/index.ts new file mode 100644 index 0000000..8fa8b1b --- /dev/null +++ b/packages/cwait/src/onchain/promisifiedFeatures/features/mutex/index.ts @@ -0,0 +1 @@ +export * from './lock'; diff --git a/packages/cwait/src/onchain/promisifiedFeatures/features/mutex/lock.ts b/packages/cwait/src/onchain/promisifiedFeatures/features/mutex/lock.ts new file mode 100644 index 0000000..60c8b33 --- /dev/null +++ b/packages/cwait/src/onchain/promisifiedFeatures/features/mutex/lock.ts @@ -0,0 +1,78 @@ +import { getTime } from 'lib/onchain'; + +import { opMarker } from '../../../global'; +import { LockedKey } from '../../../mutex'; +import { isResolvedLockOp, isResolvedSlotOp, isResolvedUnlockOp, uuid } from '../../../utils'; +import { pushAwaitedTask, shiftResolvedOp } from '../../runtime'; + +const unlock = (lockId: string, timestamp: number) => { + console.log('lockOp'); + let opMarkerValue = false; + + const result = new Promise((resolve, reject) => { + try { + const { op, isOp } = shiftResolvedOp(); + + if (!isOp) { + pushAwaitedTask({ UnlockOp: { lockId, timestamp } }); + opMarkerValue = true; + } else { + if (isResolvedSlotOp(op)) { + console.log('unlockOp-slotOp'); + return; + } + + if (!isResolvedUnlockOp(op)) { + console.log('unlockOp-error'); + throw new Error('Unlock operation not found'); + } + + resolve(); + } + } catch (error) { + reject(error); + } + }); + + (result as Promise & { [opMarker]: boolean })[opMarker] = opMarkerValue; + + return result; +}; + +export const lock = (keys: LockedKey[] | LockedKey) => { + console.log('lockOp'); + let opMarkerValue = false; + + const result = new Promise<() => Promise>((resolve, reject) => { + try { + const { op, isOp } = shiftResolvedOp(); + + if (!isOp) { + pushAwaitedTask({ + LockOp: { lockId: uuid(), keys: Array.isArray(keys) ? keys : [keys], timestamp: getTime() }, + }); + opMarkerValue = true; + } else { + if (isResolvedSlotOp(op)) { + console.log('lockOp-slotOp'); + return; + } + + if (!isResolvedLockOp(op)) { + console.log('lockOp-error'); + throw new Error('Lock operation not found'); + } + + const result = op.LockOp; + + resolve(() => unlock(result.lockId, result.timestamp)); + } + } catch (error) { + reject(error); + } + }); + + (result as Promise<() => Promise> & { [opMarker]: boolean })[opMarker] = opMarkerValue; + + return result; +}; diff --git a/packages/cwait/src/onchain/promisifiedOps/ops/block.ts b/packages/cwait/src/onchain/promisifiedFeatures/features/ops/block.ts similarity index 82% rename from packages/cwait/src/onchain/promisifiedOps/ops/block.ts rename to packages/cwait/src/onchain/promisifiedFeatures/features/ops/block.ts index 56d1086..9ec99b8 100644 --- a/packages/cwait/src/onchain/promisifiedOps/ops/block.ts +++ b/packages/cwait/src/onchain/promisifiedFeatures/features/ops/block.ts @@ -1,9 +1,8 @@ import { BlockFilter, constructBlock, extractBlock } from '@coinweb/contract-kit'; -import { opMarker } from '../../global'; -import { isResolvedBlockOp, isResolvedSlotOp } from '../../utils'; -import { pushAwaitedTask } from '../awaited'; -import { shiftResolvedOp } from '../resolved'; +import { opMarker } from '../../../global'; +import { isResolvedBlockOp, isResolvedSlotOp } from '../../../utils'; +import { pushAwaitedTask, shiftResolvedOp } from '../../runtime'; export const blockOp = (filters: BlockFilter[]) => { console.log('blockOp'); diff --git a/packages/cwait/src/onchain/promisifiedOps/ops/index.ts b/packages/cwait/src/onchain/promisifiedFeatures/features/ops/index.ts similarity index 100% rename from packages/cwait/src/onchain/promisifiedOps/ops/index.ts rename to packages/cwait/src/onchain/promisifiedFeatures/features/ops/index.ts diff --git a/packages/cwait/src/onchain/promisifiedOps/ops/rangeRead.ts b/packages/cwait/src/onchain/promisifiedFeatures/features/ops/rangeRead.ts similarity index 81% rename from packages/cwait/src/onchain/promisifiedOps/ops/rangeRead.ts rename to packages/cwait/src/onchain/promisifiedFeatures/features/ops/rangeRead.ts index 57137ab..ea1673b 100644 --- a/packages/cwait/src/onchain/promisifiedOps/ops/rangeRead.ts +++ b/packages/cwait/src/onchain/promisifiedFeatures/features/ops/rangeRead.ts @@ -1,12 +1,11 @@ import { Claim, ClaimKey, constructRangeRead, extractRead } from '@coinweb/contract-kit'; import { ClaimRange } from '@coinweb/contract-kit/dist/types/operations/read'; -import { TypedClaim } from '../../../types'; -import { context } from '../../context'; -import { opMarker } from '../../global'; -import { isResolvedReadOp, isResolvedSlotOp } from '../../utils'; -import { pushAwaitedTask } from '../awaited'; -import { shiftResolvedOp } from '../resolved'; +import { TypedClaim } from '../../../../types'; +import { context } from '../../../context'; +import { opMarker } from '../../../global'; +import { isResolvedReadOp, isResolvedSlotOp } from '../../../utils'; +import { pushAwaitedTask, shiftResolvedOp } from '../../runtime'; export const rangeReadOp = ( firstPart: ClaimKey['first_part'], diff --git a/packages/cwait/src/onchain/promisifiedOps/ops/read.ts b/packages/cwait/src/onchain/promisifiedFeatures/features/ops/read.ts similarity index 77% rename from packages/cwait/src/onchain/promisifiedOps/ops/read.ts rename to packages/cwait/src/onchain/promisifiedFeatures/features/ops/read.ts index 72f0320..af4fcf4 100644 --- a/packages/cwait/src/onchain/promisifiedOps/ops/read.ts +++ b/packages/cwait/src/onchain/promisifiedFeatures/features/ops/read.ts @@ -1,11 +1,10 @@ import { Claim, ClaimKey, constructRead, extractRead } from '@coinweb/contract-kit'; -import { TypedClaim } from '../../../types'; -import { context } from '../../context'; -import { opMarker } from '../../global'; -import { isResolvedReadOp, isResolvedSlotOp } from '../../utils'; -import { pushAwaitedTask } from '../awaited'; -import { shiftResolvedOp } from '../resolved'; +import { TypedClaim } from '../../../../types'; +import { context } from '../../../context'; +import { opMarker } from '../../../global'; +import { isResolvedReadOp, isResolvedSlotOp } from '../../../utils'; +import { pushAwaitedTask, shiftResolvedOp } from '../../runtime'; export const readOp = (key: ClaimKey) => { console.log('readOp'); diff --git a/packages/cwait/src/onchain/promisifiedOps/ops/store.ts b/packages/cwait/src/onchain/promisifiedFeatures/features/ops/store.ts similarity index 83% rename from packages/cwait/src/onchain/promisifiedOps/ops/store.ts rename to packages/cwait/src/onchain/promisifiedFeatures/features/ops/store.ts index 15857ec..d949996 100644 --- a/packages/cwait/src/onchain/promisifiedOps/ops/store.ts +++ b/packages/cwait/src/onchain/promisifiedFeatures/features/ops/store.ts @@ -1,10 +1,9 @@ import { Claim, constructStore } from '@coinweb/contract-kit'; import { extractStore } from '@coinweb/contract-kit/dist/esm/operations/store'; -import { opMarker } from '../../global'; -import { isResolvedSlotOp, isResolvedStoreOp } from '../../utils'; -import { pushAwaitedTask } from '../awaited'; -import { shiftResolvedOp } from '../resolved'; +import { opMarker } from '../../../global'; +import { isResolvedSlotOp, isResolvedStoreOp } from '../../../utils'; +import { pushAwaitedTask, shiftResolvedOp } from '../../runtime'; export const storeOp = (claim: Claim, storeCweb?: bigint) => { console.log('storeOp'); diff --git a/packages/cwait/src/onchain/promisifiedOps/ops/take.ts b/packages/cwait/src/onchain/promisifiedFeatures/features/ops/take.ts similarity index 79% rename from packages/cwait/src/onchain/promisifiedOps/ops/take.ts rename to packages/cwait/src/onchain/promisifiedFeatures/features/ops/take.ts index 2f4dcdd..95fa14c 100644 --- a/packages/cwait/src/onchain/promisifiedOps/ops/take.ts +++ b/packages/cwait/src/onchain/promisifiedFeatures/features/ops/take.ts @@ -1,10 +1,9 @@ import { Claim, ClaimKey, constructTake, extractTake } from '@coinweb/contract-kit'; -import { TypedClaim } from '../../../types'; -import { opMarker } from '../../global'; -import { isResolvedSlotOp, isResolvedTakeOp } from '../../utils'; -import { pushAwaitedTask } from '../awaited'; -import { shiftResolvedOp } from '../resolved'; +import { TypedClaim } from '../../../../types'; +import { opMarker } from '../../../global'; +import { isResolvedSlotOp, isResolvedTakeOp } from '../../../utils'; +import { pushAwaitedTask, shiftResolvedOp } from '../../runtime'; export const takeOp = (key: ClaimKey) => { console.log('takeOp'); diff --git a/packages/cwait/src/onchain/promisifiedFeatures/index.ts b/packages/cwait/src/onchain/promisifiedFeatures/index.ts new file mode 100644 index 0000000..a66d282 --- /dev/null +++ b/packages/cwait/src/onchain/promisifiedFeatures/index.ts @@ -0,0 +1,2 @@ +export * from './runtime'; +export * from './features'; diff --git a/packages/cwait/src/onchain/promisifiedOps/awaited.ts b/packages/cwait/src/onchain/promisifiedFeatures/runtime/awaitedTasks.ts similarity index 88% rename from packages/cwait/src/onchain/promisifiedOps/awaited.ts rename to packages/cwait/src/onchain/promisifiedFeatures/runtime/awaitedTasks.ts index 23c1dbc..ad3a820 100644 --- a/packages/cwait/src/onchain/promisifiedOps/awaited.ts +++ b/packages/cwait/src/onchain/promisifiedFeatures/runtime/awaitedTasks.ts @@ -1,4 +1,4 @@ -import { PreparedOp, Task } from '../../types'; +import { PreparedOp, Task } from '../../../types'; const awaitedTasks: Task[] = []; diff --git a/packages/cwait/src/onchain/promisifiedFeatures/runtime/index.ts b/packages/cwait/src/onchain/promisifiedFeatures/runtime/index.ts new file mode 100644 index 0000000..7478a6d --- /dev/null +++ b/packages/cwait/src/onchain/promisifiedFeatures/runtime/index.ts @@ -0,0 +1,2 @@ +export * from './awaitedTasks'; +export * from './resolvedOps'; diff --git a/packages/cwait/src/onchain/promisifiedOps/resolved.ts b/packages/cwait/src/onchain/promisifiedFeatures/runtime/resolvedOps.ts similarity index 94% rename from packages/cwait/src/onchain/promisifiedOps/resolved.ts rename to packages/cwait/src/onchain/promisifiedFeatures/runtime/resolvedOps.ts index b53ff78..8c7f236 100644 --- a/packages/cwait/src/onchain/promisifiedOps/resolved.ts +++ b/packages/cwait/src/onchain/promisifiedFeatures/runtime/resolvedOps.ts @@ -1,4 +1,4 @@ -import { ResolvedOp } from '../../types'; +import { ResolvedOp } from '../../../types'; const resolvedOps: ResolvedOp[] = []; diff --git a/packages/cwait/src/onchain/promisifiedOps/index.ts b/packages/cwait/src/onchain/promisifiedOps/index.ts deleted file mode 100644 index 672baa5..0000000 --- a/packages/cwait/src/onchain/promisifiedOps/index.ts +++ /dev/null @@ -1,4 +0,0 @@ -export * from './awaited'; -export * from './cwait'; -export * from './ops'; -export * from './resolved'; diff --git a/packages/cwait/src/onchain/promisifiedOps/ops/lock.ts b/packages/cwait/src/onchain/promisifiedOps/ops/lock.ts deleted file mode 100644 index 7bb3fd6..0000000 --- a/packages/cwait/src/onchain/promisifiedOps/ops/lock.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { ResolvedLockOp } from '../../../types'; -import { opMarker } from '../../global'; -import { LockedKey } from '../../mutex'; -import { isResolvedLockOp, isResolvedSlotOp, uuid } from '../../utils'; -import { pushAwaitedTask } from '../awaited'; -import { shiftResolvedOp } from '../resolved'; - -export const lockOp = (keys: LockedKey[] | LockedKey) => { - console.log('lockOp'); - let opMarkerValue = false; - - const result = new Promise((resolve, reject) => { - try { - const { op, isOp } = shiftResolvedOp(); - - if (!isOp) { - pushAwaitedTask({ LockOp: { lockId: uuid(), keys: Array.isArray(keys) ? keys : [keys] } }); - opMarkerValue = true; - } else { - if (isResolvedSlotOp(op)) { - console.log('storeOp-slotOp'); - return; - } - - if (!isResolvedLockOp(op)) { - console.log('lockOp-error'); - throw new Error('Lock operation not found'); - } - - const result = op.LockOp; - - resolve(result); - } - } catch (error) { - reject(error); - } - }); - - (result as Promise & { [opMarker]: boolean })[opMarker] = opMarkerValue; - - return result; -}; diff --git a/packages/cwait/src/onchain/promisifiedOps/ops/unlock.ts b/packages/cwait/src/onchain/promisifiedOps/ops/unlock.ts deleted file mode 100644 index 404792a..0000000 --- a/packages/cwait/src/onchain/promisifiedOps/ops/unlock.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { opMarker } from '../../global'; -import { isResolvedSlotOp, isResolvedUnlockOp } from '../../utils'; -import { pushAwaitedTask } from '../awaited'; -import { shiftResolvedOp } from '../resolved'; - -export const unlock = (lockId: string) => { - console.log('lockOp'); - let opMarkerValue = false; - - const result = new Promise((resolve, reject) => { - try { - const { op, isOp } = shiftResolvedOp(); - - if (!isOp) { - pushAwaitedTask({ UnlockOp: { lockId } }); - opMarkerValue = true; - } else { - if (isResolvedSlotOp(op)) { - console.log('unlockOp-slotOp'); - return; - } - - if (!isResolvedUnlockOp(op)) { - console.log('unlockOp-error'); - throw new Error('Unlock operation not found'); - } - - resolve(); - } - } catch (error) { - reject(error); - } - }); - - (result as Promise & { [opMarker]: boolean })[opMarker] = opMarkerValue; - - return result; -}; diff --git a/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts b/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts index a7633ff..bff28e6 100644 --- a/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts +++ b/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts @@ -152,6 +152,22 @@ export const prepareInThreadTxs = ({ resolvedChildOps.push({ SlotOp: { ok: true } }); }); + if (preparedExecOps.length > 0) { + const execId = uuid(); + + const { callInfo, fee, ops } = constructExecOpsCall({ + issuer: context.issuer, + ops: preparedExecOps, + processId: context.thisId, + execId, + }); + + childCalls.push({ callInfo }); + txFee += fee; + + callArgs.push(...ops); + } + const returnTxs: NewTx[] = []; if (callArgs.length) { @@ -230,21 +246,5 @@ export const prepareInThreadTxs = ({ } } - if (preparedExecOps.length > 0) { - const execId = uuid(); - - const { callInfo, fee, ops } = constructExecOpsCall({ - issuer: context.issuer, - ops: preparedExecOps, - processId: context.thisId, - execId, - }); - - childCalls.push({ callInfo }); - txFee += fee; - - callArgs.push(...ops); - } - return { txs: returnTxs, calls: callsPrepared, txFee }; }; diff --git a/packages/cwait/src/onchain/utils/constructTx/prepareOutThreadTxs.ts b/packages/cwait/src/onchain/utils/constructTx/prepareOutThreadTxs.ts index d04bd3a..27f879c 100644 --- a/packages/cwait/src/onchain/utils/constructTx/prepareOutThreadTxs.ts +++ b/packages/cwait/src/onchain/utils/constructTx/prepareOutThreadTxs.ts @@ -35,7 +35,7 @@ export const prepareOutThreadTxs = ({ const preparedCalls: FullCallInfo[] = []; const preparedOps: PreparedOperation[] = []; - const preparedStoreAndTakes: (PreparedExtendedStoreOp | GTake)[] = []; + const preparedExecOps: (PreparedExtendedStoreOp | GTake)[] = []; let txFee = 0n; let callsPrepared = 0; @@ -77,7 +77,7 @@ export const prepareOutThreadTxs = ({ } case isPreparedStoreOp(op): case isPreparedTakeOp(op): { - preparedStoreAndTakes.push(op); + preparedExecOps.push(op); break; } case isPreparedLockOp(op): { @@ -107,10 +107,10 @@ export const prepareOutThreadTxs = ({ siblingCallResolvedOps.push({ SlotOp: { ok: true } }); }); - if (preparedStoreAndTakes.length > 0) { + if (preparedExecOps.length > 0) { const { callInfo, fee } = constructExecOpsCall({ issuer: context.issuer, - ops: preparedStoreAndTakes, + ops: preparedExecOps, processId: context.thisId, }); @@ -118,9 +118,17 @@ export const prepareOutThreadTxs = ({ txFee += fee; } + if (preparedCalls.length > 0 || preparedOps.length > 0) { + return { + txs: [constructContinueTx(getRawContext(), preparedOps, preparedCalls)], + txFee, + calls: callsPrepared, + }; + } + return { - txs: [constructContinueTx(getRawContext(), preparedOps, preparedCalls)], - txFee, - calls: callsPrepared, + txs: [], + txFee: 0n, + calls: 0, }; }; diff --git a/packages/cwait/src/onchain/utils/constructTx/prepareTxs.ts b/packages/cwait/src/onchain/utils/constructTx/prepareTxs.ts index 7e6ec95..eae399a 100644 --- a/packages/cwait/src/onchain/utils/constructTx/prepareTxs.ts +++ b/packages/cwait/src/onchain/utils/constructTx/prepareTxs.ts @@ -1,7 +1,7 @@ import { constructContinueTx, NewTx, passCwebFrom, sendCwebInterface } from '@coinweb/contract-kit'; import { context, getRawContext } from '../../context'; -import { getAwaitedTasks } from '../../promisifiedOps'; +import { getAwaitedTasks } from '../../promisifiedFeatures'; import { prepareInThreadTxs } from './prepareInThreadTxs'; import { prepareOutThreadTxs } from './prepareOutThreadTxs'; diff --git a/packages/ui/.env b/packages/ui/.env index 2964227..1f58cad 100644 --- a/packages/ui/.env +++ b/packages/ui/.env @@ -1,4 +1,4 @@ VITE_API_URL='https://api-cloud.coinweb.io/wallet' VITE_EXPLORER_URL='https://explorer.coinweb.io' -VITE_CONTRACT_ADDRESS="0xbf9ca1687e3441bed1afd495405778a9c06c2f5173829d47d9b87dfb150063d8" +VITE_CONTRACT_ADDRESS="0xa99515a81ee23d07dd08a72ec06646374d28df16de9ccdde16ed7e421ad26c83" From 47f21aecdc281939c54955e2c707870de1e2d1df Mon Sep 17 00:00:00 2001 From: Alex Date: Mon, 5 May 2025 18:57:33 +0300 Subject: [PATCH 4/8] fix: fix mutex results --- packages/cwait/src/contract-kit/methods.ts | 18 ++++++++++--- .../cwait/src/onchain/context/extractOps.ts | 21 ++++++++++++---- .../src/onchain/mutex/methods/execOps.ts | 8 ++++-- .../src/onchain/mutex/methods/notifyLock.ts | 4 +-- .../mutex/methods/preReadExecTakeOps.ts | 4 +-- .../onchain/mutex/methods/saveExecOpResult.ts | 4 +-- .../cwait/src/onchain/mutex/methods/unlock.ts | 4 +-- .../cwait/src/onchain/mutex/methods/wait.ts | 4 +-- .../utils/constructTx/prepareInThreadTxs.ts | 25 +++++++++++++++---- .../onchain/utils/constructTx/prepareTxs.ts | 2 +- packages/ui/.env | 2 +- 11 files changed, 69 insertions(+), 27 deletions(-) diff --git a/packages/cwait/src/contract-kit/methods.ts b/packages/cwait/src/contract-kit/methods.ts index 7dfbef8..55bf585 100644 --- a/packages/cwait/src/contract-kit/methods.ts +++ b/packages/cwait/src/contract-kit/methods.ts @@ -1,4 +1,4 @@ -import { extractContinuations, Context } from '@coinweb/contract-kit'; +import { extractContinuations, Context, MethodCallback as MethodCallbackOrig } from '@coinweb/contract-kit'; import { getContextCall, getContextGenesis, @@ -54,6 +54,18 @@ export async function executeHandler(contractModule: ContractHandlers): Promise< }; const method = getMethodName(context); const handler = getMethodHandler(contractModule, method); - const txs = await handler(context); - writeToResultFile(txs); + + console.log('EXECUTE >>>', method); + try { + const txs = await handler(context); + writeToResultFile(txs); + } catch (error) { + if (error instanceof Error) { + console.log('ERROR >>>', error.message); + console.log('ERROR >>>', error.stack); + } else { + console.log('ERROR >>>', error); + } + throw error; + } } diff --git a/packages/cwait/src/onchain/context/extractOps.ts b/packages/cwait/src/onchain/context/extractOps.ts index 6321561..accc0a5 100644 --- a/packages/cwait/src/onchain/context/extractOps.ts +++ b/packages/cwait/src/onchain/context/extractOps.ts @@ -68,8 +68,10 @@ export const extractOps = ({ if (isResolvedBlockOp(op)) { const { first } = op.BlockOp.blocks_on[0][0]; + console.log('first >>>', JSON.stringify(first)); + //Maybe it is needed to check more conditions here - if (Array.isArray(first) && first[0] === resultKey) { + if (Array.isArray(first)) { switch (first[0]) { case resultKey: { const nextAfterBlock = resolvedOps[i + 1]; @@ -112,8 +114,11 @@ export const extractOps = ({ const execOpResultClaim = extractRead(nextAfterBlock)?.[0]?.content; + console.log('nextAfterBlock >>>', JSON.stringify(nextAfterBlock)); + console.log('execOpResultClaim >>>', JSON.stringify(execOpResultClaim)); + if (!execOpResultClaim) { - throw new Error('Wrong mutex exec result'); + throw new Error('Wrong mutex exec result claim'); } executedOps = execOpResultClaim.body as MutexExecOpsResult; @@ -143,21 +148,27 @@ export const extractOps = ({ const allOps: ResolvedOp[] = []; - for (let i = 0; i < resolvedOps.length + executedOps.length; i++) { + const totalOpsCount = extractedOps.length + executedOps.length; + + for (let i = 0; i < totalOpsCount; i++) { + console.log('i >>>', i); + console.log('execOpsIndexes.includes(i) >>>', execOpsIndexes.includes(i)); if (execOpsIndexes.includes(i)) { const op = executedOps.shift(); if (!op) { - throw new Error('Wrong mutex exec result'); + throw new Error('Wrong mutex exec result place'); } + console.log('op >>>', JSON.stringify(op)); + if (op.ok) { allOps.push(op.resolved); } else { allOps.push({ SlotOp: { ok: false, error: op.error } }); } } else { - const op = resolvedOps.shift(); + const op = extractedOps.shift(); if (!op) { throw new Error('Contract call args parsing error'); diff --git a/packages/cwait/src/onchain/mutex/methods/execOps.ts b/packages/cwait/src/onchain/mutex/methods/execOps.ts index 31e2842..a1f3b24 100644 --- a/packages/cwait/src/onchain/mutex/methods/execOps.ts +++ b/packages/cwait/src/onchain/mutex/methods/execOps.ts @@ -7,7 +7,7 @@ import { extractRead, PreparedOperation, } from '@coinweb/contract-kit'; -import { getCallParameters, getContractArguments, getContractIssuer } from 'lib/onchain'; +import { getCallParameters, getContractIssuer, getMethodArguments } from 'lib/onchain'; import { TypedClaim } from 'lib/shared'; import { constructMutexExecOpsClaimStore } from '../claims'; @@ -20,7 +20,7 @@ export const execOps = (context: Context) => { const { availableCweb } = getCallParameters(context); const issuer = getContractIssuer(context); - const [ops, processId, execId] = getContractArguments(context); + const [ops, processId, execId] = getMethodArguments(context); const lockQueue = extractRead(extractContractArgs(context.tx)[0])?.map( ({ content }) => content as TypedClaim @@ -29,6 +29,10 @@ export const execOps = (context: Context) => { let availableOps: PreparedOperation[] = []; const unavailableIndexes: number[] = []; + console.log('extractContractArgs(context.tx) >>>', JSON.stringify(extractContractArgs(context.tx))); + console.log('ops >>>', JSON.stringify(ops)); + console.log('lockQueue >>>', JSON.stringify(lockQueue)); + if (!lockQueue) { availableOps = ops; } else { diff --git a/packages/cwait/src/onchain/mutex/methods/notifyLock.ts b/packages/cwait/src/onchain/mutex/methods/notifyLock.ts index 3d5e838..41d6c2f 100644 --- a/packages/cwait/src/onchain/mutex/methods/notifyLock.ts +++ b/packages/cwait/src/onchain/mutex/methods/notifyLock.ts @@ -1,11 +1,11 @@ import { constructContinueTx, Context } from '@coinweb/contract-kit'; -import { getContractArguments } from 'lib/onchain'; +import { getMethodArguments } from 'lib/onchain'; import { constructMutexBlockLockClaimStore } from '../claims/mutexBlockLock'; import { MutexNotifyLockArgs } from '../types'; export const notifyLock = (context: Context) => { - const [lockId] = getContractArguments(context); + const [lockId] = getMethodArguments(context); return [constructContinueTx(context, [constructMutexBlockLockClaimStore({ lockId })])]; }; diff --git a/packages/cwait/src/onchain/mutex/methods/preReadExecTakeOps.ts b/packages/cwait/src/onchain/mutex/methods/preReadExecTakeOps.ts index 6e4fb0c..b90a951 100644 --- a/packages/cwait/src/onchain/mutex/methods/preReadExecTakeOps.ts +++ b/packages/cwait/src/onchain/mutex/methods/preReadExecTakeOps.ts @@ -6,7 +6,7 @@ import { extractRead, passCwebFrom, } from '@coinweb/contract-kit'; -import { getCallParameters, getContractArguments, getContractIssuer } from 'lib/onchain'; +import { getCallParameters, getContractIssuer, getMethodArguments } from 'lib/onchain'; import { MutexPreReadTakeOpsArgs, MutexSaveExecOpResultArgs } from '../types'; @@ -16,7 +16,7 @@ export const preReadExecTakeOps = (context: Context) => { const { availableCweb } = getCallParameters(context); const issuer = getContractIssuer(context); - const [execId, unavailableIndexes, ops] = getContractArguments(context); + const [execId, unavailableIndexes, ops] = getMethodArguments(context); const storedCweb = extractContractArgs(context.tx) .map((op) => BigInt(extractRead(op)?.[0]?.content.fees_stored ?? 0)) diff --git a/packages/cwait/src/onchain/mutex/methods/saveExecOpResult.ts b/packages/cwait/src/onchain/mutex/methods/saveExecOpResult.ts index a531c9a..7b27181 100644 --- a/packages/cwait/src/onchain/mutex/methods/saveExecOpResult.ts +++ b/packages/cwait/src/onchain/mutex/methods/saveExecOpResult.ts @@ -1,5 +1,5 @@ import { constructContinueTx, Context, extractContractArgs } from '@coinweb/contract-kit'; -import { getCallParameters, getContractArguments } from 'lib/onchain'; +import { getCallParameters, getMethodArguments } from 'lib/onchain'; import { constructMutexExecOpsClaimStore } from '../claims'; import { MutexExecOpsResult, MutexSaveExecOpResultArgs } from '../types'; @@ -7,7 +7,7 @@ import { MutexExecOpsResult, MutexSaveExecOpResultArgs } from '../types'; export const saveExecOpResult = (context: Context) => { const { availableCweb } = getCallParameters(context); - const [execId, unavailableIndexes] = getContractArguments(context); + const [execId, unavailableIndexes] = getMethodArguments(context); const resolvedOps = extractContractArgs(context.tx); const result = new Array(resolvedOps.length + unavailableIndexes.length) diff --git a/packages/cwait/src/onchain/mutex/methods/unlock.ts b/packages/cwait/src/onchain/mutex/methods/unlock.ts index ec5167c..c98abfe 100644 --- a/packages/cwait/src/onchain/mutex/methods/unlock.ts +++ b/packages/cwait/src/onchain/mutex/methods/unlock.ts @@ -1,5 +1,5 @@ import { constructContinueTx, constructContractRef, constructTake, Context, passCwebFrom } from '@coinweb/contract-kit'; -import { getCallParameters, getContractArguments, getContractIssuer } from 'lib/onchain'; +import { getCallParameters, getContractIssuer, getMethodArguments } from 'lib/onchain'; import { constructMutexLockClaimKey } from '../claims'; import { lockFee } from '../settings'; @@ -11,7 +11,7 @@ export const mutexUnlock = (context: Context) => { const { availableCweb } = getCallParameters(context); const issuer = getContractIssuer(context); - const [lockId, timestamp] = getContractArguments(context); + const [lockId, timestamp] = getMethodArguments(context); return [ constructContinueTx( diff --git a/packages/cwait/src/onchain/mutex/methods/wait.ts b/packages/cwait/src/onchain/mutex/methods/wait.ts index af8d489..5104d4b 100644 --- a/packages/cwait/src/onchain/mutex/methods/wait.ts +++ b/packages/cwait/src/onchain/mutex/methods/wait.ts @@ -1,5 +1,5 @@ import { constructContinueTx, constructContractRef, Context } from '@coinweb/contract-kit'; -import { getContractArguments, getContractIssuer } from 'lib/onchain'; +import { getContractIssuer, getMethodArguments } from 'lib/onchain'; import { waitSteps } from '../settings'; import { MutexNotifyLockArgs, MutexWaitNotifyArgs } from '../types'; @@ -8,7 +8,7 @@ import { notifyLockMethodName } from './names'; export const wait = (context: Context) => { const issuer = getContractIssuer(context); - const [step, lockId] = getContractArguments(context); + const [step, lockId] = getMethodArguments(context); //Including the notifyLock method as step if (step + 1 < waitSteps) { diff --git a/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts b/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts index bff28e6..f3d8ccd 100644 --- a/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts +++ b/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts @@ -15,7 +15,7 @@ import { ExecutorMethodArgs, PreparedExtendedStoreOp, PreparedOp, ResolvedOp, Re import { constructFundsClaimRangRead, constructFundsClaimStore } from '../../claims/funds'; import { constructResultBlockFilter, constructResultClaimTake, resultKey } from '../../claims/result'; import { context, getRawContext } from '../../context'; -import { constructExecOpsCall, constructLockCall, constructUnlockCall } from '../../mutex'; +import { constructExecOpsCall, constructLockCall, constructUnlockCall, mutexExecOpsKey } from '../../mutex'; import { isPreparedExecOp, isPreparedLockOp, @@ -47,7 +47,7 @@ export const prepareInThreadTxs = ({ const restOfAvailableCweb = availableCweb - outThreadFee; - const fee = BigInt(takeOps.length) * 100n - 3000n; + const fee = BigInt(takeOps.length) * 100n + 3000n; //TODO: Remove magic numbers const restOfCweb = restOfAvailableCweb + storedCweb - fee; const txs = @@ -115,10 +115,25 @@ export const prepareInThreadTxs = ({ break; } - case isPreparedStoreOp(op): + case isPreparedStoreOp(op): { + if ((op.StoreOp.key.first_part as [string])[0] !== resultKey) { + preparedExecOps.push(op); + excOpsIndexes.push(i); + } else { + callArgs.push(op); + txFee += 100n; + } + break; + } case isPreparedTakeOp(op): { - preparedExecOps.push(op); - excOpsIndexes.push(i); + //TODO: simplify to decrease coupling + if ((op.TakeOp.key.first_part as [string])[0] !== mutexExecOpsKey) { + preparedExecOps.push(op); + excOpsIndexes.push(i); + } else { + callArgs.push(op); + txFee += 100n; + } break; } case isPreparedLockOp(op): { diff --git a/packages/cwait/src/onchain/utils/constructTx/prepareTxs.ts b/packages/cwait/src/onchain/utils/constructTx/prepareTxs.ts index eae399a..cbcc122 100644 --- a/packages/cwait/src/onchain/utils/constructTx/prepareTxs.ts +++ b/packages/cwait/src/onchain/utils/constructTx/prepareTxs.ts @@ -27,7 +27,7 @@ export const prepareTx = ( const { availableCweb, takeOps, storedCweb } = context.funds; const { user } = context; - const fee = BigInt(takeOps.length) * 100n - 3000n; + const fee = BigInt(takeOps.length) * 100n + 3000n; //TODO: Remove magic numbers const restOfCweb = availableCweb + storedCweb - fee; const txs = diff --git a/packages/ui/.env b/packages/ui/.env index 1f58cad..87e10c5 100644 --- a/packages/ui/.env +++ b/packages/ui/.env @@ -1,4 +1,4 @@ VITE_API_URL='https://api-cloud.coinweb.io/wallet' VITE_EXPLORER_URL='https://explorer.coinweb.io' -VITE_CONTRACT_ADDRESS="0xa99515a81ee23d07dd08a72ec06646374d28df16de9ccdde16ed7e421ad26c83" +VITE_CONTRACT_ADDRESS="0x6195433d58b3415f2e4fe909c770a9ecc1c2f15ee59585daa06820a502fb9812" From 84ff2837ebbbe27b0e09fc87c0aceb2eff3b673f Mon Sep 17 00:00:00 2001 From: Alex Date: Tue, 6 May 2025 18:03:51 +0300 Subject: [PATCH 5/8] fix: parallel execution --- packages/contract.cm/src/onchain/addWord.ts | 2 +- .../contract.cm/src/onchain/extraLogic.ts | 14 ++-- .../contract.cm/src/onchain/extraLogic2.ts | 8 +-- packages/cwait/src/onchain/context/context.ts | 1 - .../cwait/src/onchain/context/extractOps.ts | 7 -- packages/cwait/src/onchain/executor.ts | 25 ++++--- .../features/cwait/cwait.ts | 26 ++----- .../promisifiedFeatures/features/ops/block.ts | 3 - .../features/ops/rangeRead.ts | 3 - .../promisifiedFeatures/features/ops/read.ts | 3 - .../promisifiedFeatures/features/ops/store.ts | 3 - .../promisifiedFeatures/features/ops/take.ts | 3 - .../runtime/awaitedTasks.ts | 2 +- .../onchain/utils/constructTx/constructTx.ts | 8 ++- .../utils/constructTx/prepareInThreadTxs.ts | 3 - .../utils/constructTx/prepareOutThreadTxs.ts | 2 - .../onchain/utils/constructTx/prepareTxs.ts | 24 +++---- .../cwait/src/onchain/utils/typeGuards.ts | 68 ------------------- packages/ui/.env | 2 +- 19 files changed, 56 insertions(+), 151 deletions(-) diff --git a/packages/contract.cm/src/onchain/addWord.ts b/packages/contract.cm/src/onchain/addWord.ts index 715dbda..aa27151 100644 --- a/packages/contract.cm/src/onchain/addWord.ts +++ b/packages/contract.cm/src/onchain/addWord.ts @@ -27,7 +27,7 @@ export const addWord = cwait(async (...[word]: AddWordArgs) => { await storeOp(constructClaim(createWordKey(id), { word }, '0x0')); console.log('await extraLogic'); - const wordClaim = await extraLogic(id); + const [wordClaim] = await Promise.all([extraLogic(id, 1), extraLogic(id, 2)]); const newWord1 = (wordClaim?.body.word ?? '') + '_1'; const newId1 = hashCode(newWord1); diff --git a/packages/contract.cm/src/onchain/extraLogic.ts b/packages/contract.cm/src/onchain/extraLogic.ts index 50c95c0..3a09acf 100644 --- a/packages/contract.cm/src/onchain/extraLogic.ts +++ b/packages/contract.cm/src/onchain/extraLogic.ts @@ -5,14 +5,18 @@ import { createWordKey, WordClaimBody } from '../offchain'; import { extraLogic2 } from './extraLogic2'; -export const extraLogic = cwait(async (id: string) => { - console.log('extraLogic START'); +export const extraLogic = cwait(async (id: string, i: number) => { + console.log('extraLogic START + ', i); const result = await readOp>(createWordKey(id)); await storeOp( - constructClaim(createWordKey(id), { word: result?.body.word.split('').reverse().join('') + '_extraLogic' }, '0x0') + constructClaim( + createWordKey(id), + { word: result?.body.word.split('').reverse().join('') + '_extraLogic + ' + i }, + '0x0' + ) ); - console.log('extraLogic return extraLogic2'); - return extraLogic2(id); + console.log('extraLogic return extraLogic2 + ', i); + return extraLogic2(id, i); }); diff --git a/packages/contract.cm/src/onchain/extraLogic2.ts b/packages/contract.cm/src/onchain/extraLogic2.ts index 80eff4a..70bbfd7 100644 --- a/packages/contract.cm/src/onchain/extraLogic2.ts +++ b/packages/contract.cm/src/onchain/extraLogic2.ts @@ -2,12 +2,12 @@ import { cwait, readOp, TypedClaim } from 'cwait'; import { createWordKey, WordClaimBody } from '../offchain'; -export const extraLogic2 = cwait(async (id: string) => { - console.log('extraLogic2 START'); +export const extraLogic2 = cwait(async (id: string, i: number) => { + console.log('extraLogic2 START + ', i); - console.log('await readOp'); + console.log('await extraLogic2 readOp + ', i); const result = await readOp>(createWordKey(id)); - console.log('extraLogic2 END'); + console.log('extraLogic2 END + ', i); return result; }); diff --git a/packages/cwait/src/onchain/context/context.ts b/packages/cwait/src/onchain/context/context.ts index cbadb3f..778ff46 100644 --- a/packages/cwait/src/onchain/context/context.ts +++ b/packages/cwait/src/onchain/context/context.ts @@ -93,7 +93,6 @@ export const handleContext = (ctx: Context) => { console.log('Available Cweb: ', availableCweb); console.log('Stored Cweb: ', storedCweb); - console.log('Take Ops: ', fundsTakeOps); initialContext.funds = { availableCweb: availableCweb, diff --git a/packages/cwait/src/onchain/context/extractOps.ts b/packages/cwait/src/onchain/context/extractOps.ts index accc0a5..f070a51 100644 --- a/packages/cwait/src/onchain/context/extractOps.ts +++ b/packages/cwait/src/onchain/context/extractOps.ts @@ -114,9 +114,6 @@ export const extractOps = ({ const execOpResultClaim = extractRead(nextAfterBlock)?.[0]?.content; - console.log('nextAfterBlock >>>', JSON.stringify(nextAfterBlock)); - console.log('execOpResultClaim >>>', JSON.stringify(execOpResultClaim)); - if (!execOpResultClaim) { throw new Error('Wrong mutex exec result claim'); } @@ -151,8 +148,6 @@ export const extractOps = ({ const totalOpsCount = extractedOps.length + executedOps.length; for (let i = 0; i < totalOpsCount; i++) { - console.log('i >>>', i); - console.log('execOpsIndexes.includes(i) >>>', execOpsIndexes.includes(i)); if (execOpsIndexes.includes(i)) { const op = executedOps.shift(); @@ -160,8 +155,6 @@ export const extractOps = ({ throw new Error('Wrong mutex exec result place'); } - console.log('op >>>', JSON.stringify(op)); - if (op.ok) { allOps.push(op.resolved); } else { diff --git a/packages/cwait/src/onchain/executor.ts b/packages/cwait/src/onchain/executor.ts index 635fe4d..1595498 100644 --- a/packages/cwait/src/onchain/executor.ts +++ b/packages/cwait/src/onchain/executor.ts @@ -1,11 +1,14 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ import { Context, NewTx, getMethodArguments, isSelfCall } from '@coinweb/contract-kit'; +import { Task } from '../types'; + import { context, getRawContext, handleContext } from './context'; +import { getAwaitedTasks } from './promisifiedFeatures'; import { pushResolvedOp } from './promisifiedFeatures/runtime/resolvedOps'; import { constructTx } from './utils'; -let abortExecution: ((result: boolean) => void) | null = null; +let abortExecution: ((result: { isFullyExecuted: boolean; awaitedTasks: Task[] }) => void) | null = null; export const executor = (method: (...args: any[]) => Promise) => { return async (ctx: Context): Promise => { @@ -18,16 +21,19 @@ export const executor = (method: (...args: any[]) => Promise) => { } if (shouldRestart) { - return constructTx(false); + const awaitedTasks = getAwaitedTasks(); + + return constructTx(awaitedTasks, false); } - const execution = new Promise((resolve, reject) => { + const execution = new Promise<{ isFullyExecuted: boolean; awaitedTasks: Task[] }>((resolve, reject) => { abortExecution = resolve; method(...context.initialArgs).then( () => { console.log('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< executor-resolved'); - resolve(true); + const awaitedTasks = getAwaitedTasks(); + resolve({ isFullyExecuted: true, awaitedTasks }); }, (error) => { console.log(' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< Promise) => { //@ts-expect-error // eslint-disable-next-line @typescript-eslint/no-unsafe-call os.setTimeout(() => { - abortExecution?.(false); + const awaitedTasks = getAwaitedTasks(); + abortExecution?.({ isFullyExecuted: false, awaitedTasks }); }, 0); try { - const isFullyExecuted = await execution; - return constructTx(isFullyExecuted); + const { isFullyExecuted, awaitedTasks } = await execution; + + return constructTx(awaitedTasks, isFullyExecuted); } catch (error) { console.log('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< executor-error'); console.log((error as Error).message); @@ -56,5 +64,6 @@ export const executor = (method: (...args: any[]) => Promise) => { export const stopExecution = () => { console.log('stopExecution'); - abortExecution?.(false); + const awaitedTasks = getAwaitedTasks(); + abortExecution?.({ isFullyExecuted: false, awaitedTasks }); }; diff --git a/packages/cwait/src/onchain/promisifiedFeatures/features/cwait/cwait.ts b/packages/cwait/src/onchain/promisifiedFeatures/features/cwait/cwait.ts index cfe97cf..633001b 100644 --- a/packages/cwait/src/onchain/promisifiedFeatures/features/cwait/cwait.ts +++ b/packages/cwait/src/onchain/promisifiedFeatures/features/cwait/cwait.ts @@ -1,37 +1,29 @@ import { constructStore } from '@coinweb/contract-kit/dist/esm/operations/store'; import { constructResultClaim } from '../../../claims/result'; -import { context } from '../../../context'; import { stopExecution } from '../../../executor'; import { opMarker } from '../../../global'; import { isResolvedChildOp, isResolvedExecOp, isResolvedSlotOp } from '../../../utils'; import { uuid } from '../../../utils'; -import { getAwaitedTasks, pushAwaitedTask } from '../../runtime/awaitedTasks'; +import { getAwaitedTasksCount, pushAwaitedTask } from '../../runtime/awaitedTasks'; import { getUsedOps, saveUsedOps, shiftResolvedOp } from '../../runtime/resolvedOps'; let isRootDetected = false; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const cwait = Promise>(asyncCallback: TAsyncCallback) => { - console.log('cwait: ', asyncCallback.name); let isRoot = false; return (async (...args: Parameters) => { - console.log('cwait callback'); if (!isRootDetected) { isRootDetected = true; isRoot = true; } - console.log('isRoot:', isRoot); - console.log('isChild:', context.isChild); - if (isRoot) { return asyncCallback(...args); } - console.log('child logic'); - const { op, isOp } = shiftResolvedOp(); if (!isOp) { @@ -48,33 +40,27 @@ export const cwait = Promise; } else { if (isResolvedSlotOp(op)) { - console.log('cwait-slotOp'); return new Promise(() => null); } if (isResolvedExecOp(op)) { - console.log('cwait-execOp'); - saveUsedOps(); - const result = await asyncCallback(...args); + asyncCallback(...args); - const awaitedOps = getAwaitedTasks(); - - if (!awaitedOps.length) { + if (!getAwaitedTasksCount()) { pushAwaitedTask(constructStore(constructResultClaim(op.ExecOp.id, getUsedOps()))); - - stopExecution(); } - return result; + stopExecution(); + + return new Promise(() => null); } if (isResolvedChildOp(op)) { return asyncCallback(...args); } - console.log('cwait-error'); throw new Error('Read operation not found'); } }) as TAsyncCallback; diff --git a/packages/cwait/src/onchain/promisifiedFeatures/features/ops/block.ts b/packages/cwait/src/onchain/promisifiedFeatures/features/ops/block.ts index 9ec99b8..5b0faf5 100644 --- a/packages/cwait/src/onchain/promisifiedFeatures/features/ops/block.ts +++ b/packages/cwait/src/onchain/promisifiedFeatures/features/ops/block.ts @@ -5,7 +5,6 @@ import { isResolvedBlockOp, isResolvedSlotOp } from '../../../utils'; import { pushAwaitedTask, shiftResolvedOp } from '../../runtime'; export const blockOp = (filters: BlockFilter[]) => { - console.log('blockOp'); let opMarkerValue = false; const result = new Promise<[BlockFilter, boolean][] | null>((resolve, reject) => { @@ -17,12 +16,10 @@ export const blockOp = (filters: BlockFilter[]) => { opMarkerValue = true; } else { if (isResolvedSlotOp(op)) { - console.log('blockOp-slotOp'); return; } if (!isResolvedBlockOp(op)) { - console.log('blockOp-error'); throw new Error('Block operation not found'); } diff --git a/packages/cwait/src/onchain/promisifiedFeatures/features/ops/rangeRead.ts b/packages/cwait/src/onchain/promisifiedFeatures/features/ops/rangeRead.ts index ea1673b..bd2162a 100644 --- a/packages/cwait/src/onchain/promisifiedFeatures/features/ops/rangeRead.ts +++ b/packages/cwait/src/onchain/promisifiedFeatures/features/ops/rangeRead.ts @@ -13,7 +13,6 @@ export const rangeReadOp = ( maxCount: number ) => { let opMarkerValue = false; - console.log('rangeReadOp'); const result = new Promise((resolve, reject) => { try { @@ -24,12 +23,10 @@ export const rangeReadOp = ( opMarkerValue = true; } else { if (isResolvedSlotOp(op)) { - console.log('rangeReadOp-slotOp'); return; } if (!isResolvedReadOp(op)) { - console.log('rangeReadOp-error'); throw new Error('Read operation not found'); } diff --git a/packages/cwait/src/onchain/promisifiedFeatures/features/ops/read.ts b/packages/cwait/src/onchain/promisifiedFeatures/features/ops/read.ts index af4fcf4..312cd15 100644 --- a/packages/cwait/src/onchain/promisifiedFeatures/features/ops/read.ts +++ b/packages/cwait/src/onchain/promisifiedFeatures/features/ops/read.ts @@ -7,7 +7,6 @@ import { isResolvedReadOp, isResolvedSlotOp } from '../../../utils'; import { pushAwaitedTask, shiftResolvedOp } from '../../runtime'; export const readOp = (key: ClaimKey) => { - console.log('readOp'); let opMarkerValue = false; const result = new Promise((resolve, reject) => { @@ -19,12 +18,10 @@ export const readOp = (key: ClaimKey) => { opMarkerValue = true; } else { if (isResolvedSlotOp(op)) { - console.log('readOp-slotOp'); return; } if (!isResolvedReadOp(op)) { - console.log('readOp-error'); throw new Error('Read operation not found'); } diff --git a/packages/cwait/src/onchain/promisifiedFeatures/features/ops/store.ts b/packages/cwait/src/onchain/promisifiedFeatures/features/ops/store.ts index d949996..432e145 100644 --- a/packages/cwait/src/onchain/promisifiedFeatures/features/ops/store.ts +++ b/packages/cwait/src/onchain/promisifiedFeatures/features/ops/store.ts @@ -6,7 +6,6 @@ import { isResolvedSlotOp, isResolvedStoreOp } from '../../../utils'; import { pushAwaitedTask, shiftResolvedOp } from '../../runtime'; export const storeOp = (claim: Claim, storeCweb?: bigint) => { - console.log('storeOp'); let opMarkerValue = false; const result = new Promise((resolve, reject) => { @@ -18,12 +17,10 @@ export const storeOp = (claim: Claim, storeCweb?: bigint) => { opMarkerValue = true; } else { if (isResolvedSlotOp(op)) { - console.log('storeOp-slotOp'); return; } if (!isResolvedStoreOp(op)) { - console.log('storeOp-error'); throw new Error('Store operation not found'); } diff --git a/packages/cwait/src/onchain/promisifiedFeatures/features/ops/take.ts b/packages/cwait/src/onchain/promisifiedFeatures/features/ops/take.ts index 95fa14c..05c8dee 100644 --- a/packages/cwait/src/onchain/promisifiedFeatures/features/ops/take.ts +++ b/packages/cwait/src/onchain/promisifiedFeatures/features/ops/take.ts @@ -6,7 +6,6 @@ import { isResolvedSlotOp, isResolvedTakeOp } from '../../../utils'; import { pushAwaitedTask, shiftResolvedOp } from '../../runtime'; export const takeOp = (key: ClaimKey) => { - console.log('takeOp'); let opMarkerValue = false; const result = new Promise((resolve, reject) => { @@ -18,12 +17,10 @@ export const takeOp = (key: ClaimKey) => { opMarkerValue = true; } else { if (isResolvedSlotOp(op)) { - console.log('takeOp-slotOp'); return; } if (!isResolvedTakeOp(op)) { - console.log('takeOp-error'); throw new Error('Take operation not found'); } diff --git a/packages/cwait/src/onchain/promisifiedFeatures/runtime/awaitedTasks.ts b/packages/cwait/src/onchain/promisifiedFeatures/runtime/awaitedTasks.ts index ad3a820..f1f4ddc 100644 --- a/packages/cwait/src/onchain/promisifiedFeatures/runtime/awaitedTasks.ts +++ b/packages/cwait/src/onchain/promisifiedFeatures/runtime/awaitedTasks.ts @@ -6,7 +6,7 @@ export const pushAwaitedTask = (op: PreparedOp) => { awaitedTasks.push({ op, batchId: -1 }); }; -export const getAwaitedTasks = () => awaitedTasks; +export const getAwaitedTasks = () => [...awaitedTasks]; export const markTaskBatch = (count: number, batchId: number) => { for (let i = 1; i <= count; i++) { diff --git a/packages/cwait/src/onchain/utils/constructTx/constructTx.ts b/packages/cwait/src/onchain/utils/constructTx/constructTx.ts index df863c8..c91adef 100644 --- a/packages/cwait/src/onchain/utils/constructTx/constructTx.ts +++ b/packages/cwait/src/onchain/utils/constructTx/constructTx.ts @@ -1,9 +1,11 @@ +import { Task } from '../../../types'; + import { prepareTx } from './prepareTxs'; -export const constructTx = (isFullyExecuted: boolean) => { - const { calls, txFee } = prepareTx(isFullyExecuted, 0n); +export const constructTx = (awaitedTasks: Task[], isFullyExecuted: boolean) => { + const { calls, txFee } = prepareTx({ awaitedTasks, isFullyExecuted, txFee: 0n }); - const { txs } = prepareTx(isFullyExecuted, txFee, calls); + const { txs } = prepareTx({ awaitedTasks, isFullyExecuted, txFee, callsCount: calls }); return txs; }; diff --git a/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts b/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts index f3d8ccd..f889651 100644 --- a/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts +++ b/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts @@ -82,7 +82,6 @@ export const prepareInThreadTxs = ({ ops.forEach((op, i) => { switch (true) { case isPreparedExecOp(op): { - console.log('Child call info'); const id = op.ExecOp.id; callArgs.push(constructBlock([constructResultBlockFilter(id)]), constructResultClaimTake(id)); @@ -192,8 +191,6 @@ export const prepareInThreadTxs = ({ if ('StoreOp' in latestCallArg && (latestCallArg.StoreOp.key.first_part as [string])[0] === resultKey) { //SAVE RESULT CLAIMS - console.log('SAVE RESULT CLAIMS', JSON.stringify(callArgs)); - if (callArgs.length > 1) { throw new Error('Unexpected count of result ops'); } diff --git a/packages/cwait/src/onchain/utils/constructTx/prepareOutThreadTxs.ts b/packages/cwait/src/onchain/utils/constructTx/prepareOutThreadTxs.ts index 27f879c..c2d98e8 100644 --- a/packages/cwait/src/onchain/utils/constructTx/prepareOutThreadTxs.ts +++ b/packages/cwait/src/onchain/utils/constructTx/prepareOutThreadTxs.ts @@ -43,8 +43,6 @@ export const prepareOutThreadTxs = ({ ops.forEach((op) => { switch (true) { case isPreparedExecOp(op): { - console.log('Sibling call info'); - const id = op.ExecOp.id; const callInfo = { diff --git a/packages/cwait/src/onchain/utils/constructTx/prepareTxs.ts b/packages/cwait/src/onchain/utils/constructTx/prepareTxs.ts index cbcc122..1167ff0 100644 --- a/packages/cwait/src/onchain/utils/constructTx/prepareTxs.ts +++ b/packages/cwait/src/onchain/utils/constructTx/prepareTxs.ts @@ -1,23 +1,23 @@ import { constructContinueTx, NewTx, passCwebFrom, sendCwebInterface } from '@coinweb/contract-kit'; +import { Task } from '../../../types'; import { context, getRawContext } from '../../context'; -import { getAwaitedTasks } from '../../promisifiedFeatures'; import { prepareInThreadTxs } from './prepareInThreadTxs'; import { prepareOutThreadTxs } from './prepareOutThreadTxs'; import { splitTasks } from './splitTasks'; -export const prepareTx = ( - isFullyExecuted: boolean, - txFee: bigint, - callsCount?: number -): { txs: NewTx[]; calls: number; txFee: bigint } => { - console.log('Calls Count: ', callsCount); - - const awaitedTasks = getAwaitedTasks(); - - console.log('Awaited Tasks: ', JSON.stringify(awaitedTasks)); - +export const prepareTx = ({ + awaitedTasks, + isFullyExecuted, + txFee, + callsCount, +}: { + awaitedTasks: Task[]; + isFullyExecuted: boolean; + txFee: bigint; + callsCount?: number; +}): { txs: NewTx[]; calls: number; txFee: bigint } => { if (!awaitedTasks.length) { if (context.isChild) { return { txs: [], calls: 0, txFee: 0n }; diff --git a/packages/cwait/src/onchain/utils/typeGuards.ts b/packages/cwait/src/onchain/utils/typeGuards.ts index b35ae5b..c825dab 100644 --- a/packages/cwait/src/onchain/utils/typeGuards.ts +++ b/packages/cwait/src/onchain/utils/typeGuards.ts @@ -29,137 +29,69 @@ import { } from '../../types'; export const isResolvedSlotOp = (op?: ResolvedOp | null): op is ResolvedSlotOp => { - if (op && 'SlotOp' in op) { - console.log('isResolvedSlotOp >>> ', JSON.stringify(op)); - } - return !!(op && 'SlotOp' in op); }; export const isResolvedExecOp = (op?: ResolvedOp | null): op is ResolvedExecOp => { - if (op && 'ExecOp' in op) { - console.log('isResolvedExecOp >>> ', JSON.stringify(op)); - } - return !!(op && 'ExecOp' in op); }; export const isResolvedChildOp = (op?: ResolvedOp | null): op is ResolvedChildOp => { - if (op && 'ChildOp' in op) { - console.log('isResolvedChildOp >>> ', JSON.stringify(op)); - } - return !!(op && 'ChildOp' in op); }; export const isResolvedLockOp = (op?: ResolvedOp | null): op is ResolvedLockOp => { - if (op && 'LockOp' in op) { - console.log('isResolvedLockOp >>> ', JSON.stringify(op)); - } - return !!(op && 'LockOp' in op); }; export const isResolvedUnlockOp = (op?: ResolvedOp | null): op is ResolvedUnlockOp => { - if (op && 'UnlockOp' in op) { - console.log('isResolvedUnlockOp >>> ', JSON.stringify(op)); - } - return !!(op && 'UnlockOp' in op); }; export const isResolvedBlockOp = (op?: ResolvedOp | null): op is GBlock => { - if (op && 'BlockOp' in op) { - console.log('isResolvedBlockOp >>> ', JSON.stringify(op)); - } - return isResolvedBlock(op as ResolvedOperation); //TODO: Fix contract-kit types }; export const isResolvedStoreOp = (op?: ResolvedOp | null): op is GStore => { - if (op && 'StoreOp' in op) { - console.log('isResolvedStoreOp >>> ', JSON.stringify(op)); - } - return isResolvedStore(op as ResolvedOperation); //TODO: Fix contract-kit types }; export const isResolvedCallOp = (op?: ResolvedOp | null): op is GCall => { - if (op && 'CallOp' in op) { - console.log('isResolvedCallOp >>> ', JSON.stringify(op)); - } - return isResolvedCall(op as ResolvedOperation); //TODO: Fix contract-kit types }; export const isResolvedTakeOp = (op?: ResolvedOp | null): op is GTake => { - if (op && 'TakeOp' in op) { - console.log('isResolvedTakeOp >>> ', JSON.stringify(op)); - } - return isResolvedTake(op as ResolvedOperation); //TODO: Fix contract-kit types }; export const isResolvedReadOp = (op?: ResolvedOp | null): op is GRead => { - if (op && 'ReadOp' in op) { - console.log('isResolvedReadOp >>> ', JSON.stringify(op)); - } - return isResolvedRead(op as ResolvedOperation); //TODO: Fix contract-kit types }; export const isPreparedExecOp = (op?: PreparedOp | null): op is PreparedExecOp => { - if (op && 'ExecOp' in op) { - console.log('isPreparedExecOp >>> ', JSON.stringify(op)); - } - return !!(op && 'ExecOp' in op); }; export const isPreparedBlockOp = (op?: PreparedOp | null): op is GBlock => { - if (op && 'BlockOp' in op) { - console.log('isPreparedBlockOp >>> ', JSON.stringify(op)); - } - return !!(op && 'BlockOp' in op); }; export const isPreparedLockOp = (op?: PreparedOp | null): op is PreparedLockOp => { - if (op && 'LockOp' in op) { - console.log('isPreparedLockOp >>> ', JSON.stringify(op)); - } - return !!(op && 'LockOp' in op); }; export const isPreparedUnlockOp = (op?: PreparedOp | null): op is PreparedUnlockOp => { - if (op && 'UnlockOp' in op) { - console.log('isPreparedUnlockOp >>> ', JSON.stringify(op)); - } - return !!(op && 'UnlockOp' in op); }; export const isPreparedStoreOp = (op?: PreparedOp | null): op is GStore => { - if (op && 'StoreOp' in op) { - console.log('isPreparedStoreOp >>> ', JSON.stringify(op)); - } - return !!(op && 'StoreOp' in op); }; export const isPreparedExtendedStoreOp = (op?: PreparedOp | null): op is PreparedExtendedStoreOp => { - if (op && 'StoreOp' in op) { - console.log('isPreparedExtendedStoreOp >>> ', JSON.stringify(op)); - } - return !!(op && 'StoreOp' in op); }; export const isPreparedTakeOp = (op?: PreparedOp | null): op is GTake => { - if (op && 'TakeOp' in op) { - console.log('isPreparedTakeOp >>> ', JSON.stringify(op)); - } - return !!(op && 'TakeOp' in op); }; diff --git a/packages/ui/.env b/packages/ui/.env index 87e10c5..d30b9fc 100644 --- a/packages/ui/.env +++ b/packages/ui/.env @@ -1,4 +1,4 @@ VITE_API_URL='https://api-cloud.coinweb.io/wallet' VITE_EXPLORER_URL='https://explorer.coinweb.io' -VITE_CONTRACT_ADDRESS="0x6195433d58b3415f2e4fe909c770a9ecc1c2f15ee59585daa06820a502fb9812" +VITE_CONTRACT_ADDRESS="0x5b7c1e53461f5497d467cb9d5c772be5e8c1a61b60e7f2f4ce71c7249d8e768a" From e9025efa220edf6cc4f98a18cbe2350cabd00f26 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 7 May 2025 08:02:55 +0300 Subject: [PATCH 6/8] wip --- .../contract.cm/src/onchain/extraLogic.ts | 13 +++++++-- packages/cwait/src/onchain/context/context.ts | 5 ++-- .../cwait/src/onchain/context/extractOps.ts | 12 ++++++-- .../onchain/mutex/calls/constructLockCall.ts | 24 +++++++++++++--- .../onchain/mutex/claims/mutexBlockLock.ts | 10 ++++--- .../src/onchain/mutex/claims/mutexLock.ts | 4 +-- packages/cwait/src/onchain/mutex/index.ts | 3 ++ .../src/onchain/mutex/methods/execLock.ts | 28 +++++++------------ .../src/onchain/mutex/methods/notifyLock.ts | 4 +-- .../cwait/src/onchain/mutex/methods/wait.ts | 17 +++++------ packages/cwait/src/onchain/mutex/types.ts | 9 ++++-- .../utils/constructTx/prepareInThreadTxs.ts | 23 ++++++++++++--- .../utils/constructTx/prepareOutThreadTxs.ts | 10 ++++++- packages/cwait/src/onchain/utils/uuid.ts | 3 +- packages/ui/.env | 2 +- 15 files changed, 113 insertions(+), 54 deletions(-) diff --git a/packages/contract.cm/src/onchain/extraLogic.ts b/packages/contract.cm/src/onchain/extraLogic.ts index 3a09acf..4eea7a2 100644 --- a/packages/contract.cm/src/onchain/extraLogic.ts +++ b/packages/contract.cm/src/onchain/extraLogic.ts @@ -1,14 +1,20 @@ import { constructClaim } from '@coinweb/contract-kit'; -import { cwait, readOp, storeOp, TypedClaim } from 'cwait'; +import { cwait, lock, readOp, storeOp, TypedClaim } from 'cwait'; import { createWordKey, WordClaimBody } from '../offchain'; import { extraLogic2 } from './extraLogic2'; export const extraLogic = cwait(async (id: string, i: number) => { - console.log('extraLogic START + ', i); + console.log('extraLogic START ++ ', i); + + console.log('extraLogic lock + ', i); + const unlock = await lock(createWordKey(id)); + + console.log('extraLogic readOp + ', i); const result = await readOp>(createWordKey(id)); + console.log('extraLogic storeOp + ', i); await storeOp( constructClaim( createWordKey(id), @@ -17,6 +23,9 @@ export const extraLogic = cwait(async (id: string, i: number) => { ) ); + console.log('extraLogic unlock + ', i); + await unlock(); + console.log('extraLogic return extraLogic2 + ', i); return extraLogic2(id, i); }); diff --git a/packages/cwait/src/onchain/context/context.ts b/packages/cwait/src/onchain/context/context.ts index 778ff46..41513b2 100644 --- a/packages/cwait/src/onchain/context/context.ts +++ b/packages/cwait/src/onchain/context/context.ts @@ -12,7 +12,6 @@ import { getCallParameters, getContractIssuer } from 'lib/onchain'; import { ExecutorMethodArgs, ResolvedOp } from '../../types'; import { pushAwaitedTask } from '../promisifiedFeatures'; -import { uuid } from '../utils'; import { extractOps } from './extractOps'; @@ -65,7 +64,9 @@ export const handleContext = (ctx: Context) => { ] = getMethodArgs(); initialContext.isChild = !!parentId; - initialContext.thisId = thisId ?? uuid(); + initialContext.thisId = + thisId ?? + (BigInt(`0x${parentId ?? '0'}`) * 3n + BigInt(`0x${getRawContext().call.txid}`)).toString(16).slice(0, 32); initialContext.parentId = parentId; initialContext.methodName = methodName; initialContext.initialArgs = initialArgs ?? []; diff --git a/packages/cwait/src/onchain/context/extractOps.ts b/packages/cwait/src/onchain/context/extractOps.ts index f070a51..1d3d302 100644 --- a/packages/cwait/src/onchain/context/extractOps.ts +++ b/packages/cwait/src/onchain/context/extractOps.ts @@ -3,7 +3,13 @@ import { CwebTake } from '@coinweb/contract-kit/dist/types/operations/take'; import { ResolvedOp, TypedClaim } from '../../types'; import { resultKey } from '../claims'; -import { mutexBlockLockKey, mutexBlockUnlockKey, mutexExecOpsKey, MutexExecOpsResult } from '../mutex'; +import { + mutexBlockLockKey, + mutexBlockUnlockKey, + mutexExecOpsKey, + MutexExecOpsResult, + MutexNotifyLockState, +} from '../mutex'; import { isResolvedBlockOp, isResolvedReadOp, isResolvedTakeOp } from '../utils'; const extractFunds = (fundsOp: GRead, takenFundsIds: string[]) => { @@ -92,7 +98,9 @@ export const extractOps = ({ throw new Error('Wrong mutex lock result'); } - extractedOps.push({ SlotOp: { ok: true } }); + extractedOps.push({ + LockOp: nextAfterBlock.TakeOp.result.body as MutexNotifyLockState, + }); i += 2; continue; diff --git a/packages/cwait/src/onchain/mutex/calls/constructLockCall.ts b/packages/cwait/src/onchain/mutex/calls/constructLockCall.ts index aee89c0..7cdeca5 100644 --- a/packages/cwait/src/onchain/mutex/calls/constructLockCall.ts +++ b/packages/cwait/src/onchain/mutex/calls/constructLockCall.ts @@ -1,10 +1,23 @@ import { ContractIssuer, constructContractRef } from '@coinweb/contract-kit'; -import { constructMutexBlockLockClaimTake, constructMutexLockBlock } from '../claims'; +import { constructMutexBlockLockClaimTake, constructMutexLockBlock, constructMutexLockClaimStore } from '../claims'; import { lockMethodName } from '../methods'; import { lockFee } from '../settings'; +import { LockedKey } from '../types'; -export const constructLockCall = (issuer: ContractIssuer, lockId: string) => { +export const constructLockCall = ({ + issuer, + keys, + lockId, + timestamp, + processId, +}: { + issuer: ContractIssuer; + lockId: string; + timestamp: number; + keys: LockedKey[]; + processId: string; +}) => { return { callInfo: { ref: constructContractRef(issuer, []), @@ -18,7 +31,10 @@ export const constructLockCall = (issuer: ContractIssuer, lockId: string) => { }, contractArgs: [], }, - ops: [constructMutexLockBlock(lockId, issuer), constructMutexBlockLockClaimTake(lockId)] as const, - fee: lockFee + 200n, + inThreadOps: [constructMutexLockBlock(lockId, issuer), constructMutexBlockLockClaimTake(lockId)] as const, + outThreadOps: [ + constructMutexLockClaimStore({ fee: lockFee, keys, lockId, timestamp, processId, locked: false }), + ] as const, + fee: lockFee * 2n + 200n, }; }; diff --git a/packages/cwait/src/onchain/mutex/claims/mutexBlockLock.ts b/packages/cwait/src/onchain/mutex/claims/mutexBlockLock.ts index 0f6484e..92de7cc 100644 --- a/packages/cwait/src/onchain/mutex/claims/mutexBlockLock.ts +++ b/packages/cwait/src/onchain/mutex/claims/mutexBlockLock.ts @@ -8,15 +8,17 @@ import { ContractIssuer, } from '@coinweb/contract-kit'; +import { MutexNotifyLockState } from '../types'; + export const mutexBlockLockKey = '_mutex_block_lock'; export const constructMutexBlockLockClaimKey = (lockId: string) => constructClaimKey([mutexBlockLockKey], [lockId]); -export const constructMutexBlockLockClaim = ({ lockId }: { lockId: string }) => - constructClaim(constructMutexBlockLockClaimKey(lockId), {}, '0x0'); +export const constructMutexBlockLockClaim = ({ lockId, timestamp }: MutexNotifyLockState) => + constructClaim(constructMutexBlockLockClaimKey(lockId), { lockId, timestamp } satisfies MutexNotifyLockState, '0x0'); -export const constructMutexBlockLockClaimStore = ({ lockId }: { lockId: string }) => - constructStore(constructMutexBlockLockClaim({ lockId })); +export const constructMutexBlockLockClaimStore = ({ lockId, timestamp }: MutexNotifyLockState) => + constructStore(constructMutexBlockLockClaim({ lockId, timestamp })); export const constructMutexBlockLockClaimTake = (lockId: string) => constructTake(constructMutexBlockLockClaimKey(lockId)); diff --git a/packages/cwait/src/onchain/mutex/claims/mutexLock.ts b/packages/cwait/src/onchain/mutex/claims/mutexLock.ts index 5441d8e..2e135c5 100644 --- a/packages/cwait/src/onchain/mutex/claims/mutexLock.ts +++ b/packages/cwait/src/onchain/mutex/claims/mutexLock.ts @@ -43,17 +43,17 @@ export const constructMutexLockClaim = ({ export const constructMutexLockClaimStore = ({ fee, keys, - locked, lockId, timestamp, processId, + locked, }: { fee: bigint; keys: LockedKey[]; - locked: boolean; lockId: string; timestamp: number; processId: string; + locked: boolean; }) => constructStore(constructMutexLockClaim({ fee, keys, locked, lockId, timestamp, processId })); export const constructMutexLockClaimRangeRead = (issuer: ContractIssuer): GRead => diff --git a/packages/cwait/src/onchain/mutex/index.ts b/packages/cwait/src/onchain/mutex/index.ts index 793cef3..16dd33a 100644 --- a/packages/cwait/src/onchain/mutex/index.ts +++ b/packages/cwait/src/onchain/mutex/index.ts @@ -6,6 +6,8 @@ import { mutexExecLock, mutexLock, mutexUnlock, + notifyLock, + notifyLockMethodName, preReadExecTakeOpsMethodName, saveExecOpResult, saveExecOpResultMethodName, @@ -27,4 +29,5 @@ export const mutexMethods = { [preReadExecTakeOpsMethodName]: preReadExecTakeOps, [saveExecOpResultMethodName]: saveExecOpResult, [waitMethodName]: wait, + [notifyLockMethodName]: notifyLock, }; diff --git a/packages/cwait/src/onchain/mutex/methods/execLock.ts b/packages/cwait/src/onchain/mutex/methods/execLock.ts index 4d8d5ff..c5a8934 100644 --- a/packages/cwait/src/onchain/mutex/methods/execLock.ts +++ b/packages/cwait/src/onchain/mutex/methods/execLock.ts @@ -16,7 +16,7 @@ import { lockFee } from '../settings'; import { MutexLockState, MutexWaitNotifyArgs } from '../types'; import { isMatchLockKeys } from '../utils'; -import { lockMethodName, notifyLockMethodName } from './names'; +import { waitMethodName } from './names'; export const mutexExecLock = (context: Context) => { const { availableCweb } = getCallParameters(context); @@ -59,25 +59,17 @@ export const mutexExecLock = (context: Context) => { callInfo: { ref: constructContractRef(issuer, []), methodInfo: { - methodName: lockMethodName, - methodArgs: [], + methodName: waitMethodName, + methodArgs: [ + 0, + { + timestamp: (lockCandidate.key.second_part as [number, string])[0], + lockId: (lockCandidate.key.second_part as [number, string])[1], + }, + ] satisfies MutexWaitNotifyArgs, }, contractInfo: { - providedCweb: lockFee, - authenticated: null, - }, - contractArgs: [], - }, - }, - { - callInfo: { - ref: constructContractRef(issuer, []), - methodInfo: { - methodName: notifyLockMethodName, - methodArgs: [0, (lockCandidate.key.second_part as [number, string])[1]] satisfies MutexWaitNotifyArgs, - }, - contractInfo: { - providedCweb: 200n, + providedCweb: availableCweb + BigInt(lockCandidate.fees_stored) - 1000n, authenticated: null, }, contractArgs: [], diff --git a/packages/cwait/src/onchain/mutex/methods/notifyLock.ts b/packages/cwait/src/onchain/mutex/methods/notifyLock.ts index 41d6c2f..a49ed88 100644 --- a/packages/cwait/src/onchain/mutex/methods/notifyLock.ts +++ b/packages/cwait/src/onchain/mutex/methods/notifyLock.ts @@ -5,7 +5,7 @@ import { constructMutexBlockLockClaimStore } from '../claims/mutexBlockLock'; import { MutexNotifyLockArgs } from '../types'; export const notifyLock = (context: Context) => { - const [lockId] = getMethodArguments(context); + const [lockInfo] = getMethodArguments(context); - return [constructContinueTx(context, [constructMutexBlockLockClaimStore({ lockId })])]; + return [constructContinueTx(context, [constructMutexBlockLockClaimStore(lockInfo)])]; }; diff --git a/packages/cwait/src/onchain/mutex/methods/wait.ts b/packages/cwait/src/onchain/mutex/methods/wait.ts index 5104d4b..7ed1eff 100644 --- a/packages/cwait/src/onchain/mutex/methods/wait.ts +++ b/packages/cwait/src/onchain/mutex/methods/wait.ts @@ -1,14 +1,15 @@ import { constructContinueTx, constructContractRef, Context } from '@coinweb/contract-kit'; -import { getContractIssuer, getMethodArguments } from 'lib/onchain'; +import { getCallParameters, getContractIssuer, getMethodArguments } from 'lib/onchain'; import { waitSteps } from '../settings'; import { MutexNotifyLockArgs, MutexWaitNotifyArgs } from '../types'; -import { notifyLockMethodName } from './names'; +import { notifyLockMethodName, waitMethodName } from './names'; export const wait = (context: Context) => { const issuer = getContractIssuer(context); - const [step, lockId] = getMethodArguments(context); + const [step, lockInfo] = getMethodArguments(context); + const { availableCweb } = getCallParameters(context); //Including the notifyLock method as step if (step + 1 < waitSteps) { @@ -21,11 +22,11 @@ export const wait = (context: Context) => { callInfo: { ref: constructContractRef(issuer, []), methodInfo: { - methodName: notifyLockMethodName, - methodArgs: [step + 1, lockId] satisfies MutexWaitNotifyArgs, + methodName: waitMethodName, + methodArgs: [step + 1, lockInfo] satisfies MutexWaitNotifyArgs, }, contractInfo: { - providedCweb: 200n, + providedCweb: availableCweb - 800n, authenticated: null, }, contractArgs: [], @@ -46,10 +47,10 @@ export const wait = (context: Context) => { ref: constructContractRef(issuer, []), methodInfo: { methodName: notifyLockMethodName, - methodArgs: [lockId] satisfies MutexNotifyLockArgs, + methodArgs: [lockInfo] satisfies MutexNotifyLockArgs, }, contractInfo: { - providedCweb: 200n, + providedCweb: availableCweb - 800n, authenticated: null, }, contractArgs: [], diff --git a/packages/cwait/src/onchain/mutex/types.ts b/packages/cwait/src/onchain/mutex/types.ts index 245e8af..9766fbf 100644 --- a/packages/cwait/src/onchain/mutex/types.ts +++ b/packages/cwait/src/onchain/mutex/types.ts @@ -9,6 +9,11 @@ export type MutexLockState = { processId: string; }; +export type MutexNotifyLockState = { + timestamp: number; + lockId: string; +}; + export type MutexExecOpsResult = ( | { ok: true; @@ -24,9 +29,9 @@ export type MutexUnlockArgs = [lockId: string, timestamp: number, notify?: boole export type MutexExecOpArgs = [ops: (GTake | GStore)[], processId: string, execId?: string]; -export type MutexWaitNotifyArgs = [step: number, lockId: string]; +export type MutexWaitNotifyArgs = [step: number, lockInfo: MutexNotifyLockState]; -export type MutexNotifyLockArgs = [lockId: string]; +export type MutexNotifyLockArgs = [lockInfo: MutexNotifyLockState]; export type MutexPreReadTakeOpsArgs = [execId: string, unavailableIndexes: number[], ops: PreparedOperation[]]; diff --git a/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts b/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts index f889651..496580f 100644 --- a/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts +++ b/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts @@ -79,6 +79,8 @@ export const prepareInThreadTxs = ({ //Info for separate child call const childCalls: FullCallInfo[] = []; + const outThreadOps: PreparedOperation[] = []; + ops.forEach((op, i) => { switch (true) { case isPreparedExecOp(op): { @@ -136,12 +138,25 @@ export const prepareInThreadTxs = ({ break; } case isPreparedLockOp(op): { - const { callInfo, fee, ops } = constructLockCall(context.issuer, op.LockOp.lockId); + const { + callInfo, + fee, + inThreadOps, + outThreadOps: outThreadOpsFromLockCall, + } = constructLockCall({ + issuer: context.issuer, + lockId: op.LockOp.lockId, + timestamp: op.LockOp.timestamp, + keys: op.LockOp.keys, + processId: context.thisId, + }); childCalls.push({ callInfo }); - callArgs.push(...ops); + callArgs.push(...inThreadOps); txFee += fee; + outThreadOps.push(...outThreadOpsFromLockCall); + break; } case isPreparedUnlockOp(op): { @@ -252,8 +267,8 @@ export const prepareInThreadTxs = ({ ) ); - if (childCalls.length) { - returnTxs.push(constructContinueTx(getRawContext(), [], childCalls)); + if (childCalls.length || outThreadOps.length) { + returnTxs.push(constructContinueTx(getRawContext(), outThreadOps, childCalls)); } } } diff --git a/packages/cwait/src/onchain/utils/constructTx/prepareOutThreadTxs.ts b/packages/cwait/src/onchain/utils/constructTx/prepareOutThreadTxs.ts index c2d98e8..ff9c2bb 100644 --- a/packages/cwait/src/onchain/utils/constructTx/prepareOutThreadTxs.ts +++ b/packages/cwait/src/onchain/utils/constructTx/prepareOutThreadTxs.ts @@ -79,11 +79,19 @@ export const prepareOutThreadTxs = ({ break; } case isPreparedLockOp(op): { - const { callInfo, fee } = constructLockCall(context.issuer, op.LockOp.lockId); + const { callInfo, fee, outThreadOps } = constructLockCall({ + issuer: context.issuer, + lockId: op.LockOp.lockId, + timestamp: op.LockOp.timestamp, + keys: op.LockOp.keys, + processId: context.thisId, + }); preparedCalls.push({ callInfo }); txFee += fee; + preparedOps.push(...outThreadOps); + break; } case isPreparedUnlockOp(op): { diff --git a/packages/cwait/src/onchain/utils/uuid.ts b/packages/cwait/src/onchain/utils/uuid.ts index 9921fef..f7d8772 100644 --- a/packages/cwait/src/onchain/utils/uuid.ts +++ b/packages/cwait/src/onchain/utils/uuid.ts @@ -3,9 +3,8 @@ import { context } from '../context'; let next = 0n; export const uuid = () => { - const parentTxId = context.parentTxId; + const id = (BigInt(`0x${context.thisId}`) * 3n + BigInt(`0x${context.parentTxId}`) + next).toString(16).slice(-32); - const id = `${parentTxId}${next.toString(16)}`; next += 1n; return id; diff --git a/packages/ui/.env b/packages/ui/.env index d30b9fc..fc545dd 100644 --- a/packages/ui/.env +++ b/packages/ui/.env @@ -1,4 +1,4 @@ VITE_API_URL='https://api-cloud.coinweb.io/wallet' VITE_EXPLORER_URL='https://explorer.coinweb.io' -VITE_CONTRACT_ADDRESS="0x5b7c1e53461f5497d467cb9d5c772be5e8c1a61b60e7f2f4ce71c7249d8e768a" +VITE_CONTRACT_ADDRESS="0xedca91a29553fa2466e89eef02ac06dc49ab68be5e81fa91d05765cab79ec02a" From 748cb6f8f57906573f4a0d3d88222d7e5da5dccf Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 7 May 2025 10:12:31 +0300 Subject: [PATCH 7/8] fix: fix parallel child process execution --- packages/cwait/package.json | 3 +- packages/cwait/src/onchain/context/context.ts | 2 +- packages/cwait/src/onchain/executor.ts | 69 ------------------- .../constructTx/constructTx.ts | 6 +- .../{utils => executor}/constructTx/index.ts | 0 .../constructTx/prepareInThreadTxs.ts | 4 +- .../constructTx/prepareOutThreadTxs.ts | 2 +- .../constructTx/prepareTxs.ts | 0 .../constructTx/splitTasks.ts | 0 .../cwait/src/onchain/executor/executor.ts | 42 +++++++++++ packages/cwait/src/onchain/executor/index.ts | 1 + .../constructCwebMain}/constructCwebMain.ts | 7 +- .../features/constructCwebMain/index.ts | 1 + .../features/cwait/cwait.ts | 43 ++++++++---- .../features/cwait/index.ts | 0 .../features/index.ts | 1 + .../features/mutex/index.ts | 0 .../features/mutex/lock.ts | 6 +- .../features/ops/block.ts | 4 +- .../features/ops/index.ts | 0 .../features/ops/rangeRead.ts | 8 +-- .../features/ops/read.ts | 8 +-- .../features/ops/store.ts | 4 +- .../features/ops/take.ts | 6 +- packages/cwait/src/onchain/globals/index.ts | 1 + .../onchain/{global.ts => globals/promise.ts} | 7 +- packages/cwait/src/onchain/index.ts | 6 +- .../src/onchain/promisifiedFeatures/index.ts | 2 - .../runtime/awaitedTasks.ts | 15 +++- .../cwait/src/onchain/runtime/execLoop.ts | 41 +++++++++++ .../runtime/index.ts | 1 + .../runtime/resolvedOps.ts | 19 ++++- packages/cwait/src/onchain/utils/batchId.ts | 3 - packages/cwait/src/onchain/utils/callstack.ts | 10 --- packages/cwait/src/onchain/utils/index.ts | 5 +- .../utils/{typeGuards.ts => opTypeGuards.ts} | 0 packages/cwait/tsconfig.json | 2 +- 37 files changed, 190 insertions(+), 139 deletions(-) delete mode 100644 packages/cwait/src/onchain/executor.ts rename packages/cwait/src/onchain/{utils => executor}/constructTx/constructTx.ts (61%) rename packages/cwait/src/onchain/{utils => executor}/constructTx/index.ts (100%) rename packages/cwait/src/onchain/{utils => executor}/constructTx/prepareInThreadTxs.ts (99%) rename packages/cwait/src/onchain/{utils => executor}/constructTx/prepareOutThreadTxs.ts (98%) rename packages/cwait/src/onchain/{utils => executor}/constructTx/prepareTxs.ts (100%) rename packages/cwait/src/onchain/{utils => executor}/constructTx/splitTasks.ts (100%) create mode 100644 packages/cwait/src/onchain/executor/executor.ts create mode 100644 packages/cwait/src/onchain/executor/index.ts rename packages/cwait/src/onchain/{ => features/constructCwebMain}/constructCwebMain.ts (90%) create mode 100644 packages/cwait/src/onchain/features/constructCwebMain/index.ts rename packages/cwait/src/onchain/{promisifiedFeatures => }/features/cwait/cwait.ts (59%) rename packages/cwait/src/onchain/{promisifiedFeatures => }/features/cwait/index.ts (100%) rename packages/cwait/src/onchain/{promisifiedFeatures => }/features/index.ts (66%) rename packages/cwait/src/onchain/{promisifiedFeatures => }/features/mutex/index.ts (100%) rename packages/cwait/src/onchain/{promisifiedFeatures => }/features/mutex/lock.ts (94%) rename packages/cwait/src/onchain/{promisifiedFeatures => }/features/ops/block.ts (88%) rename packages/cwait/src/onchain/{promisifiedFeatures => }/features/ops/index.ts (100%) rename packages/cwait/src/onchain/{promisifiedFeatures => }/features/ops/rangeRead.ts (85%) rename packages/cwait/src/onchain/{promisifiedFeatures => }/features/ops/read.ts (82%) rename packages/cwait/src/onchain/{promisifiedFeatures => }/features/ops/store.ts (89%) rename packages/cwait/src/onchain/{promisifiedFeatures => }/features/ops/take.ts (85%) create mode 100644 packages/cwait/src/onchain/globals/index.ts rename packages/cwait/src/onchain/{global.ts => globals/promise.ts} (91%) delete mode 100644 packages/cwait/src/onchain/promisifiedFeatures/index.ts rename packages/cwait/src/onchain/{promisifiedFeatures => }/runtime/awaitedTasks.ts (63%) create mode 100644 packages/cwait/src/onchain/runtime/execLoop.ts rename packages/cwait/src/onchain/{promisifiedFeatures => }/runtime/index.ts (69%) rename packages/cwait/src/onchain/{promisifiedFeatures => }/runtime/resolvedOps.ts (66%) delete mode 100644 packages/cwait/src/onchain/utils/batchId.ts delete mode 100644 packages/cwait/src/onchain/utils/callstack.ts rename packages/cwait/src/onchain/utils/{typeGuards.ts => opTypeGuards.ts} (100%) diff --git a/packages/cwait/package.json b/packages/cwait/package.json index 9314871..4f819f8 100644 --- a/packages/cwait/package.json +++ b/packages/cwait/package.json @@ -3,7 +3,8 @@ "version": "0.0.1", "type": "module", "scripts": { - "build": "yarn g:tsc -p tsconfig.build.json", + "clean": "rm -rf dist", + "build": "yarn clean && yarn g:tsc -p tsconfig.build.json", "dev": "yarn g:tsc -p tsconfig.build.json --watch", "lint": "yarn g:lint .", "publish:lib": "node scripts/publish.js" diff --git a/packages/cwait/src/onchain/context/context.ts b/packages/cwait/src/onchain/context/context.ts index 41513b2..d140bc0 100644 --- a/packages/cwait/src/onchain/context/context.ts +++ b/packages/cwait/src/onchain/context/context.ts @@ -11,7 +11,7 @@ import { CwebTake } from '@coinweb/contract-kit/dist/types/operations/take'; import { getCallParameters, getContractIssuer } from 'lib/onchain'; import { ExecutorMethodArgs, ResolvedOp } from '../../types'; -import { pushAwaitedTask } from '../promisifiedFeatures'; +import { pushAwaitedTask } from '../runtime'; import { extractOps } from './extractOps'; diff --git a/packages/cwait/src/onchain/executor.ts b/packages/cwait/src/onchain/executor.ts deleted file mode 100644 index 1595498..0000000 --- a/packages/cwait/src/onchain/executor.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ -import { Context, NewTx, getMethodArguments, isSelfCall } from '@coinweb/contract-kit'; - -import { Task } from '../types'; - -import { context, getRawContext, handleContext } from './context'; -import { getAwaitedTasks } from './promisifiedFeatures'; -import { pushResolvedOp } from './promisifiedFeatures/runtime/resolvedOps'; -import { constructTx } from './utils'; - -let abortExecution: ((result: { isFullyExecuted: boolean; awaitedTasks: Task[] }) => void) | null = null; - -export const executor = (method: (...args: any[]) => Promise) => { - return async (ctx: Context): Promise => { - console.log('executor-start >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>'); - const shouldRestart = handleContext(ctx); - pushResolvedOp(context.ops); - - if (getMethodArguments(getRawContext()).length > 2 && !isSelfCall(ctx)) { - throw new Error('Wrong contract call, check the call arguments'); - } - - if (shouldRestart) { - const awaitedTasks = getAwaitedTasks(); - - return constructTx(awaitedTasks, false); - } - - const execution = new Promise<{ isFullyExecuted: boolean; awaitedTasks: Task[] }>((resolve, reject) => { - abortExecution = resolve; - - method(...context.initialArgs).then( - () => { - console.log('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< executor-resolved'); - const awaitedTasks = getAwaitedTasks(); - resolve({ isFullyExecuted: true, awaitedTasks }); - }, - (error) => { - console.log(' <<<<<<<<<<<<<<<<<<<<<<<<<<<<<< { - const awaitedTasks = getAwaitedTasks(); - abortExecution?.({ isFullyExecuted: false, awaitedTasks }); - }, 0); - - try { - const { isFullyExecuted, awaitedTasks } = await execution; - - return constructTx(awaitedTasks, isFullyExecuted); - } catch (error) { - console.log('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< executor-error'); - console.log((error as Error).message); - throw error; - } - }; -}; - -export const stopExecution = () => { - console.log('stopExecution'); - const awaitedTasks = getAwaitedTasks(); - abortExecution?.({ isFullyExecuted: false, awaitedTasks }); -}; diff --git a/packages/cwait/src/onchain/utils/constructTx/constructTx.ts b/packages/cwait/src/onchain/executor/constructTx/constructTx.ts similarity index 61% rename from packages/cwait/src/onchain/utils/constructTx/constructTx.ts rename to packages/cwait/src/onchain/executor/constructTx/constructTx.ts index c91adef..08ee09f 100644 --- a/packages/cwait/src/onchain/utils/constructTx/constructTx.ts +++ b/packages/cwait/src/onchain/executor/constructTx/constructTx.ts @@ -1,8 +1,10 @@ -import { Task } from '../../../types'; +import { getAwaitedTasks } from '../../runtime'; import { prepareTx } from './prepareTxs'; -export const constructTx = (awaitedTasks: Task[], isFullyExecuted: boolean) => { +export const constructTx = (isFullyExecuted: boolean) => { + const awaitedTasks = getAwaitedTasks(); + const { calls, txFee } = prepareTx({ awaitedTasks, isFullyExecuted, txFee: 0n }); const { txs } = prepareTx({ awaitedTasks, isFullyExecuted, txFee, callsCount: calls }); diff --git a/packages/cwait/src/onchain/utils/constructTx/index.ts b/packages/cwait/src/onchain/executor/constructTx/index.ts similarity index 100% rename from packages/cwait/src/onchain/utils/constructTx/index.ts rename to packages/cwait/src/onchain/executor/constructTx/index.ts diff --git a/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts b/packages/cwait/src/onchain/executor/constructTx/prepareInThreadTxs.ts similarity index 99% rename from packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts rename to packages/cwait/src/onchain/executor/constructTx/prepareInThreadTxs.ts index 496580f..5f6c179 100644 --- a/packages/cwait/src/onchain/utils/constructTx/prepareInThreadTxs.ts +++ b/packages/cwait/src/onchain/executor/constructTx/prepareInThreadTxs.ts @@ -22,8 +22,8 @@ import { isPreparedStoreOp, isPreparedTakeOp, isPreparedUnlockOp, -} from '../typeGuards'; -import { uuid } from '../uuid'; +} from '../../utils/opTypeGuards'; +import { uuid } from '../../utils/uuid'; export const prepareInThreadTxs = ({ cwebPerCall, diff --git a/packages/cwait/src/onchain/utils/constructTx/prepareOutThreadTxs.ts b/packages/cwait/src/onchain/executor/constructTx/prepareOutThreadTxs.ts similarity index 98% rename from packages/cwait/src/onchain/utils/constructTx/prepareOutThreadTxs.ts rename to packages/cwait/src/onchain/executor/constructTx/prepareOutThreadTxs.ts index ff9c2bb..d3096f3 100644 --- a/packages/cwait/src/onchain/utils/constructTx/prepareOutThreadTxs.ts +++ b/packages/cwait/src/onchain/executor/constructTx/prepareOutThreadTxs.ts @@ -18,7 +18,7 @@ import { isPreparedStoreOp, isPreparedTakeOp, isPreparedUnlockOp, -} from '../typeGuards'; +} from '../../utils/opTypeGuards'; export const prepareOutThreadTxs = ({ ops, diff --git a/packages/cwait/src/onchain/utils/constructTx/prepareTxs.ts b/packages/cwait/src/onchain/executor/constructTx/prepareTxs.ts similarity index 100% rename from packages/cwait/src/onchain/utils/constructTx/prepareTxs.ts rename to packages/cwait/src/onchain/executor/constructTx/prepareTxs.ts diff --git a/packages/cwait/src/onchain/utils/constructTx/splitTasks.ts b/packages/cwait/src/onchain/executor/constructTx/splitTasks.ts similarity index 100% rename from packages/cwait/src/onchain/utils/constructTx/splitTasks.ts rename to packages/cwait/src/onchain/executor/constructTx/splitTasks.ts diff --git a/packages/cwait/src/onchain/executor/executor.ts b/packages/cwait/src/onchain/executor/executor.ts new file mode 100644 index 0000000..6a31392 --- /dev/null +++ b/packages/cwait/src/onchain/executor/executor.ts @@ -0,0 +1,42 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import { Context, NewTx, getMethodArguments, isSelfCall } from '@coinweb/contract-kit'; + +import { context, getRawContext, handleContext } from '../context'; +import { execLoop, setNextExec, stopExecution } from '../runtime'; +import { pushResolvedOp } from '../runtime/resolvedOps'; + +import { constructTx } from './constructTx'; + +export const executor = (method: (...args: any[]) => Promise) => { + return async (ctx: Context): Promise => { + console.log('executor-start >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>'); + const shouldRestart = handleContext(ctx); + pushResolvedOp(context.ops); + + if (getMethodArguments(getRawContext()).length > 2 && !isSelfCall(ctx)) { + throw new Error('Wrong contract call, check the call arguments'); + } + + if (shouldRestart) { + console.log('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< executor-finish'); + return constructTx(false); + } + + //@ts-expect-error + // eslint-disable-next-line @typescript-eslint/no-unsafe-call + os.setTimeout(() => { + stopExecution(); + }, 0); + + try { + setNextExec(() => method(...context.initialArgs)); + const isFullyExecuted = await execLoop(); + console.log('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< executor-finish'); + return constructTx(isFullyExecuted); + } catch (error) { + console.log((error as Error).message); + console.log('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< executor-error'); + throw error; + } + }; +}; diff --git a/packages/cwait/src/onchain/executor/index.ts b/packages/cwait/src/onchain/executor/index.ts new file mode 100644 index 0000000..78dc04d --- /dev/null +++ b/packages/cwait/src/onchain/executor/index.ts @@ -0,0 +1 @@ +export * from './executor'; diff --git a/packages/cwait/src/onchain/constructCwebMain.ts b/packages/cwait/src/onchain/features/constructCwebMain/constructCwebMain.ts similarity index 90% rename from packages/cwait/src/onchain/constructCwebMain.ts rename to packages/cwait/src/onchain/features/constructCwebMain/constructCwebMain.ts index 3ac7eaa..c47b55a 100644 --- a/packages/cwait/src/onchain/constructCwebMain.ts +++ b/packages/cwait/src/onchain/features/constructCwebMain/constructCwebMain.ts @@ -2,10 +2,9 @@ import { ContractHandlers as ContractHandlersOrig, SELF_REGISTER_HANDLER_NAME } import { selfRegisterHandler } from '@coinweb/self-register'; import { queue } from 'lib/onchain'; -import { addMethodHandler, ContractHandlers, executeHandler } from '../contract-kit'; - -import { executor } from './executor'; -import { mutexMethods } from './mutex'; +import { addMethodHandler, ContractHandlers, executeHandler } from '../../../contract-kit'; +import { executor } from '../../executor'; +import { mutexMethods } from '../../mutex'; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const constructCwebMain = (methods: Record Promise>) => async () => { diff --git a/packages/cwait/src/onchain/features/constructCwebMain/index.ts b/packages/cwait/src/onchain/features/constructCwebMain/index.ts new file mode 100644 index 0000000..1ee0946 --- /dev/null +++ b/packages/cwait/src/onchain/features/constructCwebMain/index.ts @@ -0,0 +1 @@ +export * from './constructCwebMain'; diff --git a/packages/cwait/src/onchain/promisifiedFeatures/features/cwait/cwait.ts b/packages/cwait/src/onchain/features/cwait/cwait.ts similarity index 59% rename from packages/cwait/src/onchain/promisifiedFeatures/features/cwait/cwait.ts rename to packages/cwait/src/onchain/features/cwait/cwait.ts index 633001b..93ee765 100644 --- a/packages/cwait/src/onchain/promisifiedFeatures/features/cwait/cwait.ts +++ b/packages/cwait/src/onchain/features/cwait/cwait.ts @@ -1,12 +1,23 @@ import { constructStore } from '@coinweb/contract-kit/dist/esm/operations/store'; -import { constructResultClaim } from '../../../claims/result'; -import { stopExecution } from '../../../executor'; -import { opMarker } from '../../../global'; -import { isResolvedChildOp, isResolvedExecOp, isResolvedSlotOp } from '../../../utils'; -import { uuid } from '../../../utils'; -import { getAwaitedTasksCount, pushAwaitedTask } from '../../runtime/awaitedTasks'; -import { getUsedOps, saveUsedOps, shiftResolvedOp } from '../../runtime/resolvedOps'; +import { constructResultClaim } from '../../claims/result'; +import { opMarker } from '../../globals/promise'; +import { setNextExec, stopExecution } from '../../runtime'; +import { + freezeAwaitedTasks, + getAwaitedTasksCount, + pushAwaitedTask, + unfreezeAwaitedTasks, +} from '../../runtime/awaitedTasks'; +import { + freezeResolvedOps, + getUsedOps, + saveUsedOps, + shiftResolvedOp, + unfreezeResolvedOps, +} from '../../runtime/resolvedOps'; +import { isResolvedChildOp, isResolvedExecOp, isResolvedSlotOp } from '../../utils'; +import { uuid } from '../../utils'; let isRootDetected = false; @@ -44,13 +55,21 @@ export const cwait = Promise { + unfreezeAwaitedTasks(); + unfreezeResolvedOps(); - if (!getAwaitedTasksCount()) { - pushAwaitedTask(constructStore(constructResultClaim(op.ExecOp.id, getUsedOps()))); - } + saveUsedOps(); + + await asyncCallback(...args); + + if (!getAwaitedTasksCount()) { + pushAwaitedTask(constructStore(constructResultClaim(op.ExecOp.id, getUsedOps()))); + } + }); stopExecution(); diff --git a/packages/cwait/src/onchain/promisifiedFeatures/features/cwait/index.ts b/packages/cwait/src/onchain/features/cwait/index.ts similarity index 100% rename from packages/cwait/src/onchain/promisifiedFeatures/features/cwait/index.ts rename to packages/cwait/src/onchain/features/cwait/index.ts diff --git a/packages/cwait/src/onchain/promisifiedFeatures/features/index.ts b/packages/cwait/src/onchain/features/index.ts similarity index 66% rename from packages/cwait/src/onchain/promisifiedFeatures/features/index.ts rename to packages/cwait/src/onchain/features/index.ts index e13c3d1..330a6d0 100644 --- a/packages/cwait/src/onchain/promisifiedFeatures/features/index.ts +++ b/packages/cwait/src/onchain/features/index.ts @@ -1,3 +1,4 @@ export * from './cwait'; export * from './mutex'; export * from './ops'; +export * from './constructCwebMain'; diff --git a/packages/cwait/src/onchain/promisifiedFeatures/features/mutex/index.ts b/packages/cwait/src/onchain/features/mutex/index.ts similarity index 100% rename from packages/cwait/src/onchain/promisifiedFeatures/features/mutex/index.ts rename to packages/cwait/src/onchain/features/mutex/index.ts diff --git a/packages/cwait/src/onchain/promisifiedFeatures/features/mutex/lock.ts b/packages/cwait/src/onchain/features/mutex/lock.ts similarity index 94% rename from packages/cwait/src/onchain/promisifiedFeatures/features/mutex/lock.ts rename to packages/cwait/src/onchain/features/mutex/lock.ts index 60c8b33..fccbe2a 100644 --- a/packages/cwait/src/onchain/promisifiedFeatures/features/mutex/lock.ts +++ b/packages/cwait/src/onchain/features/mutex/lock.ts @@ -1,8 +1,8 @@ import { getTime } from 'lib/onchain'; -import { opMarker } from '../../../global'; -import { LockedKey } from '../../../mutex'; -import { isResolvedLockOp, isResolvedSlotOp, isResolvedUnlockOp, uuid } from '../../../utils'; +import { opMarker } from '../../globals/promise'; +import { LockedKey } from '../../mutex'; +import { isResolvedLockOp, isResolvedSlotOp, isResolvedUnlockOp, uuid } from '../../utils'; import { pushAwaitedTask, shiftResolvedOp } from '../../runtime'; const unlock = (lockId: string, timestamp: number) => { diff --git a/packages/cwait/src/onchain/promisifiedFeatures/features/ops/block.ts b/packages/cwait/src/onchain/features/ops/block.ts similarity index 88% rename from packages/cwait/src/onchain/promisifiedFeatures/features/ops/block.ts rename to packages/cwait/src/onchain/features/ops/block.ts index 5b0faf5..a2b0048 100644 --- a/packages/cwait/src/onchain/promisifiedFeatures/features/ops/block.ts +++ b/packages/cwait/src/onchain/features/ops/block.ts @@ -1,7 +1,7 @@ import { BlockFilter, constructBlock, extractBlock } from '@coinweb/contract-kit'; -import { opMarker } from '../../../global'; -import { isResolvedBlockOp, isResolvedSlotOp } from '../../../utils'; +import { opMarker } from '../../globals/promise'; +import { isResolvedBlockOp, isResolvedSlotOp } from '../../utils'; import { pushAwaitedTask, shiftResolvedOp } from '../../runtime'; export const blockOp = (filters: BlockFilter[]) => { diff --git a/packages/cwait/src/onchain/promisifiedFeatures/features/ops/index.ts b/packages/cwait/src/onchain/features/ops/index.ts similarity index 100% rename from packages/cwait/src/onchain/promisifiedFeatures/features/ops/index.ts rename to packages/cwait/src/onchain/features/ops/index.ts diff --git a/packages/cwait/src/onchain/promisifiedFeatures/features/ops/rangeRead.ts b/packages/cwait/src/onchain/features/ops/rangeRead.ts similarity index 85% rename from packages/cwait/src/onchain/promisifiedFeatures/features/ops/rangeRead.ts rename to packages/cwait/src/onchain/features/ops/rangeRead.ts index bd2162a..df7fd81 100644 --- a/packages/cwait/src/onchain/promisifiedFeatures/features/ops/rangeRead.ts +++ b/packages/cwait/src/onchain/features/ops/rangeRead.ts @@ -1,10 +1,10 @@ import { Claim, ClaimKey, constructRangeRead, extractRead } from '@coinweb/contract-kit'; import { ClaimRange } from '@coinweb/contract-kit/dist/types/operations/read'; -import { TypedClaim } from '../../../../types'; -import { context } from '../../../context'; -import { opMarker } from '../../../global'; -import { isResolvedReadOp, isResolvedSlotOp } from '../../../utils'; +import { TypedClaim } from '../../../types'; +import { context } from '../../context'; +import { opMarker } from '../../globals/promise'; +import { isResolvedReadOp, isResolvedSlotOp } from '../../utils'; import { pushAwaitedTask, shiftResolvedOp } from '../../runtime'; export const rangeReadOp = ( diff --git a/packages/cwait/src/onchain/promisifiedFeatures/features/ops/read.ts b/packages/cwait/src/onchain/features/ops/read.ts similarity index 82% rename from packages/cwait/src/onchain/promisifiedFeatures/features/ops/read.ts rename to packages/cwait/src/onchain/features/ops/read.ts index 312cd15..d5477b8 100644 --- a/packages/cwait/src/onchain/promisifiedFeatures/features/ops/read.ts +++ b/packages/cwait/src/onchain/features/ops/read.ts @@ -1,9 +1,9 @@ import { Claim, ClaimKey, constructRead, extractRead } from '@coinweb/contract-kit'; -import { TypedClaim } from '../../../../types'; -import { context } from '../../../context'; -import { opMarker } from '../../../global'; -import { isResolvedReadOp, isResolvedSlotOp } from '../../../utils'; +import { TypedClaim } from '../../../types'; +import { context } from '../../context'; +import { opMarker } from '../../globals/promise'; +import { isResolvedReadOp, isResolvedSlotOp } from '../../utils'; import { pushAwaitedTask, shiftResolvedOp } from '../../runtime'; export const readOp = (key: ClaimKey) => { diff --git a/packages/cwait/src/onchain/promisifiedFeatures/features/ops/store.ts b/packages/cwait/src/onchain/features/ops/store.ts similarity index 89% rename from packages/cwait/src/onchain/promisifiedFeatures/features/ops/store.ts rename to packages/cwait/src/onchain/features/ops/store.ts index 432e145..481762e 100644 --- a/packages/cwait/src/onchain/promisifiedFeatures/features/ops/store.ts +++ b/packages/cwait/src/onchain/features/ops/store.ts @@ -1,8 +1,8 @@ import { Claim, constructStore } from '@coinweb/contract-kit'; import { extractStore } from '@coinweb/contract-kit/dist/esm/operations/store'; -import { opMarker } from '../../../global'; -import { isResolvedSlotOp, isResolvedStoreOp } from '../../../utils'; +import { opMarker } from '../../globals/promise'; +import { isResolvedSlotOp, isResolvedStoreOp } from '../../utils'; import { pushAwaitedTask, shiftResolvedOp } from '../../runtime'; export const storeOp = (claim: Claim, storeCweb?: bigint) => { diff --git a/packages/cwait/src/onchain/promisifiedFeatures/features/ops/take.ts b/packages/cwait/src/onchain/features/ops/take.ts similarity index 85% rename from packages/cwait/src/onchain/promisifiedFeatures/features/ops/take.ts rename to packages/cwait/src/onchain/features/ops/take.ts index 05c8dee..56b79ab 100644 --- a/packages/cwait/src/onchain/promisifiedFeatures/features/ops/take.ts +++ b/packages/cwait/src/onchain/features/ops/take.ts @@ -1,8 +1,8 @@ import { Claim, ClaimKey, constructTake, extractTake } from '@coinweb/contract-kit'; -import { TypedClaim } from '../../../../types'; -import { opMarker } from '../../../global'; -import { isResolvedSlotOp, isResolvedTakeOp } from '../../../utils'; +import { TypedClaim } from '../../../types'; +import { opMarker } from '../../globals/promise'; +import { isResolvedSlotOp, isResolvedTakeOp } from '../../utils'; import { pushAwaitedTask, shiftResolvedOp } from '../../runtime'; export const takeOp = (key: ClaimKey) => { diff --git a/packages/cwait/src/onchain/globals/index.ts b/packages/cwait/src/onchain/globals/index.ts new file mode 100644 index 0000000..b0a9756 --- /dev/null +++ b/packages/cwait/src/onchain/globals/index.ts @@ -0,0 +1 @@ +export * from './promise'; diff --git a/packages/cwait/src/onchain/global.ts b/packages/cwait/src/onchain/globals/promise.ts similarity index 91% rename from packages/cwait/src/onchain/global.ts rename to packages/cwait/src/onchain/globals/promise.ts index b2c8c6a..c4f8ba2 100644 --- a/packages/cwait/src/onchain/global.ts +++ b/packages/cwait/src/onchain/globals/promise.ts @@ -1,5 +1,8 @@ -import { markTaskBatch } from './promisifiedFeatures/runtime/awaitedTasks'; -import { getBatchId } from './utils'; +import { markTaskBatch } from '../runtime/awaitedTasks'; + +let batchId = 0; + +const getBatchId = () => batchId++; export const opMarker = Symbol('opMarker'); diff --git a/packages/cwait/src/onchain/index.ts b/packages/cwait/src/onchain/index.ts index b31def1..512d267 100644 --- a/packages/cwait/src/onchain/index.ts +++ b/packages/cwait/src/onchain/index.ts @@ -1,3 +1,3 @@ -export * from './context'; -export * from './promisifiedFeatures/features'; -export * from './constructCwebMain'; +import './globals'; + +export * from './features'; diff --git a/packages/cwait/src/onchain/promisifiedFeatures/index.ts b/packages/cwait/src/onchain/promisifiedFeatures/index.ts deleted file mode 100644 index a66d282..0000000 --- a/packages/cwait/src/onchain/promisifiedFeatures/index.ts +++ /dev/null @@ -1,2 +0,0 @@ -export * from './runtime'; -export * from './features'; diff --git a/packages/cwait/src/onchain/promisifiedFeatures/runtime/awaitedTasks.ts b/packages/cwait/src/onchain/runtime/awaitedTasks.ts similarity index 63% rename from packages/cwait/src/onchain/promisifiedFeatures/runtime/awaitedTasks.ts rename to packages/cwait/src/onchain/runtime/awaitedTasks.ts index f1f4ddc..d848ccc 100644 --- a/packages/cwait/src/onchain/promisifiedFeatures/runtime/awaitedTasks.ts +++ b/packages/cwait/src/onchain/runtime/awaitedTasks.ts @@ -1,8 +1,13 @@ -import { PreparedOp, Task } from '../../../types'; +import { PreparedOp, Task } from '../../types'; const awaitedTasks: Task[] = []; +let isFreezed = false; export const pushAwaitedTask = (op: PreparedOp) => { + if (isFreezed) { + return; + } + awaitedTasks.push({ op, batchId: -1 }); }; @@ -15,3 +20,11 @@ export const markTaskBatch = (count: number, batchId: number) => { }; export const getAwaitedTasksCount = () => awaitedTasks.length; + +export const freezeAwaitedTasks = () => { + isFreezed = true; +}; + +export const unfreezeAwaitedTasks = () => { + isFreezed = false; +}; diff --git a/packages/cwait/src/onchain/runtime/execLoop.ts b/packages/cwait/src/onchain/runtime/execLoop.ts new file mode 100644 index 0000000..da06ae4 --- /dev/null +++ b/packages/cwait/src/onchain/runtime/execLoop.ts @@ -0,0 +1,41 @@ +let abortExecution: ((isFullyExecuted: boolean) => void) | null = null; + +export const stopExecution = (isFullyExecuted = false) => { + console.log('stopExecution'); + abortExecution?.(isFullyExecuted); +}; + +type ExecTask = () => Promise; + +let execQueue: ExecTask[] = []; +export const setNextExec = (task: ExecTask) => (execQueue = [task]); + +export const execLoop = async (): Promise => { + const nextExec = execQueue.pop(); + + if (nextExec) { + const execution = new Promise((resolve, reject) => { + abortExecution = resolve; + + nextExec().then( + () => { + resolve(true); + }, + (error) => { + console.log(error); + reject(error); + } + ); + }); + + const isFullyExecuted = await execution; + + if (isFullyExecuted) { + return true; + } + + return execLoop(); + } + + return false; +}; diff --git a/packages/cwait/src/onchain/promisifiedFeatures/runtime/index.ts b/packages/cwait/src/onchain/runtime/index.ts similarity index 69% rename from packages/cwait/src/onchain/promisifiedFeatures/runtime/index.ts rename to packages/cwait/src/onchain/runtime/index.ts index 7478a6d..bf12790 100644 --- a/packages/cwait/src/onchain/promisifiedFeatures/runtime/index.ts +++ b/packages/cwait/src/onchain/runtime/index.ts @@ -1,2 +1,3 @@ export * from './awaitedTasks'; +export * from './execLoop'; export * from './resolvedOps'; diff --git a/packages/cwait/src/onchain/promisifiedFeatures/runtime/resolvedOps.ts b/packages/cwait/src/onchain/runtime/resolvedOps.ts similarity index 66% rename from packages/cwait/src/onchain/promisifiedFeatures/runtime/resolvedOps.ts rename to packages/cwait/src/onchain/runtime/resolvedOps.ts index 8c7f236..1cb6a4f 100644 --- a/packages/cwait/src/onchain/promisifiedFeatures/runtime/resolvedOps.ts +++ b/packages/cwait/src/onchain/runtime/resolvedOps.ts @@ -1,11 +1,16 @@ -import { ResolvedOp } from '../../../types'; +import { ResolvedOp } from '../../types'; const resolvedOps: ResolvedOp[] = []; let usedOps: ResolvedOp[] = []; let isSavingUsed = false; +let isFreezed = false; export const pushResolvedOp = (op: ResolvedOp | ResolvedOp[]) => { + if (isFreezed) { + return; + } + if (Array.isArray(op)) { resolvedOps.push(...op); } else { @@ -15,8 +20,8 @@ export const pushResolvedOp = (op: ResolvedOp | ResolvedOp[]) => { export const shiftResolvedOp = () => { const result = { - isOp: resolvedOps.length > 0, - op: resolvedOps.shift(), + isOp: resolvedOps.length > 0 && !isFreezed, + op: isFreezed ? undefined : resolvedOps.shift(), } as | { isOp: true; @@ -40,3 +45,11 @@ export const saveUsedOps = () => { usedOps = []; isSavingUsed = true; }; + +export const freezeResolvedOps = () => { + isFreezed = true; +}; + +export const unfreezeResolvedOps = () => { + isFreezed = false; +}; diff --git a/packages/cwait/src/onchain/utils/batchId.ts b/packages/cwait/src/onchain/utils/batchId.ts deleted file mode 100644 index 3880b73..0000000 --- a/packages/cwait/src/onchain/utils/batchId.ts +++ /dev/null @@ -1,3 +0,0 @@ -let batchId = 0; - -export const getBatchId = () => batchId++; diff --git a/packages/cwait/src/onchain/utils/callstack.ts b/packages/cwait/src/onchain/utils/callstack.ts deleted file mode 100644 index ea22049..0000000 --- a/packages/cwait/src/onchain/utils/callstack.ts +++ /dev/null @@ -1,10 +0,0 @@ -export const getStack = ({ skip = 0 }: { skip?: number } = {}) => - new Error().stack - ?.split('\n') - .slice(2 + skip) - .map((line) => { - const match = line.match(/at\s+([^\s(]+)/); - return match ? match[1] : ''; - }) - .filter((name) => name && name !== 'Promise') - .join('@') || ''; diff --git a/packages/cwait/src/onchain/utils/index.ts b/packages/cwait/src/onchain/utils/index.ts index d9e397c..b72ca62 100644 --- a/packages/cwait/src/onchain/utils/index.ts +++ b/packages/cwait/src/onchain/utils/index.ts @@ -1,5 +1,2 @@ -export * from './batchId'; -export * from './callstack'; -export * from './constructTx'; -export * from './typeGuards'; +export * from './opTypeGuards'; export * from './uuid'; diff --git a/packages/cwait/src/onchain/utils/typeGuards.ts b/packages/cwait/src/onchain/utils/opTypeGuards.ts similarity index 100% rename from packages/cwait/src/onchain/utils/typeGuards.ts rename to packages/cwait/src/onchain/utils/opTypeGuards.ts diff --git a/packages/cwait/tsconfig.json b/packages/cwait/tsconfig.json index 5611da8..e84c22c 100644 --- a/packages/cwait/tsconfig.json +++ b/packages/cwait/tsconfig.json @@ -9,5 +9,5 @@ "esModuleInterop": true, "types": ["vitest/globals"] }, - "include": ["**/*.ts", "vitest.*.ts", "__tests__", "scripts/publish.js", "src/onchain/utils"] + "include": ["**/*.ts", "vitest.*.ts", "__tests__", "scripts/publish.js", "src/onchain/utils", "src/onchain/executor/constructTx"] } From 99c541a0619cd844cc246161ccadc152bd3e0d36 Mon Sep 17 00:00:00 2001 From: Alex Date: Fri, 13 Jun 2025 09:00:37 +0300 Subject: [PATCH 8/8] fix: fix indeterministic --- .cweb-config/dapp-ecosystem-test.yaml | 9 +- .env.yarn | 2 +- package.json | 2 +- .../src/offchain/shared/constants.ts | 2 +- .../src/onchain/__tests__/handlers.test.ts | 94 +++++++++++++++++++ .../src/onchain/__tests__/helpers.ts | 35 +++++++ packages/contract.cm/vitest.config.ts | 14 +++ packages/cwait/src/onchain/context/context.ts | 4 +- .../cwait/src/onchain/context/extractOps.ts | 8 +- .../constructTx/prepareInThreadTxs.ts | 47 ++++++---- .../constructTx/prepareOutThreadTxs.ts | 1 + .../cwait/src/onchain/executor/executor.ts | 6 +- .../cwait/src/onchain/features/cwait/cwait.ts | 37 +++----- .../cwait/src/onchain/features/mutex/lock.ts | 4 +- .../cwait/src/onchain/features/ops/read.ts | 6 +- .../mutex/calls/constructUnlockCall.ts | 5 +- .../cwait/src/onchain/mutex/methods/unlock.ts | 8 +- .../cwait/src/onchain/runtime/awaitedTasks.ts | 13 --- .../cwait/src/onchain/runtime/execLoop.ts | 21 ++++- .../cwait/src/onchain/runtime/resolvedOps.ts | 8 +- packages/cwait/src/types.ts | 6 +- packages/ui/.env | 2 +- yarn.lock | 10 +- 23 files changed, 256 insertions(+), 88 deletions(-) create mode 100644 packages/contract.cm/src/onchain/__tests__/handlers.test.ts create mode 100644 packages/contract.cm/src/onchain/__tests__/helpers.ts create mode 100644 packages/contract.cm/vitest.config.ts diff --git a/.cweb-config/dapp-ecosystem-test.yaml b/.cweb-config/dapp-ecosystem-test.yaml index 779766e..dd5f4a7 100644 --- a/.cweb-config/dapp-ecosystem-test.yaml +++ b/.cweb-config/dapp-ecosystem-test.yaml @@ -14,9 +14,11 @@ includes: blake3: 321b1f88930aead7fe47961c8664005b3441f6502824769bf21eb06c3e5aaba4 use: - - dex-app.cm v0.0.67+test - - eth_offer_maker 0.0.67+devnet - - jump-listener.cm v0.0.5 + - jump-listener.cm v0.1.8 + - jump_listener_devnet v0.1.8 + - jump-forwarder.cm v0.1.5 + - jump_forwarder_devnet v0.1.5 + interpreters: {} @@ -31,3 +33,4 @@ contract_instances: template: contract.cm v0.0.1 parameters: content: [] + diff --git a/.env.yarn b/.env.yarn index 0e3587e..0efe5b1 100644 --- a/.env.yarn +++ b/.env.yarn @@ -4,4 +4,4 @@ NPM_PASSWORD= # This is needed when working with WASM modules (nodeLinker: node-modules is mandatory) NODE_OPTIONS="--experimental-wasm-modules --no-warnings=ExperimentalWarning --max-old-space-size=8192" -REGISTRATION_PROFILE=devnet +REGISTRATION_PROFILE=test diff --git a/package.json b/package.json index 0e00e51..0a9303c 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "@coinweb/self-register": "0.1.3", "@coinweb/claims-client": "0.1.6-debug", "@coinweb/contract-kit": "0.2.6", - "@coinweb/testing-sdk": "0.0.9-remote", + "@coinweb/testing-sdk": "0.0.10-mutex", "@coinweb/minimal-sdk": "1.2.21" }, "devDependencies": { diff --git a/packages/contract.cm/src/offchain/shared/constants.ts b/packages/contract.cm/src/offchain/shared/constants.ts index ddb5d35..c28e3bb 100644 --- a/packages/contract.cm/src/offchain/shared/constants.ts +++ b/packages/contract.cm/src/offchain/shared/constants.ts @@ -9,5 +9,5 @@ export enum PUBLIC_METHODS { } export const FEE = { - ADD_WORD: 100000n, + ADD_WORD: 10000000n, }; diff --git a/packages/contract.cm/src/onchain/__tests__/handlers.test.ts b/packages/contract.cm/src/onchain/__tests__/handlers.test.ts new file mode 100644 index 0000000..b8cd823 --- /dev/null +++ b/packages/contract.cm/src/onchain/__tests__/handlers.test.ts @@ -0,0 +1,94 @@ +import path from 'node:path'; + +import { constructContractIssuer, constructContractRef, type PreparedCallInfo } from '@coinweb/contract-kit'; +import { UnitTest, type ExecInfo, type RoundInfo, type UnitTestContext, type DbWriteOp } from '@coinweb/testing-sdk'; +import { describe, it, expect, beforeAll } from 'vitest'; + +import { AddWordArgs, FEE, PUBLIC_METHODS } from '../../offchain/shared'; + +import { CONTRACT_INSTANCE_ID, TEST_PRIVATE_KEY, TEST_PUBLIC_KEY, waitRoundsCount } from './helpers'; + +const addWordRound = (creatorAccount: Buffer): RoundInfo => { + const self = constructContractIssuer(CONTRACT_INSTANCE_ID); + + const kvDeleteCallInfo: PreparedCallInfo = { + ref: constructContractRef(self, []), + methodInfo: { methodName: PUBLIC_METHODS.ADD_WORD, methodArgs: ['TEST_HELLO'] satisfies AddWordArgs }, + contractArgs: [], + contractInfo: { providedCweb: FEE.ADD_WORD, authenticated: null }, // null = authenticated account in tests + }; + + const withFunds = { type: { privateKey: creatorAccount } }; + + return { + txsInfo: { + // @ts-ignore + txs: [{ callInfo: kvDeleteCallInfo, withFunds }], + l1_events: [], + }, + claims: [], + blocks_on: [], + }; +}; + +const runTest = async (context: UnitTestContext, input: ExecInfo, checkFn: (results: DbWriteOp[]) => void) => { + const test = new UnitTest(context); + await test.load(path.join(import.meta.dirname, '../../../tests_data/state.json')); + await test.run(input, { checkFn }); +}; + +describe('PUBLIC_METHODS.ADD_WORD Tests', () => { + let creatorAccount: { publicKey: Buffer; privateKey: Buffer }; + + beforeAll(() => { + creatorAccount = { + privateKey: Buffer.from(TEST_PRIVATE_KEY, 'hex'), + publicKey: Buffer.from(TEST_PUBLIC_KEY, 'hex'), + }; + }); + + it('Should successfully execute', async () => { + const input: ExecInfo = { + rounds: [...waitRoundsCount(50), addWordRound(creatorAccount.privateKey), ...waitRoundsCount(2)], + }; + + const checkFn = (results: DbWriteOp[]) => { + expect(results).toBeDefined(); + + // @ts-ignore + // const errors = findAllValuesRecursivelyByKey<{ ContractError: ContractError }>(results, 'Invalid'); + // expect(errors.length).toBe(0); + + // @ts-ignore + // const storeOps = findAllValuesRecursivelyByKey(results, 'StoreOp'); + // expect(storeOps.length).toBeGreaterThan(0); + + // const existingPayload = storeOps.find((s) => isEqual(s.key.first_part, createFirstPartDefault())); + + // expect(existingPayload).toBeDefined(); + // expect(existingPayload?.key).toStrictEqual(createDefaultKey(Number.MAX_SAFE_INTEGER)); + // expect(existingPayload?.key.first_part).toStrictEqual(createFirstPartDefault()); + + // const { id, data, owner, tx } = existingPayload?.body as StoreKvPayload; + // expect(id).toBeDefined(); + // expect(data).toBeDefined(); + // expect(owner).toBeDefined(); + // expect(tx).toBeDefined(); + // expect(tx?.timestamp).toBeDefined(); + // expect(tx?.coinwebTxId).toBeDefined(); + // expect(Number(id)).toStrictEqual(Number.MAX_SAFE_INTEGER); + // expect(owner).toStrictEqual({ + // auth: 'EcdsaContract', + // payload: TEST_PUBLIC_KEY, + // }); + }; + + const context: UnitTestContext = { + name: 'add_word', + testPath: path.join(import.meta.dirname, './tests_data/add_word'), + verbose: true, + }; + + await runTest(context, input, checkFn); + }); +}); diff --git a/packages/contract.cm/src/onchain/__tests__/helpers.ts b/packages/contract.cm/src/onchain/__tests__/helpers.ts new file mode 100644 index 0000000..47473e4 --- /dev/null +++ b/packages/contract.cm/src/onchain/__tests__/helpers.ts @@ -0,0 +1,35 @@ +import path from 'node:path'; + +import { getInstanceFromIndex, type RoundInfo } from '@coinweb/testing-sdk'; + +const onchainPackage = (await import('../../../dist/out/package.json')) as { + name: string; +}; + +export const CONTRACT_INSTANCE = await getInstanceFromIndex({ + path: path.resolve(import.meta.dirname, '../../../tests_data/index.yaml'), + instance_alias: 'cwait-contract 0.0.1-test', +}); + +export const CONTRACT_INSTANCE_ID = '0x'.concat(CONTRACT_INSTANCE.instance_id.replace(/0x/, '')); + +export const CONTRACT_TEMPLATE_ID = `0x${onchainPackage.name.substring(5)}`; + +export const TEST_PRIVATE_KEY = '31c70848e4e3aaffcf91f134853ec966e913aa9a813115bcb81512e7625f46a9'; +export const TEST_PUBLIC_KEY = '03951f89fe78e13f295d96eb7afa1e0da726df7d58f9c84f7144e5febc30efeec4'; + +export const waitRoundsCount = (roundsCount: number): RoundInfo[] => { + const emptyRound: RoundInfo = { + txsInfo: { + txs: [], + l1_events: [], + }, + claims: [], + blocks_on: [], + }; + const rounds: RoundInfo[] = []; + for (let i = 0; i < roundsCount; i++) { + rounds.push(emptyRound); + } + return rounds; +}; diff --git a/packages/contract.cm/vitest.config.ts b/packages/contract.cm/vitest.config.ts new file mode 100644 index 0000000..a7b2399 --- /dev/null +++ b/packages/contract.cm/vitest.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vitest/config'; + +export default defineConfig({ + test: { + globals: true, + environment: 'node', + reporters: 'default', + silent: false, + testTimeout: 240000, + teardownTimeout: 240000, + hookTimeout: 240000, + pool: 'forks', + }, +}); diff --git a/packages/cwait/src/onchain/context/context.ts b/packages/cwait/src/onchain/context/context.ts index d140bc0..8cedf7e 100644 --- a/packages/cwait/src/onchain/context/context.ts +++ b/packages/cwait/src/onchain/context/context.ts @@ -58,7 +58,7 @@ export const handleContext = (ctx: Context) => { caller, thisId, parentId, - saveResult, + shouldSaveResult, takenFundsIds = [], execOpsIndexes = [], ] = getMethodArgs(); @@ -70,7 +70,7 @@ export const handleContext = (ctx: Context) => { initialContext.parentId = parentId; initialContext.methodName = methodName; initialContext.initialArgs = initialArgs ?? []; - initialContext.needSaveResult = saveResult ?? false; + initialContext.needSaveResult = shouldSaveResult ?? false; const { authInfo } = getCallParameters(getRawContext()); initialContext.user = (authInfo && extractUser(authInfo)) ?? caller ?? null; diff --git a/packages/cwait/src/onchain/context/extractOps.ts b/packages/cwait/src/onchain/context/extractOps.ts index 1d3d302..2bb917e 100644 --- a/packages/cwait/src/onchain/context/extractOps.ts +++ b/packages/cwait/src/onchain/context/extractOps.ts @@ -86,7 +86,11 @@ export const extractOps = ({ throw new Error('Wrong subcall result'); } - extractedOps.push({ ChildOp: 0 }, ...(nextAfterBlock.TakeOp.result as TypedClaim).body); + extractedOps.push({ + ChildOp: { + ops: (nextAfterBlock.TakeOp.result as TypedClaim).body, + }, + }); i += 2; continue; @@ -112,7 +116,7 @@ export const extractOps = ({ throw new Error('Wrong mutex unlock result'); } - extractedOps.push({ SlotOp: { ok: true } }); + extractedOps.push({ UnlockOp: 0 }); i += 2; continue; diff --git a/packages/cwait/src/onchain/executor/constructTx/prepareInThreadTxs.ts b/packages/cwait/src/onchain/executor/constructTx/prepareInThreadTxs.ts index 5f6c179..af304d7 100644 --- a/packages/cwait/src/onchain/executor/constructTx/prepareInThreadTxs.ts +++ b/packages/cwait/src/onchain/executor/constructTx/prepareInThreadTxs.ts @@ -11,7 +11,7 @@ import { } from '@coinweb/contract-kit'; import { CwebTake } from '@coinweb/contract-kit/dist/types/operations/take'; -import { ExecutorMethodArgs, PreparedExtendedStoreOp, PreparedOp, ResolvedOp, ResolvedSlotOp } from '../../../types'; +import { ExecutorMethodArgs, PreparedExtendedStoreOp, PreparedOp, ResolvedSlotOp } from '../../../types'; import { constructFundsClaimRangRead, constructFundsClaimStore } from '../../claims/funds'; import { constructResultBlockFilter, constructResultClaimTake, resultKey } from '../../claims/result'; import { context, getRawContext } from '../../context'; @@ -68,16 +68,18 @@ export const prepareInThreadTxs = ({ let callsPrepared = 0; const resolvedSlotOps = new Array(outThreadTasksCount).fill({ SlotOp: { ok: true } }) satisfies ResolvedSlotOp[]; + //Mutex exec ops const preparedExecOps: (PreparedExtendedStoreOp | GTake)[] = []; const excOpsIndexes: number[] = []; - const resolvedChildOps: ResolvedOp[] = [...context.ops, ...resolvedSlotOps]; + // const resolvedChildOps: ResolvedOp[] = [...context.ops, ...resolvedSlotOps]; + //Children //Arg for the main call const callArgs: PreparedOperation[] = []; - //Info for separate child call - const childCalls: FullCallInfo[] = []; + //Info for separate parallel calls + const parallelCalls: FullCallInfo[] = []; const outThreadOps: PreparedOperation[] = []; @@ -89,14 +91,20 @@ export const prepareInThreadTxs = ({ callArgs.push(constructBlock([constructResultBlockFilter(id)]), constructResultClaimTake(id)); txFee += 200n; - childCalls.push({ + const childOps = [ + ...context.ops, + ...resolvedSlotOps, + ...ops.map((_, j) => (i === j ? { ExecOp: { id } } : { SlotOp: { ok: true } })), + ]; + + parallelCalls.push({ callInfo: { ref: constructContractRef(context.issuer, []), methodInfo: { methodName: context.methodName, methodArgs: [ context.initialArgs, - [...resolvedChildOps, { ExecOp: { id } }], + childOps, context.user, id, context.thisId, @@ -151,7 +159,7 @@ export const prepareInThreadTxs = ({ processId: context.thisId, }); - childCalls.push({ callInfo }); + parallelCalls.push({ callInfo }); callArgs.push(...inThreadOps); txFee += fee; @@ -160,14 +168,15 @@ export const prepareInThreadTxs = ({ break; } case isPreparedUnlockOp(op): { + console.log('prepareInThreadTxs >>> unlockOp'); const { callInfo, fee, ops } = constructUnlockCall( context.issuer, op.UnlockOp.lockId, op.UnlockOp.timestamp, - false + true ); - childCalls.push({ callInfo }); + parallelCalls.push({ callInfo }); callArgs.push(...ops); txFee += fee; @@ -177,8 +186,6 @@ export const prepareInThreadTxs = ({ callArgs.push(op); txFee += 100n; } - - resolvedChildOps.push({ SlotOp: { ok: true } }); }); if (preparedExecOps.length > 0) { @@ -191,9 +198,12 @@ export const prepareInThreadTxs = ({ execId, }); - childCalls.push({ callInfo }); + parallelCalls.push({ callInfo }); txFee += fee; + console.log(txFee); + console.log(context.funds.availableCweb); + callArgs.push(...ops); } @@ -206,6 +216,7 @@ export const prepareInThreadTxs = ({ if ('StoreOp' in latestCallArg && (latestCallArg.StoreOp.key.first_part as [string])[0] === resultKey) { //SAVE RESULT CLAIMS + console.log('SAVE RESULT CLAIMS'); if (callArgs.length > 1) { throw new Error('Unexpected count of result ops'); } @@ -221,10 +232,12 @@ export const prepareInThreadTxs = ({ const cwebToStore = availableCweb + storedCweb - BigInt(takeOps.length) * 100n - 500n; + console.log('cwebToStore: ', cwebToStore); + resultOps.push( - constructFundsClaimStore(context.parentId, cwebToStore), + passCwebFrom(context.issuer, availableCweb), ...takeOps, - passCwebFrom(context.issuer, availableCweb) + constructFundsClaimStore(context.parentId, cwebToStore) ); } @@ -235,6 +248,8 @@ export const prepareInThreadTxs = ({ txFee += 800n + outThreadFee + BigInt(takeOps.length) * 100n; callsPrepared++; + console.log('provided cweb: ', cwebPerCall - txFee + storedCweb); + returnTxs.push( constructContinueTx( getRawContext(), @@ -267,8 +282,8 @@ export const prepareInThreadTxs = ({ ) ); - if (childCalls.length || outThreadOps.length) { - returnTxs.push(constructContinueTx(getRawContext(), outThreadOps, childCalls)); + if (parallelCalls.length || outThreadOps.length) { + returnTxs.push(constructContinueTx(getRawContext(), outThreadOps, parallelCalls)); } } } diff --git a/packages/cwait/src/onchain/executor/constructTx/prepareOutThreadTxs.ts b/packages/cwait/src/onchain/executor/constructTx/prepareOutThreadTxs.ts index d3096f3..419285e 100644 --- a/packages/cwait/src/onchain/executor/constructTx/prepareOutThreadTxs.ts +++ b/packages/cwait/src/onchain/executor/constructTx/prepareOutThreadTxs.ts @@ -95,6 +95,7 @@ export const prepareOutThreadTxs = ({ break; } case isPreparedUnlockOp(op): { + console.log('prepareOutThreadTxs >>> unlockOp'); const { callInfo, fee } = constructUnlockCall(context.issuer, op.UnlockOp.lockId, op.UnlockOp.timestamp, false); preparedCalls.push({ callInfo }); diff --git a/packages/cwait/src/onchain/executor/executor.ts b/packages/cwait/src/onchain/executor/executor.ts index 6a31392..8c396f8 100644 --- a/packages/cwait/src/onchain/executor/executor.ts +++ b/packages/cwait/src/onchain/executor/executor.ts @@ -18,7 +18,7 @@ export const executor = (method: (...args: any[]) => Promise) => { } if (shouldRestart) { - console.log('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< executor-finish'); + console.log('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< executor-finish-restart'); return constructTx(false); } @@ -29,9 +29,9 @@ export const executor = (method: (...args: any[]) => Promise) => { }, 0); try { - setNextExec(() => method(...context.initialArgs)); + setNextExec(() => method(...context.initialArgs), []); const isFullyExecuted = await execLoop(); - console.log('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< executor-finish'); + console.log('<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< executor-finish-end'); return constructTx(isFullyExecuted); } catch (error) { console.log((error as Error).message); diff --git a/packages/cwait/src/onchain/features/cwait/cwait.ts b/packages/cwait/src/onchain/features/cwait/cwait.ts index 93ee765..e8f1870 100644 --- a/packages/cwait/src/onchain/features/cwait/cwait.ts +++ b/packages/cwait/src/onchain/features/cwait/cwait.ts @@ -3,19 +3,8 @@ import { constructStore } from '@coinweb/contract-kit/dist/esm/operations/store' import { constructResultClaim } from '../../claims/result'; import { opMarker } from '../../globals/promise'; import { setNextExec, stopExecution } from '../../runtime'; -import { - freezeAwaitedTasks, - getAwaitedTasksCount, - pushAwaitedTask, - unfreezeAwaitedTasks, -} from '../../runtime/awaitedTasks'; -import { - freezeResolvedOps, - getUsedOps, - saveUsedOps, - shiftResolvedOp, - unfreezeResolvedOps, -} from '../../runtime/resolvedOps'; +import { getAwaitedTasksCount, pushAwaitedTask } from '../../runtime/awaitedTasks'; +import { getUsedOps, startSavingUsedOps, stopSavingUsedOps, shiftResolvedOp } from '../../runtime/resolvedOps'; import { isResolvedChildOp, isResolvedExecOp, isResolvedSlotOp } from '../../utils'; import { uuid } from '../../utils'; @@ -55,32 +44,28 @@ export const cwait = Promise { - unfreezeAwaitedTasks(); - unfreezeResolvedOps(); - - saveUsedOps(); - + startSavingUsedOps(); await asyncCallback(...args); + stopSavingUsedOps(); if (!getAwaitedTasksCount()) { + console.log('push result claim'); + pushAwaitedTask(constructStore(constructResultClaim(op.ExecOp.id, getUsedOps()))); } - }); + }, []); - stopExecution(); + stopExecution(); //Check: maybe does no affect - return new Promise(() => null); + return; } if (isResolvedChildOp(op)) { - return asyncCallback(...args); + return setNextExec(() => asyncCallback(...args), op.ChildOp.ops); } - throw new Error('Read operation not found'); + throw new Error('Exec or Child operation not found'); } }) as TAsyncCallback; }; diff --git a/packages/cwait/src/onchain/features/mutex/lock.ts b/packages/cwait/src/onchain/features/mutex/lock.ts index fccbe2a..75f99d6 100644 --- a/packages/cwait/src/onchain/features/mutex/lock.ts +++ b/packages/cwait/src/onchain/features/mutex/lock.ts @@ -2,11 +2,11 @@ import { getTime } from 'lib/onchain'; import { opMarker } from '../../globals/promise'; import { LockedKey } from '../../mutex'; -import { isResolvedLockOp, isResolvedSlotOp, isResolvedUnlockOp, uuid } from '../../utils'; import { pushAwaitedTask, shiftResolvedOp } from '../../runtime'; +import { isResolvedLockOp, isResolvedSlotOp, isResolvedUnlockOp, uuid } from '../../utils'; const unlock = (lockId: string, timestamp: number) => { - console.log('lockOp'); + console.log('unlockOp'); let opMarkerValue = false; const result = new Promise((resolve, reject) => { diff --git a/packages/cwait/src/onchain/features/ops/read.ts b/packages/cwait/src/onchain/features/ops/read.ts index d5477b8..0b07821 100644 --- a/packages/cwait/src/onchain/features/ops/read.ts +++ b/packages/cwait/src/onchain/features/ops/read.ts @@ -3,8 +3,8 @@ import { Claim, ClaimKey, constructRead, extractRead } from '@coinweb/contract-k import { TypedClaim } from '../../../types'; import { context } from '../../context'; import { opMarker } from '../../globals/promise'; -import { isResolvedReadOp, isResolvedSlotOp } from '../../utils'; import { pushAwaitedTask, shiftResolvedOp } from '../../runtime'; +import { isResolvedReadOp, isResolvedSlotOp } from '../../utils'; export const readOp = (key: ClaimKey) => { let opMarkerValue = false; @@ -22,10 +22,14 @@ export const readOp = (key: ClaimKey) => { } if (!isResolvedReadOp(op)) { + console.log(JSON.stringify(op)); + throw new Error('Read operation not found'); } const claim = (extractRead(op)?.[0]?.content ?? null) as TClaim | null; + + console.log('ResolveRead claim: ', claim); resolve(claim); } } catch (error) { diff --git a/packages/cwait/src/onchain/mutex/calls/constructUnlockCall.ts b/packages/cwait/src/onchain/mutex/calls/constructUnlockCall.ts index 72b6ec6..48d102c 100644 --- a/packages/cwait/src/onchain/mutex/calls/constructUnlockCall.ts +++ b/packages/cwait/src/onchain/mutex/calls/constructUnlockCall.ts @@ -2,6 +2,7 @@ import { constructContractRef, ContractIssuer } from '@coinweb/contract-kit'; import { constructMutexBlockUnlockClaimTake, constructMutexUnlockBlock } from '../claims'; import { unlockMethodName } from '../methods'; +import { unlockFee } from '../settings'; import { MutexUnlockArgs } from '../types'; export const constructUnlockCall = (issuer: ContractIssuer, ...[lockId, timestamp, notify]: MutexUnlockArgs) => { @@ -13,7 +14,7 @@ export const constructUnlockCall = (issuer: ContractIssuer, ...[lockId, timestam methodArgs: [lockId, timestamp] satisfies MutexUnlockArgs, }, contractInfo: { - providedCweb: 1000n, + providedCweb: unlockFee, authenticated: null, }, contractArgs: [], @@ -21,6 +22,6 @@ export const constructUnlockCall = (issuer: ContractIssuer, ...[lockId, timestam ops: notify ? ([constructMutexUnlockBlock(lockId, issuer), constructMutexBlockUnlockClaimTake(lockId)] as const) : [], - fee: 1200n, + fee: unlockFee + (notify ? 1000n : 800n), }; }; diff --git a/packages/cwait/src/onchain/mutex/methods/unlock.ts b/packages/cwait/src/onchain/mutex/methods/unlock.ts index c98abfe..9b6ab01 100644 --- a/packages/cwait/src/onchain/mutex/methods/unlock.ts +++ b/packages/cwait/src/onchain/mutex/methods/unlock.ts @@ -1,7 +1,7 @@ import { constructContinueTx, constructContractRef, constructTake, Context, passCwebFrom } from '@coinweb/contract-kit'; import { getCallParameters, getContractIssuer, getMethodArguments } from 'lib/onchain'; -import { constructMutexLockClaimKey } from '../claims'; +import { constructMutexBlockUnlockClaimStore, constructMutexLockClaimKey } from '../claims'; import { lockFee } from '../settings'; import { MutexUnlockArgs } from '../types'; @@ -16,7 +16,11 @@ export const mutexUnlock = (context: Context) => { return [ constructContinueTx( context, - [passCwebFrom(issuer, availableCweb), constructTake(constructMutexLockClaimKey(lockId, timestamp))], + [ + passCwebFrom(issuer, availableCweb), + constructTake(constructMutexLockClaimKey(lockId, timestamp)), + constructMutexBlockUnlockClaimStore(lockId), + ], [ { callInfo: { diff --git a/packages/cwait/src/onchain/runtime/awaitedTasks.ts b/packages/cwait/src/onchain/runtime/awaitedTasks.ts index d848ccc..f2b3934 100644 --- a/packages/cwait/src/onchain/runtime/awaitedTasks.ts +++ b/packages/cwait/src/onchain/runtime/awaitedTasks.ts @@ -1,13 +1,8 @@ import { PreparedOp, Task } from '../../types'; const awaitedTasks: Task[] = []; -let isFreezed = false; export const pushAwaitedTask = (op: PreparedOp) => { - if (isFreezed) { - return; - } - awaitedTasks.push({ op, batchId: -1 }); }; @@ -20,11 +15,3 @@ export const markTaskBatch = (count: number, batchId: number) => { }; export const getAwaitedTasksCount = () => awaitedTasks.length; - -export const freezeAwaitedTasks = () => { - isFreezed = true; -}; - -export const unfreezeAwaitedTasks = () => { - isFreezed = false; -}; diff --git a/packages/cwait/src/onchain/runtime/execLoop.ts b/packages/cwait/src/onchain/runtime/execLoop.ts index da06ae4..6630337 100644 --- a/packages/cwait/src/onchain/runtime/execLoop.ts +++ b/packages/cwait/src/onchain/runtime/execLoop.ts @@ -1,3 +1,7 @@ +import { ResolvedOp } from '../../types'; + +import { pushResolvedOp } from './resolvedOps'; + let abortExecution: ((isFullyExecuted: boolean) => void) | null = null; export const stopExecution = (isFullyExecuted = false) => { @@ -5,10 +9,18 @@ export const stopExecution = (isFullyExecuted = false) => { abortExecution?.(isFullyExecuted); }; -type ExecTask = () => Promise; +type ExecTask = () => Promise; +type Exec = { task: ExecTask; ops: ResolvedOp[] }; -let execQueue: ExecTask[] = []; -export const setNextExec = (task: ExecTask) => (execQueue = [task]); +const execQueue: Exec[] = []; +export const setNextExec = (task: ExecTask, ops: ResolvedOp[]) => { + return new Promise((resolve, reject) => { + execQueue.push({ + task: () => task().then(resolve, reject), + ops, + }); + }); +}; export const execLoop = async (): Promise => { const nextExec = execQueue.pop(); @@ -17,7 +29,8 @@ export const execLoop = async (): Promise => { const execution = new Promise((resolve, reject) => { abortExecution = resolve; - nextExec().then( + pushResolvedOp(nextExec.ops); + nextExec.task().then( () => { resolve(true); }, diff --git a/packages/cwait/src/onchain/runtime/resolvedOps.ts b/packages/cwait/src/onchain/runtime/resolvedOps.ts index 1cb6a4f..96db33c 100644 --- a/packages/cwait/src/onchain/runtime/resolvedOps.ts +++ b/packages/cwait/src/onchain/runtime/resolvedOps.ts @@ -36,16 +36,22 @@ export const shiftResolvedOp = () => { usedOps.push(result.op); } + console.log('shiftResolvedOp: ', JSON.stringify(result)); + return result; }; export const getUsedOps = () => usedOps; -export const saveUsedOps = () => { +export const startSavingUsedOps = () => { usedOps = []; isSavingUsed = true; }; +export const stopSavingUsedOps = () => { + isSavingUsed = false; +}; + export const freezeResolvedOps = () => { isFreezed = true; }; diff --git a/packages/cwait/src/types.ts b/packages/cwait/src/types.ts index 45fc95c..ca22d77 100644 --- a/packages/cwait/src/types.ts +++ b/packages/cwait/src/types.ts @@ -37,7 +37,9 @@ export type ResolvedExecOp = { }; export type ResolvedChildOp = { - ChildOp: 0; + ChildOp: { + ops: ResolvedOp[]; + }; }; export type ResolvedLockOp = { @@ -100,7 +102,7 @@ export type ExecutorMethodArgs = [ caller?: User, thisId?: string, parentId?: string, - saveResult?: boolean, + shouldSaveResult?: boolean, takenFundsIds?: string[], execOpsIndexes?: number[], ]; diff --git a/packages/ui/.env b/packages/ui/.env index fc545dd..2b76c00 100644 --- a/packages/ui/.env +++ b/packages/ui/.env @@ -1,4 +1,4 @@ VITE_API_URL='https://api-cloud.coinweb.io/wallet' VITE_EXPLORER_URL='https://explorer.coinweb.io' -VITE_CONTRACT_ADDRESS="0xedca91a29553fa2466e89eef02ac06dc49ab68be5e81fa91d05765cab79ec02a" +VITE_CONTRACT_ADDRESS="0xc599810e4861b7b1ae25695d58eeef10556fc84f87f72fafa31c0921e86e92be" diff --git a/yarn.lock b/yarn.lock index 0e862fd..2bbc788 100644 --- a/yarn.lock +++ b/yarn.lock @@ -379,16 +379,16 @@ __metadata: languageName: node linkType: hard -"@coinweb/testing-sdk@npm:0.0.9-remote": - version: 0.0.9-remote - resolution: "@coinweb/testing-sdk@npm:0.0.9-remote" +"@coinweb/testing-sdk@npm:0.0.10-mutex": + version: 0.0.10-mutex + resolution: "@coinweb/testing-sdk@npm:0.0.10-mutex" dependencies: "@coinweb/contract-kit": "npm:0.2.0" - "@coinweb/minimal-sdk": "npm:1.2.19" + "@coinweb/minimal-sdk": "npm:1.2.18" json-stable-stringify: "npm:^1.1.1" lodash.isequal: "npm:^4.5.0" secp256k1: "npm:^5.0.0" - checksum: 10c0/80212780455d4bc2c1082d62ce382c6d07c6b5a107dc372f630c2257de686ec7e072c5d7a0b966e611b73f8b49ef9d6d8b68fbd66de1d968992a6b18444f218b + checksum: 10c0/84785ecc631510aa39189792826cff454700cba96d13d6c9f93f4166ee035fb1c8a8c0f2eab0f51d7d5fe127c661c7964792f8dfacffb9bec5eaf3a6da7fc744 languageName: node linkType: hard