├── tests ├── .gitignore ├── src │ ├── behaviors │ │ ├── trycp-addresses.ts │ │ ├── README.md │ │ ├── index.ts │ │ └── tx-per-second.ts │ ├── index.ts │ ├── unique-registration-code.ts │ ├── installAgents.ts │ ├── chat-stats.ts │ ├── batching.ts │ ├── common.ts │ ├── transient-nodes.ts │ ├── profile.ts │ ├── chat-signals.ts │ ├── membrane-proof.ts │ └── basic-chatting.ts ├── package.json └── tsconfig.json ├── zomes ├── profile │ ├── src │ │ └── lib.rs │ └── Cargo.toml ├── chat │ ├── src │ │ ├── entries.rs │ │ ├── utils.rs │ │ ├── error.rs │ │ ├── entries │ │ │ ├── channel.rs │ │ │ ├── message.rs │ │ │ ├── channel │ │ │ │ └── handlers.rs │ │ │ └── message │ │ │ │ └── handlers.rs │ │ ├── batching_helper.rs │ │ └── lib.rs │ └── Cargo.toml ├── profile_integrity │ ├── src │ │ └── lib.rs │ └── Cargo.toml ├── membrane_manager_integrity │ ├── src │ │ └── lib.rs │ └── Cargo.toml └── chat_integrity │ ├── src │ ├── lib.rs │ ├── validation.rs │ └── entries.rs │ └── Cargo.toml ├── .gitignore ├── version-manager.json ├── .github ├── nix.conf └── workflows │ └── main.yml ├── happ.yaml ├── default.nix ├── nix ├── sources.json └── sources.nix ├── release-builds.sh ├── Cargo.toml ├── dna.yaml ├── holochain_version.nix ├── gh-release.sh ├── README.md ├── Makefile ├── LICENSE └── Cargo.lock /tests/.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | *.log 3 | -------------------------------------------------------------------------------- /zomes/profile/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate hc_cz_profile; 2 | -------------------------------------------------------------------------------- /zomes/chat/src/entries.rs: -------------------------------------------------------------------------------- 1 | pub mod channel; 2 | pub mod message; 3 | -------------------------------------------------------------------------------- /zomes/profile_integrity/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate hc_iz_profile; 2 | -------------------------------------------------------------------------------- /zomes/membrane_manager_integrity/src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate hc_iz_membrane_manager; 2 | -------------------------------------------------------------------------------- /zomes/chat_integrity/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub mod entries; 2 | pub use entries::*; 3 | mod validation; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | debug/ 3 | .rustc_info.json 4 | *.dna 5 | *.dna.gz 6 | *.happ 7 | .cargo 8 | .todo.md 9 | .hc* 10 | nix.log 11 | -------------------------------------------------------------------------------- /tests/src/behaviors/trycp-addresses.ts: -------------------------------------------------------------------------------- 1 | // Edit this file before running a multi-node setup. 2 | 3 | export default [ 4 | "localhost:9000", 5 | // Add more addresses of machines running trycp here. 6 | ] 7 | -------------------------------------------------------------------------------- /version-manager.json: -------------------------------------------------------------------------------- 1 | { 2 | "hdk": "0.0.132", 3 | "hc_utils": "0.0.132", 4 | "holo_hash": "0.0.21", 5 | "holochain": "0.0.139", 6 | "holochain_rev": "v0_0_139", 7 | "holonix_rev": "44542b5c0cdc984454fad96fbec313b7b1c8ca7c" 8 | } -------------------------------------------------------------------------------- /.github/nix.conf: -------------------------------------------------------------------------------- 1 | substituters = https://cache.nixos.org/ https://cache.holo.host/ 2 | trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= cache.holo.host-1:lNXIXtJgS9Iuw4Cu6X0HINLu9sTfcjEntnrgwMQIMcE= cache.holo.host-2:ZJCkX3AUYZ8soxTLfTb60g+F3MkWD7hkH9y8CgqwhDQ= 3 | -------------------------------------------------------------------------------- /zomes/chat_integrity/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chat_integrity" 3 | version = "0.1.0" 4 | authors = [ "joel.ulahanna@holo.host" ] 5 | edition = "2021" 6 | 7 | [lib] 8 | name = "chat_integrity" 9 | crate-type = [ "cdylib", "rlib" ] 10 | 11 | [dependencies] 12 | serde = "=1.0.139" 13 | holochain_deterministic_integrity = "=0.0.10" 14 | -------------------------------------------------------------------------------- /zomes/profile/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "profile" 3 | version = "0.1.0" 4 | authors = ["zo-el "] 5 | edition = "2021" 6 | 7 | [lib] 8 | name = "profile" 9 | crate-type = [ "cdylib", "rlib" ] 10 | 11 | [dependencies] 12 | hc_cz_profile = {git = "https://github.com/holochain/hc-zome-lib", branch = "v0.0.138", package = "hc_cz_profile"} 13 | -------------------------------------------------------------------------------- /happ.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | manifest_version: "1" 3 | name: elemental-chat 4 | description: "" 5 | roles: 6 | - id: elemental-chat 7 | provisioning: 8 | strategy: create 9 | deferred: false 10 | dna: 11 | bundled: ./elemental-chat.dna 12 | properties: 13 | skip_proof: true 14 | uid: ~ 15 | version: ~ 16 | clone_limit: 0 17 | -------------------------------------------------------------------------------- /default.nix: -------------------------------------------------------------------------------- 1 | let 2 | holonixPath = (import ./nix/sources.nix).holonix; 3 | holonix = import (holonixPath) { 4 | holochainVersionId = "custom"; 5 | holochainVersion = import ./holochain_version.nix; 6 | }; 7 | nixpkgs = holonix.pkgs; 8 | in nixpkgs.mkShell { 9 | inputsFrom = [ holonix.main ]; 10 | packages = with nixpkgs; [ 11 | niv 12 | nixpkgs.binaryen 13 | ]; 14 | } -------------------------------------------------------------------------------- /zomes/profile_integrity/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "profile_integrity" 3 | version = "0.1.0" 4 | authors = ["zo-el "] 5 | edition = "2021" 6 | 7 | [lib] 8 | name = "profile_integrity" 9 | crate-type = [ "cdylib", "rlib" ] 10 | 11 | [dependencies] 12 | hc_iz_profile = {git = "https://github.com/holochain/hc-zome-lib", branch = "v0.0.138", package = "hc_iz_profile"} 13 | -------------------------------------------------------------------------------- /zomes/membrane_manager_integrity/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "membrane_manager_integrity" 3 | version = "0.1.0" 4 | authors = ["zo-el "] 5 | edition = "2021" 6 | 7 | [lib] 8 | name = "membrane_manager_integrity" 9 | crate-type = [ "cdylib", "rlib" ] 10 | 11 | [dependencies] 12 | hc_iz_membrane_manager = {git = "https://github.com/holochain/hc-zome-lib", branch = "v0.0.138", package = "hc_iz_membrane_manager"} 13 | -------------------------------------------------------------------------------- /zomes/chat/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "chat" 3 | authors = ["michael.dougherty@holo.host", "philip.beadle@holo.host", "david.meister@holo.host, tom.gowan@holo.host"] 4 | version = "0.3.0" 5 | edition = "2021" 6 | 7 | [lib] 8 | path = "src/lib.rs" 9 | crate-type = ["cdylib", "rlib"] 10 | 11 | [dependencies] 12 | uuid = "0.8.2" 13 | derive_more = "0.99" 14 | serde = "1.0" 15 | thiserror = "1.0" 16 | chrono = "0.4.6" 17 | hdk = "=0.0.138" 18 | hc_utils = "=0.0.138-patch1" 19 | chat_integrity = {path = '../chat_integrity'} 20 | -------------------------------------------------------------------------------- /nix/sources.json: -------------------------------------------------------------------------------- 1 | { 2 | "holonix": { 3 | "branch": "main", 4 | "description": "NixOS && Holochain", 5 | "homepage": "", 6 | "owner": "holochain", 7 | "repo": "holonix", 8 | "rev": "0d50068904a60a00779c3c8b5e96e56f41c32dc4", 9 | "sha256": "12l87lnfp47lvwcp3fc055nlx7jzgzg2xz932l4pdrb6x17zlfqn", 10 | "type": "tarball", 11 | "url": "https://github.com/holochain/holonix/archive/0d50068904a60a00779c3c8b5e96e56f41c32dc4.tar.gz", 12 | "url_template": "https://github.com///archive/.tar.gz" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /release-builds.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | build () { 3 | if [[ $2 = "~" ]] 4 | then 5 | sed -i "s/uid: .*/uid: \~/" happ.yaml 6 | FILE="elemental-chat.$1.happ" 7 | else 8 | sed -i "s/uid: .*/uid: \"$2\"/" happ.yaml 9 | FILE="elemental-chat.$1.$2.happ" 10 | fi 11 | hc app pack . -o $FILE 12 | } 13 | # get the version from the chat zome Cargo.toml 14 | VERSION=`grep -Po '^version = "\K([^"]+)' zomes/chat/Cargo.toml | sed -e "s/[.-]/_/g"` 15 | build $VERSION 0002 16 | build $VERSION 0001 17 | build $VERSION 0000 18 | build $VERSION develop 19 | build $VERSION "~" 20 | -------------------------------------------------------------------------------- /tests/src/behaviors/README.md: -------------------------------------------------------------------------------- 1 | # Scalability tests for Elemental Chat 2 | 3 | ## Instructions 4 | 5 | 1. Run [trycp_server](https://github.com/holochain/tryorama/tree/develop/crates/trycp_server) on a bunch of different machines 6 | 2. Edit `trycp-addresses.ts` to contain the URLs of the trycp servers you're running 7 | 3. Edit `defaultConfig` within `tx-per-second.ts` to contain the number of nodes, etc. that you'd like to benchmark 8 | 4. Within `src/` run `npm run test:behavior`, it will try increasing amounts of transactions per second, and once it completes it will output: 9 | ``` 10 | maxed message per second when sending ____: ____ (sent over ____s) 11 | ``` 12 | 13 | **Be cautious of overloading the Holochain test proxy server** -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = [ 4 | "zomes/chat", 5 | "zomes/chat_integrity", 6 | "zomes/membrane_manager_integrity", 7 | "zomes/profile", 8 | "zomes/profile_integrity" 9 | ] 10 | 11 | [profile.dev] 12 | opt-level = "z" 13 | 14 | [profile.release] 15 | opt-level = "z" 16 | 17 | [patch.crates-io] 18 | # hdk = {git ="https://github.com/holochain/holochain", rev = "04a9e8e3457851c2c1e32cbf06d4c547ba5d8c30", package = "hdk"} 19 | # hc_utils = {git = "https://github.com/holochain/hc-utils", rev = "cfb8eea4e23c99f6444afe05d7c4dcc0e8cab46e", package = "hc_utils", version = "0.0.132"} 20 | # holo_hash = {git = "https://github.com/holochain/holochain", rev = "a1206a694fe3b521440fe633db99a50b8255c1b2", package = "holo_hash"} 21 | -------------------------------------------------------------------------------- /zomes/chat/src/utils.rs: -------------------------------------------------------------------------------- 1 | use hdk::prelude::*; 2 | use timestamp::Timestamp; 3 | 4 | use crate::error::ChatResult; 5 | 6 | /// Get a local action from your chain 7 | pub(crate) fn get_local_action(action_hash: &ActionHash) -> ChatResult> { 8 | // Get the latest chain action 9 | // Query iterates backwards so index 0 is the latest. 10 | let action = query(QueryFilter::new())?.into_iter().find_map(|el| { 11 | if el.action_address() == action_hash { 12 | Some(el.into_inner().0.into_inner().0.into_content()) 13 | } else { 14 | None 15 | } 16 | }); 17 | Ok(action) 18 | } 19 | 20 | /// Turns a unix timestamp into a Date 21 | pub(crate) fn to_date(timestamp: Timestamp) -> chrono::DateTime { 22 | timestamp.try_into().unwrap() 23 | } 24 | -------------------------------------------------------------------------------- /dna.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | manifest_version: '1' 3 | name: elemental-chat 4 | uid: 9a28aac8-337c-11eb-adc1-0Z02acw20115 5 | integrity: 6 | zomes: 7 | - name: chat_integrity 8 | bundled: './target/wasm32-unknown-unknown/release/chat_integrity.wasm' 9 | - name: membrane_manager_integrity 10 | bundled: './target/wasm32-unknown-unknown/release/membrane_manager_integrity.wasm' 11 | - name: profile_integrity 12 | bundled: './target/wasm32-unknown-unknown/release/profile_integrity.wasm' 13 | properties: 14 | skip_proof: true 15 | coordinator: 16 | zomes: 17 | - name: profile 18 | bundled: './target/wasm32-unknown-unknown/release/profile.wasm' 19 | dependencies: 20 | - name: profile_integrity 21 | - name: chat 22 | bundled: './target/wasm32-unknown-unknown/release/chat.wasm' 23 | dependencies: 24 | - name: chat_integrity 25 | -------------------------------------------------------------------------------- /tests/src/index.ts: -------------------------------------------------------------------------------- 1 | import { Orchestrator } from '@holochain/tryorama' 2 | 3 | let orchestrator = new Orchestrator() 4 | require('./basic-chatting')(orchestrator) 5 | orchestrator.run() 6 | 7 | orchestrator = new Orchestrator() 8 | require('./transient-nodes')(orchestrator) 9 | orchestrator.run() 10 | 11 | orchestrator = new Orchestrator() 12 | require('./chat-signals')(orchestrator) 13 | orchestrator.run() 14 | 15 | orchestrator = new Orchestrator() 16 | require('./chat-stats')(orchestrator) 17 | orchestrator.run() 18 | 19 | orchestrator = new Orchestrator() 20 | require('./profile')(orchestrator) 21 | orchestrator.run() 22 | 23 | orchestrator = new Orchestrator() 24 | require('./membrane-proof')(orchestrator) 25 | orchestrator.run() 26 | 27 | orchestrator = new Orchestrator() 28 | require('./unique-registration-code')(orchestrator) 29 | orchestrator.run() 30 | 31 | orchestrator = new Orchestrator() 32 | require('./batching')(orchestrator) 33 | orchestrator.run() 34 | -------------------------------------------------------------------------------- /zomes/chat_integrity/src/validation.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use holochain_deterministic_integrity::prelude::*; 3 | 4 | pub fn __validate_create_entry(entry: Entry) -> ExternResult { 5 | match entry { 6 | Entry::App(_) => match entry.try_into() { 7 | Ok(Message { content, .. }) => { 8 | if content.len() <= 1024 { 9 | Ok(ValidateCallbackResult::Valid) 10 | } else { 11 | Ok(ValidateCallbackResult::Invalid( 12 | "Message too long".to_string(), 13 | )) 14 | } 15 | } 16 | _ => Ok(ValidateCallbackResult::Valid), 17 | }, 18 | _ => Ok(ValidateCallbackResult::Valid), 19 | } 20 | } 21 | 22 | #[hdk_extern] 23 | fn validate(op: Op) -> ExternResult { 24 | match op { 25 | Op::StoreEntry { entry, .. } => validation::__validate_create_entry(entry), 26 | _ => Ok(ValidateCallbackResult::Valid), 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /zomes/chat_integrity/src/entries.rs: -------------------------------------------------------------------------------- 1 | use holochain_deterministic_integrity::prelude::*; 2 | 3 | /// The actual message data that is saved into the DHT 4 | #[hdk_entry_helper] 5 | #[derive(Clone, PartialEq)] 6 | pub struct Message { 7 | pub uuid: String, 8 | pub content: String, 9 | } 10 | 11 | /// The actual channel data that is saved into the DHT 12 | /// This is the actual name of the channel that 13 | /// can change. 14 | #[hdk_entry_helper] 15 | #[derive(Clone, PartialEq, Eq)] 16 | pub struct ChannelInfo { 17 | pub category: String, 18 | pub uuid: String, 19 | pub name: String, 20 | pub created_by: AgentPubKey, 21 | pub created_at: Timestamp, 22 | } 23 | 24 | #[hdk_entry_defs] 25 | #[unit_enum(EntryTypesUnit)] 26 | pub enum EntryTypes { 27 | #[entry_def(visibility = "public", required_validations = 2)] 28 | Message(Message), 29 | #[entry_def(visibility = "public", required_validations = 2)] 30 | ChannelInfo(ChannelInfo), 31 | } 32 | 33 | #[hdk_link_types] 34 | pub enum LinkTypes { 35 | Channel, 36 | Chatter, 37 | Message, 38 | } 39 | -------------------------------------------------------------------------------- /holochain_version.nix: -------------------------------------------------------------------------------- 1 | # This file was generated with the following command: 2 | # update-holochain-versions --nvfetcher-dir=nix/nvfetcher --output-file=packages/holochain/versions/develop_lair_0_1.nix --bins-filter=holochain,hc,kitsune-p2p-proxy,kitsune-p2p-tx2-proxy --git-src=branch:develop --lair-version-req=~0.1 3 | # For usage instructions please visit https://github.com/holochain/holochain-nixpkgs/#readme 4 | 5 | { 6 | url = "https://github.com/holochain/holochain"; 7 | rev = "holochain-0.0.145"; 8 | sha256 = "18pkrnf9p0b6hriyf9jk52hds6d10h6z8bkaxmigl1xv4vwm2a1p"; 9 | cargoLock = { 10 | outputHashes = { 11 | }; 12 | }; 13 | 14 | binsFilter = [ 15 | "holochain" 16 | "hc" 17 | "kitsune-p2p-proxy" 18 | "kitsune-p2p-tx2-proxy" 19 | ]; 20 | 21 | lair = { 22 | url = "https://github.com/holochain/lair"; 23 | rev = "v0.0.10"; 24 | sha256 = "sha256-yBCsdtC6vYnGQL1JkLkOzECk1TD4RoFzNARdsc+J0cg="; 25 | 26 | binsFilter = [ 27 | "lair-keystore" 28 | ]; 29 | 30 | cargoLock = { 31 | outputHashes = { 32 | }; 33 | }; 34 | }; 35 | } -------------------------------------------------------------------------------- /tests/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "demo-1", 3 | "version": "0.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "npm run test:setup && npm run test:standard", 8 | "test:setup": "CARGO_TARGET_DIR=../target cargo build --release --target wasm32-unknown-unknown && hc dna pack .. -o ../elemental-chat.dna && hc app pack .. -o ../elemental-chat.happ", 9 | "test:standard": "TRYORAMA_LOG_LEVEL=info RUST_LOG=holochain::core::ribosome::host_fn::debug=debug RUST_BACKTRACE=1 ts-node src/index.ts | tap-diff", 10 | "test:behavior": "TRYORAMA_CONDUCTOR_TIMEOUT_MS=1450000 REMOTE_LOG_LEVEL=debug TRYORAMA_LOG_LEVEL=debug RUST_BACKTRACE=1 ts-node src/behaviors/index.ts | tap-diff", 11 | "test:debug": "RUST_LOG=[debug]=debug TRYORAMA_HOLOCHAIN_PATH=\"holochain\" ts-node src/index.ts" 12 | }, 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "@holo-host/cryptolib": "^0.3.1", 17 | "@msgpack/msgpack": "^2.4.1", 18 | "lodash": "^4.17.19", 19 | "tape": "^5.0.1", 20 | "ts-node": "^8.10.2", 21 | "typescript": "^3.9.6", 22 | "uuidv4": "^6.2.3" 23 | }, 24 | "devDependencies": { 25 | "@holochain/tryorama": "^0.4", 26 | "@types/lodash": "^4.14.158", 27 | "@types/node": "^14.0.14", 28 | "tap-diff": "^0.1.1" 29 | } 30 | } -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [master, develop] 6 | pull_request: 7 | branches: [master, develop] 8 | 9 | jobs: 10 | test: 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | node-version: [14.x] 16 | 17 | steps: 18 | - name: Fetch source code 19 | uses: actions/checkout@v2 20 | - name: Use Nix 21 | uses: cachix/install-nix-action@v16 22 | with: 23 | nix_path: nixpkgs=channel:nixos-21.05 24 | - name: Install toolchain 25 | uses: actions-rs/toolchain@v1 26 | with: 27 | profile: minimal 28 | toolchain: 1.58.1 29 | override: true 30 | - name: Use Node.js ${{ matrix.node-version }} 31 | uses: actions/setup-node@v1 32 | with: 33 | node-version: ${{ matrix.node-version }} 34 | - name: Configure Nix substituters 35 | run: | 36 | set -xe 37 | mkdir -p ~/.config/nix/ 38 | cp ./.github/nix.conf ~/.config/nix/ 39 | - uses: cachix/cachix-action@v10 40 | with: 41 | name: holochain-ci 42 | authToken: "${{ secrets.CACHIX_HOLOCHAIN_CI }}" 43 | - name: Prepare Nix environment 44 | run: nix-shell --command "echo Completed" 45 | - name: Run all tests 46 | run: nix-shell --run 'make test' 47 | -------------------------------------------------------------------------------- /tests/src/unique-registration-code.ts: -------------------------------------------------------------------------------- 1 | import { localConductorConfig } from './common' 2 | import { installJCHapp, installAgents } from './installAgents' 3 | 4 | module.exports = async (orchestrator) => { 5 | 6 | orchestrator.registerScenario('unique joining codes', async (s, t) => { 7 | const [conductor] = await s.players([localConductorConfig]) 8 | const jcHapp = await installJCHapp((await s.players([localConductorConfig]))[0]) 9 | let [bob_chat_happ, carol_chat_happ, daniel_chat_happ] = await installAgents(conductor, ["bob", "carol", "daniel" ], jcHapp) 10 | 11 | const [bob_chat] = bob_chat_happ.cells 12 | const [carol_chat] = carol_chat_happ.cells 13 | const [daniel_chat] = daniel_chat_happ.cells 14 | 15 | // try zome call with one read-only instance and 3 separate agents 16 | let agent_stats 17 | try { 18 | 19 | // read/write instances: 20 | agent_stats = await bob_chat.call('chat', 'agent_stats', null); 21 | console.log('agent_stats after BOB : ', agent_stats); 22 | 23 | agent_stats = await carol_chat.call('chat', 'agent_stats', null); 24 | console.log('agent_stats after CAROL : ', agent_stats); 25 | 26 | agent_stats = await daniel_chat.call('chat', 'agent_stats', null); 27 | console.log('agent_stats after DANIEL : ', agent_stats); 28 | } catch (error) { 29 | console.error(error) 30 | t.fail() 31 | } 32 | t.ok(agent_stats) 33 | }) 34 | } 35 | -------------------------------------------------------------------------------- /gh-release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # How to? 4 | # https://docs.github.com/en/github/authenticating-to-github/creating-a-personal-access-token 5 | # git config --global github .token YOUR_TOKEN 6 | 7 | version="v"$1 8 | text=$2 9 | branch=$(git rev-parse --abbrev-ref HEAD) 10 | token=$(git config --global github.token) 11 | USER="holochain" 12 | REPO="elemental-chat" 13 | generate_post_data() 14 | { 15 | cat < = Result; 39 | 40 | impl From for WasmError { 41 | fn from(c: ChatError) -> Self { 42 | wasm_error!(WasmErrorInner::Guest(c.to_string())) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # elemental-chat 2 | 3 | [![Project](https://img.shields.io/badge/project-holochain-blue.svg?style=flat-square)](http://holochain.org/) 4 | [![Forum](https://img.shields.io/badge/chat-forum%2eholochain%2enet-blue.svg?style=flat-square)](https://forum.holochain.org) 5 | [![Chat](https://img.shields.io/badge/chat-chat%2eholochain%2enet-blue.svg?style=flat-square)](https://chat.holochain.org) 6 | 7 | [![Twitter Follow](https://img.shields.io/twitter/follow/holochain.svg?style=social&label=Follow)](https://twitter.com/holochain) 8 | License: [![License: CAL 1.0](https://img.shields.io/badge/License-CAL%201.0-blue.svg)](https://github.com/holochain/cryptographic-autonomy-license) 9 | 10 | A fairly basic chat app. Includes channels, and exercises various holochain features including signals. 11 | 12 | ## Running the Tests 13 | 14 | ### Prerequisites 15 | 16 | - Build the Holochain tools 17 | - Clone the repo: `git clone https://github.com/holochain/holochain && cd ./holochain` 18 | - Activate the needed rust environment and load correct holochain version: `nix-shell` 19 | - Build the elemental-chat DNA (assumes you are still in the nix shell for correct rust/cargo versions from step above): 20 | - Clone this repo: `git clone https://github.com/holochain/elemental-chat && cd ./elemental-chat` 21 | - Build the wasm: `CARGO_TARGET_DIR=target cargo build --release --target wasm32-unknown-unknown` 22 | - Assemble the DNA: `hc dna pack . -o elemental-chat.dna` 23 | - Assemble the hApp: `hc app pack . -o elemental-chat.happ` 24 | 25 | ## Running 26 | 27 | ```bash 28 | cd elemental-chat/tests 29 | npm install 30 | npm test 31 | ``` 32 | 33 | > `npm test` will also run the build and assemble commands for you. 34 | 35 | ## Contribute 36 | 37 | Holochain is an open source project. We welcome all sorts of participation and are actively working on increasing surface area to accept it. Please see our [contributing guidelines](/CONTRIBUTING.md) for our general practices and protocols on participating in the community, as well as specific expectations around things like code formatting, testing practices, continuous integration, etc. 38 | 39 | - Connect with us on our [forum](https://forum.holochain.org) 40 | 41 | ## License 42 | 43 | [![License: CAL 1.0](https://img.shields.io/badge/License-CAL%201.0-blue.svg)](https://github.com/holochain/cryptographic-autonomy-license) 44 | 45 | Copyright (C) 2019 - 2020, Holochain Foundation 46 | 47 | This program is free software: you can redistribute it and/or modify it under the terms of the license 48 | provided in the LICENSE file (CAL-1.0). This program is distributed in the hope that it will be useful, 49 | but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 50 | PURPOSE. 51 | -------------------------------------------------------------------------------- /tests/src/installAgents.ts: -------------------------------------------------------------------------------- 1 | const { Codec } = require('@holo-host/cryptolib') 2 | import * as path from 'path' 3 | import * as msgpack from '@msgpack/msgpack' 4 | import { InstalledHapp, Player } from '@holochain/tryorama' 5 | 6 | const dnaConfiguration = { 7 | role_id: 'elemental-chat', 8 | } 9 | const dnaPath = path.join(__dirname, '../../elemental-chat.dna') 10 | const jcFactoryDna = path.join(__dirname, '../../dnas/joining-code-factory.dna') 11 | const installedAppId = (agentName) => `${agentName}_chat` 12 | 13 | export type Memproof = { 14 | signed_action: { 15 | action: any 16 | signature: Buffer 17 | } 18 | entry: any 19 | } 20 | 21 | export const installJCHapp = async ( 22 | conductor: Player 23 | ): Promise => { 24 | const admin = conductor.adminWs() 25 | const holo_agent_override = await admin.generateAgentPubKey() 26 | let happ = await conductor._installHapp({ 27 | installed_app_id: `holo_agent_override`, 28 | agent_key: holo_agent_override, 29 | dnas: [ 30 | { 31 | hash: await conductor.registerDna( 32 | { path: jcFactoryDna }, 33 | conductor.scenarioUID 34 | ), 35 | role_id: 'jc', 36 | }, 37 | ], 38 | }) 39 | return happ 40 | } 41 | 42 | export const installAgents = async ( 43 | conductor: Player, 44 | agentNames: string[], 45 | jcHapp?: InstalledHapp, 46 | memProofMutator = (m) => m 47 | ): Promise => { 48 | let holo_agent_override = undefined 49 | if (!!jcHapp) { 50 | holo_agent_override = Codec.AgentId.encode(jcHapp.agent) 51 | } 52 | console.log(`registering dna for: ${dnaPath}`) 53 | const dnaHash = await conductor.registerDna( 54 | { path: dnaPath }, 55 | conductor.scenarioUID, 56 | { skip_proof: !jcHapp, holo_agent_override } 57 | ) 58 | const admin = conductor.adminWs() 59 | 60 | const agents: Array = [] 61 | for (const i in agentNames) { 62 | const agent = agentNames[i] 63 | console.log(`generating key for: ${agent}:`) 64 | const agent_key = await admin.generateAgentPubKey() 65 | console.log(`${agent} pubkey:`, Codec.AgentId.encode(agent_key)) 66 | 67 | let dna = { 68 | hash: dnaHash, 69 | ...dnaConfiguration, 70 | } 71 | if (!!jcHapp) { 72 | const membrane_proof = await jcHapp.cells[0].call( 73 | 'code-generator', 74 | 'make_proof', 75 | { 76 | role: 'ROLE', 77 | record_locator: 'RECORD_LOCATOR', 78 | registered_agent: Codec.AgentId.encode(agent_key), 79 | } 80 | ) 81 | const mutated = memProofMutator(membrane_proof) 82 | dna['membrane_proof'] = Array.from(msgpack.encode(mutated)) 83 | } 84 | 85 | const req = { 86 | installed_app_id: installedAppId(agent), 87 | agent_key, 88 | dnas: [dna], 89 | } 90 | console.log(`installing happ for: ${agent}`) 91 | let installed = await conductor._installHapp(req) 92 | console.log(`${installedAppId(agent)} installed`) 93 | agents.push(installed) 94 | } 95 | 96 | return agents 97 | } 98 | -------------------------------------------------------------------------------- /zomes/chat/src/entries/channel.rs: -------------------------------------------------------------------------------- 1 | use hdk::{hash_path::path::Component, prelude::*}; 2 | use uuid::Uuid; 3 | pub mod handlers; 4 | pub use chat_integrity::{ChannelInfo, Message}; 5 | use std; 6 | 7 | /// Input to the create channel call 8 | #[derive(Debug, Serialize, Deserialize, SerializedBytes)] 9 | pub struct ChannelInput { 10 | pub name: String, 11 | pub entry: Channel, 12 | } 13 | 14 | /// A channel is consists of the category it belongs to 15 | /// and a unique id 16 | #[derive(Debug, Clone, Serialize, Deserialize, SerializedBytes, PartialEq, Eq)] 17 | pub struct Channel { 18 | pub category: String, 19 | pub uuid: String, 20 | } 21 | 22 | /* using global chatters list for now. 23 | impl Channel { 24 | pub fn chatters_path(&self) -> Path { 25 | let mut components: Vec = Path::from(self.clone()).into(); 26 | components.push("chatters".into()); 27 | components.into() 28 | } 29 | } 30 | */ 31 | 32 | /// The message type that goes to the UI 33 | #[derive( 34 | Serialize, Deserialize, SerializedBytes, derive_more::Constructor, Debug, Clone, PartialEq, Eq, 35 | )] 36 | #[serde(rename_all = "camelCase")] 37 | pub struct ChannelData { 38 | pub entry: Channel, 39 | pub info: ChannelInfo, 40 | } 41 | 42 | /// Input to the list channels call 43 | #[derive(Debug, Serialize, Deserialize, SerializedBytes)] 44 | pub struct ChannelListInput { 45 | pub category: String, 46 | } 47 | 48 | /// The channels returned from list channels 49 | #[derive(Debug, Serialize, Deserialize, SerializedBytes, derive_more::From)] 50 | pub struct ChannelList { 51 | pub channels: Vec, 52 | } 53 | 54 | impl From for Path { 55 | fn from(c: Channel) -> Self { 56 | let u = Uuid::parse_str(&c.uuid).unwrap(); 57 | let path = vec![ 58 | Component::from(c.category.as_bytes().to_vec()), 59 | Component::from(u.to_u128_le().to_le_bytes().to_vec()), 60 | ]; 61 | Path::from(path) 62 | } 63 | } 64 | 65 | impl TryFrom<&Path> for Channel { 66 | type Error = SerializedBytesError; 67 | 68 | fn try_from(p: &Path) -> Result { 69 | let path: &Vec<_> = p.as_ref(); 70 | let u128 = u128::from_le_bytes(path[1].as_ref().try_into().expect("wrong length")); 71 | let u = Uuid::from_u128(u128); 72 | let c: String = std::str::from_utf8(path[0].as_ref()) 73 | .expect("bad string") 74 | .to_string(); 75 | let channel = Channel { 76 | category: String::try_from(c)?, 77 | uuid: u.to_string(), 78 | }; 79 | Ok(channel) 80 | } 81 | } 82 | 83 | /// A easy way to create the channel info tag 84 | pub(crate) struct ChannelInfoTag; 85 | 86 | impl ChannelInfoTag { 87 | const TAG: &'static [u8; 4] = b"info"; 88 | 89 | /// Create the tag 90 | pub(crate) fn tag() -> LinkTag { 91 | LinkTag::new(*Self::TAG) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /tests/src/chat-stats.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuidv4 } from "uuid"; 2 | import { localConductorConfig, awaitIntegration } from './common' 3 | import { installAgents } from "./installAgents"; 4 | 5 | const delay = ms => new Promise(r => setTimeout(r, ms)) 6 | 7 | module.exports = async (orchestrator) => { 8 | 9 | orchestrator.registerScenario('test stats', async (s, t) => { 10 | const config = localConductorConfig; 11 | 12 | const [alice, bob] = await s.players([config, config], false) 13 | await alice.startup() 14 | await bob.startup() 15 | const [alice_chat_happ] = await installAgents(alice, ["alice"]) 16 | const [bob_chat_happ] = await installAgents(bob, ['bobbo']) 17 | const [alice_chat] = alice_chat_happ.cells 18 | const [bob_chat] = bob_chat_happ.cells 19 | 20 | await s.shareAllNodes([alice, bob]); 21 | 22 | // bob declares self as chatter 23 | await bob_chat.call('chat', 'refresh_chatter', null); 24 | // alice declares self as chatter 25 | await alice_chat.call('chat', 'refresh_chatter', null); 26 | 27 | // Create a channel 28 | const channel_uuid = uuidv4(); 29 | const channel = await alice_chat.call('chat', 'create_channel', { name: "Test Channel", entry: { category: "General", uuid: channel_uuid } }); 30 | console.log("CHANNEL: >>>", channel.entry); 31 | 32 | const msg1 = { 33 | last_seen: { First: null }, 34 | channel: channel.entry, 35 | entry: { 36 | uuid: uuidv4(), 37 | content: "Hello from alice :)", 38 | } 39 | } 40 | 41 | console.log('msg1', msg1) 42 | 43 | const r1 = await alice_chat.call('chat', 'create_message', msg1); 44 | t.deepEqual(r1.entry, msg1.entry); 45 | 46 | const msg2 = { 47 | last_seen: { First: null }, 48 | channel: channel.entry, 49 | entry: { 50 | uuid: uuidv4(), 51 | content: "second messages", 52 | } 53 | } 54 | const r2 = await alice_chat.call('chat', 'create_message', msg2); 55 | t.deepEqual(r2.entry, msg2.entry); 56 | 57 | const channel_uuid2 = uuidv4(); 58 | const channel2 = await alice_chat.call('chat', 'create_channel', { name: "Test2 Channel", entry: { category: "General", uuid: channel_uuid2 } }); 59 | 60 | const msg3 = { 61 | last_seen: { First: null }, 62 | channel: channel2.entry, 63 | entry: { 64 | uuid: uuidv4(), 65 | content: "Hello from bob :)", 66 | } 67 | } 68 | const r3 = await alice_chat.call('chat', 'create_message', msg3); 69 | t.deepEqual(r3.entry, msg3.entry); 70 | 71 | await awaitIntegration(alice_chat) 72 | await awaitIntegration(bob_chat) 73 | 74 | let stats = await alice_chat.call('chat', 'stats', { category: "General" }); 75 | t.deepEqual(stats, { agents: 2, active: 2, channels: 2, messages: 0 }); // stats for messages is depricated 76 | 77 | await delay(1500) 78 | 79 | stats = await bob_chat.call('chat', 'stats', { category: "General" }); 80 | t.deepEqual(stats, { agents: 2, active: 2, channels: 2, messages: 0 }); // stats for messages is depricated 81 | 82 | }) 83 | } 84 | -------------------------------------------------------------------------------- /tests/src/batching.ts: -------------------------------------------------------------------------------- 1 | import * as _ from "lodash"; 2 | import { v4 as uuidv4 } from "uuid"; 3 | import { localConductorConfig, delay, awaitIntegration } from "./common"; 4 | import { installAgents } from "./installAgents"; 5 | 6 | module.exports = async (orchestrator) => { 7 | orchestrator.registerScenario("chat with batches", async (s, t) => { 8 | // Declare two players using the previously specified config, nicknaming them "alice" and "bob" 9 | // note that the first argument to players is just an array conductor configs that that will 10 | // be used to spin up the conductor processes which are returned in a matching array. 11 | const [a_and_b_conductor] = await s.players([localConductorConfig]); 12 | 13 | // install your happs into the coductors and destructuring the returned happ data using the same 14 | // array structure as you created in your installation array. 15 | let [alice_chat_happ, bobbo_chat_happ] = await installAgents( 16 | a_and_b_conductor, 17 | ["alice", "bobbo"] 18 | ); 19 | const [alice_chat] = alice_chat_happ.cells; 20 | const [bobbo_chat] = bobbo_chat_happ.cells; 21 | 22 | // Create a channel 23 | const channel_uuid = uuidv4(); 24 | const channel = await alice_chat.call("chat", "create_channel", { 25 | name: "Test Channel", 26 | entry: { category: "General", uuid: channel_uuid }, 27 | }); 28 | console.log(channel); 29 | 30 | const num_messages = 10 31 | let micros = 200000000 32 | const messages: { content: string, timestamp : number }[] = [] 33 | 34 | for (let i = 0; i < num_messages; i++) { 35 | micros += (100000000 * 1000_000) 36 | messages.push({ content: "", timestamp: micros }) 37 | } 38 | 39 | micros += (10 * 1000_000) 40 | messages.push({ content: "", timestamp: micros }) 41 | micros += (10 * 1000_000) 42 | messages.push({ content: "", timestamp: micros }) 43 | 44 | await alice_chat.call("chat", "insert_fake_messages", { channel: channel.entry, messages }) 45 | 46 | await awaitIntegration(alice_chat); 47 | await awaitIntegration(bobbo_chat); 48 | 49 | // Alice lists the messages 50 | let results = await alice_chat.call("chat", "list_messages", { 51 | channel: channel.entry, 52 | active_chatter: false, 53 | target_message_count: 1, 54 | }); 55 | t.is(results.messages.length, 3); // because 3 are clustered in the last hour 56 | results = await alice_chat.call("chat", "list_messages", { 57 | channel: channel.entry, 58 | active_chatter: false, 59 | target_message_count: 3, 60 | }); 61 | t.is(results.messages.length, 3); 62 | results = await alice_chat.call("chat", "list_messages", { 63 | channel: channel.entry, 64 | active_chatter: false, 65 | target_message_count: 5, 66 | }); 67 | t.is(results.messages.length, 5); 68 | results = await alice_chat.call("chat", "list_messages", { 69 | channel: channel.entry, 70 | active_chatter: false, 71 | target_message_count: 10, 72 | }); 73 | t.is(results.messages.length, 10); 74 | results = await alice_chat.call("chat", "list_messages", { 75 | channel: channel.entry, 76 | active_chatter: false, 77 | target_message_count: 12, 78 | }); 79 | t.is(results.messages.length, 12); 80 | results = await alice_chat.call("chat", "list_messages", { 81 | channel: channel.entry, 82 | active_chatter: false, 83 | target_message_count: 100, 84 | }); 85 | t.is(results.messages.length, 12); 86 | 87 | console.log(`got ${results.messages.length} messages`); 88 | //t.deepEqual(bobbos_view.messages.length, 10) 89 | //console.log("ALICE ", alices_view); 90 | //console.log("BOBBO: ", bobbos_view); 91 | }); 92 | }; 93 | -------------------------------------------------------------------------------- /tests/src/behaviors/index.ts: -------------------------------------------------------------------------------- 1 | import { Orchestrator, tapeExecutor, compose } from '@holochain/tryorama' 2 | import { defaultConfig, gossipTx, signalTx, phasesTx } from './tx-per-second' // import config and runner here 3 | import { v4 as uuidv4 } from "uuid"; 4 | 5 | process.on('unhandledRejection', error => { 6 | console.error('****************************'); 7 | console.error('got unhandledRejection:', error); 8 | console.error('****************************'); 9 | }); 10 | 11 | const runName = process.argv[2] || "" + Date.now() // default exam name is just a timestamp 12 | let config = process.argv[3] ? require(process.argv[3]) : defaultConfig // use imported config or one passed as a test arg 13 | 14 | console.log(`Running behavior test id=${runName} with:\n`, config) 15 | 16 | // Below this line should not need changes 17 | 18 | config.numConductors = config.nodes * config.conductors 19 | 20 | const local = false 21 | 22 | const middleware = /*config.endpoints 23 | ? compose(tapeExecutor(require('tape')), groupPlayersByMachine(config.endpoints, config.conductors)) 24 | :*/ undefined 25 | 26 | const orchestrator = new Orchestrator({ middleware }) 27 | 28 | const trial: string = "phases" 29 | 30 | if (trial === "gossip") { 31 | orchestrator.registerScenario('Measuring messages per-second--gossip', async (s, t) => { 32 | let txCount = 1 33 | while (true) { 34 | t.comment(`trial with ${txCount} tx`) 35 | // bump the scenario UID for each run of the trial so a different DNA hash will be generated 36 | s._uid = uuidv4(); 37 | const duration = await gossipTx(s, t, config, txCount, local) 38 | const txPerSecond = txCount / (duration * 1000) 39 | t.comment(`took ${duration}ms to receive ${txCount} messages through gossip. TPS: ${txPerSecond}`) 40 | txCount *= 2 41 | } 42 | }) 43 | } else if (trial === "signal") { 44 | const period = 60 * 1000 // timeout 45 | orchestrator.registerScenario('Measuring messages per-second--signals', async (s, t) => { 46 | let txCount = 100 47 | let duration 48 | let txPerSecondAtMax = 0 49 | t.comment(`trial with a network of ${config.nodes} nodes, ${config.conductors} conductors per node, and ${config.instances} cells per conductor, but only ${config.activeAgents} active agents (cells)`) 50 | do { 51 | t.comment(`trial with ${txCount} tx per ${period}ms`) 52 | // bump the scenario UID for each run of the trial so a different DNA hash will be generated 53 | s._uid = uuidv4(); 54 | duration = await signalTx(s, t, config, period, txCount, local) 55 | if (!duration) { 56 | t.comment(`failed when attempting ${txCount} messages`) 57 | break; 58 | } else { 59 | t.comment(`succeeded when attempting ${txCount} messages in ${duration}`) 60 | txCount *= 2 61 | } 62 | } while (true) 63 | }) 64 | } else if (trial === "phases") { 65 | const phases = [ 66 | { 67 | period: 1000 * 60 * 1, 68 | messages: 10, 69 | active: 5, 70 | senders: 5, 71 | }, 72 | /* { 73 | period: 1000 * 60 * 1, 74 | messages: 133, 75 | active: 10, 76 | senders: 1, 77 | }*/ 78 | ] 79 | orchestrator.registerScenario('Measuring messages per-second--phases', async (s, t) => { 80 | t.comment(`trial with a network of ${config.nodes} nodes, ${config.conductors} conductors per node, and ${config.instances} cells per conductor, in the following phases: ${JSON.stringify(phases)}`) 81 | s._uid = uuidv4(); 82 | await phasesTx(s, t, config, phases, local); 83 | }); 84 | } 85 | 86 | orchestrator.run() 87 | -------------------------------------------------------------------------------- /tests/src/common.ts: -------------------------------------------------------------------------------- 1 | import { Config } from '@holochain/tryorama' 2 | 3 | export const RETRY_DELAY = 5000 4 | export const RETRY_COUNT = 16 5 | 6 | // Set up a Conductor configuration using the handy `Conductor.config` helper. 7 | // Read the docs for more on configuration. 8 | export const localConductorConfig = Config.gen() 9 | 10 | import { TransportConfigType, ProxyConfigType, NetworkType } from '@holochain/tryorama' 11 | export const NETWORK = { 12 | bootstrap_service: "https://holodev-bootstrap.holo.host/", 13 | network_type: NetworkType.QuicBootstrap, 14 | transport_pool: [{ 15 | type: TransportConfigType.Proxy, 16 | sub_transport: { type: TransportConfigType.Quic }, 17 | proxy_config: { 18 | type: ProxyConfigType.RemoteProxyClient, 19 | proxy_url: "kitsune-proxy://f3gH2VMkJ4qvZJOXx0ccL_Zo5n-s_CnBjSzAsEHHDCA/kitsune-quic/h/137.184.142.208/p/5788/--", 20 | } 21 | }], 22 | tuning_params: { 23 | // holo-nixpkgs settings 24 | // gossip_strategy: "sharded-gossip", 25 | // default_rpc_multi_remote_agent_count: 1, 26 | // gossip_loop_iteration_delay_ms: 2000, // # Default was 10 27 | // agent_info_expires_after_ms: 1000 * 60 * 30, // # Default was 20 minutes 28 | tx2_channel_count_per_connection: 16, // # Default was 3 29 | // default_rpc_multi_remote_request_grace_ms: 10, 30 | // gossip_single_storage_arc_per_space: true, 31 | // Test settings 32 | gossip_loop_iteration_delay_ms: 200, //number // default 10 33 | default_notify_remote_agent_count: 5, //number // default 5 34 | default_notify_timeout_ms: 100, //number // default 1000 35 | default_rpc_single_timeout_ms: 20000, // number // default 2000 36 | default_rpc_multi_remote_agent_count: 2, //number // default 2 37 | default_rpc_multi_timeout_ms: 2000, //number // default 2000 38 | agent_info_expires_after_ms: 1000 * 60 * 20, //number // default 1000 * 60 * 20 (20 minutes) 39 | tls_in_mem_session_storage: 512, // default 512 40 | proxy_keepalive_ms: 1000 * 30, // default 1000 * 60 * 2 (2 minutes) 41 | proxy_to_expire_ms: 1000 * 60 * 5 // default 1000 * 60 * 5 (5 minutes) 42 | 43 | } 44 | } 45 | 46 | export const networkedConductorConfig = Config.gen({ network: NETWORK }) 47 | 48 | export const delay = ms => new Promise(r => setTimeout(r, ms)) 49 | 50 | export const awaitIntegration = async (cell) => { 51 | while (true) { 52 | const dump = await cell.stateDump() 53 | console.log("integration dump was:", dump) 54 | const idump = dump[0].integration_dump 55 | if (idump.validation_limbo == 0 56 | // && idump.integration_limbo == 0 57 | ) { 58 | break 59 | } 60 | console.log("waiting 5 seconds for integration") 61 | await delay(5000) 62 | } 63 | } 64 | 65 | export const consistency = async (cells) => { 66 | // 20 Seconds. 67 | const MAX_TIMEOUT = 1000 * 20; 68 | var total_published = 0; 69 | for (const cell of cells) { 70 | const dump = await cell.stateDump() 71 | const sdump = dump[0].source_chain_dump 72 | total_published += sdump.published_ops_count; 73 | } 74 | while (true) { 75 | var total_integrated = 0; 76 | var total_missing = 0; 77 | for (const cell of cells) { 78 | const dump = await cell.stateDump() 79 | console.log("integration dump was:", dump) 80 | const idump = dump[0].integration_dump 81 | if (idump.integrated >= total_published) { 82 | total_integrated += 1; 83 | } else { 84 | total_missing += total_published - idump.integrated; 85 | } 86 | console.log("Missing ", total_missing, "ops. Waiting 0.5 seconds for integration") 87 | await delay(500) 88 | } 89 | if (cells.length == total_integrated) { 90 | return; 91 | } 92 | } 93 | } 94 | 95 | export const awaitPeers = async (cell, count) => { 96 | while (true) { 97 | const dump = await cell.stateDump() 98 | console.log("peer dump was:", dump) 99 | const peer_dump = dump[0].peer_dump 100 | if (peer_dump.peers.length >= count) { 101 | break 102 | } 103 | console.log("waiting 5 seconds for peers to reach", count) 104 | await delay(5000) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # 2 | # Test and build Elemental Chat Project 3 | # 4 | # This Makefile is primarily instructional; you can simply enter the Nix environment for 5 | # holochain-rust development (supplied by holonix;) via `nix-shell` and run 6 | # `make test` directly, or build a target directly, eg. `nix-build -A elemental-chat 7 | # 8 | SHELL = bash 9 | DNANAME = elemental-chat 10 | DNA = $(DNANAME).dna 11 | HAPP = $(DNANAME).happ 12 | WASM = target/wasm32-unknown-unknown/release/chat.wasm 13 | WASM2 = target/wasm32-unknown-unknown/release/profile.wasm 14 | 15 | .PHONY: DNAs 16 | 17 | dnas: 18 | mkdir -p ./dnas 19 | dnas/joining-code-factory.dna: dnas 20 | curl 'https://holo-host.github.io/joining-code-happ/releases/downloads/0_3_0/joining-code-factory.0_3_0.dna' -o $@ 21 | 22 | DNAs: dnas/joining-code-factory.dna 23 | 24 | # External targets; Uses a nix-shell environment to obtain Holochain runtimes, run tests, etc. 25 | .PHONY: all FORCE 26 | all: nix-test 27 | 28 | # nix-test, nix-install, ... 29 | nix-%: 30 | nix-shell --pure --run "make $*" 31 | 32 | # Internal targets; require a Nix environment in order to be deterministic. 33 | # - Uses the version of `hc` and `holochain` on the system PATH. 34 | # - Normally called from within a Nix environment, eg. run `nix-shell` 35 | .PHONY: rebuild install build build-cargo build-dna 36 | rebuild: clean build 37 | 38 | install: build 39 | 40 | build: build-cargo build-dna 41 | 42 | build: $(DNA) 43 | 44 | # Package the DNA from the built target release WASM 45 | $(DNA): $(WASM) FORCE 46 | @echo "Packaging DNA: hc path: `which hc`" 47 | @hc dna pack . -o $(DNA) 48 | @hc app pack . -o $(HAPP) 49 | @ls -l $@ 50 | 51 | # Recompile the target release WASM 52 | $(WASM): FORCE 53 | @echo "Building DNA WASM:" 54 | @RUST_BACKTRACE=1 CARGO_TARGET_DIR=target cargo build \ 55 | --release --target wasm32-unknown-unknown 56 | @echo "Optimizing wasms:" 57 | 58 | .PHONY: test test-all test-unit test-e2e test-dna test-dna-debug test-stress test-sim2h test-node 59 | test-all: test 60 | 61 | test: test-unit test-e2e # test-stress # re-enable when Stress tests end reliably 62 | 63 | test-unit: $(DNA) FORCE 64 | RUST_BACKTRACE=1 cargo test \ 65 | -- --nocapture 66 | 67 | test-dna: DNAs $(DNA) FORCE 68 | @echo "Starting Scenario tests in $$(pwd)..."; \ 69 | cd tests && ( [ -d node_modules ] || npm install ) && npm test 70 | 71 | test-dna-debug: DNAs $(DNA) FORCE 72 | @echo "Starting Scenario tests in $$(pwd)..."; \ 73 | cd tests && ( [ -d node_modules ] || npm install ) && npm run test:debug 74 | 75 | test-behavior: DNAs $(DNA) FORCE 76 | @echo "Starting Scenario tests in $$(pwd)..."; \ 77 | cd tests && ( [ -d node_modules ] || npm install ) && npm run test:behavior 78 | 79 | test-e2e: test-dna 80 | 81 | ############################# 82 | # █▀█ █▀▀ █░░ █▀▀ ▄▀█ █▀ █▀▀ 83 | # █▀▄ ██▄ █▄▄ ██▄ █▀█ ▄█ ██▄ 84 | ############################# 85 | # requirements 86 | # - cargo-edit crate: `cargo install cargo-edit` 87 | # - jq linux terminal tool : `sudo apt-get install jq` 88 | # How to make a release? 89 | # make update 90 | 91 | update: 92 | echo '⚙️ Updating hdk crate...' 93 | cargo upgrade hdk@=$(shell jq .hdk ./version-manager.json) --workspace 94 | echo '⚙️ Updating holo_hash crate...' 95 | cargo upgrade holo_hash@=$(shell jq .holo_hash ./version-manager.json) --workspace 96 | echo '⚙️ Updating holochain crate...' 97 | cargo upgrade holochain@=$(shell jq .holochain ./version-manager.json) --workspace 98 | echo '⚙️ Updating hc_utils crate...' 99 | cargo upgrade hc_utils@=$(shell jq .hc_utils ./version-manager.json) --workspace 100 | echo '⚙️ Updating holochainVersionId in nix...' 101 | sed -i -e 's/^ holonixRevision = .*/ holonixRevision = $(shell jq .holonix_rev ./version-manager.json);/' config.nix;\ 102 | sed -i -e 's/^ holochainVersionId = .*/ holochainVersionId = $(shell jq .holochain_rev ./version-manager.json);/' config.nix;\ 103 | echo '⚙️ Building dnas and happ...' 104 | rm -rf Cargo.lock 105 | make nix-build 106 | echo '⚙️ Running tests...' 107 | make nix-test-dna-debug 108 | 109 | # release-%: 110 | # echo '⚙️ Making new release...' 111 | # make HC_REV=$(HC_REV) github-release-$* 112 | # echo '🚀 Successful release elemental-chat '$* 113 | 114 | 115 | # Generic targets; does not require a Nix environment 116 | .PHONY: clean 117 | clean: 118 | rm -rf \ 119 | tests/node_modules \ 120 | .cargo \ 121 | target \ 122 | dnas 123 | -------------------------------------------------------------------------------- /tests/src/transient-nodes.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash' 2 | import { v4 as uuidv4 } from "uuid"; 3 | import { RETRY_DELAY, RETRY_COUNT, localConductorConfig, networkedConductorConfig, awaitIntegration, delay } from './common' 4 | import { installAgents } from './installAgents' 5 | 6 | module.exports = async (orchestrator) => { 7 | orchestrator.registerScenario('transient nodes-local', async (s, t) => { 8 | await doTransientNodes(s, t, true) 9 | }) 10 | /* Restore when tryorama double registerScenario bug fixed 11 | orchestrator.registerScenario('transient nodes-proxied', async (s, t) => { 12 | await doTransientNodes(s, t, false) 13 | })*/ 14 | } 15 | 16 | const gotChannelsAndMessages = async (t, name, happ, channelEntry, retry_count, retry_delay) => { 17 | var retries = retry_count 18 | while (true) { 19 | const channel_list = await happ.call('chat', 'list_channels', { category: "General" }); 20 | console.log(`${name}'s channel list:`, channel_list.channels); 21 | let batch_payload = { channel: channelEntry, active_chatter: false, target_message_count: 2 } 22 | 23 | const r = await happ.call('chat', 'list_messages', batch_payload) 24 | t.ok(r) 25 | console.log(`${name}'s message list:`, r); 26 | if (r.messages.length > 0) { 27 | t.equal(r.messages.length, 1) 28 | break; 29 | } 30 | else { 31 | retries -= 1; 32 | if (retries == 0) { 33 | t.fail(`bailing after ${retry_count} retries waiting for ${name}`) 34 | break; 35 | } 36 | } 37 | console.log(`retry ${retries}`); 38 | await delay(retry_delay) 39 | } 40 | } 41 | 42 | 43 | 44 | const doTransientNodes = async (s, t, local) => { 45 | const config = local ? localConductorConfig : networkedConductorConfig; 46 | 47 | const [alice, bob, carol] = await s.players([config, config, config], false) 48 | await alice.startup() 49 | await bob.startup() 50 | 51 | 52 | 53 | const [alice_chat_happ] = await installAgents(alice, ["alice"]) 54 | const [bob_chat_happ] = await installAgents(bob, ['bobbo']) 55 | const [alice_chat] = alice_chat_happ.cells 56 | const [bob_chat] = bob_chat_happ.cells 57 | 58 | if (local) { 59 | await s.shareAllNodes([alice, bob]); 60 | } 61 | 62 | // Create a channel 63 | const channel_uuid = uuidv4(); 64 | const channel = await alice_chat.call('chat', 'create_channel', { name: "Test Channel", entry: { category: "General", uuid: channel_uuid } }); 65 | 66 | const msg1 = { 67 | last_seen: { First: null }, 68 | channel: channel.entry, 69 | entry: { 70 | uuid: uuidv4(), 71 | content: "Hello from alice :)", 72 | } 73 | } 74 | const r1 = await alice_chat.call('chat', 'create_message', msg1); 75 | t.deepEqual(r1.entry, msg1.entry); 76 | 77 | console.log("******************************************************************") 78 | console.log("checking to see if bob can see the message") 79 | await gotChannelsAndMessages(t, "bob", bob_chat, channel.entry, RETRY_COUNT, RETRY_DELAY) 80 | console.log("waiting for bob to integrate the message not just see it via get") 81 | await awaitIntegration(bob_chat) 82 | await delay(10000) 83 | console.log("shutting down alice") 84 | await alice.shutdown() 85 | await delay(10000) 86 | console.log("checking again to see if bob can see the message") 87 | await gotChannelsAndMessages(t, "bob", bob_chat, channel.entry, RETRY_COUNT, RETRY_DELAY) 88 | await carol.startup() 89 | let [carol_chat_happ] = await installAgents(carol, ["carol"]) 90 | const [carol_chat] = carol_chat_happ.cells 91 | 92 | if (local) { 93 | await s.shareAllNodes([carol, bob]); 94 | } 95 | 96 | console.log("******************************************************************") 97 | console.log("checking to see if carol can see the message via bob") 98 | await gotChannelsAndMessages(t, "carol", carol_chat, channel.entry, RETRY_COUNT, RETRY_DELAY) 99 | 100 | // This above loop SHOULD work because carol should get the message via bob, but it doesn't 101 | // So we try starting up alice and getting the message gossiped that way, but that also 102 | // doesn't work! 103 | await alice.startup() 104 | if (local) { 105 | await s.shareAllNodes([carol, alice]); 106 | } 107 | console.log("******************************************************************") 108 | console.log("checking to see if carol can see the message via alice after back on") 109 | await gotChannelsAndMessages(t, "carol", carol_chat, channel.entry, RETRY_COUNT, RETRY_DELAY) 110 | 111 | } 112 | -------------------------------------------------------------------------------- /zomes/chat/src/entries/message.rs: -------------------------------------------------------------------------------- 1 | use super::channel::{Channel, ChannelData}; 2 | use crate::{error::ChatError, error::ChatResult, timestamp::Timestamp}; 3 | pub use chat_integrity::{ChannelInfo, Message}; 4 | use hdk::prelude::*; 5 | pub mod handlers; 6 | 7 | /// This allows the app to properly order messages. 8 | /// This message is either the first message of the time block 9 | /// or has another message that was observed at the time of sending. 10 | #[derive(Debug, Serialize, Deserialize, SerializedBytes, Clone, PartialEq)] 11 | pub enum LastSeen { 12 | First, 13 | Message(EntryHash), 14 | } 15 | 16 | /// Input to the create message call 17 | #[derive(Debug, Serialize, Deserialize, SerializedBytes, Clone, PartialEq)] 18 | pub struct MessageInput { 19 | pub last_seen: LastSeen, 20 | pub channel: Channel, 21 | pub entry: Message, 22 | } 23 | 24 | /// The message type that goes to the UI 25 | #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, SerializedBytes)] 26 | #[serde(rename_all = "camelCase")] 27 | pub struct MessageData { 28 | pub entry: Message, 29 | pub entry_hash: EntryHash, 30 | pub created_by: AgentPubKey, 31 | pub created_at: Timestamp, 32 | } 33 | 34 | // Input to the signal_specific_chatters call 35 | #[derive(Debug, Serialize, Deserialize, SerializedBytes)] 36 | pub struct SignalSpecificInput { 37 | signal_message_data: SignalMessageData, 38 | chatters: Vec, 39 | include_active_chatters: Option, 40 | } 41 | 42 | /// The message type that goes to the UI via emit_signal 43 | #[derive(Debug, Serialize, Deserialize, SerializedBytes, Clone)] 44 | #[serde(rename_all = "camelCase")] 45 | pub struct SignalMessageData { 46 | pub message_data: MessageData, 47 | pub channel_data: ChannelData, 48 | } 49 | /// Input to the list messages call 50 | #[derive(Debug, Clone, Serialize, Deserialize, SerializedBytes)] 51 | pub struct ListMessagesInput { 52 | pub channel: Channel, 53 | pub earliest_seen: Option, 54 | // Keep expanding search interval until this count is reached 55 | pub target_message_count: usize, // UI will say 20 to start 56 | } 57 | 58 | #[derive(Debug, Serialize, Deserialize, SerializedBytes)] 59 | pub struct SigResults { 60 | pub total: usize, 61 | pub sent: Vec, 62 | } 63 | 64 | #[derive(Debug, Serialize, Deserialize, SerializedBytes)] 65 | pub struct ActiveChatters { 66 | pub chatters: Vec, 67 | } 68 | 69 | /// The messages returned from list messages 70 | #[derive(Debug, Serialize, Deserialize, SerializedBytes, derive_more::From, Clone, PartialEq)] 71 | pub struct ListMessages { 72 | pub messages: Vec, 73 | } 74 | 75 | impl MessageData { 76 | pub fn new(action: Action, message: Message) -> ChatResult { 77 | let entry_hash = action 78 | .entry_hash() 79 | .ok_or(ChatError::WrongActionType)? 80 | .clone(); 81 | Ok(Self { 82 | entry: message, 83 | entry_hash, 84 | created_by: action.author().to_owned(), 85 | created_at: action.timestamp().to_owned(), 86 | }) 87 | } 88 | } 89 | 90 | impl SignalMessageData { 91 | pub fn new(message_data: MessageData, channel_data: ChannelData) -> Self { 92 | Self { 93 | message_data, 94 | channel_data, 95 | } 96 | } 97 | } 98 | 99 | /// This key allows us to sort the messages by who they reply to 100 | /// then by time 101 | #[derive(Debug, Clone, Serialize, Deserialize, SerializedBytes, Ord, PartialOrd, Eq, PartialEq)] 102 | struct LastSeenKey { 103 | parent_hash: EntryHash, 104 | timestamp: Option, 105 | } 106 | 107 | impl LastSeenKey { 108 | pub fn new(parent_hash: EntryHash, timestamp: Timestamp) -> Self { 109 | Self { 110 | parent_hash, 111 | timestamp: Some(timestamp), 112 | } 113 | } 114 | } 115 | 116 | impl From for LastSeenKey { 117 | fn from(parent_hash: EntryHash) -> Self { 118 | Self { 119 | parent_hash, 120 | timestamp: None, 121 | } 122 | } 123 | } 124 | 125 | impl From for LinkTag { 126 | fn from(key: LastSeenKey) -> Self { 127 | Self::new(UnsafeBytes::from( 128 | SerializedBytes::try_from(key).expect("This serialization should never fail"), 129 | )) 130 | } 131 | } 132 | 133 | impl From for LastSeenKey { 134 | fn from(t: LinkTag) -> Self { 135 | Self::try_from(SerializedBytes::from(UnsafeBytes::from(t.0))) 136 | .expect("This serialization should never fail") 137 | } 138 | } 139 | -------------------------------------------------------------------------------- /tests/src/profile.ts: -------------------------------------------------------------------------------- 1 | import { localConductorConfig, awaitIntegration } from './common' 2 | import { installJCHapp, installAgents } from './installAgents' 3 | const wait = (ms) => new Promise((r, j) => setTimeout(r, ms)) 4 | 5 | module.exports = (orchestrator) => { 6 | orchestrator.registerScenario('test profile zomes', async (s, t) => { 7 | // spawn the conductor process 8 | const [conductor] = await s.players([localConductorConfig]) 9 | 10 | const jcHapp = await installJCHapp( 11 | ( 12 | await s.players([localConductorConfig]) 13 | )[0] 14 | ) 15 | 16 | const [alice_chat_happ, bob_chat_happ] = await installAgents( 17 | conductor, 18 | ['alice', 'bobbo'], 19 | jcHapp 20 | ) 21 | const [alice] = alice_chat_happ.cells 22 | const [bobbo] = bob_chat_happ.cells 23 | 24 | // Trigger init to run in both zomes 25 | await alice.call('chat', 'list_channels', { category: 'General' }) 26 | await bobbo.call('chat', 'list_channels', { category: 'General' }) 27 | 28 | // Create a channel 29 | const profile_input = { 30 | nickname: 'Alice', 31 | avatar_url: 'https://alice.img', 32 | } 33 | let profile_hash 34 | 35 | try { 36 | profile_hash = await alice.call( 37 | 'profile', 38 | 'update_my_profile', 39 | profile_input 40 | ) 41 | console.log('PROFILE_Hash:', profile_hash) 42 | t.ok(profile_hash) 43 | } catch (e) { 44 | console.error('Error: ', e) 45 | t.fail() 46 | } 47 | 48 | let a_check_a_profile = await alice.call('profile', 'get_my_profile', null) 49 | console.log('Alice checks her profile:', a_check_a_profile) 50 | t.ok(a_check_a_profile) 51 | t.equal(profile_input.nickname, a_check_a_profile.nickname) 52 | t.equal(profile_input.avatar_url, a_check_a_profile.avatar_url) 53 | await awaitIntegration(alice) 54 | await awaitIntegration(bobbo) 55 | let bobbo_check_alice_profile = await bobbo.call( 56 | 'profile', 57 | 'get_profile', 58 | a_check_a_profile.agent_address 59 | ) 60 | console.log("Bobbo checks alice's profile:", bobbo_check_alice_profile) 61 | t.ok(bobbo_check_alice_profile) 62 | t.equal(profile_input.nickname, bobbo_check_alice_profile.nickname) 63 | t.equal(profile_input.avatar_url, bobbo_check_alice_profile.avatar_url) 64 | 65 | await wait(1000) 66 | const updated_profile_input_1 = { 67 | nickname: 'Alicia', 68 | avatar_url: 'https://alicia.img', 69 | } 70 | profile_hash = await alice.call( 71 | 'profile', 72 | 'update_my_profile', 73 | updated_profile_input_1 74 | ) 75 | console.log('PROFILE_Hash:', profile_hash) 76 | t.ok(profile_hash) 77 | 78 | await awaitIntegration(alice) 79 | await awaitIntegration(bobbo) 80 | 81 | a_check_a_profile = await alice.call('profile', 'get_my_profile', null) 82 | console.log('Alice checks her updated profile:', a_check_a_profile) 83 | t.ok(a_check_a_profile) 84 | t.equal(updated_profile_input_1.nickname, a_check_a_profile.nickname) 85 | t.equal(updated_profile_input_1.avatar_url, a_check_a_profile.avatar_url) 86 | 87 | bobbo_check_alice_profile = await bobbo.call( 88 | 'profile', 89 | 'get_profile', 90 | a_check_a_profile.agent_address 91 | ) 92 | console.log( 93 | "Bobbo checks alice's updated profile:", 94 | bobbo_check_alice_profile 95 | ) 96 | t.ok(bobbo_check_alice_profile) 97 | t.equal( 98 | updated_profile_input_1.nickname, 99 | bobbo_check_alice_profile.nickname 100 | ) 101 | t.equal( 102 | updated_profile_input_1.avatar_url, 103 | bobbo_check_alice_profile.avatar_url 104 | ) 105 | 106 | await wait(1000) 107 | const updated_profile_input_2 = { 108 | nickname: 'Alexandria', 109 | avatar_url: 'https://alexandria.img', 110 | } 111 | profile_hash = await alice.call( 112 | 'profile', 113 | 'update_my_profile', 114 | updated_profile_input_2 115 | ) 116 | console.log('PROFILE_Hash:', profile_hash) 117 | t.ok(profile_hash) 118 | 119 | await awaitIntegration(alice) 120 | await awaitIntegration(bobbo) 121 | 122 | a_check_a_profile = await alice.call('profile', 'get_my_profile', null) 123 | console.log('Alice checks her updated profile:', a_check_a_profile) 124 | t.ok(a_check_a_profile) 125 | t.equal(updated_profile_input_2.nickname, a_check_a_profile.nickname) 126 | t.equal(updated_profile_input_2.avatar_url, a_check_a_profile.avatar_url) 127 | 128 | bobbo_check_alice_profile = await bobbo.call( 129 | 'profile', 130 | 'get_profile', 131 | a_check_a_profile.agent_address 132 | ) 133 | console.log( 134 | "Bobbo checks alice's updated profile:", 135 | bobbo_check_alice_profile 136 | ) 137 | t.ok(bobbo_check_alice_profile) 138 | t.equal( 139 | updated_profile_input_2.nickname, 140 | bobbo_check_alice_profile.nickname 141 | ) 142 | t.equal( 143 | updated_profile_input_2.avatar_url, 144 | bobbo_check_alice_profile.avatar_url 145 | ) 146 | }) 147 | } 148 | -------------------------------------------------------------------------------- /tests/src/chat-signals.ts: -------------------------------------------------------------------------------- 1 | import { v4 as uuidv4 } from "uuid"; 2 | import { localConductorConfig, consistency, awaitIntegration } from './common' 3 | import { installAgents } from "./installAgents"; 4 | const { Codec } = require("@holo-host/cryptolib"); 5 | 6 | const delay = ms => new Promise(r => setTimeout(r, ms)) 7 | 8 | module.exports = async (orchestrator) => { 9 | 10 | orchestrator.registerScenario('test signal', async (s, t) => { 11 | const config = localConductorConfig; 12 | 13 | const [alice, bob] = await s.players([config, config], false) 14 | await alice.startup() 15 | await bob.startup() 16 | let MESSAGE = { 17 | uuid: uuidv4(), 18 | content: "Hello from alice :)", 19 | } 20 | let receivedCount = 0 21 | bob.setSignalHandler((signal) => { 22 | console.log("Received Signal:", signal) 23 | t.deepEqual(signal.data.payload.signal_payload.messageData.entry, MESSAGE) 24 | receivedCount += 1 25 | }) 26 | const [alice_chat_happ] = await installAgents(alice, ["alice"]) 27 | const [bob_chat_happ] = await installAgents(bob, ['bobbo']) 28 | const [alice_chat] = alice_chat_happ.cells 29 | const [bob_chat] = bob_chat_happ.cells 30 | 31 | await s.shareAllNodes([alice, bob]); 32 | 33 | let stats = await alice_chat.call('chat', 'stats', { category: "General" }); 34 | t.deepEqual(stats, { agents: 0, active: 0, channels: 0, messages: 0 }); 35 | 36 | // bob declares self as chatter 37 | await bob_chat.call('chat', 'refresh_chatter', null); 38 | // alice declares self as chatter 39 | await alice_chat.call('chat', 'refresh_chatter', null); 40 | 41 | // Create a channel 42 | const channel_uuid = uuidv4(); 43 | const channel = await alice_chat.call('chat', 'create_channel', { name: "Test Channel", entry: { category: "General", uuid: channel_uuid } }); 44 | console.log("CHANNEL: >>>", channel); 45 | 46 | const msg1 = { 47 | last_seen: { First: null }, 48 | channel: channel.entry, 49 | entry: MESSAGE 50 | } 51 | await consistency([bob_chat, alice_chat]); 52 | const r1 = await alice_chat.call('chat', 'create_message', msg1); 53 | t.deepEqual(r1.entry, msg1.entry); 54 | 55 | const signalMessageData = { 56 | messageData: r1, 57 | channelData: channel, 58 | }; 59 | 60 | const r4 = await alice_chat.call('chat', 'signal_chatters', signalMessageData); 61 | t.equal(r4.total, 2) 62 | t.equal(r4.sent.length, 1) 63 | 64 | // waiting for the signal to be received by bob. 65 | for (let i = 0; i < 5; i++) { 66 | if (receivedCount > 0) break; 67 | console.log(`waiting for signal: ${i}`) 68 | await delay(500) 69 | } 70 | // bob should have gotten a signal becayse he's an active chatter 71 | t.equal(receivedCount, 1) 72 | 73 | stats = await alice_chat.call('chat', 'stats', { category: "General" }); 74 | t.deepEqual(stats, { agents: 2, active: 2, channels: 1, messages: 0 }); // stats for messages will not be returned 75 | 76 | await alice_chat.call('chat', 'signal_specific_chatters', { 77 | signal_message_data: signalMessageData, 78 | chatters: [bob_chat.cellId[1]] 79 | }) 80 | 81 | // waiting for the signal to be received by bob. 82 | for (let i = 0; i < 5; i++) { 83 | if (receivedCount > 1) break; 84 | console.log(`waiting for signal: ${i}`) 85 | await delay(500) 86 | } 87 | // bob should have gotten a 2nd signal because he's specified in the call 88 | t.equal(receivedCount, 2) 89 | await awaitIntegration(alice_chat) 90 | await awaitIntegration(bob_chat) 91 | 92 | const result = await alice_chat.call('chat', 'get_active_chatters'); 93 | t.equal(result.chatters.length, 1) 94 | t.equal(Codec.AgentId.encode(result.chatters[0]), Codec.AgentId.encode(bob_chat.cellId[1])) 95 | 96 | await alice_chat.call('chat', 'signal_specific_chatters', { 97 | signal_message_data: signalMessageData, 98 | chatters: [], 99 | include_active_chatters: false 100 | }) 101 | 102 | // waiting for the signal to be received by bob. 103 | for (let i = 0; i < 5; i++) { 104 | if (receivedCount > 2) break; 105 | console.log(`waiting for signal: ${i}`) 106 | await delay(500) 107 | } 108 | // bob should NOT have gotten a 3rd signal because he's not specified in the call 109 | 110 | t.equal(receivedCount, 2) 111 | 112 | await alice_chat.call('chat', 'signal_specific_chatters', { 113 | signal_message_data: signalMessageData, 114 | chatters: [], 115 | include_active_chatters: true 116 | }) 117 | 118 | // waiting for the signal to be received by bob. 119 | for (let i = 0; i < 5; i++) { 120 | if (receivedCount > 2) break; 121 | console.log(`waiting for signal: ${i}`) 122 | await delay(500) 123 | } 124 | // bob should now have gotten a 3rd signal because he's an active chatter and we included active chatters 125 | t.equal(receivedCount, 3) 126 | }) 127 | } 128 | -------------------------------------------------------------------------------- /zomes/chat/src/entries/channel/handlers.rs: -------------------------------------------------------------------------------- 1 | use super::{ChannelData, ChannelInfo, ChannelInfoTag, ChannelList, ChannelListInput}; 2 | use crate::{ 3 | channel::{Channel, ChannelInput}, 4 | error::ChatResult, 5 | }; 6 | use hdk::hash_path::path::{Component, TypedPath}; 7 | use hdk::prelude::*; 8 | use link::Link; 9 | 10 | /// Create a new channel 11 | /// This effectively just stores channel info on the 12 | /// path that is `category:channel_id` 13 | pub(crate) fn create_channel(channel_input: ChannelInput) -> ChatResult { 14 | let ChannelInput { name, entry } = channel_input; 15 | // Create the path for this channel 16 | let path: Path = entry.clone().into(); 17 | let type_path: TypedPath = TypedPath::new( 18 | LinkType::try_from(chat_integrity::LinkTypes::Channel)?, 19 | path, 20 | ); 21 | type_path.ensure()?; 22 | // Create the channel info 23 | let info = ChannelInfo { 24 | category: entry.category.clone(), 25 | uuid: entry.uuid.clone(), 26 | // This agent 27 | created_by: agent_info()?.agent_initial_pubkey, 28 | // Right now 29 | created_at: sys_time()?, 30 | name, 31 | }; 32 | // Commit the channel info 33 | create_entry(chat_integrity::EntryTypes::ChannelInfo(info.clone()))?; 34 | let info_hash = hash_entry(&info)?; 35 | // link the channel info to the path 36 | create_link( 37 | type_path.path_entry_hash()?, 38 | info_hash, 39 | chat_integrity::LinkTypes::Channel, 40 | ChannelInfoTag::tag(), 41 | )?; 42 | // Return the channel and the info for the UI 43 | Ok(ChannelData::new(entry, info)) 44 | } 45 | 46 | fn category_path(category: String) -> ChatResult { 47 | let path = vec![Component::from(category.as_bytes().to_vec())]; 48 | Ok(TypedPath::new( 49 | LinkType::try_from(chat_integrity::LinkTypes::Channel)?, 50 | Path::from(path), 51 | )) 52 | } 53 | 54 | pub(crate) fn list_channels(list_channels_input: ChannelListInput) -> ChatResult { 55 | // Get the category path 56 | let path = category_path(list_channels_input.category)?; 57 | // Get any channels on this path 58 | let links = path.children()?; 59 | let mut channels = Vec::with_capacity(links.len()); 60 | 61 | let mut channel_data: Vec = Vec::new(); 62 | // For each channel get the channel info links and choose the latest 63 | for target in links.into_iter().map(|link| link.target) { 64 | // Path links have their full path as the tag so 65 | // we don't need to get_links on the child. 66 | // The tag can be turned into the channel path 67 | // let channel_path = Path::try_from(&tag)?; 68 | 69 | // // Turn the channel path into the channel 70 | // let channel = Channel::try_from(&channel_path)?; 71 | 72 | // Get any channel info links on this channel 73 | let channel_info = get_links( 74 | target, 75 | LinkTypeRange::from(LinkType::try_from(chat_integrity::LinkTypes::Channel)?), 76 | Some(ChannelInfoTag::tag()), 77 | )?; 78 | 79 | // Find the latest 80 | let latest_info = channel_info 81 | .into_iter() 82 | .fold(None, |latest: Option, link| match latest { 83 | Some(latest) => { 84 | if link.timestamp > latest.timestamp { 85 | Some(link) 86 | } else { 87 | Some(latest) 88 | } 89 | } 90 | None => Some(link), 91 | }); 92 | 93 | // If there is none we will skip this channel 94 | let latest_info: EntryHash = match latest_info.and_then(|l| l.target.into_entry_hash()) { 95 | Some(h) => h, 96 | None => continue, 97 | }; 98 | 99 | channel_data.push(latest_info); 100 | } 101 | let chan_results_input = channel_data 102 | .into_iter() 103 | .map(|t| GetInput::new(t.into(), GetOptions::default())) 104 | .collect(); 105 | let all_channel_results_elements = HDK.with(|hdk| hdk.borrow().get(chan_results_input))?; 106 | // Get the actual channel info entry 107 | for ele in all_channel_results_elements.into_iter() { 108 | if let Some(element) = ele { 109 | if let Some(info) = element.into_inner().1.to_app_option::()? { 110 | // Turn the info into Channel 111 | channels.push(ChannelData { 112 | entry: Channel { 113 | category: info.category.clone(), 114 | uuid: info.uuid.clone(), 115 | }, 116 | info, 117 | }) 118 | } 119 | } 120 | } 121 | 122 | // Return all the channels data to the UI 123 | Ok(channels.into()) 124 | } 125 | 126 | // Note: This function can get very heavy 127 | pub(crate) fn channel_stats(list_channels_input: ChannelListInput) -> ChatResult<(usize, usize)> { 128 | let channel_path = category_path(list_channels_input.category)?; 129 | 130 | let channel_links = channel_path.children()?; 131 | Ok((channel_links.len(), 0)) 132 | } 133 | -------------------------------------------------------------------------------- /tests/src/membrane-proof.ts: -------------------------------------------------------------------------------- 1 | const { Codec } = require('@holo-host/cryptolib') 2 | import { v4 as uuidv4 } from 'uuid' 3 | import { localConductorConfig, awaitIntegration } from './common' 4 | import { installJCHapp, installAgents, Memproof } from './installAgents' 5 | 6 | module.exports = async (orchestrator) => { 7 | orchestrator.registerScenario('membrane proof tests', async (s, t) => { 8 | const [conductor] = await s.players([localConductorConfig]) 9 | const jcHapp1 = await installJCHapp( 10 | ( 11 | await s.players([localConductorConfig]) 12 | )[0] 13 | ) 14 | const jcHapp2 = await installJCHapp( 15 | ( 16 | await s.players([localConductorConfig]) 17 | )[0] 18 | ) 19 | let [alice_chat_happ, bobbo_chat_happ] = await installAgents( 20 | conductor, 21 | ['alice', 'bob'], 22 | jcHapp1 23 | ) 24 | const [alice_chat] = alice_chat_happ.cells 25 | const [bobbo_chat] = bobbo_chat_happ.cells 26 | t.ok(alice_chat) 27 | t.ok(bobbo_chat) 28 | 29 | // zome call triggers init 30 | let channel_list = await alice_chat.call('chat', 'list_channels', { 31 | category: 'General', 32 | }) 33 | console.log('channel_list : ', channel_list) 34 | t.equal(channel_list.channels.length, 0, 'number of channels succeeded') 35 | 36 | await awaitIntegration(alice_chat) 37 | 38 | // TODO: add back in when the proofs carry that agent ID 39 | // this second one should fail because the membrane proofs are agent specific 40 | /* try { 41 | channel_list = await bobbo_chat.call('chat', 'list_channels', { category: "General" }); 42 | t.fail() 43 | } catch(e) { 44 | t.deepEqual(e, { 45 | type: 'error', 46 | data: { 47 | type: 'internal_error', 48 | data: 'The cell tried to run the initialize zomes callback but failed because Fail(ZomeName("chat"), "membrane proof for uhCkknmyjli8dQ_bh8TwZM1YzoJt4LTusPFZIohL4oEn4E3hVi1Tf already used")' 49 | } 50 | }) 51 | }*/ 52 | 53 | // now try and install doug with the read-only membrane proof 54 | let [doug_chat_happ] = await installAgents( 55 | conductor, 56 | ['doug'], 57 | jcHapp1, 58 | (_) => { 59 | return 0 60 | } 61 | ) 62 | const [doug_chat] = doug_chat_happ.cells 63 | // reading the channel list should work 64 | channel_list = await doug_chat.call('chat', 'list_channels', { 65 | category: 'General', 66 | }) 67 | 68 | // creating a channel should fail 69 | try { 70 | const channel = await doug_chat.call('chat', 'create_channel', { 71 | name: 'Test Channel', 72 | entry: { category: 'General', uuid: '123' }, 73 | }) 74 | t.fail() 75 | } catch (e) { 76 | t.deepEqual(e, { 77 | type: 'error', 78 | data: { 79 | type: 'ribosome_error', 80 | data: 'Wasm runtime error while working with Ribosome: RuntimeError: WasmError { file: "zomes/chat/src/error.rs", line: 42, error: Guest("Read only instance") }', 81 | }, 82 | }) 83 | } 84 | 85 | let first_message = { 86 | last_seen: { First: null }, 87 | channel: { category: 'General', uuid: '123' }, 88 | entry: { 89 | uuid: uuidv4(), 90 | content: 'x'.repeat(1), 91 | }, 92 | } 93 | // sending a message should fail 94 | try { 95 | const x = await doug_chat.call('chat', 'create_message', first_message) 96 | t.fail() 97 | } catch (e) { 98 | t.deepEqual(e, { 99 | type: 'error', 100 | data: { 101 | type: 'ribosome_error', 102 | data: 'Wasm runtime error while working with Ribosome: RuntimeError: WasmError { file: "zomes/chat/src/error.rs", line: 42, error: Guest("Read only instance") }', 103 | }, 104 | }) 105 | } 106 | 107 | // now try and install carol with a membrane proof from a different joining code authority 108 | try { 109 | await installAgents(conductor, ['carol'], { 110 | ...jcHapp2, 111 | agent: jcHapp1.agent, 112 | }) 113 | t.fail() 114 | } catch (e) { 115 | t.deepEqual(e, { 116 | type: 'error', 117 | data: { 118 | type: 'internal_error', 119 | data: `Conductor returned an error while using a ConductorApi: GenesisFailed { errors: [ConductorApiError(WorkflowError(GenesisFailure(\ 120 | "Joining code invalid: unexpected author (AgentPubKey(${Codec.AgentId.encode( 121 | jcHapp2.agent 122 | )}))")))] }`, 123 | }, 124 | }) 125 | } 126 | 127 | // now install david with a membrane proof that has a mismatched signature 128 | const corruptMemproofSignature = (memproof: Memproof) => { 129 | const sig = Array.from(memproof.signed_action.signature) 130 | sig[sig.length - 1] ^= 1 131 | const signature = Buffer.from(sig) 132 | return { 133 | ...memproof, 134 | signed_action: { 135 | ...memproof.signed_action, 136 | signature, 137 | }, 138 | } 139 | } 140 | try { 141 | await installAgents( 142 | conductor, 143 | ['david'], 144 | jcHapp1, 145 | corruptMemproofSignature 146 | ) 147 | t.fail() 148 | } catch (e) { 149 | t.deepEqual(e, { 150 | type: 'error', 151 | data: { 152 | type: 'internal_error', 153 | data: 'Conductor returned an error while using a ConductorApi: GenesisFailed { errors: [ConductorApiError(WorkflowError(GenesisFailure("Joining code invalid: incorrect signature")))] }', 154 | }, 155 | }) 156 | } 157 | }) 158 | } 159 | -------------------------------------------------------------------------------- /tests/src/basic-chatting.ts: -------------------------------------------------------------------------------- 1 | import * as _ from 'lodash' 2 | import { v4 as uuidv4 } from 'uuid' 3 | import { localConductorConfig, delay, awaitIntegration } from './common' 4 | import { installAgents } from './installAgents' 5 | 6 | module.exports = async (orchestrator) => { 7 | orchestrator.registerScenario('chat away', async (s, t) => { 8 | // Declare two players using the previously specified config, nicknaming them "alice" and "bob" 9 | // note that the first argument to players is just an array conductor configs that that will 10 | // be used to spin up the conductor processes which are returned in a matching array. 11 | const [a_and_b_conductor] = await s.players([localConductorConfig]) 12 | 13 | // install your happs into the coductors and destructuring the returned happ data using the same 14 | // array structure as you created in your installation array. 15 | let [alice_chat_happ, bobbo_chat_happ] = await installAgents( 16 | a_and_b_conductor, 17 | ['alice', 'bobbo'] 18 | ) 19 | const [alice_chat] = alice_chat_happ.cells 20 | const [bobbo_chat] = bobbo_chat_happ.cells 21 | 22 | // Create a channel 23 | const channel_uuid = uuidv4() 24 | const channel = await alice_chat.call('chat', 'create_channel', { 25 | name: 'Test Channel', 26 | entry: { category: 'General', uuid: channel_uuid }, 27 | }) 28 | console.log(channel) 29 | 30 | var sends: any[] = [] 31 | var recvs: any[] = [] 32 | function messageEntry(m) { 33 | return m.entry 34 | } 35 | 36 | let first_message = { 37 | last_seen: { First: null }, 38 | channel: channel.entry, 39 | entry: { 40 | uuid: uuidv4(), 41 | content: 'x'.repeat(1025), 42 | }, 43 | } 44 | 45 | //Send a messages that's too long 46 | console.log('>>>>>') 47 | 48 | try { 49 | console.log('>>>>>') 50 | let a = await alice_chat.call('chat', 'create_message', first_message) 51 | console.log('>>>>>', a) 52 | t.fail() 53 | } catch (e) { 54 | t.deepEqual(e, { 55 | type: 'error', 56 | data: { 57 | type: 'internal_error', 58 | data: 'Source chain error: InvalidCommit error: Message too long', 59 | }, 60 | }) 61 | } 62 | 63 | first_message.entry.content = 'Hello from alice :)' 64 | // Alice send a message 65 | sends.push(first_message) 66 | console.log(sends[0]) 67 | 68 | recvs.push(await alice_chat.call('chat', 'create_message', sends[0])) 69 | console.log(recvs[0]) 70 | t.deepEqual(sends[0].entry, recvs[0].entry) 71 | 72 | // Alice sends another message 73 | sends.push({ 74 | last_seen: { Message: recvs[0].entryHash }, 75 | channel: channel.entry, 76 | entry: { 77 | uuid: uuidv4(), 78 | content: 'Is anybody out there?', 79 | }, 80 | }) 81 | console.log(sends[1]) 82 | recvs.push(await alice_chat.call('chat', 'create_message', sends[1])) 83 | console.log(recvs[1]) 84 | t.deepEqual(sends[1].entry, recvs[1].entry) 85 | 86 | const channel_list = await alice_chat.call('chat', 'list_channels', { 87 | category: 'General', 88 | }) 89 | console.log(channel_list) 90 | 91 | // Alice lists the messages 92 | var msgs: any[] = [] 93 | let batch_payload = { 94 | channel: channel.entry, 95 | active_chatter: false, 96 | target_message_count: 2, 97 | } 98 | msgs.push(await alice_chat.call('chat', 'list_messages', batch_payload)) 99 | console.log(_.map(msgs[0].messages, messageEntry)) 100 | t.deepEqual( 101 | [sends[0].entry, sends[1].entry], 102 | _.map(msgs[0].messages, messageEntry) 103 | ) 104 | // Bobbo lists the messages 105 | 106 | await awaitIntegration(bobbo_chat) 107 | 108 | msgs.push(await bobbo_chat.call('chat', 'list_messages', batch_payload)) 109 | console.log('bobbo.list_messages: ' + _.map(msgs[1].messages, messageEntry)) 110 | t.deepEqual( 111 | [sends[0].entry, sends[1].entry], 112 | _.map(msgs[1].messages, messageEntry) 113 | ) 114 | 115 | // Bobbo and Alice both reply to the same message 116 | sends.push({ 117 | last_seen: { Message: recvs[1].entryHash }, 118 | channel: channel.entry, 119 | entry: { 120 | uuid: uuidv4(), 121 | content: "I'm here", 122 | }, 123 | }) 124 | sends.push({ 125 | last_seen: { Message: recvs[1].entryHash }, 126 | channel: channel.entry, 127 | entry: { 128 | uuid: uuidv4(), 129 | content: 'Anybody?', 130 | }, 131 | }) 132 | recvs.push(await bobbo_chat.call('chat', 'create_message', sends[2])) 133 | console.log(recvs[2]) 134 | t.deepEqual(sends[2].entry, recvs[2].entry) 135 | recvs.push(await alice_chat.call('chat', 'create_message', sends[3])) 136 | console.log(recvs[3]) 137 | t.deepEqual(sends[3].entry, recvs[3].entry) 138 | await awaitIntegration(bobbo_chat) 139 | await awaitIntegration(alice_chat) 140 | 141 | // Alice lists the messages 142 | msgs.push(await alice_chat.call('chat', 'list_messages', batch_payload)) 143 | console.log(_.map(msgs[2].messages, messageEntry)) 144 | t.deepEqual( 145 | [sends[0].entry, sends[1].entry, sends[2].entry, sends[3].entry], 146 | _.map(msgs[2].messages, messageEntry) 147 | ) 148 | // Bobbo lists the messages 149 | msgs.push(await bobbo_chat.call('chat', 'list_messages', batch_payload)) 150 | console.log(_.map(msgs[3].messages, messageEntry)) 151 | t.deepEqual( 152 | [sends[0].entry, sends[1].entry, sends[2].entry, sends[3].entry], 153 | _.map(msgs[3].messages, messageEntry) 154 | ) 155 | }) 156 | } 157 | -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | /* Basic Options */ 5 | // "incremental": true, /* Enable incremental compilation */ 6 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 7 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 8 | // "lib": [], /* Specify library files to be included in the compilation. */ 9 | // "allowJs": true, /* Allow javascript files to be compiled. */ 10 | // "checkJs": true, /* Report errors in .js files. */ 11 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 12 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 13 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 14 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 15 | // "outFile": "./", /* Concatenate and emit output to single file. */ 16 | // "outDir": "./", /* Redirect output structure to the directory. */ 17 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 18 | // "composite": true, /* Enable project compilation */ 19 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 20 | // "removeComments": true, /* Do not emit comments to output. */ 21 | // "noEmit": true, /* Do not emit outputs. */ 22 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 23 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 24 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 25 | /* Strict Type-Checking Options */ 26 | "strict": true, /* Enable all strict type-checking options. */ 27 | "noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */ 28 | // "strictNullChecks": true, /* Enable strict null checks. */ 29 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 30 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 31 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 32 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 33 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 34 | /* Additional Checks */ 35 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 36 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 37 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 38 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 39 | /* Module Resolution Options */ 40 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 41 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 42 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 43 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 44 | // "typeRoots": [], /* List of folders to include type definitions from. */ 45 | // "types": [], /* Type declaration files to be included in compilation. */ 46 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 47 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 48 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 49 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 50 | /* Source Map Options */ 51 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 52 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 53 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 54 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 55 | /* Experimental Options */ 56 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 57 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 58 | /* Advanced Options */ 59 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 60 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /zomes/chat/src/batching_helper.rs: -------------------------------------------------------------------------------- 1 | //! TODO: Document how to use this crate in general 2 | //! 3 | //! 4 | //! 5 | use crate::{error::ChatResult, ChatError}; 6 | use chrono::{DateTime, Datelike, NaiveDateTime, Timelike, Utc}; 7 | use hdk::{ 8 | hash_path::path::{Component, TypedPath}, 9 | prelude::*, 10 | }; 11 | use std::cmp; 12 | 13 | pub fn get_previous_hour(time: Timestamp) -> Result { 14 | time - std::time::Duration::from_secs(60 * 60) 15 | } 16 | 17 | /// Returns at least `target_count` messages that are all earlier than `earliest_seen`. 18 | /// 19 | /// Navigates a tree of timestamp-based links to find messages. 20 | /// We used to link all the messages for a channel in the same place, 21 | /// but it was too slow to load them, so we created this tree to reduce the work done per zome call. 22 | pub fn get_message_links( 23 | channel: Path, 24 | earliest_seen: Option, 25 | target_count: usize, 26 | ) -> Result, WasmError> { 27 | let newest_included_hour = if let Some(earliest_seen) = earliest_seen { 28 | if let Ok(hour) = earliest_seen - std::time::Duration::from_secs(60 * 60) { 29 | hour 30 | } else { 31 | return Ok(Vec::new()); 32 | } 33 | } else { 34 | sys_time()? 35 | }; 36 | 37 | let mut links = Vec::new(); 38 | 39 | let root_path_length = channel.as_ref().len(); 40 | let newest_included_hour_path = timestamp_into_path(channel, newest_included_hour)?; 41 | if newest_included_hour_path.exists()? { 42 | links.append(&mut get_links( 43 | newest_included_hour_path.path_entry_hash()?, 44 | LinkTypeRange::from(LinkType::try_from(chat_integrity::LinkTypes::Message)?), 45 | None, 46 | )?); 47 | } 48 | 49 | let mut earliest_seen_child_path = newest_included_hour_path; 50 | let mut current_search_path = earliest_seen_child_path.parent().unwrap(); 51 | let mut depth = 0; 52 | while links.len() < target_count && current_search_path.as_ref().len() >= root_path_length { 53 | if current_search_path.exists()? { 54 | let earliest_seen_child_segment = 55 | last_segment_from_path(&earliest_seen_child_path).unwrap(); 56 | let children = current_search_path.children()?; 57 | 58 | let raw_children = children 59 | .iter() 60 | .map(|l| format!("{{ tag: {:?} timestamp: {:?} }}, ", l.tag, l.timestamp)) 61 | .collect::(); 62 | let mut children = children 63 | .into_iter() 64 | .filter_map(|l| path_component_from_link(&l).ok().map(|c| (c, l))) // filter out non-path links 65 | .map(|(c, l)| Ok((segment_from_component(&c)?, l))) 66 | .collect::, ChatError>>()?; 67 | 68 | children.retain(|(segment, _)| *segment < earliest_seen_child_segment); 69 | 70 | let link_count_before = links.len(); 71 | append_message_links_recursive(children, &mut links, target_count, depth)?; 72 | 73 | let links_added = links.get(link_count_before..).unwrap_or(&[]); 74 | debug!("batching: Finished including all descendants of node in tree (depth {:?} current_search_path {:?}). 75 | Raw children {:?}. Messages added {:?}", depth, current_search_path, raw_children, links_added); 76 | } 77 | 78 | earliest_seen_child_path = current_search_path; 79 | current_search_path = earliest_seen_child_path.parent().unwrap(); 80 | depth += 1; 81 | } 82 | 83 | Ok(links) 84 | } 85 | 86 | fn append_message_links_recursive( 87 | mut children: Vec<(i32, Link)>, 88 | links: &mut Vec, 89 | target_count: usize, 90 | depth: u8, 91 | ) -> ChatResult<()> { 92 | // It's important to sort by segment instead of timestamp, 93 | // since in the proptest, fake messages are inserted with chosen time-path-segments, 94 | // but the timestamp is not fake and still represents the system time. 95 | children.sort_unstable_by_key(|(segment, _)| cmp::Reverse(*segment)); 96 | for (_, link) in children { 97 | if depth == 0 { 98 | let mut message_links = get_links( 99 | link.target, 100 | LinkTypeRange::from(LinkType::try_from(chat_integrity::LinkTypes::Message)?), 101 | None, 102 | )?; 103 | links.append(&mut message_links); 104 | } else { 105 | let grandchildren = get_links( 106 | link.target, 107 | LinkTypeRange::from(LinkType::try_from(chat_integrity::LinkTypes::Message)?), 108 | None, 109 | )?; 110 | let grandchildren = grandchildren 111 | .into_iter() 112 | .filter_map(|l| path_component_from_link(&l).ok().map(|c| (c, l))) // filter out non-path links 113 | .map(|(c, l)| Ok((segment_from_component(&c)?, l))) 114 | .collect::, ChatError>>()?; 115 | append_message_links_recursive(grandchildren, links, target_count, depth - 1)?; 116 | } 117 | if links.len() >= target_count { 118 | break; 119 | } 120 | } 121 | 122 | Ok(()) 123 | } 124 | 125 | fn path_component_from_link(link: &Link) -> Result { 126 | SerializedBytes::from(UnsafeBytes::from(link.tag.clone().into_inner())).try_into() 127 | } 128 | 129 | fn segment_from_component(component: &Component) -> ChatResult { 130 | let bytes: [u8; 4] = component 131 | .as_ref() 132 | .try_into() 133 | .map_err(|_| ChatError::InvalidBatchingPath)?; 134 | Ok(i32::from_be_bytes(bytes)) 135 | } 136 | 137 | pub fn last_segment_from_path(path: &Path) -> ChatResult { 138 | let component = path.leaf().ok_or(ChatError::InvalidBatchingPath)?; 139 | segment_from_component(component) 140 | } 141 | 142 | /// Add the message from the Date type to this path 143 | pub fn timestamp_into_path(path: Path, time: Timestamp) -> ChatResult { 144 | let (ms, ns) = time.as_seconds_and_nanos(); 145 | let time = DateTime::::from_utc(NaiveDateTime::from_timestamp(ms, ns), Utc); 146 | let mut components: Vec<_> = path.into(); 147 | 148 | components.push((time.year() as i32).to_be_bytes().to_vec().into()); 149 | components.push((time.month() as i32).to_be_bytes().to_vec().into()); 150 | components.push((time.day() as i32).to_be_bytes().to_vec().into()); 151 | // DEV_MODE: This can be updated to sec() for testing 152 | components.push((time.hour() as i32).to_be_bytes().to_vec().into()); 153 | 154 | Ok(TypedPath::new( 155 | LinkType::try_from(chat_integrity::LinkTypes::Message)?, 156 | components.into(), 157 | )) 158 | } 159 | -------------------------------------------------------------------------------- /nix/sources.nix: -------------------------------------------------------------------------------- 1 | # This file has been generated by Niv. 2 | 3 | let 4 | 5 | # 6 | # The fetchers. fetch_ fetches specs of type . 7 | # 8 | 9 | fetch_file = pkgs: name: spec: 10 | let 11 | name' = sanitizeName name + "-src"; 12 | in 13 | if spec.builtin or true then 14 | builtins_fetchurl { inherit (spec) url sha256; name = name'; } 15 | else 16 | pkgs.fetchurl { inherit (spec) url sha256; name = name'; }; 17 | 18 | fetch_tarball = pkgs: name: spec: 19 | let 20 | name' = sanitizeName name + "-src"; 21 | in 22 | if spec.builtin or true then 23 | builtins_fetchTarball { name = name'; inherit (spec) url sha256; } 24 | else 25 | pkgs.fetchzip { name = name'; inherit (spec) url sha256; }; 26 | 27 | fetch_git = name: spec: 28 | let 29 | ref = 30 | if spec ? ref then spec.ref else 31 | if spec ? branch then "refs/heads/${spec.branch}" else 32 | if spec ? tag then "refs/tags/${spec.tag}" else 33 | abort "In git source '${name}': Please specify `ref`, `tag` or `branch`!"; 34 | in 35 | builtins.fetchGit { url = spec.repo; inherit (spec) rev; inherit ref; }; 36 | 37 | fetch_local = spec: spec.path; 38 | 39 | fetch_builtin-tarball = name: throw 40 | ''[${name}] The niv type "builtin-tarball" is deprecated. You should instead use `builtin = true`. 41 | $ niv modify ${name} -a type=tarball -a builtin=true''; 42 | 43 | fetch_builtin-url = name: throw 44 | ''[${name}] The niv type "builtin-url" will soon be deprecated. You should instead use `builtin = true`. 45 | $ niv modify ${name} -a type=file -a builtin=true''; 46 | 47 | # 48 | # Various helpers 49 | # 50 | 51 | # https://github.com/NixOS/nixpkgs/pull/83241/files#diff-c6f540a4f3bfa4b0e8b6bafd4cd54e8bR695 52 | sanitizeName = name: 53 | ( 54 | concatMapStrings (s: if builtins.isList s then "-" else s) 55 | ( 56 | builtins.split "[^[:alnum:]+._?=-]+" 57 | ((x: builtins.elemAt (builtins.match "\\.*(.*)" x) 0) name) 58 | ) 59 | ); 60 | 61 | # The set of packages used when specs are fetched using non-builtins. 62 | mkPkgs = sources: system: 63 | let 64 | sourcesNixpkgs = 65 | import (builtins_fetchTarball { inherit (sources.nixpkgs) url sha256; }) { inherit system; }; 66 | hasNixpkgsPath = builtins.any (x: x.prefix == "nixpkgs") builtins.nixPath; 67 | hasThisAsNixpkgsPath = == ./.; 68 | in 69 | if builtins.hasAttr "nixpkgs" sources 70 | then sourcesNixpkgs 71 | else if hasNixpkgsPath && ! hasThisAsNixpkgsPath then 72 | import {} 73 | else 74 | abort 75 | '' 76 | Please specify either (through -I or NIX_PATH=nixpkgs=...) or 77 | add a package called "nixpkgs" to your sources.json. 78 | ''; 79 | 80 | # The actual fetching function. 81 | fetch = pkgs: name: spec: 82 | 83 | if ! builtins.hasAttr "type" spec then 84 | abort "ERROR: niv spec ${name} does not have a 'type' attribute" 85 | else if spec.type == "file" then fetch_file pkgs name spec 86 | else if spec.type == "tarball" then fetch_tarball pkgs name spec 87 | else if spec.type == "git" then fetch_git name spec 88 | else if spec.type == "local" then fetch_local spec 89 | else if spec.type == "builtin-tarball" then fetch_builtin-tarball name 90 | else if spec.type == "builtin-url" then fetch_builtin-url name 91 | else 92 | abort "ERROR: niv spec ${name} has unknown type ${builtins.toJSON spec.type}"; 93 | 94 | # If the environment variable NIV_OVERRIDE_${name} is set, then use 95 | # the path directly as opposed to the fetched source. 96 | replace = name: drv: 97 | let 98 | saneName = stringAsChars (c: if isNull (builtins.match "[a-zA-Z0-9]" c) then "_" else c) name; 99 | ersatz = builtins.getEnv "NIV_OVERRIDE_${saneName}"; 100 | in 101 | if ersatz == "" then drv else 102 | # this turns the string into an actual Nix path (for both absolute and 103 | # relative paths) 104 | if builtins.substring 0 1 ersatz == "/" then /. + ersatz else /. + builtins.getEnv "PWD" + "/${ersatz}"; 105 | 106 | # Ports of functions for older nix versions 107 | 108 | # a Nix version of mapAttrs if the built-in doesn't exist 109 | mapAttrs = builtins.mapAttrs or ( 110 | f: set: with builtins; 111 | listToAttrs (map (attr: { name = attr; value = f attr set.${attr}; }) (attrNames set)) 112 | ); 113 | 114 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/lists.nix#L295 115 | range = first: last: if first > last then [] else builtins.genList (n: first + n) (last - first + 1); 116 | 117 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L257 118 | stringToCharacters = s: map (p: builtins.substring p 1 s) (range 0 (builtins.stringLength s - 1)); 119 | 120 | # https://github.com/NixOS/nixpkgs/blob/0258808f5744ca980b9a1f24fe0b1e6f0fecee9c/lib/strings.nix#L269 121 | stringAsChars = f: s: concatStrings (map f (stringToCharacters s)); 122 | concatMapStrings = f: list: concatStrings (map f list); 123 | concatStrings = builtins.concatStringsSep ""; 124 | 125 | # https://github.com/NixOS/nixpkgs/blob/8a9f58a375c401b96da862d969f66429def1d118/lib/attrsets.nix#L331 126 | optionalAttrs = cond: as: if cond then as else {}; 127 | 128 | # fetchTarball version that is compatible between all the versions of Nix 129 | builtins_fetchTarball = { url, name ? null, sha256 }@attrs: 130 | let 131 | inherit (builtins) lessThan nixVersion fetchTarball; 132 | in 133 | if lessThan nixVersion "1.12" then 134 | fetchTarball ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) 135 | else 136 | fetchTarball attrs; 137 | 138 | # fetchurl version that is compatible between all the versions of Nix 139 | builtins_fetchurl = { url, name ? null, sha256 }@attrs: 140 | let 141 | inherit (builtins) lessThan nixVersion fetchurl; 142 | in 143 | if lessThan nixVersion "1.12" then 144 | fetchurl ({ inherit url; } // (optionalAttrs (!isNull name) { inherit name; })) 145 | else 146 | fetchurl attrs; 147 | 148 | # Create the final "sources" from the config 149 | mkSources = config: 150 | mapAttrs ( 151 | name: spec: 152 | if builtins.hasAttr "outPath" spec 153 | then abort 154 | "The values in sources.json should not have an 'outPath' attribute" 155 | else 156 | spec // { outPath = replace name (fetch config.pkgs name spec); } 157 | ) config.sources; 158 | 159 | # The "config" used by the fetchers 160 | mkConfig = 161 | { sourcesFile ? if builtins.pathExists ./sources.json then ./sources.json else null 162 | , sources ? if isNull sourcesFile then {} else builtins.fromJSON (builtins.readFile sourcesFile) 163 | , system ? builtins.currentSystem 164 | , pkgs ? mkPkgs sources system 165 | }: rec { 166 | # The sources, i.e. the attribute set of spec name to spec 167 | inherit sources; 168 | 169 | # The "pkgs" (evaluated nixpkgs) to use for e.g. non-builtin fetchers 170 | inherit pkgs; 171 | }; 172 | 173 | in 174 | mkSources (mkConfig {}) // { __functor = _: settings: mkSources (mkConfig settings); } 175 | -------------------------------------------------------------------------------- /zomes/chat/src/lib.rs: -------------------------------------------------------------------------------- 1 | pub use channel::{Channel, ChannelData, ChannelInput, ChannelList, ChannelListInput}; 2 | pub use entries::{channel, message}; 3 | pub use error::{ChatError, ChatResult}; 4 | pub use hdk::prelude::Path; 5 | pub use hdk::prelude::*; 6 | pub use message::{ 7 | ActiveChatters, ListMessages, ListMessagesInput, MessageData, MessageInput, SigResults, 8 | SignalMessageData, SignalSpecificInput, 9 | }; 10 | pub mod batching_helper; 11 | pub mod entries; 12 | pub mod error; 13 | pub mod utils; 14 | pub use chat_integrity::{ChannelInfo, Message}; 15 | 16 | // signals: 17 | pub const NEW_MESSAGE_SIGNAL_TYPE: &str = "new_message"; 18 | pub const NEW_CHANNEL_SIGNAL_TYPE: &str = "new_channel"; 19 | 20 | #[derive(Serialize, Deserialize, SerializedBytes, Debug)] 21 | #[serde(tag = "signal_name", content = "signal_payload")] 22 | pub enum SignalPayload { 23 | Message(SignalMessageData), 24 | Channel(ChannelData), 25 | } 26 | 27 | // pub(crate) fn _signal_ui(signal: SignalPayload) -> ChatResult<()> { 28 | /*let signal_payload = match signal { 29 | SignalPayload::SignalMessageData(_) => SignalDetails { 30 | signal_name: "message".to_string(), 31 | signal_payload: signal, 32 | }, 33 | SignalPayload::ChannelData(_) => SignalDetails { 34 | signal_name: "channel".to_string(), 35 | signal_payload: signal, 36 | }, 37 | };*/ 38 | // Ok(emit_signal(&signal)?) 39 | // } 40 | 41 | #[hdk_extern] 42 | fn recv_remote_signal(signal: ExternIO) -> ExternResult<()> { 43 | let sig: SignalPayload = signal.decode().unwrap(); 44 | trace!("Received remote signal {:?}", sig); 45 | Ok(emit_signal(&sig)?) 46 | } 47 | 48 | #[hdk_extern] 49 | fn init(_: ()) -> ExternResult { 50 | // grant unrestricted access to accept_cap_claim so other agents can send us claims 51 | let mut functions = BTreeSet::new(); 52 | functions.insert((zome_info()?.name, "recv_remote_signal".into())); 53 | functions.insert((zome_info()?.name, "list_messages".into())); 54 | functions.insert((zome_info()?.name, "get_messages".into())); 55 | functions.insert((zome_info()?.name, "active_chatters".into())); 56 | functions.insert((zome_info()?.name, "get_active_chatters".into())); 57 | functions.insert((zome_info()?.name, "signal_chatters".into())); 58 | functions.insert((zome_info()?.name, "is_active_chatter".into())); 59 | functions.insert((zome_info()?.name, "refresh_chatter".into())); 60 | functions.insert((zome_info()?.name, "agent_stats".into())); 61 | functions.insert((zome_info()?.name, "list_channels".into())); 62 | functions.insert((zome_info()?.name, "channel_stats".into())); 63 | 64 | create_cap_grant(CapGrantEntry { 65 | tag: "".into(), 66 | // empty access converts to unrestricted 67 | access: ().into(), 68 | functions, 69 | })?; 70 | Ok(InitCallbackResult::Pass) 71 | } 72 | 73 | #[hdk_extern] 74 | fn create_channel(channel_input: ChannelInput) -> ExternResult { 75 | if is_read_only_instance() { 76 | return Err(ChatError::ReadOnly.into()); 77 | } 78 | Ok(channel::handlers::create_channel(channel_input)?) 79 | } 80 | 81 | #[hdk_extern] 82 | fn insert_fake_messages(input: message::handlers::InsertFakeMessagesPayload) -> ExternResult<()> { 83 | message::handlers::insert_fake_messages(input)?; 84 | Ok(()) 85 | } 86 | 87 | #[hdk_extern] 88 | fn create_message(message_input: MessageInput) -> ExternResult { 89 | if is_read_only_instance() { 90 | return Err(ChatError::ReadOnly.into()); 91 | } 92 | Ok(message::handlers::create_message( 93 | message_input, 94 | sys_time()?, 95 | )?) 96 | } 97 | 98 | /*#[hdk_extern] 99 | fn signal_users_on_channel(message_data SignalMessageData) -> ChatResult<()> { 100 | message::handlers::signal_users_on_channel(message_data) 101 | }*/ 102 | #[hdk_extern] 103 | fn get_active_chatters(_: ()) -> ExternResult { 104 | Ok(message::handlers::get_active_chatters()?) 105 | } 106 | 107 | #[hdk_extern] 108 | fn signal_specific_chatters(input: SignalSpecificInput) -> ExternResult<()> { 109 | if is_read_only_instance() { 110 | return Err(ChatError::ReadOnly.into()); 111 | } 112 | Ok(message::handlers::signal_specific_chatters(input)?) 113 | } 114 | 115 | #[hdk_extern] 116 | fn signal_chatters(message_data: SignalMessageData) -> ExternResult { 117 | if is_read_only_instance() { 118 | return Err(ChatError::ReadOnly.into()); 119 | } 120 | Ok(message::handlers::signal_chatters(message_data)?) 121 | } 122 | 123 | #[hdk_extern] 124 | fn refresh_chatter(_: ()) -> ExternResult<()> { 125 | Ok(message::handlers::refresh_chatter()?) 126 | } 127 | 128 | // #[hdk_extern] 129 | // fn new_message_signal(message_input: SignalMessageData) -> ChatResult<()> { 130 | // message::handlers::new_message_signal(message_input) 131 | // } 132 | 133 | #[hdk_extern] 134 | fn list_channels(list_channels_input: ChannelListInput) -> ExternResult { 135 | Ok(channel::handlers::list_channels(list_channels_input)?) 136 | } 137 | 138 | #[hdk_extern] 139 | fn list_messages(list_messages_input: ListMessagesInput) -> ExternResult { 140 | Ok(message::handlers::list_messages(list_messages_input)?) 141 | } 142 | 143 | #[derive(Debug, Serialize, Deserialize, SerializedBytes, Clone)] 144 | #[serde(rename_all = "camelCase")] 145 | pub struct ChannelMessages { 146 | pub channel: ChannelData, 147 | pub messages: Vec, 148 | } 149 | 150 | #[derive(Debug, Serialize, Deserialize, SerializedBytes, Clone)] 151 | #[serde(rename_all = "camelCase")] 152 | pub struct AllMessagesList(pub Vec); 153 | 154 | #[derive(Debug, Serialize, Deserialize, SerializedBytes, Clone)] 155 | #[serde(rename_all = "camelCase")] 156 | pub struct ListAllMessagesInput { 157 | pub category: String, 158 | } 159 | 160 | #[derive(Serialize, Deserialize, SerializedBytes, Debug)] 161 | pub struct Stats { 162 | agents: usize, 163 | active: usize, 164 | channels: usize, 165 | messages: usize, 166 | } 167 | 168 | #[hdk_extern] 169 | fn stats(list_channels_input: ChannelListInput) -> ExternResult { 170 | let (agents, active) = message::handlers::agent_stats()?; 171 | let (channels, messages) = channel::handlers::channel_stats(list_channels_input)?; 172 | Ok(Stats { 173 | agents, 174 | active, 175 | channels, 176 | messages, 177 | }) 178 | } 179 | 180 | #[derive(Serialize, Deserialize, SerializedBytes, Debug)] 181 | pub struct AgentStats { 182 | agents: usize, 183 | active: usize, 184 | } 185 | #[hdk_extern] 186 | fn agent_stats(_: ()) -> ExternResult { 187 | let (agents, active) = message::handlers::agent_stats()?; 188 | Ok(AgentStats { agents, active }) 189 | } 190 | 191 | #[derive(Debug, Serialize, Deserialize, SerializedBytes, Clone)] 192 | pub struct Props { 193 | pub skip_proof: bool, 194 | } 195 | 196 | /// Checking properties for `not_editable_profile` flag 197 | pub fn is_skipped() -> bool { 198 | if let Ok(info) = dna_info() { 199 | return is_skipped_sb(&info.properties); 200 | } 201 | false 202 | } 203 | 204 | /// Deserialize properties into the Props expected by this zome 205 | pub fn is_skipped_sb(encoded_props: &SerializedBytes) -> bool { 206 | let maybe_props = Props::try_from(encoded_props.to_owned()); 207 | if let Ok(props) = maybe_props { 208 | return props.skip_proof; 209 | } 210 | false 211 | } 212 | 213 | pub fn is_read_only_instance() -> bool { 214 | if is_skipped() { 215 | return false; 216 | } 217 | if let Ok(entries) = &query(ChainQueryFilter::new().action_type(ActionType::AgentValidationPkg)) 218 | { 219 | if let Action::AgentValidationPkg(h) = entries[0].action() { 220 | if let Some(mem_proof) = &h.membrane_proof { 221 | if is_read_only_proof(&mem_proof) { 222 | return true; 223 | } 224 | } 225 | } 226 | }; 227 | false 228 | } 229 | 230 | /// check to see if this is the valid read_only membrane proof 231 | pub fn is_read_only_proof(mem_proof: &MembraneProof) -> bool { 232 | let b = mem_proof.bytes(); 233 | b == &[0] 234 | } 235 | -------------------------------------------------------------------------------- /zomes/chat/src/entries/message/handlers.rs: -------------------------------------------------------------------------------- 1 | use super::{ 2 | ActiveChatters, LastSeen, LastSeenKey, ListMessages, ListMessagesInput, MessageData, 3 | SigResults, SignalMessageData, SignalSpecificInput, 4 | }; 5 | use crate::{ 6 | channel::Channel, 7 | error::ChatError, 8 | error::ChatResult, 9 | message::{Message, MessageInput}, 10 | utils::{get_local_action, to_date}, 11 | SignalPayload, 12 | }; 13 | pub use chat_integrity::ChannelInfo; 14 | use hdk::{hash_path::path::TypedPath, prelude::*}; 15 | use link::Link; 16 | use metadata::EntryDetails; 17 | 18 | #[derive(Serialize, Deserialize, SerializedBytes, Debug)] 19 | pub struct InsertFakeMessagesPayload { 20 | pub messages: Vec, 21 | pub channel: Channel, 22 | } 23 | 24 | #[derive(Serialize, Deserialize, SerializedBytes, Debug, Clone, PartialEq, Eq)] 25 | pub struct FakeMessage { 26 | pub content: String, 27 | pub timestamp: Timestamp, 28 | } 29 | 30 | /// Create a new message 31 | pub(crate) fn insert_fake_messages(input: InsertFakeMessagesPayload) -> ChatResult<()> { 32 | for FakeMessage { content, timestamp } in input.messages { 33 | create_message( 34 | MessageInput { 35 | last_seen: LastSeen::First, 36 | channel: input.channel.clone(), 37 | entry: Message { 38 | uuid: "".into(), 39 | content, 40 | }, 41 | }, 42 | timestamp, 43 | )?; 44 | } 45 | 46 | Ok(()) 47 | } 48 | 49 | /// Create a new message 50 | pub(crate) fn create_message( 51 | message_input: MessageInput, 52 | time: Timestamp, 53 | ) -> ChatResult { 54 | let MessageInput { 55 | last_seen, 56 | channel, 57 | entry, 58 | .. 59 | } = message_input; 60 | 61 | // Commit the message 62 | let action_hash = create_entry(chat_integrity::EntryTypes::Message(entry.clone()))?; 63 | 64 | // Get the local action and create the message type for the UI 65 | let action = get_local_action(&action_hash)?.ok_or(ChatError::MissingLocalAction)?; 66 | let message = MessageData::new(action, entry)?; 67 | 68 | // Get the channel hash 69 | let path: Path = channel.clone().into(); 70 | 71 | // Add the current time components 72 | let path = crate::batching_helper::timestamp_into_path(path, time)?; 73 | // Ensure the path exists 74 | path.ensure()?; 75 | 76 | // The actual hash we are going to hang this message on 77 | let path_hash = path.path_entry_hash()?; 78 | // debug!( 79 | // "committing message to hour {:?}", 80 | // crate::batching_helper::last_segment_from_path(&path)? 81 | // ); 82 | // Get the hash of the last_seen of this message 83 | let parent_hash_entry = match last_seen { 84 | LastSeen::Message(hash_entry) => hash_entry, 85 | LastSeen::First => path_hash.clone(), 86 | }; 87 | // Turn the reply to and timestamp into a link tag 88 | let tag = LastSeenKey::new(parent_hash_entry, message.created_at); 89 | create_link( 90 | path_hash, 91 | message.entry_hash.clone(), 92 | chat_integrity::LinkTypes::Message, 93 | LinkTag::from(tag), 94 | )?; 95 | 96 | // Return the message for the UI 97 | Ok(message) 98 | } 99 | 100 | /// Using batching to List all the messages on this channel 101 | pub(crate) fn list_messages(list_message_input: ListMessagesInput) -> ChatResult { 102 | let ListMessagesInput { 103 | channel, 104 | earliest_seen, 105 | target_message_count, 106 | } = list_message_input; 107 | 108 | let path: Path = channel.into(); 109 | let links = 110 | crate::batching_helper::get_message_links(path, earliest_seen, target_message_count)?; 111 | let mut messages = get_messages(links)?; 112 | debug!("Total length of messages {:?}", messages.len()); 113 | 114 | // Return messages in timestamp-ascending order. 115 | // This is not strictly necessary because the UI does not care about order. 116 | // Without this, some of our tests may fail spuriously when called on an hour boundary. 117 | messages.sort_unstable_by_key(|m| m.created_at); 118 | Ok(messages.into()) 119 | } 120 | 121 | // pub(crate) fn _new_message_signal(message: SignalMessageData) -> ChatResult<()> { 122 | // debug!( 123 | // "Received message: {:?}", 124 | // message.message_data.message.content 125 | // ); 126 | // emit signal alerting all connected uis about new message 127 | // signal_ui(SignalPayload::Message(message)) 128 | // } 129 | 130 | // Turn all the link targets into the actual message 131 | fn get_messages(links: Vec) -> ChatResult> { 132 | // Optimizing by calling parallel gets 133 | let mut messages = Vec::with_capacity(links.len()); 134 | // for every link get details on the target and create the message 135 | let msg_results_input: Vec = links 136 | .into_iter() 137 | .map(|link| GetInput::new(link.target.into(), GetOptions::default())) 138 | .collect(); 139 | let all_msg_results_elements = HDK.with(|hdk| hdk.borrow().get_details(msg_results_input))?; 140 | 141 | for ele in all_msg_results_elements.into_iter() { 142 | match ele { 143 | Some(Details::Entry(EntryDetails { 144 | entry, mut actions, .. 145 | })) => { 146 | // Turn the entry into a MessageEntry 147 | let message: Message = entry.try_into()?; 148 | let signed_action = match actions.pop() { 149 | Some(h) => h, 150 | // Ignoring missing messages 151 | None => { 152 | debug!("Ignoring missing messages"); 153 | continue; 154 | } 155 | }; 156 | 157 | // Create the message type for the UI 158 | messages.push(MessageData::new(signed_action.action().clone(), message)?) 159 | } 160 | // Message is missing. This could be an error but we are 161 | // going to ignore it. 162 | _ => continue, // Create the message type for the UI 163 | } 164 | } 165 | Ok(messages) 166 | } 167 | 168 | pub fn chatters_path() -> ChatResult { 169 | Ok(TypedPath::new( 170 | LinkType::try_from(chat_integrity::LinkTypes::Channel)?, 171 | Path::from("chatters"), 172 | )) 173 | } 174 | 175 | /* At some point maybe add back in chatters on a channel, but for now 176 | simple list of global chatters. 177 | pub(crate) fn signal_users_on_channel(signal_message_data: SignalMessageData) -> ChatResult<()> { 178 | let me = agent_info()?.agent_latest_pubkey; 179 | 180 | let path: Path = signal_message_data.channel_data.channel.chatters_path(); 181 | let hour_path = add_current_hour_path(path.clone())?; 182 | hour_path.ensure()?; 183 | signal_hour(hour_path, signal_message_data.clone(), me.clone())?; 184 | let hour_path = add_current_hour_minus_n_path(path, 1)?; 185 | hour_path.ensure()?; 186 | signal_hour(hour_path, signal_message_data, me)?; 187 | 188 | let path: Path = chatters_path(); 189 | signal_chatters(path, signal_message_data, me)?; 190 | 191 | Ok(()) 192 | } */ 193 | 194 | const CHATTER_REFRESH_HOURS: i64 = 2; 195 | 196 | use std::collections::HashSet; 197 | 198 | /// return the list of active chatters on a path. 199 | /// N.B.: assumes that the path has been ensured elsewhere. 200 | fn active_chatters(chatters_path: TypedPath) -> ChatResult<(usize, Vec)> { 201 | let chatters = get_links( 202 | chatters_path.path_entry_hash()?, 203 | LinkTypeRange::from(LinkType::try_from(chat_integrity::LinkTypes::Chatter)?), 204 | None, 205 | )?; 206 | debug!("num online chatters {}", chatters.len()); 207 | let now = to_date(sys_time()?); 208 | let total = chatters.len(); 209 | let mut agents = HashSet::new(); 210 | let active: Vec = chatters 211 | .into_iter() 212 | .map(|l| { 213 | let link_time: chrono::DateTime = l.timestamp.try_into()?; 214 | let maybe_agent = 215 | if now.signed_duration_since(link_time).num_hours() < CHATTER_REFRESH_HOURS { 216 | let tag = l.tag; 217 | if let Ok(agent) = tag_to_agent(tag) { 218 | if agents.contains(&agent) { 219 | None 220 | } else { 221 | agents.insert(agent.clone()); 222 | Some(agent) 223 | } 224 | } else { 225 | None 226 | } 227 | } else { 228 | None 229 | }; 230 | ChatResult::Ok(maybe_agent) 231 | }) 232 | .collect::>, _>>()? 233 | .into_iter() 234 | .flatten() 235 | .collect(); 236 | Ok((total, active)) 237 | } 238 | 239 | pub(crate) fn get_active_chatters() -> ChatResult { 240 | let me = agent_info()?.agent_latest_pubkey; 241 | let chatters_path = chatters_path()?; 242 | let (_total, mut chatters) = active_chatters(chatters_path)?; 243 | chatters.retain(|a| *a != me); 244 | Ok(ActiveChatters { chatters }) 245 | } 246 | 247 | pub(crate) fn signal_specific_chatters(input: SignalSpecificInput) -> ChatResult<()> { 248 | let mut chatters = input.chatters; 249 | 250 | if let Some(include_active_chatters) = input.include_active_chatters { 251 | if include_active_chatters { 252 | let active_chatters_result = get_active_chatters(); 253 | if let Ok(mut active_chatters) = active_chatters_result { 254 | chatters.append(&mut active_chatters.chatters); 255 | } 256 | } 257 | } 258 | let input = SignalPayload::Message(input.signal_message_data); 259 | let payload = ExternIO::encode(input)?; 260 | remote_signal(payload, chatters)?; 261 | Ok(()) 262 | } 263 | 264 | pub(crate) fn signal_chatters(signal_message_data: SignalMessageData) -> ChatResult { 265 | let me = agent_info()?.agent_latest_pubkey; 266 | let chatters_path = chatters_path()?; 267 | let (total, mut active_chatters) = active_chatters(chatters_path)?; 268 | active_chatters.retain(|a| *a != me); 269 | debug!("sending to {:?}", active_chatters); 270 | 271 | let mut sent: Vec = Vec::new(); 272 | for a in active_chatters.clone() { 273 | sent.push(format!("{}", a.to_string())); 274 | } 275 | let input = SignalPayload::Message(signal_message_data); 276 | let payload = ExternIO::encode(input)?; 277 | remote_signal(payload, active_chatters)?; 278 | Ok(SigResults { total, sent }) 279 | } 280 | 281 | pub(crate) fn is_active_chatter(chatters_path: TypedPath) -> ChatResult { 282 | let base = chatters_path.path_entry_hash()?; 283 | let filter = QueryFilter::new(); 284 | let action_filter = filter.action_type(ActionType::CreateLink); 285 | let query_result: Vec = query(action_filter)?; 286 | let now = to_date(sys_time()?); 287 | let mut pass = false; 288 | for x in query_result.into_iter() { 289 | match x.into_inner().0.into_inner().0.into_content() { 290 | Action::CreateLink(c) => { 291 | if c.base_address.retype(holo_hash::hash_type::Entry) == base { 292 | let link_time = to_date(c.timestamp); 293 | if now.signed_duration_since(link_time).num_hours() < CHATTER_REFRESH_HOURS { 294 | pass = true; 295 | break; 296 | } else { 297 | pass = false; 298 | break; 299 | } 300 | } else { 301 | continue; 302 | } 303 | } 304 | _ => unreachable!(), 305 | } 306 | } 307 | Ok(pass) 308 | } 309 | 310 | // TODO: re add chatter/channel instead of global 311 | // simplified and expected as a zome call 312 | pub(crate) fn refresh_chatter() -> ChatResult<()> { 313 | let path = chatters_path()?; 314 | path.ensure()?; 315 | let agent = agent_info()?.agent_latest_pubkey; 316 | let agent_tag = agent_to_tag(&agent); 317 | if !is_active_chatter(path.clone())? { 318 | create_link( 319 | path.path_entry_hash()?, 320 | agent, 321 | chat_integrity::LinkTypes::Chatter, 322 | agent_tag.clone(), 323 | )?; 324 | } 325 | Ok(()) 326 | } 327 | 328 | // this is a relatively expensive call and really only for testing purposes 329 | pub(crate) fn agent_stats() -> ChatResult<(usize, usize)> { 330 | let chatters_path = chatters_path()?; 331 | let chatters = get_links( 332 | chatters_path.path_entry_hash()?, 333 | LinkTypeRange::from(LinkType::try_from(chat_integrity::LinkTypes::Chatter)?), 334 | None, 335 | )?; 336 | 337 | let agents = chatters 338 | .into_iter() 339 | .map(|l| l.tag) 340 | .collect::<::std::collections::HashSet<_>>(); 341 | 342 | let (_, active_chatters) = active_chatters(chatters_path)?; 343 | Ok((agents.len(), active_chatters.len())) 344 | } 345 | 346 | /* old way using hours 347 | fn add_chatter(path: Path) -> ChatResult<()> { 348 | let agent = agent_info()?.agent_latest_pubkey; 349 | let agent_tag = agent_to_tag(&agent); 350 | 351 | let hour_path = add_current_hour_path(path.clone())?; 352 | hour_path.ensure()?; 353 | let my_chatter = get_links(hour_path.hash()?, Some(agent_tag.clone()))?.into_inner(); 354 | debug!("checking chatters"); 355 | if my_chatter.is_empty() { 356 | debug!("adding chatters"); 357 | create_link(hour_path.hash()?, agent.into(), agent_tag.clone())?; 358 | let hour_path = add_current_hour_minus_n_path(path, 1)?; 359 | hour_path.ensure()?; 360 | for link in get_links(hour_path.hash()?, Some(agent_tag.clone()))?.into_inner() { 361 | delete_link(link.create_link_hash)?; 362 | } 363 | } 364 | 365 | Ok(()) 366 | } 367 | */ 368 | 369 | fn agent_to_tag(agent: &AgentPubKey) -> LinkTag { 370 | let agent_tag: &[u8] = agent.as_ref(); 371 | LinkTag::new(agent_tag) 372 | } 373 | 374 | fn tag_to_agent(tag: LinkTag) -> ChatResult { 375 | Ok(AgentPubKey::from_raw_39(tag.0).map_err(|_| ChatError::AgentTag)?) 376 | } 377 | 378 | /* 379 | fn add_current_hour_path(path: Path) -> ChatResult { 380 | add_current_hour_path_inner(path, None) 381 | } 382 | 383 | fn add_current_hour_minus_n_path(path: Path, sub: u64) -> ChatResult { 384 | add_current_hour_path_inner(path, Some(sub)) 385 | } 386 | 387 | fn add_current_hour_path_inner(path: Path, sub: Option) -> ChatResult { 388 | use chrono::{Datelike, Timelike}; 389 | let mut components: Vec<_> = path.into(); 390 | 391 | // Get the current times and turn them to dates; 392 | let mut now = to_date(sys_time()?); 393 | if let Some(sub) = sub { 394 | now = date_minus_hours(now, sub); 395 | } 396 | let year = now.year().to_string(); 397 | let month = now.month().to_string(); 398 | let day = now.day().to_string(); 399 | let hour = now.hour().to_string(); 400 | 401 | // Add the date parts as components to the path 402 | components.push(year.into()); 403 | components.push(month.into()); 404 | components.push(day.into()); 405 | components.push(hour.into()); 406 | Ok(components.into()) 407 | } 408 | 409 | fn date_minus_hours( 410 | date: chrono::DateTime, 411 | hours: u64, 412 | ) -> chrono::DateTime { 413 | let hours = chrono::Duration::hours(hours as i64); 414 | date - hours 415 | } 416 | */ 417 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | # Cryptographic Autonomy License version 1.0 2 | 3 | *This Cryptographic Autonomy License (the “License”) applies to any Work whose owner has marked it with any of the following notices:* 4 | 5 | *“Licensed under the Cryptographic Autonomy License version 1.0,” or* 6 | 7 | *“SPDX-License-Identifier: CAL-1.0,” or* 8 | 9 | *“Licensed under the Cryptographic Autonomy License version 1.0, with Combined Work Exception,” or* 10 | 11 | *“SPDX-License-Identifier: CAL-1.0 with Combined-Work-Exception.”* 12 | 13 | ------ 14 | 15 | ## 1. Purpose 16 | 17 | This License gives You unlimited permission to use and modify the software to which it applies (the “Work”), either as-is or in modified form, for Your private purposes, while protecting the owners and contributors to the software from liability. 18 | 19 | This License also strives to protect the freedom and autonomy of third parties who receive the Work from you. If any non-affiliated third party receives any part, aspect, or element of the Work from You, this License requires that You provide that third party all the permissions and materials needed to independently use and modify the Work without that third party having a loss of data or capability due to your actions. 20 | 21 | The full permissions, conditions, and other terms are laid out below. 22 | 23 | ## 2. Receiving a License 24 | 25 | In order to receive this License, You must agree to its rules. The rules of this License are both obligations of Your agreement with the Licensor and conditions to your License. You must not do anything with the Work that triggers a rule You cannot or will not follow. 26 | 27 | ### 2.1. Application 28 | 29 | The terms of this License apply to the Work as you receive it from Licensor, as well as to any modifications, elaborations, or implementations created by You that contain any licenseable portion of the Work (a “Modified Work”). Unless specified, any reference to the Work also applies to a Modified Work. 30 | 31 | ### 2.2. Offer and Acceptance 32 | 33 | This License is automatically offered to every person and organization. You show that you accept this License and agree to its conditions by taking any action with the Work that, absent this License, would infringe any intellectual property right held by Licensor. 34 | 35 | ### 2.3. Compliance and Remedies 36 | 37 | Any failure to act according to the terms and conditions of this License places Your use of the Work outside the scope of the License and infringes the intellectual property rights of the Licensor. In the event of infringement, the terms and conditions of this License may be enforced by Licensor under the intellectual property laws of any jurisdiction to which You are subject. You also agree that either the Licensor or a Recipient (as an intended third-party beneficiary) may enforce the terms and conditions of this License against You via specific performance. 38 | 39 | ## 3. Permissions and Conditions 40 | 41 | ### 3.1. Permissions Granted 42 | 43 | Conditioned on compliance with section 4, and subject to the limitations of section 3.2, Licensor grants You the world-wide, royalty-free, non-exclusive permission to: 44 | 45 | > a) Take any action with the Work that would infringe the non-patent intellectual property laws of any jurisdiction to which You are subject; and 46 | > 47 | > b) Take any action with the Work that would infringe any patent claims that Licensor can license or becomes able to license, to the extent that those claims are embodied in the Work as distributed by Licensor. 48 | 49 | ### 3.2. Limitations on Permissions Granted 50 | 51 | The following limitations apply to the permissions granted in section 3.1: 52 | 53 | > a) Licensor does not grant any patent license for claims that are only infringed due to modification of the Work as provided by Licensor, or the combination of the Work as provided by Licensor, directly or indirectly, with any other component, including other software or hardware. 54 | > 55 | > b) Licensor does not grant any license to the trademarks, service marks, or logos of Licensor, except to the extent necessary to comply with the attribution conditions in section 4.1 of this License. 56 | 57 | ## 4. Conditions 58 | 59 | If You exercise any permission granted by this License, such that the Work, or any part, aspect, or element of the Work, is distributed, communicated, made available, or made perceptible to a non-Affiliate third party (a “Recipient”), either via physical delivery or via a network connection to the Recipient, You must comply with the following conditions: 60 | 61 | ### 4.1. Provide Access to Source Code 62 | 63 | Subject to the exception in section 4.4, You must provide to each Recipient a copy of, or no-charge unrestricted network access to, the Source Code corresponding to the Work. 64 | 65 | The “Source Code” of the Work means the form of the Work preferred for making modifications, including any comments, configuration information, documentation, help materials, installation instructions, cryptographic seeds or keys, and any information reasonably necessary for the Recipient to independently compile and use the Source Code and to have full access to the functionality contained in the Work. 66 | 67 | #### 4.1.1. Providing Network Access to the Source Code 68 | 69 | Network access to the Notices and Source Code may be provided by You or by a third party, such as a public software repository, and must persist during the same period in which You exercise any of the permissions granted to You under this License and for at least one year thereafter. 70 | 71 | #### 4.1.2. Source Code for a Modified Work 72 | 73 | Subject to the exception in section 4.5, You must provide to each Recipient of a Modified Work Access to Source Code corresponding to those portions of the Work remaining in the Modified Work as well as the modifications used by You to create the Modified Work. The Source Code corresponding to the modifications in the Modified Work must be provided to the Recipient either a) under this License, or b) under a Compatible Open Source License. 74 | 75 | A “Compatible Open Source License” means a license accepted by the Open Source Initiative that allows object code created using both Source Code provided under this License and Source Code provided under the other open source license to be distributed together as a single work. 76 | 77 | #### 4.1.3. Coordinated Disclosure of Security Vulnerabilities 78 | 79 | You may delay providing the Source Code corresponding to a particular modification of the Work for up to ninety (90) days (the “Embargo Period”) if: a) the modification is intended to address a newly-identified vulnerability or a security flaw in the Work, b) disclosure of the vulnerability or security flaw before the end of the Embargo Period would put the data, identity, or autonomy of one or more Recipients of the Work at significant risk, c) You are participating in a coordinated disclosure of the vulnerability or security flaw with one or more additional Licensees, and d) Access to the Source Code pertaining to the modification is provided to all Recipients at the end of the Embargo Period. 80 | 81 | ### 4.2. Maintain User Autonomy 82 | 83 | In addition to providing each Recipient the opportunity to have Access to the Source Code, You cannot use the permissions given under this License to interfere with a Recipient’s ability to fully use an independent copy of the Work generated from the Source Code You provide with the Recipient’s own User Data. 84 | 85 | “User Data” means any data that is an input to or an output from the Work, where the presence of the data is necessary for substantially identical use of the Work in an equivalent context chosen by the Recipient, and where the Recipient has an existing ownership interest, an existing right to possess, or where the data has been generated by, for, or has been assigned to the Recipient. 86 | 87 | #### 4.2.1. No Withholding User Data 88 | 89 | Throughout any period in which You exercise any of the permissions granted to You under this License, You must also provide to any Recipient to whom you provide services via the Work, a no-charge copy, provided in a commonly used electronic form, of the Recipient’s User Data in your possession, to the extent that such User Data is available to You for use in conjunction with the Work. 90 | 91 | #### 4.2.2. No Technical Measures that Limit Access 92 | 93 | You may not, by the use of cryptographic methods applied to anything provided to the Recipient, by possession or control of cryptographic keys, seeds, or hashes, by other technological protection measures, or by any other method, limit a Recipient's ability to access any functionality present in the Recipient's independent copy of the Work, or deny a Recipient full control of the Recipient's User Data. 94 | 95 | #### 4.2.3. No Legal or Contractual Measures that Limit Access 96 | 97 | You may not contractually restrict a Recipient's ability to independently exercise the permissions granted under this License. You waive any legal power to forbid circumvention of technical protection measures that include use of the Work, and You waive any claim that the capabilities of the Work were limited or modified as a means of enforcing the legal rights of third parties against Recipients. 98 | 99 | ### 4.3. Provide Notices and Attribution 100 | 101 | You must retain all licensing, authorship, or attribution notices contained in the Source Code (the “Notices”), and provide all such Notices to each Recipient, together with a statement acknowledging the use of the Work. Notices may be provided directly to a Recipient or via an easy-to-find hyperlink to an Internet location also providing Access to Source Code. 102 | 103 | ### 4.4. Scope of Conditions in this License 104 | 105 | You are required to uphold the conditions of this License only relative to those who are Recipients of the Work from You. Other than providing Recipients with the applicable Notices, Access to Source Code, and a copy of and full control of their User Data, nothing in this License requires You to provide processing services to or engage in network interactions with anyone. 106 | 107 | ### 4.5. Combined Work Exception 108 | 109 | As an exception to condition that You provide Recipients Access to Source Code, any Source Code files marked by the Licensor as having the “Combined Work Exception,” or any object code exclusively resulting from Source Code files so marked, may be combined with other Software into a “Larger Work.” So long as you comply with the requirements to provide Recipients the applicable Notices and Access to the Source Code provided to You by Licensor, and you provide Recipients access to their User Data and do not limit Recipient’s ability to independently work with their User Data, any other Software in the Larger Work as well as the Larger Work as a whole may be licensed under the terms of your choice. 110 | 111 | ## 5. Term and Termination 112 | 113 | The term of this License begins when You receive the Work, and continues until terminated for any of the reasons described herein, or until all Licensor’s intellectual property rights in the Software expire, whichever comes first (“Term”). This License cannot be revoked, only terminated for the reasons listed below. 114 | 115 | ### 5.1. Effect of Termination 116 | 117 | If this License is terminated for any reason, all permissions granted to You under Section 3 by any Licensor automatically terminate. You will immediately cease exercising any permissions granted in this License relative to the Work, including as part of any Modified Work. 118 | 119 | ### 5.2. Termination for Non-Compliance; Reinstatement 120 | 121 | This License terminates automatically if You fail to comply with any of the conditions in section 4. As a special exception to termination for non-compliance, Your permissions for the Work under this License will automatically be reinstated if You come into compliance with all the conditions in section 2 within sixty (60) days of being notified by Licensor or an intended third party beneficiary of Your noncompliance. You are eligible for reinstatement of permissions for the Work one time only, and only for the sixty days immediately after becoming aware of noncompliance. Loss of permissions granted for the Work under this License due to either a) sustained noncompliance lasting more than sixty days or b) subsequent termination for noncompliance after reinstatement, is permanent, unless rights are specifically restored by Licensor in writing. 122 | 123 | ### 5.3 Termination Due to Litigation 124 | 125 | If You initiate litigation against Licensor, or any Recipient of the Work, either direct or indirect, asserting that the Work directly or indirectly infringes any patent, then all permissions granted to You by this License shall terminate. In the event of termination due to litigation, all permissions validly granted by You under this License, directly or indirectly, shall survive termination. Administrative review procedures, declaratory judgment actions, counterclaims in response to patent litigation, and enforcement actions against former Licensees terminated under this section do not cause termination due to litigation. 126 | 127 | ## 6. Disclaimer of Warranty and Limit on Liability 128 | 129 | As far as the law allows, the Work comes AS-IS, without any warranty of any kind, and no Licensor or contributor will be liable to anyone for any damages related to this software or this license, under any kind of legal claim, or for any type of damages, including indirect, special, incidental, or consequential damages of any type arising as a result of this License or the use of the Work including, without limitation, damages for loss of goodwill, work stoppage, computer failure or malfunction, loss of profits, revenue, or any and all other commercial damages or losses. 130 | 131 | ## 7. Other Provisions 132 | 133 | ### 7.1. Affiliates 134 | 135 | An “Affiliate” means any other entity that, directly or indirectly through one or more intermediaries, controls, is controlled by, or is under common control with, the Licensee. Employees of a Licensee and natural persons acting as contractors exclusively providing services to Licensee are also Affiliates. 136 | 137 | ### 7.2. Choice of Jurisdiction and Governing Law 138 | 139 | A Licensor may require that any action or suit by a Licensee relating to a Work provided by Licensor under this License may be brought only in the courts of a particular jurisdiction and under the laws of a particular jurisdiction (excluding its conflict-of-law provisions), if Licensor provides conspicuous notice of the particular jurisdiction to all Licensees. 140 | 141 | ### 7.3. No Sublicensing 142 | 143 | This License is not sublicensable. Each time You provide the Work or a Modified Work to a Recipient, the Recipient automatically receives a license under the terms described in this License. You may not impose any further reservations, conditions, or other provisions on any Recipients’ exercise of the permissions granted herein. 144 | 145 | ### 7.4. Attorneys' Fees 146 | 147 | In any action to enforce the terms of this License, or seeking damages relating thereto, including by an intended third party beneficiary, the prevailing party shall be entitled to recover its costs and expenses, including, without limitation, reasonable attorneys' fees and costs incurred in connection with such action, including any appeal of such action. A “prevailing party” is the party that achieves, or avoids, compliance with this License, including through settlement. This section shall survive the termination of this License. 148 | 149 | ### 7.5. No Waiver 150 | 151 | Any failure by Licensor to enforce any provision of this License will not constitute a present or future waiver of such provision nor limit Licensor’s ability to enforce such provision at a later time. 152 | 153 | ### 7.6. Severability 154 | 155 | If any provision of this License is held to be unenforceable, such provision shall be reformed only to the extent necessary to make it enforceable. Any invalid or unenforceable portion will be interpreted to the effect and intent of the original portion. If such a construction is not possible, the invalid or unenforceable portion will be severed from this License but the rest of this License will remain in full force and effect. 156 | 157 | ### 7.7. License for the Text of this License 158 | 159 | The text of this license is released under the Creative Commons Attribution-ShareAlike 4.0 International License, with the caveat that any modifications of this license may not use the name “Cryptographic Autonomy License” or any name confusingly similar thereto to describe any derived work of this License. 160 | 161 | -------------------------------------------------------------------------------- /tests/src/behaviors/tx-per-second.ts: -------------------------------------------------------------------------------- 1 | import { Player, DnaPath, PlayerConfig, Config, InstallAgentsHapps, InstalledAgentHapps, TransportConfigType, ProxyAcceptConfig, ProxyConfigType, Cell } from '@holochain/tryorama' 2 | import { ScenarioApi } from '@holochain/tryorama/lib/api'; 3 | import * as _ from 'lodash' 4 | import { v4 as uuidv4 } from "uuid"; 5 | import { NETWORK as defaultNetworkConfig } from '../common' 6 | import { installAgents } from '../installAgents' 7 | const path = require('path') 8 | 9 | const delay = ms => new Promise(r => setTimeout(r, ms)) 10 | 11 | export const defaultConfig = { 12 | trycpAddresses: [ 13 | // "localhost:9000", 14 | "172.26.147.205:9000", // zippy1 (58f9o0jx7l73xu7vi13oi0yju06644xm5we2a7i8oqbt918o48 15 | ], 16 | proxys: [ 17 | "kitsune-proxy://f3gH2VMkJ4qvZJOXx0ccL_Zo5n-s_CnBjSzAsEHHDCA/kitsune-quic/h/45.55.107.33/p/5788/--", 18 | ], 19 | proxyCount: 1, 20 | nodes: 1, // Number of machines 21 | conductors: 1, // Conductors per machine 22 | instances: 10, // Instances per conductor 23 | activeAgents: 5, // Number of agents to consider "active" for chatting 24 | dnaSource: path.join(__dirname, '../../../elemental-chat.dna'), 25 | // dnaSource: { url: "https://github.com/holochain/elemental-chat/releases/download/v0.0.1-alpha15/elemental-chat.dna" }, 26 | } 27 | 28 | type Agents = Array<{ hAppId: string, agent: Buffer, cell: Cell, playerIdx: number }> 29 | type PlayerAgents = Array 30 | 31 | const selectActiveAgents = (count: number, playerAgents: any): Agents => { 32 | if (count > playerAgents.length * playerAgents[0].length) { 33 | throw new Error(`not enough agents to make ${count} active`) 34 | } 35 | let res = new Array(count) 36 | let i = 0 37 | let playerIdx = 0 38 | let agentIdx = 0 39 | while (i < count) { 40 | res[i] = playerAgents[playerIdx][agentIdx] 41 | i += 1 42 | playerIdx += 1 43 | if (playerIdx === playerAgents.length) { 44 | playerIdx = 0 45 | agentIdx += 1 46 | } 47 | } 48 | return res 49 | } 50 | 51 | type StateDump = [ 52 | any, // unused 53 | string // summary data which we parse 54 | ] 55 | 56 | type StateDumpRelevant = { 57 | numPeers: number, 58 | elementsAuthored: number, 59 | opsPublished: number, 60 | } 61 | 62 | 63 | // Example state dump 64 | // [ 65 | // /*irrelevant json object omitted*/, 66 | // "--- Cell State Dump Summary ---\nNumber of other peers in p2p store: 0,\nOps: Limbo (validation: 0 integration: 0) Integrated: 7\nElements authored: 3, Ops published: 7" 67 | // ] 68 | 69 | 70 | const parseStateDump = ([unused, stateDumpRelevant]: StateDump): StateDumpRelevant => { 71 | 72 | const regex = /^--- Cell State Dump Summary ---\nNumber of other peers in p2p store: (\d+),\nElements authored: (\d+), Ops published: (\d+)/ 73 | 74 | const groups = regex.exec(stateDumpRelevant) 75 | 76 | if (groups === null) { 77 | throw new Error("failed to parse state dump") 78 | } 79 | 80 | return { 81 | numPeers: Number.parseInt(groups[1], 10), 82 | elementsAuthored: Number.parseInt(groups[2], 10), 83 | opsPublished: Number.parseInt(groups[3], 10), 84 | } 85 | } 86 | 87 | const waitAllPeers = async (totalPeers: number, playerAgents: any, allPlayers: Player[]) => { 88 | let now = Date.now() 89 | console.log(`Start waiting for peer stores at ${new Date(now).toLocaleString("en-US")}`) 90 | // Wait for all agents to have complete peer stores. 91 | for (const playerIdx in allPlayers) { 92 | for (const agentIdx in playerAgents[playerIdx]) { 93 | const player = allPlayers[playerIdx] 94 | const agent = playerAgents[playerIdx][agentIdx] 95 | while (true) { 96 | const stateDumpRes = await player.adminWs().dumpState({ cell_id: agent.cell.cellId }) 97 | console.log('state dump:', stateDumpRes) 98 | const stateDump = parseStateDump(stateDumpRes) 99 | console.log(`waiting for all agents are present in peer store of player #${playerIdx} agent #${agentIdx}`, stateDump) 100 | if (stateDump.numPeers === totalPeers-1) { 101 | break 102 | } 103 | await delay(5000) 104 | } 105 | } 106 | } 107 | const endWaitPeers = Date.now() 108 | console.log(`Finished waiting for peers at ${new Date(endWaitPeers).toLocaleString("en-US")}`) 109 | took(`Waiting for peer consistency`, now, endWaitPeers) 110 | } 111 | 112 | // wait until the active peers can see all the peers 113 | const waitActivePeers = async (percentNeeded: number, totalPeers: number, activeAgents: Agents, allPlayers: Player[]) : Promise => { 114 | let startWait = Date.now() 115 | console.log(`Start waiting for peer stores at ${new Date(startWait).toLocaleString("en-US")}`) 116 | let conductors = {} 117 | for (const agent of activeAgents) { 118 | if (!conductors[agent.playerIdx]) { 119 | conductors[agent.playerIdx] = { cell_id: agent.cell.cellId } 120 | } 121 | } 122 | for (const [playerIdx, param] of Object.entries(conductors)) { 123 | while (true) { 124 | const stateDumpRes = await allPlayers[playerIdx].adminWs().dumpState(param) 125 | const stateDump = parseStateDump(stateDumpRes) 126 | console.log('state dump:', stateDump) 127 | const needed = (totalPeers-1)*(percentNeeded/100) 128 | console.log(`got ${stateDump.numPeers} of ${needed} (${percentNeeded}% of ${totalPeers}) of peers to be present in peer store of player #${playerIdx}, ${time2text(Date.now() - startWait)} so far`, stateDump) 129 | if (stateDump.numPeers > needed) { 130 | break 131 | } 132 | console.log(`waiting 5 seconds`) 133 | await delay(5000) 134 | } 135 | } 136 | 137 | const endWaitPeers = Date.now() 138 | console.log(`Finished waiting for peers at ${new Date(endWaitPeers).toLocaleString("en-US")}`) 139 | took(`Waiting for active peer consistency`, startWait, endWaitPeers) 140 | 141 | return endWaitPeers - startWait 142 | } 143 | 144 | const activateAgents = async (count: number, playerAgents): Promise => { 145 | const activeAgents = selectActiveAgents(count, playerAgents); 146 | _activateAgents(activeAgents, playerAgents); 147 | _waitAgentsActivated(activeAgents); 148 | return activeAgents; 149 | } 150 | 151 | const _activateAgents = async (activeAgents: Agents, playerAgents) => { 152 | let now = Date.now() 153 | 154 | console.log(`Start calling refresh chatter for ${activeAgents.length} agents at ${new Date(now).toLocaleString("en-US")}`) 155 | await Promise.all(activeAgents.map( 156 | agent => agent.cell.call('chat', 'refresh_chatter', null))); 157 | const endRefresh = Date.now(); 158 | console.log(`End calling refresh chatter at ${new Date(endRefresh).toLocaleString("en-US")}`) 159 | took(`Activating agents`, now, endRefresh) 160 | } 161 | 162 | const _waitAgentsActivated = async (activeAgents: Agents) : Promise => { 163 | let startWait = Date.now() 164 | console.log(`Start find agents at ${new Date(startWait).toLocaleString("en-US")}`) 165 | // wait for all active agents to see all other active agents: 166 | for (const agentIdx in activeAgents) { 167 | while (true) { 168 | const stats = await activeAgents[agentIdx].cell.call('chat', 'agent_stats', null); 169 | console.log(`waiting for #${agentIdx}'s agent_stats to show ${activeAgents.length} as active (${time2text(Date.now() - startWait)} so far), got:`, stats) 170 | if (stats.agents === activeAgents.length) { 171 | break; 172 | } 173 | await delay(5000) 174 | } 175 | } 176 | const endFindAgents = Date.now() 177 | console.log(`End find agents at ${new Date(endFindAgents).toLocaleString("en-US")}`) 178 | took(`Waiting for active agent consistency`, startWait, endFindAgents) 179 | return endFindAgents - startWait 180 | } 181 | 182 | const maxMinAvg = (counts) => { 183 | let min = Math.min(...counts) 184 | let max = Math.max(...counts) 185 | let sum = counts.reduce((a, b) => a + b) 186 | let avg = sum / counts.length 187 | return {min, max, avg} 188 | } 189 | 190 | const doListMessages = async (msg, channel, activeAgents): Promise> => { 191 | let i = 0; 192 | const counts : Array = await Promise.all( 193 | activeAgents.map(async agent => { 194 | const r = await agent.cell.call('chat', 'list_messages', { channel: channel.entry, active_chatter: false, target_message_count: 10000}) 195 | i+=1; 196 | console.log(`${i}--called list messages for: `, agent.agent.toString('base64'), r.messages.length) 197 | return r.messages.length 198 | })) 199 | 200 | const m = maxMinAvg(counts) 201 | console.log(`${msg}: Min: ${m.min} Max: ${m.max} Avg ${m.avg.toFixed(1)}`) 202 | return counts 203 | } 204 | 205 | const setup = async (s: ScenarioApi, t, config, local): Promise<{ playerAgents, allPlayers: Player[], channel: any }> => { 206 | let network; 207 | if (local) { 208 | network = { transport_pool: [], bootstrap_service: undefined } 209 | } else { 210 | network = defaultNetworkConfig 211 | } 212 | 213 | t.comment(`Preparing playground: initializing conductors and spawning`) 214 | 215 | const installation: InstallAgentsHapps = _.times(config.instances, () => [[config.dnaSource]]); 216 | 217 | let conductorConfigsArray: Array = [] 218 | for (let i = 0; i < config.conductors; i++) { 219 | network = _.cloneDeep(network) 220 | network.transport_pool[0].proxy_config.proxy_url = config.proxys[i%config.proxyCount] 221 | const conductorConfig = Config.gen({network}) 222 | conductorConfigsArray.push(conductorConfig) 223 | } 224 | for (let i = 0; i < config.conductors; i++) { 225 | // console.log("C",conductorConfigsArray[i]()) 226 | } 227 | let allPlayers: Player[] 228 | let i = 0; 229 | 230 | // remote in config means use trycp server 231 | if (config.trycpAddresses.length == 0) { 232 | allPlayers = await s.players(conductorConfigsArray, false) 233 | await Promise.all(allPlayers.map(player => player.startup(() => { }))); 234 | i = allPlayers.length 235 | } else { 236 | allPlayers = [] 237 | while (allPlayers.length / config.conductors < config.nodes) { 238 | if (i >= config.trycpAddresses.length) { 239 | throw new Error(`ran out of trycp addresses after contacting ${allPlayers.length / config.conductors} nodes`) 240 | } 241 | let players: Player[]; 242 | try { 243 | console.log("Starting players at trycp server:", config.trycpAddresses[i]) 244 | players = await s.players(conductorConfigsArray, false, config.trycpAddresses[i]) 245 | await Promise.all(players.map(player => player.startup(() => { }))); 246 | } catch (e) { 247 | console.log(`Skipping trycp node ${config.trycpAddresses[i]} due to error: ${JSON.stringify(e)}`) 248 | i += 1 249 | continue 250 | } 251 | players.forEach(player => allPlayers.push(player)); 252 | i += 1 253 | } 254 | } 255 | 256 | // install chat on all the conductors 257 | const playerAgents = await Promise.all(allPlayers.map(async (player, i) => { 258 | console.log("installing player", i) 259 | // console.log("installation", installation) 260 | // const agents = await player.installAgentsHapps(installation) 261 | const agentNames = _.times(config.instances, (n) => `c${i}p${n}`) 262 | const agents = await installAgents(player,agentNames) 263 | //const installedAgentHapps: InstalledAgentHapps = agents. 264 | return agents.map((happs) => { 265 | const [{ hAppId, agent, cells: [cell] }] = [happs]; 266 | console.log(`PlayerIdx: ${i} DNA HASH: ${cell.cellId[0].toString()}`) 267 | return { hAppId, agent, cell, playerIdx: i } 268 | }) 269 | })) 270 | 271 | if (local) { 272 | let now = Date.now() 273 | console.log(`Calling share all nodes ${new Date(now).toLocaleString("en-US")}`) 274 | await s.shareAllNodes(allPlayers); 275 | console.log(`Finished share all nodes ${new Date(Date.now()).toLocaleString("en-US")}`) 276 | } 277 | 278 | console.log(`Creating channel for test:`) 279 | const channel_uuid = uuidv4(); 280 | const entry = { category: "General", uuid: channel_uuid } 281 | const createChannelResult = await playerAgents[0][0].cell.call('chat', 'create_channel', { name: `Test Channel`, entry }); 282 | console.log(createChannelResult); 283 | 284 | return { playerAgents, allPlayers, channel: createChannelResult } 285 | } 286 | 287 | const send = async (i, cell, channel, signal: "signal" | "noSignal") => { 288 | const msg = { 289 | last_seen: { First: null }, 290 | channel: channel.entry, 291 | entry: { 292 | uuid: uuidv4(), 293 | content: `message ${i}`, 294 | }, 295 | } 296 | console.log(`creating message ${i}`) 297 | const messageData = await cell.call('chat', 'create_message', msg) 298 | console.log(`message created ${i}`) 299 | 300 | if (signal === "signal") { 301 | console.log(`sending signal ${i}`) 302 | const r = await cell.call('chat', 'signal_chatters', { 303 | messageData, 304 | channelData: channel, 305 | }) 306 | console.log(`signal sent ${i}`) 307 | } 308 | } 309 | 310 | const sendSerially = async (end: number, sendingCell: Cell, channel, messagesToSend: number) => { 311 | // const msDelayBetweenMessage = period/messagesToSend 312 | for (let i = 0; i < messagesToSend; i++) { 313 | await send(i, sendingCell, channel, "signal") 314 | if (Date.now() > end) { 315 | i = i + 1 316 | console.log(`Couldn't send all messages in period, sent ${i}`) 317 | return i 318 | } 319 | // console.log(`waiting ${msDelayBetweenMessage}ms`) 320 | // await delay(msDelayBetweenMessage-20) 321 | } 322 | return messagesToSend 323 | } 324 | 325 | const sendConcurrently = async (agents: Agents, channel, messagesToSend: number, signal: "signal" | "noSignal") => { 326 | const messagePromises = new Array(messagesToSend) 327 | for (let i = 0; i < messagesToSend; i++) { 328 | messagePromises[i] = send(i, agents[i % (agents.length)].cell, channel, signal) 329 | } 330 | await Promise.all(messagePromises) 331 | } 332 | 333 | const gossipTrial = async (activeAgents: Agents, playerAgents, channel, messagesToSend: number): Promise => { 334 | const receivingCell = playerAgents[0][0].cell 335 | const start = Date.now() 336 | await sendConcurrently(activeAgents, channel, messagesToSend, "noSignal") 337 | console.log(`Getting messages (should be ${messagesToSend})`) 338 | let received = 0 339 | while (true) { 340 | let justReceived = 0; 341 | try { 342 | justReceived = (await receivingCell.call('chat', 'list_messages', { channel: channel.entry, target_message_count: 10000 })).messages.length 343 | } catch (e) { 344 | console.error("error while checking number of messages received", e) 345 | } 346 | 347 | if (received !== justReceived) { 348 | received = justReceived 349 | console.log(`After ${(Date.now() - start)}ms, receiver got ${received} messages`) 350 | if (received === messagesToSend) { 351 | return Date.now() - start 352 | } 353 | } else { 354 | await delay(200) 355 | } 356 | } 357 | } 358 | 359 | const took = (msg, start, end) => { 360 | _took(msg, end - start) 361 | } 362 | 363 | const _took = (msg, ms) => { 364 | const r = time2text(ms); 365 | console.log(`${msg} took: ${r}`) 366 | } 367 | 368 | const time2text = (ms) => { 369 | let r 370 | if (ms < 1000) { 371 | r = `${ms}ms` 372 | } else if (ms < 60000) { 373 | r = `${(ms/1000).toFixed(1)}s` 374 | } else { 375 | r = `${(ms/(60000)).toFixed(2)}m` 376 | } 377 | return r 378 | } 379 | 380 | const signalTrial = async (period, activeAgents: Agents, allPlayers: Player[], channel, messagesToSend) => { 381 | let totalActiveAgents = activeAgents.length 382 | // Track how many signals each agent has received. 383 | const receipts: Record = {} 384 | let totalReceived = 0; 385 | const totalExpected = messagesToSend * (totalActiveAgents - 1) // sender doesn't receive signals 386 | for (const agent of activeAgents) { 387 | receipts[agent.agent.toString('base64')] = 0 388 | } 389 | console.log("Receipts:", receipts) 390 | 391 | let allReceiptsResolve 392 | const allReceipts = new Promise((resolve, reject) => allReceiptsResolve = resolve) 393 | 394 | // setup the signal handler for all the players so we can check 395 | // if all the signals are returned 396 | for (let i = 0; i < allPlayers.length; i++) { 397 | const conductor = allPlayers[i] 398 | conductor.setSignalHandler((signal) => { 399 | const { data: { cellId: [dnaHash, agentKey], payload: any } } = signal 400 | const key = agentKey.toString('base64') 401 | if (key in receipts) { 402 | receipts[key] += 1 403 | totalReceived += 1 404 | console.log(`${key} got signal. Total so far: ${totalReceived}`) 405 | // console.log(`Received Signal for conductor #${i.toString()}, agentKey ${agentKey.toString('hex')}, agent #${idx}:`, signal.data.payload.signal_payload.messageData.message) 406 | if (totalReceived === totalExpected) { 407 | allReceiptsResolve(Date.now()) 408 | } 409 | } 410 | }) 411 | } 412 | 413 | const start = Date.now() 414 | console.log(`Start sending messages at ${new Date(start).toLocaleString("en-US")}`) 415 | const delayPromise = delay(period).then(() => undefined) 416 | await sendConcurrently(activeAgents, channel, messagesToSend, "signal") 417 | console.log(`Finished sending messages at ${new Date(Date.now()).toLocaleString("en-US")}`) 418 | console.log(`Getting messages (should be ${messagesToSend})`) 419 | 420 | const finishTime: number | undefined = await Promise.race([allReceipts, delayPromise]) 421 | 422 | if (finishTime === undefined) { 423 | console.log(`Didn't receive all messages in period (${period / 1000}s)!`) 424 | console.log(`Total active agents: ${totalActiveAgents}`) 425 | // console.log(`Total agents that received all signals: ${finishedCount} (${(finishedCount/totalActiveAgents*100).toFixed(1)}%)`) 426 | console.log(`Total messages created: ${messagesToSend}`) 427 | console.log(`Total signals sent: ${totalExpected}`) 428 | console.log(`Total signals received: ${totalReceived} (${(totalReceived / totalExpected * 100).toFixed(1)}%)`) 429 | const numPeersPerActiveAgent = await Promise.all(activeAgents.map(async agent => 430 | parseStateDump(await allPlayers[agent.playerIdx].adminWs().dumpState({ cell_id: agent.cell.cellId })).numPeers)) 431 | const min = Math.min(...numPeersPerActiveAgent) 432 | const max = Math.max(...numPeersPerActiveAgent) 433 | const sum = numPeersPerActiveAgent.reduce((a, b) => a + b) 434 | const avg = sum / numPeersPerActiveAgent.length 435 | console.log(`Peers amongst active agents: Min: ${min} Max: ${max} Avg ${avg}`) 436 | return undefined 437 | } 438 | 439 | console.log(`All nodes got all signals!`) 440 | return finishTime - start 441 | } 442 | 443 | const sendOnInterval = async (senders: number, agents: Agents, channel, period: number, sendInterval: number) : Promise => { 444 | let totalSent = 0 445 | const start = Date.now() 446 | do { 447 | const intervalStart = Date.now() 448 | let messagePromises = new Array(senders) 449 | for (let i = 0; i < senders; i++) { 450 | messagePromises[i] = send(i, agents[i].cell, channel, "signal" ) 451 | } 452 | await Promise.all(messagePromises) 453 | const intervalDuration = (Date.now() - intervalStart) 454 | console.log(`took: ${intervalDuration}ms to send ${senders} messages`) 455 | totalSent += senders 456 | if (intervalDuration < sendInterval) { 457 | console.log(`Waiting ${(sendInterval - intervalDuration)}ms before sending again`) 458 | await delay(sendInterval - intervalDuration) 459 | } 460 | } while((Date.now() - start) < period) 461 | return totalSent 462 | } 463 | 464 | const PEER_CONSISTENCY_PERCENT = 90 465 | 466 | const phaseTrial = async (config, phase, playerAgents, allPlayers: Player[], channel) => { 467 | const period = phase.period 468 | const messagesInPeriod = phase.messages 469 | const senders = phase.senders 470 | 471 | const totalPeers = config.nodes * config.conductors * config.instances 472 | const activeAgents = selectActiveAgents(phase.active, playerAgents) 473 | const peerConsistencyTook = await waitActivePeers(PEER_CONSISTENCY_PERCENT, totalPeers, activeAgents, allPlayers) // need 75% of peers for go 474 | await _activateAgents(activeAgents, playerAgents) 475 | const activationConsistencyTook = await _waitAgentsActivated(activeAgents) 476 | 477 | let totalActiveAgents = activeAgents.length 478 | // Track how many signals are received in various latencies 479 | const receipts: { [key: number]: number; } = {} 480 | 481 | let totalSignalsReceived = 0; 482 | 483 | const latencyTranches = 5 484 | // setup the signal handler for all the players so we can check 485 | // if all the signals are returned 486 | for (let i = 0; i < allPlayers.length; i++) { 487 | const conductor = allPlayers[i] 488 | conductor.setSignalHandler((signal) => { 489 | const { data: { cellId: [dnaHash, agentKey], payload: payload } } = signal 490 | const now = Date.now() 491 | console.log(">>", payload.signal_payload.messageData.createdAt); 492 | console.log(">>", now); 493 | 494 | const latency = now - (payload.signal_payload.messageData.createdAt/1000) 495 | let tranch:number 496 | if (latency < 5000) { 497 | tranch = 5000 498 | } else if (latency < 10000) { 499 | tranch = 10000 500 | } else if (latency < 20000) { 501 | tranch = 20000 502 | } else if (latency < 30000) { 503 | tranch = 30000 504 | } else { 505 | tranch = 1000 * 60 506 | } 507 | 508 | if (receipts[tranch] === undefined) { 509 | receipts[tranch] = 1 510 | } else { 511 | receipts[tranch]+=1 512 | } 513 | const key = agentKey.toString('base64') 514 | totalSignalsReceived += 1 515 | console.log(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); 516 | console.log(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); 517 | console.log(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); 518 | console.log(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>"); 519 | 520 | console.log(`${key} got signal with latency ${latency}. Total so far: ${totalSignalsReceived}`) 521 | }) 522 | } 523 | const sendInterval = period/(messagesInPeriod/senders) 524 | const start = Date.now() 525 | console.log(`Phase begins at ${new Date(start).toLocaleString("en-US")}:`) 526 | console.log(` 1 message per ${senders} sender ${sendInterval}ms for ${period}ms`) 527 | const totalMessagesSent = await sendOnInterval(senders, activeAgents, channel, period, sendInterval) 528 | const phaseEnd = Date.now() 529 | console.log(`Phase ends at ${new Date(phaseEnd).toLocaleString("en-US")}`) 530 | took(`Sending ${totalMessagesSent} messages`, start, phaseEnd) 531 | 532 | const totalSignalsExpected = totalMessagesSent * (totalActiveAgents - 1) // sender doesn't receive signals 533 | let curArrived; 534 | const msToWaitBeforeCallingItQuits = 30000 535 | do { 536 | console.log(`Waiting for ${msToWaitBeforeCallingItQuits/1000}s for signals to finish arriving (expecting ${totalSignalsExpected})`) 537 | curArrived = totalSignalsReceived 538 | await delay(msToWaitBeforeCallingItQuits) 539 | 540 | } while (curArrived != totalSignalsReceived) 541 | 542 | let waitingEnd = Date.now() 543 | 544 | console.log(`Waiting for signals ends at ${new Date(waitingEnd).toLocaleString("en-US")}`) 545 | took(`Waiting for signals`, phaseEnd, waitingEnd) 546 | 547 | let listMessagesResults: Array> = [] 548 | let listMessagesMin = 0 549 | let done = false 550 | let maxMinutesForListMessages = 6 551 | while (!done) { 552 | const results = await doListMessages(`listMessages at min ${listMessagesMin} after waiting for signals`, channel, activeAgents) 553 | listMessagesResults[listMessagesMin] = results 554 | // stop waiting if all messages received or maxMinutesForListMessages min have passed 555 | const minReceived = maxMinAvg(results).min 556 | if ((minReceived == totalMessagesSent) || 557 | listMessagesMin == (maxMinutesForListMessages-1)) { 558 | done = true 559 | } else { 560 | console.log(`minimun received: ${minReceived} waiting another minute`) 561 | await delay(1000*60) 562 | listMessagesMin += 1 563 | } 564 | } 565 | 566 | console.log("----------------------------------------------------------") 567 | console.log("Results ") 568 | console.log("----------------------------------------------------------") 569 | console.log(`Nodes: ${config.nodes}\nConductors/Node: ${config.conductors}\nCells/Conductor: ${config.instances}`) 570 | console.log(`Proxys: ${config.proxyCount}`) 571 | console.log(`Total total peers in network: ${totalPeers}`) 572 | console.log(`Total active peers: ${totalActiveAgents}`) 573 | _took(`Waiting for ${PEER_CONSISTENCY_PERCENT}% peer consistency`, peerConsistencyTook) 574 | _took("Waiting for active agent consistency", activationConsistencyTook) 575 | console.log(`Senders: ${senders}`) 576 | console.log(`Sending 1 message per ${senders} sender ${(sendInterval/1000).toFixed(1)}s for ${(period/1000).toFixed(1)}s`) 577 | took(`Sending ${totalMessagesSent} messages`, start, phaseEnd) 578 | console.log(`Total messages sent: ${totalMessagesSent}`) 579 | console.log(`Total signals sent: ${totalSignalsExpected}`) 580 | 581 | took(`Waiting for signals after sending ended`, phaseEnd, waitingEnd) 582 | took(`From start of sending till fished waiting for signals`, start, waitingEnd) 583 | 584 | 585 | console.log(`Total signals received: ${totalSignalsReceived} (${(totalSignalsReceived / totalSignalsExpected * 100).toFixed(2)}%)`) 586 | 587 | const latencies = Object.keys(receipts) 588 | latencies.sort((a, b) => parseInt(a) - parseInt(b)); 589 | 590 | for (let i = 0; i < latencyTranches; i++) { 591 | if (i >= latencies.length) { 592 | // empty line so things line up in the spreadsheet across runs 593 | console.log(`Latencies in tranch: --` ) 594 | } else { 595 | const l = latencies[i]; 596 | const count = receipts[l]; 597 | const percent = (count/totalSignalsReceived * 100).toFixed(1); 598 | const tranch = parseInt(l)/1000 599 | console.log(`Latencies in ${tranch}s: ${count} (${percent})%` ) 600 | } 601 | } 602 | 603 | const numPeersPerActiveAgent = await Promise.all(activeAgents.map(async agent => 604 | parseStateDump(await allPlayers[agent.playerIdx].adminWs().dumpState({ cell_id: agent.cell.cellId })).numPeers)) 605 | 606 | const m = maxMinAvg(numPeersPerActiveAgent) 607 | console.log(`Final peers count in peer stores of active peers:\nMin: ${m.min}\nMax: ${m.max}\nAvg: ${m.avg.toFixed(1)}`) 608 | 609 | // print out list messages results at the bottom because the length is variable 610 | for (let i = 0; i < listMessagesResults.length; i++) { 611 | logCounts(`listMessages at min ${i}`, listMessagesResults[i], totalMessagesSent) 612 | } 613 | 614 | } 615 | 616 | const logCounts = (msg, messagesByAgent, totalMessages) => { 617 | const counts: Record = {} 618 | for (const c of messagesByAgent) { 619 | let tranch:string 620 | if (c < totalMessages*.25) { 621 | tranch = "0-25%" 622 | } else if (c < totalMessages*.75) { 623 | tranch = "25-75%" 624 | } else if (c < totalMessages*.99) { 625 | tranch = "75-99%" 626 | } else { 627 | tranch = " 100%" 628 | } 629 | 630 | if (counts[tranch] === undefined) { 631 | counts[tranch] = 1 632 | } else { 633 | counts[tranch]+=1 634 | } 635 | 636 | } 637 | console.log(msg) 638 | for (const tranch of [" 100%", "75-99%", "25-75%", "0-25%"]) { 639 | let count = counts[tranch] 640 | if (count === undefined) { 641 | count = 0 642 | } 643 | const percent = (count/messagesByAgent.length*100).toFixed(1) 644 | console.log(`${tranch}: ${count} (${percent}%)`) 645 | } 646 | } 647 | 648 | export const gossipTx = async (s, t, config, txCount, local) => { 649 | const { playerAgents, allPlayers, channel } = await setup(s, t, config, local) 650 | const totalPeers = config.nodes * config.conductors * config.instances 651 | await waitAllPeers(totalPeers, playerAgents, allPlayers) 652 | const activeAgents = await activateAgents(config.activeAgents, playerAgents) 653 | const actual = await gossipTrial(activeAgents, playerAgents, channel, txCount) 654 | await Promise.all(allPlayers.map(player => player.shutdown())) 655 | return actual 656 | } 657 | 658 | export const signalTx = async (s, t, config, period, txCount, local) => { 659 | // do the standard setup 660 | const { playerAgents, allPlayers, channel } = await setup(s, t, config, local) 661 | const totalPeers = config.nodes * config.conductors * config.instances 662 | await waitAllPeers(totalPeers, playerAgents, allPlayers) 663 | const activeAgents = await activateAgents(config.activeAgents, playerAgents) 664 | 665 | const actual = await signalTrial(period, activeAgents, allPlayers, channel, txCount) 666 | await Promise.all(allPlayers.map(player => player.shutdown())) 667 | return actual 668 | } 669 | 670 | export const phasesTx = async (s, t, config, phases, local) => { 671 | // do the standard setup 672 | const { playerAgents, allPlayers, channel } = await setup(s, t, config, local) 673 | for (const phase of phases) { 674 | await phaseTrial(config, phase, playerAgents, allPlayers, channel) 675 | } 676 | await Promise.all(allPlayers.map(player => player.shutdown())) 677 | } 678 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "addr2line" 7 | version = "0.17.0" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" 10 | dependencies = [ 11 | "gimli 0.26.1", 12 | ] 13 | 14 | [[package]] 15 | name = "adler" 16 | version = "1.0.2" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" 19 | 20 | [[package]] 21 | name = "ahash" 22 | version = "0.7.6" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" 25 | dependencies = [ 26 | "getrandom", 27 | "once_cell", 28 | "version_check", 29 | ] 30 | 31 | [[package]] 32 | name = "arrayref" 33 | version = "0.3.6" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "a4c527152e37cf757a3f78aae5a06fbeefdb07ccc535c980a3208ee3060dd544" 36 | 37 | [[package]] 38 | name = "arrayvec" 39 | version = "0.5.2" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" 42 | 43 | [[package]] 44 | name = "autocfg" 45 | version = "1.1.0" 46 | source = "registry+https://github.com/rust-lang/crates.io-index" 47 | checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" 48 | 49 | [[package]] 50 | name = "backtrace" 51 | version = "0.3.64" 52 | source = "registry+https://github.com/rust-lang/crates.io-index" 53 | checksum = "5e121dee8023ce33ab248d9ce1493df03c3b38a659b240096fcbd7048ff9c31f" 54 | dependencies = [ 55 | "addr2line", 56 | "cc", 57 | "cfg-if", 58 | "libc", 59 | "miniz_oxide", 60 | "object 0.27.1", 61 | "rustc-demangle", 62 | ] 63 | 64 | [[package]] 65 | name = "base64" 66 | version = "0.13.0" 67 | source = "registry+https://github.com/rust-lang/crates.io-index" 68 | checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" 69 | 70 | [[package]] 71 | name = "bit-set" 72 | version = "0.5.2" 73 | source = "registry+https://github.com/rust-lang/crates.io-index" 74 | checksum = "6e11e16035ea35e4e5997b393eacbf6f63983188f7a2ad25bfb13465f5ad59de" 75 | dependencies = [ 76 | "bit-vec", 77 | ] 78 | 79 | [[package]] 80 | name = "bit-vec" 81 | version = "0.6.3" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" 84 | 85 | [[package]] 86 | name = "bitflags" 87 | version = "1.3.2" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 90 | 91 | [[package]] 92 | name = "blake2b_simd" 93 | version = "0.5.11" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | checksum = "afa748e348ad3be8263be728124b24a24f268266f6f5d58af9d75f6a40b5c587" 96 | dependencies = [ 97 | "arrayref", 98 | "arrayvec", 99 | "constant_time_eq", 100 | ] 101 | 102 | [[package]] 103 | name = "bumpalo" 104 | version = "3.9.1" 105 | source = "registry+https://github.com/rust-lang/crates.io-index" 106 | checksum = "a4a45a46ab1f2412e53d3a0ade76ffad2025804294569aae387231a0cd6e0899" 107 | 108 | [[package]] 109 | name = "bytecheck" 110 | version = "0.6.8" 111 | source = "registry+https://github.com/rust-lang/crates.io-index" 112 | checksum = "3a31f923c2db9513e4298b72df143e6e655a759b3d6a0966df18f81223fff54f" 113 | dependencies = [ 114 | "bytecheck_derive", 115 | "ptr_meta", 116 | ] 117 | 118 | [[package]] 119 | name = "bytecheck_derive" 120 | version = "0.6.8" 121 | source = "registry+https://github.com/rust-lang/crates.io-index" 122 | checksum = "edb17c862a905d912174daa27ae002326fff56dc8b8ada50a0a5f0976cb174f0" 123 | dependencies = [ 124 | "proc-macro2", 125 | "quote", 126 | "syn", 127 | ] 128 | 129 | [[package]] 130 | name = "byteorder" 131 | version = "1.4.3" 132 | source = "registry+https://github.com/rust-lang/crates.io-index" 133 | checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" 134 | 135 | [[package]] 136 | name = "cc" 137 | version = "1.0.73" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" 140 | 141 | [[package]] 142 | name = "cfg-if" 143 | version = "1.0.0" 144 | source = "registry+https://github.com/rust-lang/crates.io-index" 145 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 146 | 147 | [[package]] 148 | name = "chat" 149 | version = "0.3.0" 150 | dependencies = [ 151 | "chat_integrity", 152 | "chrono", 153 | "derive_more", 154 | "hc_utils", 155 | "hdk", 156 | "serde", 157 | "thiserror", 158 | "uuid", 159 | ] 160 | 161 | [[package]] 162 | name = "chat_integrity" 163 | version = "0.1.0" 164 | dependencies = [ 165 | "holochain_deterministic_integrity", 166 | "serde", 167 | ] 168 | 169 | [[package]] 170 | name = "chrono" 171 | version = "0.4.19" 172 | source = "registry+https://github.com/rust-lang/crates.io-index" 173 | checksum = "670ad68c9088c2a963aaa298cb369688cf3f9465ce5e2d4ca10e6e0098a1ce73" 174 | dependencies = [ 175 | "libc", 176 | "num-integer", 177 | "num-traits", 178 | "serde", 179 | "time", 180 | "winapi", 181 | ] 182 | 183 | [[package]] 184 | name = "constant_time_eq" 185 | version = "0.1.5" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" 188 | 189 | [[package]] 190 | name = "convert_case" 191 | version = "0.4.0" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" 194 | 195 | [[package]] 196 | name = "cranelift-bforest" 197 | version = "0.76.0" 198 | source = "registry+https://github.com/rust-lang/crates.io-index" 199 | checksum = "7e6bea67967505247f54fa2c85cf4f6e0e31c4e5692c9b70e4ae58e339067333" 200 | dependencies = [ 201 | "cranelift-entity", 202 | ] 203 | 204 | [[package]] 205 | name = "cranelift-codegen" 206 | version = "0.76.0" 207 | source = "registry+https://github.com/rust-lang/crates.io-index" 208 | checksum = "48194035d2752bdd5bdae429e3ab88676e95f52a2b1355a5d4e809f9e39b1d74" 209 | dependencies = [ 210 | "cranelift-bforest", 211 | "cranelift-codegen-meta", 212 | "cranelift-codegen-shared", 213 | "cranelift-entity", 214 | "gimli 0.25.0", 215 | "log", 216 | "regalloc", 217 | "smallvec", 218 | "target-lexicon", 219 | ] 220 | 221 | [[package]] 222 | name = "cranelift-codegen-meta" 223 | version = "0.76.0" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "976efb22fcab4f2cd6bd4e9913764616a54d895c1a23530128d04e03633c555f" 226 | dependencies = [ 227 | "cranelift-codegen-shared", 228 | "cranelift-entity", 229 | ] 230 | 231 | [[package]] 232 | name = "cranelift-codegen-shared" 233 | version = "0.76.0" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "9dabb5fe66e04d4652e434195b45ae65b5c8172d520247b8f66d8df42b2b45dc" 236 | 237 | [[package]] 238 | name = "cranelift-entity" 239 | version = "0.76.0" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "3329733e4d4b8e91c809efcaa4faee80bf66f20164e3dd16d707346bd3494799" 242 | 243 | [[package]] 244 | name = "cranelift-frontend" 245 | version = "0.76.0" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "279afcc0d3e651b773f94837c3d581177b348c8d69e928104b2e9fccb226f921" 248 | dependencies = [ 249 | "cranelift-codegen", 250 | "log", 251 | "smallvec", 252 | "target-lexicon", 253 | ] 254 | 255 | [[package]] 256 | name = "crc32fast" 257 | version = "1.3.2" 258 | source = "registry+https://github.com/rust-lang/crates.io-index" 259 | checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" 260 | dependencies = [ 261 | "cfg-if", 262 | ] 263 | 264 | [[package]] 265 | name = "crossbeam-channel" 266 | version = "0.5.4" 267 | source = "registry+https://github.com/rust-lang/crates.io-index" 268 | checksum = "5aaa7bd5fb665c6864b5f963dd9097905c54125909c7aa94c9e18507cdbe6c53" 269 | dependencies = [ 270 | "cfg-if", 271 | "crossbeam-utils", 272 | ] 273 | 274 | [[package]] 275 | name = "crossbeam-deque" 276 | version = "0.8.1" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" 279 | dependencies = [ 280 | "cfg-if", 281 | "crossbeam-epoch", 282 | "crossbeam-utils", 283 | ] 284 | 285 | [[package]] 286 | name = "crossbeam-epoch" 287 | version = "0.9.8" 288 | source = "registry+https://github.com/rust-lang/crates.io-index" 289 | checksum = "1145cf131a2c6ba0615079ab6a638f7e1973ac9c2634fcbeaaad6114246efe8c" 290 | dependencies = [ 291 | "autocfg", 292 | "cfg-if", 293 | "crossbeam-utils", 294 | "lazy_static", 295 | "memoffset", 296 | "scopeguard", 297 | ] 298 | 299 | [[package]] 300 | name = "crossbeam-utils" 301 | version = "0.8.8" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "0bf124c720b7686e3c2663cf54062ab0f68a88af2fb6a030e87e30bf721fcb38" 304 | dependencies = [ 305 | "cfg-if", 306 | "lazy_static", 307 | ] 308 | 309 | [[package]] 310 | name = "darling" 311 | version = "0.13.1" 312 | source = "registry+https://github.com/rust-lang/crates.io-index" 313 | checksum = "d0d720b8683f8dd83c65155f0530560cba68cd2bf395f6513a483caee57ff7f4" 314 | dependencies = [ 315 | "darling_core 0.13.1", 316 | "darling_macro 0.13.1", 317 | ] 318 | 319 | [[package]] 320 | name = "darling" 321 | version = "0.14.1" 322 | source = "registry+https://github.com/rust-lang/crates.io-index" 323 | checksum = "4529658bdda7fd6769b8614be250cdcfc3aeb0ee72fe66f9e41e5e5eb73eac02" 324 | dependencies = [ 325 | "darling_core 0.14.1", 326 | "darling_macro 0.14.1", 327 | ] 328 | 329 | [[package]] 330 | name = "darling_core" 331 | version = "0.13.1" 332 | source = "registry+https://github.com/rust-lang/crates.io-index" 333 | checksum = "7a340f241d2ceed1deb47ae36c4144b2707ec7dd0b649f894cb39bb595986324" 334 | dependencies = [ 335 | "fnv", 336 | "ident_case", 337 | "proc-macro2", 338 | "quote", 339 | "strsim", 340 | "syn", 341 | ] 342 | 343 | [[package]] 344 | name = "darling_core" 345 | version = "0.14.1" 346 | source = "registry+https://github.com/rust-lang/crates.io-index" 347 | checksum = "649c91bc01e8b1eac09fb91e8dbc7d517684ca6be8ebc75bb9cafc894f9fdb6f" 348 | dependencies = [ 349 | "fnv", 350 | "ident_case", 351 | "proc-macro2", 352 | "quote", 353 | "strsim", 354 | "syn", 355 | ] 356 | 357 | [[package]] 358 | name = "darling_macro" 359 | version = "0.13.1" 360 | source = "registry+https://github.com/rust-lang/crates.io-index" 361 | checksum = "72c41b3b7352feb3211a0d743dc5700a4e3b60f51bd2b368892d1e0f9a95f44b" 362 | dependencies = [ 363 | "darling_core 0.13.1", 364 | "quote", 365 | "syn", 366 | ] 367 | 368 | [[package]] 369 | name = "darling_macro" 370 | version = "0.14.1" 371 | source = "registry+https://github.com/rust-lang/crates.io-index" 372 | checksum = "ddfc69c5bfcbd2fc09a0f38451d2daf0e372e367986a83906d1b0dbc88134fb5" 373 | dependencies = [ 374 | "darling_core 0.14.1", 375 | "quote", 376 | "syn", 377 | ] 378 | 379 | [[package]] 380 | name = "derive_more" 381 | version = "0.99.17" 382 | source = "registry+https://github.com/rust-lang/crates.io-index" 383 | checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" 384 | dependencies = [ 385 | "convert_case", 386 | "proc-macro2", 387 | "quote", 388 | "rustc_version", 389 | "syn", 390 | ] 391 | 392 | [[package]] 393 | name = "either" 394 | version = "1.6.1" 395 | source = "registry+https://github.com/rust-lang/crates.io-index" 396 | checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" 397 | 398 | [[package]] 399 | name = "enum-iterator" 400 | version = "0.7.0" 401 | source = "registry+https://github.com/rust-lang/crates.io-index" 402 | checksum = "4eeac5c5edb79e4e39fe8439ef35207780a11f69c52cbe424ce3dfad4cb78de6" 403 | dependencies = [ 404 | "enum-iterator-derive", 405 | ] 406 | 407 | [[package]] 408 | name = "enum-iterator-derive" 409 | version = "0.7.0" 410 | source = "registry+https://github.com/rust-lang/crates.io-index" 411 | checksum = "c134c37760b27a871ba422106eedbb8247da973a09e82558bf26d619c882b159" 412 | dependencies = [ 413 | "proc-macro2", 414 | "quote", 415 | "syn", 416 | ] 417 | 418 | [[package]] 419 | name = "enumset" 420 | version = "1.0.8" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "6216d2c19a6fb5f29d1ada1dc7bc4367a8cbf0fa4af5cf12e07b5bbdde6b5b2c" 423 | dependencies = [ 424 | "enumset_derive", 425 | ] 426 | 427 | [[package]] 428 | name = "enumset_derive" 429 | version = "0.5.5" 430 | source = "registry+https://github.com/rust-lang/crates.io-index" 431 | checksum = "6451128aa6655d880755345d085494cf7561a6bee7c8dc821e5d77e6d267ecd4" 432 | dependencies = [ 433 | "darling 0.13.1", 434 | "proc-macro2", 435 | "quote", 436 | "syn", 437 | ] 438 | 439 | [[package]] 440 | name = "fallible-iterator" 441 | version = "0.2.0" 442 | source = "registry+https://github.com/rust-lang/crates.io-index" 443 | checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" 444 | 445 | [[package]] 446 | name = "fastrand" 447 | version = "1.7.0" 448 | source = "registry+https://github.com/rust-lang/crates.io-index" 449 | checksum = "c3fcf0cee53519c866c09b5de1f6c56ff9d647101f81c1964fa632e148896cdf" 450 | dependencies = [ 451 | "instant", 452 | ] 453 | 454 | [[package]] 455 | name = "fnv" 456 | version = "1.0.7" 457 | source = "registry+https://github.com/rust-lang/crates.io-index" 458 | checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" 459 | 460 | [[package]] 461 | name = "gcollections" 462 | version = "1.5.0" 463 | source = "registry+https://github.com/rust-lang/crates.io-index" 464 | checksum = "2f551fdf23ef80329f754919669147a71c67b6cfe3569cd93b6fabdd62044377" 465 | dependencies = [ 466 | "bit-set", 467 | "num-integer", 468 | "num-traits", 469 | "trilean", 470 | ] 471 | 472 | [[package]] 473 | name = "getrandom" 474 | version = "0.2.7" 475 | source = "registry+https://github.com/rust-lang/crates.io-index" 476 | checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" 477 | dependencies = [ 478 | "cfg-if", 479 | "libc", 480 | "wasi 0.11.0+wasi-snapshot-preview1", 481 | ] 482 | 483 | [[package]] 484 | name = "gimli" 485 | version = "0.25.0" 486 | source = "registry+https://github.com/rust-lang/crates.io-index" 487 | checksum = "f0a01e0497841a3b2db4f8afa483cce65f7e96a3498bd6c541734792aeac8fe7" 488 | dependencies = [ 489 | "fallible-iterator", 490 | "indexmap", 491 | "stable_deref_trait", 492 | ] 493 | 494 | [[package]] 495 | name = "gimli" 496 | version = "0.26.1" 497 | source = "registry+https://github.com/rust-lang/crates.io-index" 498 | checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" 499 | 500 | [[package]] 501 | name = "hashbrown" 502 | version = "0.11.2" 503 | source = "registry+https://github.com/rust-lang/crates.io-index" 504 | checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" 505 | dependencies = [ 506 | "ahash", 507 | ] 508 | 509 | [[package]] 510 | name = "hashbrown" 511 | version = "0.12.3" 512 | source = "registry+https://github.com/rust-lang/crates.io-index" 513 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 514 | dependencies = [ 515 | "ahash", 516 | ] 517 | 518 | [[package]] 519 | name = "hc_cz_profile" 520 | version = "0.2.0" 521 | source = "git+https://github.com/holochain/hc-zome-lib?branch=v0.0.138#e9e874901499e2ce154b884b315f58c18207d845" 522 | dependencies = [ 523 | "hc_iz_profile", 524 | "hc_utils", 525 | "hdk", 526 | "serde", 527 | "thiserror", 528 | ] 529 | 530 | [[package]] 531 | name = "hc_iz_membrane_manager" 532 | version = "0.1.0" 533 | source = "git+https://github.com/holochain/hc-zome-lib?branch=v0.0.138#e9e874901499e2ce154b884b315f58c18207d845" 534 | dependencies = [ 535 | "holochain_deterministic_integrity", 536 | "membrane_manager_utils", 537 | "serde", 538 | ] 539 | 540 | [[package]] 541 | name = "hc_iz_profile" 542 | version = "0.1.0" 543 | source = "git+https://github.com/holochain/hc-zome-lib?branch=v0.0.138#e9e874901499e2ce154b884b315f58c18207d845" 544 | dependencies = [ 545 | "holochain_deterministic_integrity", 546 | "serde", 547 | ] 548 | 549 | [[package]] 550 | name = "hc_utils" 551 | version = "0.0.138-patch1" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "6bb0d9585eb1470fb24b3c234aa664692bf5a306a9dbaaf85794dfffb47b6593" 554 | dependencies = [ 555 | "hdk", 556 | "holochain_deterministic_integrity", 557 | "serde", 558 | "thiserror", 559 | ] 560 | 561 | [[package]] 562 | name = "hdk" 563 | version = "0.0.138" 564 | source = "registry+https://github.com/rust-lang/crates.io-index" 565 | checksum = "187ae183ddd5cd381a6f2a0c4a9dbe9048fbdbff4fc72316f69d927d5c3e0b32" 566 | dependencies = [ 567 | "getrandom", 568 | "hdk_derive", 569 | "holo_hash", 570 | "holochain_deterministic_integrity", 571 | "holochain_wasmer_guest", 572 | "holochain_zome_types", 573 | "paste", 574 | "serde", 575 | "serde_bytes", 576 | "thiserror", 577 | "tracing", 578 | "tracing-core", 579 | ] 580 | 581 | [[package]] 582 | name = "hdk_derive" 583 | version = "0.0.37" 584 | source = "registry+https://github.com/rust-lang/crates.io-index" 585 | checksum = "81ebeefd67938656e6fc9482f552d3e5526af8ff82714d6041c1f46dbce602e6" 586 | dependencies = [ 587 | "darling 0.14.1", 588 | "heck", 589 | "holochain_integrity_types", 590 | "paste", 591 | "proc-macro-error", 592 | "proc-macro2", 593 | "quote", 594 | "syn", 595 | ] 596 | 597 | [[package]] 598 | name = "heck" 599 | version = "0.4.0" 600 | source = "registry+https://github.com/rust-lang/crates.io-index" 601 | checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" 602 | 603 | [[package]] 604 | name = "hermit-abi" 605 | version = "0.1.19" 606 | source = "registry+https://github.com/rust-lang/crates.io-index" 607 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 608 | dependencies = [ 609 | "libc", 610 | ] 611 | 612 | [[package]] 613 | name = "holo_hash" 614 | version = "0.0.29" 615 | source = "registry+https://github.com/rust-lang/crates.io-index" 616 | checksum = "dabbace9f53558b8e858701e78733f47c78e4ef5bfb052aebafa323214192de2" 617 | dependencies = [ 618 | "base64", 619 | "blake2b_simd", 620 | "derive_more", 621 | "holochain_serialized_bytes", 622 | "kitsune_p2p_dht_arc", 623 | "serde", 624 | "serde_bytes", 625 | "thiserror", 626 | ] 627 | 628 | [[package]] 629 | name = "holochain_deterministic_integrity" 630 | version = "0.0.10" 631 | source = "registry+https://github.com/rust-lang/crates.io-index" 632 | checksum = "a05da70e7054c69825250c6fde4fd9681def1f00557b79e9ef1968df6c11fcd3" 633 | dependencies = [ 634 | "hdk_derive", 635 | "holo_hash", 636 | "holochain_integrity_types", 637 | "holochain_wasmer_guest", 638 | "paste", 639 | "serde", 640 | "serde_bytes", 641 | "tracing", 642 | "tracing-core", 643 | ] 644 | 645 | [[package]] 646 | name = "holochain_integrity_types" 647 | version = "0.0.9" 648 | source = "registry+https://github.com/rust-lang/crates.io-index" 649 | checksum = "4bd76c76feb26c255f0886da841c69c005ebf0d6f50deba4520b272fcce0c720" 650 | dependencies = [ 651 | "holo_hash", 652 | "holochain_serialized_bytes", 653 | "kitsune_p2p_timestamp", 654 | "serde", 655 | "serde_bytes", 656 | "subtle", 657 | "tracing", 658 | ] 659 | 660 | [[package]] 661 | name = "holochain_serialized_bytes" 662 | version = "0.0.51" 663 | source = "registry+https://github.com/rust-lang/crates.io-index" 664 | checksum = "9805b3e01e7b5c144782a0823db4dc895fec18a9ccd45a492ce7c7bf157a9e38" 665 | dependencies = [ 666 | "holochain_serialized_bytes_derive", 667 | "rmp-serde", 668 | "serde", 669 | "serde-transcode", 670 | "serde_bytes", 671 | "serde_json", 672 | "thiserror", 673 | ] 674 | 675 | [[package]] 676 | name = "holochain_serialized_bytes_derive" 677 | version = "0.0.51" 678 | source = "registry+https://github.com/rust-lang/crates.io-index" 679 | checksum = "1077232d0c427d64feb9e138fa22800e447eafb1810682d6c13beb95333cb32c" 680 | dependencies = [ 681 | "quote", 682 | "syn", 683 | ] 684 | 685 | [[package]] 686 | name = "holochain_wasmer_common" 687 | version = "0.0.80" 688 | source = "registry+https://github.com/rust-lang/crates.io-index" 689 | checksum = "b9475ce2cd820534f537619635d128a79bc973d307a5b1b58f06b0f1282aa613" 690 | dependencies = [ 691 | "holochain_serialized_bytes", 692 | "serde", 693 | "serde_bytes", 694 | "thiserror", 695 | "wasmer", 696 | "wasmer-engine", 697 | ] 698 | 699 | [[package]] 700 | name = "holochain_wasmer_guest" 701 | version = "0.0.80" 702 | source = "registry+https://github.com/rust-lang/crates.io-index" 703 | checksum = "aee0b3eb36f5d6a008898ec7e25ca948cebb3f9b67d201f3a3333a90af4c25a6" 704 | dependencies = [ 705 | "holochain_serialized_bytes", 706 | "holochain_wasmer_common", 707 | "parking_lot", 708 | "serde", 709 | "tracing", 710 | ] 711 | 712 | [[package]] 713 | name = "holochain_zome_types" 714 | version = "0.0.37" 715 | source = "registry+https://github.com/rust-lang/crates.io-index" 716 | checksum = "0ec72c24d61eb81d562aea4db82573976e0c21c0fbf3dba6676b40614ef79fa9" 717 | dependencies = [ 718 | "holo_hash", 719 | "holochain_integrity_types", 720 | "holochain_serialized_bytes", 721 | "holochain_wasmer_common", 722 | "kitsune_p2p_timestamp", 723 | "paste", 724 | "serde", 725 | "serde_bytes", 726 | "subtle", 727 | "thiserror", 728 | "tracing", 729 | ] 730 | 731 | [[package]] 732 | name = "ident_case" 733 | version = "1.0.1" 734 | source = "registry+https://github.com/rust-lang/crates.io-index" 735 | checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" 736 | 737 | [[package]] 738 | name = "indexmap" 739 | version = "1.8.0" 740 | source = "registry+https://github.com/rust-lang/crates.io-index" 741 | checksum = "282a6247722caba404c065016bbfa522806e51714c34f5dfc3e4a3a46fcb4223" 742 | dependencies = [ 743 | "autocfg", 744 | "hashbrown 0.11.2", 745 | "serde", 746 | ] 747 | 748 | [[package]] 749 | name = "instant" 750 | version = "0.1.12" 751 | source = "registry+https://github.com/rust-lang/crates.io-index" 752 | checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" 753 | dependencies = [ 754 | "cfg-if", 755 | ] 756 | 757 | [[package]] 758 | name = "intervallum" 759 | version = "1.4.0" 760 | source = "registry+https://github.com/rust-lang/crates.io-index" 761 | checksum = "c8ccecd834666f695ecec3ff0d5fc32e32c91abea91a28fd0aceb4b35a82cee1" 762 | dependencies = [ 763 | "bit-set", 764 | "gcollections", 765 | "num-integer", 766 | "num-traits", 767 | "trilean", 768 | ] 769 | 770 | [[package]] 771 | name = "itoa" 772 | version = "1.0.1" 773 | source = "registry+https://github.com/rust-lang/crates.io-index" 774 | checksum = "1aab8fc367588b89dcee83ab0fd66b72b50b72fa1904d7095045ace2b0c81c35" 775 | 776 | [[package]] 777 | name = "js-sys" 778 | version = "0.3.56" 779 | source = "registry+https://github.com/rust-lang/crates.io-index" 780 | checksum = "a38fc24e30fd564ce974c02bf1d337caddff65be6cc4735a1f7eab22a7440f04" 781 | dependencies = [ 782 | "wasm-bindgen", 783 | ] 784 | 785 | [[package]] 786 | name = "kitsune_p2p_dht_arc" 787 | version = "0.0.12" 788 | source = "registry+https://github.com/rust-lang/crates.io-index" 789 | checksum = "57c515a2482bf7aaa67e8b2ba478aef332c27369e5a747f4c487a66dbce1e273" 790 | dependencies = [ 791 | "derive_more", 792 | "gcollections", 793 | "intervallum", 794 | "num-traits", 795 | "serde", 796 | ] 797 | 798 | [[package]] 799 | name = "kitsune_p2p_timestamp" 800 | version = "0.0.10" 801 | source = "registry+https://github.com/rust-lang/crates.io-index" 802 | checksum = "3de953329d6030118db10584dd982cfa37bfe8e9c6c14f8387b7973d4c5639b1" 803 | dependencies = [ 804 | "chrono", 805 | "serde", 806 | ] 807 | 808 | [[package]] 809 | name = "lazy_static" 810 | version = "1.4.0" 811 | source = "registry+https://github.com/rust-lang/crates.io-index" 812 | checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" 813 | 814 | [[package]] 815 | name = "leb128" 816 | version = "0.2.5" 817 | source = "registry+https://github.com/rust-lang/crates.io-index" 818 | checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" 819 | 820 | [[package]] 821 | name = "libc" 822 | version = "0.2.121" 823 | source = "registry+https://github.com/rust-lang/crates.io-index" 824 | checksum = "efaa7b300f3b5fe8eb6bf21ce3895e1751d9665086af2d64b42f19701015ff4f" 825 | 826 | [[package]] 827 | name = "libloading" 828 | version = "0.7.3" 829 | source = "registry+https://github.com/rust-lang/crates.io-index" 830 | checksum = "efbc0f03f9a775e9f6aed295c6a1ba2253c5757a9e03d55c6caa46a681abcddd" 831 | dependencies = [ 832 | "cfg-if", 833 | "winapi", 834 | ] 835 | 836 | [[package]] 837 | name = "lock_api" 838 | version = "0.4.6" 839 | source = "registry+https://github.com/rust-lang/crates.io-index" 840 | checksum = "88943dd7ef4a2e5a4bfa2753aaab3013e34ce2533d1996fb18ef591e315e2b3b" 841 | dependencies = [ 842 | "scopeguard", 843 | ] 844 | 845 | [[package]] 846 | name = "log" 847 | version = "0.4.16" 848 | source = "registry+https://github.com/rust-lang/crates.io-index" 849 | checksum = "6389c490849ff5bc16be905ae24bc913a9c8892e19b2341dbc175e14c341c2b8" 850 | dependencies = [ 851 | "cfg-if", 852 | ] 853 | 854 | [[package]] 855 | name = "loupe" 856 | version = "0.1.3" 857 | source = "registry+https://github.com/rust-lang/crates.io-index" 858 | checksum = "9b6a72dfa44fe15b5e76b94307eeb2ff995a8c5b283b55008940c02e0c5b634d" 859 | dependencies = [ 860 | "indexmap", 861 | "loupe-derive", 862 | "rustversion", 863 | ] 864 | 865 | [[package]] 866 | name = "loupe-derive" 867 | version = "0.1.3" 868 | source = "registry+https://github.com/rust-lang/crates.io-index" 869 | checksum = "c0fbfc88337168279f2e9ae06e157cfed4efd3316e14dc96ed074d4f2e6c5952" 870 | dependencies = [ 871 | "quote", 872 | "syn", 873 | ] 874 | 875 | [[package]] 876 | name = "mach" 877 | version = "0.3.2" 878 | source = "registry+https://github.com/rust-lang/crates.io-index" 879 | checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" 880 | dependencies = [ 881 | "libc", 882 | ] 883 | 884 | [[package]] 885 | name = "membrane_manager_integrity" 886 | version = "0.1.0" 887 | dependencies = [ 888 | "hc_iz_membrane_manager", 889 | ] 890 | 891 | [[package]] 892 | name = "membrane_manager_utils" 893 | version = "0.2.0" 894 | source = "git+https://github.com/holochain/hc-zome-lib?branch=v0.0.138#e9e874901499e2ce154b884b315f58c18207d845" 895 | dependencies = [ 896 | "derive_more", 897 | "holochain_deterministic_integrity", 898 | "serde", 899 | ] 900 | 901 | [[package]] 902 | name = "memchr" 903 | version = "2.4.1" 904 | source = "registry+https://github.com/rust-lang/crates.io-index" 905 | checksum = "308cc39be01b73d0d18f82a0e7b2a3df85245f84af96fdddc5d202d27e47b86a" 906 | 907 | [[package]] 908 | name = "memmap2" 909 | version = "0.5.5" 910 | source = "registry+https://github.com/rust-lang/crates.io-index" 911 | checksum = "3a79b39c93a7a5a27eeaf9a23b5ff43f1b9e0ad6b1cdd441140ae53c35613fc7" 912 | dependencies = [ 913 | "libc", 914 | ] 915 | 916 | [[package]] 917 | name = "memoffset" 918 | version = "0.6.5" 919 | source = "registry+https://github.com/rust-lang/crates.io-index" 920 | checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" 921 | dependencies = [ 922 | "autocfg", 923 | ] 924 | 925 | [[package]] 926 | name = "miniz_oxide" 927 | version = "0.4.4" 928 | source = "registry+https://github.com/rust-lang/crates.io-index" 929 | checksum = "a92518e98c078586bc6c934028adcca4c92a53d6a958196de835170a01d84e4b" 930 | dependencies = [ 931 | "adler", 932 | "autocfg", 933 | ] 934 | 935 | [[package]] 936 | name = "more-asserts" 937 | version = "0.2.2" 938 | source = "registry+https://github.com/rust-lang/crates.io-index" 939 | checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" 940 | 941 | [[package]] 942 | name = "num-integer" 943 | version = "0.1.44" 944 | source = "registry+https://github.com/rust-lang/crates.io-index" 945 | checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" 946 | dependencies = [ 947 | "autocfg", 948 | "num-traits", 949 | ] 950 | 951 | [[package]] 952 | name = "num-traits" 953 | version = "0.2.14" 954 | source = "registry+https://github.com/rust-lang/crates.io-index" 955 | checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" 956 | dependencies = [ 957 | "autocfg", 958 | ] 959 | 960 | [[package]] 961 | name = "num_cpus" 962 | version = "1.13.1" 963 | source = "registry+https://github.com/rust-lang/crates.io-index" 964 | checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" 965 | dependencies = [ 966 | "hermit-abi", 967 | "libc", 968 | ] 969 | 970 | [[package]] 971 | name = "object" 972 | version = "0.27.1" 973 | source = "registry+https://github.com/rust-lang/crates.io-index" 974 | checksum = "67ac1d3f9a1d3616fd9a60c8d74296f22406a238b6a72f5cc1e6f314df4ffbf9" 975 | dependencies = [ 976 | "memchr", 977 | ] 978 | 979 | [[package]] 980 | name = "object" 981 | version = "0.28.4" 982 | source = "registry+https://github.com/rust-lang/crates.io-index" 983 | checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" 984 | dependencies = [ 985 | "crc32fast", 986 | "hashbrown 0.11.2", 987 | "indexmap", 988 | "memchr", 989 | ] 990 | 991 | [[package]] 992 | name = "once_cell" 993 | version = "1.10.0" 994 | source = "registry+https://github.com/rust-lang/crates.io-index" 995 | checksum = "87f3e037eac156d1775da914196f0f37741a274155e34a0b7e427c35d2a2ecb9" 996 | 997 | [[package]] 998 | name = "parking_lot" 999 | version = "0.11.2" 1000 | source = "registry+https://github.com/rust-lang/crates.io-index" 1001 | checksum = "7d17b78036a60663b797adeaee46f5c9dfebb86948d1255007a1d6be0271ff99" 1002 | dependencies = [ 1003 | "instant", 1004 | "lock_api", 1005 | "parking_lot_core", 1006 | ] 1007 | 1008 | [[package]] 1009 | name = "parking_lot_core" 1010 | version = "0.8.5" 1011 | source = "registry+https://github.com/rust-lang/crates.io-index" 1012 | checksum = "d76e8e1493bcac0d2766c42737f34458f1c8c50c0d23bcb24ea953affb273216" 1013 | dependencies = [ 1014 | "cfg-if", 1015 | "instant", 1016 | "libc", 1017 | "redox_syscall", 1018 | "smallvec", 1019 | "winapi", 1020 | ] 1021 | 1022 | [[package]] 1023 | name = "paste" 1024 | version = "1.0.5" 1025 | source = "registry+https://github.com/rust-lang/crates.io-index" 1026 | checksum = "acbf547ad0c65e31259204bd90935776d1c693cec2f4ff7abb7a1bbbd40dfe58" 1027 | 1028 | [[package]] 1029 | name = "pin-project-lite" 1030 | version = "0.2.8" 1031 | source = "registry+https://github.com/rust-lang/crates.io-index" 1032 | checksum = "e280fbe77cc62c91527259e9442153f4688736748d24660126286329742b4c6c" 1033 | 1034 | [[package]] 1035 | name = "proc-macro-error" 1036 | version = "1.0.4" 1037 | source = "registry+https://github.com/rust-lang/crates.io-index" 1038 | checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" 1039 | dependencies = [ 1040 | "proc-macro-error-attr", 1041 | "proc-macro2", 1042 | "quote", 1043 | "syn", 1044 | "version_check", 1045 | ] 1046 | 1047 | [[package]] 1048 | name = "proc-macro-error-attr" 1049 | version = "1.0.4" 1050 | source = "registry+https://github.com/rust-lang/crates.io-index" 1051 | checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" 1052 | dependencies = [ 1053 | "proc-macro2", 1054 | "quote", 1055 | "version_check", 1056 | ] 1057 | 1058 | [[package]] 1059 | name = "proc-macro2" 1060 | version = "1.0.40" 1061 | source = "registry+https://github.com/rust-lang/crates.io-index" 1062 | checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" 1063 | dependencies = [ 1064 | "unicode-ident", 1065 | ] 1066 | 1067 | [[package]] 1068 | name = "profile" 1069 | version = "0.1.0" 1070 | dependencies = [ 1071 | "hc_cz_profile", 1072 | ] 1073 | 1074 | [[package]] 1075 | name = "profile_integrity" 1076 | version = "0.1.0" 1077 | dependencies = [ 1078 | "hc_iz_profile", 1079 | ] 1080 | 1081 | [[package]] 1082 | name = "ptr_meta" 1083 | version = "0.1.4" 1084 | source = "registry+https://github.com/rust-lang/crates.io-index" 1085 | checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" 1086 | dependencies = [ 1087 | "ptr_meta_derive", 1088 | ] 1089 | 1090 | [[package]] 1091 | name = "ptr_meta_derive" 1092 | version = "0.1.4" 1093 | source = "registry+https://github.com/rust-lang/crates.io-index" 1094 | checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" 1095 | dependencies = [ 1096 | "proc-macro2", 1097 | "quote", 1098 | "syn", 1099 | ] 1100 | 1101 | [[package]] 1102 | name = "quote" 1103 | version = "1.0.20" 1104 | source = "registry+https://github.com/rust-lang/crates.io-index" 1105 | checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" 1106 | dependencies = [ 1107 | "proc-macro2", 1108 | ] 1109 | 1110 | [[package]] 1111 | name = "rayon" 1112 | version = "1.5.1" 1113 | source = "registry+https://github.com/rust-lang/crates.io-index" 1114 | checksum = "c06aca804d41dbc8ba42dfd964f0d01334eceb64314b9ecf7c5fad5188a06d90" 1115 | dependencies = [ 1116 | "autocfg", 1117 | "crossbeam-deque", 1118 | "either", 1119 | "rayon-core", 1120 | ] 1121 | 1122 | [[package]] 1123 | name = "rayon-core" 1124 | version = "1.9.1" 1125 | source = "registry+https://github.com/rust-lang/crates.io-index" 1126 | checksum = "d78120e2c850279833f1dd3582f730c4ab53ed95aeaaaa862a2a5c71b1656d8e" 1127 | dependencies = [ 1128 | "crossbeam-channel", 1129 | "crossbeam-deque", 1130 | "crossbeam-utils", 1131 | "lazy_static", 1132 | "num_cpus", 1133 | ] 1134 | 1135 | [[package]] 1136 | name = "redox_syscall" 1137 | version = "0.2.12" 1138 | source = "registry+https://github.com/rust-lang/crates.io-index" 1139 | checksum = "8ae183fc1b06c149f0c1793e1eb447c8b04bfe46d48e9e48bfb8d2d7ed64ecf0" 1140 | dependencies = [ 1141 | "bitflags", 1142 | ] 1143 | 1144 | [[package]] 1145 | name = "regalloc" 1146 | version = "0.0.31" 1147 | source = "registry+https://github.com/rust-lang/crates.io-index" 1148 | checksum = "571f7f397d61c4755285cd37853fe8e03271c243424a907415909379659381c5" 1149 | dependencies = [ 1150 | "log", 1151 | "rustc-hash", 1152 | "smallvec", 1153 | ] 1154 | 1155 | [[package]] 1156 | name = "region" 1157 | version = "3.0.0" 1158 | source = "registry+https://github.com/rust-lang/crates.io-index" 1159 | checksum = "76e189c2369884dce920945e2ddf79b3dff49e071a167dd1817fa9c4c00d512e" 1160 | dependencies = [ 1161 | "bitflags", 1162 | "libc", 1163 | "mach", 1164 | "winapi", 1165 | ] 1166 | 1167 | [[package]] 1168 | name = "remove_dir_all" 1169 | version = "0.5.3" 1170 | source = "registry+https://github.com/rust-lang/crates.io-index" 1171 | checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" 1172 | dependencies = [ 1173 | "winapi", 1174 | ] 1175 | 1176 | [[package]] 1177 | name = "rend" 1178 | version = "0.3.6" 1179 | source = "registry+https://github.com/rust-lang/crates.io-index" 1180 | checksum = "79af64b4b6362ffba04eef3a4e10829718a4896dac19daa741851c86781edf95" 1181 | dependencies = [ 1182 | "bytecheck", 1183 | ] 1184 | 1185 | [[package]] 1186 | name = "rkyv" 1187 | version = "0.7.39" 1188 | source = "registry+https://github.com/rust-lang/crates.io-index" 1189 | checksum = "cec2b3485b07d96ddfd3134767b8a447b45ea4eb91448d0a35180ec0ffd5ed15" 1190 | dependencies = [ 1191 | "bytecheck", 1192 | "hashbrown 0.12.3", 1193 | "ptr_meta", 1194 | "rend", 1195 | "rkyv_derive", 1196 | "seahash", 1197 | ] 1198 | 1199 | [[package]] 1200 | name = "rkyv_derive" 1201 | version = "0.7.39" 1202 | source = "registry+https://github.com/rust-lang/crates.io-index" 1203 | checksum = "6eaedadc88b53e36dd32d940ed21ae4d850d5916f2581526921f553a72ac34c4" 1204 | dependencies = [ 1205 | "proc-macro2", 1206 | "quote", 1207 | "syn", 1208 | ] 1209 | 1210 | [[package]] 1211 | name = "rmp" 1212 | version = "0.8.10" 1213 | source = "registry+https://github.com/rust-lang/crates.io-index" 1214 | checksum = "4f55e5fa1446c4d5dd1f5daeed2a4fe193071771a2636274d0d7a3b082aa7ad6" 1215 | dependencies = [ 1216 | "byteorder", 1217 | "num-traits", 1218 | ] 1219 | 1220 | [[package]] 1221 | name = "rmp-serde" 1222 | version = "0.15.5" 1223 | source = "registry+https://github.com/rust-lang/crates.io-index" 1224 | checksum = "723ecff9ad04f4ad92fe1c8ca6c20d2196d9286e9c60727c4cb5511629260e9d" 1225 | dependencies = [ 1226 | "byteorder", 1227 | "rmp", 1228 | "serde", 1229 | ] 1230 | 1231 | [[package]] 1232 | name = "rustc-demangle" 1233 | version = "0.1.21" 1234 | source = "registry+https://github.com/rust-lang/crates.io-index" 1235 | checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" 1236 | 1237 | [[package]] 1238 | name = "rustc-hash" 1239 | version = "1.1.0" 1240 | source = "registry+https://github.com/rust-lang/crates.io-index" 1241 | checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" 1242 | 1243 | [[package]] 1244 | name = "rustc_version" 1245 | version = "0.4.0" 1246 | source = "registry+https://github.com/rust-lang/crates.io-index" 1247 | checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" 1248 | dependencies = [ 1249 | "semver", 1250 | ] 1251 | 1252 | [[package]] 1253 | name = "rustversion" 1254 | version = "1.0.6" 1255 | source = "registry+https://github.com/rust-lang/crates.io-index" 1256 | checksum = "f2cc38e8fa666e2de3c4aba7edeb5ffc5246c1c2ed0e3d17e560aeeba736b23f" 1257 | 1258 | [[package]] 1259 | name = "ryu" 1260 | version = "1.0.9" 1261 | source = "registry+https://github.com/rust-lang/crates.io-index" 1262 | checksum = "73b4b750c782965c211b42f022f59af1fbceabdd026623714f104152f1ec149f" 1263 | 1264 | [[package]] 1265 | name = "scopeguard" 1266 | version = "1.1.0" 1267 | source = "registry+https://github.com/rust-lang/crates.io-index" 1268 | checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" 1269 | 1270 | [[package]] 1271 | name = "seahash" 1272 | version = "4.1.0" 1273 | source = "registry+https://github.com/rust-lang/crates.io-index" 1274 | checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" 1275 | 1276 | [[package]] 1277 | name = "semver" 1278 | version = "1.0.7" 1279 | source = "registry+https://github.com/rust-lang/crates.io-index" 1280 | checksum = "d65bd28f48be7196d222d95b9243287f48d27aca604e08497513019ff0502cc4" 1281 | 1282 | [[package]] 1283 | name = "serde" 1284 | version = "1.0.139" 1285 | source = "registry+https://github.com/rust-lang/crates.io-index" 1286 | checksum = "0171ebb889e45aa68b44aee0859b3eede84c6f5f5c228e6f140c0b2a0a46cad6" 1287 | dependencies = [ 1288 | "serde_derive", 1289 | ] 1290 | 1291 | [[package]] 1292 | name = "serde-transcode" 1293 | version = "1.1.1" 1294 | source = "registry+https://github.com/rust-lang/crates.io-index" 1295 | checksum = "590c0e25c2a5bb6e85bf5c1bce768ceb86b316e7a01bdf07d2cb4ec2271990e2" 1296 | dependencies = [ 1297 | "serde", 1298 | ] 1299 | 1300 | [[package]] 1301 | name = "serde_bytes" 1302 | version = "0.11.5" 1303 | source = "registry+https://github.com/rust-lang/crates.io-index" 1304 | checksum = "16ae07dd2f88a366f15bd0632ba725227018c69a1c8550a927324f8eb8368bb9" 1305 | dependencies = [ 1306 | "serde", 1307 | ] 1308 | 1309 | [[package]] 1310 | name = "serde_derive" 1311 | version = "1.0.139" 1312 | source = "registry+https://github.com/rust-lang/crates.io-index" 1313 | checksum = "dc1d3230c1de7932af58ad8ffbe1d784bd55efd5a9d84ac24f69c72d83543dfb" 1314 | dependencies = [ 1315 | "proc-macro2", 1316 | "quote", 1317 | "syn", 1318 | ] 1319 | 1320 | [[package]] 1321 | name = "serde_json" 1322 | version = "1.0.79" 1323 | source = "registry+https://github.com/rust-lang/crates.io-index" 1324 | checksum = "8e8d9fa5c3b304765ce1fd9c4c8a3de2c8db365a5b91be52f186efc675681d95" 1325 | dependencies = [ 1326 | "indexmap", 1327 | "itoa", 1328 | "ryu", 1329 | "serde", 1330 | ] 1331 | 1332 | [[package]] 1333 | name = "smallvec" 1334 | version = "1.8.0" 1335 | source = "registry+https://github.com/rust-lang/crates.io-index" 1336 | checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" 1337 | 1338 | [[package]] 1339 | name = "stable_deref_trait" 1340 | version = "1.2.0" 1341 | source = "registry+https://github.com/rust-lang/crates.io-index" 1342 | checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" 1343 | 1344 | [[package]] 1345 | name = "strsim" 1346 | version = "0.10.0" 1347 | source = "registry+https://github.com/rust-lang/crates.io-index" 1348 | checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" 1349 | 1350 | [[package]] 1351 | name = "subtle" 1352 | version = "2.4.1" 1353 | source = "registry+https://github.com/rust-lang/crates.io-index" 1354 | checksum = "6bdef32e8150c2a081110b42772ffe7d7c9032b606bc226c8260fd97e0976601" 1355 | 1356 | [[package]] 1357 | name = "syn" 1358 | version = "1.0.98" 1359 | source = "registry+https://github.com/rust-lang/crates.io-index" 1360 | checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" 1361 | dependencies = [ 1362 | "proc-macro2", 1363 | "quote", 1364 | "unicode-ident", 1365 | ] 1366 | 1367 | [[package]] 1368 | name = "target-lexicon" 1369 | version = "0.12.3" 1370 | source = "registry+https://github.com/rust-lang/crates.io-index" 1371 | checksum = "d7fa7e55043acb85fca6b3c01485a2eeb6b69c5d21002e273c79e465f43b7ac1" 1372 | 1373 | [[package]] 1374 | name = "tempfile" 1375 | version = "3.3.0" 1376 | source = "registry+https://github.com/rust-lang/crates.io-index" 1377 | checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" 1378 | dependencies = [ 1379 | "cfg-if", 1380 | "fastrand", 1381 | "libc", 1382 | "redox_syscall", 1383 | "remove_dir_all", 1384 | "winapi", 1385 | ] 1386 | 1387 | [[package]] 1388 | name = "thiserror" 1389 | version = "1.0.30" 1390 | source = "registry+https://github.com/rust-lang/crates.io-index" 1391 | checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" 1392 | dependencies = [ 1393 | "thiserror-impl", 1394 | ] 1395 | 1396 | [[package]] 1397 | name = "thiserror-impl" 1398 | version = "1.0.30" 1399 | source = "registry+https://github.com/rust-lang/crates.io-index" 1400 | checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" 1401 | dependencies = [ 1402 | "proc-macro2", 1403 | "quote", 1404 | "syn", 1405 | ] 1406 | 1407 | [[package]] 1408 | name = "time" 1409 | version = "0.1.44" 1410 | source = "registry+https://github.com/rust-lang/crates.io-index" 1411 | checksum = "6db9e6914ab8b1ae1c260a4ae7a49b6c5611b40328a735b21862567685e73255" 1412 | dependencies = [ 1413 | "libc", 1414 | "wasi 0.10.0+wasi-snapshot-preview1", 1415 | "winapi", 1416 | ] 1417 | 1418 | [[package]] 1419 | name = "tracing" 1420 | version = "0.1.32" 1421 | source = "registry+https://github.com/rust-lang/crates.io-index" 1422 | checksum = "4a1bdf54a7c28a2bbf701e1d2233f6c77f473486b94bee4f9678da5a148dca7f" 1423 | dependencies = [ 1424 | "cfg-if", 1425 | "log", 1426 | "pin-project-lite", 1427 | "tracing-attributes", 1428 | "tracing-core", 1429 | ] 1430 | 1431 | [[package]] 1432 | name = "tracing-attributes" 1433 | version = "0.1.20" 1434 | source = "registry+https://github.com/rust-lang/crates.io-index" 1435 | checksum = "2e65ce065b4b5c53e73bb28912318cb8c9e9ad3921f1d669eb0e68b4c8143a2b" 1436 | dependencies = [ 1437 | "proc-macro2", 1438 | "quote", 1439 | "syn", 1440 | ] 1441 | 1442 | [[package]] 1443 | name = "tracing-core" 1444 | version = "0.1.23" 1445 | source = "registry+https://github.com/rust-lang/crates.io-index" 1446 | checksum = "aa31669fa42c09c34d94d8165dd2012e8ff3c66aca50f3bb226b68f216f2706c" 1447 | dependencies = [ 1448 | "lazy_static", 1449 | "valuable", 1450 | ] 1451 | 1452 | [[package]] 1453 | name = "trilean" 1454 | version = "1.1.0" 1455 | source = "registry+https://github.com/rust-lang/crates.io-index" 1456 | checksum = "683ba5022fe6dbd7133cad150478ccf51bdb6d861515181e5fc6b4323d4fa424" 1457 | 1458 | [[package]] 1459 | name = "unicode-ident" 1460 | version = "1.0.2" 1461 | source = "registry+https://github.com/rust-lang/crates.io-index" 1462 | checksum = "15c61ba63f9235225a22310255a29b806b907c9b8c964bcbd0a2c70f3f2deea7" 1463 | 1464 | [[package]] 1465 | name = "unicode-width" 1466 | version = "0.1.9" 1467 | source = "registry+https://github.com/rust-lang/crates.io-index" 1468 | checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" 1469 | 1470 | [[package]] 1471 | name = "uuid" 1472 | version = "0.8.2" 1473 | source = "registry+https://github.com/rust-lang/crates.io-index" 1474 | checksum = "bc5cf98d8186244414c848017f0e2676b3fcb46807f6668a97dfe67359a3c4b7" 1475 | 1476 | [[package]] 1477 | name = "valuable" 1478 | version = "0.1.0" 1479 | source = "registry+https://github.com/rust-lang/crates.io-index" 1480 | checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d" 1481 | 1482 | [[package]] 1483 | name = "version_check" 1484 | version = "0.9.4" 1485 | source = "registry+https://github.com/rust-lang/crates.io-index" 1486 | checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" 1487 | 1488 | [[package]] 1489 | name = "wasi" 1490 | version = "0.10.0+wasi-snapshot-preview1" 1491 | source = "registry+https://github.com/rust-lang/crates.io-index" 1492 | checksum = "1a143597ca7c7793eff794def352d41792a93c481eb1042423ff7ff72ba2c31f" 1493 | 1494 | [[package]] 1495 | name = "wasi" 1496 | version = "0.11.0+wasi-snapshot-preview1" 1497 | source = "registry+https://github.com/rust-lang/crates.io-index" 1498 | checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" 1499 | 1500 | [[package]] 1501 | name = "wasm-bindgen" 1502 | version = "0.2.79" 1503 | source = "registry+https://github.com/rust-lang/crates.io-index" 1504 | checksum = "25f1af7423d8588a3d840681122e72e6a24ddbcb3f0ec385cac0d12d24256c06" 1505 | dependencies = [ 1506 | "cfg-if", 1507 | "wasm-bindgen-macro", 1508 | ] 1509 | 1510 | [[package]] 1511 | name = "wasm-bindgen-backend" 1512 | version = "0.2.79" 1513 | source = "registry+https://github.com/rust-lang/crates.io-index" 1514 | checksum = "8b21c0df030f5a177f3cba22e9bc4322695ec43e7257d865302900290bcdedca" 1515 | dependencies = [ 1516 | "bumpalo", 1517 | "lazy_static", 1518 | "log", 1519 | "proc-macro2", 1520 | "quote", 1521 | "syn", 1522 | "wasm-bindgen-shared", 1523 | ] 1524 | 1525 | [[package]] 1526 | name = "wasm-bindgen-macro" 1527 | version = "0.2.79" 1528 | source = "registry+https://github.com/rust-lang/crates.io-index" 1529 | checksum = "2f4203d69e40a52ee523b2529a773d5ffc1dc0071801c87b3d270b471b80ed01" 1530 | dependencies = [ 1531 | "quote", 1532 | "wasm-bindgen-macro-support", 1533 | ] 1534 | 1535 | [[package]] 1536 | name = "wasm-bindgen-macro-support" 1537 | version = "0.2.79" 1538 | source = "registry+https://github.com/rust-lang/crates.io-index" 1539 | checksum = "bfa8a30d46208db204854cadbb5d4baf5fcf8071ba5bf48190c3e59937962ebc" 1540 | dependencies = [ 1541 | "proc-macro2", 1542 | "quote", 1543 | "syn", 1544 | "wasm-bindgen-backend", 1545 | "wasm-bindgen-shared", 1546 | ] 1547 | 1548 | [[package]] 1549 | name = "wasm-bindgen-shared" 1550 | version = "0.2.79" 1551 | source = "registry+https://github.com/rust-lang/crates.io-index" 1552 | checksum = "3d958d035c4438e28c70e4321a2911302f10135ce78a9c7834c0cab4123d06a2" 1553 | 1554 | [[package]] 1555 | name = "wasmer" 1556 | version = "2.2.0" 1557 | source = "registry+https://github.com/rust-lang/crates.io-index" 1558 | checksum = "bfc7dff846db3f38f8ed0be4a009fdfeb729cf1f94a2c7fb6ff2fec01cefa110" 1559 | dependencies = [ 1560 | "cfg-if", 1561 | "indexmap", 1562 | "js-sys", 1563 | "loupe", 1564 | "more-asserts", 1565 | "target-lexicon", 1566 | "thiserror", 1567 | "wasm-bindgen", 1568 | "wasmer-compiler", 1569 | "wasmer-compiler-cranelift", 1570 | "wasmer-derive", 1571 | "wasmer-engine", 1572 | "wasmer-engine-dylib", 1573 | "wasmer-engine-universal", 1574 | "wasmer-types", 1575 | "wasmer-vm", 1576 | "wat", 1577 | "winapi", 1578 | ] 1579 | 1580 | [[package]] 1581 | name = "wasmer-compiler" 1582 | version = "2.2.0" 1583 | source = "registry+https://github.com/rust-lang/crates.io-index" 1584 | checksum = "8c91abf22b16dad3826ec0d0e3ec0a8304262a6c7a14e16528c536131b80e63d" 1585 | dependencies = [ 1586 | "enumset", 1587 | "loupe", 1588 | "rkyv", 1589 | "serde", 1590 | "serde_bytes", 1591 | "smallvec", 1592 | "target-lexicon", 1593 | "thiserror", 1594 | "wasmer-types", 1595 | "wasmer-vm", 1596 | "wasmparser", 1597 | ] 1598 | 1599 | [[package]] 1600 | name = "wasmer-compiler-cranelift" 1601 | version = "2.2.0" 1602 | source = "registry+https://github.com/rust-lang/crates.io-index" 1603 | checksum = "7624a1f496b163139a7e0b442426cad805bec70486900287506f9d15a29323ab" 1604 | dependencies = [ 1605 | "cranelift-codegen", 1606 | "cranelift-entity", 1607 | "cranelift-frontend", 1608 | "gimli 0.25.0", 1609 | "loupe", 1610 | "more-asserts", 1611 | "rayon", 1612 | "smallvec", 1613 | "target-lexicon", 1614 | "tracing", 1615 | "wasmer-compiler", 1616 | "wasmer-types", 1617 | "wasmer-vm", 1618 | ] 1619 | 1620 | [[package]] 1621 | name = "wasmer-derive" 1622 | version = "2.2.0" 1623 | source = "registry+https://github.com/rust-lang/crates.io-index" 1624 | checksum = "933b23b5cee0f58aa6c17c6de7e1f3007279357e0d555f22e24d6b395cfe7f89" 1625 | dependencies = [ 1626 | "proc-macro-error", 1627 | "proc-macro2", 1628 | "quote", 1629 | "syn", 1630 | ] 1631 | 1632 | [[package]] 1633 | name = "wasmer-engine" 1634 | version = "2.2.0" 1635 | source = "registry+https://github.com/rust-lang/crates.io-index" 1636 | checksum = "41db0ac4df90610cda8320cfd5abf90c6ec90e298b6fe5a09a81dff718b55640" 1637 | dependencies = [ 1638 | "backtrace", 1639 | "enumset", 1640 | "lazy_static", 1641 | "loupe", 1642 | "memmap2", 1643 | "more-asserts", 1644 | "rustc-demangle", 1645 | "serde", 1646 | "serde_bytes", 1647 | "target-lexicon", 1648 | "thiserror", 1649 | "wasmer-compiler", 1650 | "wasmer-types", 1651 | "wasmer-vm", 1652 | ] 1653 | 1654 | [[package]] 1655 | name = "wasmer-engine-dylib" 1656 | version = "2.2.0" 1657 | source = "registry+https://github.com/rust-lang/crates.io-index" 1658 | checksum = "591683f3356ac31cc88aaecaf77ac2cc9f456014348b01af46c164f44f531162" 1659 | dependencies = [ 1660 | "cfg-if", 1661 | "enum-iterator", 1662 | "enumset", 1663 | "leb128", 1664 | "libloading", 1665 | "loupe", 1666 | "object 0.28.4", 1667 | "rkyv", 1668 | "serde", 1669 | "tempfile", 1670 | "tracing", 1671 | "wasmer-compiler", 1672 | "wasmer-engine", 1673 | "wasmer-object", 1674 | "wasmer-types", 1675 | "wasmer-vm", 1676 | "which", 1677 | ] 1678 | 1679 | [[package]] 1680 | name = "wasmer-engine-universal" 1681 | version = "2.2.0" 1682 | source = "registry+https://github.com/rust-lang/crates.io-index" 1683 | checksum = "dccfde103e9b87427099a6de344b7c791574f307d035c8c7dbbc00974c1af0c1" 1684 | dependencies = [ 1685 | "cfg-if", 1686 | "enum-iterator", 1687 | "enumset", 1688 | "leb128", 1689 | "loupe", 1690 | "region", 1691 | "rkyv", 1692 | "wasmer-compiler", 1693 | "wasmer-engine", 1694 | "wasmer-types", 1695 | "wasmer-vm", 1696 | "winapi", 1697 | ] 1698 | 1699 | [[package]] 1700 | name = "wasmer-object" 1701 | version = "2.2.0" 1702 | source = "registry+https://github.com/rust-lang/crates.io-index" 1703 | checksum = "1d0c4005592998bd840f2289102ef9c67b6138338ed78e1fc0809586aa229040" 1704 | dependencies = [ 1705 | "object 0.28.4", 1706 | "thiserror", 1707 | "wasmer-compiler", 1708 | "wasmer-types", 1709 | ] 1710 | 1711 | [[package]] 1712 | name = "wasmer-types" 1713 | version = "2.2.0" 1714 | source = "registry+https://github.com/rust-lang/crates.io-index" 1715 | checksum = "4deb854f178265a76b59823c41547d259c65da3687b606b0b9c12d80ab950e3e" 1716 | dependencies = [ 1717 | "indexmap", 1718 | "loupe", 1719 | "rkyv", 1720 | "serde", 1721 | "thiserror", 1722 | ] 1723 | 1724 | [[package]] 1725 | name = "wasmer-vm" 1726 | version = "2.2.0" 1727 | source = "registry+https://github.com/rust-lang/crates.io-index" 1728 | checksum = "5dbc5c989cb14a102433927e630473da52f83d82c469acd5cfa8fc7efacc1e70" 1729 | dependencies = [ 1730 | "backtrace", 1731 | "cc", 1732 | "cfg-if", 1733 | "enum-iterator", 1734 | "indexmap", 1735 | "libc", 1736 | "loupe", 1737 | "memoffset", 1738 | "more-asserts", 1739 | "region", 1740 | "rkyv", 1741 | "serde", 1742 | "thiserror", 1743 | "wasmer-types", 1744 | "winapi", 1745 | ] 1746 | 1747 | [[package]] 1748 | name = "wasmparser" 1749 | version = "0.78.2" 1750 | source = "registry+https://github.com/rust-lang/crates.io-index" 1751 | checksum = "52144d4c78e5cf8b055ceab8e5fa22814ce4315d6002ad32cfd914f37c12fd65" 1752 | 1753 | [[package]] 1754 | name = "wast" 1755 | version = "39.0.0" 1756 | source = "registry+https://github.com/rust-lang/crates.io-index" 1757 | checksum = "e9bbbd53432b267421186feee3e52436531fa69a7cfee9403f5204352df3dd05" 1758 | dependencies = [ 1759 | "leb128", 1760 | "memchr", 1761 | "unicode-width", 1762 | ] 1763 | 1764 | [[package]] 1765 | name = "wat" 1766 | version = "1.0.41" 1767 | source = "registry+https://github.com/rust-lang/crates.io-index" 1768 | checksum = "ab98ed25494f97c69f28758617f27c3e92e5336040b5c3a14634f2dd3fe61830" 1769 | dependencies = [ 1770 | "wast", 1771 | ] 1772 | 1773 | [[package]] 1774 | name = "which" 1775 | version = "4.2.5" 1776 | source = "registry+https://github.com/rust-lang/crates.io-index" 1777 | checksum = "5c4fb54e6113b6a8772ee41c3404fb0301ac79604489467e0a9ce1f3e97c24ae" 1778 | dependencies = [ 1779 | "either", 1780 | "lazy_static", 1781 | "libc", 1782 | ] 1783 | 1784 | [[package]] 1785 | name = "winapi" 1786 | version = "0.3.9" 1787 | source = "registry+https://github.com/rust-lang/crates.io-index" 1788 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 1789 | dependencies = [ 1790 | "winapi-i686-pc-windows-gnu", 1791 | "winapi-x86_64-pc-windows-gnu", 1792 | ] 1793 | 1794 | [[package]] 1795 | name = "winapi-i686-pc-windows-gnu" 1796 | version = "0.4.0" 1797 | source = "registry+https://github.com/rust-lang/crates.io-index" 1798 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 1799 | 1800 | [[package]] 1801 | name = "winapi-x86_64-pc-windows-gnu" 1802 | version = "0.4.0" 1803 | source = "registry+https://github.com/rust-lang/crates.io-index" 1804 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 1805 | --------------------------------------------------------------------------------