@solarpunkltd/swarm-id 0.0.0-stage → 0.8.1
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/LICENSE +202 -0
- package/README.md +89 -2
- package/dist/bus/account-bus.d.ts +104 -0
- package/dist/bus/account-delta.d.ts +52 -0
- package/dist/bus/bus-context.d.ts +7 -0
- package/dist/bus/envelope.d.ts +9 -0
- package/dist/bus/messages.d.ts +241 -0
- package/dist/bus/presence.d.ts +62 -0
- package/dist/bus/signaling-transport.d.ts +79 -0
- package/dist/chunk/bmt.d.ts +16 -0
- package/dist/chunk/cac.d.ts +17 -0
- package/dist/chunk/constants.d.ts +10 -0
- package/dist/chunk/encrypted-cac.d.ts +47 -0
- package/dist/chunk/encryption.d.ts +85 -0
- package/dist/chunk/index.d.ts +5 -0
- package/dist/chunks/chat-binding-DO5aGhPl.js +23 -0
- package/dist/chunks/chat-binding-DO5aGhPl.js.map +1 -0
- package/dist/errors.d.ts +35 -0
- package/dist/index.d.ts +27 -0
- package/dist/internal.d.ts +49 -0
- package/dist/internal.esm.js +2 -0
- package/dist/internal.esm.js.map +1 -0
- package/dist/proxy/act/act.d.ts +58 -0
- package/dist/proxy/act/crypto.d.ts +46 -0
- package/dist/proxy/act/grantee-list.d.ts +81 -0
- package/dist/proxy/act/history.d.ts +182 -0
- package/dist/proxy/act/index.d.ts +114 -0
- package/dist/proxy/app-stamp-limits.d.ts +62 -0
- package/dist/proxy/chunk-upload-stream.d.ts +37 -0
- package/dist/proxy/chunking.d.ts +15 -0
- package/dist/proxy/collection.d.ts +38 -0
- package/dist/proxy/download-data.d.ts +27 -0
- package/dist/proxy/errors.d.ts +8 -0
- package/dist/proxy/feed-manifest.d.ts +60 -0
- package/dist/proxy/feeds/epochs/async-finder.d.ts +70 -0
- package/dist/proxy/feeds/epochs/epoch.d.ts +79 -0
- package/dist/proxy/feeds/epochs/finder.d.ts +60 -0
- package/dist/proxy/feeds/epochs/index.d.ts +30 -0
- package/dist/proxy/feeds/epochs/types.d.ts +110 -0
- package/dist/proxy/feeds/epochs/updater.d.ts +76 -0
- package/dist/proxy/feeds/epochs/utils.d.ts +21 -0
- package/dist/proxy/feeds/index.d.ts +4 -0
- package/dist/proxy/feeds/sequence/async-finder.d.ts +14 -0
- package/dist/proxy/feeds/sequence/finder.d.ts +11 -0
- package/dist/proxy/feeds/sequence/index.d.ts +22 -0
- package/dist/proxy/feeds/sequence/types.d.ts +82 -0
- package/dist/proxy/feeds/sequence/updater.d.ts +20 -0
- package/dist/proxy/index.d.ts +5 -0
- package/dist/proxy/manifest-builder.d.ts +164 -0
- package/dist/proxy/mantaray.d.ts +76 -0
- package/dist/proxy/soc-restamp-slots.d.ts +48 -0
- package/dist/proxy/stamp-chunk.d.ts +45 -0
- package/dist/proxy/stamp-worker-handler.d.ts +32 -0
- package/dist/proxy/stamp-worker-pool.d.ts +52 -0
- package/dist/proxy/stamp-worker.d.ts +1 -0
- package/dist/proxy/types.d.ts +38 -0
- package/dist/proxy/upload.d.ts +207 -0
- package/dist/schemas.d.ts +671 -0
- package/dist/stamp-worker.iife.js +1 -0
- package/dist/storage/debounced-uploader.d.ts +61 -0
- package/dist/storage/utilization-store.d.ts +113 -0
- package/dist/swarm-id-client.d.ts +1143 -0
- package/dist/swarm-id-proxy.d.ts +1079 -0
- package/dist/swarm-id.esm.js +2 -0
- package/dist/swarm-id.esm.js.map +1 -0
- package/dist/sync/batch-write-coordinator.d.ts +348 -0
- package/dist/sync/device-roster.d.ts +59 -0
- package/dist/sync/device-state.d.ts +199 -0
- package/dist/sync/fold-account-from-swarm.d.ts +53 -0
- package/dist/sync/index.d.ts +11 -0
- package/dist/sync/merge-snapshot.d.ts +57 -0
- package/dist/sync/partition-intent.d.ts +218 -0
- package/dist/sync/partition-lease.d.ts +531 -0
- package/dist/sync/partition-lock.d.ts +218 -0
- package/dist/sync/partition-state.d.ts +287 -0
- package/dist/sync/session-check.d.ts +29 -0
- package/dist/sync/store-interfaces.d.ts +48 -0
- package/dist/sync/sync-account.d.ts +45 -0
- package/dist/sync/timing-constants.d.ts +32 -0
- package/dist/sync/types.d.ts +18 -0
- package/dist/types.d.ts +3042 -0
- package/dist/utils/account-state-snapshot.d.ts +43 -0
- package/dist/utils/active-devices.d.ts +45 -0
- package/dist/utils/avatar.d.ts +30 -0
- package/dist/utils/batch-utilization.d.ts +840 -0
- package/dist/utils/batch-write-lock.d.ts +12 -0
- package/dist/utils/bee-error.d.ts +11 -0
- package/dist/utils/chat-binding.d.ts +113 -0
- package/dist/utils/coalesced-task.d.ts +28 -0
- package/dist/utils/collection.d.ts +6 -0
- package/dist/utils/connection-info.d.ts +11 -0
- package/dist/utils/constants.d.ts +28 -0
- package/dist/utils/device-id.d.ts +109 -0
- package/dist/utils/hex.d.ts +31 -0
- package/dist/utils/json-rpc.d.ts +34 -0
- package/dist/utils/key-derivation.d.ts +124 -0
- package/dist/utils/lock-soc.d.ts +35 -0
- package/dist/utils/postage-contract.d.ts +143 -0
- package/dist/utils/postage-stamp-association.d.ts +39 -0
- package/dist/utils/promise.d.ts +37 -0
- package/dist/utils/sealed-values.d.ts +23 -0
- package/dist/utils/seed-encryption.d.ts +17 -0
- package/dist/utils/site-allow-list.d.ts +54 -0
- package/dist/utils/stamp-lifespan.d.ts +37 -0
- package/dist/utils/storage-managers.d.ts +62 -0
- package/dist/utils/storage-probe.d.ts +40 -0
- package/dist/utils/tag.d.ts +11 -0
- package/dist/utils/ttl.d.ts +120 -0
- package/dist/utils/url.d.ts +55 -0
- package/dist/utils/versioned-storage.d.ts +183 -0
- package/package.json +91 -3
|
@@ -0,0 +1,840 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Batch Utilization Tracking for Swarm Storage
|
|
3
|
+
*
|
|
4
|
+
* Tracks slot usage for mutable postage batches. Each of the 65,536 buckets
|
|
5
|
+
* has a uint32 in-memory counter (`dataCounters[bucket]`) representing the
|
|
6
|
+
* next free slot. The counter is initialised at `DATA_COUNTER_START` so the
|
|
7
|
+
* first slots of every bucket are reserved as headroom for utilisation
|
|
8
|
+
* chunks that may incidentally land there — utilisation and data chunks
|
|
9
|
+
* share the same slot space.
|
|
10
|
+
*
|
|
11
|
+
* Per-chunk encryption keys are derived deterministically from the account's
|
|
12
|
+
* `swarmEncryptionKey` plus the chunk index and a small nonce. The nonce is
|
|
13
|
+
* bumped only when an upload would land in the same bucket as a lower-index
|
|
14
|
+
* chunk in the same save, so the on-chain placement of utilisation chunks is
|
|
15
|
+
* tidy and dedup stays stable across re-saves.
|
|
16
|
+
*/
|
|
17
|
+
import { type Stamper, BatchId, type Bee, EthAddress, PrivateKey, type EnvelopeWithBatchId } from "@ethersphere/bee-js";
|
|
18
|
+
import { type ContentAddressedChunk } from "../chunk";
|
|
19
|
+
import type { UtilizationStoreDB } from "../storage/utilization-store";
|
|
20
|
+
/**
|
|
21
|
+
* Thrown by `UtilizationAwareStamper.stamp()` when the partition lease was
|
|
22
|
+
* invalidated (peer took over the partition) between binding and stamping.
|
|
23
|
+
* Callers should surface this as "your partition was reclaimed" — the
|
|
24
|
+
* upload cannot complete and any in-flight chunks would silently overwrite
|
|
25
|
+
* the peer's data if stamped anyway.
|
|
26
|
+
*/
|
|
27
|
+
export declare class PartitionLeaseLostError extends Error {
|
|
28
|
+
constructor(message?: string);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Thrown by `UtilizationAwareStamper.stampDataChunk()` when the address is one
|
|
32
|
+
* the stamper routes to a reserved slot: a lock SOC, a counter or state chunk,
|
|
33
|
+
* or an intent SOC. Those slots hold the account's own bookkeeping, so their
|
|
34
|
+
* stamp is never handed to an app.
|
|
35
|
+
*/
|
|
36
|
+
export declare class ReservedSlotError extends Error {
|
|
37
|
+
constructor(message?: string);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Thrown by `UtilizationAwareStamper.stampDataChunk()` when the chunk's bucket
|
|
41
|
+
* has no slot left in the app slice: the rest of that bucket's lane is kept
|
|
42
|
+
* for the account's own uploads.
|
|
43
|
+
*/
|
|
44
|
+
export declare class AppSliceFullError extends Error {
|
|
45
|
+
constructor(message?: string);
|
|
46
|
+
}
|
|
47
|
+
/** Number of buckets in a postage batch (2^16) */
|
|
48
|
+
export declare const NUM_BUCKETS = 65536;
|
|
49
|
+
/** Bucket depth parameter (determines bucket count) */
|
|
50
|
+
export declare const BUCKET_DEPTH = 16;
|
|
51
|
+
/**
|
|
52
|
+
* Number of partitions the per-bucket slot space is divided into for
|
|
53
|
+
* multi-device sharing of a single postage batch. With K=2, partition 0
|
|
54
|
+
* uses data slots `{DATA_COUNTER_START + 0, DATA_COUNTER_START + 2, …}` and
|
|
55
|
+
* partition 1 uses `{DATA_COUNTER_START + 1, DATA_COUNTER_START + 3, …}`.
|
|
56
|
+
* See docs/Postage-Batch-Partitioning.md.
|
|
57
|
+
*/
|
|
58
|
+
export declare const PARTITION_COUNT = 2;
|
|
59
|
+
/**
|
|
60
|
+
* Share of each bucket's data lane, counted from its top, that only the
|
|
61
|
+
* account's own uploads may use. An app stamp (`stampDataChunk`) is refused
|
|
62
|
+
* once the lane's counter reaches the rest, the app slice. Apps and the
|
|
63
|
+
* account draw from the one per-partition counter, so no slot is ever issued
|
|
64
|
+
* twice, and the slice needs no state of its own: it is read from the counter
|
|
65
|
+
* that is saved, published and handed over already.
|
|
66
|
+
*/
|
|
67
|
+
export declare const ACCOUNT_RESERVED_LANE_SHARE: number;
|
|
68
|
+
/** First data slot index; reserved slots `[0, PARTITION_COUNT)` precede it. */
|
|
69
|
+
export declare const DATA_COUNTER_START = 2;
|
|
70
|
+
export { LEASE_TTL_MS } from "../sync/timing-constants";
|
|
71
|
+
/**
|
|
72
|
+
* How often the holder re-writes its partition lock SOC to bump
|
|
73
|
+
* `leasedUntil`. One third of the TTL gives three "safety net" refresh
|
|
74
|
+
* attempts before a peer would consider the lease expired.
|
|
75
|
+
*/
|
|
76
|
+
export declare const LEASE_REFRESH_MS: number;
|
|
77
|
+
/**
|
|
78
|
+
* Local-clock margin for treating a lease as "could have lapsed". A holder must
|
|
79
|
+
* stop writing (and re-validating before ack) once `now >= leasedUntil - margin`
|
|
80
|
+
* so it ceases before a peer — reading the same `leasedUntil` from the lock SOC
|
|
81
|
+
* — could validly take over. Bounds the write/ack exposure to NTP-level skew,
|
|
82
|
+
* the same assumption the lock protocol already makes. Consumed by the stamper's
|
|
83
|
+
* local-lapse fence and the coordinator's reverse-clobber guard.
|
|
84
|
+
*/
|
|
85
|
+
export declare const LEASE_SKEW_MARGIN_MS = 2000;
|
|
86
|
+
/**
|
|
87
|
+
* How long a holder may go without an upload before it voluntarily yields
|
|
88
|
+
* its partition (writes the release sentinel + publishes its final counter)
|
|
89
|
+
* so a waiting device can take the slot without waiting out the full TTL.
|
|
90
|
+
* The yielded device re-acquires transparently on its next upload. Enables
|
|
91
|
+
* turn-taking among 3+ devices sharing `PARTITION_COUNT` slots.
|
|
92
|
+
*/
|
|
93
|
+
export declare const IDLE_YIELD_MS: number;
|
|
94
|
+
/** Size of each chunk in bytes */
|
|
95
|
+
export declare const CHUNK_SIZE = 4096;
|
|
96
|
+
/**
|
|
97
|
+
* Maximum batch depth at which a uint16 per-bucket counter is sufficient.
|
|
98
|
+
*
|
|
99
|
+
* At depth D the post-stamp counter caps at 2^(D-16). uint16 can hold
|
|
100
|
+
* 0..65535; depth 31 → 32768 (fits), depth 32 → 65536 (overflows).
|
|
101
|
+
* For depth > UINT16_COUNTER_MAX_DEPTH the serializer falls back to uint32.
|
|
102
|
+
*
|
|
103
|
+
* Issue: https://github.com/snaha/swarm-id/issues/243
|
|
104
|
+
*/
|
|
105
|
+
export declare const UINT16_COUNTER_MAX_DEPTH = 31;
|
|
106
|
+
/**
|
|
107
|
+
* Per-batch-depth chunk layout for the utilization state.
|
|
108
|
+
*
|
|
109
|
+
* The on-chunk counter representation switches between uint16 and uint32
|
|
110
|
+
* based on whether the maximum slot index for the given batch depth fits
|
|
111
|
+
* in 16 bits. Each utilization chunk is always CHUNK_SIZE bytes; the
|
|
112
|
+
* number of buckets it covers — and thus the number of chunks needed —
|
|
113
|
+
* doubles when counters shrink from 4 bytes to 2.
|
|
114
|
+
*/
|
|
115
|
+
export interface ChunkLayout {
|
|
116
|
+
/** Bytes per per-bucket counter on disk / on the wire */
|
|
117
|
+
counterByteSize: 2 | 4;
|
|
118
|
+
/** Number of buckets packed into a single CHUNK_SIZE-byte chunk */
|
|
119
|
+
bucketsPerChunk: number;
|
|
120
|
+
/** Total number of utilization chunks for one batch */
|
|
121
|
+
numUtilizationChunks: number;
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Pick the on-disk chunk layout for a given batch depth.
|
|
125
|
+
*/
|
|
126
|
+
export declare function getChunkLayout(batchDepth: number): ChunkLayout;
|
|
127
|
+
/**
|
|
128
|
+
* Metadata for a single utilization chunk
|
|
129
|
+
*/
|
|
130
|
+
export interface ChunkMetadata {
|
|
131
|
+
/** Chunk index within the depth-dependent layout */
|
|
132
|
+
index: number;
|
|
133
|
+
/**
|
|
134
|
+
* Content hash / CAC reference (same thing for content-addressed chunks)
|
|
135
|
+
* Empty string means never uploaded
|
|
136
|
+
*/
|
|
137
|
+
contentHash: string;
|
|
138
|
+
/** Last upload timestamp */
|
|
139
|
+
lastUpload: number;
|
|
140
|
+
/** Whether this chunk needs uploading */
|
|
141
|
+
dirty: boolean;
|
|
142
|
+
/**
|
|
143
|
+
* Encryption-key nonce that produced this chunk's `contentHash`. Used as
|
|
144
|
+
* the starting point for the next save's bucket-collision search so
|
|
145
|
+
* unchanged plaintexts reuse the same key (and skip re-upload).
|
|
146
|
+
*/
|
|
147
|
+
nonce: number;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Utilization state for a postage batch
|
|
151
|
+
*
|
|
152
|
+
* Utilization data is stored as 32 chunks (uint16 codec, depth ≤ 31) or
|
|
153
|
+
* 64 chunks (uint32 codec, depth ≥ 32) on Swarm, with IndexedDB caching
|
|
154
|
+
* for performance.
|
|
155
|
+
*/
|
|
156
|
+
export interface BatchUtilizationState {
|
|
157
|
+
/** Batch ID this state belongs to */
|
|
158
|
+
batchId: BatchId;
|
|
159
|
+
/** Batch depth — determines on-disk chunk layout (uint16 vs uint32) */
|
|
160
|
+
batchDepth: number;
|
|
161
|
+
/**
|
|
162
|
+
* Per-partition local counter (65,536 entries) — the 0-based `j` in
|
|
163
|
+
* `slot = partitionCount + partition + partitionCount·j` for the partition
|
|
164
|
+
* this state represents (partition 0 in single-device mode). This is the
|
|
165
|
+
* single counter of record: slot selection, persistence, reporting, and
|
|
166
|
+
* cross-device handoff all derive from it. Always uint32 in memory
|
|
167
|
+
* regardless of the on-disk codec; narrowing happens at the serialize
|
|
168
|
+
* boundary.
|
|
169
|
+
*/
|
|
170
|
+
dataCounters: Uint32Array;
|
|
171
|
+
/** Metadata for each utilization chunk (32 for uint16, 64 for uint32) */
|
|
172
|
+
chunks: ChunkMetadata[];
|
|
173
|
+
/** Last sync timestamp */
|
|
174
|
+
lastSync: number;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Calculate which bucket a chunk belongs to based on its address.
|
|
178
|
+
* Uses the first 2 bytes of the chunk address as a big-endian uint16.
|
|
179
|
+
*
|
|
180
|
+
* This matches bee-js Stamper implementation.
|
|
181
|
+
*
|
|
182
|
+
* @param chunkAddress - The chunk's content address (32 bytes)
|
|
183
|
+
* @returns Bucket index (0-65535)
|
|
184
|
+
*/
|
|
185
|
+
export declare function toBucket(chunkAddress: Uint8Array): number;
|
|
186
|
+
/**
|
|
187
|
+
* Calculate which utilization chunk a bucket belongs to.
|
|
188
|
+
*
|
|
189
|
+
* @param bucketIndex - Bucket index (0-65535)
|
|
190
|
+
* @param batchDepth - Batch depth, drives the chunk layout
|
|
191
|
+
* @returns Chunk index in the layout for this depth
|
|
192
|
+
*/
|
|
193
|
+
export declare function getChunkIndexForBucket(bucketIndex: number, batchDepth: number): number;
|
|
194
|
+
/**
|
|
195
|
+
* Extract a CHUNK_SIZE-byte chunk from the dataCounters array using the
|
|
196
|
+
* codec implied by the batch depth.
|
|
197
|
+
*
|
|
198
|
+
* @param dataCounters - Full array of 65,536 counters (kept as Uint32Array
|
|
199
|
+
* in memory regardless of serialization size)
|
|
200
|
+
* @param chunkIndex - Index of chunk to extract
|
|
201
|
+
* @param batchDepth - Batch depth, drives the chunk layout
|
|
202
|
+
* @returns CHUNK_SIZE byte Uint8Array containing serialized counters
|
|
203
|
+
*/
|
|
204
|
+
export declare function extractChunk(dataCounters: Uint32Array, chunkIndex: number, batchDepth: number): Uint8Array;
|
|
205
|
+
/**
|
|
206
|
+
* Merge a CHUNK_SIZE-byte chunk back into the dataCounters array using the
|
|
207
|
+
* codec implied by the batch depth.
|
|
208
|
+
*
|
|
209
|
+
* @param dataCounters - Full array of 65,536 counters (modified in place)
|
|
210
|
+
* @param chunkIndex - Index of chunk to merge
|
|
211
|
+
* @param chunkData - CHUNK_SIZE byte Uint8Array containing serialized counters
|
|
212
|
+
* @param batchDepth - Batch depth, drives the chunk layout
|
|
213
|
+
*/
|
|
214
|
+
export declare function mergeChunk(dataCounters: Uint32Array, chunkIndex: number, chunkData: Uint8Array, batchDepth: number): void;
|
|
215
|
+
/**
|
|
216
|
+
* Tracks which utilization chunks have been modified and need uploading
|
|
217
|
+
*/
|
|
218
|
+
export declare class DirtyChunkTracker {
|
|
219
|
+
private dirtyChunks;
|
|
220
|
+
private batchDepth;
|
|
221
|
+
constructor(batchDepth: number);
|
|
222
|
+
/**
|
|
223
|
+
* Mark a bucket as dirty (marks its containing chunk)
|
|
224
|
+
* @param bucketIndex - Bucket index (0-65535)
|
|
225
|
+
*/
|
|
226
|
+
markDirty(bucketIndex: number): void;
|
|
227
|
+
/**
|
|
228
|
+
* Mark a chunk as clean (uploaded successfully)
|
|
229
|
+
* @param chunkIndex - Chunk index
|
|
230
|
+
*/
|
|
231
|
+
markClean(chunkIndex: number): void;
|
|
232
|
+
/**
|
|
233
|
+
* Get array of dirty chunk indices
|
|
234
|
+
* @returns Sorted array of chunk indices that need uploading
|
|
235
|
+
*/
|
|
236
|
+
getDirtyChunks(): number[];
|
|
237
|
+
/**
|
|
238
|
+
* Check if there are any dirty chunks
|
|
239
|
+
* @returns true if there are chunks waiting to be uploaded
|
|
240
|
+
*/
|
|
241
|
+
hasDirtyChunks(): boolean;
|
|
242
|
+
/**
|
|
243
|
+
* Clear all dirty markers
|
|
244
|
+
*/
|
|
245
|
+
clear(): void;
|
|
246
|
+
/**
|
|
247
|
+
* Get number of dirty chunks
|
|
248
|
+
* @returns Count of chunks waiting to be uploaded
|
|
249
|
+
*/
|
|
250
|
+
get count(): number;
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Resolved per-chunk-index encryption material.
|
|
254
|
+
*
|
|
255
|
+
* `nonce` is the smallest non-negative integer that placed the chunk in a
|
|
256
|
+
* bucket distinct from every chunk with a lower `chunkIndex` during the
|
|
257
|
+
* current save. `key` is the HMAC-derived 32-byte key used to produce the
|
|
258
|
+
* encrypted-CAC address (and therefore the chunk's bucket).
|
|
259
|
+
*/
|
|
260
|
+
export interface UtilizationChunkKey {
|
|
261
|
+
key: Uint8Array;
|
|
262
|
+
nonce: number;
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Derive the per-chunk encryption key used for utilisation chunks.
|
|
266
|
+
*
|
|
267
|
+
* `chunkKey = HMAC-SHA256(swarmEncryptionKey, "swarm-id-util-chunk-v1" || batchId || chunkIndex || nonce)`
|
|
268
|
+
*
|
|
269
|
+
* Same inputs → same key, so dedup at the upload path stays stable across
|
|
270
|
+
* re-saves whenever neither the plaintext nor the chosen nonce changes.
|
|
271
|
+
*/
|
|
272
|
+
export declare function deriveUtilizationChunkKey(swarmEncryptionKey: Uint8Array, batchId: BatchId, chunkIndex: number, nonce: number): Promise<Uint8Array>;
|
|
273
|
+
/**
|
|
274
|
+
* Upload an encrypted utilization chunk to Swarm as CAC
|
|
275
|
+
*
|
|
276
|
+
* Architecture: Just upload encrypted chunk data as CAC (immutable)
|
|
277
|
+
*
|
|
278
|
+
* @param bee - Bee client instance
|
|
279
|
+
* @param stamper - Stamper for signing
|
|
280
|
+
* @param data - Chunk data to upload (4KB)
|
|
281
|
+
* @param encryptionKey - Encryption key (32 bytes)
|
|
282
|
+
* @returns CAC reference
|
|
283
|
+
*/
|
|
284
|
+
export declare function uploadUtilizationChunk(bee: Bee, stamper: Stamper, data: Uint8Array, encryptionKey: Uint8Array): Promise<Uint8Array>;
|
|
285
|
+
/**
|
|
286
|
+
* Serialize Uint32Array to bytes (little-endian)
|
|
287
|
+
*/
|
|
288
|
+
export declare function serializeUint32Array(arr: Uint32Array): Uint8Array;
|
|
289
|
+
/**
|
|
290
|
+
* Deserialize bytes to Uint32Array (little-endian)
|
|
291
|
+
*/
|
|
292
|
+
export declare function deserializeUint32Array(bytes: Uint8Array): Uint32Array;
|
|
293
|
+
/**
|
|
294
|
+
* Serialize a Uint32Array to bytes as little-endian uint16 values.
|
|
295
|
+
*
|
|
296
|
+
* Counters exceeding uint16 throw — callers must use the uint32 codec
|
|
297
|
+
* (i.e. batchDepth >= UINT16_COUNTER_MAX_DEPTH + 1) for those depths.
|
|
298
|
+
*/
|
|
299
|
+
export declare function serializeUint16Array(arr: Uint32Array): Uint8Array;
|
|
300
|
+
/**
|
|
301
|
+
* Deserialize little-endian uint16 bytes back into a Uint32Array (the
|
|
302
|
+
* in-memory representation stays uint32 even when the on-disk codec is
|
|
303
|
+
* narrower).
|
|
304
|
+
*/
|
|
305
|
+
export declare function deserializeUint16Array(bytes: Uint8Array): Uint32Array;
|
|
306
|
+
/**
|
|
307
|
+
* Initialize a new batch utilization state
|
|
308
|
+
*
|
|
309
|
+
* Seeds `dataCounters[bucket]` at `DATA_COUNTER_START` for every bucket so
|
|
310
|
+
* the first stamping operations naturally skip the headroom slots.
|
|
311
|
+
*
|
|
312
|
+
* With 65,536 buckets and ~32–64 utilization chunks per save, the
|
|
313
|
+
* probability of any bucket getting 3+ utilisation chunks in one save is
|
|
314
|
+
* < 10⁻⁷, so a headroom of 2 leaves ample margin.
|
|
315
|
+
*/
|
|
316
|
+
export declare function initializeBatchUtilization(batchId: BatchId, batchDepth: number): BatchUtilizationState;
|
|
317
|
+
/**
|
|
318
|
+
* Calculate max slots per bucket based on batch depth
|
|
319
|
+
*/
|
|
320
|
+
export declare function calculateMaxSlotsPerBucket(batchDepth: number): number;
|
|
321
|
+
/**
|
|
322
|
+
* Check if a bucket has capacity for more chunks.
|
|
323
|
+
*
|
|
324
|
+
* `dataCounter` is the per-partition `j`, so it must be compared against the
|
|
325
|
+
* partition's lane capacity — not the raw slots-per-bucket. Comparing against
|
|
326
|
+
* the latter let `j` run past the lane and only surfaced as a mid-stamp bee-js
|
|
327
|
+
* throw with counters already incremented (#416).
|
|
328
|
+
*/
|
|
329
|
+
export declare function hasBucketCapacity(dataCounter: number, batchDepth: number, partitionCount?: number): boolean;
|
|
330
|
+
/**
|
|
331
|
+
* Physical slot index for partition `partition`'s `j`-th data chunk in a
|
|
332
|
+
* bucket: `partitionCount + partition + partitionCount·j`. The first
|
|
333
|
+
* `partitionCount` slots `[0, partitionCount)` are reserved (one per
|
|
334
|
+
* partition, index = partition) for that partition's lock SOC / counter
|
|
335
|
+
* chunk, so data starts at `partitionCount` and the partitions' data lanes
|
|
336
|
+
* interleave without ever colliding. Legacy single-device is `partitionCount
|
|
337
|
+
* = 1, partition = 0` (reserved slot 0, data from slot 1).
|
|
338
|
+
* See docs/Postage-Batch-Partitioning.md.
|
|
339
|
+
*/
|
|
340
|
+
export declare function dataSlot(partition: number, j: number, partitionCount: number): number;
|
|
341
|
+
/**
|
|
342
|
+
* Number of data chunks a single partition may write into one bucket, i.e.
|
|
343
|
+
* the count of distinct `j` values: `floor(slotsPerBucket / partitionCount)
|
|
344
|
+
* - 1` (the `-1` is the partition's reserved slot). Each partition's data
|
|
345
|
+
* lane is independent, so this is the per-partition per-bucket capacity.
|
|
346
|
+
*/
|
|
347
|
+
export declare function partitionCapacity(batchDepth: number, partitionCount: number): number;
|
|
348
|
+
/**
|
|
349
|
+
* Fewest data chunks a partition's bucket lane must hold for a batch to be
|
|
350
|
+
* worth buying. At 1 the first chunk written to a bucket already fills that
|
|
351
|
+
* lane, so the batch reports 100% used while effectively empty (#538); at 3
|
|
352
|
+
* some bucket fills after ~1 MB of uniformly placed chunks, a drive too small
|
|
353
|
+
* to be worth its purchase (#566). 7 (depth 20) holds ~66 MB.
|
|
354
|
+
*/
|
|
355
|
+
export declare const MIN_USABLE_PARTITION_CAPACITY = 4;
|
|
356
|
+
/**
|
|
357
|
+
* Smallest batch depth with a usable data lane: the first depth whose
|
|
358
|
+
* {@link partitionCapacity} reaches {@link MIN_USABLE_PARTITION_CAPACITY}.
|
|
359
|
+
* Searched rather than written out, so it follows BUCKET_DEPTH,
|
|
360
|
+
* PARTITION_COUNT and the reserved-slot term; capacity doubles with every
|
|
361
|
+
* depth, so the search terminates. With BUCKET_DEPTH 16 and PARTITION_COUNT 2
|
|
362
|
+
* it is 20 — depth 17 has no data slot at all, 18 exactly one, 19 three.
|
|
363
|
+
*/
|
|
364
|
+
export declare const MIN_USABLE_BATCH_DEPTH: number;
|
|
365
|
+
/**
|
|
366
|
+
* Load utilization state with cache hierarchy
|
|
367
|
+
*
|
|
368
|
+
* Load order:
|
|
369
|
+
* 1. Try IndexedDB cache (all chunks for the depth's layout)
|
|
370
|
+
* 2. If a bucket has no cached chunk, seed it with a zeroed default
|
|
371
|
+
* 3. If nothing is cached, initialize new state
|
|
372
|
+
*
|
|
373
|
+
* The on-Swarm counter is resumed via the partition-state feed (see
|
|
374
|
+
* `sync/partition-state.ts`), not here — this only reads the local cache.
|
|
375
|
+
*
|
|
376
|
+
* @param batchId - Batch ID
|
|
377
|
+
* @param options - Load options (local IndexedDB cache)
|
|
378
|
+
* @returns Utilization state
|
|
379
|
+
*/
|
|
380
|
+
export declare function loadUtilizationState(batchId: BatchId, batchDepth: number, options: {
|
|
381
|
+
cache: UtilizationStoreDB;
|
|
382
|
+
}): Promise<BatchUtilizationState>;
|
|
383
|
+
/**
|
|
384
|
+
* Save utilization state with incremental upload
|
|
385
|
+
*
|
|
386
|
+
* Only uploads dirty chunks to minimize network traffic.
|
|
387
|
+
* Updates IndexedDB cache with new chunk data.
|
|
388
|
+
*
|
|
389
|
+
* @param state - Current utilization state (modified in place)
|
|
390
|
+
* @param options - Save options
|
|
391
|
+
*/
|
|
392
|
+
export declare function saveUtilizationState(state: BatchUtilizationState, options: {
|
|
393
|
+
bee: Bee;
|
|
394
|
+
stamper: Stamper;
|
|
395
|
+
encryptionKey: Uint8Array;
|
|
396
|
+
cache: UtilizationStoreDB;
|
|
397
|
+
tracker: DirtyChunkTracker;
|
|
398
|
+
reservedBuckets?: ReadonlySet<number>;
|
|
399
|
+
}): Promise<void>;
|
|
400
|
+
/**
|
|
401
|
+
* Update utilization state after writing data chunks
|
|
402
|
+
*
|
|
403
|
+
* This function:
|
|
404
|
+
* 1. Loads current state from the local cache
|
|
405
|
+
* 2. Updates bucket counters for new data chunks
|
|
406
|
+
* 3. Marks affected utilization chunks as dirty
|
|
407
|
+
* 4. Returns state and tracker for later upload
|
|
408
|
+
*
|
|
409
|
+
* @param batchId - Batch ID
|
|
410
|
+
* @param dataChunks - Data chunks that were written
|
|
411
|
+
* @param batchDepth - Batch depth parameter
|
|
412
|
+
* @param options - Load options (local IndexedDB cache)
|
|
413
|
+
* @returns Updated state and dirty chunk tracker
|
|
414
|
+
*/
|
|
415
|
+
export declare function updateAfterWrite(batchId: BatchId, dataChunks: ContentAddressedChunk[], batchDepth: number, options: {
|
|
416
|
+
cache: UtilizationStoreDB;
|
|
417
|
+
}): Promise<{
|
|
418
|
+
state: BatchUtilizationState;
|
|
419
|
+
tracker: DirtyChunkTracker;
|
|
420
|
+
}>;
|
|
421
|
+
/**
|
|
422
|
+
* Calculate current utilization fraction for a batch
|
|
423
|
+
*
|
|
424
|
+
* @param state - Current utilization state
|
|
425
|
+
* @param batchDepth - Batch depth parameter
|
|
426
|
+
* @returns Utilization as decimal fraction (0-1)
|
|
427
|
+
*/
|
|
428
|
+
export declare function calculateUtilization(state: BatchUtilizationState, batchDepth: number, partitionCount?: number): number;
|
|
429
|
+
/**
|
|
430
|
+
* Decides which slot of its bucket a chunk is stamped at. Assigning a slot
|
|
431
|
+
* spends it, so a stamp has exactly one assigner however many things can sign.
|
|
432
|
+
*/
|
|
433
|
+
export interface SlotAssigner {
|
|
434
|
+
readonly batchId: BatchId;
|
|
435
|
+
assignSlot(chunkAddress: Uint8Array): number;
|
|
436
|
+
}
|
|
437
|
+
/**
|
|
438
|
+
* Stamper that owns slot assignment for a postage batch
|
|
439
|
+
*
|
|
440
|
+
* - Loads the per-bucket counters from cached utilization data on creation
|
|
441
|
+
* - `assignSlot()` decides where a chunk's stamp goes: a reserved slot for the
|
|
442
|
+
* lock / intent SOCs and utilisation chunks, otherwise the next slot of this
|
|
443
|
+
* device's partition lane, behind the lease fence
|
|
444
|
+
* - `stamp()` is `assignSlot()` plus core-sdk's stateless signing. Anything
|
|
445
|
+
* that signs elsewhere (`StampWorkerPool`) takes its slot from `assignSlot()`
|
|
446
|
+
* too, so there is one slot owner whoever signs
|
|
447
|
+
* - Provides a flush() method to persist updates back to cache
|
|
448
|
+
*/
|
|
449
|
+
export declare class UtilizationAwareStamper implements Stamper, SlotAssigner {
|
|
450
|
+
private utilizationState;
|
|
451
|
+
private cache;
|
|
452
|
+
private readonly encryptionKey;
|
|
453
|
+
private dirty;
|
|
454
|
+
private dirtyBuckets;
|
|
455
|
+
/**
|
|
456
|
+
* Partition this device holds within the shared postage batch. `undefined`
|
|
457
|
+
* means the legacy single-device path, stamped as partition 0 of 1: the
|
|
458
|
+
* behaviour for every account created before the partition-lease shipped.
|
|
459
|
+
*/
|
|
460
|
+
private partition;
|
|
461
|
+
/**
|
|
462
|
+
* Number of partitions the slot space is divided into. `1` is the
|
|
463
|
+
* legacy/no-partitioning value and goes hand-in-hand with `partition`
|
|
464
|
+
* being `undefined`.
|
|
465
|
+
*/
|
|
466
|
+
private partitionCountValue;
|
|
467
|
+
/**
|
|
468
|
+
* Per-account lock-SOC addresses (one per partition). When `stamp()` sees
|
|
469
|
+
* a chunk whose address matches one of these, it routes the write to the
|
|
470
|
+
* lock SOC's reserved slot (= the partition index) within the same bucket,
|
|
471
|
+
* bypassing the partition data-slot formula and the local counter bump.
|
|
472
|
+
* Overstamping the same SOC at the same slot does not consume new slot
|
|
473
|
+
* budget, so the heartbeat cadence is sustainable.
|
|
474
|
+
*
|
|
475
|
+
* Populated by `bindLockSocs()` once per stamper lifetime (deterministic
|
|
476
|
+
* from accountId + partitionCount + owner). Independent of `partition`
|
|
477
|
+
* binding so lock-SOC writes work both before and after `bindPartition`.
|
|
478
|
+
*/
|
|
479
|
+
private lockSocs;
|
|
480
|
+
/**
|
|
481
|
+
* Hex addresses of the utilisation (counter) chunks for the in-flight save,
|
|
482
|
+
* mapped to the reserved SLOT each must overstamp. `stamp()` routes these to
|
|
483
|
+
* that slot instead of a data slot. The slot is recorded at marking time
|
|
484
|
+
* (not read from `this.partition` at stamp time) because the teardown
|
|
485
|
+
* release publishes through an UNBOUND stamper — `partition` is already
|
|
486
|
+
* undefined there, and falling back to slot 0 would overstamp the OTHER
|
|
487
|
+
* partition's reserved chunks. Unlike the lock SOC, a counter chunk's
|
|
488
|
+
* address changes whenever the counter changes, so writers register the
|
|
489
|
+
* current set via `markReservedUtilizationChunk` and clear it with
|
|
490
|
+
* `clearReservedUtilizationChunks`.
|
|
491
|
+
*/
|
|
492
|
+
private reservedUtilizationChunks;
|
|
493
|
+
/**
|
|
494
|
+
* The per-partition intent SOC for an in-flight intent round (Phase 2 —
|
|
495
|
+
* `partition-intent.ts`), mapped to the reserved SLOT it must overstamp (the
|
|
496
|
+
* contended partition index). Routed like the lock SOC / utilisation chunks:
|
|
497
|
+
* `stamp()` places it in the partition's reserved slot, so it NEVER lands in
|
|
498
|
+
* a data lane (`>= DATA_COUNTER_START`) and cannot overstamp user data.
|
|
499
|
+
* Single-entry and self-clearing (set immediately before the upload, cleared
|
|
500
|
+
* in a `finally`), so it stays isolated from the `reservedUtilizationChunks`
|
|
501
|
+
* save lifecycle and never grows. See `reserveIntentSocSlot`.
|
|
502
|
+
*/
|
|
503
|
+
private intentSoc;
|
|
504
|
+
/**
|
|
505
|
+
* Promise-chain mutex serializing {@link withIntentSocSlot} critical sections.
|
|
506
|
+
* The intent / occupancy / state-pointer SOC writes all share the single
|
|
507
|
+
* {@link intentSoc} reservation, but their callers run concurrently (the
|
|
508
|
+
* off-lock refresh-tick presence/occupancy beacon vs. an under-lock upload's
|
|
509
|
+
* state-pointer publish). Without this, the two reserve→stamp windows overlap:
|
|
510
|
+
* the second `reserveIntentSocSlot` clobbers the first's address, so the
|
|
511
|
+
* first `stamp()` no longer matches `intentSoc` and falls through to a DATA
|
|
512
|
+
* slot — consuming data budget and bumping the counter under the very publish
|
|
513
|
+
* that is diffing it. The mutex makes each writer wait for the previous to
|
|
514
|
+
* clear its reservation.
|
|
515
|
+
*/
|
|
516
|
+
private intentSocLock;
|
|
517
|
+
/**
|
|
518
|
+
* Circuit breaker for in-flight uploads. Flipped to `true` when the proxy
|
|
519
|
+
* detects displacement on a refresh tick (or upload-start lease check), or
|
|
520
|
+
* tears the lease down; subsequent data `stamp()` calls throw
|
|
521
|
+
* `PartitionLeaseLostError` to abort the upload cleanly instead of silently
|
|
522
|
+
* corrupting the peer's slot space. Outlives `unbindPartition` — an unbound
|
|
523
|
+
* stamper would otherwise stamp the rest of that upload at legacy slots —
|
|
524
|
+
* and is cleared only by the next `bindPartition`.
|
|
525
|
+
*/
|
|
526
|
+
private leaseStale;
|
|
527
|
+
/**
|
|
528
|
+
* Wall-clock ms this device's lease is valid until (from the lock SOC's
|
|
529
|
+
* `leasedUntil`, pushed by the coordinator on bind and every renewal). While a
|
|
530
|
+
* multi-chunk upload runs, each `stamp()` re-checks it locally (no network) and
|
|
531
|
+
* throws once `Date.now()` passes it (minus a skew margin), so an un-renewable
|
|
532
|
+
* holder stops writing mid-op the instant its own clock says the lease lapsed —
|
|
533
|
+
* closing the "ack-safe, not write-safe" residual to the irreducible skew bound
|
|
534
|
+
* (Postage-Batch-Partitioning.md §12). `undefined` = not fenced (legacy mode,
|
|
535
|
+
* or between bind and the coordinator's first push).
|
|
536
|
+
*/
|
|
537
|
+
private leaseValidUntil;
|
|
538
|
+
/**
|
|
539
|
+
* This device's clock, injectable for tests (#385). The local-lapse fence
|
|
540
|
+
* compares it against `leaseValidUntil`, so a test MUST drive the stamper with
|
|
541
|
+
* the SAME clock it gives the `PartitionLease` (whose `leasedUntil` feeds
|
|
542
|
+
* `leaseValidUntil`) — otherwise the two disagree and the fence mis-fires.
|
|
543
|
+
* Defaults to `Date.now()` (production: one wall clock everywhere).
|
|
544
|
+
*/
|
|
545
|
+
private readonly now;
|
|
546
|
+
/**
|
|
547
|
+
* Per-partition buckets of the latest published partition-state chunks.
|
|
548
|
+
* Utilisation saves must not place chunks in these buckets (same reserved
|
|
549
|
+
* slot → overstamp → the published resume point is evicted from the
|
|
550
|
+
* reserve). Mirrors `BatchMetadata.stateChunkBuckets`; restored on create.
|
|
551
|
+
*/
|
|
552
|
+
private protectedStateBuckets;
|
|
553
|
+
readonly signer: PrivateKey;
|
|
554
|
+
readonly batchId: BatchId;
|
|
555
|
+
readonly depth: number;
|
|
556
|
+
readonly maxSlot: number;
|
|
557
|
+
/**
|
|
558
|
+
* The next physical data slot per bucket, as core-sdk's `Stamper` means it.
|
|
559
|
+
* A snapshot: the counters of record are lane-relative (`dataCounters`), so
|
|
560
|
+
* writing here moves nothing — take a slot with {@link assignSlot}.
|
|
561
|
+
* Allocates a fresh NUM_BUCKETS-entry array per call.
|
|
562
|
+
*/
|
|
563
|
+
get buckets(): Uint32Array;
|
|
564
|
+
/** Current partition (undefined in single-device legacy mode). */
|
|
565
|
+
get currentPartition(): number | undefined;
|
|
566
|
+
/** Total partition count (1 in legacy mode). */
|
|
567
|
+
get partitionCount(): number;
|
|
568
|
+
/**
|
|
569
|
+
* Per-bucket local stamping counter, exposed so the lease orchestrator
|
|
570
|
+
* can publish it to the partition-state feed on release. Returns
|
|
571
|
+
* undefined in legacy mode.
|
|
572
|
+
*/
|
|
573
|
+
getLocalCounter(): Uint32Array | undefined;
|
|
574
|
+
/**
|
|
575
|
+
* Buckets that contain a per-partition lock SOC. External callers that
|
|
576
|
+
* persist utilisation chunks (e.g. `saveUtilizationState` from the sync
|
|
577
|
+
* path) should treat these as already claimed so utilisation-chunk key
|
|
578
|
+
* search avoids dropping a chunk on the lock SOC's bucket.
|
|
579
|
+
*/
|
|
580
|
+
getLockSocBuckets(): ReadonlySet<number>;
|
|
581
|
+
/**
|
|
582
|
+
* All buckets a utilisation save must avoid: the lock-SOC buckets plus the
|
|
583
|
+
* buckets of the latest published partition-state chunks for the partition
|
|
584
|
+
* this stamper writes reserved slots in (`partition ?? 0`). A utilisation
|
|
585
|
+
* chunk landing in a published state chunk's bucket would overstamp the
|
|
586
|
+
* same reserved slot and evict the published chunk from the reserve —
|
|
587
|
+
* destroying the resume point a later takeover depends on.
|
|
588
|
+
*/
|
|
589
|
+
getProtectedBuckets(): ReadonlySet<number>;
|
|
590
|
+
/**
|
|
591
|
+
* Buckets occupied by this stamper's current CLEAN utilisation chunks.
|
|
592
|
+
* The partition-state publish avoids them so a randomly-keyed state chunk
|
|
593
|
+
* does not evict a live utilisation chunk from the reserve.
|
|
594
|
+
*/
|
|
595
|
+
getCleanChunkBuckets(): ReadonlySet<number>;
|
|
596
|
+
/**
|
|
597
|
+
* Record the buckets occupied by the latest published partition-state
|
|
598
|
+
* chunks for `partition` (in memory + persisted in the batch metadata, so
|
|
599
|
+
* a reload keeps avoiding them). Called after every successful publish
|
|
600
|
+
* (`PartitionLease.release`) and after every successful state read
|
|
601
|
+
* (`claimPartition`) — the set is replaced wholesale; only the latest
|
|
602
|
+
* publish needs protection, superseded chunks are dead.
|
|
603
|
+
*/
|
|
604
|
+
setProtectedStateBuckets(partition: number, buckets: number[]): Promise<void>;
|
|
605
|
+
/**
|
|
606
|
+
* Bind this stamper to a leased partition. Called once by the lease
|
|
607
|
+
* orchestrator after `acquire()` succeeds. `localCounter` is the
|
|
608
|
+
* starting per-bucket count for THIS device — fresh zeros for Case A,
|
|
609
|
+
* the resumed high-water from the state feed for Cases B/D.
|
|
610
|
+
*/
|
|
611
|
+
bindPartition(opts: {
|
|
612
|
+
partition: number;
|
|
613
|
+
partitionCount: number;
|
|
614
|
+
localCounter: Uint32Array;
|
|
615
|
+
}): void;
|
|
616
|
+
/**
|
|
617
|
+
* Bind the per-partition lock-SOC addresses. Called once per stamper
|
|
618
|
+
* lifetime (the addresses are deterministic from the account). Routes
|
|
619
|
+
* lock-SOC overstamps to a fixed reserved slot per partition so the tight
|
|
620
|
+
* heartbeat cadence doesn't consume new slot budget every refresh.
|
|
621
|
+
*
|
|
622
|
+
* Independent of `bindPartition` — can be called before the partition
|
|
623
|
+
* lease is acquired (the first lock-SOC write inside `acquirePartitionLock`
|
|
624
|
+
* needs this routing to already be in place).
|
|
625
|
+
*/
|
|
626
|
+
bindLockSocs(socs: ReadonlyArray<{
|
|
627
|
+
partition: number;
|
|
628
|
+
address: Uint8Array;
|
|
629
|
+
}>): void;
|
|
630
|
+
/**
|
|
631
|
+
* Register a utilisation/state chunk address so the next `stamp()` of it
|
|
632
|
+
* routes to a reserved slot instead of a data slot. `slot` defaults to the
|
|
633
|
+
* currently bound partition — pass it explicitly when the stamper may be
|
|
634
|
+
* unbound at stamp time (the teardown release publishes state chunks after
|
|
635
|
+
* `unbindPartition()`, and a default of 0 would overstamp partition 0's
|
|
636
|
+
* reserved chunks). Idempotent per address.
|
|
637
|
+
*/
|
|
638
|
+
markReservedUtilizationChunk(address: Uint8Array, slot?: number): void;
|
|
639
|
+
/** Clear the registered utilisation-chunk addresses after a save. */
|
|
640
|
+
clearReservedUtilizationChunks(): void;
|
|
641
|
+
/**
|
|
642
|
+
* Register the per-partition intent SOC so `stamp()` routes it to `slot`
|
|
643
|
+
* (the contended partition's reserved index) instead of a data slot — never
|
|
644
|
+
* consuming data budget or bumping the counter. `slot` is passed explicitly
|
|
645
|
+
* because the intent round runs BEFORE `bindPartition`, so `this.partition`
|
|
646
|
+
* is not yet set. Call immediately before the upload; pair with
|
|
647
|
+
* `clearIntentSocSlot` in a `finally`.
|
|
648
|
+
*/
|
|
649
|
+
reserveIntentSocSlot(address: Uint8Array, slot: number): void;
|
|
650
|
+
/** Clear the registered intent SOC after its upload. */
|
|
651
|
+
clearIntentSocSlot(): void;
|
|
652
|
+
/**
|
|
653
|
+
* Reserve the intent-SOC slot for `address`, run `fn` (the upload), then
|
|
654
|
+
* clear it — serialized against every other `withIntentSocSlot` call on this
|
|
655
|
+
* stamper so concurrent intent/occupancy/state-pointer writes can't clobber
|
|
656
|
+
* one another's reservation (see {@link intentSocLock}). Production callers
|
|
657
|
+
* (`writeReservedPartitionSoc`, `writeStatePointer`) MUST use this instead of
|
|
658
|
+
* the bare `reserveIntentSocSlot`/`clearIntentSocSlot` pair. `partition` is the
|
|
659
|
+
* reserved slot index the SOC routes to (the contended partition's index).
|
|
660
|
+
*/
|
|
661
|
+
withIntentSocSlot<T>(address: Uint8Array, partition: number, fn: () => Promise<T>): Promise<T>;
|
|
662
|
+
/**
|
|
663
|
+
* Inverse of `bindPartition` — clears partition slot state on demote.
|
|
664
|
+
* Leaves `lockSocs` intact (still valid for the account; refresh/yield
|
|
665
|
+
* writes may need them), and leaves an invalidated lease invalidated: see
|
|
666
|
+
* `leaseStale`.
|
|
667
|
+
*/
|
|
668
|
+
unbindPartition(): void;
|
|
669
|
+
/**
|
|
670
|
+
* Circuit-break: mark the bound lease as stale. Every data `stamp()` from
|
|
671
|
+
* here to the next `bindPartition` throws `PartitionLeaseLostError`, so an
|
|
672
|
+
* in-flight upload aborts cleanly mid-stream when a peer takes our partition
|
|
673
|
+
* or the lease is torn down under it. No-op when no partition is bound:
|
|
674
|
+
* there is no lease to lose, and legacy stamping must stay unfenced.
|
|
675
|
+
*/
|
|
676
|
+
invalidateLease(): void;
|
|
677
|
+
/**
|
|
678
|
+
* Record the wall-clock ms this device's lease is valid until (the lock SOC's
|
|
679
|
+
* `leasedUntil`). The coordinator pushes it on bind and every successful
|
|
680
|
+
* renewal; `undefined` disables the local-lapse fence. See `leaseValidUntil`.
|
|
681
|
+
*/
|
|
682
|
+
setLeaseValidUntil(ms: number | undefined): void;
|
|
683
|
+
/**
|
|
684
|
+
* True when this device's own clock says the lease has lapsed (or is within
|
|
685
|
+
* the skew margin of it). Pure/local — no network — so it can gate every
|
|
686
|
+
* `stamp()` on the hot path for free. `undefined` deadline → not fenced.
|
|
687
|
+
*/
|
|
688
|
+
private leaseLocallyLapsed;
|
|
689
|
+
private constructor();
|
|
690
|
+
/**
|
|
691
|
+
* Create a UtilizationAwareStamper with bucket state from cache
|
|
692
|
+
*
|
|
693
|
+
* @param privateKey - Signer private key
|
|
694
|
+
* @param batchId - Postage batch ID
|
|
695
|
+
* @param depth - Batch depth
|
|
696
|
+
* @param cache - Utilization cache database
|
|
697
|
+
* @param _owner - Owner address (required for validation, reserved for future Swarm upload)
|
|
698
|
+
* @param encryptionKey - Encryption key used to compute the canonical Swarm chunk address on local flush. Both call sites already pass the account's encryption key; "anything stable" works for now, will be tied to multi-device collision avoidance later.
|
|
699
|
+
* @returns New UtilizationAwareStamper instance
|
|
700
|
+
*/
|
|
701
|
+
static create(privateKey: Uint8Array | string, batchId: BatchId, depth: number, cache: UtilizationStoreDB, owner: EthAddress, encryptionKey: Uint8Array, now?: () => number): Promise<UtilizationAwareStamper>;
|
|
702
|
+
/**
|
|
703
|
+
* The reserved slot `chunkAddress` overstamps, if it is one of the account's
|
|
704
|
+
* own bookkeeping chunks, else undefined.
|
|
705
|
+
*/
|
|
706
|
+
private reservedSlotFor;
|
|
707
|
+
/**
|
|
708
|
+
* Partition lease was reclaimed: abort cleanly before a data stamp lands in
|
|
709
|
+
* slot space the peer now controls. Single-device legacy mode has no lease
|
|
710
|
+
* to invalidate, so neither condition can hold there. Two conditions:
|
|
711
|
+
* - `leaseStale`: the refresh tick CONFIRMED a peer took over (write-verify
|
|
712
|
+
* failed / displacement beacon seen), or the coordinator tore the lease
|
|
713
|
+
* down. Holds across the unbind that follows, where the fall-through
|
|
714
|
+
* in `assignSlot` would stamp at legacy slots.
|
|
715
|
+
* - `leaseLocallyLapsed()`: this device's own clock says the lease expired
|
|
716
|
+
* (skew margin applied). Catches the un-renewable case a disjoint-gateway
|
|
717
|
+
* refresh can't confirm by reading, stopping a long op mid-stream the
|
|
718
|
+
* moment the lease lapses locally, so the data write is fenced to the same
|
|
719
|
+
* skew bound as the ack (Postage-Batch-Partitioning.md §12, "ack-safe").
|
|
720
|
+
*
|
|
721
|
+
* @throws PartitionLeaseLostError
|
|
722
|
+
*/
|
|
723
|
+
private requireLiveLease;
|
|
724
|
+
/**
|
|
725
|
+
* Pick the slot `chunkAddress` is stamped at within its bucket, and spend it.
|
|
726
|
+
*
|
|
727
|
+
* Synchronous on purpose: callers fire stamps concurrently, and with no await
|
|
728
|
+
* in here each assignment runs to completion before the next starts. Whoever
|
|
729
|
+
* signs — {@link stamp} here, a `StampWorkerPool` worker — must take the slot
|
|
730
|
+
* from this method and nowhere else.
|
|
731
|
+
*
|
|
732
|
+
* @throws PartitionLeaseLostError when the bound partition's lease is gone
|
|
733
|
+
*/
|
|
734
|
+
assignSlot(chunkAddress: Uint8Array): number;
|
|
735
|
+
/**
|
|
736
|
+
* Stamp a chunk address (implements Stamper interface)
|
|
737
|
+
*
|
|
738
|
+
* @param chunkAddress - Address of the chunk to stamp
|
|
739
|
+
* @param timestampMs - Stamp timestamp, defaults to now
|
|
740
|
+
* @returns Envelope with batch ID and signature
|
|
741
|
+
*/
|
|
742
|
+
stamp(chunkAddress: Uint8Array, timestampMs?: number): EnvelopeWithBatchId;
|
|
743
|
+
/**
|
|
744
|
+
* Stamp a chunk an app will send through its own Swarm access. Always a
|
|
745
|
+
* data slot: an address the stamper would route to a reserved slot is
|
|
746
|
+
* refused, so an app can never be handed the stamp that overwrites the
|
|
747
|
+
* account's lock, counter, state or intent chunks. And always inside the
|
|
748
|
+
* bucket's app slice, so apps can never take the slots the account keeps
|
|
749
|
+
* for its own uploads.
|
|
750
|
+
*
|
|
751
|
+
* @throws ReservedSlotError for one of the account's own bookkeeping chunks
|
|
752
|
+
* @throws AppSliceFullError when the bucket's app slice is used up
|
|
753
|
+
* @throws PartitionLeaseLostError when the bound partition's lease is gone
|
|
754
|
+
*/
|
|
755
|
+
stampDataChunk(chunkAddress: Uint8Array, timestampMs?: number): EnvelopeWithBatchId;
|
|
756
|
+
/**
|
|
757
|
+
* Whether `slot` is a data slot this device's current lane has already
|
|
758
|
+
* taken in `chunkAddress`'s bucket, so stamping `chunkAddress` there again
|
|
759
|
+
* replaces only a chunk this lane put there. A reserved chunk's address is
|
|
760
|
+
* never one.
|
|
761
|
+
*/
|
|
762
|
+
isOwnDataSlot(chunkAddress: Uint8Array, slot: number): boolean;
|
|
763
|
+
/**
|
|
764
|
+
* Stamp a single-owner chunk an app had stamped before at the same slot,
|
|
765
|
+
* with a newer timestamp, so a mutable batch replaces the old chunk at that
|
|
766
|
+
* slot rather than spending a new one. No slot is taken and the counter
|
|
767
|
+
* does not move.
|
|
768
|
+
*
|
|
769
|
+
* @param slot - A slot {@link isOwnDataSlot} accepts for `chunkAddress`
|
|
770
|
+
* @param timestampMs - Must be newer than the last stamp at this slot
|
|
771
|
+
* @throws ReservedSlotError for one of the account's own bookkeeping chunks
|
|
772
|
+
* @throws PartitionLeaseLostError when the bound partition's lease is gone
|
|
773
|
+
*/
|
|
774
|
+
restampDataChunk(chunkAddress: Uint8Array, slot: number, timestampMs?: number): EnvelopeWithBatchId;
|
|
775
|
+
/** How many data chunks this device's partition lane holds in one bucket */
|
|
776
|
+
laneCapacity(): number;
|
|
777
|
+
/**
|
|
778
|
+
* How far up a bucket's lane an app stamp may go: the lane less the share
|
|
779
|
+
* {@link ACCOUNT_RESERVED_LANE_SHARE} keeps for the account's own uploads.
|
|
780
|
+
*/
|
|
781
|
+
appSliceCapacity(): number;
|
|
782
|
+
/**
|
|
783
|
+
* Get bucket state (implements Stamper interface) — see {@link buckets}.
|
|
784
|
+
*/
|
|
785
|
+
getState(): Uint32Array;
|
|
786
|
+
/**
|
|
787
|
+
* Flush dirty utilization chunks to cache
|
|
788
|
+
*
|
|
789
|
+
* This persists any bucket state changes made during stamping.
|
|
790
|
+
* Should be called after all stamping operations are complete.
|
|
791
|
+
*/
|
|
792
|
+
flush(): Promise<void>;
|
|
793
|
+
/**
|
|
794
|
+
* Get current utilization state
|
|
795
|
+
*
|
|
796
|
+
* @returns Current utilization state
|
|
797
|
+
*/
|
|
798
|
+
getUtilizationState(): BatchUtilizationState;
|
|
799
|
+
/**
|
|
800
|
+
* Apply utilization update from another tab.
|
|
801
|
+
* Updates local bucket counters to match leader's state.
|
|
802
|
+
*
|
|
803
|
+
* @param buckets Array of bucket indices and their new counter values
|
|
804
|
+
*/
|
|
805
|
+
applyUtilizationUpdate(buckets: Array<{
|
|
806
|
+
index: number;
|
|
807
|
+
value: number;
|
|
808
|
+
}>): void;
|
|
809
|
+
/**
|
|
810
|
+
* Build the partition-local counter from the stamper's current
|
|
811
|
+
* `dataCounters` — i.e. resume exactly where this device left off. The
|
|
812
|
+
* lease orchestrator passes the result into `bindPartition` as the seed
|
|
813
|
+
* counter for a held partition.
|
|
814
|
+
*/
|
|
815
|
+
buildLeaseLocalCounter(): Uint32Array;
|
|
816
|
+
/**
|
|
817
|
+
* Read the cached "synced reference" for `partition` — the partition-state
|
|
818
|
+
* feed reference this device's local counter was last in sync with. Used by
|
|
819
|
+
* the acquire path to skip re-downloading unchanged counter chunks.
|
|
820
|
+
*/
|
|
821
|
+
getSyncedReference(partition: number): Promise<string | undefined>;
|
|
822
|
+
/**
|
|
823
|
+
* Record the partition-state feed reference this device's local counter is
|
|
824
|
+
* now in sync with (after a download or a publish). Merges into the existing
|
|
825
|
+
* batch metadata so other fields / partitions are preserved.
|
|
826
|
+
*/
|
|
827
|
+
setSyncedReference(partition: number, referenceHex: string): Promise<void>;
|
|
828
|
+
/**
|
|
829
|
+
* Get bucket counter values for broadcasting to other tabs.
|
|
830
|
+
* Returns only the dirty buckets with their current values.
|
|
831
|
+
*
|
|
832
|
+
* IMPORTANT: Call this BEFORE flush() as flush() clears dirtyBuckets.
|
|
833
|
+
*
|
|
834
|
+
* @returns Array of bucket index/value pairs for broadcasting
|
|
835
|
+
*/
|
|
836
|
+
getBucketUpdatesForBroadcast(): Array<{
|
|
837
|
+
index: number;
|
|
838
|
+
value: number;
|
|
839
|
+
}>;
|
|
840
|
+
}
|