UNPKG

@mysten/sui

Version:

Sui TypeScript API(Work in Progress)

368 lines (367 loc) 15.3 kB
var __typeError = (msg) => { throw TypeError(msg); }; var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg); var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj)); var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value); var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), setter ? setter.call(obj, value) : member.set(obj, value), value); var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method); var __privateWrapper = (obj, member, setter, getter) => ({ set _(value) { __privateSet(obj, member, value, setter); }, get _() { return __privateGet(obj, member, getter); } }); var _signer, _client, _coinBatchSize, _initialCoinBalance, _minimumCoinBalance, _epochBoundaryWindow, _defaultGasBudget, _maxPoolSize, _sourceCoins, _coinPool, _cache, _objectIdQueues, _buildQueue, _executeQueue, _lastDigest, _cacheLock, _pendingTransactions, _gasPrice, _ParallelTransactionExecutor_instances, getUsedObjects_fn, execute_fn, updateCache_fn, waitForLastDigest_fn, getGasCoin_fn, getGasPrice_fn, refillCoinPool_fn; import { toB64 } from "@mysten/bcs"; import { bcs } from "../../bcs/index.js"; import { Transaction } from "../Transaction.js"; import { TransactionDataBuilder } from "../TransactionData.js"; import { CachingTransactionExecutor } from "./caching.js"; import { ParallelQueue, SerialQueue } from "./queue.js"; import { getGasCoinFromEffects } from "./serial.js"; const PARALLEL_EXECUTOR_DEFAULTS = { coinBatchSize: 20, initialCoinBalance: 200000000n, minimumCoinBalance: 50000000n, maxPoolSize: 50, epochBoundaryWindow: 1e3 }; class ParallelTransactionExecutor { constructor(options) { __privateAdd(this, _ParallelTransactionExecutor_instances); __privateAdd(this, _signer); __privateAdd(this, _client); __privateAdd(this, _coinBatchSize); __privateAdd(this, _initialCoinBalance); __privateAdd(this, _minimumCoinBalance); __privateAdd(this, _epochBoundaryWindow); __privateAdd(this, _defaultGasBudget); __privateAdd(this, _maxPoolSize); __privateAdd(this, _sourceCoins); __privateAdd(this, _coinPool, []); __privateAdd(this, _cache); __privateAdd(this, _objectIdQueues, /* @__PURE__ */ new Map()); __privateAdd(this, _buildQueue, new SerialQueue()); __privateAdd(this, _executeQueue); __privateAdd(this, _lastDigest, null); __privateAdd(this, _cacheLock, null); __privateAdd(this, _pendingTransactions, 0); __privateAdd(this, _gasPrice, null); __privateSet(this, _signer, options.signer); __privateSet(this, _client, options.client); __privateSet(this, _coinBatchSize, options.coinBatchSize ?? PARALLEL_EXECUTOR_DEFAULTS.coinBatchSize); __privateSet(this, _initialCoinBalance, options.initialCoinBalance ?? PARALLEL_EXECUTOR_DEFAULTS.initialCoinBalance); __privateSet(this, _minimumCoinBalance, options.minimumCoinBalance ?? PARALLEL_EXECUTOR_DEFAULTS.minimumCoinBalance); __privateSet(this, _defaultGasBudget, options.defaultGasBudget ?? __privateGet(this, _minimumCoinBalance)); __privateSet(this, _epochBoundaryWindow, options.epochBoundaryWindow ?? PARALLEL_EXECUTOR_DEFAULTS.epochBoundaryWindow); __privateSet(this, _maxPoolSize, options.maxPoolSize ?? PARALLEL_EXECUTOR_DEFAULTS.maxPoolSize); __privateSet(this, _cache, new CachingTransactionExecutor({ client: options.client, cache: options.cache })); __privateSet(this, _executeQueue, new ParallelQueue(__privateGet(this, _maxPoolSize))); __privateSet(this, _sourceCoins, options.sourceCoins ? new Map(options.sourceCoins.map((id) => [id, null])) : null); } resetCache() { __privateSet(this, _gasPrice, null); return __privateMethod(this, _ParallelTransactionExecutor_instances, updateCache_fn).call(this, () => __privateGet(this, _cache).reset()); } async waitForLastTransaction() { await __privateMethod(this, _ParallelTransactionExecutor_instances, updateCache_fn).call(this, () => __privateMethod(this, _ParallelTransactionExecutor_instances, waitForLastDigest_fn).call(this)); } async executeTransaction(transaction, options) { const { promise, resolve, reject } = promiseWithResolvers(); const usedObjects = await __privateMethod(this, _ParallelTransactionExecutor_instances, getUsedObjects_fn).call(this, transaction); const execute = () => { __privateGet(this, _executeQueue).runTask(() => { const promise2 = __privateMethod(this, _ParallelTransactionExecutor_instances, execute_fn).call(this, transaction, usedObjects, options); return promise2.then(resolve, reject); }); }; const conflicts = /* @__PURE__ */ new Set(); usedObjects.forEach((objectId) => { const queue = __privateGet(this, _objectIdQueues).get(objectId); if (queue) { conflicts.add(objectId); __privateGet(this, _objectIdQueues).get(objectId).push(() => { conflicts.delete(objectId); if (conflicts.size === 0) { execute(); } }); } else { __privateGet(this, _objectIdQueues).set(objectId, []); } }); if (conflicts.size === 0) { execute(); } return promise; } } _signer = new WeakMap(); _client = new WeakMap(); _coinBatchSize = new WeakMap(); _initialCoinBalance = new WeakMap(); _minimumCoinBalance = new WeakMap(); _epochBoundaryWindow = new WeakMap(); _defaultGasBudget = new WeakMap(); _maxPoolSize = new WeakMap(); _sourceCoins = new WeakMap(); _coinPool = new WeakMap(); _cache = new WeakMap(); _objectIdQueues = new WeakMap(); _buildQueue = new WeakMap(); _executeQueue = new WeakMap(); _lastDigest = new WeakMap(); _cacheLock = new WeakMap(); _pendingTransactions = new WeakMap(); _gasPrice = new WeakMap(); _ParallelTransactionExecutor_instances = new WeakSet(); getUsedObjects_fn = async function(transaction) { const usedObjects = /* @__PURE__ */ new Set(); let serialized = false; transaction.addSerializationPlugin(async (blockData, _options, next) => { await next(); if (serialized) { return; } serialized = true; blockData.inputs.forEach((input) => { if (input.Object?.ImmOrOwnedObject?.objectId) { usedObjects.add(input.Object.ImmOrOwnedObject.objectId); } else if (input.Object?.Receiving?.objectId) { usedObjects.add(input.Object.Receiving.objectId); } else if (input.UnresolvedObject?.objectId && !input.UnresolvedObject.initialSharedVersion) { usedObjects.add(input.UnresolvedObject.objectId); } }); }); await transaction.prepareForSerialization({ client: __privateGet(this, _client) }); return usedObjects; }; execute_fn = async function(transaction, usedObjects, options) { let gasCoin; try { transaction.setSenderIfNotSet(__privateGet(this, _signer).toSuiAddress()); await __privateGet(this, _buildQueue).runTask(async () => { const data = transaction.getData(); if (!data.gasData.price) { transaction.setGasPrice(await __privateMethod(this, _ParallelTransactionExecutor_instances, getGasPrice_fn).call(this)); } transaction.setGasBudgetIfNotSet(__privateGet(this, _defaultGasBudget)); await __privateMethod(this, _ParallelTransactionExecutor_instances, updateCache_fn).call(this); gasCoin = await __privateMethod(this, _ParallelTransactionExecutor_instances, getGasCoin_fn).call(this); __privateWrapper(this, _pendingTransactions)._++; transaction.setGasPayment([ { objectId: gasCoin.id, version: gasCoin.version, digest: gasCoin.digest } ]); await __privateGet(this, _cache).buildTransaction({ transaction, onlyTransactionKind: true }); }); const bytes = await transaction.build({ client: __privateGet(this, _client) }); const { signature } = await __privateGet(this, _signer).signTransaction(bytes); const results = await __privateGet(this, _cache).executeTransaction({ transaction: bytes, signature, options: { ...options, showEffects: true } }); const effectsBytes = Uint8Array.from(results.rawEffects); const effects = bcs.TransactionEffects.parse(effectsBytes); const gasResult = getGasCoinFromEffects(effects); const gasUsed = effects.V2?.gasUsed; if (gasCoin && gasUsed && gasResult.owner === __privateGet(this, _signer).toSuiAddress()) { const totalUsed = BigInt(gasUsed.computationCost) + BigInt(gasUsed.storageCost) + BigInt(gasUsed.storageCost) - BigInt(gasUsed.storageRebate); let usesGasCoin = false; new TransactionDataBuilder(transaction.getData()).mapArguments((arg) => { if (arg.$kind === "GasCoin") { usesGasCoin = true; } return arg; }); if (!usesGasCoin && gasCoin.balance >= __privateGet(this, _minimumCoinBalance)) { __privateGet(this, _coinPool).push({ id: gasResult.ref.objectId, version: gasResult.ref.version, digest: gasResult.ref.digest, balance: gasCoin.balance - totalUsed }); } else { if (!__privateGet(this, _sourceCoins)) { __privateSet(this, _sourceCoins, /* @__PURE__ */ new Map()); } __privateGet(this, _sourceCoins).set(gasResult.ref.objectId, gasResult.ref); } } __privateSet(this, _lastDigest, results.digest); return { digest: results.digest, effects: toB64(effectsBytes), data: results }; } catch (error) { if (gasCoin) { if (!__privateGet(this, _sourceCoins)) { __privateSet(this, _sourceCoins, /* @__PURE__ */ new Map()); } __privateGet(this, _sourceCoins).set(gasCoin.id, null); } await __privateMethod(this, _ParallelTransactionExecutor_instances, updateCache_fn).call(this, async () => { await Promise.all([ __privateGet(this, _cache).cache.deleteObjects([...usedObjects]), __privateMethod(this, _ParallelTransactionExecutor_instances, waitForLastDigest_fn).call(this) ]); }); throw error; } finally { usedObjects.forEach((objectId) => { const queue = __privateGet(this, _objectIdQueues).get(objectId); if (queue && queue.length > 0) { queue.shift()(); } else if (queue) { __privateGet(this, _objectIdQueues).delete(objectId); } }); __privateWrapper(this, _pendingTransactions)._--; } }; updateCache_fn = async function(fn) { if (__privateGet(this, _cacheLock)) { await __privateGet(this, _cacheLock); } __privateSet(this, _cacheLock, fn?.().then( () => { __privateSet(this, _cacheLock, null); }, () => { } ) ?? null); }; waitForLastDigest_fn = async function() { const digest = __privateGet(this, _lastDigest); if (digest) { __privateSet(this, _lastDigest, null); await __privateGet(this, _client).waitForTransaction({ digest }); } }; getGasCoin_fn = async function() { if (__privateGet(this, _coinPool).length === 0 && __privateGet(this, _pendingTransactions) <= __privateGet(this, _maxPoolSize)) { await __privateMethod(this, _ParallelTransactionExecutor_instances, refillCoinPool_fn).call(this); } if (__privateGet(this, _coinPool).length === 0) { throw new Error("No coins available"); } const coin = __privateGet(this, _coinPool).shift(); return coin; }; getGasPrice_fn = async function() { const remaining = __privateGet(this, _gasPrice) ? __privateGet(this, _gasPrice).expiration - __privateGet(this, _epochBoundaryWindow) - Date.now() : 0; if (remaining > 0) { return __privateGet(this, _gasPrice).price; } if (__privateGet(this, _gasPrice)) { const timeToNextEpoch = Math.max( __privateGet(this, _gasPrice).expiration + __privateGet(this, _epochBoundaryWindow) - Date.now(), 1e3 ); await new Promise((resolve) => setTimeout(resolve, timeToNextEpoch)); } const state = await __privateGet(this, _client).getLatestSuiSystemState(); __privateSet(this, _gasPrice, { price: BigInt(state.referenceGasPrice), expiration: Number.parseInt(state.epochStartTimestampMs, 10) + Number.parseInt(state.epochDurationMs, 10) }); return __privateMethod(this, _ParallelTransactionExecutor_instances, getGasPrice_fn).call(this); }; refillCoinPool_fn = async function() { const batchSize = Math.min( __privateGet(this, _coinBatchSize), __privateGet(this, _maxPoolSize) - (__privateGet(this, _coinPool).length + __privateGet(this, _pendingTransactions)) + 1 ); if (batchSize === 0) { return; } const txb = new Transaction(); const address = __privateGet(this, _signer).toSuiAddress(); txb.setSender(address); if (__privateGet(this, _sourceCoins)) { const refs = []; const ids = []; for (const [id, ref] of __privateGet(this, _sourceCoins)) { if (ref) { refs.push(ref); } else { ids.push(id); } } if (ids.length > 0) { const coins = await __privateGet(this, _client).multiGetObjects({ ids }); refs.push( ...coins.filter((coin) => coin.data !== null).map(({ data }) => ({ objectId: data.objectId, version: data.version, digest: data.digest })) ); } txb.setGasPayment(refs); __privateSet(this, _sourceCoins, /* @__PURE__ */ new Map()); } const amounts = new Array(batchSize).fill(__privateGet(this, _initialCoinBalance)); const results = txb.splitCoins(txb.gas, amounts); const coinResults = []; for (let i = 0; i < amounts.length; i++) { coinResults.push(results[i]); } txb.transferObjects(coinResults, address); await this.waitForLastTransaction(); const result = await __privateGet(this, _client).signAndExecuteTransaction({ transaction: txb, signer: __privateGet(this, _signer), options: { showRawEffects: true } }); const effects = bcs.TransactionEffects.parse(Uint8Array.from(result.rawEffects)); effects.V2?.changedObjects.forEach(([id, { outputState }], i) => { if (i === effects.V2?.gasObjectIndex || !outputState.ObjectWrite) { return; } __privateGet(this, _coinPool).push({ id, version: effects.V2.lamportVersion, digest: outputState.ObjectWrite[0], balance: BigInt(__privateGet(this, _initialCoinBalance)) }); }); if (!__privateGet(this, _sourceCoins)) { __privateSet(this, _sourceCoins, /* @__PURE__ */ new Map()); } const gasObject = getGasCoinFromEffects(effects).ref; __privateGet(this, _sourceCoins).set(gasObject.objectId, gasObject); await __privateGet(this, _client).waitForTransaction({ digest: result.digest }); }; function promiseWithResolvers() { let resolve; let reject; const promise = new Promise((_resolve, _reject) => { resolve = _resolve; reject = _reject; }); return { promise, resolve, reject }; } export { ParallelTransactionExecutor }; //# sourceMappingURL=parallel.js.map