2023-08-30 18:37:51 +00:00
|
|
|
import { Multihash } from "./multihash.js";
|
|
|
|
import NodeId from "./nodeId.js";
|
2023-08-31 14:58:10 +00:00
|
|
|
import { Logger, S5Config, S5NodeConfig, S5Services } from "./types.js";
|
2023-08-30 18:37:51 +00:00
|
|
|
import Unpacker from "./serialization/unpack.js";
|
|
|
|
import Packer from "./serialization/pack.js";
|
|
|
|
import StorageLocation from "./storage.js";
|
2023-08-31 07:29:28 +00:00
|
|
|
import KeyPairEd25519 from "#ed25519.js";
|
|
|
|
import { AbstractLevel } from "abstract-level";
|
|
|
|
import { P2PService } from "#service/p2p.js";
|
|
|
|
import { RegistryService } from "#service/registry.js";
|
2023-09-02 01:07:28 +00:00
|
|
|
import { hash } from "@noble/hashes/_assert";
|
2023-08-31 07:29:28 +00:00
|
|
|
const DEFAULT_LOGGER = {
|
|
|
|
info(s: any) {
|
|
|
|
console.info(s);
|
|
|
|
},
|
|
|
|
verbose(s: any) {
|
|
|
|
console.log(s);
|
|
|
|
},
|
|
|
|
warn(s: any) {
|
|
|
|
console.warn(s);
|
|
|
|
},
|
|
|
|
error(s: any) {
|
|
|
|
console.error(s);
|
|
|
|
},
|
|
|
|
catched(e: any, context?: string | null) {
|
|
|
|
console.error(e, context);
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
export class S5Node {
|
|
|
|
private _nodeConfig: S5NodeConfig;
|
2023-08-31 10:09:23 +00:00
|
|
|
|
2023-08-31 07:29:28 +00:00
|
|
|
constructor(config: S5NodeConfig) {
|
|
|
|
this._nodeConfig = config;
|
|
|
|
}
|
|
|
|
|
2023-09-02 01:07:28 +00:00
|
|
|
private _started = false;
|
|
|
|
|
|
|
|
get started(): boolean {
|
|
|
|
return this._started;
|
|
|
|
}
|
|
|
|
|
2023-08-31 10:09:23 +00:00
|
|
|
private _config?: S5Config;
|
|
|
|
|
|
|
|
get config() {
|
|
|
|
return this._config as S5Config;
|
|
|
|
}
|
|
|
|
|
|
|
|
get services() {
|
|
|
|
return this._config?.services as S5Services;
|
|
|
|
}
|
|
|
|
|
|
|
|
get db() {
|
|
|
|
return this._config?.db as AbstractLevel<Uint8Array, string, Uint8Array>;
|
|
|
|
}
|
|
|
|
|
|
|
|
get logger() {
|
|
|
|
return this._config?.logger as Logger;
|
|
|
|
}
|
|
|
|
|
2023-08-31 07:29:28 +00:00
|
|
|
public async start() {
|
|
|
|
this._config = {
|
|
|
|
keyPair: this._nodeConfig.keyPair,
|
|
|
|
db: this._nodeConfig.db,
|
|
|
|
logger: this._nodeConfig.logger ?? DEFAULT_LOGGER,
|
|
|
|
cacheDb: this._nodeConfig.db.sublevel("s5-object-cache", {}),
|
|
|
|
services: {} as any,
|
2023-08-31 10:18:59 +00:00
|
|
|
p2p: this._nodeConfig.p2p,
|
2023-08-31 07:29:28 +00:00
|
|
|
};
|
|
|
|
|
2023-09-02 01:07:28 +00:00
|
|
|
this._started = true;
|
|
|
|
|
2023-08-31 07:29:28 +00:00
|
|
|
const p2p = new P2PService(this);
|
|
|
|
const registry = new RegistryService(this);
|
|
|
|
|
|
|
|
await p2p.init();
|
|
|
|
await registry.init();
|
|
|
|
await p2p.start();
|
|
|
|
}
|
|
|
|
|
2023-09-02 00:55:21 +00:00
|
|
|
public async stop() {
|
2023-09-02 01:07:28 +00:00
|
|
|
this._started = false;
|
2023-09-02 00:55:21 +00:00
|
|
|
await this.services.p2p.stop();
|
|
|
|
}
|
|
|
|
|
2023-08-31 10:09:23 +00:00
|
|
|
async readStorageLocationsFromDB(
|
|
|
|
hash: Multihash,
|
|
|
|
): Promise<Map<number, Map<string, Map<number, any>>>> {
|
|
|
|
const map = new Map<number, Map<string, Map<number, any>>>();
|
|
|
|
const bytes = await this.db.get(stringifyHash(hash));
|
|
|
|
if (bytes === null) {
|
|
|
|
return map;
|
|
|
|
}
|
|
|
|
const unpacker = Unpacker.fromPacked(bytes);
|
|
|
|
const mapLength = unpacker.unpackMapLength();
|
|
|
|
for (let i = 0; i < mapLength; i++) {
|
|
|
|
const type = unpacker.unpackInt() as number;
|
|
|
|
const innerMap = new Map<string, Map<number, any>>();
|
|
|
|
map.set(type, innerMap);
|
|
|
|
const innerMapLength = unpacker.unpackMapLength();
|
|
|
|
for (let j = 0; j < innerMapLength; j++) {
|
|
|
|
const nodeId = new NodeId(unpacker.unpackBinary());
|
|
|
|
innerMap.set(
|
|
|
|
nodeId.toString(),
|
|
|
|
new Map(unpacker.unpackMap() as [number, any][]),
|
|
|
|
);
|
|
|
|
}
|
2023-08-30 18:37:51 +00:00
|
|
|
}
|
2023-08-31 10:09:23 +00:00
|
|
|
return map;
|
2023-08-30 18:37:51 +00:00
|
|
|
}
|
|
|
|
|
2023-08-31 10:09:23 +00:00
|
|
|
async addStorageLocation({
|
|
|
|
hash,
|
|
|
|
nodeId,
|
|
|
|
location,
|
|
|
|
message,
|
|
|
|
config,
|
|
|
|
}: {
|
|
|
|
hash: Multihash;
|
|
|
|
nodeId: NodeId;
|
|
|
|
location: StorageLocation;
|
|
|
|
message?: Uint8Array;
|
|
|
|
config: S5Config;
|
|
|
|
}) {
|
|
|
|
const map = await this.readStorageLocationsFromDB(hash);
|
|
|
|
const innerMap =
|
|
|
|
map.get(location.type) || new Map<string, Map<number, any>>();
|
|
|
|
map.set(location.type, innerMap);
|
|
|
|
|
|
|
|
const locationMap = new Map<number, any>([
|
|
|
|
[1, location.parts],
|
|
|
|
// [2, location.binaryParts],
|
|
|
|
[3, location.expiry],
|
|
|
|
[4, message],
|
|
|
|
]);
|
|
|
|
|
|
|
|
innerMap.set(nodeId.toString(), locationMap);
|
|
|
|
await config.cacheDb.put(
|
|
|
|
stringifyHash(hash),
|
|
|
|
new Packer().pack(map).takeBytes(),
|
|
|
|
);
|
|
|
|
}
|
2023-08-30 18:37:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
export function stringifyBytes(data: Uint8Array) {
|
|
|
|
return String.fromCharCode(...data);
|
|
|
|
}
|
|
|
|
|
|
|
|
function stringifyHash(hash: Multihash) {
|
|
|
|
return stringifyBytes(hash.fullBytes);
|
|
|
|
}
|
|
|
|
export function stringifyNode(node: NodeId) {
|
|
|
|
return stringifyBytes(node.bytes);
|
|
|
|
}
|