Skip to content

Commit 452e3fc

Browse files
authored
Feature/dht search (#967)
1 parent 8f417a2 commit 452e3fc

12 files changed

Lines changed: 313 additions & 104 deletions

File tree

docs/API.md

Lines changed: 46 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -404,19 +404,58 @@ returns list of logs
404404

405405
---
406406

407-
## Advertise Did
407+
## Get providers for a string
408408

409-
### `HTTP` GET /advertiseDid/?did=did:op:123"
409+
### `HTTP` GET /getProvidersForString/?input=did:op:123"
410410

411411
#### Description
412412

413-
returns empty if advertising did around peers was successful
413+
returns list of nodes providing the specific element(s) (dids, c2d resources, etc)
414414

415415
#### Query Parameters
416416

417-
| name | type | required | description |
418-
| ---- | ------ | -------- | ------------------ |
419-
| did | string | v | document id or did |
417+
| name | type | required | description |
418+
| ----- | ------ | -------- | ---------------------- |
419+
| input | string | v | did, c2d resource, etc |
420+
421+
## Get providers for a list of strings
422+
423+
### `HTTP` POST /getProvidersForStrings?timeout=10"
424+
425+
#### Description
426+
427+
returns list of nodes providing all specific elements.
428+
429+
#### Query Parameters
430+
431+
| name | type | required | description |
432+
| ------- | ------ | -------- | ---------------------- |
433+
| timeout | string | optional | timeout in miliseconds |
434+
435+
#### Request
436+
437+
```json
438+
["{\"c2d\":{\"free\":false,\"disk\":1}}", "{\"c2d\":{\"free\":false,\"cpu\":1}}"]
439+
```
440+
441+
#### Response
442+
443+
```json
444+
[
445+
{
446+
"id": "16Uiu2HAmENNgCY1QAdQrPxipgUCQjyookUgpnbgXua4ZMju4Rkou",
447+
"multiaddrs": [
448+
"/ip4/10.255.255.254/tcp/41015/ws",
449+
"/ip4/10.255.255.254/tcp/41347",
450+
"/ip4/127.0.0.1/tcp/41015/ws",
451+
"/ip4/127.0.0.1/tcp/41347",
452+
"/ip4/172.27.58.101/tcp/41015/ws",
453+
"/ip4/172.27.58.101/tcp/41347",
454+
"/ip6/::1/tcp/37527"
455+
]
456+
}
457+
]
458+
```
420459

421460
---
422461

@@ -1404,7 +1443,7 @@ returns job result
14041443
| jobId | string | v | jobId address to use as filter |
14051444
| signature | string | v | signature (consumerAddress + jobId + index.toString() + nonce) |
14061445
| nonce | string | v | nonce for the request |
1407-
| index | number | v | index of result (0 for main result, 1 for logs) |
1446+
| index | number | v | index of result (0 for main result, 1 for logs) |
14081447

14091448
#### Response
14101449

src/@types/C2D/C2D.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ export interface ComputeResource {
5151
id: ComputeResourceType
5252
description?: string
5353
type?: string
54-
kind?: string
54+
kind?: string // discreet, named, etc
5555
total: number // total number of specific resource
5656
min: number // min number of resource needed for a job
5757
max: number // max number of resource for a job

src/components/P2P/index.ts

Lines changed: 56 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
// import diff from 'hyperdiff'
2-
import { P2PCommandResponse, TypesenseSearchResponse } from '../../@types/index'
2+
import { P2PCommandResponse } from '../../@types/index'
33
import EventEmitter from 'node:events'
44
import clone from 'lodash.clonedeep'
55

@@ -61,10 +61,6 @@ type DDOCache = {
6161
dht: Map<string, FindDDOResponse>
6262
}
6363

64-
// republish any ddos we are providing to the network every 4 hours
65-
// (we can put smaller interval for testing purposes)
66-
const REPUBLISH_INTERVAL_HOURS = 1000 * 60 * 60 * 4 // 4 hours
67-
6864
let index = 0
6965

7066
export class OceanP2P extends EventEmitter {
@@ -127,15 +123,13 @@ export class OceanP2P extends EventEmitter {
127123
this._connections = {}
128124
this._protocol = '/ocean/nodes/1.0.0'
129125

130-
// this._interval = setInterval(this._pollPeers.bind(this), this._options.pollInterval)
126+
this._interval = setInterval(this._flushAdvertiseQueue.bind(this), 60 * 1000) // every 60 seconds
131127

132128
// only enable handling of commands if not bootstrap node
133129
if (!this._config.isBootstrap) {
134130
this._libp2p.handle(this._protocol, handleProtocolCommands.bind(this))
135131
}
136132

137-
setInterval(this.republishStoredDDOS.bind(this), REPUBLISH_INTERVAL_HOURS)
138-
139133
this._idx = index++
140134

141135
this._analyzeRemoteResponse = new Transform({
@@ -146,7 +140,7 @@ export class OceanP2P extends EventEmitter {
146140
// listen for indexer events and advertise did
147141
INDEXER_DDO_EVENT_EMITTER.addListener(EVENTS.METADATA_CREATED, (did) => {
148142
P2P_LOGGER.info(`Listened "${EVENTS.METADATA_CREATED}"`)
149-
this.advertiseDid(did)
143+
this.advertiseString(did)
150144
})
151145
}
152146

@@ -755,17 +749,32 @@ export class OceanP2P extends EventEmitter {
755749
}
756750
}
757751

758-
async advertiseDid(did: string) {
759-
P2P_LOGGER.logMessage('Advertising ' + did, true)
752+
async _flushAdvertiseQueue() {
753+
if (this._pendingAdvertise.length > 0) {
754+
P2P_LOGGER.debug(
755+
`Flushing advertise queue with ${this._pendingAdvertise.length} items`
756+
)
757+
const list = JSON.parse(JSON.stringify(this._pendingAdvertise))
758+
for (const did of list) {
759+
this._pendingAdvertise = this._pendingAdvertise.filter((item) => item !== did)
760+
761+
await this.advertiseString(did)
762+
}
763+
// this._pendingAdvertise = []
764+
}
765+
}
766+
767+
async advertiseString(did: string) {
760768
try {
769+
const cid = await cidFromRawString(did)
770+
P2P_LOGGER.debug('Advertising "' + did + `" as CID:` + cid)
761771
const x = (await this.getAllOceanPeers()).length
762772
if (x > 0) {
763-
const cid = await cidFromRawString(did)
764773
const multiAddrs = this._libp2p.components.addressManager.getAddresses()
765774
// console.log('multiaddrs: ', multiAddrs)
766-
await this._libp2p.contentRouting.provide(cid, multiAddrs)
775+
this._libp2p.contentRouting.provide(cid, multiAddrs)
767776
} else {
768-
P2P_LOGGER.verbose(
777+
P2P_LOGGER.debug(
769778
'Could not find any Ocean peers. Nobody is listening at the moment, skipping...'
770779
)
771780
// save it for retry later
@@ -779,67 +788,50 @@ export class OceanP2P extends EventEmitter {
779788
}
780789
}
781790

782-
async getProvidersForDid(did: string) {
783-
P2P_LOGGER.logMessage('Fetching providers for ' + did, true)
784-
const cid = await cidFromRawString(did)
791+
getCommonPeers(
792+
rets: Array<Array<{ id: string; multiaddrs: any[] }>>
793+
): Array<{ id: string; multiaddrs: any[] }> {
794+
return rets.reduce(
795+
(acc, curr) =>
796+
acc.filter((item) => curr.some((el) => el.id.toString() === item.id.toString())),
797+
rets[0] // Initialize with first subarray
798+
)
799+
}
800+
801+
async getProvidersForStrings(
802+
input: string[],
803+
timeout?: number
804+
): Promise<Array<{ id: string; multiaddrs: any[] }>> {
805+
const rets = await Promise.all(
806+
input.map(async (x) => {
807+
const providers = await this.getProvidersForString(x, timeout)
808+
return providers && providers.length > 0 ? providers : [] // Keep only valid results
809+
})
810+
)
811+
return this.getCommonPeers(rets)
812+
}
813+
814+
async getProvidersForString(
815+
input: string,
816+
timeout?: number
817+
): Promise<Array<{ id: string; multiaddrs: any[] }>> {
818+
P2P_LOGGER.logMessage('Fetching providers for ' + input, true)
819+
const cid = await cidFromRawString(input)
785820
const peersFound = []
786821
try {
787822
const f = await this._libp2p.contentRouting.findProviders(cid, {
788-
queryFuncTimeout: 20000 // 20 seconds
823+
queryFuncTimeout: timeout || 20000 // 20 seconds
789824
// on timeout the query ends with an abort signal => CodeError: Query aborted
790825
})
791826
for await (const value of f) {
792827
peersFound.push(value)
793828
}
794829
} catch (e) {
795-
P2P_LOGGER.error('getProvidersForDid()' + e.message)
830+
P2P_LOGGER.error('getProvidersForString()' + e.message)
796831
}
797832
return peersFound
798833
}
799834

800-
// republish the ddos we have
801-
// related: https://github.com/libp2p/go-libp2p-kad-dht/issues/323
802-
async republishStoredDDOS() {
803-
try {
804-
if (!this.db || !this.db.ddo) {
805-
P2P_LOGGER.logMessage(
806-
`republishStoredDDOS() attempt aborted because there is no database!`,
807-
true
808-
)
809-
return
810-
}
811-
const db = this.db.ddo
812-
const searchParameters = {
813-
q: '*'
814-
}
815-
816-
const result: TypesenseSearchResponse[] = await db.search(searchParameters)
817-
if (result && result.length > 0 && result[0].found) {
818-
P2P_LOGGER.logMessage(`Will republish cid for ${result[0].found} documents`, true)
819-
result[0].hits.forEach((hit: any) => {
820-
const ddo = hit.document
821-
this.advertiseDid(ddo.id)
822-
// populate hash table if not exists
823-
// (even if no peers are listening, it still goes to the pending publish table)
824-
if (!this._ddoDHT.dht.has(ddo.id)) {
825-
this.cacheDDO(ddo)
826-
}
827-
// todo check stuff like purgatory
828-
})
829-
// update time
830-
this._ddoDHT.updated = new Date().getTime()
831-
} else {
832-
P2P_LOGGER.logMessage('There is nothing to republish, skipping...', true)
833-
}
834-
} catch (err) {
835-
P2P_LOGGER.log(
836-
LOG_LEVELS_STR.LEVEL_ERROR,
837-
`Caught "${err.message}" on republishStoredDDOS()`,
838-
true
839-
)
840-
}
841-
}
842-
843835
// cache a ddos object
844836
cacheDDO(ddo: any) {
845837
this._ddoDHT.dht.set(ddo.id, {
@@ -848,6 +840,7 @@ export class OceanP2P extends EventEmitter {
848840
lastUpdateTime: ddo.metadata.updated,
849841
provider: this.getPeerId()
850842
})
843+
this._ddoDHT.updated = new Date().getTime()
851844
}
852845

853846
/**
@@ -893,7 +886,7 @@ export class OceanP2P extends EventEmitter {
893886
// if already added before, create() will return null, but still advertise it
894887
try {
895888
await db.create(ddo)
896-
await this.advertiseDid(ddo.id)
889+
await this.advertiseString(ddo.id)
897890
// populate hash table
898891
this.cacheDDO(ddo)
899892
count++

src/components/c2d/compute_engine_docker.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -172,12 +172,14 @@ export class C2DEngineDocker extends C2DEngine {
172172
this.envs[0].resources = []
173173
this.envs[0].resources.push({
174174
id: 'cpu',
175+
type: 'cpu',
175176
total: sysinfo.NCPU,
176177
max: sysinfo.NCPU,
177178
min: 1
178179
})
179180
this.envs[0].resources.push({
180181
id: 'ram',
182+
type: 'ram',
181183
total: sysinfo.MemTotal,
182184
max: sysinfo.MemTotal,
183185
min: 1e9

src/components/core/handler/ddoHandler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -616,7 +616,7 @@ export class FindDdoHandler extends CommandHandler {
616616
}, 1000 * MAX_RESPONSE_WAIT_TIME_SECONDS)
617617

618618
// check other providers for this ddo
619-
const providers = await p2pNode.getProvidersForDid(task.id)
619+
const providers = await p2pNode.getProvidersForString(task.id)
620620
// check if includes self and exclude from check list
621621
if (providers.length > 0) {
622622
// exclude this node from the providers list if present

src/components/httpRoutes/dids.ts

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2,41 +2,56 @@ import express, { Request, Response } from 'express'
22
import { sendMissingP2PResponse } from './index.js'
33
import { hasP2PInterface } from '../../utils/config.js'
44

5-
export const advertiseDidRoute = express.Router()
6-
7-
advertiseDidRoute.post(
8-
'/advertiseDid',
5+
export const getProvidersForStringRoute = express.Router()
6+
getProvidersForStringRoute.get(
7+
'/getProvidersForString',
98
express.urlencoded({ extended: true, type: '*/*' }),
109
async (req: Request, res: Response): Promise<void> => {
11-
if (!req.query.did) {
10+
if (!req.query.input) {
1211
res.sendStatus(400)
1312
return
1413
}
1514
if (hasP2PInterface) {
16-
await req.oceanNode.getP2PNode().advertiseDid(req.query.did as string)
17-
res.sendStatus(200)
15+
const providers = await req.oceanNode
16+
.getP2PNode()
17+
.getProvidersForString(req.query.input as string)
18+
res.json(providers)
1819
} else {
1920
sendMissingP2PResponse(res)
2021
}
2122
}
2223
)
2324

24-
export const getProvidersForDidRoute = express.Router()
25-
getProvidersForDidRoute.get(
26-
'/getProvidersForDid',
27-
express.urlencoded({ extended: true, type: '*/*' }),
28-
async (req: Request, res: Response): Promise<void> => {
29-
if (!req.query.did) {
30-
res.sendStatus(400)
31-
return
32-
}
33-
if (hasP2PInterface) {
34-
const providers = await req.oceanNode
35-
.getP2PNode()
36-
.getProvidersForDid(req.query.did as string)
37-
res.json(providers)
38-
} else {
39-
sendMissingP2PResponse(res)
25+
export const getProvidersForStringsRoute = express.Router()
26+
getProvidersForStringsRoute.post(
27+
'/getProvidersForStrings',
28+
express.json(),
29+
async (req, res) => {
30+
try {
31+
if (!req.body) {
32+
res.status(400).send('Missing array of strings in request body.')
33+
return
34+
}
35+
// const body = JSON.parse(req.body)
36+
if (
37+
Array.isArray(req.body) &&
38+
req.body.every((item: unknown) => typeof item === 'string')
39+
) {
40+
const timeout =
41+
typeof req.query?.timeout === 'string'
42+
? parseInt(req.query.timeout, 10)
43+
: undefined
44+
const providers = await req.oceanNode
45+
.getP2PNode()
46+
.getProvidersForStrings(req.body, timeout)
47+
48+
res.json(providers)
49+
} else {
50+
res.status(400).send('Expected an array of strings.')
51+
}
52+
} catch (error) {
53+
console.error('Error processing request:', error)
54+
res.status(400).send(error)
4055
}
4156
}
4257
)

0 commit comments

Comments
 (0)