add: mutex calls

This commit is contained in:
Alex 2025-04-26 13:38:01 +03:00
parent 5dd0aef472
commit 02f81660a7
26 changed files with 672 additions and 347 deletions

View File

@ -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<CwebTake> | 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<CwebTake> | GStore<CwebStore>)[] = 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) : [],
};
};

View File

@ -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,
};
};

View File

@ -1,6 +1,6 @@
import { ContractIssuer, constructContractRef } from '@coinweb/contract-kit'; import { ContractIssuer, constructContractRef } from '@coinweb/contract-kit';
import { constructMutexBlockAccessClaimTake, constructMutexLockBlock } from '../claims'; import { constructMutexBlockLockClaimTake, constructMutexLockBlock } from '../claims';
import { lockMethodName } from '../methods'; import { lockMethodName } from '../methods';
import { lockFee } from '../settings'; import { lockFee } from '../settings';
@ -18,7 +18,7 @@ export const constructLockCall = (issuer: ContractIssuer, lockId: string) => {
}, },
contractArgs: [], contractArgs: [],
}, },
ops: [constructMutexLockBlock(lockId, issuer), constructMutexBlockAccessClaimTake(lockId)], ops: [constructMutexLockBlock(lockId, issuer), constructMutexBlockLockClaimTake(lockId)] as const,
fee: lockFee + 200n, fee: lockFee + 200n,
}; };
}; };

View File

@ -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,
};
};

View File

@ -18,7 +18,9 @@ export const constructUnlockCall = (issuer: ContractIssuer, ...[lockId, timestam
}, },
contractArgs: [], contractArgs: [],
}, },
ops: notify ? [constructMutexUnlockBlock(lockId, issuer), constructMutexBlockUnlockClaimTake(lockId)] : [], ops: notify
? ([constructMutexUnlockBlock(lockId, issuer), constructMutexBlockUnlockClaimTake(lockId)] as const)
: [],
fee: 1200n, fee: 1200n,
}; };
}; };

View File

@ -1,4 +1,3 @@
export * from './constructGetAccessCall';
export * from './constructLockCall'; export * from './constructLockCall';
export * from './constructTryOpCall'; export * from './constructExecOpsCall';
export * from './constructUnlockCall'; export * from './constructUnlockCall';

View File

@ -1,4 +1,4 @@
export * from './mutexBlockAccess'; export * from './mutexExecOps';
export * from './mutexBlockLock'; export * from './mutexBlockLock';
export * from './mutexBlockUnlock'; export * from './mutexBlockUnlock';
export * from './mutexLock'; export * from './mutexLock';

View File

@ -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<CwebStore> =>
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)]);
};

View File

@ -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<CwebStore> => 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)]);
};

View File

@ -1,20 +1,27 @@
import { import {
execLockMethodName, execLockMethodName,
getAccessMethodName, execOps,
execOpsMethodName,
lockMethodName, lockMethodName,
mutexExecLock, mutexExecLock,
mutexGetAccess,
mutexLock, mutexLock,
mutexUnlock, mutexUnlock,
preReadExecTakeOpsMethodName,
saveExecOpResult,
saveExecOpResultMethodName,
unlockMethodName, unlockMethodName,
} from './methods'; } from './methods';
import { preReadExecTakeOps } from './methods/preReadExecTakeOps';
export * from './claims'; export * from './claims';
export * from './types'; export * from './types';
export * from './calls';
export const mutexMethods = { export const mutexMethods = {
[execLockMethodName]: mutexExecLock, [execLockMethodName]: mutexExecLock,
[getAccessMethodName]: mutexGetAccess,
[lockMethodName]: mutexLock, [lockMethodName]: mutexLock,
[unlockMethodName]: mutexUnlock, [unlockMethodName]: mutexUnlock,
[execOpsMethodName]: execOps,
[preReadExecTakeOpsMethodName]: preReadExecTakeOps,
[saveExecOpResultMethodName]: saveExecOpResult,
}; };

View File

