@mysten/sui 1.1.2 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +6 -0
- package/dist/cjs/transactions/ObjectCache.js +7 -6
- package/dist/cjs/transactions/ObjectCache.js.map +2 -2
- package/dist/cjs/transactions/executor/caching.d.ts +3 -2
- package/dist/cjs/transactions/executor/caching.js +6 -2
- package/dist/cjs/transactions/executor/caching.js.map +2 -2
- package/dist/cjs/transactions/executor/parallel.d.ts +13 -0
- package/dist/cjs/transactions/executor/parallel.js +96 -9
- package/dist/cjs/transactions/executor/parallel.js.map +2 -2
- package/dist/cjs/transactions/executor/serial.d.ts +1 -1
- package/dist/cjs/transactions/executor/serial.js +20 -2
- package/dist/cjs/transactions/executor/serial.js.map +2 -2
- package/dist/cjs/transactions/json-rpc-resolver.js +4 -4
- package/dist/cjs/transactions/json-rpc-resolver.js.map +2 -2
- package/dist/cjs/version.d.ts +2 -2
- package/dist/cjs/version.js +2 -2
- package/dist/cjs/version.js.map +1 -1
- package/dist/esm/transactions/ObjectCache.js +7 -6
- package/dist/esm/transactions/ObjectCache.js.map +2 -2
- package/dist/esm/transactions/executor/caching.d.ts +3 -2
- package/dist/esm/transactions/executor/caching.js +6 -2
- package/dist/esm/transactions/executor/caching.js.map +2 -2
- package/dist/esm/transactions/executor/parallel.d.ts +13 -0
- package/dist/esm/transactions/executor/parallel.js +96 -9
- package/dist/esm/transactions/executor/parallel.js.map +2 -2
- package/dist/esm/transactions/executor/serial.d.ts +1 -1
- package/dist/esm/transactions/executor/serial.js +20 -2
- package/dist/esm/transactions/executor/serial.js.map +2 -2
- package/dist/esm/transactions/json-rpc-resolver.js +4 -4
- package/dist/esm/transactions/json-rpc-resolver.js.map +2 -2
- package/dist/esm/version.d.ts +2 -2
- package/dist/esm/version.js +2 -2
- package/dist/esm/version.js.map +1 -1
- package/dist/tsconfig.esm.tsbuildinfo +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/transactions/ObjectCache.ts +7 -7
- package/src/transactions/executor/caching.ts +6 -1
- package/src/transactions/executor/parallel.ts +114 -7
- package/src/transactions/executor/serial.ts +12 -1
- package/src/transactions/json-rpc-resolver.ts +6 -5
- package/src/version.ts +2 -2
|
@@ -18,14 +18,28 @@ const PARALLEL_EXECUTOR_DEFAULTS = {
|
|
|
18
18
|
initialCoinBalance: 200_000_000n,
|
|
19
19
|
minimumCoinBalance: 50_000_000n,
|
|
20
20
|
maxPoolSize: 50,
|
|
21
|
+
epochBoundaryWindow: 1_000,
|
|
21
22
|
} satisfies Omit<ParallelTransactionExecutorOptions, 'signer' | 'client'>;
|
|
22
23
|
export interface ParallelTransactionExecutorOptions extends Omit<ObjectCacheOptions, 'address'> {
|
|
23
24
|
client: SuiClient;
|
|
24
25
|
signer: Signer;
|
|
26
|
+
/** The number of coins to create in a batch when refilling the gas pool */
|
|
25
27
|
coinBatchSize?: number;
|
|
28
|
+
/** The initial balance of each coin created for the gas pool */
|
|
26
29
|
initialCoinBalance?: bigint;
|
|
30
|
+
/** The minimum balance of a coin that can be reused for future transactions. If the gasCoin is below this value, it will be used when refilling the gasPool */
|
|
27
31
|
minimumCoinBalance?: bigint;
|
|
32
|
+
/** The gasBudget to use if the transaction has not defined it's own gasBudget, defaults to `minimumCoinBalance` */
|
|
33
|
+
defaultGasBudget?: bigint;
|
|
34
|
+
/**
|
|
35
|
+
* Time to wait before/after the expected epoch boundary before re-fetching the gas pool (in milliseconds).
|
|
36
|
+
* Building transactions will be paused for up to 2x this duration around each epoch boundary to ensure the
|
|
37
|
+
* gas price is up-to-date for the next epoch.
|
|
38
|
+
* */
|
|
39
|
+
epochBoundaryWindow?: number;
|
|
40
|
+
/** The maximum number of transactions that can be execute in parallel, this also determines the maximum number of gas coins that will be created */
|
|
28
41
|
maxPoolSize?: number;
|
|
42
|
+
/** An initial list of coins used to fund the gas pool, uses all owned SUI coins by default */
|
|
29
43
|
sourceCoins?: string[];
|
|
30
44
|
}
|
|
31
45
|
|
|
@@ -41,6 +55,8 @@ export class ParallelTransactionExecutor {
|
|
|
41
55
|
#coinBatchSize: number;
|
|
42
56
|
#initialCoinBalance: bigint;
|
|
43
57
|
#minimumCoinBalance: bigint;
|
|
58
|
+
#epochBoundaryWindow: number;
|
|
59
|
+
#defaultGasBudget: bigint;
|
|
44
60
|
#maxPoolSize: number;
|
|
45
61
|
#sourceCoins: Map<string, SuiObjectRef | null> | null;
|
|
46
62
|
#coinPool: CoinWithBalance[] = [];
|
|
@@ -48,6 +64,13 @@ export class ParallelTransactionExecutor {
|
|
|
48
64
|
#objectIdQueues = new Map<string, (() => void)[]>();
|
|
49
65
|
#buildQueue = new SerialQueue();
|
|
50
66
|
#executeQueue: ParallelQueue;
|
|
67
|
+
#lastDigest: string | null = null;
|
|
68
|
+
#cacheLock: Promise<void> | null = null;
|
|
69
|
+
#pendingTransactions = 0;
|
|
70
|
+
#gasPrice: null | {
|
|
71
|
+
price: bigint;
|
|
72
|
+
expiration: number;
|
|
73
|
+
} = null;
|
|
51
74
|
|
|
52
75
|
constructor(options: ParallelTransactionExecutorOptions) {
|
|
53
76
|
this.#signer = options.signer;
|
|
@@ -57,6 +80,9 @@ export class ParallelTransactionExecutor {
|
|
|
57
80
|
options.initialCoinBalance ?? PARALLEL_EXECUTOR_DEFAULTS.initialCoinBalance;
|
|
58
81
|
this.#minimumCoinBalance =
|
|
59
82
|
options.minimumCoinBalance ?? PARALLEL_EXECUTOR_DEFAULTS.minimumCoinBalance;
|
|
83
|
+
this.#defaultGasBudget = options.defaultGasBudget ?? this.#minimumCoinBalance;
|
|
84
|
+
this.#epochBoundaryWindow =
|
|
85
|
+
options.epochBoundaryWindow ?? PARALLEL_EXECUTOR_DEFAULTS.epochBoundaryWindow;
|
|
60
86
|
this.#maxPoolSize = options.maxPoolSize ?? PARALLEL_EXECUTOR_DEFAULTS.maxPoolSize;
|
|
61
87
|
this.#cache = new CachingTransactionExecutor({
|
|
62
88
|
client: options.client,
|
|
@@ -69,7 +95,8 @@ export class ParallelTransactionExecutor {
|
|
|
69
95
|
}
|
|
70
96
|
|
|
71
97
|
resetCache() {
|
|
72
|
-
|
|
98
|
+
this.#gasPrice = null;
|
|
99
|
+
return this.#updateCache(() => this.#cache.reset());
|
|
73
100
|
}
|
|
74
101
|
|
|
75
102
|
async executeTransaction(transaction: Transaction) {
|
|
@@ -145,8 +172,22 @@ export class ParallelTransactionExecutor {
|
|
|
145
172
|
async #execute(transaction: Transaction, usedObjects: Set<string>) {
|
|
146
173
|
let gasCoin!: CoinWithBalance;
|
|
147
174
|
try {
|
|
148
|
-
|
|
175
|
+
transaction.setSenderIfNotSet(this.#signer.toSuiAddress());
|
|
176
|
+
|
|
177
|
+
await this.#buildQueue.runTask(async () => {
|
|
178
|
+
const data = transaction.getData();
|
|
179
|
+
|
|
180
|
+
if (!data.gasData.price) {
|
|
181
|
+
transaction.setGasPrice(await this.#getGasPrice());
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
if (!data.gasData.budget) {
|
|
185
|
+
transaction.setGasBudget(this.#defaultGasBudget);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
await this.#updateCache();
|
|
149
189
|
gasCoin = await this.#getGasCoin();
|
|
190
|
+
this.#pendingTransactions++;
|
|
150
191
|
transaction.setGasPayment([
|
|
151
192
|
{
|
|
152
193
|
objectId: gasCoin.id,
|
|
@@ -154,11 +195,13 @@ export class ParallelTransactionExecutor {
|
|
|
154
195
|
digest: gasCoin.digest,
|
|
155
196
|
},
|
|
156
197
|
]);
|
|
157
|
-
transaction.setSenderIfNotSet(this.#signer.toSuiAddress());
|
|
158
198
|
|
|
159
|
-
|
|
199
|
+
// Resolve cached references
|
|
200
|
+
await this.#cache.buildTransaction({ transaction, onlyTransactionKind: true });
|
|
160
201
|
});
|
|
161
202
|
|
|
203
|
+
const bytes = await transaction.build({ client: this.#client });
|
|
204
|
+
|
|
162
205
|
const { signature } = await this.#signer.signTransaction(bytes);
|
|
163
206
|
|
|
164
207
|
const results = await this.#cache.executeTransaction({
|
|
@@ -197,6 +240,8 @@ export class ParallelTransactionExecutor {
|
|
|
197
240
|
}
|
|
198
241
|
}
|
|
199
242
|
|
|
243
|
+
this.#lastDigest = results.digest;
|
|
244
|
+
|
|
200
245
|
return {
|
|
201
246
|
digest: results.digest,
|
|
202
247
|
effects: toB64(effectsBytes),
|
|
@@ -210,7 +255,13 @@ export class ParallelTransactionExecutor {
|
|
|
210
255
|
this.#sourceCoins.set(gasCoin.id, null);
|
|
211
256
|
}
|
|
212
257
|
|
|
213
|
-
await this.#
|
|
258
|
+
await this.#updateCache(async () => {
|
|
259
|
+
await Promise.all([
|
|
260
|
+
this.#cache.cache.deleteObjects([...usedObjects]),
|
|
261
|
+
this.#waitForLastDigest(),
|
|
262
|
+
]);
|
|
263
|
+
});
|
|
264
|
+
|
|
214
265
|
throw error;
|
|
215
266
|
} finally {
|
|
216
267
|
usedObjects.forEach((objectId) => {
|
|
@@ -221,11 +272,35 @@ export class ParallelTransactionExecutor {
|
|
|
221
272
|
this.#objectIdQueues.delete(objectId);
|
|
222
273
|
}
|
|
223
274
|
});
|
|
275
|
+
this.#pendingTransactions--;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/** Helper for synchronizing cache updates, by ensuring only one update happens at a time. This can also be used to wait for any pending cache updates */
|
|
280
|
+
async #updateCache(fn?: () => Promise<void>) {
|
|
281
|
+
if (this.#cacheLock) {
|
|
282
|
+
await this.#cacheLock;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
this.#cacheLock =
|
|
286
|
+
fn?.().then(
|
|
287
|
+
() => {
|
|
288
|
+
this.#cacheLock = null;
|
|
289
|
+
},
|
|
290
|
+
() => {},
|
|
291
|
+
) ?? null;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async #waitForLastDigest() {
|
|
295
|
+
const digest = this.#lastDigest;
|
|
296
|
+
if (digest) {
|
|
297
|
+
this.#lastDigest = null;
|
|
298
|
+
await this.#client.waitForTransaction({ digest });
|
|
224
299
|
}
|
|
225
300
|
}
|
|
226
301
|
|
|
227
302
|
async #getGasCoin() {
|
|
228
|
-
if (this.#coinPool.length === 0 && this.#
|
|
303
|
+
if (this.#coinPool.length === 0 && this.#pendingTransactions <= this.#maxPoolSize) {
|
|
229
304
|
await this.#refillCoinPool();
|
|
230
305
|
}
|
|
231
306
|
|
|
@@ -237,10 +312,40 @@ export class ParallelTransactionExecutor {
|
|
|
237
312
|
return coin;
|
|
238
313
|
}
|
|
239
314
|
|
|
315
|
+
async #getGasPrice(): Promise<bigint> {
|
|
316
|
+
const remaining = this.#gasPrice
|
|
317
|
+
? this.#gasPrice.expiration - this.#epochBoundaryWindow - Date.now()
|
|
318
|
+
: 0;
|
|
319
|
+
|
|
320
|
+
if (remaining > 0) {
|
|
321
|
+
return this.#gasPrice!.price;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
if (this.#gasPrice) {
|
|
325
|
+
const timeToNextEpoch = Math.max(
|
|
326
|
+
this.#gasPrice.expiration + this.#epochBoundaryWindow - Date.now(),
|
|
327
|
+
1_000,
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
await new Promise((resolve) => setTimeout(resolve, timeToNextEpoch));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const state = await this.#client.getLatestSuiSystemState();
|
|
334
|
+
|
|
335
|
+
this.#gasPrice = {
|
|
336
|
+
price: BigInt(state.referenceGasPrice),
|
|
337
|
+
expiration:
|
|
338
|
+
Number.parseInt(state.epochStartTimestampMs, 10) +
|
|
339
|
+
Number.parseInt(state.epochDurationMs, 10),
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
return this.#getGasPrice();
|
|
343
|
+
}
|
|
344
|
+
|
|
240
345
|
async #refillCoinPool() {
|
|
241
346
|
const batchSize = Math.min(
|
|
242
347
|
this.#coinBatchSize,
|
|
243
|
-
this.#maxPoolSize - (this.#coinPool.length + this.#
|
|
348
|
+
this.#maxPoolSize - (this.#coinPool.length + this.#pendingTransactions) + 1,
|
|
244
349
|
);
|
|
245
350
|
|
|
246
351
|
if (batchSize === 0) {
|
|
@@ -289,6 +394,8 @@ export class ParallelTransactionExecutor {
|
|
|
289
394
|
}
|
|
290
395
|
txb.transferObjects(coinResults, address);
|
|
291
396
|
|
|
397
|
+
await this.#updateCache(() => this.#waitForLastDigest());
|
|
398
|
+
|
|
292
399
|
const result = await this.#client.signAndExecuteTransaction({
|
|
293
400
|
transaction: txb,
|
|
294
401
|
signer: this.#signer,
|
|
@@ -15,6 +15,8 @@ export class SerialTransactionExecutor {
|
|
|
15
15
|
#queue = new SerialQueue();
|
|
16
16
|
#signer: Signer;
|
|
17
17
|
#cache: CachingTransactionExecutor;
|
|
18
|
+
#client: SuiClient;
|
|
19
|
+
#lastDigest: string | null = null;
|
|
18
20
|
|
|
19
21
|
constructor({
|
|
20
22
|
signer,
|
|
@@ -24,6 +26,7 @@ export class SerialTransactionExecutor {
|
|
|
24
26
|
signer: Signer;
|
|
25
27
|
}) {
|
|
26
28
|
this.#signer = signer;
|
|
29
|
+
this.#client = options.client;
|
|
27
30
|
this.#cache = new CachingTransactionExecutor({
|
|
28
31
|
client: options.client,
|
|
29
32
|
cache: options.cache,
|
|
@@ -69,7 +72,14 @@ export class SerialTransactionExecutor {
|
|
|
69
72
|
};
|
|
70
73
|
|
|
71
74
|
resetCache() {
|
|
72
|
-
return this.#cache.reset();
|
|
75
|
+
return Promise.all([this.#cache.reset(), this.#waitForLastTransaction()]);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async #waitForLastTransaction() {
|
|
79
|
+
if (this.#lastDigest) {
|
|
80
|
+
await this.#client.waitForTransaction({ digest: this.#lastDigest });
|
|
81
|
+
this.#lastDigest = null;
|
|
82
|
+
}
|
|
73
83
|
}
|
|
74
84
|
|
|
75
85
|
executeTransaction(transaction: Transaction | Uint8Array) {
|
|
@@ -92,6 +102,7 @@ export class SerialTransactionExecutor {
|
|
|
92
102
|
const effectsBytes = Uint8Array.from(results.rawEffects!);
|
|
93
103
|
const effects = bcs.TransactionEffects.parse(effectsBytes);
|
|
94
104
|
await this.applyEffects(effects);
|
|
105
|
+
this.#lastDigest = results.digest;
|
|
95
106
|
|
|
96
107
|
return {
|
|
97
108
|
digest: results.digest,
|
|
@@ -148,8 +148,8 @@ async function resolveObjectReferences(
|
|
|
148
148
|
// We keep the input by-reference to avoid needing to re-resolve it:
|
|
149
149
|
const objectsToResolve = transactionData.inputs.filter((input) => {
|
|
150
150
|
return (
|
|
151
|
-
|
|
152
|
-
input.UnresolvedObject?.initialSharedVersion
|
|
151
|
+
input.UnresolvedObject &&
|
|
152
|
+
!(input.UnresolvedObject.version || input.UnresolvedObject?.initialSharedVersion)
|
|
153
153
|
);
|
|
154
154
|
}) as Extract<CallArg, { UnresolvedObject: unknown }>[];
|
|
155
155
|
|
|
@@ -179,7 +179,7 @@ async function resolveObjectReferences(
|
|
|
179
179
|
|
|
180
180
|
const invalidObjects = Array.from(responsesById)
|
|
181
181
|
.filter(([_, obj]) => obj.error)
|
|
182
|
-
.map(([
|
|
182
|
+
.map(([_, obj]) => JSON.stringify(obj.error));
|
|
183
183
|
|
|
184
184
|
if (invalidObjects.length) {
|
|
185
185
|
throw new Error(`The following input objects are invalid: ${invalidObjects.join(', ')}`);
|
|
@@ -218,10 +218,11 @@ async function resolveObjectReferences(
|
|
|
218
218
|
const id = normalizeSuiAddress(input.UnresolvedObject.objectId);
|
|
219
219
|
const object = objectsById.get(id);
|
|
220
220
|
|
|
221
|
-
if (object?.initialSharedVersion) {
|
|
221
|
+
if (input.UnresolvedObject.initialSharedVersion ?? object?.initialSharedVersion) {
|
|
222
222
|
updated = Inputs.SharedObjectRef({
|
|
223
223
|
objectId: id,
|
|
224
|
-
initialSharedVersion:
|
|
224
|
+
initialSharedVersion:
|
|
225
|
+
input.UnresolvedObject.initialSharedVersion || object?.initialSharedVersion!,
|
|
225
226
|
mutable: isUsedAsMutable(transactionData, index),
|
|
226
227
|
});
|
|
227
228
|
} else if (isUsedAsReceiving(transactionData, index)) {
|
package/src/version.ts
CHANGED