intro: 4-ipfs-code-execution (#15)

This commit is contained in:
folex 2021-07-21 11:37:25 +03:00 committed by GitHub
parent 5ecce263a6
commit e5e0d59835
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
35 changed files with 31554 additions and 0 deletions

16
intro/4-ipfs-code-execution/.gitignore vendored Normal file
View File

@ -0,0 +1,16 @@
# Generated by Cargo
# will have compiled files and executables
**/target/
# These are backup files generated by rustfmt
**/*.rs.bk
# Added by cargo
.idea
**/artifacts/*.wasm
**/node_modules
**/dist
.DS_store
.bic_cache

View File

@ -0,0 +1,20 @@
# IPFS code execution
This example showcases 2 things:
1. how it's possible to store .wasm modules on IPFS, then deploy them to Fluence as a service
2. ability to process IPFS files via a Fluence service. In this example, we get a size of a file
## How to run & use it
### Web
1. Run it
```
cd web
npm i
npm start
```
2. Press "deploy"
3. Copy WASM service CID and press "get_size"
## Aqua implementation
The business logic is implemented in Aqua in [process.aqua](aqua/src/process_files.aqua)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,42 @@
{
"name": "@fluencelabs/ipfs-execution",
"version": "0.1.0",
"description": "An example of executing WASM code from IPFS over IPFS files",
"main": "dist/index.js",
"scripts": {
"compile-aqua": "aqua-cli -i . -o .",
"prebuild": "npm run compile-aqua",
"build": "tsc",
"prestart:local": "npm run build",
"start:local": "node dist/index.js local",
"prestart:remote": "npm run build",
"start:remote": "node dist/index.js stage",
"start": "npm run start:remote"
},
"keywords": [
"fluence",
"wasm",
"ipfs",
"functions",
"faas",
"decentralization",
"p2p",
"libp2p"
],
"author": "Fluence Labs",
"license": "MIT",
"dependencies": {
"@fluencelabs/aqua-ipfs": "^0.1.8",
"@fluencelabs/fluence": "0.9.53",
"@fluencelabs/fluence-network-environment": "1.0.10",
"@fluencelabs/aqua-lib": "0.1.9",
"ipfs-http-client": "^50.1.2",
"it-all": "^1.0.5",
"uint8arrays": "^2.1.5",
"multiaddr": "^10.0.0"
},
"devDependencies": {
"typescript": "^3.9.5",
"@fluencelabs/aqua-cli": "0.1.9-164"
}
}

View File

@ -0,0 +1,7 @@
data SizeResult:
size: u32
success: bool
error: string
service ProcessFiles:
file_size(file_path: string) -> SizeResult

View File

@ -0,0 +1,84 @@
/*
* Copyright 2020 Fluence Labs Limited
*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
import { set_timeout } from "@fluencelabs/aqua-ipfs";
import {createClient, FluenceClient, setLogLevel} from "@fluencelabs/fluence";
import {stage, krasnodar, Node, testNet} from "@fluencelabs/fluence-network-environment";
import { provideFile, globSource, urlSource } from "./provider";
import { deploy_service, get_file_size, remove_service } from "./process";
async function main(environment: Node[]) {
// setLogLevel('DEBUG');
let providerHost = environment[0];
let providerClient = await createClient(providerHost);
console.log("📘 uploading .wasm to node %s", providerHost.multiaddr);
let path = globSource('../service/artifacts/process_files.wasm');
let { file, swarmAddr, rpcAddr } = await provideFile(path, providerClient);
console.log("📗 swarmAddr", swarmAddr);
console.log("📗 rpcAddr", rpcAddr);
const fluence = await createClient(environment[1]);
console.log("📗 created a fluence client %s with relay %s", fluence.selfPeerId, fluence.relayPeerId);
// default IPFS timeout is 1 sec, set to 10 secs to retrieve file from remote node
await set_timeout(fluence, environment[2].peerId, 10);
console.log("\n\n📘 Will deploy ProcessFiles service");
let service_id = await deploy_service(
fluence,
environment[2].peerId, file.cid.toString(), rpcAddr,
(msg, value) => console.log(msg, value),
{ ttl: 10000 }
)
console.log("📗 ProcessFiles service is now deployed and available as", service_id);
console.log("\n\n📘 Will upload file & calculate its size");
let { file: newFile } = await provideFile(urlSource("https://i.imgur.com/NZgK6DB.png"), providerClient);
let fileSize = await get_file_size(
fluence,
environment[2].peerId, newFile.cid.toString(), rpcAddr, service_id,
{ ttl: 10000 }
)
console.log("📗 Calculated file size:", fileSize)
let result = await remove_service(fluence, environment[2].peerId, service_id);
console.log("📕 ProcessFiles service removed", result);
return;
}
let args = process.argv.slice(2);
var environment: Node[];
if (args.length >= 1 && args[0] == "testnet") {
environment = testNet;
console.log("📘 Will connect to testNet");
} else if (args[0] == "stage") {
environment = stage;
console.log("📘 Will connect to stage");
} else if (args[0] == "krasnodar") {
environment = krasnodar;
console.log("📘 Will connect to krasnodar");
} else {
throw "Specify environment";
}
main(environment)
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});

View File

@ -0,0 +1,2 @@
export * from './provider'
export * from './process'

View File

@ -0,0 +1,45 @@
import "@fluencelabs/aqua-lib/builtin.aqua"
import "@fluencelabs/aqua-ipfs/ipfs.aqua"
import "process_files.aqua"
alias PeerId : string
alias CID : string
alias Multiaddr : string
alias Hash : string
alias ServiceID : string
service NewDist("dist"):
default_module_config(name: string) -> ModuleConfig
add_module_from_vault(path: string, config: ModuleConfig) -> Hash
service NewOp("op"):
concat_strings(a: string, b: string) -> string
array(s: string) -> []string
func deploy_service(relay: PeerId, cid: CID, provider_ipfs: Multiaddr, log: string, u32 -> ()) -> ServiceID:
on relay:
get_result <- Ipfs.get_from(cid, provider_ipfs)
config <- NewDist.default_module_config("process_files")
module_hash <- NewDist.add_module_from_vault(get_result.path, config)
prefixed_hash <- NewOp.concat_strings("hash:", module_hash)
dependencies <- NewOp.array(prefixed_hash)
blueprint <- Dist.make_blueprint("process_files", dependencies)
blueprint_id <- Dist.add_blueprint(blueprint)
service_id <- Srv.create(blueprint_id)
ProcessFiles service_id
size <- ProcessFiles.file_size(get_result.path)
log("Size of the .wasm module is", size.size)
<- service_id
func get_file_size(relay: PeerId, cid: CID, provider_ipfs: Multiaddr, service_id: ServiceID) -> SizeResult:
ProcessFiles service_id
on relay:
get_result <- Ipfs.get_from(cid, provider_ipfs)
size <- ProcessFiles.file_size(get_result.path)
<- size
func remove_service(relay: PeerId, service_id: ServiceID) -> bool:
on relay:
Srv.remove(service_id)
<- true

View File

@ -0,0 +1,274 @@
/**
*
* This file is auto-generated. Do not edit manually: changes may be erased.
* Generated by Aqua compiler: https://github.com/fluencelabs/aqua/.
* If you find any bugs, please write an issue on GitHub: https://github.com/fluencelabs/aqua/issues
* Aqua version: 0.1.9-164
*
*/
import { FluenceClient, PeerIdB58 } from '@fluencelabs/fluence';
import { RequestFlowBuilder } from '@fluencelabs/fluence/dist/api.unstable';
import { RequestFlow } from '@fluencelabs/fluence/dist/internal/RequestFlow';
export async function deploy_service(client: FluenceClient, relay: string, cid: string, provider_ipfs: string, log: (arg0: string, arg1: number) => void, config?: {ttl?: number}): Promise<string> {
let request: RequestFlow;
const promise = new Promise<string>((resolve, reject) => {
const r = new RequestFlowBuilder()
.disableInjections()
.withRawScript(
`
(xor
(seq
(seq
(seq
(seq
(seq
(seq
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
(call %init_peer_id% ("getDataSrv" "relay") [] relay)
)
(call %init_peer_id% ("getDataSrv" "cid") [] cid)
)
(call %init_peer_id% ("getDataSrv" "provider_ipfs") [] provider_ipfs)
)
(call -relay- ("op" "noop") [])
)
(xor
(seq
(seq
(seq
(seq
(seq
(seq
(seq
(seq
(seq
(seq
(call relay ("ipfs-adapter" "get_from") [cid provider_ipfs] get_result)
(call relay ("dist" "default_module_config") ["process_files"] config)
)
(call relay ("dist" "add_module_from_vault") [get_result.$.path! config] module_hash)
)
(call relay ("op" "concat_strings") ["hash:" module_hash] prefixed_hash)
)
(call relay ("op" "array") [prefixed_hash] dependencies)
)
(call relay ("dist" "make_blueprint") ["process_files" dependencies] blueprint)
)
(call relay ("dist" "add_blueprint") [blueprint] blueprint_id)
)
(call relay ("srv" "create") [blueprint_id] service_id)
)
(call relay (service_id "file_size") [get_result.$.path!] size)
)
(call -relay- ("op" "noop") [])
)
(xor
(call %init_peer_id% ("callbackSrv" "log") ["Size of the .wasm module is" size.$.size!])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 1])
)
)
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 2])
)
)
(xor
(call %init_peer_id% ("callbackSrv" "response") [service_id])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 3])
)
)
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 4])
)
`,
)
.configHandler((h) => {
h.on('getDataSrv', '-relay-', () => {
return client.relayPeerId!;
});
h.on('getDataSrv', 'relay', () => {return relay;});
h.on('getDataSrv', 'cid', () => {return cid;});
h.on('getDataSrv', 'provider_ipfs', () => {return provider_ipfs;});
h.on('callbackSrv', 'log', (args) => {log(args[0], args[1]); return {};});
h.onEvent('callbackSrv', 'response', (args) => {
const [res] = args;
resolve(res);
});
h.onEvent('errorHandlingSrv', 'error', (args) => {
// assuming error is the single argument
const [err] = args;
reject(err);
});
})
.handleScriptError(reject)
.handleTimeout(() => {
reject('Request timed out for deploy_service');
})
if(config && config.ttl) {
r.withTTL(config.ttl)
}
request = r.build();
});
await client.initiateFlow(request!);
return promise;
}
export async function get_file_size(client: FluenceClient, relay: string, cid: string, provider_ipfs: string, service_id: string, config?: {ttl?: number}): Promise<{error:string;size:number;success:boolean}> {
let request: RequestFlow;
const promise = new Promise<{error:string;size:number;success:boolean}>((resolve, reject) => {
const r = new RequestFlowBuilder()
.disableInjections()
.withRawScript(
`
(xor
(seq
(seq
(seq
(seq
(seq
(seq
(seq
(seq
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
(call %init_peer_id% ("getDataSrv" "relay") [] relay)
)
(call %init_peer_id% ("getDataSrv" "cid") [] cid)
)
(call %init_peer_id% ("getDataSrv" "provider_ipfs") [] provider_ipfs)
)
(call %init_peer_id% ("getDataSrv" "service_id") [] service_id)
)
(call -relay- ("op" "noop") [])
)
(xor
(seq
(call relay ("ipfs-adapter" "get_from") [cid provider_ipfs] get_result)
(call relay (service_id "file_size") [get_result.$.path!] size)
)
(seq
(call -relay- ("op" "noop") [])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 1])
)
)
)
(call -relay- ("op" "noop") [])
)
(xor
(call %init_peer_id% ("callbackSrv" "response") [size])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 2])
)
)
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 3])
)
`,
)
.configHandler((h) => {
h.on('getDataSrv', '-relay-', () => {
return client.relayPeerId!;
});
h.on('getDataSrv', 'relay', () => {return relay;});
h.on('getDataSrv', 'cid', () => {return cid;});
h.on('getDataSrv', 'provider_ipfs', () => {return provider_ipfs;});
h.on('getDataSrv', 'service_id', () => {return service_id;});
h.onEvent('callbackSrv', 'response', (args) => {
const [res] = args;
resolve(res);
});
h.onEvent('errorHandlingSrv', 'error', (args) => {
// assuming error is the single argument
const [err] = args;
reject(err);
});
})
.handleScriptError(reject)
.handleTimeout(() => {
reject('Request timed out for get_file_size');
})
if(config && config.ttl) {
r.withTTL(config.ttl)
}
request = r.build();
});
await client.initiateFlow(request!);
return promise;
}
export async function remove_service(client: FluenceClient, relay: string, service_id: string, config?: {ttl?: number}): Promise<boolean> {
let request: RequestFlow;
const promise = new Promise<boolean>((resolve, reject) => {
const r = new RequestFlowBuilder()
.disableInjections()
.withRawScript(
`
(xor
(seq
(seq
(seq
(seq
(seq
(seq
(call %init_peer_id% ("getDataSrv" "-relay-") [] -relay-)
(call %init_peer_id% ("getDataSrv" "relay") [] relay)
)
(call %init_peer_id% ("getDataSrv" "service_id") [] service_id)
)
(call -relay- ("op" "noop") [])
)
(xor
(call relay ("srv" "remove") [service_id])
(seq
(call -relay- ("op" "noop") [])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 1])
)
)
)
(call -relay- ("op" "noop") [])
)
(xor
(call %init_peer_id% ("callbackSrv" "response") [true])
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 2])
)
)
(call %init_peer_id% ("errorHandlingSrv" "error") [%last_error% 3])
)
`,
)
.configHandler((h) => {
h.on('getDataSrv', '-relay-', () => {
return client.relayPeerId!;
});
h.on('getDataSrv', 'relay', () => {return relay;});
h.on('getDataSrv', 'service_id', () => {return service_id;});
h.onEvent('callbackSrv', 'response', (args) => {
const [res] = args;
resolve(res);
});
h.onEvent('errorHandlingSrv', 'error', (args) => {
// assuming error is the single argument
const [err] = args;
reject(err);
});
})
.handleScriptError(reject)
.handleTimeout(() => {
reject('Request timed out for remove_service');
})
if(config && config.ttl) {
r.withTTL(config.ttl)
}
request = r.build();
});
await client.initiateFlow(request!);
return promise;
}

View File

@ -0,0 +1,7 @@
data SizeResult:
size: u32
success: bool
error: string
service ProcessFiles:
file_size(file_path: string) -> SizeResult

View File

@ -0,0 +1,43 @@
export const { create, globSource, urlSource, CID } = require('ipfs-http-client');
import { Multiaddr, protocols } from 'multiaddr';
import { get_external_swarm_multiaddr, get_external_api_multiaddr } from "@fluencelabs/aqua-ipfs";
import { FluenceClient } from "@fluencelabs/fluence";
export async function provideFile(source: any, provider: FluenceClient): Promise<{ file: typeof CID, swarmAddr: string, rpcAddr: string }> {
var swarmAddr;
var result = await get_external_swarm_multiaddr(provider, provider.relayPeerId!);
if (result.success) {
swarmAddr = result.multiaddr;
} else {
console.error("Failed to retrieve external swarm multiaddr from %s: ", provider.relayPeerId);
throw result.error;
}
var rpcAddr;
var result = await get_external_api_multiaddr(provider, provider.relayPeerId!);
if (result.success) {
rpcAddr = result.multiaddr;
} else {
console.error("Failed to retrieve external api multiaddr from %s: ", provider.relayPeerId);
throw result.error;
}
var rpcMaddr = new Multiaddr(rpcAddr).decapsulateCode(protocols.names.p2p.code);
const ipfs = create(rpcMaddr);
console.log("📗 created ipfs client to %s", rpcMaddr);
await ipfs.id();
console.log("📗 connected to ipfs");
const file = await ipfs.add(source);
console.log("📗 uploaded file:", file);
// To download the file, uncomment the following code:
// let files = await ipfs.get(file.cid);
// for await (const file of files) {
// const content = uint8ArrayConcat(await all(file.content));
// console.log("📗 downloaded file of length ", content.length);
// }
return { file, swarmAddr, rpcAddr };
}

View File

@ -0,0 +1,62 @@
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig.json to read more about this file */
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "lib": [], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
// "checkJs": true, /* Report errors in .js files. */
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
"declaration": true, /* Generates corresponding '.d.ts' file. */
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./dist", /* Redirect output structure to the directory. */
//"rootDir": ".", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */
// "noEmit": true, /* Do not emit outputs. */
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
/* Strict Type-Checking Options */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* Enable strict null checks. */
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
// "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
/* Additional Checks */
// "noUnusedLocals": true, /* Report errors on unused locals. */
// "noUnusedParameters": true, /* Report errors on unused parameters. */
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
/* Module Resolution Options */
// "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
// "baseUrl": "./", /* Base directory to resolve non-absolute module names. */
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
// "typeRoots": [], /* List of folders to include type definitions from. */
// "types": [], /* Type declaration files to be included in compilation. */
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
"esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
/* Source Map Options */
// "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
/* Experimental Options */
// "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
// "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
/* Advanced Options */
"skipLibCheck": true, /* Skip type checking of declaration files. */
"forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */
}
}

