@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.
Files changed (42) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/cjs/transactions/ObjectCache.js +7 -6
  3. package/dist/cjs/transactions/ObjectCache.js.map +2 -2
  4. package/dist/cjs/transactions/executor/caching.d.ts +3 -2
  5. package/dist/cjs/transactions/executor/caching.js +6 -2
  6. package/dist/cjs/transactions/executor/caching.js.map +2 -2
  7. package/dist/cjs/transactions/executor/parallel.d.ts +13 -0
  8. package/dist/cjs/transactions/executor/parallel.js +96 -9
  9. package/dist/cjs/transactions/executor/parallel.js.map +2 -2
  10. package/dist/cjs/transactions/executor/serial.d.ts +1 -1
  11. package/dist/cjs/transactions/executor/serial.js +20 -2
  12. package/dist/cjs/transactions/executor/serial.js.map +2 -2
  13. package/dist/cjs/transactions/json-rpc-resolver.js +4 -4
  14. package/dist/cjs/transactions/json-rpc-resolver.js.map +2 -2
  15. package/dist/cjs/version.d.ts +2 -2
  16. package/dist/cjs/version.js +2 -2
  17. package/dist/cjs/version.js.map +1 -1
  18. package/dist/esm/transactions/ObjectCache.js +7 -6
  19. package/dist/esm/transactions/ObjectCache.js.map +2 -2
  20. package/dist/esm/transactions/executor/caching.d.ts +3 -2
  21. package/dist/esm/transactions/executor/caching.js +6 -2
  22. package/dist/esm/transactions/executor/caching.js.map +2 -2
  23. package/dist/esm/transactions/executor/parallel.d.ts +13 -0
  24. package/dist/esm/transactions/executor/parallel.js +96 -9
  25. package/dist/esm/transactions/executor/parallel.js.map +2 -2
  26. package/dist/esm/transactions/executor/serial.d.ts +1 -1
  27. package/dist/esm/transactions/executor/serial.js +20 -2
  28. package/dist/esm/transactions/executor/serial.js.map +2 -2
  29. package/dist/esm/transactions/json-rpc-resolver.js +4 -4
  30. package/dist/esm/transactions/json-rpc-resolver.js.map +2 -2
  31. package/dist/esm/version.d.ts +2 -2
  32. package/dist/esm/version.js +2 -2
  33. package/dist/esm/version.js.map +1 -1
  34. package/dist/tsconfig.esm.tsbuildinfo +1 -1
  35. package/dist/tsconfig.tsbuildinfo +1 -1
  36. package/package.json +1 -1
  37. package/src/transactions/ObjectCache.ts +7 -7
  38. package/src/transactions/executor/caching.ts +6 -1
  39. package/src/transactions/executor/parallel.ts +114 -7
  40. package/src/transactions/executor/serial.ts +12 -1
  41. package/src/transactions/json-rpc-resolver.ts +6 -5
  42. 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
- return this.#cache.reset();
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
- const bytes = await this.#buildQueue.runTask(async () => {
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
- return this.#cache.buildTransaction({ transaction: transaction });
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.#cache.cache.deleteObjects([...usedObjects]);
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.#executeQueue.activeTasks <= this.#maxPoolSize) {
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.#executeQueue.activeTasks) + 1,
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
- (input.UnresolvedObject && !input.UnresolvedObject.version) ||
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(([id, _obj]) => id);
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: object.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
@@ -3,5 +3,5 @@
3
3
 
4
4
  // This file is generated by genversion.mjs. Do not edit it directly.
5
5
 
6
- export const PACKAGE_VERSION = '1.1.2';
7
- export const TARGETED_RPC_VERSION = '1.28.0';
6
+ export const PACKAGE_VERSION = '1.2.0';
7
+ export const TARGETED_RPC_VERSION = '1.29.0';