@ -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<MutexExecOpArgs>(context);
const lockQueue = extractRead(extractContractArgs(context.tx)[0])?.map(
({ content }) => content as TypedClaim<MutexLockState>
);
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,
},
},
]
),
];
};

View File

@ -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<MutexGetAccessArgs>(context);
const lockQueue = extractRead(extractContractArgs(context.tx)[0])?.map(
({ content }) => content as TypedClaim<MutexLockState>
);
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
),
]),
];
};

View File

@ -1,7 +1,7 @@
export * from './execLock'; export * from './execLock';
export * from './getAccess';
export * from './lock'; export * from './lock';
export * from './names'; export * from './names';
export * from './notifyLock'; export * from './notifyLock';
export * from './tryOp'; export * from './execOps';
export * from './unlock'; export * from './unlock';
export * from './saveExecOpResult';

View File

@ -1,6 +1,7 @@
export const lockMethodName = '_mutex_lock'; export const lockMethodName = '_mutex_lock';
export const execLockMethodName = '_mutex_execLock'; export const execLockMethodName = '_mutex_execLock';
export const getAccessMethodName = '_mutex_get_access';
export const unlockMethodName = '_mutex_unlock'; export const unlockMethodName = '_mutex_unlock';
export const tryOpMethodName = '_mutex_try_op';
export const notifyLockMethodName = '_mutex_notify_lock'; 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';

View File

@ -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<MutexPreReadTakeOpsArgs>(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,
},
},
]
),
];
};

View File

@ -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<MutexSaveExecOpResultArgs>(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)])];
};

View File

@ -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<MutexTryOpArgs>(context);
const claimKey = 'StoreOp' in op ? op.StoreOp.key : op.TakeOp.key;
const lockQueue = extractRead(extractContractArgs(context.tx)[0])?.map(
({ content }) => content as TypedClaim<MutexLockState>
);
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])];
};

View File

@ -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<ClaimKey, 'second_part'> & Partial<Pick<ClaimKey, 'second_part'>>; export type LockedKey = Omit<ClaimKey, 'second_part'> & Partial<Pick<ClaimKey, 'second_part'>>;
@ -8,19 +9,23 @@ export type MutexLockState = {
processId: string; processId: string;
}; };
export enum MutexAccessStatus { export type MutexExecOpsResult = (
GRANTED = 'granted', | {
DENIED = 'denied', ok: true;
} resolved: ResolvedOperation;
}
export type MutexAccessResult = { | {
status: MutexAccessStatus; ok: false;
}; error: string;
}
export type MutexGetAccessArgs = [claimKey: ClaimKey, processId: string, uniqueId: string]; )[];
export type MutexUnlockArgs = [lockId: string, timestamp: number, notify?: boolean]; export type MutexUnlockArgs = [lockId: string, timestamp: number, notify?: boolean];
export type MutexTryOpArgs = [op: GTake<Claim> | GStore<Claim>, processId: string]; export type MutexExecOpArgs = [ops: (GTake<CwebTake> | GStore<CwebStore>)[], processId: string, execId?: string];
export type MutexNotifyLockArgs = [lockId: string]; export type MutexNotifyLockArgs = [lockId: string];
export type MutexPreReadTakeOpsArgs = [execId: string, unavailableIndexes: number[], ops: PreparedOperation[]];
export type MutexSaveExecOpResultArgs = [execId: string, unavailableIndexes: number[]];

View File