View File

@ -0,0 +1,85 @@
# management secret key is NAB5rGwT4qOEB+6nLQawkTfCOV2eiFSjgQK8bfEdZXY=
services:
fluence-0: # /ip4/127.0.0.1/tcp/9990/ws/p2p/12D3KooWHBG9oaVx4i3vi6c1rSBUm7MLBmyGmmbHoZ23pmjDCnvK
command: -f ed25519 -k 29Apzfedhw2Jxh94Jj4rNSmavQ1TkNe8ALYRA7bMegobwp423aLrURxLk32WtXgXHDqoSz7GAT9fQfoMhVd1e5Ww -m 12D3KooWFRgVmb1uWcmCbmJqLr8tBQghL6ysSpK2VyE2VZbaQ6wy -t 7770 -w 9990 --bootstraps /ip4/127.0.0.1/tcp/7771 /ip4/127.0.0.1/tcp/7772
container_name: fluence-0
environment:
RUST_BACKTRACE: full
RUST_LOG: info,network=trace,aquamarine=info,aquamarine::actor=info,tokio_threadpool=info,tokio_reactor=info,mio=info,tokio_io=info,soketto=info,yamux=info,multistream_select=info,libp2p_secio=info,libp2p_websocket::framed=info,libp2p_ping=info,libp2p_core::upgrade::apply=info,libp2p_kad::kbucket=info,cranelift_codegen=info,wasmer_wasi=info,async_io=info,polling=info,wasmer_interface_types_fl=info,cranelift_codegen=info,wasmer_wasi=info,async_io=info,polling=info,wasmer_interface_types_fl=info,particle_server::behaviour::identify=info,libp2p_mplex=info,libp2p_identify=info,walrus=info,particle_protocol::libp2p_protocol::upgrade=info,kademlia::behaviour=info
WASM_LOG: info
IPFS_API_PORT: 5000
IPFS_SWARM_PORT: 4000
FLUENCE_ENV_IPFS_ADAPTER_EXTERNAL_API_MULTIADDR: '/ip4/127.0.0.1/tcp/5000'
FLUENCE_ENV_IPFS_ADAPTER_EXTERNAL_SWARM_MULTIADDR: '/ip4/127.0.0.1/tcp/4000'
image: fluencelabs/node:latest
ports:
- 7770:7770 # tcp
- 9990:9990 # ws
- 5000:5001 # ipfs
- 18080:18080 # /metrics
restart: always
volumes:
- fluence-0:/.fluence
- data-0:/config
networks:
- fluence
fluence-1: # /ip4/127.0.0.1/tcp/9991/ws/p2p/12D3KooWRABanQHUn28dxavN9ZS1zZghqoZVAYtFpoN7FdtoGTFv
command: -f ed25519 -k 5fNENMwkUT4dW3hPs9ZwqV4qA5pdTtUChTazAx9Awe2Vpz1yaJu3VCmcEZow6YgdFBGoZoFAZUZBbF3c2Ebd2iL -m 12D3KooWFRgVmb1uWcmCbmJqLr8tBQghL6ysSpK2VyE2VZbaQ6wy -t 7771 -w 9991 --bootstraps /ip4/127.0.0.1/tcp/7770 /ip4/127.0.0.1/tcp/7772 #/dns4/kras-00.fluence.dev/tcp/7770
container_name: fluence-1
environment:
RUST_BACKTRACE: full
RUST_LOG: info,network=trace,aquamarine=info,aquamarine::actor=info,tokio_threadpool=info,tokio_reactor=info,mio=info,tokio_io=info,soketto=info,yamux=info,multistream_select=info,libp2p_secio=info,libp2p_websocket::framed=info,libp2p_ping=info,libp2p_core::upgrade::apply=info,libp2p_kad::kbucket=info,cranelift_codegen=info,wasmer_wasi=info,async_io=info,polling=info,wasmer_interface_types_fl=info,cranelift_codegen=info,wasmer_wasi=info,async_io=info,polling=info,wasmer_interface_types_fl=info,particle_server::behaviour::identify=info,libp2p_mplex=info,libp2p_identify=info,walrus=info,particle_protocol::libp2p_protocol::upgrade=info,kademlia::behaviour=info
WASM_LOG: info
IPFS_API_PORT: 5001
IPFS_SWARM_PORT: 4001
FLUENCE_ENV_IPFS_ADAPTER_EXTERNAL_API_MULTIADDR: '/ip4/127.0.0.1/tcp/5001'
FLUENCE_ENV_IPFS_ADAPTER_EXTERNAL_SWARM_MULTIADDR: '/ip4/127.0.0.1/tcp/4001'
image: fluencelabs/node:latest
ports:
- 7771:7771 # tcp
- 9991:9991 # ws
- 5001:5001 # ipfs
- 18081:18080 # /metrics
restart: always
volumes:
- fluence-1:/.fluence
- data-1:/config
networks:
- fluence
fluence-2: # /ip4/127.0.0.1/tcp/9992/ws/p2p/12D3KooWFpQ7LHxcC9FEBUh3k4nSCC12jBhijJv3gJbi7wsNYzJ5
command: -f ed25519 -k 5DTs9LQS8Ay2dM8xBcikDRwYLMcanhsC6tynSSgpLyBZEv5Ey34LVw1fYcCuUj9A9EfvQJB2bsaGhSRoHQ7D6UE5 -m 12D3KooWFRgVmb1uWcmCbmJqLr8tBQghL6ysSpK2VyE2VZbaQ6wy -t 7772 -w 9992 --bootstraps /ip4/127.0.0.1/tcp/7770 /ip4/127.0.0.1/tcp/7771 #/dns4/kras-00.fluence.dev/tcp/7770
container_name: fluence-2
environment:
RUST_BACKTRACE: full
RUST_LOG: info,network=trace,aquamarine=info,aquamarine::actor=info,tokio_threadpool=info,tokio_reactor=info,mio=info,tokio_io=info,soketto=info,yamux=info,multistream_select=info,libp2p_secio=info,libp2p_websocket::framed=info,libp2p_ping=info,libp2p_core::upgrade::apply=info,libp2p_kad::kbucket=info,cranelift_codegen=info,wasmer_wasi=info,async_io=info,polling=info,wasmer_interface_types_fl=info,cranelift_codegen=info,wasmer_wasi=info,async_io=info,polling=info,wasmer_interface_types_fl=info,particle_server::behaviour::identify=info,libp2p_mplex=info,libp2p_identify=info,walrus=info,particle_protocol::libp2p_protocol::upgrade=info,kademlia::behaviour=info
WASM_LOG: info
IPFS_API_PORT: 5002
IPFS_SWARM_PORT: 4002
FLUENCE_ENV_IPFS_ADAPTER_EXTERNAL_API_MULTIADDR: '/ip4/127.0.0.1/tcp/5002'
FLUENCE_ENV_IPFS_ADAPTER_EXTERNAL_SWARM_MULTIADDR: '/ip4/127.0.0.1/tcp/4002'
image: fluencelabs/node:latest
ports:
- 7772:7772 # tcp
- 9992:9992 # ws
- 5002:5001 # ipfs
- 18082:18080 # /metrics
restart: always
volumes:
- fluence-2:/.fluence
- data-2:/config
networks:
- fluence
version: "3.5"
volumes:
fluence-0:
fluence-1:
fluence-2:
data-0:
data-1:
data-2:
networks:
fluence:

