@mysten/sui
Version:
Sui TypeScript API(Work in Progress)
368 lines (367 loc) • 15.3 kB
JavaScript
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