@ -6,7 +6,7 @@ import { isResolvedSlotOp, isResolvedStoreOp } from '../../utils';
import { pushAwaitedTask } from '../awaited'; import { pushAwaitedTask } from '../awaited';
import { shiftResolvedOp } from '../resolved'; import { shiftResolvedOp } from '../resolved';
export const storeOp = (claim: Claim) => { export const storeOp = (claim: Claim, storeCweb?: bigint) => {
console.log('storeOp'); console.log('storeOp');
let opMarkerValue = false; let opMarkerValue = false;
@ -15,7 +15,7 @@ export const storeOp = (claim: Claim) => {
const { op, isOp } = shiftResolvedOp(); const { op, isOp } = shiftResolvedOp();
if (!isOp) { if (!isOp) {
pushAwaitedTask(constructStore(claim)); pushAwaitedTask({ ...constructStore(claim), providedCweb: storeCweb });
opMarkerValue = true; opMarkerValue = true;
} else { } else {
if (isResolvedSlotOp(op)) { if (isResolvedSlotOp(op)) {

View File

@ -1,9 +1,9 @@
import { prepareTx } from './prepareTxs'; import { prepareTx } from './prepareTxs';
export const constructTx = (isFullyExecuted: boolean) => { 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; return txs;
}; };

View File

@ -3,42 +3,52 @@ import {
constructContinueTx, constructContinueTx,
constructContractRef, constructContractRef,
FullCallInfo, FullCallInfo,
GTake,
NewTx, NewTx,
passCwebFrom, passCwebFrom,
PreparedOperation, PreparedOperation,
sendCwebInterface, sendCwebInterface,
} from '@coinweb/contract-kit'; } from '@coinweb/contract-kit';
import { CwebBlock } from '@coinweb/contract-kit/dist/esm/operations/block'; import { CwebTake } from '@coinweb/contract-kit/dist/types/operations/take';
import { GBlock } from '@coinweb/contract-kit/dist/types/operations/generics';
import { ExecutorMethodArgs, ResolvedOp, ResolvedSlotOp, Task } from '../../../types'; import { ExecutorMethodArgs, PreparedExtendedStoreOp, PreparedOp, ResolvedOp, ResolvedSlotOp } from '../../../types';
import { constructFundsClaimRangRead, constructFundsClaimStore } from '../../claims/funds'; import { constructFundsClaimRangRead, constructFundsClaimStore } from '../../claims/funds';
import { constructResultBlockFilter, constructResultClaimTake, resultKey } from '../../claims/result'; import { constructResultBlockFilter, constructResultClaimTake, resultKey } from '../../claims/result';
import { context, getRawContext } from '../../context'; 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 = ({ export const prepareInThreadTxs = ({
cwebPerCall, cwebPerCall,
outThreadTasksCount, outThreadTasksCount,
outThreadFee, outThreadFee,
tasks, ops,
}: { }: {
tasks: Task[]; ops: PreparedOp[];
cwebPerCall: bigint; cwebPerCall: bigint;
outThreadTasksCount: number; outThreadTasksCount: number;
outThreadFee: bigint; outThreadFee: bigint;
}): { }): {
txs: NewTx[]; txs: NewTx[];
calls: number; calls: number;
txFee: bigint;
} => { } => {
if (!tasks.length) { if (!ops.length) {
const { constructSendCweb } = sendCwebInterface(); const { constructSendCweb } = sendCwebInterface();
const { availableCweb, takeOps, storedCweb } = context.funds; const { availableCweb, takeOps, storedCweb } = context.funds;
const { user } = context; const { user } = context;
const restOfAvailableCweb = availableCweb - outThreadFee; 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 = const txs =
restOfCweb > 0n restOfCweb > 0n
@ -51,12 +61,15 @@ export const prepareInThreadTxs = ({
] ]
: []; : [];
return { txs, calls: 0 }; return { txs, calls: 0, txFee: fee };
} }
let txFee = 0n; let txFee = 0n;
let callsPrepared = 0; 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<CwebTake>)[] = [];
const excOpsIndexes: number[] = [];
const resolvedChildOps: ResolvedOp[] = [...context.ops, ...resolvedSlotOps]; const resolvedChildOps: ResolvedOp[] = [...context.ops, ...resolvedSlotOps];
@ -66,15 +79,11 @@ export const prepareInThreadTxs = ({
//Info for separate child call //Info for separate child call
const childCalls: FullCallInfo[] = []; const childCalls: FullCallInfo[] = [];
//Block ops for separate child call ops.forEach((op, i) => {
const childBlocks = tasks switch (true) {
.filter((task): task is { op: GBlock<CwebBlock>; batchId: number } => isPreparedBlockOp(task.op)) case isPreparedExecOp(op): {
.map(({ op }) => op);
tasks.forEach((task) => {
if (isPreparedExecOp(task.op)) {
console.log('Child call info'); console.log('Child call info');
const id = task.op.ExecOp.id; const id = op.ExecOp.id;
callArgs.push(constructBlock([constructResultBlockFilter(id)]), constructResultClaimTake(id)); callArgs.push(constructBlock([constructResultBlockFilter(id)]), constructResultClaimTake(id));
txFee += 200n; txFee += 200n;
@ -94,7 +103,7 @@ export const prepareInThreadTxs = ({
] satisfies ExecutorMethodArgs, ] satisfies ExecutorMethodArgs,
}, },
contractInfo: { contractInfo: {
providedCweb: cwebPerCall - 700n, providedCweb: cwebPerCall,
authenticated: null, authenticated: null,
}, },
contractArgs: [], contractArgs: [],
@ -102,12 +111,45 @@ export const prepareInThreadTxs = ({
}); });
callsPrepared++; callsPrepared++;
} else { txFee += 700n;
callArgs.push(task.op);
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; txFee += 100n;
} }
resolvedChildOps.push({ SlotOp: 0 }); resolvedChildOps.push({ SlotOp: { ok: true } });
}); });
const returnTxs: NewTx[] = []; const returnTxs: NewTx[] = [];
@ -168,6 +210,7 @@ export const prepareInThreadTxs = ({
context.parentId, context.parentId,
context.needSaveResult, context.needSaveResult,
takeOps.map((op) => (op.TakeOp.key.second_part as [string])[0]), takeOps.map((op) => (op.TakeOp.key.second_part as [string])[0]),
excOpsIndexes,
] satisfies ExecutorMethodArgs, ] satisfies ExecutorMethodArgs,
}, },
contractInfo: { contractInfo: {
@ -182,10 +225,26 @@ export const prepareInThreadTxs = ({
); );
if (childCalls.length) { 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 };
}; };

View File

@ -2,42 +2,50 @@ import {
constructContinueTx, constructContinueTx,
constructContractRef, constructContractRef,
FullCallInfo, FullCallInfo,
GTake,
NewTx, NewTx,
PreparedOperation, PreparedOperation,
} from '@coinweb/contract-kit'; } 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 { 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 = ({ export const prepareOutThreadTxs = ({
tasks, ops,
cwebPerCall, cwebPerCall,
}: { }: {
tasks: Task[]; ops: PreparedOp[];
cwebPerCall: bigint; cwebPerCall: bigint;
}): { }): {
txs: NewTx[]; txs: NewTx[];
fee: bigint; txFee: bigint;
calls: number; calls: number;
} => { } => {
const siblingCallResolvedOps = [...context.ops]; const siblingCallResolvedOps = [...context.ops];
const siblingTxInfo: { const preparedCalls: FullCallInfo[] = [];
[batchId: number]: { const preparedOps: PreparedOperation[] = [];
calls: FullCallInfo[]; const preparedStoreAndTakes: (PreparedExtendedStoreOp | GTake<CwebTake>)[] = [];
ops: PreparedOperation[];
};
} = {};
let txFee = 0n; let txFee = 0n;
let callsPrepared = 0; let callsPrepared = 0;
tasks.forEach((task) => { ops.forEach((op) => {
if (isPreparedExecOp(task.op)) { switch (true) {
case isPreparedExecOp(op): {
console.log('Sibling call info'); console.log('Sibling call info');
const id = task.op.ExecOp.id; const id = op.ExecOp.id;
const callInfo = { const callInfo = {
callInfo: { callInfo: {
@ -54,44 +62,65 @@ export const prepareOutThreadTxs = ({
] satisfies ExecutorMethodArgs, ] satisfies ExecutorMethodArgs,
}, },
contractInfo: { contractInfo: {
providedCweb: cwebPerCall - 700n, providedCweb: cwebPerCall,
authenticated: null, authenticated: null,
}, },
contractArgs: [], contractArgs: [],
}, },
}; };
if (siblingTxInfo[task.batchId]) { preparedCalls.push(callInfo);
siblingTxInfo[task.batchId].calls.push(callInfo); txFee += 700n;
} else {
siblingTxInfo[task.batchId] = {
calls: [callInfo],
ops: [],
};
}
callsPrepared++; callsPrepared++;
} else { break;
if (siblingTxInfo[task.batchId]) {
siblingTxInfo[task.batchId].ops.push(task.op);
} else {
siblingTxInfo[task.batchId] = {
calls: [],
ops: [task.op],
};
} }
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; txFee += 100n;
} }
siblingCallResolvedOps.push({ SlotOp: 0 }); siblingCallResolvedOps.push({ SlotOp: { ok: true } });
}); });
const siblingTxs = Object.values(siblingTxInfo).map(({ calls, ops }) => if (preparedStoreAndTakes.length > 0) {
constructContinueTx(getRawContext(), ops, calls) 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,
};
}; };