View File

@ -0,0 +1,4 @@
debug/
target/
**/*.bk
**/*.bak

View File

@ -0,0 +1,21 @@
[package]
name = "process_files"
version = "0.1.0"
authors = ["Fluence Labs"]
edition = "2018"
description = "Marine service that processes files in various ways"
license = "Apache-2.0"
[[bin]]
name = "process_files"
path = "src/main.rs"
[dependencies]
marine-rs-sdk = { version="0.6.11", features=["logger"] }
log = "0.4.14"
[dev-dependencies]
marine-rs-sdk-test = "0.1.11"
[profile.release]
opt-level = "s"

View File

@ -0,0 +1,6 @@
modules_dir = "artifacts/"
[[module]]
name = "process_files"
mem_pages_count = 1
logger_enabled = true

View File

@ -0,0 +1,8 @@
#!/usr/bin/env bash -o errexit -o nounset -o pipefail
cargo update --aggressive
mkdir -p artifacts
rm -f artifacts/*.wasm
marine build --release
cp target/wasm32-wasi/release/process_files.wasm artifacts/
marine aqua artifacts/process_files.wasm >../aqua/process_files.aqua

View File

@ -0,0 +1,37 @@
/*
* Copyright 2021 Fluence Labs Limited
*
* 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
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* 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.
*/
use marine_rs_sdk::{marine, module_manifest};
module_manifest!();
pub fn main() { }
#[marine]
pub struct SizeResult {
pub size: u32,
pub success: bool,
pub error: String,
}
#[marine]
pub fn file_size(file_path: String) -> SizeResult {
match std::fs::read(file_path) {
Ok(bytes) => SizeResult { size: bytes.len() as _, success: true, error: String::new() },
Err(err) => SizeResult { size: 0, success: false, error: err.to_string() },
}
}

