ox 0.14.18 → 0.14.19
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/_cjs/tempo/TxEnvelopeTempo.js.map +1 -1
- package/_cjs/tempo/VirtualMaster.js +192 -13
- package/_cjs/tempo/VirtualMaster.js.map +1 -1
- package/_cjs/tempo/index.js.map +1 -1
- package/_cjs/tempo/internal/mine.wasm.js +6 -0
- package/_cjs/tempo/internal/mine.wasm.js.map +1 -0
- package/_cjs/tempo/internal/virtualMasterPool.js +186 -0
- package/_cjs/tempo/internal/virtualMasterPool.js.map +1 -0
- package/_cjs/version.js +1 -1
- package/_esm/tempo/TxEnvelopeTempo.js +6 -3
- package/_esm/tempo/TxEnvelopeTempo.js.map +1 -1
- package/_esm/tempo/VirtualMaster.js +305 -22
- package/_esm/tempo/VirtualMaster.js.map +1 -1
- package/_esm/tempo/index.js +4 -2
- package/_esm/tempo/index.js.map +1 -1
- package/_esm/tempo/internal/mine.wasm.js +15 -0
- package/_esm/tempo/internal/mine.wasm.js.map +1 -0
- package/_esm/tempo/internal/virtualMasterPool.js +216 -0
- package/_esm/tempo/internal/virtualMasterPool.js.map +1 -0
- package/_esm/version.js +1 -1
- package/_types/tempo/TxEnvelopeTempo.d.ts +6 -3
- package/_types/tempo/TxEnvelopeTempo.d.ts.map +1 -1
- package/_types/tempo/VirtualMaster.d.ts +92 -10
- package/_types/tempo/VirtualMaster.d.ts.map +1 -1
- package/_types/tempo/index.d.ts +4 -2
- package/_types/tempo/index.d.ts.map +1 -1
- package/_types/tempo/internal/mine.wasm.d.ts +5 -0
- package/_types/tempo/internal/mine.wasm.d.ts.map +1 -0
- package/_types/tempo/internal/virtualMasterPool.d.ts +46 -0
- package/_types/tempo/internal/virtualMasterPool.d.ts.map +1 -0
- package/_types/version.d.ts +1 -1
- package/package.json +1 -1
- package/tempo/TxEnvelopeTempo.ts +6 -3
- package/tempo/VirtualMaster.test.ts +77 -0
- package/tempo/VirtualMaster.ts +431 -23
- package/tempo/index.ts +5 -2
- package/tempo/internal/mine.c +182 -0
- package/tempo/internal/mine.wasm.ts +17 -0
- package/tempo/internal/virtualMasterPool.ts +254 -0
- package/version.ts +1 -1
|
@@ -115,6 +115,82 @@ describe('mineSalt', () => {
|
|
|
115
115
|
})
|
|
116
116
|
})
|
|
117
117
|
|
|
118
|
+
describe('mineSaltAsync', () => {
|
|
119
|
+
test('finds the same salt as the sync version', async () => {
|
|
120
|
+
const result = await VirtualMaster.mineSaltAsync({
|
|
121
|
+
address,
|
|
122
|
+
count: 16,
|
|
123
|
+
start: 0xabf52ba0n,
|
|
124
|
+
workers: 1,
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
expect(result).toMatchInlineSnapshot(`
|
|
128
|
+
{
|
|
129
|
+
"masterId": "0x58e21090",
|
|
130
|
+
"registrationHash": "0x0000000058e21090d8f4bee424b90cddc2378aefa1bbbfa1443631a929ae966d",
|
|
131
|
+
"salt": "0x00000000000000000000000000000000000000000000000000000000abf52baf",
|
|
132
|
+
}
|
|
133
|
+
`)
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
test('finds the same salt with workers', async () => {
|
|
137
|
+
const result = await VirtualMaster.mineSaltAsync({
|
|
138
|
+
address,
|
|
139
|
+
count: 16,
|
|
140
|
+
start: 0xabf52ba0n,
|
|
141
|
+
workers: 2,
|
|
142
|
+
})
|
|
143
|
+
|
|
144
|
+
expect(result).toEqual({
|
|
145
|
+
masterId: '0x58e21090',
|
|
146
|
+
registrationHash:
|
|
147
|
+
'0x0000000058e21090d8f4bee424b90cddc2378aefa1bbbfa1443631a929ae966d',
|
|
148
|
+
salt: '0x00000000000000000000000000000000000000000000000000000000abf52baf',
|
|
149
|
+
})
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
test('returns undefined when no salt is found', async () => {
|
|
153
|
+
const result = await VirtualMaster.mineSaltAsync({
|
|
154
|
+
address,
|
|
155
|
+
count: 1,
|
|
156
|
+
start: 0n,
|
|
157
|
+
workers: 1,
|
|
158
|
+
})
|
|
159
|
+
|
|
160
|
+
expect(result).toBeUndefined()
|
|
161
|
+
})
|
|
162
|
+
|
|
163
|
+
test('reports progress', async () => {
|
|
164
|
+
const progressCalls: VirtualMaster.mineSaltAsync.Progress[] = []
|
|
165
|
+
|
|
166
|
+
await VirtualMaster.mineSaltAsync({
|
|
167
|
+
address,
|
|
168
|
+
count: 16,
|
|
169
|
+
start: 0xabf52ba0n,
|
|
170
|
+
workers: 1,
|
|
171
|
+
chunkSize: 8,
|
|
172
|
+
onProgress: (p) => progressCalls.push({ ...p }),
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
expect(progressCalls.length).toBeGreaterThanOrEqual(1)
|
|
176
|
+
expect(progressCalls[0]!.workers).toBe(1)
|
|
177
|
+
expect(progressCalls[0]!.attempts).toBeGreaterThan(0)
|
|
178
|
+
})
|
|
179
|
+
|
|
180
|
+
test('supports AbortSignal cancellation', async () => {
|
|
181
|
+
const controller = new AbortController()
|
|
182
|
+
controller.abort()
|
|
183
|
+
|
|
184
|
+
await expect(
|
|
185
|
+
VirtualMaster.mineSaltAsync({
|
|
186
|
+
address,
|
|
187
|
+
count: 1_000_000,
|
|
188
|
+
signal: controller.signal,
|
|
189
|
+
}),
|
|
190
|
+
).rejects.toThrow()
|
|
191
|
+
})
|
|
192
|
+
})
|
|
193
|
+
|
|
118
194
|
test('exports', () => {
|
|
119
195
|
expect(Object.keys(VirtualMaster)).toMatchInlineSnapshot(`
|
|
120
196
|
[
|
|
@@ -122,6 +198,7 @@ test('exports', () => {
|
|
|
122
198
|
"getMasterId",
|
|
123
199
|
"validateSalt",
|
|
124
200
|
"mineSalt",
|
|
201
|
+
"mineSaltAsync",
|
|
125
202
|
]
|
|
126
203
|
`)
|
|
127
204
|
})
|
package/tempo/VirtualMaster.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { keccak_256 } from '@noble/hashes/sha3'
|
|
1
2
|
import * as Address from '../core/Address.js'
|
|
2
3
|
import * as Bytes from '../core/Bytes.js'
|
|
3
4
|
import * as Errors from '../core/Errors.js'
|
|
4
5
|
import * as Hash from '../core/Hash.js'
|
|
5
6
|
import * as Hex from '../core/Hex.js'
|
|
7
|
+
import * as VirtualMasterPool from './internal/virtualMasterPool.js'
|
|
6
8
|
import * as TempoAddress from './TempoAddress.js'
|
|
7
9
|
import * as VirtualAddress from './VirtualAddress.js'
|
|
8
10
|
|
|
@@ -25,11 +27,12 @@ export type Salt = Hex.Hex | Bytes.Bytes | number | bigint
|
|
|
25
27
|
*
|
|
26
28
|
* @example
|
|
27
29
|
* ```ts twoslash
|
|
30
|
+
* import { Address, Hex } from 'ox'
|
|
28
31
|
* import { VirtualMaster } from 'ox/tempo'
|
|
29
32
|
*
|
|
30
33
|
* const hash = VirtualMaster.getRegistrationHash({
|
|
31
|
-
* address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
|
|
32
|
-
* salt: '0x00000000000000000000000000000000000000000000000000000000abf52baf',
|
|
34
|
+
* address: Address.from('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'),
|
|
35
|
+
* salt: Hex.from('0x00000000000000000000000000000000000000000000000000000000abf52baf'),
|
|
33
36
|
* })
|
|
34
37
|
*
|
|
35
38
|
* hash
|
|
@@ -79,11 +82,12 @@ export declare namespace getRegistrationHash {
|
|
|
79
82
|
*
|
|
80
83
|
* @example
|
|
81
84
|
* ```ts twoslash
|
|
85
|
+
* import { Address, Hex } from 'ox'
|
|
82
86
|
* import { VirtualMaster } from 'ox/tempo'
|
|
83
87
|
*
|
|
84
88
|
* const masterId = VirtualMaster.getMasterId({
|
|
85
|
-
* address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
|
|
86
|
-
* salt: '0x00000000000000000000000000000000000000000000000000000000abf52baf',
|
|
89
|
+
* address: Address.from('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'),
|
|
90
|
+
* salt: Hex.from('0x00000000000000000000000000000000000000000000000000000000abf52baf'),
|
|
87
91
|
* })
|
|
88
92
|
*
|
|
89
93
|
* masterId
|
|
@@ -112,11 +116,12 @@ export declare namespace getMasterId {
|
|
|
112
116
|
*
|
|
113
117
|
* @example
|
|
114
118
|
* ```ts twoslash
|
|
119
|
+
* import { Address, Hex } from 'ox'
|
|
115
120
|
* import { VirtualMaster } from 'ox/tempo'
|
|
116
121
|
*
|
|
117
122
|
* const valid = VirtualMaster.validateSalt({
|
|
118
|
-
* address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
|
|
119
|
-
* salt: '0x00000000000000000000000000000000000000000000000000000000abf52baf',
|
|
123
|
+
* address: Address.from('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'),
|
|
124
|
+
* salt: Hex.from('0x00000000000000000000000000000000000000000000000000000000abf52baf'),
|
|
120
125
|
* })
|
|
121
126
|
*
|
|
122
127
|
* valid
|
|
@@ -146,7 +151,7 @@ export declare namespace validateSalt {
|
|
|
146
151
|
/**
|
|
147
152
|
* Searches a bounded range of salts for the first value that satisfies TIP-1022 PoW.
|
|
148
153
|
*
|
|
149
|
-
* [TIP-1022](https://
|
|
154
|
+
* [TIP-1022](https://tips.sh/1022)
|
|
150
155
|
*
|
|
151
156
|
* This is intentionally a small, deterministic primitive. It does not coordinate
|
|
152
157
|
* workers or async execution. Callers that need large searches can shard ranges
|
|
@@ -155,14 +160,21 @@ export declare namespace validateSalt {
|
|
|
155
160
|
* Master addresses must satisfy TIP-1022 registration constraints: they cannot
|
|
156
161
|
* be the zero address, another virtual address, or a TIP-20 token address.
|
|
157
162
|
*
|
|
163
|
+
* :::warning
|
|
164
|
+
*
|
|
165
|
+
* It is strongly recommended to use {@link ox#VirtualMaster.(mineSaltAsync:function)} instead of this
|
|
166
|
+
* function. `mineSaltAsync` uses WASM-accelerated keccak256 with parallel
|
|
167
|
+
* workers and is a lot faster than the pure JS implementation used here.
|
|
168
|
+
*
|
|
169
|
+
* :::
|
|
170
|
+
*
|
|
158
171
|
* @example
|
|
159
172
|
* ```ts twoslash
|
|
173
|
+
* import { Address } from 'ox'
|
|
160
174
|
* import { VirtualMaster } from 'ox/tempo'
|
|
161
175
|
*
|
|
162
176
|
* const result = VirtualMaster.mineSalt({
|
|
163
177
|
* address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
|
|
164
|
-
* start: 0xabf52ba0n,
|
|
165
|
-
* count: 16,
|
|
166
178
|
* })
|
|
167
179
|
*
|
|
168
180
|
* result?.salt
|
|
@@ -175,28 +187,30 @@ export declare namespace validateSalt {
|
|
|
175
187
|
export function mineSalt(
|
|
176
188
|
value: mineSalt.Value,
|
|
177
189
|
): mineSalt.ReturnType | undefined {
|
|
178
|
-
|
|
190
|
+
const count = value.count ?? 2 ** 32
|
|
191
|
+
|
|
192
|
+
assertCount(count)
|
|
179
193
|
|
|
180
|
-
const
|
|
181
|
-
const
|
|
182
|
-
const saltBytes = toFixedBytes(value.start ?? 0n, 32)
|
|
183
|
-
const input = new Uint8Array(addressBytes.length + saltBytes.length)
|
|
194
|
+
const addressBytes = Bytes.fromHex(resolveAddress(value.address))
|
|
195
|
+
const input = new Uint8Array(addressBytes.length + 32)
|
|
184
196
|
input.set(addressBytes)
|
|
185
197
|
|
|
186
|
-
|
|
187
|
-
|
|
198
|
+
// Salt is a view into input — increment mutates input directly, no copy.
|
|
199
|
+
const saltView = input.subarray(addressBytes.length)
|
|
200
|
+
saltView.set(toFixedBytes(value.start ?? 0n, 32))
|
|
188
201
|
|
|
189
|
-
|
|
202
|
+
for (let i = 0; i < count; i++) {
|
|
203
|
+
const hash = keccak_256(input)
|
|
190
204
|
|
|
191
|
-
if (
|
|
205
|
+
if (hash[0] === 0 && hash[1] === 0 && hash[2] === 0 && hash[3] === 0) {
|
|
192
206
|
return {
|
|
193
|
-
masterId: Hex.fromBytes(
|
|
194
|
-
registrationHash: Hex.fromBytes(
|
|
195
|
-
salt: Hex.fromBytes(
|
|
207
|
+
masterId: Hex.fromBytes(hash.subarray(4, 8)),
|
|
208
|
+
registrationHash: Hex.fromBytes(hash),
|
|
209
|
+
salt: Hex.fromBytes(saltView),
|
|
196
210
|
}
|
|
197
211
|
}
|
|
198
212
|
|
|
199
|
-
if (i <
|
|
213
|
+
if (i < count - 1 && !increment(saltView)) break
|
|
200
214
|
}
|
|
201
215
|
|
|
202
216
|
return undefined
|
|
@@ -207,7 +221,7 @@ export declare namespace mineSalt {
|
|
|
207
221
|
/** Master address. Accepts both hex and Tempo addresses. */
|
|
208
222
|
address: TempoAddress.Address
|
|
209
223
|
/** Number of consecutive salts to try. */
|
|
210
|
-
count
|
|
224
|
+
count?: number | undefined
|
|
211
225
|
/** Starting salt value. @default 0n */
|
|
212
226
|
start?: Salt | undefined
|
|
213
227
|
}
|
|
@@ -235,10 +249,379 @@ export declare namespace mineSalt {
|
|
|
235
249
|
| Errors.GlobalErrorType
|
|
236
250
|
}
|
|
237
251
|
|
|
252
|
+
/**
|
|
253
|
+
* Searches for a salt that satisfies TIP-1022 PoW using parallel workers and
|
|
254
|
+
* WASM-accelerated keccak256.
|
|
255
|
+
*
|
|
256
|
+
* [TIP-1022](https://tips.sh/1022)
|
|
257
|
+
*
|
|
258
|
+
* Uses WASM-accelerated keccak256 with parallel
|
|
259
|
+
* workers when available. Falls back to chunked single-threaded mining in
|
|
260
|
+
* environments without worker support.
|
|
261
|
+
*
|
|
262
|
+
* - **Node.js / Bun / Deno**: Spawns `worker_threads` with inline WASM keccak256.
|
|
263
|
+
* - **Browsers**: Spawns Web Workers via Blob URLs with inline WASM keccak256.
|
|
264
|
+
*
|
|
265
|
+
* @example
|
|
266
|
+
* ```ts twoslash
|
|
267
|
+
* import { Address } from 'ox'
|
|
268
|
+
* import { VirtualMaster } from 'ox/tempo'
|
|
269
|
+
*
|
|
270
|
+
* const result = await VirtualMaster.mineSaltAsync({
|
|
271
|
+
* address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
|
|
272
|
+
* })
|
|
273
|
+
* ```
|
|
274
|
+
*
|
|
275
|
+
* @param parameters - Search parameters.
|
|
276
|
+
* @returns The first matching salt, if any.
|
|
277
|
+
*/
|
|
278
|
+
export async function mineSaltAsync(
|
|
279
|
+
parameters: mineSaltAsync.Parameters,
|
|
280
|
+
): Promise<mineSalt.ReturnType | undefined> {
|
|
281
|
+
const {
|
|
282
|
+
chunkSize = 100_000,
|
|
283
|
+
count = 2 ** 32,
|
|
284
|
+
onProgress,
|
|
285
|
+
signal,
|
|
286
|
+
start: start_ = 0n,
|
|
287
|
+
workers = getDefaultWorkerCount(),
|
|
288
|
+
} = parameters
|
|
289
|
+
|
|
290
|
+
const address = resolveAddress(parameters.address)
|
|
291
|
+
const start = toFixedHex(start_, 32)
|
|
292
|
+
|
|
293
|
+
assertCount(count)
|
|
294
|
+
if (workers !== undefined) assertWorkers(workers)
|
|
295
|
+
throwIfAborted(signal)
|
|
296
|
+
|
|
297
|
+
const workerCount = Math.max(
|
|
298
|
+
1,
|
|
299
|
+
Math.min(workers, Math.ceil(count / chunkSize)),
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
if (workerCount <= 1)
|
|
303
|
+
return mineSaltAsyncFallback({
|
|
304
|
+
address,
|
|
305
|
+
chunkSize,
|
|
306
|
+
count,
|
|
307
|
+
onProgress,
|
|
308
|
+
signal,
|
|
309
|
+
start,
|
|
310
|
+
})
|
|
311
|
+
|
|
312
|
+
const pool = await VirtualMasterPool.resolve()
|
|
313
|
+
if (!pool)
|
|
314
|
+
return mineSaltAsyncFallback({
|
|
315
|
+
address,
|
|
316
|
+
chunkSize,
|
|
317
|
+
count,
|
|
318
|
+
onProgress,
|
|
319
|
+
signal,
|
|
320
|
+
start,
|
|
321
|
+
})
|
|
322
|
+
|
|
323
|
+
return mineSaltWithWorkerPool({
|
|
324
|
+
address,
|
|
325
|
+
chunkSize,
|
|
326
|
+
count,
|
|
327
|
+
onProgress,
|
|
328
|
+
pool,
|
|
329
|
+
signal,
|
|
330
|
+
start,
|
|
331
|
+
workerCount,
|
|
332
|
+
})
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
export declare namespace mineSaltAsync {
|
|
336
|
+
type Parameters = {
|
|
337
|
+
/** Master address. Accepts both hex and Tempo addresses. */
|
|
338
|
+
address: TempoAddress.Address
|
|
339
|
+
/**
|
|
340
|
+
* Number of salts each worker processes before sending a progress update.
|
|
341
|
+
*
|
|
342
|
+
* @default 100_000
|
|
343
|
+
*/
|
|
344
|
+
chunkSize?: number | undefined
|
|
345
|
+
/** Number of consecutive salts to try. @default 2 ** 32 */
|
|
346
|
+
count?: number | undefined
|
|
347
|
+
/** Progress callback invoked after each completed chunk. */
|
|
348
|
+
onProgress?: ((progress: Progress) => void) | undefined
|
|
349
|
+
/** AbortSignal for cancellation. */
|
|
350
|
+
signal?: AbortSignal | undefined
|
|
351
|
+
/** Starting salt value. @default 0n */
|
|
352
|
+
start?: Salt | undefined
|
|
353
|
+
/**
|
|
354
|
+
* Number of workers to use.
|
|
355
|
+
*
|
|
356
|
+
* Set to `0` or `1` to disable worker parallelism.
|
|
357
|
+
*
|
|
358
|
+
* @default os.availableParallelism() - 1
|
|
359
|
+
*/
|
|
360
|
+
workers?: number | undefined
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
type Progress = {
|
|
364
|
+
/** Total attempts so far. */
|
|
365
|
+
attempts: number
|
|
366
|
+
/** Configured chunk size. */
|
|
367
|
+
chunkSize: number
|
|
368
|
+
/** Total count requested. */
|
|
369
|
+
count: number
|
|
370
|
+
/** Elapsed time in milliseconds. */
|
|
371
|
+
elapsed: number
|
|
372
|
+
/** Fraction complete (0–1). */
|
|
373
|
+
progress: number
|
|
374
|
+
/** Hashes per second. */
|
|
375
|
+
rate: number
|
|
376
|
+
/** Number of workers in use. */
|
|
377
|
+
workers: number
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
type ErrorType =
|
|
381
|
+
| mineSalt.ErrorType
|
|
382
|
+
| Errors.BaseError
|
|
383
|
+
| Errors.GlobalErrorType
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
/**
|
|
387
|
+
* Runs parallel salt mining across a worker pool.
|
|
388
|
+
*
|
|
389
|
+
* @internal
|
|
390
|
+
*/
|
|
391
|
+
// biome-ignore lint/correctness/noUnusedVariables: _
|
|
392
|
+
function mineSaltWithWorkerPool(
|
|
393
|
+
value: mineSaltWithWorkerPool.Options,
|
|
394
|
+
): Promise<mineSalt.ReturnType | undefined> {
|
|
395
|
+
const startedAt = Date.now()
|
|
396
|
+
|
|
397
|
+
return new Promise<mineSalt.ReturnType | undefined>((resolve, reject) => {
|
|
398
|
+
let settled = false
|
|
399
|
+
let attempts = 0
|
|
400
|
+
let completedWorkers = 0
|
|
401
|
+
const handles: { terminate(): void }[] = []
|
|
402
|
+
|
|
403
|
+
const emitProgress = () => {
|
|
404
|
+
if (!value.onProgress) return
|
|
405
|
+
const elapsed = Date.now() - startedAt
|
|
406
|
+
const seconds = elapsed / 1000
|
|
407
|
+
value.onProgress({
|
|
408
|
+
attempts,
|
|
409
|
+
chunkSize: value.chunkSize,
|
|
410
|
+
count: value.count,
|
|
411
|
+
elapsed,
|
|
412
|
+
progress: Math.min(1, attempts / value.count),
|
|
413
|
+
rate: seconds === 0 ? 0 : attempts / seconds,
|
|
414
|
+
workers: value.workerCount,
|
|
415
|
+
})
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const terminateAll = () => {
|
|
419
|
+
for (const h of handles) h.terminate()
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const succeed = (result: mineSalt.ReturnType | undefined) => {
|
|
423
|
+
if (settled) return
|
|
424
|
+
settled = true
|
|
425
|
+
value.signal?.removeEventListener('abort', onAbort)
|
|
426
|
+
terminateAll()
|
|
427
|
+
resolve(result)
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const fail = (error: unknown) => {
|
|
431
|
+
if (settled) return
|
|
432
|
+
settled = true
|
|
433
|
+
value.signal?.removeEventListener('abort', onAbort)
|
|
434
|
+
terminateAll()
|
|
435
|
+
reject(
|
|
436
|
+
error instanceof Error
|
|
437
|
+
? error
|
|
438
|
+
: new Errors.BaseError('Failed to mine virtual master salt.'),
|
|
439
|
+
)
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const onMessage = (msg: VirtualMasterPool.Message) => {
|
|
443
|
+
if (settled) return
|
|
444
|
+
switch (msg.type) {
|
|
445
|
+
case 'found':
|
|
446
|
+
succeed(msg.result as mineSalt.ReturnType)
|
|
447
|
+
return
|
|
448
|
+
case 'progress':
|
|
449
|
+
attempts += msg.attempts
|
|
450
|
+
emitProgress()
|
|
451
|
+
return
|
|
452
|
+
case 'done':
|
|
453
|
+
completedWorkers++
|
|
454
|
+
if (completedWorkers === value.workerCount) succeed(undefined)
|
|
455
|
+
return
|
|
456
|
+
case 'error':
|
|
457
|
+
fail(new Errors.BaseError(msg.message))
|
|
458
|
+
return
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const onAbort = () => fail(getAbortError(value.signal))
|
|
463
|
+
value.signal?.addEventListener('abort', onAbort, { once: true })
|
|
464
|
+
|
|
465
|
+
for (let i = 0; i < value.workerCount; i++) {
|
|
466
|
+
const handle = value.pool.spawn(i, onMessage, fail)
|
|
467
|
+
handles.push(handle)
|
|
468
|
+
handle.postMessage({
|
|
469
|
+
type: 'start',
|
|
470
|
+
address: value.address,
|
|
471
|
+
chunkSize: value.chunkSize,
|
|
472
|
+
count: value.count,
|
|
473
|
+
start: value.start,
|
|
474
|
+
workerCount: value.workerCount,
|
|
475
|
+
workerIndex: i,
|
|
476
|
+
})
|
|
477
|
+
}
|
|
478
|
+
})
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
declare namespace mineSaltWithWorkerPool {
|
|
482
|
+
type Options = {
|
|
483
|
+
/** Resolved master address. */
|
|
484
|
+
address: Address.Address
|
|
485
|
+
/** Salts per chunk before a progress update. */
|
|
486
|
+
chunkSize: number
|
|
487
|
+
/** Total number of salts to try. */
|
|
488
|
+
count: number
|
|
489
|
+
/** Progress callback. */
|
|
490
|
+
onProgress: mineSaltAsync.Parameters['onProgress']
|
|
491
|
+
/** Worker pool to distribute work across. */
|
|
492
|
+
pool: VirtualMasterPool.Pool
|
|
493
|
+
/** AbortSignal for cancellation. */
|
|
494
|
+
signal: AbortSignal | undefined
|
|
495
|
+
/** Starting salt as a hex string. */
|
|
496
|
+
start: Hex.Hex
|
|
497
|
+
/** Number of workers to use. */
|
|
498
|
+
workerCount: number
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Single-threaded chunked fallback when no worker pool is available.
|
|
504
|
+
*
|
|
505
|
+
* @internal
|
|
506
|
+
*/
|
|
507
|
+
// biome-ignore lint/correctness/noUnusedVariables: _
|
|
508
|
+
async function mineSaltAsyncFallback(
|
|
509
|
+
value: mineSaltAsyncFallback.Options,
|
|
510
|
+
): Promise<mineSalt.ReturnType | undefined> {
|
|
511
|
+
const startedAt = Date.now()
|
|
512
|
+
const startBigInt = BigInt(value.start)
|
|
513
|
+
|
|
514
|
+
for (let offset = 0; offset < value.count; offset += value.chunkSize) {
|
|
515
|
+
throwIfAborted(value.signal)
|
|
516
|
+
|
|
517
|
+
const count = Math.min(value.chunkSize, value.count - offset)
|
|
518
|
+
const result = mineSalt({
|
|
519
|
+
address: value.address,
|
|
520
|
+
count,
|
|
521
|
+
start: startBigInt + BigInt(offset),
|
|
522
|
+
})
|
|
523
|
+
|
|
524
|
+
if (value.onProgress) {
|
|
525
|
+
const attempts = Math.min(value.count, offset + count)
|
|
526
|
+
const elapsed = Date.now() - startedAt
|
|
527
|
+
const seconds = elapsed / 1000
|
|
528
|
+
value.onProgress({
|
|
529
|
+
attempts,
|
|
530
|
+
chunkSize: value.chunkSize,
|
|
531
|
+
count: value.count,
|
|
532
|
+
elapsed,
|
|
533
|
+
progress: Math.min(1, attempts / value.count),
|
|
534
|
+
rate: seconds === 0 ? 0 : attempts / seconds,
|
|
535
|
+
workers: 1,
|
|
536
|
+
})
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
if (result) return result
|
|
540
|
+
|
|
541
|
+
// Yield to the event loop between chunks.
|
|
542
|
+
await new Promise<void>((r) => setTimeout(r, 0))
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
return undefined
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
declare namespace mineSaltAsyncFallback {
|
|
549
|
+
type Options = {
|
|
550
|
+
/** Resolved master address. */
|
|
551
|
+
address: Address.Address
|
|
552
|
+
/** Salts per chunk before yielding to the event loop. */
|
|
553
|
+
chunkSize: number
|
|
554
|
+
/** Total number of salts to try. */
|
|
555
|
+
count: number
|
|
556
|
+
/** Progress callback. */
|
|
557
|
+
onProgress: mineSaltAsync.Parameters['onProgress']
|
|
558
|
+
/** AbortSignal for cancellation. */
|
|
559
|
+
signal: AbortSignal | undefined
|
|
560
|
+
/** Starting salt as a hex string. */
|
|
561
|
+
start: Hex.Hex
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
/**
|
|
566
|
+
* Asserts that `workers` is a non-negative safe integer.
|
|
567
|
+
*
|
|
568
|
+
* @internal
|
|
569
|
+
*/
|
|
570
|
+
function assertWorkers(workers: number) {
|
|
571
|
+
if (Number.isSafeInteger(workers) && workers >= 0) return
|
|
572
|
+
throw new Errors.BaseError(
|
|
573
|
+
`Workers "${workers}" is invalid. Expected a non-negative safe integer.`,
|
|
574
|
+
)
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
/**
|
|
578
|
+
* Extracts or creates an error from an `AbortSignal`.
|
|
579
|
+
*
|
|
580
|
+
* @internal
|
|
581
|
+
*/
|
|
582
|
+
function getAbortError(signal?: AbortSignal): Error {
|
|
583
|
+
const reason = signal?.reason
|
|
584
|
+
if (reason instanceof Error) return reason
|
|
585
|
+
return new Errors.BaseError('The operation was aborted.')
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Returns the default number of workers for the current platform.
|
|
590
|
+
*
|
|
591
|
+
* @internal
|
|
592
|
+
*/
|
|
593
|
+
function getDefaultWorkerCount(): number {
|
|
594
|
+
if (typeof navigator !== 'undefined') {
|
|
595
|
+
const c = navigator.hardwareConcurrency
|
|
596
|
+
if (c && c > 1) return c - 1
|
|
597
|
+
}
|
|
598
|
+
return 1
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
/**
|
|
602
|
+
* Throws the signal's abort reason if the signal is aborted.
|
|
603
|
+
*
|
|
604
|
+
* @internal
|
|
605
|
+
*/
|
|
606
|
+
function throwIfAborted(signal?: AbortSignal) {
|
|
607
|
+
if (!signal?.aborted) return
|
|
608
|
+
throw getAbortError(signal)
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Returns `true` if the first 4 bytes of a hash are zero.
|
|
613
|
+
*
|
|
614
|
+
* @internal
|
|
615
|
+
*/
|
|
238
616
|
function hasProofOfWork(hash: Bytes.Bytes): boolean {
|
|
239
617
|
return hash[0] === 0 && hash[1] === 0 && hash[2] === 0 && hash[3] === 0
|
|
240
618
|
}
|
|
241
619
|
|
|
620
|
+
/**
|
|
621
|
+
* Asserts that `count` is a positive safe integer.
|
|
622
|
+
*
|
|
623
|
+
* @internal
|
|
624
|
+
*/
|
|
242
625
|
function assertCount(count: number) {
|
|
243
626
|
if (Number.isSafeInteger(count) && count > 0) return
|
|
244
627
|
|
|
@@ -247,6 +630,11 @@ function assertCount(count: number) {
|
|
|
247
630
|
)
|
|
248
631
|
}
|
|
249
632
|
|
|
633
|
+
/**
|
|
634
|
+
* Increments a big-endian byte array by one. Returns `false` on overflow.
|
|
635
|
+
*
|
|
636
|
+
* @internal
|
|
637
|
+
*/
|
|
250
638
|
function increment(bytes: Bytes.Bytes): boolean {
|
|
251
639
|
for (let i = bytes.length - 1; i >= 0; i--) {
|
|
252
640
|
const value = bytes[i]!
|
|
@@ -262,6 +650,11 @@ function increment(bytes: Bytes.Bytes): boolean {
|
|
|
262
650
|
return false
|
|
263
651
|
}
|
|
264
652
|
|
|
653
|
+
/**
|
|
654
|
+
* Resolves a Tempo or hex address, validates it as a valid master.
|
|
655
|
+
*
|
|
656
|
+
* @internal
|
|
657
|
+
*/
|
|
265
658
|
function resolveAddress(address: string): Address.Address {
|
|
266
659
|
const resolved = TempoAddress.resolve(address as TempoAddress.Address)
|
|
267
660
|
Address.assert(resolved, { strict: false })
|
|
@@ -269,6 +662,11 @@ function resolveAddress(address: string): Address.Address {
|
|
|
269
662
|
return resolved
|
|
270
663
|
}
|
|
271
664
|
|
|
665
|
+
/**
|
|
666
|
+
* Throws if the address is zero, virtual, or a TIP-20 token.
|
|
667
|
+
*
|
|
668
|
+
* @internal
|
|
669
|
+
*/
|
|
272
670
|
function assertValidMasterAddress(address: Address.Address) {
|
|
273
671
|
const normalized = address.toLowerCase()
|
|
274
672
|
|
|
@@ -288,10 +686,20 @@ function assertValidMasterAddress(address: Address.Address) {
|
|
|
288
686
|
)
|
|
289
687
|
}
|
|
290
688
|
|
|
689
|
+
/**
|
|
690
|
+
* Converts a salt to a fixed-size byte array.
|
|
691
|
+
*
|
|
692
|
+
* @internal
|
|
693
|
+
*/
|
|
291
694
|
function toFixedBytes(value: Salt, size: number): Bytes.Bytes {
|
|
292
695
|
return Bytes.fromHex(toFixedHex(value, size))
|
|
293
696
|
}
|
|
294
697
|
|
|
698
|
+
/**
|
|
699
|
+
* Converts a salt to a zero-padded hex string of the given size.
|
|
700
|
+
*
|
|
701
|
+
* @internal
|
|
702
|
+
*/
|
|
295
703
|
function toFixedHex(value: Salt, size: number): Hex.Hex {
|
|
296
704
|
if (typeof value === 'number' || typeof value === 'bigint')
|
|
297
705
|
return Hex.fromNumber(value, { size })
|
package/tempo/index.ts
CHANGED
|
@@ -131,6 +131,7 @@ export * as PoolId from './PoolId.js'
|
|
|
131
131
|
*
|
|
132
132
|
* @example
|
|
133
133
|
* ```ts twoslash
|
|
134
|
+
* import 'ox/window'
|
|
134
135
|
* import { Provider, RpcSchema } from 'ox'
|
|
135
136
|
* import { RpcSchemaTempo } from 'ox/tempo'
|
|
136
137
|
*
|
|
@@ -407,11 +408,12 @@ export * as VirtualAddress from './VirtualAddress.js'
|
|
|
407
408
|
*
|
|
408
409
|
* @example
|
|
409
410
|
* ```ts twoslash
|
|
411
|
+
* import { Address, Hex } from 'ox'
|
|
410
412
|
* import { VirtualMaster } from 'ox/tempo'
|
|
411
413
|
*
|
|
412
414
|
* const registration = {
|
|
413
|
-
* address: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
|
|
414
|
-
* salt: '0x00000000000000000000000000000000000000000000000000000000abf52baf',
|
|
415
|
+
* address: Address.from('0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'),
|
|
416
|
+
* salt: Hex.from('0x00000000000000000000000000000000000000000000000000000000abf52baf'),
|
|
415
417
|
* }
|
|
416
418
|
*
|
|
417
419
|
* const registrationHash = VirtualMaster.getRegistrationHash(registration) // keccak256(address || salt)
|
|
@@ -421,6 +423,7 @@ export * as VirtualAddress from './VirtualAddress.js'
|
|
|
421
423
|
* @category Reference
|
|
422
424
|
*/
|
|
423
425
|
export * as VirtualMaster from './VirtualMaster.js'
|
|
426
|
+
|
|
424
427
|
/**
|
|
425
428
|
* Zone ID utilities for converting between zone IDs and zone chain IDs.
|
|
426
429
|
*
|