From 218938e1ba38bdbbdc61c22313a3ec7e1a09340b Mon Sep 17 00:00:00 2001 From: 8e8b2c <138928994+8e8b2c@users.noreply.github.com> Date: Thu, 6 Jun 2024 15:13:27 +0100 Subject: [PATCH] feat: oracle + jq (#6) * chore: tidy up * feat: jq aggregation; also: - rationalise test containers - tidy integrity validation function * chore: update e2e --- .github/workflows/e2e.yml | 5 + .vscode/settings.json | 1 + Cargo.lock | 110 +++ Cargo.toml | 7 + crates/holoom_dna_tests/Cargo.toml | 1 + .../src/tests/username_registry/mod.rs | 1 + .../src/tests/username_registry/oracle.rs | 161 ++++ crates/holoom_types/src/external_id.rs | 40 + crates/holoom_types/src/lib.rs | 107 +-- crates/holoom_types/src/metadata.rs | 21 + crates/holoom_types/src/oracle.rs | 52 ++ crates/holoom_types/src/username.rs | 16 + crates/holoom_types/src/wallet.rs | 27 + crates/jaq_wrapper/Cargo.toml | 16 + crates/jaq_wrapper/src/lib.rs | 125 +++ .../username_registry_coordinator/Cargo.toml | 3 + .../src/jq_execution.rs | 45 + .../username_registry_coordinator/src/lib.rs | 3 + .../src/oracle_document.rs | 69 ++ .../src/oracle_document_list_snapshot.rs | 85 ++ .../src/entry_types.rs | 57 ++ crates/username_registry_integrity/src/lib.rs | 496 +---------- .../src/link_types.rs | 129 +++ crates/username_registry_utils/Cargo.toml | 18 + crates/username_registry_utils/src/lib.rs | 24 + .../username_registry_validation/Cargo.toml | 3 + .../src/external_id_attestation.rs | 20 - .../src/jq_execution.rs | 43 + .../username_registry_validation/src/lib.rs | 10 + .../src/name_oracle_document.rs | 41 + .../src/oracle_document.rs | 9 + .../src/oracle_document_list_snapshot.rs | 65 ++ .../src/relate_oracle_document_name.rs | 24 + .../src/username_attestation.rs | 20 - .../src/wallet_attestation.rs | 20 - docker/mock-oracle/Dockerfile | 14 + package-lock.json | 773 +++++++++++++++++- package.json | 3 +- packages/client/src/holoom-client.ts | 14 + packages/client/src/types.ts | 6 + packages/e2e/src/main.ts | 4 +- packages/e2e/tests/evm-wallet-binding.test.js | 4 +- packages/e2e/tests/external-id.test.js | 4 +- packages/e2e/tests/metadata.test.js | 4 +- packages/e2e/tests/oracle.test.js | 77 ++ packages/e2e/tests/username.test.js | 4 +- packages/e2e/tests/utils/oracle.js | 7 + packages/e2e/tests/utils/testcontainers.js | 150 ++-- packages/external-id-attestor/src/index.ts | 3 +- packages/mock-oracle/package.json | 23 + packages/mock-oracle/src/index.ts | 104 +++ packages/mock-oracle/tsconfig.json | 109 +++ scripts/build_docker_images.sh | 4 + 53 files changed, 2482 insertions(+), 699 deletions(-) create mode 100644 crates/holoom_dna_tests/src/tests/username_registry/oracle.rs create mode 100644 crates/holoom_types/src/external_id.rs create mode 100644 crates/holoom_types/src/metadata.rs create mode 100644 crates/holoom_types/src/oracle.rs create mode 100644 crates/holoom_types/src/username.rs create mode 100644 crates/holoom_types/src/wallet.rs create mode 100644 crates/jaq_wrapper/Cargo.toml create mode 100644 crates/jaq_wrapper/src/lib.rs create mode 100644 crates/username_registry_coordinator/src/jq_execution.rs create mode 100644 crates/username_registry_coordinator/src/oracle_document.rs create mode 100644 crates/username_registry_coordinator/src/oracle_document_list_snapshot.rs create mode 100644 crates/username_registry_integrity/src/entry_types.rs create mode 100644 crates/username_registry_integrity/src/link_types.rs create mode 100644 crates/username_registry_utils/Cargo.toml create mode 100644 crates/username_registry_utils/src/lib.rs create mode 100644 crates/username_registry_validation/src/jq_execution.rs create mode 100644 crates/username_registry_validation/src/name_oracle_document.rs create mode 100644 crates/username_registry_validation/src/oracle_document.rs create mode 100644 crates/username_registry_validation/src/oracle_document_list_snapshot.rs create mode 100644 crates/username_registry_validation/src/relate_oracle_document_name.rs create mode 100644 docker/mock-oracle/Dockerfile create mode 100644 packages/e2e/tests/oracle.test.js create mode 100644 packages/e2e/tests/utils/oracle.js create mode 100644 packages/mock-oracle/package.json create mode 100644 packages/mock-oracle/src/index.ts create mode 100644 packages/mock-oracle/tsconfig.json diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 54f0f5a..899f1c6 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -139,6 +139,7 @@ jobs: - run: npx puppeteer browsers install chrome - run: npm run build:client - run: npm run build:external-id-attestor + - run: npm run build:mock-oracle - name: Save client build for possible publish uses: actions/upload-artifact@v4 if: always() @@ -156,6 +157,10 @@ jobs: -t holoom/external-id-attestor \ -f docker/external-id-attestor/Dockerfile \ packages/external-id-attestor + docker build \ + -t holoom/mock-oracle \ + -f docker/mock-oracle/Dockerfile \ + packages/mock-oracle docker build -t holoom/rocket docker/rocket - name: Start frontend in background run: npm run dev -w @holoom/e2e & diff --git a/.vscode/settings.json b/.vscode/settings.json index 0d9128f..9f84365 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,5 @@ { + "cSpell.language": "en,en-GB", "cSpell.words": [ "Dalek", "Faceit", diff --git a/Cargo.lock b/Cargo.lock index 7f3504f..4840a3c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -776,6 +776,12 @@ version = "0.21.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" +[[package]] +name = "base64" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" + [[package]] name = "base64ct" version = "1.6.0" @@ -1136,6 +1142,15 @@ dependencies = [ "windows-targets 0.52.4", ] +[[package]] +name = "chumsky" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eebd66744a15ded14960ab4ccdbfb51ad3b81f51f3f04a80adac98c985396c9" +dependencies = [ + "hashbrown 0.14.3", +] + [[package]] name = "cipher" version = "0.3.0" @@ -3259,6 +3274,12 @@ version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" +[[package]] +name = "hifijson" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "18ae468bcb4dfecf0e4949ee28abbc99076b6a0077f51ddbc94dbfff8e6a870c" + [[package]] name = "hmac" version = "0.11.0" @@ -3929,6 +3950,7 @@ dependencies = [ "holoom_types", "serde", "tokio", + "username_registry_utils", "username_registry_validation", ] @@ -4393,6 +4415,74 @@ version = "1.0.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1a46d1a171d865aa5f83f92695765caa047a9b4cbae2cbf37dbd613a793fd4c" +[[package]] +name = "jaq-core" +version = "1.4.0" +source = "git+https://github.com/8e8b2c/jaq?rev=1870c7c#1870c7cdcdea4682afcea6271c4590ea87ce82e4" +dependencies = [ + "aho-corasick", + "base64 0.22.1", + "hifijson", + "jaq-interpret", + "libm", + "log", + "regex", + "time", + "urlencoding", +] + +[[package]] +name = "jaq-interpret" +version = "1.4.0" +source = "git+https://github.com/8e8b2c/jaq?rev=1870c7c#1870c7cdcdea4682afcea6271c4590ea87ce82e4" +dependencies = [ + "dyn-clone", + "hifijson", + "indexmap 2.2.5", + "jaq-syn", + "once_cell", + "serde_json", +] + +[[package]] +name = "jaq-parse" +version = "1.0.2" +source = "git+https://github.com/8e8b2c/jaq?rev=1870c7c#1870c7cdcdea4682afcea6271c4590ea87ce82e4" +dependencies = [ + "chumsky", + "jaq-syn", +] + +[[package]] +name = "jaq-std" +version = "1.4.0" +source = "git+https://github.com/8e8b2c/jaq?rev=1870c7c#1870c7cdcdea4682afcea6271c4590ea87ce82e4" +dependencies = [ + "bincode", + "jaq-parse", + "jaq-syn", +] + +[[package]] +name = "jaq-syn" +version = "1.1.0" +source = "git+https://github.com/8e8b2c/jaq?rev=1870c7c#1870c7cdcdea4682afcea6271c4590ea87ce82e4" +dependencies = [ + "serde", +] + +[[package]] +name = "jaq_wrapper" +version = "0.0.1" +dependencies = [ + "hdi", + "hifijson", + "jaq-core", + "jaq-interpret", + "jaq-parse", + "jaq-std", +] + [[package]] name = "js-sys" version = "0.3.69" @@ -9106,8 +9196,11 @@ dependencies = [ "bincode", "hdk", "holoom_types", + "jaq_wrapper", "serde", + "serde_json", "username_registry_integrity", + "username_registry_utils", "username_registry_validation", ] @@ -9121,6 +9214,20 @@ dependencies = [ "username_registry_validation", ] +[[package]] +name = "username_registry_utils" +version = "0.0.1" +dependencies = [ + "bincode", + "bs58 0.5.0", + "ed25519-dalek", + "hdi", + "holo_hash", + "holoom_types", + "jaq_wrapper", + "serde", +] + [[package]] name = "username_registry_validation" version = "0.0.1" @@ -9131,7 +9238,10 @@ dependencies = [ "hdi", "holo_hash", "holoom_types", + "jaq_wrapper", "serde", + "serde_json", + "username_registry_utils", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index bd560cf..5f988ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ hdi = "=0.3.6" hdk = "=0.2.6" holo_hash = { version = "=0.2.6", features = ["encoding"] } serde = "=1.0.166" +serde_json = "1.0.109" bincode = "1.3.3" alloy-primitives = { version = "0.6.3", features = ["serde", "k256"] } ed25519-dalek = { version = "2.1.1", features = ["serde"] } @@ -31,6 +32,12 @@ rocket_okapi = { version = "0.8.0", features = ["swagger"] } [workspace.dependencies.holoom_types] path = "crates/holoom_types" +[workspace.dependencies.jaq_wrapper] +path = "crates/jaq_wrapper" + +[workspace.dependencies.username_registry_utils] +path = "crates/username_registry_utils" + [workspace.dependencies.username_registry_validation] path = "crates/username_registry_validation" diff --git a/crates/holoom_dna_tests/Cargo.toml b/crates/holoom_dna_tests/Cargo.toml index 201fc0c..a36b25c 100644 --- a/crates/holoom_dna_tests/Cargo.toml +++ b/crates/holoom_dna_tests/Cargo.toml @@ -17,6 +17,7 @@ holochain = { workspace = true, default-features = false, features = [ [dev-dependencies] +username_registry_utils = { workspace = true } username_registry_validation = { workspace = true } tokio = "1.32.0" holochain_keystore = { workspace = true } diff --git a/crates/holoom_dna_tests/src/tests/username_registry/mod.rs b/crates/holoom_dna_tests/src/tests/username_registry/mod.rs index 74512bc..bd2abad 100644 --- a/crates/holoom_dna_tests/src/tests/username_registry/mod.rs +++ b/crates/holoom_dna_tests/src/tests/username_registry/mod.rs @@ -1,3 +1,4 @@ +mod oracle; mod user_metadata; mod username_attestation; mod wallet_attestation; diff --git a/crates/holoom_dna_tests/src/tests/username_registry/oracle.rs b/crates/holoom_dna_tests/src/tests/username_registry/oracle.rs new file mode 100644 index 0000000..5715025 --- /dev/null +++ b/crates/holoom_dna_tests/src/tests/username_registry/oracle.rs @@ -0,0 +1,161 @@ +use hdk::prelude::*; + +use holochain::conductor::api::error::ConductorApiResult; +use holoom_types::{ + JqExecution, OracleDocument, OracleDocumentListSnapshot, + RefreshJqExecutionForNamedRelationPayload, RelateOracleDocumentPayload, SnapshotInput, +}; +use username_registry_utils::deserialize_record_entry; + +use crate::TestSetup; + +#[tokio::test(flavor = "multi_thread")] +async fn can_fetch_documents_by_relation() { + let setup = TestSetup::authority_only().await; + + let foo1_record: Record = setup + .authority_call( + "username_registry", + "create_oracle_document", + OracleDocument { + name: "foo/1".into(), + json_data: "{\"type\":\"foo\",\"value\":1}".into(), + }, + ) + .await + .unwrap(); + + let foo2_record: Record = setup + .authority_call( + "username_registry", + "create_oracle_document", + OracleDocument { + name: "foo/2".into(), + json_data: "{\"type\":\"foo\",\"value\":2}".into(), + }, + ) + .await + .unwrap(); + + let res: ConductorApiResult<()> = setup + .authority_call( + "username_registry", + "relate_oracle_document", + RelateOracleDocumentPayload { + name: "foo/1".into(), + relation: "foo".into(), + }, + ) + .await; + assert!(res.is_ok()); + + let res: ConductorApiResult<()> = setup + .authority_call( + "username_registry", + "relate_oracle_document", + RelateOracleDocumentPayload { + name: "foo/2".into(), + relation: "foo".into(), + }, + ) + .await; + assert!(res.is_ok()); + + let identifiers: Vec = setup + .authority_call( + "username_registry", + "get_related_oracle_document", + String::from("foo"), + ) + .await + .unwrap(); + + let expected_identifiers = vec![String::from("foo/1"), String::from("foo/2")]; + assert_eq!(identifiers, expected_identifiers); + + let snapshot_record: Record = setup + .authority_call( + "username_registry", + "refresh_oracle_document_snapshot_for_relation", + String::from("foo"), + ) + .await + .unwrap(); + + let snapshot: OracleDocumentListSnapshot = deserialize_record_entry(snapshot_record).unwrap(); + + assert_eq!( + snapshot.identifiers_input, + SnapshotInput::RelationSnapshot(expected_identifiers) + ); + assert_eq!( + snapshot.resolved_documents, + vec![ + foo1_record.action_address().clone(), + foo2_record.action_address().clone() + ] + ); + + let jq_execution_record: Record = setup + .authority_call( + "username_registry", + "refresh_jq_execution_for_named_relation", + RefreshJqExecutionForNamedRelationPayload { + relation_name: "foo".into(), + program: "[.[].value]".into(), + }, + ) + .await + .unwrap(); + + let jq_execution: JqExecution = deserialize_record_entry(jq_execution_record).unwrap(); + assert_eq!(jq_execution.output, String::from("[1,2]")); + + let revised_foo2_record: Record = setup + .authority_call( + "username_registry", + "create_oracle_document", + OracleDocument { + name: "foo/2".into(), + json_data: "{\"type\":\"foo\",\"value\":\"two\"}".into(), + }, + ) + .await + .unwrap(); + + let revised_snapshot_record: Record = setup + .authority_call( + "username_registry", + "refresh_oracle_document_snapshot_for_relation", + String::from("foo"), + ) + .await + .unwrap(); + + let revised_snapshot: OracleDocumentListSnapshot = + deserialize_record_entry(revised_snapshot_record).unwrap(); + + assert_eq!( + revised_snapshot.resolved_documents, + vec![ + foo1_record.action_address().clone(), + revised_foo2_record.action_address().clone() + ] + ); + + let revised_jq_execution_record: Record = setup + .authority_call( + "username_registry", + "refresh_jq_execution_for_named_relation", + RefreshJqExecutionForNamedRelationPayload { + relation_name: "foo".into(), + program: "[.[].value]".into(), + }, + ) + .await + .unwrap(); + + let revised_jq_execution: JqExecution = + deserialize_record_entry(revised_jq_execution_record).unwrap(); + assert_eq!(revised_jq_execution.output, String::from("[1,\"two\"]")); +} diff --git a/crates/holoom_types/src/external_id.rs b/crates/holoom_types/src/external_id.rs new file mode 100644 index 0000000..6902815 --- /dev/null +++ b/crates/holoom_types/src/external_id.rs @@ -0,0 +1,40 @@ +use hdi::prelude::*; +use serde::{Deserialize, Serialize}; + +#[hdk_entry_helper] +#[derive(Clone, PartialEq)] +pub struct ExternalIdAttestation { + pub request_id: String, + pub internal_pubkey: AgentPubKey, + pub external_id: String, + pub display_name: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct SendExternalIdAttestationRequestPayload { + pub request_id: String, + pub code_verifier: String, + pub code: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct IngestExternalIdAttestationRequestPayload { + pub request_id: String, + pub code_verifier: String, + pub code: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct ConfirmExternalIdRequestPayload { + pub request_id: String, + pub external_id: String, + pub display_name: String, + pub requestor: AgentPubKey, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct RejectExternalIdRequestPayload { + pub request_id: String, + pub requestor: AgentPubKey, + pub reason: String, +} diff --git a/crates/holoom_types/src/lib.rs b/crates/holoom_types/src/lib.rs index 6a25562..c786f14 100644 --- a/crates/holoom_types/src/lib.rs +++ b/crates/holoom_types/src/lib.rs @@ -1,6 +1,17 @@ use hdi::prelude::*; use serde::{Deserialize, Serialize}; +pub mod external_id; +pub use external_id::*; +pub mod metadata; +pub use metadata::*; +pub mod wallet; +pub use wallet::*; +pub mod username; +pub use username::*; +pub mod oracle; +pub use oracle::*; + #[derive(Serialize, Deserialize, Debug)] #[serde(tag = "type")] pub enum LocalHoloomSignal { @@ -26,102 +37,6 @@ pub enum RemoteHoloomSignal { ExternalIdRejected { request_id: String, reason: String }, } -#[hdk_entry_helper] -#[derive(Clone, PartialEq)] -pub struct UsernameAttestation { - pub agent: AgentPubKey, - pub username: String, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct SignedUsername { - pub username: String, - pub signature: Signature, - pub signer: AgentPubKey, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct MetadataItem { - pub name: String, - pub value: String, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct UpdateMetadataItemPayload { - pub agent_pubkey: AgentPubKey, - pub name: String, - pub value: String, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct GetMetadataItemValuePayload { - pub agent_pubkey: AgentPubKey, - pub name: String, -} - -pub type EvmAddress = alloy_primitives::Address; -pub type EvmSignature = alloy_primitives::Signature; -pub type SolanaAddress = ed25519_dalek::VerifyingKey; -pub type SolanaSignature = ed25519_dalek::Signature; - -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] -pub enum ChainWalletSignature { - Evm { - evm_address: EvmAddress, - evm_signature: EvmSignature, - }, - Solana { - solana_address: Box, - solana_signature: SolanaSignature, - }, -} - -#[hdk_entry_helper] -#[derive(Clone, PartialEq)] -pub struct WalletAttestation { - pub agent: AgentPubKey, - pub chain_wallet_signature: ChainWalletSignature, - pub prev_action: ActionHash, -} - -#[hdk_entry_helper] -#[derive(Clone, PartialEq)] -pub struct ExternalIdAttestation { - pub request_id: String, - pub internal_pubkey: AgentPubKey, - pub external_id: String, - pub display_name: String, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct SendExternalIdAttestationRequestPayload { - pub request_id: String, - pub code_verifier: String, - pub code: String, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct IngestExternalIdAttestationRequestPayload { - pub request_id: String, - pub code_verifier: String, - pub code: String, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct ConfirmExternalIdRequestPayload { - pub request_id: String, - pub external_id: String, - pub display_name: String, - pub requestor: AgentPubKey, -} - -#[derive(Serialize, Deserialize, Debug)] -pub struct RejectExternalIdRequestPayload { - pub request_id: String, - pub requestor: AgentPubKey, - pub reason: String, -} - #[derive(Serialize, Deserialize, Debug, Clone, SerializedBytes)] pub struct SignableBytes(pub Vec); diff --git a/crates/holoom_types/src/metadata.rs b/crates/holoom_types/src/metadata.rs new file mode 100644 index 0000000..bdc836e --- /dev/null +++ b/crates/holoom_types/src/metadata.rs @@ -0,0 +1,21 @@ +use hdi::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Serialize, Deserialize, Debug)] +pub struct MetadataItem { + pub name: String, + pub value: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct UpdateMetadataItemPayload { + pub agent_pubkey: AgentPubKey, + pub name: String, + pub value: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct GetMetadataItemValuePayload { + pub agent_pubkey: AgentPubKey, + pub name: String, +} diff --git a/crates/holoom_types/src/oracle.rs b/crates/holoom_types/src/oracle.rs new file mode 100644 index 0000000..6582808 --- /dev/null +++ b/crates/holoom_types/src/oracle.rs @@ -0,0 +1,52 @@ +use hdi::prelude::*; +use serde::{Deserialize, Serialize}; + +#[hdk_entry_helper] +#[derive(Clone, PartialEq)] +pub struct OracleDocument { + // E.g. organizers/1234/championship-ids + pub name: String, + pub json_data: String, +} + +#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)] +pub enum SnapshotInput { + JqExecution(ActionHash), + OracleDocument(ActionHash), + RelationSnapshot(Vec), +} + +#[hdk_entry_helper] +#[derive(Clone, PartialEq)] +pub struct OracleDocumentListSnapshot { + /// The action hash of an OracleDocument that gives a list of identifiers + pub identifiers_input: SnapshotInput, + pub resolved_documents: Vec, +} + +#[derive(Clone, PartialEq, Serialize, Deserialize, Debug)] +pub enum JqExecutionInput { + OracleDocument(ActionHash), + OracleDocumentListSnapshot(ActionHash), + JqExecution(ActionHash), +} + +#[hdk_entry_helper] +#[derive(Clone, PartialEq)] +pub struct JqExecution { + pub program: String, + pub input: JqExecutionInput, + pub output: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct RefreshJqExecutionForNamedRelationPayload { + pub relation_name: String, + pub program: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct RelateOracleDocumentPayload { + pub name: String, + pub relation: String, +} diff --git a/crates/holoom_types/src/username.rs b/crates/holoom_types/src/username.rs new file mode 100644 index 0000000..b96c97f --- /dev/null +++ b/crates/holoom_types/src/username.rs @@ -0,0 +1,16 @@ +use hdi::prelude::*; +use serde::{Deserialize, Serialize}; + +#[hdk_entry_helper] +#[derive(Clone, PartialEq)] +pub struct UsernameAttestation { + pub agent: AgentPubKey, + pub username: String, +} + +#[derive(Serialize, Deserialize, Debug)] +pub struct SignedUsername { + pub username: String, + pub signature: Signature, + pub signer: AgentPubKey, +} diff --git a/crates/holoom_types/src/wallet.rs b/crates/holoom_types/src/wallet.rs new file mode 100644 index 0000000..aabb560 --- /dev/null +++ b/crates/holoom_types/src/wallet.rs @@ -0,0 +1,27 @@ +use hdi::prelude::*; +use serde::{Deserialize, Serialize}; + +pub type EvmAddress = alloy_primitives::Address; +pub type EvmSignature = alloy_primitives::Signature; +pub type SolanaAddress = ed25519_dalek::VerifyingKey; +pub type SolanaSignature = ed25519_dalek::Signature; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)] +pub enum ChainWalletSignature { + Evm { + evm_address: EvmAddress, + evm_signature: EvmSignature, + }, + Solana { + solana_address: Box, + solana_signature: SolanaSignature, + }, +} + +#[hdk_entry_helper] +#[derive(Clone, PartialEq)] +pub struct WalletAttestation { + pub agent: AgentPubKey, + pub chain_wallet_signature: ChainWalletSignature, + pub prev_action: ActionHash, +} diff --git a/crates/jaq_wrapper/Cargo.toml b/crates/jaq_wrapper/Cargo.toml new file mode 100644 index 0000000..09354de --- /dev/null +++ b/crates/jaq_wrapper/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "jaq_wrapper" +version = "0.0.1" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] +name = "jaq_wrapper" + +[dependencies] +hdi = { workspace = true } +jaq-interpret = { git = "https://github.com/8e8b2c/jaq", rev = "1870c7c" } +jaq-parse = { git = "https://github.com/8e8b2c/jaq", rev = "1870c7c" } +jaq-core = { git = "https://github.com/8e8b2c/jaq", rev = "1870c7c" } +jaq-std = { git = "https://github.com/8e8b2c/jaq", rev = "1870c7c" } +hifijson = "0.2.0" diff --git a/crates/jaq_wrapper/src/lib.rs b/crates/jaq_wrapper/src/lib.rs new file mode 100644 index 0000000..dbca4e8 --- /dev/null +++ b/crates/jaq_wrapper/src/lib.rs @@ -0,0 +1,125 @@ +use hdi::prelude::*; +use jaq_interpret::{Ctx, Filter, FilterT, ParseCtx, RcIter, Val}; +use std::io::{self, BufRead}; + +pub enum JqProgramInput { + Single(String), + Slurp(Vec), +} + +pub fn compile_and_run_jq(program_str: &str, input: JqProgramInput) -> ExternResult { + let filter = compile_filter(program_str)?; + let input = input.parse()?; + let output = run_filter(filter, input)?; + Ok(output.to_string()) +} + +impl JqProgramInput { + fn parse(self) -> ExternResult { + match self { + Self::Single(json) => parse_single_json(&json), + Self::Slurp(jsons) => { + let items = jsons + .into_iter() + .map(|json| parse_single_json(&json)) + .collect::>>()?; + Ok(Val::arr(items)) + } + } + } +} + +fn compile_filter(program_str: &str) -> ExternResult { + let (maybe_main, errs) = jaq_parse::parse(program_str, jaq_parse::main()); + let main = maybe_main.ok_or(wasm_error!(format!( + "jq program compilation failed with {} error(s)", + errs.len() + )))?; + let mut defs = ParseCtx::new(vec![]); + defs.insert_natives(jaq_core::core()); + defs.insert_defs(jaq_std::std()); + let filter = defs.compile(main); + Ok(filter) +} + +fn run_filter(filter: Filter, input: Val) -> ExternResult { + // Seems jaq is designed to pipe errors forwards, whilst tracking a global reference - hence + // the iterator gubbins. + let vars = vec![]; + let inputs: Vec> = vec![Ok(input)]; + let iter = Box::new(inputs.into_iter()) as Box>; + let iter = RcIter::new(iter); + let ctx = Ctx::new(vars, &iter); + + // The above iterator dance makes it very hard for me to understand how to take just one item. + // As a workaround I use a for loop (which the compiler seems happy with) and then error if + // the loop repeats. + let mut looped_once = false; + let mut output: Option = None; + for input in &iter { + if looped_once { + return Err(wasm_error!(WasmErrorInner::Guest( + "Unexpected continued input".into() + ))); + } + let input = input.map_err(|err| { + wasm_error!(WasmErrorInner::Guest(format!( + "Error in jq program input: {}", + err + ))) + })?; + let mut outputs = filter + .run((ctx.clone(), input)) + .collect::, _>>() + .map_err(|err| { + wasm_error!(WasmErrorInner::Guest(format!( + "jq execution error: {}", + err + ))) + })?; + if outputs.len() > 1 { + return Err(wasm_error!(WasmErrorInner::Guest( + "Unexpected multiple outputs to jq program".into() + ))); + } + if let Some(val) = outputs.pop() { + output = Some(val); + } + looped_once = true; + } + output.ok_or(wasm_error!(WasmErrorInner::Guest( + "jq program produced no output".into() + ))) +} + +fn parse_single_json(json: &str) -> ExternResult { + let mut iter = json_read(json.as_bytes()); + let val = iter + .next() + .ok_or(wasm_error!(WasmErrorInner::Guest("No input parsed".into())))? + .map_err(|err| { + wasm_error!(WasmErrorInner::Guest(format!( + "Input parse failed: {}", + err + ))) + })?; + if iter.next().is_some() { + return Err(wasm_error!(WasmErrorInner::Guest( + "Unexpected continuation of input".into() + ))); + } + Ok(val) +} + +fn json_read<'a>(read: impl BufRead + 'a) -> impl Iterator> + 'a { + let mut lexer = hifijson::IterLexer::new(read.bytes()); + core::iter::from_fn(move || { + use hifijson::token::Lex; + let v = Val::parse(lexer.ws_token()?, &mut lexer); + Some(v.map_err(|e| core::mem::take(&mut lexer.error).unwrap_or_else(|| invalid_data(e)))) + }) +} + +fn invalid_data(e: impl std::error::Error + Send + Sync + 'static) -> std::io::Error { + io::Error::new(io::ErrorKind::InvalidData, e) +} diff --git a/crates/username_registry_coordinator/Cargo.toml b/crates/username_registry_coordinator/Cargo.toml index 861da26..18abe34 100644 --- a/crates/username_registry_coordinator/Cargo.toml +++ b/crates/username_registry_coordinator/Cargo.toml @@ -14,3 +14,6 @@ bincode = { workspace = true } username_registry_integrity = { workspace = true } holoom_types = { workspace = true } username_registry_validation = { workspace = true } +username_registry_utils = { workspace = true } +jaq_wrapper = { workspace = true } +serde_json = { workspace = true } diff --git a/crates/username_registry_coordinator/src/jq_execution.rs b/crates/username_registry_coordinator/src/jq_execution.rs new file mode 100644 index 0000000..0a00fcd --- /dev/null +++ b/crates/username_registry_coordinator/src/jq_execution.rs @@ -0,0 +1,45 @@ +use hdk::prelude::*; +use holoom_types::{ + JqExecution, JqExecutionInput, OracleDocument, OracleDocumentListSnapshot, + RefreshJqExecutionForNamedRelationPayload, +}; +use jaq_wrapper::{compile_and_run_jq, JqProgramInput}; +use username_registry_integrity::EntryTypes; +use username_registry_utils::deserialize_record_entry; + +use crate::oracle_document_list_snapshot::refresh_oracle_document_snapshot_for_relation; + +#[hdk_extern] +pub fn refresh_jq_execution_for_named_relation( + payload: RefreshJqExecutionForNamedRelationPayload, +) -> ExternResult { + let snapshot_record = refresh_oracle_document_snapshot_for_relation(payload.relation_name)?; + let snapshot_ah = snapshot_record.action_address().clone(); + let snapshot: OracleDocumentListSnapshot = deserialize_record_entry(snapshot_record)?; + + let jsons = snapshot + .resolved_documents + .into_iter() + .map(|ah| { + let oracle_document_record = get(ah, GetOptions::default())?.ok_or(wasm_error!( + WasmErrorInner::Guest("OracleDocument not found".into()) + ))?; + let oracle_document: OracleDocument = deserialize_record_entry(oracle_document_record)?; + Ok(oracle_document.json_data) + }) + .collect::>>()?; + + let jq_program_output = compile_and_run_jq(&payload.program, JqProgramInput::Slurp(jsons))?; + + let jq_execution = JqExecution { + program: payload.program, + input: JqExecutionInput::OracleDocumentListSnapshot(snapshot_ah), + output: jq_program_output, + }; + + let jq_execution_ah = create_entry(EntryTypes::JqExecution(jq_execution))?; + let jq_execution_record = get(jq_execution_ah, GetOptions::default())?.ok_or(wasm_error!( + WasmErrorInner::Guest("Newly created JqExecution not found".into()) + ))?; + Ok(jq_execution_record) +} diff --git a/crates/username_registry_coordinator/src/lib.rs b/crates/username_registry_coordinator/src/lib.rs index 6336656..0d9f8c3 100644 --- a/crates/username_registry_coordinator/src/lib.rs +++ b/crates/username_registry_coordinator/src/lib.rs @@ -1,4 +1,7 @@ pub mod external_id_attestation; +pub mod jq_execution; +pub mod oracle_document; +pub mod oracle_document_list_snapshot; pub mod user_metadata; pub mod username_attestation; pub mod wallet_attestation; diff --git a/crates/username_registry_coordinator/src/oracle_document.rs b/crates/username_registry_coordinator/src/oracle_document.rs new file mode 100644 index 0000000..6f39915 --- /dev/null +++ b/crates/username_registry_coordinator/src/oracle_document.rs @@ -0,0 +1,69 @@ +use hdk::prelude::*; +use holoom_types::{OracleDocument, RelateOracleDocumentPayload}; +use username_registry_integrity::{EntryTypes, LinkTypes}; +use username_registry_utils::hash_identifier; + +#[hdk_extern] +pub fn create_oracle_document(oracle_document: OracleDocument) -> ExternResult { + let base_address = hash_identifier(oracle_document.name.clone())?; + let oracle_document_ah = create_entry(EntryTypes::OracleDocument(oracle_document))?; + create_link( + base_address, + oracle_document_ah.clone(), + LinkTypes::NameToOracleDocument, + (), + )?; + let record = get(oracle_document_ah, GetOptions::default())?.ok_or(wasm_error!( + WasmErrorInner::Guest(String::from( + "Could not find the newly created OracleDocument" + )) + ))?; + + Ok(record) +} + +pub fn get_latest_oracle_document_ah_for_name(name: String) -> ExternResult> { + let base_address = hash_identifier(name)?; + let mut links = get_links(base_address, LinkTypes::NameToOracleDocument, None)?; + links.sort_by_key(|link| link.timestamp); + let Some(link) = links.pop() else { + return Ok(None); + }; + let action_hash = ActionHash::try_from(link.target).map_err(|_| { + wasm_error!(WasmErrorInner::Guest( + "Link target isn't an ActionHash".into() + )) + })?; + Ok(Some(action_hash)) +} + +#[hdk_extern] +pub fn relate_oracle_document(payload: RelateOracleDocumentPayload) -> ExternResult<()> { + let base_address = hash_identifier(payload.relation)?; + let target_address = hash_identifier(payload.name.clone())?; + create_link( + base_address, + target_address, + LinkTypes::RelateOracleDocumentName, + payload.name.as_bytes().to_vec(), + )?; + + Ok(()) +} + +#[hdk_extern] +pub fn get_related_oracle_document(relation_name: String) -> ExternResult> { + // BTreeSet ensures order an no repeats + let identifiers: BTreeSet = get_links( + hash_identifier(relation_name)?, + LinkTypes::RelateOracleDocumentName, + None, + )? + .into_iter() + .map(|link| { + String::from_utf8(link.tag.into_inner()) + .map_err(|_| wasm_error!(WasmErrorInner::Guest("LinkTag isn't utf8".into()))) + }) + .collect::>()?; + Ok(identifiers.into_iter().collect()) +} diff --git a/crates/username_registry_coordinator/src/oracle_document_list_snapshot.rs b/crates/username_registry_coordinator/src/oracle_document_list_snapshot.rs new file mode 100644 index 0000000..16c8279 --- /dev/null +++ b/crates/username_registry_coordinator/src/oracle_document_list_snapshot.rs @@ -0,0 +1,85 @@ +use hdk::prelude::*; +use holoom_types::{JqExecution, OracleDocument, OracleDocumentListSnapshot, SnapshotInput}; +use serde_json; +use username_registry_integrity::EntryTypes; +use username_registry_utils::deserialize_record_entry; + +use crate::oracle_document::{get_latest_oracle_document_ah_for_name, get_related_oracle_document}; + +pub fn resolve_snapshot_input(input: SnapshotInput) -> ExternResult> { + let json_list = match input { + SnapshotInput::JqExecution(jq_execution_ah) => { + let record = get(jq_execution_ah, GetOptions::default())?.ok_or(wasm_error!( + WasmErrorInner::Guest("JqExecution for SnapshotInput not found".into()) + ))?; + let jq_execution: JqExecution = deserialize_record_entry(record)?; + jq_execution.output + } + SnapshotInput::OracleDocument(oracle_document_ah) => { + let record = get(oracle_document_ah, GetOptions::default())?.ok_or(wasm_error!( + WasmErrorInner::Guest("JqExecution for SnapshotInput not found".into()) + ))?; + let oracle_document: OracleDocument = deserialize_record_entry(record)?; + oracle_document.json_data + } + SnapshotInput::RelationSnapshot(identifiers) => return Ok(identifiers), + }; + + serde_json::from_str(&json_list).map_err(|_| { + wasm_error!(WasmErrorInner::Guest( + "SnapshotInput doesn't point to a list".into() + )) + }) +} + +pub fn build_latest_oracle_document_list_snapshot_for_frozen_input( + input: SnapshotInput, +) -> ExternResult { + let names = resolve_snapshot_input(input.clone())?; + let action_hashes = names + .into_iter() + .map(|name| { + let action_hash = get_latest_oracle_document_ah_for_name(name.clone())?; + action_hash.ok_or(wasm_error!(WasmErrorInner::Guest(format!( + "Cannot build snapshot with missing document '{}'", + name + )))) + }) + .collect::>>()?; + let snapshot = OracleDocumentListSnapshot { + identifiers_input: input, + resolved_documents: action_hashes, + }; + Ok(snapshot) +} + +pub fn refresh_oracle_document_snapshot_for_named_input_list_document( + input_list_document_name: String, +) -> ExternResult { + let input_ah = get_latest_oracle_document_ah_for_name(input_list_document_name.clone())? + .ok_or(wasm_error!(WasmErrorInner::Guest(format!( + "No linked documents for '{}'", + input_list_document_name + ))))?; + let snapshot_input = SnapshotInput::OracleDocument(input_ah); + let snapshot = build_latest_oracle_document_list_snapshot_for_frozen_input(snapshot_input)?; + let snapshot_ah = create_entry(EntryTypes::OracleDocumentListSnapshot(snapshot))?; + let record = get(snapshot_ah, GetOptions::default())?.ok_or(wasm_error!( + WasmErrorInner::Guest("Newly created OracleDocumentListSnapshot not found".into()) + ))?; + Ok(record) +} + +#[hdk_extern] +pub fn refresh_oracle_document_snapshot_for_relation( + relation_name: String, +) -> ExternResult { + let identifiers = get_related_oracle_document(relation_name)?; + let snapshot_input = SnapshotInput::RelationSnapshot(identifiers); + let snapshot = build_latest_oracle_document_list_snapshot_for_frozen_input(snapshot_input)?; + let snapshot_ah = create_entry(EntryTypes::OracleDocumentListSnapshot(snapshot))?; + let record = get(snapshot_ah, GetOptions::default())?.ok_or(wasm_error!( + WasmErrorInner::Guest("Newly created OracleDocumentListSnapshot not found".into()) + ))?; + Ok(record) +} diff --git a/crates/username_registry_integrity/src/entry_types.rs b/crates/username_registry_integrity/src/entry_types.rs new file mode 100644 index 0000000..7cdd9b7 --- /dev/null +++ b/crates/username_registry_integrity/src/entry_types.rs @@ -0,0 +1,57 @@ +use hdi::prelude::*; +use holoom_types::{ + ExternalIdAttestation, JqExecution, OracleDocument, OracleDocumentListSnapshot, + UsernameAttestation, WalletAttestation, +}; +use username_registry_validation::*; + +#[derive(Serialize, Deserialize)] +#[serde(tag = "type")] +#[hdk_entry_defs] +#[unit_enum(UnitEntryTypes)] +pub enum EntryTypes { + UsernameAttestation(UsernameAttestation), + WalletAttestation(WalletAttestation), + ExternalIdAttestation(ExternalIdAttestation), + OracleDocument(OracleDocument), + OracleDocumentListSnapshot(OracleDocumentListSnapshot), + JqExecution(JqExecution), +} + +impl EntryTypes { + pub fn validate_create(self, action: Create) -> ExternResult { + match self { + EntryTypes::UsernameAttestation(username_attestation) => { + validate_create_username_attestation( + EntryCreationAction::Create(action), + username_attestation, + ) + } + EntryTypes::WalletAttestation(wallet_attestation) => { + validate_create_wallet_attestation( + EntryCreationAction::Create(action), + wallet_attestation, + ) + } + EntryTypes::ExternalIdAttestation(external_id_attestation) => { + validate_create_external_id_attestation( + EntryCreationAction::Create(action), + external_id_attestation, + ) + } + EntryTypes::OracleDocument(oracle_document) => validate_create_oracle_document( + EntryCreationAction::Create(action), + oracle_document, + ), + EntryTypes::OracleDocumentListSnapshot(oracle_document_list_snapshot) => { + validate_create_oracle_document_list_snapshot( + EntryCreationAction::Create(action), + oracle_document_list_snapshot, + ) + } + EntryTypes::JqExecution(jq_execution) => { + validate_create_jq_execution(EntryCreationAction::Create(action), jq_execution) + } + } + } +} diff --git a/crates/username_registry_integrity/src/lib.rs b/crates/username_registry_integrity/src/lib.rs index b88ebdd..b6937e0 100644 --- a/crates/username_registry_integrity/src/lib.rs +++ b/crates/username_registry_integrity/src/lib.rs @@ -1,24 +1,9 @@ use hdi::prelude::*; -use holoom_types::{ExternalIdAttestation, UsernameAttestation, WalletAttestation}; -use username_registry_validation::*; +pub mod entry_types; +pub use entry_types::*; +pub mod link_types; +pub use link_types::*; -#[derive(Serialize, Deserialize)] -#[serde(tag = "type")] -#[hdk_entry_defs] -#[unit_enum(UnitEntryTypes)] -pub enum EntryTypes { - UsernameAttestation(UsernameAttestation), - WalletAttestation(WalletAttestation), - ExternalIdAttestation(ExternalIdAttestation), -} -#[derive(Serialize, Deserialize)] -#[hdk_link_types] -pub enum LinkTypes { - AgentToUsernameAttestations, - AgentMetadata, - AgentToWalletAttestations, - AgentToExternalIdAttestation, -} #[hdk_extern] pub fn genesis_self_check(_data: GenesisSelfCheckData) -> ExternResult { Ok(ValidateCallbackResult::Valid) @@ -33,105 +18,22 @@ pub fn validate_agent_joining( pub fn validate(op: Op) -> ExternResult { match op.flattened::()? { FlatOp::StoreEntry(store_entry) => match store_entry { - OpEntry::CreateEntry { app_entry, action } => match app_entry { - EntryTypes::UsernameAttestation(username_attestation) => { - validate_create_username_attestation( - EntryCreationAction::Create(action), - username_attestation, - ) - } - EntryTypes::WalletAttestation(wallet_attestation) => { - validate_create_wallet_attestation( - EntryCreationAction::Create(action), - wallet_attestation, - ) - } - EntryTypes::ExternalIdAttestation(external_id_attestation) => { - validate_create_external_id_attestation( - EntryCreationAction::Create(action), - external_id_attestation, - ) - } - }, - OpEntry::UpdateEntry { - app_entry, action, .. - } => match app_entry { - EntryTypes::UsernameAttestation(username_attestation) => { - validate_create_username_attestation( - EntryCreationAction::Update(action), - username_attestation, - ) - } - EntryTypes::WalletAttestation(wallet_attestation) => { - validate_create_wallet_attestation( - EntryCreationAction::Update(action), - wallet_attestation, - ) - } - EntryTypes::ExternalIdAttestation(external_id_attestation) => { - validate_create_external_id_attestation( - EntryCreationAction::Update(action), - external_id_attestation, - ) - } - }, + OpEntry::CreateEntry { app_entry, action } => app_entry.validate_create(action), + OpEntry::UpdateEntry { .. } => Ok(ValidateCallbackResult::Invalid( + "App EntryTypes cannot be updated".into(), + )), _ => Ok(ValidateCallbackResult::Valid), }, FlatOp::RegisterUpdate(update_entry) => match update_entry { - OpUpdate::Entry { - original_action, - original_app_entry, - app_entry, - action, - } => match (app_entry, original_app_entry) { - ( - EntryTypes::UsernameAttestation(username_attestation), - EntryTypes::UsernameAttestation(original_username_attestation), - ) => validate_update_username_attestation( - action, - username_attestation, - original_action, - original_username_attestation, - ), - ( - EntryTypes::WalletAttestation(wallet_attestation), - EntryTypes::WalletAttestation(original_wallet_attestation), - ) => validate_update_wallet_attestation( - action, - wallet_attestation, - original_action, - original_wallet_attestation, - ), - _ => Ok(ValidateCallbackResult::Invalid( - "Updated and original types must match".into(), - )), - }, + OpUpdate::Entry { .. } => Ok(ValidateCallbackResult::Invalid( + "App EntryTypes cannot be updated".into(), + )), _ => Ok(ValidateCallbackResult::Valid), }, FlatOp::RegisterDelete(delete_entry) => match delete_entry { - OpDelete::Entry { - original_action, - original_app_entry, - action, - } => match original_app_entry { - EntryTypes::UsernameAttestation(username_attestation) => { - validate_delete_username_attestation( - action, - original_action, - username_attestation, - ) - } - EntryTypes::WalletAttestation(wallet_attestation) => { - validate_delete_wallet_attestation(action, original_action, wallet_attestation) - } - EntryTypes::ExternalIdAttestation(external_id_attestation) => { - validate_delete_external_id_attestation( - action, - original_action, - external_id_attestation, - ) - } - }, + OpDelete::Entry { .. } => Ok(ValidateCallbackResult::Invalid( + "App EntryTypes cannot be deleted".into(), + )), _ => Ok(ValidateCallbackResult::Valid), }, FlatOp::RegisterCreateLink { @@ -140,35 +42,7 @@ pub fn validate(op: Op) -> ExternResult { target_address, tag, action, - } => match link_type { - LinkTypes::AgentToUsernameAttestations => { - validate_create_link_agent_to_username_attestations( - action, - base_address, - target_address, - tag, - ) - } - LinkTypes::AgentMetadata => { - validate_create_link_user_metadata(action, base_address, target_address, tag) - } - LinkTypes::AgentToWalletAttestations => { - validate_create_link_agent_to_wallet_attestations( - action, - base_address, - target_address, - tag, - ) - } - LinkTypes::AgentToExternalIdAttestation => { - validate_create_link_agent_to_external_id_attestations( - action, - base_address, - target_address, - tag, - ) - } - }, + } => link_type.validate_create(action, base_address, target_address, tag), FlatOp::RegisterDeleteLink { link_type, base_address, @@ -176,302 +50,23 @@ pub fn validate(op: Op) -> ExternResult { tag, original_action, action, - } => match link_type { - LinkTypes::AgentToUsernameAttestations => { - validate_delete_link_agent_to_username_attestations( - action, - original_action, - base_address, - target_address, - tag, - ) - } - LinkTypes::AgentMetadata => validate_delete_link_user_metadata( - action, - original_action, - base_address, - target_address, - tag, - ), - LinkTypes::AgentToWalletAttestations => { - validate_delete_link_agent_to_wallet_attestations( - action, - original_action, - base_address, - target_address, - tag, - ) - } - LinkTypes::AgentToExternalIdAttestation => { - validate_delete_link_agent_to_external_id_attestations( - action, - original_action, - base_address, - target_address, - tag, - ) - } - }, + } => link_type.validate_delete(action, original_action, base_address, target_address, tag), FlatOp::StoreRecord(store_record) => { match store_record { - OpRecord::CreateEntry { app_entry, action } => match app_entry { - EntryTypes::UsernameAttestation(username_attestation) => { - validate_create_username_attestation( - EntryCreationAction::Create(action), - username_attestation, - ) - } - EntryTypes::WalletAttestation(wallet_attestation) => { - validate_create_wallet_attestation( - EntryCreationAction::Create(action), - wallet_attestation, - ) - } - EntryTypes::ExternalIdAttestation(external_id_attestation) => { - validate_create_external_id_attestation( - EntryCreationAction::Create(action), - external_id_attestation, - ) - } - }, - OpRecord::UpdateEntry { - original_action_hash, - app_entry, - action, - .. - } => { - let original_record = must_get_valid_record(original_action_hash)?; - let original_action = original_record.action().clone(); - let original_action = match original_action { - Action::Create(create) => EntryCreationAction::Create(create), - Action::Update(update) => EntryCreationAction::Update(update), - _ => { - return Ok(ValidateCallbackResult::Invalid( - "Original action for an update must be a Create or Update action" - .to_string(), - )); - } - }; - match app_entry { - EntryTypes::UsernameAttestation(username_attestation) => { - let result = validate_create_username_attestation( - EntryCreationAction::Update(action.clone()), - username_attestation.clone(), - )?; - if let ValidateCallbackResult::Valid = result { - let original_username_attestation: Option = - original_record - .entry() - .to_app_option() - .map_err(|e| wasm_error!(e))?; - let original_username_attestation = - match original_username_attestation { - Some(username_attestation) => username_attestation, - None => { - return Ok( - ValidateCallbackResult::Invalid( - "The updated entry type must be the same as the original entry type" - .to_string(), - ), - ); - } - }; - validate_update_username_attestation( - action, - username_attestation, - original_action, - original_username_attestation, - ) - } else { - Ok(result) - } - } - EntryTypes::WalletAttestation(wallet_attestation) => { - let result = validate_create_wallet_attestation( - EntryCreationAction::Update(action.clone()), - wallet_attestation.clone(), - )?; - if let ValidateCallbackResult::Valid = result { - let original_wallet_attestation: Option = - original_record - .entry() - .to_app_option() - .map_err(|e| wasm_error!(e))?; - let original_wallet_attestation = match original_wallet_attestation - { - Some(wallet_attestation) => wallet_attestation, - None => { - return Ok( - ValidateCallbackResult::Invalid( - "The updated entry type must be the same as the original entry type" - .to_string(), - ), - ); - } - }; - validate_update_wallet_attestation( - action, - wallet_attestation, - original_action, - original_wallet_attestation, - ) - } else { - Ok(result) - } - } - - EntryTypes::ExternalIdAttestation(external_id_attestation) => { - let result = validate_create_external_id_attestation( - EntryCreationAction::Update(action.clone()), - external_id_attestation.clone(), - )?; - if let ValidateCallbackResult::Valid = result { - let original_external_id_attestation: Option< - ExternalIdAttestation, - > = original_record - .entry() - .to_app_option() - .map_err(|e| wasm_error!(e))?; - let original_external_id_attestation = - match original_external_id_attestation { - Some(external_id_attestation) => external_id_attestation, - None => { - return Ok( - ValidateCallbackResult::Invalid( - "The updated entry type must be the same as the original entry type" - .to_string(), - ), - ); - } - }; - validate_update_external_id_attestation( - action, - external_id_attestation, - original_action, - original_external_id_attestation, - ) - } else { - Ok(result) - } - } - } - } - OpRecord::DeleteEntry { - original_action_hash, - action, - .. - } => { - let original_record = must_get_valid_record(original_action_hash)?; - let original_action = original_record.action().clone(); - let original_action = match original_action { - Action::Create(create) => EntryCreationAction::Create(create), - Action::Update(update) => EntryCreationAction::Update(update), - _ => { - return Ok(ValidateCallbackResult::Invalid( - "Original action for a delete must be a Create or Update action" - .to_string(), - )); - } - }; - let app_entry_type = match original_action.entry_type() { - EntryType::App(app_entry_type) => app_entry_type, - _ => { - return Ok(ValidateCallbackResult::Valid); - } - }; - let entry = match original_record.entry().as_option() { - Some(entry) => entry, - None => { - if original_action.entry_type().visibility().is_public() { - return Ok( - ValidateCallbackResult::Invalid( - "Original record for a delete of a public entry must contain an entry" - .to_string(), - ), - ); - } else { - return Ok(ValidateCallbackResult::Valid); - } - } - }; - let original_app_entry = match EntryTypes::deserialize_from_type( - app_entry_type.zome_index, - app_entry_type.entry_index, - entry, - )? { - Some(app_entry) => app_entry, - None => { - // @todo This must be treated as valid due to holochain bug - // see https://github.com/holochain/holochain/issues/2868 - return Ok(ValidateCallbackResult::Valid); - // ValidateCallbackResult::Invalid( - // "Original app entry must be one of the defined entry types for this zome" - // .to_string(), - // ) - } - }; - match original_app_entry { - EntryTypes::UsernameAttestation(original_username_attestation) => { - validate_delete_username_attestation( - action, - original_action, - original_username_attestation, - ) - } - EntryTypes::WalletAttestation(original_wallet_attestation) => { - validate_delete_wallet_attestation( - action, - original_action, - original_wallet_attestation, - ) - } - EntryTypes::ExternalIdAttestation(original_external_id_attestation) => { - validate_delete_external_id_attestation( - action, - original_action, - original_external_id_attestation, - ) - } - } - } + OpRecord::CreateEntry { app_entry, action } => app_entry.validate_create(action), + OpRecord::UpdateEntry { .. } => Ok(ValidateCallbackResult::Invalid( + "App EntryTypes cannot be updated".into(), + )), + OpRecord::DeleteEntry { .. } => Ok(ValidateCallbackResult::Invalid( + "App EntryTypes cannot be deleted".into(), + )), OpRecord::CreateLink { base_address, target_address, tag, link_type, action, - } => match link_type { - LinkTypes::AgentToUsernameAttestations => { - validate_create_link_agent_to_username_attestations( - action, - base_address, - target_address, - tag, - ) - } - LinkTypes::AgentMetadata => validate_create_link_user_metadata( - action, - base_address, - target_address, - tag, - ), - LinkTypes::AgentToWalletAttestations => { - validate_create_link_agent_to_wallet_attestations( - action, - base_address, - target_address, - tag, - ) - } - LinkTypes::AgentToExternalIdAttestation => { - validate_create_link_agent_to_external_id_attestations( - action, - base_address, - target_address, - tag, - ) - } - }, + } => link_type.validate_create(action, base_address, target_address, tag), OpRecord::DeleteLink { original_action_hash, base_address, @@ -498,42 +93,13 @@ pub fn validate(op: Op) -> ExternResult { return Ok(ValidateCallbackResult::Valid); } }; - match link_type { - LinkTypes::AgentToUsernameAttestations => { - validate_delete_link_agent_to_username_attestations( - action, - create_link.clone(), - base_address, - create_link.target_address, - create_link.tag, - ) - } - LinkTypes::AgentMetadata => validate_delete_link_user_metadata( - action, - create_link.clone(), - base_address, - create_link.target_address, - create_link.tag, - ), - LinkTypes::AgentToWalletAttestations => { - validate_delete_link_agent_to_wallet_attestations( - action, - create_link.clone(), - base_address, - create_link.target_address, - create_link.tag, - ) - } - LinkTypes::AgentToExternalIdAttestation => { - validate_delete_link_agent_to_external_id_attestations( - action, - create_link.clone(), - base_address, - create_link.target_address, - create_link.tag, - ) - } - } + link_type.validate_delete( + action, + create_link.clone(), + base_address, + create_link.target_address, + create_link.tag, + ) } OpRecord::CreatePrivateEntry { .. } => Ok(ValidateCallbackResult::Valid), OpRecord::UpdatePrivateEntry { .. } => Ok(ValidateCallbackResult::Valid), diff --git a/crates/username_registry_integrity/src/link_types.rs b/crates/username_registry_integrity/src/link_types.rs new file mode 100644 index 0000000..9350bb7 --- /dev/null +++ b/crates/username_registry_integrity/src/link_types.rs @@ -0,0 +1,129 @@ +use hdi::prelude::*; +use username_registry_validation::*; + +#[derive(Serialize, Deserialize)] +#[hdk_link_types] +pub enum LinkTypes { + AgentToUsernameAttestations, + AgentMetadata, + AgentToWalletAttestations, + AgentToExternalIdAttestation, + NameToOracleDocument, + RelateOracleDocumentName, +} + +impl LinkTypes { + pub fn validate_create( + self, + action: CreateLink, + base_address: AnyLinkableHash, + target_address: AnyLinkableHash, + tag: LinkTag, + ) -> ExternResult { + match self { + LinkTypes::AgentToUsernameAttestations => { + validate_create_link_agent_to_username_attestations( + action, + base_address, + target_address, + tag, + ) + } + LinkTypes::AgentMetadata => { + validate_create_link_user_metadata(action, base_address, target_address, tag) + } + LinkTypes::AgentToWalletAttestations => { + validate_create_link_agent_to_wallet_attestations( + action, + base_address, + target_address, + tag, + ) + } + LinkTypes::AgentToExternalIdAttestation => { + validate_create_link_agent_to_external_id_attestations( + action, + base_address, + target_address, + tag, + ) + } + LinkTypes::NameToOracleDocument => validate_create_link_name_to_oracle_document( + action, + base_address, + target_address, + tag, + ), + LinkTypes::RelateOracleDocumentName => { + validate_create_link_relate_oracle_document_name( + action, + base_address, + target_address, + tag, + ) + } + } + } + + pub fn validate_delete( + self, + action: DeleteLink, + original_action: CreateLink, + base_address: AnyLinkableHash, + target_address: AnyLinkableHash, + tag: LinkTag, + ) -> ExternResult { + match self { + LinkTypes::AgentToUsernameAttestations => { + validate_delete_link_agent_to_username_attestations( + action, + original_action, + base_address, + target_address, + tag, + ) + } + LinkTypes::AgentMetadata => validate_delete_link_user_metadata( + action, + original_action, + base_address, + target_address, + tag, + ), + LinkTypes::AgentToWalletAttestations => { + validate_delete_link_agent_to_wallet_attestations( + action, + original_action, + base_address, + target_address, + tag, + ) + } + LinkTypes::AgentToExternalIdAttestation => { + validate_delete_link_agent_to_external_id_attestations( + action, + original_action, + base_address, + target_address, + tag, + ) + } + LinkTypes::NameToOracleDocument => validate_delete_link_name_to_oracle_document( + action, + original_action, + base_address, + target_address, + tag, + ), + LinkTypes::RelateOracleDocumentName => { + validate_delete_link_relate_oracle_document_name( + action, + original_action, + base_address, + target_address, + tag, + ) + } + } + } +} diff --git a/crates/username_registry_utils/Cargo.toml b/crates/username_registry_utils/Cargo.toml new file mode 100644 index 0000000..756ca7a --- /dev/null +++ b/crates/username_registry_utils/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "username_registry_utils" +version = "0.0.1" +edition = "2021" + +[lib] +crate-type = ["cdylib", "rlib"] +name = "username_registry_utils" + +[dependencies] +hdi = { workspace = true } +holo_hash = { workspace = true } +serde = { workspace = true } +bincode = { workspace = true } +holoom_types = { workspace = true } +ed25519-dalek = { workspace = true } +bs58 = { workspace = true } +jaq_wrapper = { workspace = true } diff --git a/crates/username_registry_utils/src/lib.rs b/crates/username_registry_utils/src/lib.rs new file mode 100644 index 0000000..37765ca --- /dev/null +++ b/crates/username_registry_utils/src/lib.rs @@ -0,0 +1,24 @@ +use hdi::prelude::*; + +pub fn deserialize_record_entry(record: Record) -> ExternResult +where + O: TryFrom, +{ + let entry: O = record + .entry() + .to_app_option() + .map_err(|e| wasm_error!(e))? + .ok_or(wasm_error!(WasmErrorInner::Guest( + "Entry not present in Record".into() + )))?; + Ok(entry) +} + +pub fn hash_identifier(identifier: String) -> ExternResult { + #[derive(SerializedBytes, Serialize, Debug, Deserialize)] + struct SerializableIdentifier(String); + + let bytes = SerializedBytes::try_from(SerializableIdentifier(identifier)) + .map_err(|err| wasm_error!(err))?; + hash_entry(Entry::App(AppEntryBytes(bytes))) +} diff --git a/crates/username_registry_validation/Cargo.toml b/crates/username_registry_validation/Cargo.toml index 49a43b9..f7c0590 100644 --- a/crates/username_registry_validation/Cargo.toml +++ b/crates/username_registry_validation/Cargo.toml @@ -11,7 +11,10 @@ name = "username_registry_validation" hdi = { workspace = true } holo_hash = { workspace = true } serde = { workspace = true } +serde_json = { workspace = true } bincode = { workspace = true } holoom_types = { workspace = true } ed25519-dalek = { workspace = true } bs58 = { workspace = true } +jaq_wrapper = { workspace = true } +username_registry_utils = { workspace = true } diff --git a/crates/username_registry_validation/src/external_id_attestation.rs b/crates/username_registry_validation/src/external_id_attestation.rs index 8d05293..c9291a0 100644 --- a/crates/username_registry_validation/src/external_id_attestation.rs +++ b/crates/username_registry_validation/src/external_id_attestation.rs @@ -15,23 +15,3 @@ pub fn validate_create_external_id_attestation( Ok(ValidateCallbackResult::Valid) } - -pub fn validate_update_external_id_attestation( - _action: Update, - _external_id_attestation: ExternalIdAttestation, - _original_action: EntryCreationAction, - _original_external_id_attestation: ExternalIdAttestation, -) -> ExternResult { - Ok(ValidateCallbackResult::Invalid(String::from( - "External ID Attestations cannot be updated", - ))) -} -pub fn validate_delete_external_id_attestation( - _action: Delete, - _original_action: EntryCreationAction, - _original_external_id_attestation: ExternalIdAttestation, -) -> ExternResult { - Ok(ValidateCallbackResult::Invalid(String::from( - "External ID Attestations cannot be deleted", - ))) -} diff --git a/crates/username_registry_validation/src/jq_execution.rs b/crates/username_registry_validation/src/jq_execution.rs new file mode 100644 index 0000000..b1d2075 --- /dev/null +++ b/crates/username_registry_validation/src/jq_execution.rs @@ -0,0 +1,43 @@ +use hdi::prelude::*; +use holoom_types::{JqExecution, JqExecutionInput, OracleDocument, OracleDocumentListSnapshot}; +use jaq_wrapper::{compile_and_run_jq, JqProgramInput}; +use username_registry_utils::deserialize_record_entry; + +pub fn validate_create_jq_execution( + _action: EntryCreationAction, + jq_execution: JqExecution, +) -> ExternResult { + let input = match jq_execution.input { + JqExecutionInput::OracleDocument(oracle_document_ah) => { + let record = must_get_valid_record(oracle_document_ah)?; + let oracle_document: OracleDocument = deserialize_record_entry(record)?; + JqProgramInput::Single(oracle_document.json_data) + } + JqExecutionInput::OracleDocumentListSnapshot(snapshot_ah) => { + let record = must_get_valid_record(snapshot_ah)?; + let snapshot: OracleDocumentListSnapshot = deserialize_record_entry(record)?; + let jsons = snapshot + .resolved_documents + .into_iter() + .map(|oracle_document_ah| { + let record = must_get_valid_record(oracle_document_ah)?; + let oracle_document: OracleDocument = deserialize_record_entry(record)?; + Ok(oracle_document.json_data) + }) + .collect::>>()?; + JqProgramInput::Slurp(jsons) + } + JqExecutionInput::JqExecution(_) => { + todo!() + } + }; + + let output = compile_and_run_jq(&jq_execution.program, input)?; + if output != jq_execution.output { + return Ok(ValidateCallbackResult::Invalid( + "Program output doesn't match".into(), + )); + } + + Ok(ValidateCallbackResult::Valid) +} diff --git a/crates/username_registry_validation/src/lib.rs b/crates/username_registry_validation/src/lib.rs index c8cfa36..aec0c3c 100644 --- a/crates/username_registry_validation/src/lib.rs +++ b/crates/username_registry_validation/src/lib.rs @@ -4,6 +4,8 @@ pub mod wallet_attestation; pub use wallet_attestation::*; pub mod user_metadata; pub use user_metadata::*; +pub mod oracle_document; +pub use oracle_document::*; pub mod agent_username_attestation; pub use agent_username_attestation::*; pub mod agent_wallet_attestation; @@ -12,3 +14,11 @@ pub mod external_id_attestation; pub use external_id_attestation::*; pub mod agent_external_id_attestation; pub use agent_external_id_attestation::*; +pub mod name_oracle_document; +pub use name_oracle_document::*; +pub mod oracle_document_list_snapshot; +pub use oracle_document_list_snapshot::*; +pub mod jq_execution; +pub use jq_execution::*; +pub mod relate_oracle_document_name; +pub use relate_oracle_document_name::*; diff --git a/crates/username_registry_validation/src/name_oracle_document.rs b/crates/username_registry_validation/src/name_oracle_document.rs new file mode 100644 index 0000000..6436441 --- /dev/null +++ b/crates/username_registry_validation/src/name_oracle_document.rs @@ -0,0 +1,41 @@ +use hdi::prelude::*; +use holoom_types::OracleDocument; +use username_registry_utils::hash_identifier; + +pub fn validate_create_link_name_to_oracle_document( + _action: CreateLink, + base_address: AnyLinkableHash, + target_address: AnyLinkableHash, + _tag: LinkTag, +) -> ExternResult { + // Check the entry type for the given action hash + let action_hash = ActionHash::try_from(target_address).map_err(|e| wasm_error!(e))?; + let record = must_get_valid_record(action_hash)?; + let oracle_document: OracleDocument = record + .entry() + .to_app_option() + .map_err(|e| wasm_error!(e))? + .ok_or(wasm_error!(WasmErrorInner::Guest(String::from( + "Linked action must reference an entry" + ))))?; + + let expected_base_address = hash_identifier(oracle_document.name)?; + if AnyLinkableHash::from(expected_base_address) != base_address { + return Ok(ValidateCallbackResult::Invalid( + "OracleDocument name doesn't hash to base address".into(), + )); + } + + Ok(ValidateCallbackResult::Valid) +} +pub fn validate_delete_link_name_to_oracle_document( + _action: DeleteLink, + _original_action: CreateLink, + _base: AnyLinkableHash, + _target: AnyLinkableHash, + _tag: LinkTag, +) -> ExternResult { + Ok(ValidateCallbackResult::Invalid(String::from( + "OracleDocument links cannot be deleted", + ))) +} diff --git a/crates/username_registry_validation/src/oracle_document.rs b/crates/username_registry_validation/src/oracle_document.rs new file mode 100644 index 0000000..f69983f --- /dev/null +++ b/crates/username_registry_validation/src/oracle_document.rs @@ -0,0 +1,9 @@ +use hdi::prelude::*; +use holoom_types::OracleDocument; + +pub fn validate_create_oracle_document( + _action: EntryCreationAction, + _oracle_document: OracleDocument, +) -> ExternResult { + Ok(ValidateCallbackResult::Valid) +} diff --git a/crates/username_registry_validation/src/oracle_document_list_snapshot.rs b/crates/username_registry_validation/src/oracle_document_list_snapshot.rs new file mode 100644 index 0000000..4d2a487 --- /dev/null +++ b/crates/username_registry_validation/src/oracle_document_list_snapshot.rs @@ -0,0 +1,65 @@ +use hdi::prelude::*; +use holoom_types::{JqExecution, OracleDocument, OracleDocumentListSnapshot, SnapshotInput}; +use username_registry_utils::deserialize_record_entry; + +pub fn validate_create_oracle_document_list_snapshot( + _action: EntryCreationAction, + oracle_document_list_snapshot: OracleDocumentListSnapshot, +) -> ExternResult { + let (resolution_validity, identifiers) = + must_resolve_snapshot_input(oracle_document_list_snapshot.identifiers_input)?; + if resolution_validity != ValidateCallbackResult::Valid { + return Ok(resolution_validity); + } + + let resolved_documents = oracle_document_list_snapshot + .resolved_documents + .into_iter() + .map(|ah| { + let record = must_get_valid_record(ah)?; + let oracle_document: OracleDocument = deserialize_record_entry(record)?; + Ok(oracle_document) + }) + .collect::>>()?; + for (oracle_document, identifier) in resolved_documents.into_iter().zip(identifiers.into_iter()) + { + if oracle_document.name != identifier { + return Ok(ValidateCallbackResult::Invalid(format!( + "Oracle document identifier mismatch: '{}' != '{}'", + oracle_document.name, identifier + ))); + } + } + + Ok(ValidateCallbackResult::Valid) +} + +pub fn must_resolve_snapshot_input( + input: SnapshotInput, +) -> ExternResult<(ValidateCallbackResult, Vec)> { + // Must get the input document + let json_list = match input { + SnapshotInput::JqExecution(jq_execution_ah) => { + let record = must_get_valid_record(jq_execution_ah)?; + let jq_execution: JqExecution = deserialize_record_entry(record)?; + jq_execution.output + } + SnapshotInput::OracleDocument(oracle_document_ah) => { + let record = must_get_valid_record(oracle_document_ah)?; + let oracle_document: OracleDocument = deserialize_record_entry(record)?; + oracle_document.json_data + } + SnapshotInput::RelationSnapshot(identifiers) => { + return Ok((ValidateCallbackResult::Valid, identifiers)) + } + }; + + // Input document must be a list of identifiers + let Ok(identifiers) = serde_json::from_str::>(&json_list) else { + return Ok(( + ValidateCallbackResult::Invalid("SnapshotInput doesn't point to a list".into()), + Vec::default(), + )); + }; + Ok((ValidateCallbackResult::Valid, identifiers)) +} diff --git a/crates/username_registry_validation/src/relate_oracle_document_name.rs b/crates/username_registry_validation/src/relate_oracle_document_name.rs new file mode 100644 index 0000000..82fcceb --- /dev/null +++ b/crates/username_registry_validation/src/relate_oracle_document_name.rs @@ -0,0 +1,24 @@ +use hdi::prelude::*; + +pub fn validate_create_link_relate_oracle_document_name( + _action: CreateLink, + _base_address: AnyLinkableHash, + _target_address: AnyLinkableHash, + _tag: LinkTag, +) -> ExternResult { + // TODO: maybe check tag looks like a valid address? + // TODO: only authority? + + Ok(ValidateCallbackResult::Valid) +} +pub fn validate_delete_link_relate_oracle_document_name( + _action: DeleteLink, + _original_action: CreateLink, + _base_address: AnyLinkableHash, + _target_address: AnyLinkableHash, + _tag: LinkTag, +) -> ExternResult { + // TODO: only authority? + + Ok(ValidateCallbackResult::Valid) +} diff --git a/crates/username_registry_validation/src/username_attestation.rs b/crates/username_registry_validation/src/username_attestation.rs index aace545..d120160 100644 --- a/crates/username_registry_validation/src/username_attestation.rs +++ b/crates/username_registry_validation/src/username_attestation.rs @@ -89,23 +89,3 @@ pub fn validate_create_username_attestation( Ok(ValidateCallbackResult::Valid) } - -pub fn validate_update_username_attestation( - _action: Update, - _username_attestation: UsernameAttestation, - _original_action: EntryCreationAction, - _original_username_attestation: UsernameAttestation, -) -> ExternResult { - Ok(ValidateCallbackResult::Invalid(String::from( - "Username Attestations cannot be updated", - ))) -} -pub fn validate_delete_username_attestation( - _action: Delete, - _original_action: EntryCreationAction, - _original_username_attestation: UsernameAttestation, -) -> ExternResult { - Ok(ValidateCallbackResult::Invalid(String::from( - "Username Attestations cannot be deleted", - ))) -} diff --git a/crates/username_registry_validation/src/wallet_attestation.rs b/crates/username_registry_validation/src/wallet_attestation.rs index 17df729..5a01a56 100644 --- a/crates/username_registry_validation/src/wallet_attestation.rs +++ b/crates/username_registry_validation/src/wallet_attestation.rs @@ -107,23 +107,3 @@ pub fn validate_create_wallet_attestation( ), } } - -pub fn validate_update_wallet_attestation( - _action: Update, - _wallet_attestation: WalletAttestation, - _original_action: EntryCreationAction, - _original_wallet_attestation: WalletAttestation, -) -> ExternResult { - Ok(ValidateCallbackResult::Invalid(String::from( - "Wallet Attestations cannot be updated", - ))) -} -pub fn validate_delete_wallet_attestation( - _action: Delete, - _original_action: EntryCreationAction, - _original_wallet_attestation: WalletAttestation, -) -> ExternResult { - Ok(ValidateCallbackResult::Invalid(String::from( - "Wallet Attestations cannot be deleted", - ))) -} diff --git a/docker/mock-oracle/Dockerfile b/docker/mock-oracle/Dockerfile new file mode 100644 index 0000000..acc415f --- /dev/null +++ b/docker/mock-oracle/Dockerfile @@ -0,0 +1,14 @@ +FROM node:alpine +USER node +WORKDIR /home/node +COPY dist ./dist +COPY package.json . +ENV NODE_ENV=production +RUN npm i + +ENV PORT= +ENV HOLOCHAIN_HOST_NAME= +ENV HOLOCHAIN_ADMIN_WS_PORT= +ENV HOLOCHAIN_APP_WS_PORT= +ENV HOLOCHAIN_APP_ID= +CMD [ "npm", "start" ] diff --git a/package-lock.json b/package-lock.json index 4688d0d..f0a2b21 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1134,6 +1134,10 @@ "resolved": "packages/external-id-attestor", "link": true }, + "node_modules/@holoom/mock-oracle": { + "resolved": "packages/mock-oracle", + "link": true + }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1999,6 +2003,16 @@ "@babel/types": "^7.20.7" } }, + "node_modules/@types/body-parser": { + "version": "1.19.5", + "resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz", + "integrity": "sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg==", + "dev": true, + "dependencies": { + "@types/connect": "*", + "@types/node": "*" + } + }, "node_modules/@types/connect": { "version": "3.4.38", "resolved": "https://registry.npmjs.org/@types/connect/-/connect-3.4.38.tgz", @@ -2034,6 +2048,30 @@ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", "dev": true }, + "node_modules/@types/express": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.21.tgz", + "integrity": "sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==", + "dev": true, + "dependencies": { + "@types/body-parser": "*", + "@types/express-serve-static-core": "^4.17.33", + "@types/qs": "*", + "@types/serve-static": "*" + } + }, + "node_modules/@types/express-serve-static-core": { + "version": "4.19.3", + "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.3.tgz", + "integrity": "sha512-KOzM7MhcBFlmnlr/fzISFF5vGWVSvN6fTd4T+ExOt08bA/dA5kpSzY52nMsI1KDFmUREpJelPYyuslLRSjjgCg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "@types/qs": "*", + "@types/range-parser": "*", + "@types/send": "*" + } + }, "node_modules/@types/graceful-fs": { "version": "4.1.9", "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", @@ -2043,6 +2081,12 @@ "@types/node": "*" } }, + "node_modules/@types/http-errors": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.4.tgz", + "integrity": "sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA==", + "dev": true + }, "node_modules/@types/istanbul-lib-coverage": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz", @@ -2067,6 +2111,12 @@ "@types/istanbul-lib-report": "*" } }, + "node_modules/@types/mime": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/@types/mime/-/mime-1.3.5.tgz", + "integrity": "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w==", + "dev": true + }, "node_modules/@types/node": { "version": "20.12.11", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.11.tgz", @@ -2076,6 +2126,39 @@ "undici-types": "~5.26.4" } }, + "node_modules/@types/qs": { + "version": "6.9.15", + "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.15.tgz", + "integrity": "sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg==", + "dev": true + }, + "node_modules/@types/range-parser": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@types/range-parser/-/range-parser-1.2.7.tgz", + "integrity": "sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==", + "dev": true + }, + "node_modules/@types/send": { + "version": "0.17.4", + "resolved": "https://registry.npmjs.org/@types/send/-/send-0.17.4.tgz", + "integrity": "sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA==", + "dev": true, + "dependencies": { + "@types/mime": "^1", + "@types/node": "*" + } + }, + "node_modules/@types/serve-static": { + "version": "1.15.7", + "resolved": "https://registry.npmjs.org/@types/serve-static/-/serve-static-1.15.7.tgz", + "integrity": "sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw==", + "dev": true, + "dependencies": { + "@types/http-errors": "*", + "@types/node": "*", + "@types/send": "*" + } + }, "node_modules/@types/ssh2": { "version": "1.15.0", "resolved": "https://registry.npmjs.org/@types/ssh2/-/ssh2-1.15.0.tgz", @@ -2170,6 +2253,18 @@ } } }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/acorn": { "version": "8.11.3", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", @@ -2368,6 +2463,11 @@ "sprintf-js": "~1.0.2" } }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -2697,6 +2797,42 @@ "integrity": "sha512-eXRvHzWyYPBuB4NBy0cmYQjGitUrtqwbvlzP3G6VFnNRbsZQIxQ10PbKKHt8gZ/HW/D/747aDl+QkDqg3KQLMQ==", "dev": true }, + "node_modules/body-parser": { + "version": "1.20.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz", + "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==", + "dependencies": { + "bytes": "3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "on-finished": "2.4.1", + "qs": "6.11.0", + "raw-body": "2.5.2", + "type-is": "~1.6.18", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/body-parser/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/body-parser/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/borsh": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/borsh/-/borsh-0.7.0.tgz", @@ -2850,6 +2986,32 @@ "node": ">=0.10.0" } }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -3108,12 +3270,44 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true }, + "node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + }, "node_modules/core-util-is": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", @@ -3330,6 +3524,22 @@ "node": ">=0.10.0" } }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/degenerator": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", @@ -3366,6 +3576,23 @@ "node": ">=0.4.0" } }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -3480,6 +3707,11 @@ "url": "https://dotenvx.com" } }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + }, "node_modules/electron-to-chromium": { "version": "1.4.713", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.713.tgz", @@ -3503,6 +3735,14 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, + "node_modules/encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", @@ -3531,6 +3771,25 @@ "is-arrayish": "^0.2.1" } }, + "node_modules/es-define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "dependencies": { + "get-intrinsic": "^1.2.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/es6-promise": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz", @@ -3593,6 +3852,11 @@ "node": ">=6" } }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + }, "node_modules/escape-string-regexp": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz", @@ -3657,6 +3921,14 @@ "node": ">=0.10.0" } }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/eventemitter3": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz", @@ -3732,6 +4004,60 @@ "node": ">=16" } }, + "node_modules/express": { + "version": "4.19.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.19.2.tgz", + "integrity": "sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "1.20.2", + "content-disposition": "0.5.4", + "content-type": "~1.0.4", + "cookie": "0.6.0", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.2.0", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.7", + "qs": "6.11.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "0.18.0", + "serve-static": "1.15.0", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + } + }, + "node_modules/express/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/express/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", @@ -3833,6 +4159,36 @@ "node": ">=8" } }, + "node_modules/finalhandler": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", + "integrity": "sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "2.4.1", + "parseurl": "~1.3.3", + "statuses": "2.0.1", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/finalhandler/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/finalhandler/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, "node_modules/find-file-up": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/find-file-up/-/find-file-up-0.1.3.tgz", @@ -3928,6 +4284,22 @@ "node": ">= 6" } }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", @@ -3982,7 +4354,6 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4005,6 +4376,24 @@ "node": "6.* || 8.* || >= 10.*" } }, + "node_modules/get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/get-package-type": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", @@ -4135,6 +4524,17 @@ "node": ">=4" } }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -4150,11 +4550,43 @@ "node": ">=8" } }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.3.tgz", + "integrity": "sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -4180,6 +4612,21 @@ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==", "dev": true }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/http-proxy-agent": { "version": "7.0.2", "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", @@ -4226,6 +4673,17 @@ "ms": "^2.0.0" } }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -4318,8 +4776,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/ini": { "version": "1.3.8", @@ -4348,6 +4805,14 @@ "dev": true, "peer": true }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -5656,12 +6121,33 @@ "tmpl": "1.0.5" } }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/micromatch": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", @@ -5675,11 +6161,21 @@ "node": ">=8.6" } }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/mime-db": { "version": "1.52.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "dev": true, "engines": { "node": ">= 0.6" } @@ -5688,7 +6184,6 @@ "version": "2.1.35", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "dev": true, "dependencies": { "mime-db": "1.52.0" }, @@ -5754,8 +6249,7 @@ "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, "node_modules/nan": { "version": "2.19.0", @@ -5793,6 +6287,14 @@ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", "dev": true }, + "node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "engines": { + "node": ">= 0.6" + } + }, "node_modules/netmask": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", @@ -5943,6 +6445,25 @@ "node": ">=8" } }, + "node_modules/object-inspect": { + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz", + "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -6100,6 +6621,14 @@ "node": ">=0.10.0" } }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -6133,6 +6662,11 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", @@ -6303,6 +6837,18 @@ "url": "https://github.com/steveukx/properties?sponsor=1" } }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/proxy-agent": { "version": "6.4.0", "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.4.0.tgz", @@ -6455,12 +7001,48 @@ } ] }, + "node_modules/qs": { + "version": "6.11.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz", + "integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==", + "dependencies": { + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/queue-tick": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/queue-tick/-/queue-tick-1.0.1.tgz", "integrity": "sha512-kJt5qhMxoszgU/62PLP1CJytzd2NKetjSRnyuj31fDd3Rlcz3fzlFdFLD1SItunPwyqEOkca6GbV612BWfaBag==", "dev": true }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz", + "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.4.24", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", @@ -6690,8 +7272,7 @@ "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/semver": { "version": "6.3.1", @@ -6702,6 +7283,77 @@ "semver": "bin/semver.js" } }, + "node_modules/send": { + "version": "0.18.0", + "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", + "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "2.0.0", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "2.4.1", + "range-parser": "~1.2.1", + "statuses": "2.0.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/send/node_modules/debug/node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + }, + "node_modules/serve-static": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz", + "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==", + "dependencies": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "0.18.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -6732,6 +7384,23 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/side-channel": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "dependencies": { + "call-bind": "^1.0.7", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/signal-exit": { "version": "3.0.7", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", @@ -6939,6 +7608,14 @@ "node": ">=10" } }, + "node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/streamx": { "version": "2.16.1", "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.16.1.tgz", @@ -7170,6 +7847,14 @@ "node": ">=8.0" } }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "engines": { + "node": ">=0.6" + } + }, "node_modules/touch": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.0.tgz", @@ -7273,6 +7958,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/typescript": { "version": "5.4.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", @@ -7344,6 +8041,14 @@ "node": ">= 10.0.0" } }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/update-browserslist-db": { "version": "1.0.13", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", @@ -7400,6 +8105,14 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "dev": true }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "engines": { + "node": ">= 0.4.0" + } + }, "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", @@ -7429,6 +8142,14 @@ "node": ">=10.12.0" } }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "engines": { + "node": ">= 0.8" + } + }, "node_modules/viem": { "version": "2.8.13", "resolved": "https://registry.npmjs.org/viem/-/viem-2.8.13.tgz", @@ -7882,6 +8603,38 @@ "vite": "^5.1.6", "vite-plugin-dts": "^3.7.3" } + }, + "packages/json-oracle": { + "name": "@holoom/json-oracle", + "version": "0.0.0", + "extraneous": true, + "dependencies": { + "@holochain/client": "^0.16.7", + "dotenv": "^16.4.5", + "express": "^4.19.2" + }, + "devDependencies": { + "@types/node": "^20.12.7", + "nodemon": "^3.1.0", + "ts-node": "^10.9.2", + "typescript": "^5.4.5" + } + }, + "packages/mock-oracle": { + "name": "@holoom/mock-oracle", + "version": "0.0.0", + "dependencies": { + "@holochain/client": "^0.16.7", + "dotenv": "^16.4.5", + "express": "^4.19.2" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "@types/node": "^20.12.7", + "nodemon": "^3.1.0", + "ts-node": "^10.9.2", + "typescript": "^5.4.5" + } } } } diff --git a/package.json b/package.json index 28ce247..6337c06 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "test:dna": "npm run build:dna && cargo nextest run -j 1", "build:client": "npm run build -w @holoom/client", "build:external-id-attestor": "npm run build -w @holoom/external-id-attestor", - "build:docker": "npm run build:dna && npm run build:external-id-attestor && scripts/build_docker_images.sh", + "build:mock-oracle": "npm run build -w @holoom/mock-oracle", + "build:docker": "npm run build:dna && npm run build:external-id-attestor && npm run build:mock-oracle && scripts/build_docker_images.sh", "test:e2e": "npm run build:docker && npm run build:client && npm run e2e -w @holoom/e2e" }, "devDependencies": { diff --git a/packages/client/src/holoom-client.ts b/packages/client/src/holoom-client.ts index 4103961..7c2ae1d 100644 --- a/packages/client/src/holoom-client.ts +++ b/packages/client/src/holoom-client.ts @@ -4,6 +4,7 @@ import { BoundWallet, ChainWalletSignature_Evm, ChainWalletSignature_Solana, + JqExecution, UsernameAttestation, WalletAttestation, } from "./types"; @@ -159,4 +160,17 @@ export class HoloomClient { } }); } + + async refreshJq(arg: { + program: string; + input: { collection: string }; + }): Promise { + const record: Record = await this.appAgent.callZome({ + role_name: "holoom", + zome_name: "username_registry", + fn_name: "refresh_jq_execution_for_named_relation", + payload: { program: arg.program, relation_name: arg.input.collection }, + }); + return JSON.parse(decodeAppEntry(record).output); + } } diff --git a/packages/client/src/types.ts b/packages/client/src/types.ts index 6d6f637..733b43a 100644 --- a/packages/client/src/types.ts +++ b/packages/client/src/types.ts @@ -44,6 +44,12 @@ export type BoundWallet_Solana = { }; export type BoundWallet = BoundWallet_Evm | BoundWallet_Solana; +export interface JqExecution { + program: string; + input: unknown; + output: string; +} + export interface ExternalIdAttestation { request_id: string; internal_pubkey: AgentPubKey; diff --git a/packages/e2e/src/main.ts b/packages/e2e/src/main.ts index 549336b..25ec545 100644 --- a/packages/e2e/src/main.ts +++ b/packages/e2e/src/main.ts @@ -6,7 +6,6 @@ import { ExternalIdAttestationRequestorClient, } from "@holoom/client"; import WebSdkApi, { ChaperoneState } from "@holo-host/web-sdk"; -import { ExternalIdAttestation } from "@holoom/client/dist/types"; function untilSignedIn(holoClient: WebSdkApi) { return new Promise((resolve) => { @@ -26,6 +25,7 @@ function untilSignedIn(holoClient: WebSdkApi) { const global = window as any; async function createClients() { + console.log("creating clients"); const holo = await WebSdkApi.connect({ chaperoneUrl: "http://localhost:24274", }); @@ -62,5 +62,5 @@ global.clientsProm = createClients() global.clients = clients; }) .catch((err) => { - console.error("\n\nAuth callback failed\n\n", err); + console.error("Failed to create clients", err); }); diff --git a/packages/e2e/tests/evm-wallet-binding.test.js b/packages/e2e/tests/evm-wallet-binding.test.js index f0f6178..237679d 100644 --- a/packages/e2e/tests/evm-wallet-binding.test.js +++ b/packages/e2e/tests/evm-wallet-binding.test.js @@ -6,10 +6,10 @@ const { rocketFetch } = require("./utils/rocket"); describe("EVM Wallet Binding", () => { let testContainers; beforeEach(async () => { - testContainers = await startTestContainers(); + testContainers = await startTestContainers({ rocket: true }); }, 60_000); afterEach(async () => { - await Promise.all([testContainers.stop(), jestPuppeteer.resetPage()]); + await Promise.all([testContainers?.stop(), jestPuppeteer.resetPage()]); }); it("should register only one username", async () => { diff --git a/packages/e2e/tests/external-id.test.js b/packages/e2e/tests/external-id.test.js index b8f792f..b0d48fb 100644 --- a/packages/e2e/tests/external-id.test.js +++ b/packages/e2e/tests/external-id.test.js @@ -4,10 +4,10 @@ const { loadPageAndRegister } = require("./utils/holo"); describe("external-id", () => { let testContainers; beforeEach(async () => { - testContainers = await startTestContainers(true); + testContainers = await startTestContainers({ externalId: true }); }, 60_000); afterEach(async () => { - await Promise.all([testContainers.stop(), jestPuppeteer.resetPage()]); + await Promise.all([testContainers?.stop(), jestPuppeteer.resetPage()]); }); it("should bind a valid access token", async () => { diff --git a/packages/e2e/tests/metadata.test.js b/packages/e2e/tests/metadata.test.js index 54da23a..d1f9114 100644 --- a/packages/e2e/tests/metadata.test.js +++ b/packages/e2e/tests/metadata.test.js @@ -5,10 +5,10 @@ const { rocketFetch } = require("./utils/rocket"); describe("metadata", () => { let testContainers; beforeEach(async () => { - testContainers = await startTestContainers(); + testContainers = await startTestContainers({ rocket: true }); }, 60_000); afterEach(async () => { - await Promise.all([testContainers.stop(), jestPuppeteer.resetPage()]); + await Promise.all([testContainers?.stop(), jestPuppeteer.resetPage()]); }); it("Should manage metadata like key-value store", async () => { diff --git a/packages/e2e/tests/oracle.test.js b/packages/e2e/tests/oracle.test.js new file mode 100644 index 0000000..2de43c6 --- /dev/null +++ b/packages/e2e/tests/oracle.test.js @@ -0,0 +1,77 @@ +const { startTestContainers } = require("./utils/testcontainers"); +const { loadPageAndRegister } = require("./utils/holo"); +const { postOracleWebhook } = require("./utils/oracle"); + +describe("metadata", () => { + let testContainers; + beforeEach(async () => { + testContainers = await startTestContainers({ oracle: true }); + }, 60_000); + afterEach(async () => { + await Promise.all([testContainers?.stop(), jestPuppeteer.resetPage()]); + }); + + it("Should aggregate from attested json", async () => { + debug("Started test"); + await loadPageAndRegister("test@test.com", "test1234"); + debug("Loaded chaperone and registered agent"); + + // Check the jq initially interprets the collection as empty + await expect( + page.evaluate(() => + clients.holoom.refreshJq({ + program: "[.[].name]", + input: { collection: "tournaments" }, + }) + ) + ).resolves.toEqual([]); + debug("Tested empty collection"); + + await postOracleWebhook("tournament_created", { + id: "tournament-1", + name: "super-rank", + }); + await postOracleWebhook("tournament_created", { + id: "tournament-2", + name: "mega-rank", + }); + debug("Published two oracle documents"); + + // Poll until consistency + while (true) { + const output = await page.evaluate(() => + clients.holoom.refreshJq({ + program: "[.[].name]", + input: { collection: "tournaments" }, + }) + ); + if (output.length === 2) { + expect(output).toEqual(["super-rank", "mega-rank"]); + break; + } + await new Promise((r) => setTimeout(r, 200)); + } + debug("Polled jq program until oracle documents included"); + + await postOracleWebhook("tournament_updated", { + id: "tournament-2", + name: "uber-rank", + }); + + // Poll until consistency + while (true) { + const output = await page.evaluate(() => + clients.holoom.refreshJq({ + program: "[.[].name]", + input: { collection: "tournaments" }, + }) + ); + if (output[1] !== "mega-rank") { + expect(output).toEqual(["super-rank", "uber-rank"]); + break; + } + await new Promise((r) => setTimeout(r, 200)); + } + debug("Polled jq program until revised oracle document included"); + }, 120_000); +}); diff --git a/packages/e2e/tests/username.test.js b/packages/e2e/tests/username.test.js index 0802318..93ff74d 100644 --- a/packages/e2e/tests/username.test.js +++ b/packages/e2e/tests/username.test.js @@ -5,10 +5,10 @@ const { rocketFetch } = require("./utils/rocket"); describe("username", () => { let testContainers; beforeEach(async () => { - testContainers = await startTestContainers(); + testContainers = await startTestContainers({ rocket: true }); }, 60_000); afterEach(async () => { - await Promise.all([testContainers.stop(), jestPuppeteer.resetPage()]); + await Promise.all([testContainers?.stop(), jestPuppeteer.resetPage()]); }); it("should register only one username", async () => { diff --git a/packages/e2e/tests/utils/oracle.js b/packages/e2e/tests/utils/oracle.js new file mode 100644 index 0000000..df859ec --- /dev/null +++ b/packages/e2e/tests/utils/oracle.js @@ -0,0 +1,7 @@ +module.exports.postOracleWebhook = async function (type, data) { + return await fetch("http://localhost:8001/webhook-ingress", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ type, data }), + }); +}; diff --git a/packages/e2e/tests/utils/testcontainers.js b/packages/e2e/tests/utils/testcontainers.js index 40dfb3b..f0468e1 100644 --- a/packages/e2e/tests/utils/testcontainers.js +++ b/packages/e2e/tests/utils/testcontainers.js @@ -4,6 +4,13 @@ const createDebug = require("debug"); const BOOTSTRAP_PORT = 51804; const SIGNAL_PORT = 51805; +const logConsumer = (name) => (stream) => { + const logInfo = createDebug(`e2e:${name}:info`); + const logErr = createDebug(`e2e:${name}:error`); + stream.on("data", logInfo); + stream.on("err", logErr); +}; + function startLocalServicesContainer(network) { return new GenericContainer("holoom/local-services") .withNetwork(network) @@ -11,6 +18,7 @@ function startLocalServicesContainer(network) { BOOTSTRAP_PORT, SIGNAL_PORT, }) + .withLogConsumer(logConsumer("local-services")) .withCommand("/run.sh") .start(); } @@ -26,12 +34,7 @@ function startAuthorityContainer(network, localServicesIp) { APP_WS_PORT_INTERNAL: 3335, APP_WS_PORT_EXPOSED: 3336, }) - .withLogConsumer((stream) => { - const logInfo = createDebug("e2e:authority:info"); - const logErr = createDebug("e2e:authority:error"); - stream.on("data", logInfo); - stream.on("err", logErr); - }) + .withLogConsumer(logConsumer("authority")) .withCommand("/run.sh") .start(); } @@ -47,6 +50,7 @@ function startHoloContainer(network, localServicesIp) { BOOTSTRAP_SERVER: `http://${localServicesIp}:${BOOTSTRAP_PORT}`, SIGNAL_SERVER: `ws://${localServicesIp}:${SIGNAL_PORT}`, }) + .withLogConsumer(logConsumer("holo-dev-server")) .withCommand("/run.sh") .start(); } @@ -64,12 +68,7 @@ function startRocketContainer(network, authorityIp) { HOLOCHAIN_APP_ID: "holoom", HOLOCHAIN_CELL_ROLES: "holoom", }) - .withLogConsumer((stream) => { - const logInfo = createDebug("e2e:rocket:info"); - const logErr = createDebug("e2e:rocket:error"); - stream.on("data", logInfo); - stream.on("err", logErr); - }) + .withLogConsumer(logConsumer("rocket")) .withCommand("/usr/local/bin/holoom_rocket_server") .start(); } @@ -89,12 +88,7 @@ function startExternalIdAttestorContainer(network, authorityIp, mockAuthIp) { AUTH_EXTERNAL_ID_FIELD_NAME: "guid", AUTH_DISPLAY_NAME_FIELD_NAME: "nickname", }) - .withLogConsumer((stream) => { - const logInfo = createDebug("e2e:external-id-attestor:info"); - const logErr = createDebug("e2e:external-id-attestor:error"); - stream.on("data", logInfo); - stream.on("err", logErr); - }) + .withLogConsumer(logConsumer("external-id-attestor")) .withCommand(["npm", "start"]) .start(); } @@ -103,56 +97,102 @@ function startMockAuthContainer(network) { return new GenericContainer("holoom/mock-auth") .withNetwork(network) .withExposedPorts({ host: 3002, container: 3002 }) + .withEnvironment({ PORT: "3002" }) + .withLogConsumer(logConsumer("mock-auth")) + .withCommand(["node", "index.js"]) + .start(); +} + +function startMockOracleContainer(network, authorityIp) { + return new GenericContainer("holoom/mock-oracle") + .withNetwork(network) + .withExposedPorts({ host: 8001, container: 8001 }) .withEnvironment({ - PORT: "3002", + PORT: "8001", + HOLOCHAIN_HOST_NAME: authorityIp, + HOLOCHAIN_ADMIN_WS_PORT: 3334, + HOLOCHAIN_APP_WS_PORT: 3336, + HOLOCHAIN_APP_ID: "holoom", }) - .withCommand(["node", "index.js"]) + .withLogConsumer(logConsumer("mock-oracle")) + .withCommand(["npm", "start"]) .start(); } -module.exports.startTestContainers = async ( - includeExternalIdAttestorAndMockAuth = false -) => { +module.exports.startTestContainers = async (opts = {}) => { debug("Begin test container setup"); - network = await new Network().start(); - debug("Network created"); - localServicesContainer = await startLocalServicesContainer(network); - const localServiceIp = localServicesContainer.getIpAddress(network.getName()); - debug("Started local-services"); - // The next two containers only depend on local-services, and can be - // loaded in parallel. - const authorityContainerProm = startAuthorityContainer( - network, - localServiceIp + const network = await new Network().start(); + const localServicesProm = startLocalServicesContainer(network); + const localServicesIpProm = localServicesProm.then((localServices) => + localServices.getIpAddress(network.getName()) + ); + const authorityProm = localServicesIpProm.then(async (localServicesIp) => { + const authority = startAuthorityContainer(network, localServicesIp); + await new Promise((r) => setTimeout(r, 5_000)); + return authority; + }); + const holoProm = localServicesIpProm.then(async (localServicesIp) => { + await new Promise((r) => setTimeout(r, 10_000)); + return startHoloContainer(network, localServicesIp); + }); + const authorityIpProm = authorityProm.then((authority) => + authority.getIpAddress(network.getName()) ); - const holoContainerProm = startHoloContainer(network, localServiceIp); - authorityContainer = await authorityContainerProm; - const authorityIp = authorityContainer.getIpAddress(network.getName()); - const rocketContainer = await startRocketContainer(network, authorityIp); - const mockAuthContainer = includeExternalIdAttestorAndMockAuth - ? await startMockAuthContainer(network) - : null; - const mockAuthIp = mockAuthContainer?.getIpAddress(network.getName()); - const externalIdAttestorContainer = includeExternalIdAttestorAndMockAuth - ? await startExternalIdAttestorContainer(network, authorityIp, mockAuthIp) - : null; - holoContainer = await holoContainerProm; - debug("Started authority-agent-sandbox and holo-dev-server"); - debug("Finished test container setup"); + + const containerProms = [localServicesProm, authorityProm, holoProm]; + + if (opts.rocket) { + containerProms.push( + authorityIpProm.then((authorityIp) => + startRocketContainer(network, authorityIp) + ) + ); + } + + if (opts.externalId) { + const mockAuthProm = startMockAuthContainer(network); + const externalIdAttestorProm = Promise.all([ + mockAuthProm, + authorityIpProm, + ]).then(([mockAuth, authorityIp]) => { + const mockAuthIp = mockAuth.getIpAddress(network.getName()); + return startExternalIdAttestorContainer(network, authorityIp, mockAuthIp); + }); + containerProms.push(mockAuthProm, externalIdAttestorProm); + } + + if (opts.oracle) { + containerProms.push( + authorityIpProm.then((authorityIp) => + startMockOracleContainer(network, authorityIp) + ) + ); + } + + const startedContainers = []; + const failures = []; + for (prom of containerProms) { + try { + startedContainers.push(await prom); + } catch (err) { + failures.push(err); + } + } const stop = async () => { debug("Begin test container teardown"); - await Promise.all([ - localServicesContainer.stop(), - authorityContainer.stop(), - holoContainer.stop(), - rocketContainer.stop(), - externalIdAttestorContainer?.stop() ?? Promise.resolve(), - mockAuthContainer?.stop() ?? Promise.resolve(), - ]); + await Promise.all(startedContainers.map((container) => container.stop())); await network.stop(); debug("Finished test container teardown"); }; + if (failures.length) { + debug(`${failures.length} container(s) failed to start`); + await stop(); + throw failures[0]; + } + + debug("Test container setup finished"); + return { stop }; }; diff --git a/packages/external-id-attestor/src/index.ts b/packages/external-id-attestor/src/index.ts index 8982a01..d271530 100644 --- a/packages/external-id-attestor/src/index.ts +++ b/packages/external-id-attestor/src/index.ts @@ -1,8 +1,8 @@ import dotenv from "dotenv"; import { AdminWebsocket, AppAgentWebsocket } from "@holochain/client"; -// import { AccessTokenAssessor, ExternalIdAttestorClient } from "@holoom/client"; import { AccessTokenAssessor } from "./access-token-assessor.js"; import { ExternalIdAttestorClient } from "./external-id-attestor-client.js"; + async function main() { dotenv.config(); @@ -19,7 +19,6 @@ async function main() { const adminWebsocket = await AdminWebsocket.connect( new URL(`ws://${hostName}:${getEnv("HOLOCHAIN_ADMIN_WS_PORT")}`) ); - const _ = await adminWebsocket.listApps({}); // Is this necessary? const cellIds = await adminWebsocket.listCellIds(); await adminWebsocket.authorizeSigningCredentials(cellIds[0]); diff --git a/packages/mock-oracle/package.json b/packages/mock-oracle/package.json new file mode 100644 index 0000000..27697f9 --- /dev/null +++ b/packages/mock-oracle/package.json @@ -0,0 +1,23 @@ +{ + "name": "@holoom/mock-oracle", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "build": "npx tsc", + "start": "node dist/index.js", + "dev": "nodemon src/index.ts" + }, + "dependencies": { + "@holochain/client": "^0.16.7", + "dotenv": "^16.4.5", + "express": "^4.19.2" + }, + "devDependencies": { + "@types/express": "^4.17.21", + "@types/node": "^20.12.7", + "nodemon": "^3.1.0", + "ts-node": "^10.9.2", + "typescript": "^5.4.5" + } +} diff --git a/packages/mock-oracle/src/index.ts b/packages/mock-oracle/src/index.ts new file mode 100644 index 0000000..88d6159 --- /dev/null +++ b/packages/mock-oracle/src/index.ts @@ -0,0 +1,104 @@ +import dotenv from "dotenv"; +import { AdminWebsocket, AppAgentWebsocket } from "@holochain/client"; +import express, { Request, Response } from "express"; + +async function main() { + dotenv.config(); + + const getEnv = (name: string) => { + const value = process.env[name]; + if (!value) { + throw new Error(`${name} env var not defined`); + } + return value; + }; + + const hostName = getEnv("HOLOCHAIN_HOST_NAME"); + + const adminWebsocket = await AdminWebsocket.connect( + new URL(`ws://${hostName}:${getEnv("HOLOCHAIN_ADMIN_WS_PORT")}`) + ); + const cellIds = await adminWebsocket.listCellIds(); + await adminWebsocket.authorizeSigningCredentials(cellIds[0]); + + const appAgentClient = await AppAgentWebsocket.connect( + new URL(`ws://${hostName}:${getEnv("HOLOCHAIN_APP_WS_PORT")}`), + getEnv("HOLOCHAIN_APP_ID") + ); + + const app = express(); + app.use(express.json()); + + app.post("/webhook-ingress", async (req: Request, res: Response) => { + console.log("Incoming webhook invocation"); + const payload = req.body; + if (!("type" in payload) || !("data" in payload)) { + res.status(400).send(); + } + const identifier = getIdentifier(payload.type)(payload.data); + const relations = getRelations(payload.type)(payload.data); + await appAgentClient.callZome({ + role_name: "holoom", + zome_name: "username_registry", + fn_name: "create_oracle_document", + payload: { + name: identifier, + json_data: JSON.stringify(payload.data), + }, + }); + console.log("Created oracle document"); + for (const relation of relations) { + await appAgentClient.callZome({ + role_name: "holoom", + zome_name: "username_registry", + fn_name: "relate_oracle_document", + payload: { + relation, + identifier, + name: identifier, + }, + }); + console.log("Added oracle document relation"); + } + res.status(200).send(); + }); + + const PORT = getEnv("PORT"); + app.listen(PORT, () => { + console.log(`Listening on port ${PORT}`); + }); +} + +const getIdentifier = (type: string) => { + switch (type) { + case "match_created": + case "match_updated": + case "match_finished": + return (data: { id: string }) => `match/${data.id}`; + case "tournament_created": + case "tournament_updated": + case "tournament_finished": + return (data: { id: string }) => `tournament/${data.id}`; + default: + throw new Error("Unknown event type"); + } +}; + +const getRelations = (type: string) => { + switch (type) { + case "match_created": + case "match_updated": + case "match_finished": + return (data: { tournament_id: string }) => [ + `tournament/${data.tournament_id}/match`, + ]; + case "tournament_created": + case "tournament_updated": + case "tournament_finished": + return () => ["tournaments"]; + default: + throw new Error("Unknown event type"); + } +}; + +main().catch((err) => console.error(err)); diff --git a/packages/mock-oracle/tsconfig.json b/packages/mock-oracle/tsconfig.json new file mode 100644 index 0000000..fba8bb8 --- /dev/null +++ b/packages/mock-oracle/tsconfig.json @@ -0,0 +1,109 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */, + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "NodeNext" /* Specify what module code is generated. */, + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./dist" /* Specify an output folder for all emitted files. */, + // "removeComments": true, /* Disable emitting comments. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */, + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, + + /* Type Checking */ + "strict": true /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + } +} diff --git a/scripts/build_docker_images.sh b/scripts/build_docker_images.sh index 6903fa0..9424cc6 100755 --- a/scripts/build_docker_images.sh +++ b/scripts/build_docker_images.sh @@ -11,6 +11,10 @@ docker build --target authority-agent-sandbox -t holoom/authority-agent-sandbox docker build --target holo-dev-server -t holoom/holo-dev-server docker/misc_hc docker build -t holoom/mock-auth docker/mock-auth +docker build \ + -t holoom/mock-oracle \ + -f docker/mock-oracle/Dockerfile \ + packages/mock-oracle # context docker build \ -t holoom/external-id-attestor \