View File

@ -0,0 +1,23 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# production
/build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
npm-debug.log*
yarn-debug.log*
yarn-error.log*

View File

@ -0,0 +1,2 @@
# Getting Started with Fluence

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,53 @@
{
"name": "ipfs-aqua-browser",
"version": "0.1.0",
"private": true,
"dependencies": {
"@fluencelabs/aqua-ipfs": "0.1.8",
"@fluencelabs/ipfs-execution": "file:../aqua",
"@fluencelabs/fluence": "0.9.53",
"@fluencelabs/fluence-network-environment": "1.0.10",
"ipfs-http-client": "^50.1.2",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^11.2.7",
"@testing-library/user-event": "^12.8.3",
"@types/jest": "^26.0.24",
"@types/node": "^12.20.16",
"@types/react": "^17.0.14",
"@types/react-dom": "^17.0.9",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-scripts": "4.0.3",
"typescript": "^4.3.5",
"web-vitals": "^1.1.2"
},
"scripts": {
"prestart": "(cd ../aqua; npm run build)",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"chokidar-cli": "^2.1.0",
"node-sass": "^6.0.1"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

View File

@ -0,0 +1,39 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>Fluence getting started</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,25 @@
{
"short_name": "React App",
"name": "Create React App Sample",
"icons": [
{
"src": "favicon.ico",
"sizes": "64x64 32x32 24x24 16x16",
"type": "image/x-icon"
},
{
"src": "logo192.png",
"type": "image/png",
"sizes": "192x192"
},
{
"src": "logo512.png",
"type": "image/png",
"sizes": "512x512"
}
],
"start_url": ".",
"display": "standalone",
"theme_color": "#000000",
"background_color": "#ffffff"
}

View File

@ -0,0 +1,3 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@ -0,0 +1,202 @@
$color1: black;
$color2: rgb(214, 214, 214);
$accent-color: rgb(225, 30, 90);
.logo {
height: 15vmin;
pointer-events: none;
}
.mono {
font-family: monospace, monospace;
}
.bold {
font-weight: bold;
}
header {
margin-top: 10vmin;
}
header,
h1,
h2,
h3 {
text-align: center;
}
.content {
width: 800px;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
align-content: center;
margin-left: auto;
margin-right: auto;
}
ul,
li {
list-style-type: none;
margin: 0;
padding: 0;
}
.p {
width: 550px;
}
.btn-clipboard {
border: none;
background-color: transparent;
cursor: pointer;
&:hover,
&:focus {
color: $accent-color;
}
&:focus {
outline: none;
}
}
.btn {
height: 26px;
border: 1px solid;
border-color: $color2;
background-color: transparent;
margin: 5px;
font-size: 16px;
color: $color1;
&::placeholder {
color: $color2;
}
&:hover,
&:focus {
outline: 1px solid white;
border-color: $accent-color;
color: $accent-color;
}
}
.btn-hello {
width: 200px;
display: inline;
float: right;
}
table {
text-align: right;
}
.label {
width: 70px;
display: inline-block;
}
.input {
width: 500px;
height: 26px;
box-sizing: border-box;
margin: 5px;
border: 1px solid;
border-color: $color2;
color: $color1;
&::placeholder {
color: $color2;
}
&:hover,
&:focus {
outline: 1px solid white;
border-color: $accent-color;
}
}
.gg-clipboard {
box-sizing: border-box;
position: relative;
display: block;
transform: scale(var(--ggs, 1));
width: 18px;
height: 18px;
border: 2px solid;
border-radius: 2px;
}
.gg-clipboard::after,
.gg-clipboard::before {
content: "";
display: block;
box-sizing: border-box;
position: absolute;
border-radius: 2px;
width: 10px;
left: 2px;
}
.gg-clipboard::before {
border: 2px solid;
border-bottom-left-radius: 3px;
border-bottom-right-radius: 3px;
top: -2px;
height: 6px;
}
.gg-clipboard::after {
height: 2px;
background: currentColor;
box-shadow: 0 -4px 0 0;
bottom: 2px;
}
.gg-trash {
box-sizing: border-box;
position: relative;
display: block;
transform: scale(var(--ggs,1));
width: 10px;
height: 12px;
border: 2px solid transparent;
box-shadow:
0 0 0 2px,
inset -2px 0 0,
inset 2px 0 0;
border-bottom-left-radius: 1px;
border-bottom-right-radius: 1px;
margin-top: 4px
}
.gg-trash::after,
.gg-trash::before {
content: "";
display: block;
box-sizing: border-box;
position: absolute
}
.gg-trash::after {
background: currentColor;
border-radius: 3px;
width: 16px;
height: 2px;
top: -4px;
left: -5px
}
.gg-trash::before {
width: 10px;
height: 4px;
border: 2px solid;
border-bottom: transparent;
border-top-left-radius: 2px;
border-top-right-radius: 2px;
top: -7px;
left: -2px
}

View File

@ -0,0 +1,304 @@
import React, { useState } from "react";
import logo from "./logo.svg";
import "./App.scss";
import { createClient, FluenceClient } from "@fluencelabs/fluence";
import { get_external_api_multiaddr } from "@fluencelabs/aqua-ipfs";
import { stage } from "@fluencelabs/fluence-network-environment";
import { deploy_service, get_file_size, remove_service, provideFile } from "@fluencelabs/ipfs-execution";
const { create, globSource, urlSource, CID } = require('ipfs-http-client');
const relayNodes = [stage[0], stage[1], stage[2]];
const copyToClipboard = (text: string) => {
navigator.clipboard.writeText(text);
};
function App() {
const [client, setClient] = useState<FluenceClient | null>(null);
const [serviceId, setServiceId] = useState<string | null>(null);
const [peerIdInput, setPeerIdInput] = useState<string>("");
const [relayPeerIdInput, setRelayPeerIdInput] = useState<string>("");
const [wasm, setWasm] = useState<string | null>("QmVg9EnanAbwTuEqjjuc1R2uf3AdtEkrNagSifQMkHfyNU");
const [rpcAddr, setRpcAddr] = useState<string | null>("");
const [fileCID, setFileCID] = useState<string>("");
const [fileSize, setFileSize] = useState<string | null>(null);
const isConnected = client !== null;
const gotRpcAddr = rpcAddr !== null;
const deployed = serviceId !== null;
const connect = async (relayPeerId: string) => {
try {
let client = await createClient(relayPeerId);
setClient(client);
await getRpcAddr(client);
} catch (err) {
console.log("Client initialization failed", err);
}
};
const getRpcAddr = async (client: FluenceClient) => {
if (client === null) {
console.log("getRpcAddr client is null");
return;
}
let result = await get_external_api_multiaddr(client, client.relayPeerId!);
console.log("getRpcAddr result", result);
let rpcAddr = result.multiaddr;
setRpcAddr(rpcAddr);
}
const deployService = async () => {
console.log("wasm %s rpcAddr %s", wasm, rpcAddr);
if (client === null || wasm === null || rpcAddr === null) {
return;
}
let service_id = await deploy_service(
client,
client.relayPeerId!, wasm, rpcAddr,
(msg, value) => console.log(msg, value),
{ ttl: 10000 }
);
setServiceId(service_id);
};
const getFileSize = async () => {
if (client === null || serviceId === null || rpcAddr === null) {
return;
}
let size = await get_file_size(client, client.relayPeerId!, fileCID, rpcAddr, serviceId, { ttl: 10000 });
if (size.success) {
setFileSize(size.size.toString());
} else {
setFileSize("Error: " + size.error);
}
};
const removeService = async () => {
if (client === null || serviceId === null) {
return;
}
await remove_service(client, client.relayPeerId!, serviceId, { ttl: 10000 });
setServiceId(null);
};
console.log("isConnected gotRpcAddr deployed\n", isConnected, gotRpcAddr, deployed);
if (!isConnected) {
return (<div className="App">
<header>
<img src={logo} className="logo" alt="logo" />
</header>
<div className="content">
<>
<h1>Pick a relay</h1>
<ul>
{relayNodes.map((x) => (
<li key={x.peerId}>
<span className="mono">{x.peerId}</span>
<button className="btn" onClick={async () => await connect(x.multiaddr)}>
Connect
</button>
</li>
))}
</ul>
</>
</div>
</div>
);
} else if (isConnected && gotRpcAddr && !deployed) {
return (
<div className="App">
<header>
<img src={logo} className="logo" alt="logo" />
</header>
<div className="content">
<>
<h1>Connected</h1>
<table>
<tr>
<td className="bold">Peer id:</td>
<td className="mono">{client!.selfPeerId}</td>
<td>
<button
className="btn-clipboard"
onClick={() => copyToClipboard(client!.selfPeerId)}
>
<i className="gg-clipboard"></i>
</button>
</td>
</tr>
<tr>
<td className="bold">Relay peer id:</td>
<td className="mono">{client!.relayPeerId}</td>
<td>
<button
className="btn-clipboard"
onClick={() => copyToClipboard(client!.relayPeerId!)}
>
<i className="gg-clipboard"></i>
</button>
</td>
</tr>
<tr>
<td className="bold">IPFS RPC:</td>
<td className="mono">{rpcAddr?.substring(0, 49) + "..."}</td>
<td>
<button
className="btn-clipboard"
onClick={() => copyToClipboard(rpcAddr!)}
>
<i className="gg-clipboard"></i>
</button>
</td>
</tr>
</table>
<div>
<div className="row">
<h2>Set process_files.wasm module CID</h2>
<p className="p">
To deploy a service, specify CID of WebAssembly module.
</p>
<input
className="input"
type="text"
onChange={(e) => setWasm(e.target.value)}
value={wasm!}
/>
</div>
</div>
<div>
<h2>Deploy ProcessFiles service</h2>
<p className="p">
process_files.wasm will be downloaded to the Fluence node,
and then a service will be dynamically created from it!
After that, you will be able to use that service to get sizes of IPFS files!
</p>
<div className="row">
<button className="btn btn-hello" onClick={deployService}>
deploy service
</button>
</div>
</div>
</>
</div>
</div>
)
} else if (deployed) {
return (
<div className="App">
<header>
<img src={logo} className="logo" alt="logo" />
</header>
<div className="content">
<>
<h1>Deployed</h1>
<table>
<tr>
<td className="bold">Peer id:</td>
<td className="mono">{client!.selfPeerId}</td>
<td>
<button
className="btn-clipboard"
onClick={() => copyToClipboard(client!.selfPeerId)}
>
<i className="gg-clipboard"></i>
</button>
</td>
</tr>
<tr>
<td className="bold">Relay peer id:</td>
<td className="mono">{client!.relayPeerId}</td>
<td>
<button
className="btn-clipboard"
onClick={() => copyToClipboard(client!.relayPeerId!)}
>
<i className="gg-clipboard"></i>
</button>
</td>
</tr>
<tr>
<td className="bold">process_files.wasm CID:</td>
<td className="mono">{wasm}</td>
<td>
<button
className="btn-clipboard"
onClick={() => copyToClipboard(wasm!)}
>
<i className="gg-clipboard"></i>
</button>
</td>
</tr>
<tr>
<td className="bold">ProcessFiles service ID:</td>
<td className="mono">{serviceId}</td>
<button
className="btn-clipboard"
onClick={() => removeService()}
>
<i className="gg-trash"></i>
</button>
</tr>
<tr>
<td className="bold">File Size:</td>
<td className="mono">{fileSize}</td>
</tr>
</table>
<div>
<h2>Get file size</h2>
<p className="p">
Upload any file to IPFS node
<p><b>{ rpcAddr }</b></p>
paste CID here and get the size of that file via ProcessFiles service you have just deployed
</p>
<div className="row">
<label className="label bold">IPFS CID</label>
<input
className="input"
type="text"
onChange={(e) => setFileCID(e.target.value)}
value={fileCID}
/>
</div>
<div className="row">
<button className="btn btn-hello" onClick={getFileSize} >
get size
</button>
</div>
<div className="row">
<label className="label bold">File Size:</label>
<label className="mono"> {fileSize}</label>
</div>
</div>
</>
</div>
</div>
)
} else {
return (
<div className="App">
<header>
<img src={logo} className="logo" alt="logo" />
</header>
<div className="content">
<>
<h2>Unimplemented! isConnected {isConnected} deployed {deployed} wasm {wasm} </h2>
</>
</div>
</div>
)
}
}
export default App;

View File

@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

View File

@ -0,0 +1,12 @@
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1 @@
/// <reference types="react-scripts" />

View File

@ -0,0 +1,26 @@
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}