View File

@ -7,7 +7,11 @@ import { prepareInThreadTxs } from './prepareInThreadTxs';
import { prepareOutThreadTxs } from './prepareOutThreadTxs'; import { prepareOutThreadTxs } from './prepareOutThreadTxs';
import { splitTasks } from './splitTasks'; 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); console.log('Calls Count: ', callsCount);
const awaitedTasks = getAwaitedTasks(); const awaitedTasks = getAwaitedTasks();
@ -16,14 +20,15 @@ export const prepareTx = (isFullyExecuted: boolean, callsCount?: number): { txs:
if (!awaitedTasks.length) { if (!awaitedTasks.length) {
if (context.isChild) { if (context.isChild) {
return { txs: [], calls: 0 }; return { txs: [], calls: 0, txFee: 0n };
} }
const { constructSendCweb } = sendCwebInterface(); const { constructSendCweb } = sendCwebInterface();
const { availableCweb, takeOps, storedCweb } = context.funds; const { availableCweb, takeOps, storedCweb } = context.funds;
const { user } = context; 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 = const txs =
restOfCweb > 0n 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 { availableCweb } = context.funds;
const cwebPerCall = availableCweb / BigInt(callsCount || 1); const cwebPerCall = (availableCweb - txFee) / BigInt(callsCount || 1);
const { const {
txs: outThreadTxs, txs: outThreadTxs,
fee: outThreadFee,
calls: outThreadCallsPrepared, calls: outThreadCallsPrepared,
} = prepareOutThreadTxs({ tasks: outThreadTasks, cwebPerCall }); txFee: outThreadFee,
} = prepareOutThreadTxs({ ops: outThreadOps, cwebPerCall });
const { txs: inThreadTxs, calls: inThreadCallsPrepared } = prepareInThreadTxs({ const {
tasks: inThreadTasks, txs: inThreadTxs,
calls: inThreadCallsPrepared,
txFee: inThreadFee,
} = prepareInThreadTxs({
ops: inThreadOps,
cwebPerCall, cwebPerCall,
outThreadTasksCount: outThreadTasks.length, outThreadTasksCount: outThreadOps.length,
outThreadFee, outThreadFee,
}); });
return { txs: [...inThreadTxs, ...outThreadTxs], calls: inThreadCallsPrepared + outThreadCallsPrepared }; return {
txs: [...inThreadTxs, ...outThreadTxs],
calls: inThreadCallsPrepared + outThreadCallsPrepared,
txFee: inThreadFee + outThreadFee,
};
}; };

View File

@ -1,48 +1,26 @@
import { Task } from '../../../types'; import { PreparedOp, Task } from '../../../types';
import { isPreparedBlockOp } from '../typeGuards';
export const splitTasks = (awaitedTasks: Task[], isFullyExecuted: boolean) => { export const splitTasks = (awaitedTasks: Task[], isFullyExecuted: boolean) => {
const awaitedTasksBatched: (Task | Task[])[] = []; if (awaitedTasks.length === 0) {
const preparedTasks: (Task | Task[])[] = []; return { inThreadOps: [], outThreadOps: [] };
awaitedTasks.forEach((task) => {
if (task.batchId === -1) {
awaitedTasksBatched.push(task);
return;
} }
const latestTask = awaitedTasksBatched.at(-1); const inThreadOps: PreparedOp[] = [];
let outThreadOps: PreparedOp[] = [];
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) { if (!isFullyExecuted) {
const preparedCallTasks = preparedTasks.at(-1)!; const latestTaskBatchId = awaitedTasks.at(-1)!.batchId ?? -1;
inThreadTasks = [...(Array.isArray(preparedCallTasks) ? preparedCallTasks : [preparedCallTasks])];
outThreadTasks = preparedTasks.slice(0, -1).flat(); awaitedTasks.forEach((task) => {
if (task.batchId === latestTaskBatchId) {
inThreadOps.push(task.op);
} else { } else {
outThreadTasks = preparedTasks.flat(); outThreadOps.push(task.op);
}
});
} else {
outThreadOps = awaitedTasks.map((task) => task.op);
} }
return { inThreadTasks, outThreadTasks }; return { inThreadOps, outThreadOps };
}; };

View File

@ -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 { CwebCallRefResolved, isResolvedCall } from '@coinweb/contract-kit/dist/esm/operations/call';
import { ResolvedBlock } from '@coinweb/contract-kit/dist/types/operations/block'; 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 { GBlock, GCall, GRead, GStore, GTake } from '@coinweb/contract-kit/dist/types/operations/generics';
import { CwebTake } from '@coinweb/contract-kit/dist/types/operations/take';
import { import {
PreparedExecOp, PreparedExecOp,
PreparedExtendedStoreOp,
PreparedLockOp,
PreparedOp, PreparedOp,
PreparedUnlockOp,
ResolvedChildOp, ResolvedChildOp,
ResolvedExecOp, ResolvedExecOp,
ResolvedLockOp, ResolvedLockOp,
@ -119,3 +123,43 @@ export const isPreparedBlockOp = (op?: PreparedOp | null): op is GBlock<CwebBloc
return !!(op && 'BlockOp' in 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<CwebStore> => {
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<CwebTake> => {
if (op && 'TakeOp' in op) {
console.log('isPreparedTakeOp >>> ', JSON.stringify(op));
}
return !!(op && 'TakeOp' in op);
};

View File

@ -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'; import { LockedKey } from './onchain/mutex';
@ -11,7 +20,14 @@ export type TypedClaim<TBody extends OrdJson = OrdJson, TKey extends ClaimKey =
}; };
export type ResolvedSlotOp = { export type ResolvedSlotOp = {
SlotOp: 0; SlotOp:
| {
ok: false;
error: string;
}
| {
ok: true;
};
}; };
export type ResolvedExecOp = { export type ResolvedExecOp = {
@ -27,6 +43,7 @@ export type ResolvedChildOp = {
export type ResolvedLockOp = { export type ResolvedLockOp = {
LockOp: { LockOp: {
lockId: string; lockId: string;
timestamp: number;
}; };
}; };
@ -52,16 +69,25 @@ export type PreparedLockOp = {
LockOp: { LockOp: {
lockId: string; lockId: string;
keys: LockedKey[]; keys: LockedKey[];
timestamp: number;
}; };
}; };
export type PreparedUnlockOp = { export type PreparedUnlockOp = {
UnlockOp: { UnlockOp: {
lockId: string; lockId: string;
timestamp: number;
}; };
}; };
export type PreparedOp = PreparedOperation | PreparedExecOp | PreparedLockOp | PreparedUnlockOp; export type PreparedExtendedStoreOp = GStore<CwebStore> & { providedCweb?: bigint };
export type PreparedOp =
| PreparedOperation
| PreparedExecOp
| PreparedLockOp
| PreparedUnlockOp
| PreparedExtendedStoreOp;
export type Task = { export type Task = {
op: PreparedOp; op: PreparedOp;
@ -76,4 +102,5 @@ export type ExecutorMethodArgs = [
parentId?: string, parentId?: string,
saveResult?: boolean, saveResult?: boolean,
takenFundsIds?: string[], takenFundsIds?: string[],
execOpsIndexes?: number[],
]; ];