wao 0.6.0 → 0.6.2
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/cjs/ao.js +102 -81
- package/cjs/aoconnect.js +64 -60
- package/cjs/helpers.js +4 -12
- package/cjs/lua/weavedb_mock.lua +43 -0
- package/cjs/server.js +3 -1
- package/cjs/tao.js +3 -1
- package/cjs/utils.js +17 -1
- package/cjs/weavedb.js +447 -0
- package/cjs/weavedrive.js +240 -591
- package/esm/ao.js +25 -13
- package/esm/aoconnect.js +8 -7
- package/esm/helpers.js +4 -12
- package/esm/lua/weavedb_mock.lua +43 -0
- package/esm/server.js +1 -1
- package/esm/tao.js +1 -1
- package/esm/utils.js +14 -0
- package/esm/weavedb.js +309 -0
- package/esm/weavedrive.js +75 -309
- package/package.json +1 -1
package/esm/weavedrive.js
CHANGED
|
@@ -1,16 +1,13 @@
|
|
|
1
|
-
import Arweave from "arweave"
|
|
2
|
-
import { toGraphObj } from "./utils.js"
|
|
3
|
-
import { map } from "ramda"
|
|
4
|
-
|
|
5
1
|
const KB = 1024
|
|
6
2
|
const MB = KB * 1024
|
|
7
3
|
const CACHE_SZ = 32 * KB
|
|
8
4
|
const CHUNK_SZ = 128 * MB
|
|
9
5
|
const NOTIFY_SZ = 512 * MB
|
|
10
6
|
const log = console.log
|
|
7
|
+
|
|
11
8
|
export default class WeaveDrive {
|
|
12
9
|
constructor(ar) {
|
|
13
|
-
this.
|
|
10
|
+
this.ext = (mod, FS) => {
|
|
14
11
|
return {
|
|
15
12
|
reset(fd) {
|
|
16
13
|
//console.log("WeaveDrive: Resetting fd: ", fd)
|
|
@@ -18,67 +15,22 @@ export default class WeaveDrive {
|
|
|
18
15
|
FS.streams[fd].node.cache = new Uint8Array(0)
|
|
19
16
|
},
|
|
20
17
|
|
|
21
|
-
joinUrl({ url, path }) {
|
|
22
|
-
if (!path) return url
|
|
23
|
-
if (path.startsWith("/"))
|
|
24
|
-
return this.joinUrl({ url, path: path.slice(1) })
|
|
25
|
-
|
|
26
|
-
url = new URL(url)
|
|
27
|
-
url.pathname += path
|
|
28
|
-
return url.toString()
|
|
29
|
-
},
|
|
30
|
-
|
|
31
|
-
async customFetch(path, options) {
|
|
32
|
-
/**
|
|
33
|
-
* mod.ARWEAVE may be a comma-delimited list of urls.
|
|
34
|
-
* So we parse it into an array that we sequentially consume
|
|
35
|
-
* using fetch, and return the first successful response.
|
|
36
|
-
*
|
|
37
|
-
* The first url is considered "primary". So if all urls fail
|
|
38
|
-
* to produce a successful response, then we return the primary's
|
|
39
|
-
* error response
|
|
40
|
-
*/
|
|
41
|
-
const urlList = mod.ARWEAVE.includes(",")
|
|
42
|
-
? mod.ARWEAVE.split(",").map(url => url.trim())
|
|
43
|
-
: [mod.ARWEAVE]
|
|
44
|
-
|
|
45
|
-
let p
|
|
46
|
-
for (const url of urlList) {
|
|
47
|
-
const res = fetch(this.joinUrl({ url, path }), options)
|
|
48
|
-
if (await res.then(r => r.ok).catch(() => false)) return res
|
|
49
|
-
if (!p) p = res
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
/**
|
|
53
|
-
* None succeeded so fallback to the primary and accept
|
|
54
|
-
* whatever it returned
|
|
55
|
-
*/
|
|
56
|
-
return p
|
|
57
|
-
},
|
|
58
|
-
|
|
59
18
|
async create(id) {
|
|
60
19
|
var properties = { isDevice: false, contents: null }
|
|
61
20
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return 0
|
|
65
|
-
}
|
|
21
|
+
//console.log("WeaveDrive: Arweave ID is not admissable! ", id)
|
|
22
|
+
if (!(await this.checkAdmissible(id))) return 0
|
|
66
23
|
|
|
67
24
|
// Create the file in the emscripten FS
|
|
68
25
|
|
|
69
|
-
|
|
70
|
-
// called first because were only loading Data, we needed to create
|
|
71
|
-
// the directory. See: https://github.com/permaweb/aos/issues/342
|
|
72
|
-
if (!FS.analyzePath("/data/").exists) {
|
|
73
|
-
FS.mkdir("/data/")
|
|
74
|
-
}
|
|
26
|
+
if (!FS.analyzePath("/data/").exists) FS.mkdir("/data/")
|
|
75
27
|
|
|
76
28
|
var node = FS.createFile("/", "data/" + id, properties, true, false)
|
|
77
29
|
// Set initial parameters
|
|
78
30
|
/*
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
31
|
+
var bytesLength = await this.customFetch(`/${id}`, {
|
|
32
|
+
method: "HEAD",
|
|
33
|
+
}).then(res => res.headers.get("Content-Length"))
|
|
82
34
|
*/
|
|
83
35
|
let data = await ar.data(id)
|
|
84
36
|
const bytesLength = data?.length ?? 0
|
|
@@ -88,11 +40,7 @@ export default class WeaveDrive {
|
|
|
88
40
|
|
|
89
41
|
// Add a function that defers querying the file size until it is asked the first time.
|
|
90
42
|
Object.defineProperties(node, {
|
|
91
|
-
usedBytes: {
|
|
92
|
-
get: function () {
|
|
93
|
-
return bytesLength
|
|
94
|
-
},
|
|
95
|
-
},
|
|
43
|
+
usedBytes: { get: () => bytesLength },
|
|
96
44
|
})
|
|
97
45
|
|
|
98
46
|
// Now we have created the file in the emscripten FS, we can open it as a stream
|
|
@@ -102,23 +50,21 @@ export default class WeaveDrive {
|
|
|
102
50
|
return stream
|
|
103
51
|
},
|
|
104
52
|
async createBlockHeader(id) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
var bytesLength = result.length
|
|
53
|
+
var result = ""
|
|
54
|
+
try {
|
|
55
|
+
// todo: implement indep_hash
|
|
56
|
+
// fetch(`/block/height/${id}`)
|
|
57
|
+
const block = ar.mem.blockmap[ar.mem.blocks[id]]
|
|
58
|
+
if (block) {
|
|
59
|
+
result = JSON.stringify({
|
|
60
|
+
id: block.id,
|
|
61
|
+
timestamp: block.timestamp,
|
|
62
|
+
previous_block: block.previous,
|
|
63
|
+
txs: block.txs,
|
|
64
|
+
height: id,
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
} catch (e) {}
|
|
122
68
|
|
|
123
69
|
var node = FS.createDataFile(
|
|
124
70
|
"/",
|
|
@@ -132,33 +78,12 @@ export default class WeaveDrive {
|
|
|
132
78
|
return stream
|
|
133
79
|
},
|
|
134
80
|
async createTxHeader(id) {
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
)
|
|
140
|
-
}
|
|
141
|
-
async function retry(x) {
|
|
142
|
-
return new Promise(r => {
|
|
143
|
-
setTimeout(function () {
|
|
144
|
-
r(customFetch(`/tx/${id}`))
|
|
145
|
-
}, x * 10000)
|
|
146
|
-
})
|
|
147
|
-
}
|
|
148
|
-
// todo: add a bunch of retries
|
|
149
|
-
var result = await this.customFetch(`/tx/${id}`)
|
|
150
|
-
.then(res => (!res.ok ? retry(1) : res))
|
|
151
|
-
.then(res => (!res.ok ? retry(2) : res))
|
|
152
|
-
.then(res => (!res.ok ? retry(3) : res))
|
|
153
|
-
.then(res => (!res.ok ? retry(4) : res))
|
|
154
|
-
.then(res => res.json())
|
|
155
|
-
.then(async entry => ({
|
|
156
|
-
...entry,
|
|
157
|
-
ownerAddress: await toAddress(entry.owner),
|
|
158
|
-
}))
|
|
159
|
-
//.then(x => (console.error(x), x))
|
|
160
|
-
.then(x => JSON.stringify(x))
|
|
161
|
-
|
|
81
|
+
var result = ""
|
|
82
|
+
// fetch(`/tx/${id}`)
|
|
83
|
+
try {
|
|
84
|
+
let tx = ar.mem.txs[id]
|
|
85
|
+
if (tx) result = JSON.stringify(tx)
|
|
86
|
+
} catch (e) {}
|
|
162
87
|
var node = FS.createDataFile(
|
|
163
88
|
"/",
|
|
164
89
|
"tx/" + id,
|
|
@@ -170,89 +95,25 @@ export default class WeaveDrive {
|
|
|
170
95
|
return stream
|
|
171
96
|
},
|
|
172
97
|
async createDataItemTxHeader(id) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
}
|
|
190
|
-
fee {
|
|
191
|
-
ar
|
|
192
|
-
winston
|
|
193
|
-
}
|
|
194
|
-
quantity {
|
|
195
|
-
winston
|
|
196
|
-
ar
|
|
197
|
-
}
|
|
198
|
-
tags {
|
|
199
|
-
name
|
|
200
|
-
value
|
|
201
|
-
}
|
|
202
|
-
bundledIn {
|
|
203
|
-
id
|
|
204
|
-
}
|
|
205
|
-
block {
|
|
206
|
-
id
|
|
207
|
-
timestamp
|
|
208
|
-
height
|
|
209
|
-
previous
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
}`
|
|
215
|
-
var variables = { transactionIds: [id] }
|
|
216
|
-
async function retry(x) {
|
|
217
|
-
return new Promise(r => {
|
|
218
|
-
setTimeout(function () {
|
|
219
|
-
r(gqlQuery(GET_TRANSACTION_QUERY, variables))
|
|
220
|
-
}, x * 10000)
|
|
221
|
-
})
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const gqlExists = await this.gqlExists()
|
|
225
|
-
if (!gqlExists) {
|
|
226
|
-
return "GQL Not Found!"
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// todo: add a bunch of retries
|
|
230
|
-
var result = await this.gqlQuery(GET_TRANSACTION_QUERY, variables)
|
|
231
|
-
.then(res => (!res.ok ? retry(1) : res))
|
|
232
|
-
.then(res => (!res.ok ? retry(2) : res))
|
|
233
|
-
.then(res => (!res.ok ? retry(3) : res))
|
|
234
|
-
.then(res => (!res.ok ? retry(4) : res))
|
|
235
|
-
.then(res => res.json())
|
|
236
|
-
.then(res => {
|
|
237
|
-
return res?.data?.transactions?.edges?.[0]?.node
|
|
238
|
-
? res.data.transactions.edges[0].node
|
|
239
|
-
: "No results"
|
|
240
|
-
})
|
|
241
|
-
.then(async entry => {
|
|
242
|
-
return typeof entry == "string"
|
|
243
|
-
? entry
|
|
244
|
-
: {
|
|
245
|
-
format: 3,
|
|
246
|
-
...entry,
|
|
247
|
-
}
|
|
248
|
-
})
|
|
249
|
-
.then(x => {
|
|
250
|
-
return typeof x == "string" ? x : JSON.stringify(x)
|
|
98
|
+
let result = (
|
|
99
|
+
await ar.gql.txs({
|
|
100
|
+
id,
|
|
101
|
+
fields: [
|
|
102
|
+
"id",
|
|
103
|
+
"anchor",
|
|
104
|
+
{ data: ["size"] },
|
|
105
|
+
"signature",
|
|
106
|
+
"recipient",
|
|
107
|
+
"owner",
|
|
108
|
+
"fee",
|
|
109
|
+
"quantity",
|
|
110
|
+
"tags",
|
|
111
|
+
"bundledIn",
|
|
112
|
+
"block",
|
|
113
|
+
],
|
|
251
114
|
})
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
return result
|
|
255
|
-
}
|
|
115
|
+
)[0]
|
|
116
|
+
result = result ? JSON.stringify(result) : "No results"
|
|
256
117
|
FS.createDataFile(
|
|
257
118
|
"/",
|
|
258
119
|
"tx2/" + id,
|
|
@@ -351,10 +212,10 @@ export default class WeaveDrive {
|
|
|
351
212
|
to_read -= bytes_read
|
|
352
213
|
|
|
353
214
|
// Return if we have satisfied the request
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
215
|
+
|
|
216
|
+
//console.log("WeaveDrive: Satisfied request with cache. Returning...")
|
|
217
|
+
if (to_read === 0) return bytes_read
|
|
218
|
+
|
|
358
219
|
//console.log("WeaveDrive: Read from cache: ", bytes_read, " Remaining to read: ", to_read)
|
|
359
220
|
|
|
360
221
|
const chunk_download_sz = Math.max(to_read, CACHE_SZ)
|
|
@@ -364,17 +225,9 @@ export default class WeaveDrive {
|
|
|
364
225
|
)
|
|
365
226
|
//console.log("WeaveDrive: fd: ", fd, " Read length: ", to_read, " Reading ahead:", to - to_read - stream.position)
|
|
366
227
|
|
|
367
|
-
//
|
|
368
|
-
/*
|
|
369
|
-
const response = await this.customFetch(`/${stream.node.name}`, {
|
|
370
|
-
method: "GET",
|
|
371
|
-
redirect: "follow",
|
|
372
|
-
headers: { Range: `bytes=${stream.position}-${to}` },
|
|
373
|
-
})
|
|
374
|
-
|
|
375
|
-
const reader = response.body.getReader()
|
|
376
|
-
*/
|
|
228
|
+
// fetch(`/${stream.node.name}`)
|
|
377
229
|
const data = await ar.data(stream.node.name)
|
|
230
|
+
|
|
378
231
|
// Extract the Range header to determine the start and end of the requested chunk
|
|
379
232
|
const start = 0
|
|
380
233
|
const end = data.length
|
|
@@ -465,9 +318,7 @@ export default class WeaveDrive {
|
|
|
465
318
|
close(fd) {
|
|
466
319
|
var stream = 0
|
|
467
320
|
for (var i = 0; i < FS.streams.length; i++) {
|
|
468
|
-
if (FS.streams[i].fd === fd)
|
|
469
|
-
stream = FS.streams[i]
|
|
470
|
-
}
|
|
321
|
+
if (FS.streams[i].fd === fd) stream = FS.streams[i]
|
|
471
322
|
}
|
|
472
323
|
FS.close(stream)
|
|
473
324
|
},
|
|
@@ -516,10 +367,9 @@ export default class WeaveDrive {
|
|
|
516
367
|
|
|
517
368
|
// General helpder functions
|
|
518
369
|
async checkAdmissible(ID) {
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
}
|
|
370
|
+
// CAUTION: If the module is initiated with `mode = test` we don't check availability.
|
|
371
|
+
if (mod.mode && mod.mode == "test") return true
|
|
372
|
+
|
|
523
373
|
// Check if we are attempting to load the On-Boot id, if so allow it
|
|
524
374
|
// this was added for AOP 6 Boot loader See: https://github.com/permaweb/aos/issues/342
|
|
525
375
|
const bootTag = this.getTagValue("On-Boot", mod.spawn.tags)
|
|
@@ -546,7 +396,6 @@ export default class WeaveDrive {
|
|
|
546
396
|
)
|
|
547
397
|
return false
|
|
548
398
|
}
|
|
549
|
-
|
|
550
399
|
const modes = ["Assignments", "Individual", "Library"]
|
|
551
400
|
// Get the Availability-Type from the spawned process's Module or Process item
|
|
552
401
|
// First check the module for its defaults
|
|
@@ -579,66 +428,30 @@ export default class WeaveDrive {
|
|
|
579
428
|
)
|
|
580
429
|
// Init a set of GraphQL queries to run in order to find a valid attestation
|
|
581
430
|
// Every WeaveDrive process has at least the "Assignments" availability check form.
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
block: {min: 0, max: ${blockHeight}},
|
|
588
|
-
tags: [
|
|
589
|
-
{ name: "Type", values: ["Attestation"] },
|
|
590
|
-
{ name: "Message", values: ["${ID}"]}
|
|
591
|
-
{ name: "Data-Protocol", values: ["ao"] },
|
|
592
|
-
]
|
|
593
|
-
)
|
|
594
|
-
{
|
|
595
|
-
edges {
|
|
596
|
-
node {
|
|
597
|
-
tags {
|
|
598
|
-
name
|
|
599
|
-
value
|
|
600
|
-
}
|
|
601
|
-
}
|
|
431
|
+
const exists = async tags => {
|
|
432
|
+
const common = {
|
|
433
|
+
owners: attestors,
|
|
434
|
+
block: [0, blockHeight],
|
|
435
|
+
fields: ["tags"],
|
|
602
436
|
}
|
|
437
|
+
return (await ar.gql.txs({ ...common, tags })).length > 0
|
|
603
438
|
}
|
|
604
|
-
|
|
605
|
-
|
|
439
|
+
const assignmentsHaveID = await exists({
|
|
440
|
+
Type: "Attestation",
|
|
441
|
+
Message: ID,
|
|
442
|
+
"Data-Protocol": "ao",
|
|
443
|
+
})
|
|
606
444
|
if (assignmentsHaveID) return true
|
|
607
445
|
|
|
608
446
|
if (processMode == "Individual") {
|
|
609
|
-
const individualsHaveID = await
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
tags: [
|
|
615
|
-
{ name: "Type", values: ["Available"]},
|
|
616
|
-
{ name: "ID", values: ["${ID}"]}
|
|
617
|
-
{ name: "Data-Protocol", values: ["WeaveDrive"] },
|
|
618
|
-
]
|
|
619
|
-
)
|
|
620
|
-
{
|
|
621
|
-
edges {
|
|
622
|
-
node {
|
|
623
|
-
tags {
|
|
624
|
-
name
|
|
625
|
-
value
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
}`,
|
|
631
|
-
)
|
|
632
|
-
|
|
447
|
+
const individualsHaveID = await exists({
|
|
448
|
+
Type: "Available",
|
|
449
|
+
ID,
|
|
450
|
+
"Data-Protocol": "WeaveDrive",
|
|
451
|
+
})
|
|
633
452
|
if (individualsHaveID) return true
|
|
634
453
|
}
|
|
635
454
|
|
|
636
|
-
// Halt message processing if the process requires Library mode.
|
|
637
|
-
// This should signal 'Cannot Process' to the CU, not that the message itself is
|
|
638
|
-
// invalid. Subsequently, the CU should not be slashable for saying that the process
|
|
639
|
-
// execution failed on this message. The CU must also not continue to execute further
|
|
640
|
-
// messages on this process. Attesting to them would be slashable, as the state would
|
|
641
|
-
// be incorrect.
|
|
642
455
|
if (processMode == "Library") {
|
|
643
456
|
throw "This WeaveDrive implementation does not support Library attestations yet!"
|
|
644
457
|
}
|
|
@@ -653,9 +466,7 @@ export default class WeaveDrive {
|
|
|
653
466
|
getTagValues(key, tags) {
|
|
654
467
|
var values = []
|
|
655
468
|
for (let i = 0; i < tags.length; i++) {
|
|
656
|
-
if (tags[i].name == key)
|
|
657
|
-
values.push(tags[i].value)
|
|
658
|
-
}
|
|
469
|
+
if (tags[i].name == key) values.push(tags[i].value)
|
|
659
470
|
}
|
|
660
471
|
return values
|
|
661
472
|
},
|
|
@@ -664,51 +475,6 @@ export default class WeaveDrive {
|
|
|
664
475
|
const values = this.getTagValues(key, tags)
|
|
665
476
|
return values.pop()
|
|
666
477
|
},
|
|
667
|
-
|
|
668
|
-
async queryHasResult(query, variables) {
|
|
669
|
-
const json = await this.gqlQuery(query, variables).then(res =>
|
|
670
|
-
res.json(),
|
|
671
|
-
)
|
|
672
|
-
return !!json?.data?.transactions?.edges?.length
|
|
673
|
-
},
|
|
674
|
-
|
|
675
|
-
async gqlExists() {
|
|
676
|
-
const query = `query {
|
|
677
|
-
transactions(
|
|
678
|
-
first: 1
|
|
679
|
-
) {
|
|
680
|
-
pageInfo {
|
|
681
|
-
hasNextPage
|
|
682
|
-
}
|
|
683
|
-
}
|
|
684
|
-
}
|
|
685
|
-
`
|
|
686
|
-
|
|
687
|
-
const gqlExists = await this.gqlQuery(query, {}).then(res => res.ok)
|
|
688
|
-
return gqlExists
|
|
689
|
-
},
|
|
690
|
-
|
|
691
|
-
async gqlQuery(query, variables) {
|
|
692
|
-
let json = null
|
|
693
|
-
try {
|
|
694
|
-
const { tar, args } = toGraphObj({ query, variables })
|
|
695
|
-
let res2 = null
|
|
696
|
-
if (tar === "transactions") {
|
|
697
|
-
res2 = await ar.gql.txs({ ...args })
|
|
698
|
-
} else if (tar === "blocks") {
|
|
699
|
-
res2 = await ar.gql.blocks({ ...args })
|
|
700
|
-
}
|
|
701
|
-
const edges = map(v => ({ node: v, cursor: v.cursor }), res2)
|
|
702
|
-
json = {
|
|
703
|
-
data: {
|
|
704
|
-
transactions: { pageInfo: { hasNextPage: true }, edges },
|
|
705
|
-
},
|
|
706
|
-
}
|
|
707
|
-
} catch (e) {
|
|
708
|
-
log(e)
|
|
709
|
-
}
|
|
710
|
-
return { json: () => json }
|
|
711
|
-
},
|
|
712
478
|
}
|
|
713
479
|
}
|
|
714
480
|
}
|