add: mutex calls
This commit is contained in:
parent
5dd0aef472
commit
02f81660a7
@ -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) : [],
|
||||||
|
};
|
||||||
|
};
|
||||||
@ -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,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
@ -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,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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';
|
||||||
|
|||||||
@ -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';
|
||||||
|
|||||||
@ -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)]);
|
|
||||||
};
|
|
||||||
43
packages/cwait/src/onchain/mutex/claims/mutexExecOps.ts
Normal file
43
packages/cwait/src/onchain/mutex/claims/mutexExecOps.ts
Normal 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)]);
|
||||||
|
};
|
||||||
@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
117
packages/cwait/src/onchain/mutex/methods/execOps.ts
Normal file
117
packages/cwait/src/onchain/mutex/methods/execOps.ts
Normal 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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
),
|
||||||
|
];
|
||||||
|
};
|
||||||
@ -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
|
|
||||||
),
|
|
||||||
]),
|
|
||||||
];
|
|
||||||
};
|
|
||||||
@ -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';
|
||||||
|
|||||||
@ -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';
|
||||||
|
|||||||
@ -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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
),
|
||||||
|
];
|
||||||
|
};
|
||||||
33
packages/cwait/src/onchain/mutex/methods/saveExecOpResult.ts
Normal file
33
packages/cwait/src/onchain/mutex/methods/saveExecOpResult.ts
Normal 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)])];
|
||||||
|
};
|
||||||
@ -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])];
|
|
||||||
};
|
|
||||||
@ -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[]];
|
||||||
|
|||||||
@ -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)) {
|
||||||
|
|||||||
@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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 };
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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 };
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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);
|
||||||
|
};
|
||||||
|
|||||||
@ -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[],
|
||||||
];
|
];
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user