248 lines
8.4 KiB
TypeScript
Raw Normal View History

2020-05-14 15:20:39 +03:00
/*
2020-05-14 17:30:17 +03:00
* Copyright 2020 Fluence Labs Limited
2020-05-14 15:20:39 +03:00
*
2020-05-14 17:30:17 +03:00
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
2020-05-14 15:20:39 +03:00
*
2020-05-14 17:30:17 +03:00
* http://www.apache.org/licenses/LICENSE-2.0
2020-05-14 15:20:39 +03:00
*
2020-05-14 17:30:17 +03:00
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
2020-05-14 15:20:39 +03:00
*/
import { PeerIdB58 } from '@fluencelabs/interfaces';
import { pipe } from 'it-pipe';
import { decode, encode } from 'it-length-prefixed';
feat!: Unify all packages (#327) * * Separate marine worker as a package * Trying to fix tests * Finalizing test fixes * fix: rename back to Fluence CLI (#320) chore: rename back to Fluence CLI * fix(deps): update dependency @fluencelabs/avm to v0.43.1 (#322) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore: release master (#324) * chore: release master * chore: Regenerate pnpm lock file * feat: use marine-js 0.7.2 (#321) * use marine-js 0.5.0 * increace some timeouts * increace some timeouts * use latest marine + remove larger timeouts * propagate CallParameters type * use marine 0.7.2 * Temp use node 18 and 20 * Comment out node 20.x --------- Co-authored-by: Anatoly Laskaris <github_me@nahsi.dev> * chore: Fix test with node 18/20 error message (#323) * Fix test with node 18/20 error message * Run tests on node 18 and 20 * Enhance description * Fix type and obj property --------- Co-authored-by: Anatoly Laskaris <github_me@nahsi.dev> * * Separate marine worker as a package * Trying to fix tests * Finalizing test fixes * * Refactoring packages. * Using CDN to load .wasm deps. * Setting up tests for new architecture * Fix almost all tests * Fix last strange test * Remove package specific packages * Remove avm class as it looks excessive * marine worker new version * misc refactoring/remove console.log's * Rename package js-peer to js-client * Move service info to marine worker * Change CDN path * Fix worker race confition * Remove buffer type * Remove turned off headless mode in platform tests * Remove async keyword to make tests pass * Remove util package * Make js-client.api package just reexport interface from js-client main package * Update package info in CI * Fix review comments * Remove test entry from marine-worker package * Misc fixes * Fix worker type * Add fetchers * Specify correct versions for js-client package * Set first ver for js-client * Update libp2p and related dep versions to the latest * Build all deps into package itself * Fix review * Refine package * Fix comment * Update packages/core/js-client/src/fetchers/browser.ts * Update packages/core/js-client/src/fetchers/index.ts * Update packages/core/js-client/src/fetchers/node.ts * Update packages/core/js-client/src/jsPeer/FluencePeer.ts * Update packages/core/js-client/src/keypair/__test__/KeyPair.spec.ts * Update packages/core/js-client/src/jsPeer/FluencePeer.ts Co-authored-by: shamsartem <shamsartem@gmail.com> * Delete outdated file * Need types for build to work * Inline func call * Add comments to replacement lines. P.S. we can remove some of them after update libp2p --------- Co-authored-by: shamsartem <shamsartem@gmail.com> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: fluencebot <116741523+fluencebot@users.noreply.github.com> Co-authored-by: Valery Antopol <valery.antopol@gmail.com> Co-authored-by: Anatoly Laskaris <github_me@nahsi.dev>
2023-08-25 00:15:49 +07:00
import type { PeerId } from '@libp2p/interface/peer-id';
import { createLibp2p, Libp2p } from 'libp2p';
import { noise } from '@chainsafe/libp2p-noise';
import { yamux } from '@chainsafe/libp2p-yamux';
import { webSockets } from '@libp2p/websockets';
import { all } from '@libp2p/websockets/filters';
import { multiaddr, type Multiaddr } from '@multiformats/multiaddr';
import map from 'it-map';
import { fromString } from 'uint8arrays/from-string';
import { toString } from 'uint8arrays/to-string';
import { logger } from '../util/logger.js';
import { Subject } from 'rxjs';
import { throwIfHasNoPeerId } from '../util/libp2pUtils.js';
import { IConnection } from './interfaces.js';
import { IParticle } from '../particle/interfaces.js';
import { buildParticleMessage, Particle, serializeToString, verifySignature } from '../particle/Particle.js';
import { identifyService } from 'libp2p/identify';
import { pingService } from 'libp2p/ping';
import { unmarshalPublicKey } from '@libp2p/crypto/keys';
import { peerIdFromString } from '@libp2p/peer-id';
import { Stream } from '@libp2p/interface/connection';
import { KeyPair } from '../keypair/index.js';
const log = logger('connection');
2020-05-14 15:20:39 +03:00
2021-08-24 17:37:03 +03:00
export const PROTOCOL_NAME = '/fluence/particle/2.0.0';
2020-05-14 15:20:39 +03:00
/**
* Options to configure fluence relay connection
*/
export interface RelayConnectionConfig {
/**
* Peer id of the Fluence Peer
*/
peerId: PeerId;
/**
* Multiaddress of the relay to make connection to
*/
relayAddress: Multiaddr;
/**
* The dialing timeout in milliseconds
*/
dialTimeoutMs?: number;
/**
* The maximum number of inbound streams for the libp2p node.
* Default: 1024
*/
maxInboundStreams: number;
/**
* The maximum number of outbound streams for the libp2p node.
* Default: 1024
*/
maxOutboundStreams: number;
}
/**
* Implementation for JS peers which connects to Fluence through relay node
*/
export class RelayConnection implements IConnection {
private relayAddress: Multiaddr;
private lib2p2Peer: Libp2p | null = null;
constructor(private config: RelayConnectionConfig) {
this.relayAddress = multiaddr(this.config.relayAddress);
throwIfHasNoPeerId(this.relayAddress);
}
getRelayPeerId(): string {
// since we check for peer id in constructor, we can safely use ! here
return this.relayAddress.getPeerId()!;
}
supportsRelay(): boolean {
return true;
}
2021-04-13 15:11:52 +03:00
particleSource = new Subject<IParticle>();
async start(): Promise<void> {
// check if already started
if (this.lib2p2Peer !== null) {
return;
}
2021-04-13 15:11:52 +03:00
this.lib2p2Peer = await createLibp2p({
peerId: this.config.peerId,
transports: [
webSockets({
filter: all,
}),
],
streamMuxers: [yamux()],
connectionEncryption: [noise()],
connectionManager: {
dialTimeout: this.config.dialTimeoutMs,
},
feat!: Unify all packages (#327) * * Separate marine worker as a package * Trying to fix tests * Finalizing test fixes * fix: rename back to Fluence CLI (#320) chore: rename back to Fluence CLI * fix(deps): update dependency @fluencelabs/avm to v0.43.1 (#322) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> * chore: release master (#324) * chore: release master * chore: Regenerate pnpm lock file * feat: use marine-js 0.7.2 (#321) * use marine-js 0.5.0 * increace some timeouts * increace some timeouts * use latest marine + remove larger timeouts * propagate CallParameters type * use marine 0.7.2 * Temp use node 18 and 20 * Comment out node 20.x --------- Co-authored-by: Anatoly Laskaris <github_me@nahsi.dev> * chore: Fix test with node 18/20 error message (#323) * Fix test with node 18/20 error message * Run tests on node 18 and 20 * Enhance description * Fix type and obj property --------- Co-authored-by: Anatoly Laskaris <github_me@nahsi.dev> * * Separate marine worker as a package * Trying to fix tests * Finalizing test fixes * * Refactoring packages. * Using CDN to load .wasm deps. * Setting up tests for new architecture * Fix almost all tests * Fix last strange test * Remove package specific packages * Remove avm class as it looks excessive * marine worker new version * misc refactoring/remove console.log's * Rename package js-peer to js-client * Move service info to marine worker * Change CDN path * Fix worker race confition * Remove buffer type * Remove turned off headless mode in platform tests * Remove async keyword to make tests pass * Remove util package * Make js-client.api package just reexport interface from js-client main package * Update package info in CI * Fix review comments * Remove test entry from marine-worker package * Misc fixes * Fix worker type * Add fetchers * Specify correct versions for js-client package * Set first ver for js-client * Update libp2p and related dep versions to the latest * Build all deps into package itself * Fix review * Refine package * Fix comment * Update packages/core/js-client/src/fetchers/browser.ts * Update packages/core/js-client/src/fetchers/index.ts * Update packages/core/js-client/src/fetchers/node.ts * Update packages/core/js-client/src/jsPeer/FluencePeer.ts * Update packages/core/js-client/src/keypair/__test__/KeyPair.spec.ts * Update packages/core/js-client/src/jsPeer/FluencePeer.ts Co-authored-by: shamsartem <shamsartem@gmail.com> * Delete outdated file * Need types for build to work * Inline func call * Add comments to replacement lines. P.S. we can remove some of them after update libp2p --------- Co-authored-by: shamsartem <shamsartem@gmail.com> Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: fluencebot <116741523+fluencebot@users.noreply.github.com> Co-authored-by: Valery Antopol <valery.antopol@gmail.com> Co-authored-by: Anatoly Laskaris <github_me@nahsi.dev>
2023-08-25 00:15:49 +07:00
connectionGater: {
// By default, this function forbids connections to private peers. For example multiaddr with ip 127.0.0.1 isn't allowed
denyDialMultiaddr: () => Promise.resolve(false),
},
services: {
identify: identifyService(),
ping: pingService(),
},
2020-05-14 15:20:39 +03:00
});
const supportedProtocols = (await this.lib2p2Peer.peerStore.get(this.lib2p2Peer.peerId)).protocols;
await this.lib2p2Peer.peerStore.patch(this.lib2p2Peer.peerId, {
protocols: [...supportedProtocols, PROTOCOL_NAME],
});
await this.connect();
}
async stop(): Promise<void> {
// check if already stopped
if (this.lib2p2Peer === null) {
return;
}
2020-05-14 15:20:39 +03:00
await this.lib2p2Peer.unhandle(PROTOCOL_NAME);
await this.lib2p2Peer.stop();
2020-05-14 15:20:39 +03:00
}
async sendParticle(nextPeerIds: PeerIdB58[], particle: IParticle): Promise<void> {
if (this.lib2p2Peer === null) {
throw new Error('Relay connection is not started');
}
2020-05-14 15:20:39 +03:00
if (nextPeerIds.length !== 1 && nextPeerIds[0] !== this.getRelayPeerId()) {
throw new Error(
`Relay connection only accepts peer id of the connected relay. Got: ${JSON.stringify(
nextPeerIds,
)} instead.`,
);
}
log.trace('sending particle...');
// Reusing active connection here
const stream = await this.lib2p2Peer.dialProtocol(this.relayAddress, PROTOCOL_NAME);
log.trace('created stream with id ', stream.id);
const sink = stream.sink;
2020-05-14 15:20:39 +03:00
await pipe([fromString(serializeToString(particle))], encode(), sink);
log.trace('data written to sink');
2020-05-14 15:20:39 +03:00
}
private async processIncomingMessage(msg: string, stream: Stream) {
let particle: Particle | undefined;
try {
particle = Particle.fromString(msg);
log.trace('got particle from stream with id %s and particle id %s', stream.id, particle.id);
const initPeerId = peerIdFromString(particle.initPeerId);
if (initPeerId.publicKey === undefined) {
log.error(
'cannot retrieve public key from init_peer_id. particle id: %s. init_peer_id: %s',
particle.id,
particle.initPeerId,
);
return;
}
const isVerified = await KeyPair.verifyWithPublicKey(
initPeerId.publicKey,
buildParticleMessage(particle),
particle.signature,
);
if (isVerified) {
this.particleSource.next(particle);
} else {
log.trace('particle signature is incorrect. rejecting particle with id: %s', particle.id);
}
} catch (e) {
const particleId = particle?.id;
const particleIdMessage = typeof particleId === 'string' ? `. particle id: ${particleId}` : '';
log.error(`error on handling an incoming message: %O%s`, e, particleIdMessage);
}
}
private async connect() {
if (this.lib2p2Peer === null) {
throw new Error('Relay connection is not started');
}
await this.lib2p2Peer.handle(
[PROTOCOL_NAME],
async ({ connection, stream }) =>
pipe(
stream.source,
decode(),
(source) => map(source, (buf) => toString(buf.subarray())),
async (source) => {
try {
for await (const msg of source) {
await this.processIncomingMessage(msg, stream);
}
} catch (e) {
log.error('connection closed: %j', e);
}
},
),
{
maxInboundStreams: this.config.maxInboundStreams,
maxOutboundStreams: this.config.maxOutboundStreams,
},
);
log.debug("dialing to the node with client's address: %s", this.lib2p2Peer.peerId.toString());
try {
await this.lib2p2Peer.dial(this.relayAddress);
} catch (e: any) {
if (e.name === 'AggregateError' && e._errors?.length === 1) {
const error = e._errors[0];
throw new Error(`Error dialing node ${this.relayAddress}:\n${error.code}\n${error.message}`);
} else {
throw e;
}
}
}
2020-06-19 14:29:06 +03:00
}